Aleksander Machniak
2013-10-17 197203727417a03d87053a47e5aa5175a76e3e0b
program/include/rcube_imap.php
@@ -32,7 +32,6 @@
 */
class rcube_imap
{
    public $debug_level = 1;
    public $skip_deleted = false;
    public $page_size = 10;
    public $list_page = 1;
@@ -318,6 +317,19 @@
    /**
     * Activate/deactivate debug mode
     *
     * @param boolean $dbg True if IMAP conversation should be logged
     * @access public
     */
    function set_debug($dbg = true)
    {
        $this->options['debug'] = $dbg;
        $this->conn->setDebug($dbg, array($this, 'debug_handler'));
    }
    /**
     * Set default message charset
     *
     * This will be used for message decoding if a charset specification is not available
@@ -466,7 +478,7 @@
     */
    function get_mailbox_name()
    {
        return $this->conn->connected() ? $this->mailbox : '';
        return $this->mailbox;
    }
@@ -675,8 +687,8 @@
    {
        $mode = strtoupper($mode);
        // count search set
        if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) {
        // count search set, assume search set is always up-to-date (don't check $force flag)
        if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS')) {
            if ($this->search_threads)
                return $mode == 'ALL' ? count((array)$this->search_set['depth']) : count((array)$this->search_set['tree']);
            else
@@ -1361,6 +1373,11 @@
        $this->_messagecount($mailbox, 'ALL', true);
        $result = 0;
        if (empty($old)) {
            return $result;
        }
        $new = $this->get_folder_stats($mailbox);
        // got new messages
@@ -1619,6 +1636,7 @@
     * @param  string  $sort_field Header field to sort by
     * @return array   search results as list of message IDs
     * @access public
     * @todo: Search criteria should be provided in non-IMAP format, eg. array
     */
    function search($mailbox='', $str=NULL, $charset=NULL, $sort_field=NULL)
    {
@@ -1704,7 +1722,7 @@
            // Error, try with US-ASCII (some servers may support only US-ASCII)
            if ($a_messages === false && $charset && $charset != 'US-ASCII')
                $a_messages = $this->conn->search($mailbox,
                    'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset));
                    $this->convert_criteria($criteria, $charset));
            // I didn't found that SEARCH should return sorted IDs
            if (is_array($a_messages) && !$this->sort_field)
@@ -1758,9 +1776,9 @@
                $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
                $string = substr($str, $string_offset - 1, $m[0]);
                $string = rcube_charset_convert($string, $charset, $dest_charset);
                if (!$string)
                if ($string === false)
                    continue;
                $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string);
                $res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
                $last = $m[0] + $string_offset - 1;
            }
            if ($last < strlen($str))
@@ -2987,14 +3005,14 @@
    /**
     * Private method for mailbox listing
     * Private method for mailbox listing (LSUB)
     *
     * @param   string  $root   Optional root folder
     * @param   string  $name   Optional name pattern
     * @param   mixed   $filter Optional filter
     * @param   string  $rights Optional ACL requirements
     *
     * @return  array   List of mailboxes/folders
     * @return  array   List of subscribed folders
     * @see     rcube_imap::list_mailboxes()
     * @access  private
     */
@@ -3022,9 +3040,10 @@
                    NULL, array('SUBSCRIBED'));
                // unsubscribe non-existent folders, remove from the list
                if (is_array($a_folders) && $name == '*') {
                // we can do this only when LIST response is available
                if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
                    foreach ($a_folders as $idx => $folder) {
                        if ($this->conn->data['LIST'] && ($opts = $this->conn->data['LIST'][$folder])
                        if (($opts = $this->conn->data['LIST'][$folder])
                            && in_array('\\NonExistent', $opts)
                        ) {
                            $this->conn->unsubscribe($folder);
@@ -3037,11 +3056,12 @@
            else {
                $a_folders = $this->conn->listSubscribed($root, $name);
                // unsubscribe non-existent folders, remove from the list
                if (is_array($a_folders) && $name == '*') {
                // unsubscribe non-existent folders, remove them from the list,
                // we can do this only when LIST response is available
                if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) {
                    foreach ($a_folders as $idx => $folder) {
                        if ($this->conn->data['LIST'] && ($opts = $this->conn->data['LIST'][$folder])
                            && in_array('\\Noselect', $opts)
                        if (!isset($this->conn->data['LIST'][$folder])
                            || in_array('\\Noselect', $this->conn->data['LIST'][$folder])
                        ) {
                            // Some servers returns \Noselect for existing folders
                            if (!$this->mailbox_exists($folder)) {
@@ -3075,7 +3095,19 @@
     */
    function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
    {
        // @TODO: caching
        $cache_key = $root.':'.$name;
        if (!empty($filter)) {
            $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
        }
        $cache_key .= ':'.$rights;
        $cache_key = 'mailboxes.list.'.md5($cache_key);
        // get cached folder list
        $a_mboxes = $this->get_cache($cache_key);
        if (is_array($a_mboxes)) {
            return $a_mboxes;
        }
        // Give plugins a chance to provide a list of mailboxes
        $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
            array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
@@ -3085,7 +3117,7 @@
        }
        else {
            // retrieve list of folders from IMAP server
            $a_mboxes = $this->conn->listMailboxes($root, $name);
            $a_mboxes = $this->_list_unsubscribed($root, $name);
        }
        if (!is_array($a_mboxes)) {
@@ -3095,6 +3127,11 @@
        // INBOX should always be available
        if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
            array_unshift($a_mboxes, 'INBOX');
        }
        // cache folder attributes
        if ($root == '' && $name == '*' && empty($filter)) {
            $this->update_cache('mailboxes.attributes', $this->conn->data['LIST']);
        }
        // filter folders list according to rights requirements
@@ -3107,7 +3144,74 @@
            $a_mboxes = $this->_sort_mailbox_list($a_mboxes);
        }
        // write mailboxlist to cache
        $this->update_cache($cache_key, $a_mboxes);
        return $a_mboxes;
    }
    /**
     * Private method for mailbox listing (LIST)
     *
     * @param   string  $root   Optional root folder
     * @param   string  $name   Optional name pattern
     *
     * @return  array   List of folders
     * @see     rcube_imap::list_unsubscribed()
     */
    private function _list_unsubscribed($root='', $name='*')
    {
        $result = $this->conn->listMailboxes($root, $name);
        if (!is_array($result)) {
            return array();
        }
        // #1486796: some server configurations doesn't
        // return folders in all namespaces, we'll try to detect that situation
        // and ask for these namespaces separately
        if ($root == '' && $name == '*') {
            $delim     = $this->get_hierarchy_delimiter();
            $namespace = $this->get_namespace();
            $search    = array();
            // build list of namespace prefixes
            foreach ((array)$namespace as $ns) {
                if (is_array($ns)) {
                    foreach ($ns as $ns_data) {
                        if (strlen($ns_data[0])) {
                            $search[] = $ns_data[0];
                        }
                    }
                }
            }
            if (!empty($search)) {
                // go through all folders detecting namespace usage
                foreach ($result as $folder) {
                    foreach ($search as $idx => $prefix) {
                        if (strpos($folder, $prefix) === 0) {
                            unset($search[$idx]);
                        }
                    }
                    if (empty($search)) {
                        break;
                    }
                }
                // get folders in hidden namespaces and add to the result
                foreach ($search as $prefix) {
                    $list = $this->conn->listMailboxes($prefix, $name);
                    if (!empty($list)) {
                        $result = array_merge($result, $list);
                    }
                }
            }
        }
        return $result;
    }
@@ -3383,8 +3487,8 @@
        foreach ($this->namespace as $type => $namespace) {
            if (is_array($namespace)) {
                foreach ($namespace as $ns) {
                    if (strlen($ns[0])) {
                        if ((strlen($ns[0])>1 && $mailbox == substr($ns[0], 0, -1))
                    if ($len = strlen($ns[0])) {
                        if (($len > 1 && $mailbox == substr($ns[0], 0, -1))
                            || strpos($mailbox, $ns[0]) === 0
                        ) {
                            return $type;
@@ -3438,30 +3542,29 @@
    /**
     * Gets folder options from LIST response, e.g. \Noselect, \Noinferiors
     * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors
     *
     * @param string $mailbox Folder name
     * @param bool   $force   Set to True if options should be refreshed
     *                        Options are available after LIST command only
     * @param bool   $force   Set to True if attributes should be refreshed
     *
     * @return array Options list
     */
    function mailbox_options($mailbox, $force=false)
    function mailbox_attributes($mailbox, $force=false)
    {
        if ($mailbox == 'INBOX') {
            return array();
        // get attributes directly from LIST command
        if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$mailbox])) {
            $opts = $this->conn->data['LIST'][$mailbox];
        }
        // get cached folder attributes
        else if (!$force) {
            $opts = $this->get_cache('mailboxes.attributes');
            $opts = $opts[$mailbox];
        }
        if (!is_array($this->conn->data['LIST']) || !is_array($this->conn->data['LIST'][$mailbox])) {
            if ($force) {
                $this->conn->listMailboxes('', $mailbox);
            }
            else {
                return array();
            }
        if (!is_array($opts)) {
            $this->conn->listMailboxes('', $mailbox);
            $opts = $this->conn->data['LIST'][$mailbox];
        }
        $opts = $this->conn->data['LIST'][$mailbox];
        return is_array($opts) ? $opts : array();
    }
@@ -3544,17 +3647,17 @@
            }
        }
        $options['name']      = $mailbox;
        $options['options']   = $this->mailbox_options($mailbox, true);
        $options['namespace'] = $this->mailbox_namespace($mailbox);
        $options['rights']    = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array();
        $options['special']   = in_array($mailbox, $this->default_folders);
        $options['name']       = $mailbox;
        $options['attributes'] = $this->mailbox_attributes($mailbox, true);
        $options['namespace']  = $this->mailbox_namespace($mailbox);
        $options['rights']     = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array();
        $options['special']    = in_array($mailbox, $this->default_folders);
        // Set 'noselect' and 'norename' flags
        if (is_array($options['options'])) {
            foreach ($options['options'] as $opt) {
                $opt = strtolower($opt);
                if ($opt == '\noselect' || $opt == '\nonexistent') {
        // Set 'noselect' flag
        if (is_array($options['attributes'])) {
            foreach ($options['attributes'] as $attrib) {
                $attrib = strtolower($attrib);
                if ($attrib == '\noselect' || $attrib == '\nonexistent') {
                    $options['noselect'] = true;
                }
            }
@@ -3563,6 +3666,15 @@
            $options['noselect'] = true;
        }
        // Get folder rights (MYRIGHTS)
        if ($acl && !$options['noselect']) {
            // skip shared roots
            if (!$options['is_root'] || $options['namespace'] == 'personal') {
                $options['rights'] =  (array)$this->my_rights($mailbox);
            }
        }
        // Set 'norename' flag
        if (!empty($options['rights'])) {
            $options['norename'] = !in_array('x', $options['rights']) && !in_array('d', $options['rights']);
@@ -4062,7 +4174,7 @@
        $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
        // encoded-word regexp
        $re = '/=\?([^?]+)\?([BbQq])\?([^?\n]*)\?=/';
        $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
        // Find all RFC2047's encoded words
        if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {