Aleksander Machniak
2013-10-14 c1bc8f6c827a27540b5510b42dcc65b39d38f2c1
program/lib/Roundcube/rcube_imap.php
@@ -2,8 +2,6 @@
/*
 +-----------------------------------------------------------------------+
 | program/include/rcube_imap.php                                        |
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
 | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -14,13 +12,11 @@
 |                                                                       |
 | PURPOSE:                                                              |
 |   IMAP Storage Engine                                                 |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
 | Author: Aleksander Machniak <alec@alec.pl>                            |
 +-----------------------------------------------------------------------+
*/
/**
 * Interface class for accessing an IMAP server
@@ -74,7 +70,7 @@
    protected $search_sort_field = '';
    protected $search_threads = false;
    protected $search_sorted = false;
    protected $options = array('auth_method' => 'check');
    protected $options = array('auth_type' => 'check');
    protected $caching = false;
    protected $messages_caching = false;
    protected $threading = false;
@@ -141,17 +137,17 @@
            $this->set_debug(true);
            $this->options['ident'] = array(
                'name' => 'Roundcube Webmail',
                'version' => RCMAIL_VERSION,
                'php' => PHP_VERSION,
                'os' => PHP_OS,
                'name'    => 'Roundcube',
                'version' => RCUBE_VERSION,
                'php'     => PHP_VERSION,
                'os'      => PHP_OS,
                'command' => $_SERVER['REQUEST_URI'],
            );
        }
        $attempt = 0;
        do {
            $data = rcube::get_instance()->plugins->exec_hook('imap_connect',
            $data = rcube::get_instance()->plugins->exec_hook('storage_connect',
                array_merge($this->options, array('host' => $host, 'user' => $user,
                    'attempt' => ++$attempt)));
@@ -312,14 +308,7 @@
     */
    public function set_folder($folder)
    {
        if ($this->folder == $folder) {
            return;
        }
        $this->folder = $folder;
        // clear messagecount cache for this folder
        $this->clear_messagecount($folder);
    }
@@ -402,10 +391,10 @@
    public function check_permflag($flag)
    {
        $flag       = strtoupper($flag);
        $imap_flag  = $this->conn->flags[$flag];
        $perm_flags = $this->get_permflags($this->folder);
        $imap_flag  = $this->conn->flags[$flag];
        return in_array_nocase($imap_flag, $perm_flags);
        return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags);
    }
@@ -421,17 +410,7 @@
        if (!strlen($folder)) {
            return array();
        }
/*
        Checking PERMANENTFLAGS is rather rare, so we disable caching of it
        Re-think when we'll use it for more than only MDNSENT flag
        $cache_key = 'mailboxes.permanentflags.' . $folder;
        $permflags = $this->get_cache($cache_key);
        if ($permflags !== null) {
            return explode(' ', $permflags);
        }
*/
        if (!$this->check_connection()) {
            return array();
        }
@@ -446,10 +425,7 @@
        if (!is_array($permflags)) {
            $permflags = array();
        }
/*
        // Store permflags as string to limit cached object size
        $this->update_cache($cache_key, implode(' ', $permflags));
*/
        return $permflags;
    }
@@ -571,7 +547,7 @@
     * Get message count for a specific folder
     *
     * @param  string  $folder  Folder name
     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
     * @param  string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
     * @param  boolean $force   Force reading from server and update cache
     * @param  boolean $status  Enables storing folder status info (max UID/count),
     *                          required for folder_status()
@@ -592,7 +568,7 @@
     * protected method for getting nr of messages
     *
     * @param string  $folder  Folder name
     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT]
     * @param string  $mode    Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS]
     * @param boolean $force   Force reading from server and update cache
     * @param boolean $status  Enables storing folder status info (max UID/count),
     *                         required for folder_status()
@@ -614,6 +590,10 @@
            }
        }
        // EXISTS is a special alias for ALL, it allows to get the number
        // of all messages in a folder also when search is active and with
        // any skip_deleted setting
        $a_folder_cache = $this->get_cache('messagecount');
        // return cached value
@@ -626,7 +606,7 @@
        }
        if ($mode == 'THREADS') {
            $res   = $this->fetch_threads($folder, $force);
            $res   = $this->threads($folder);
            $count = $res->count();
            if ($status) {
@@ -644,7 +624,7 @@
            $count = $this->conn->countRecent($folder);
        }
        // use SEARCH for message counting
        else if (!empty($this->options['skip_deleted'])) {
        else if ($mode != 'EXISTS' && !empty($this->options['skip_deleted'])) {
            $search_str = "ALL UNDELETED";
            $keys       = array('COUNT');
@@ -656,11 +636,11 @@
                    $keys[] = 'ALL';
                }
                if ($status) {
                    $keys[]   = 'MAX';
                    $keys[] = 'MAX';
                }
            }
            // @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
            // @TODO: if $mode == 'ALL' we could try to use cache index here
            // get message count using (E)SEARCH
            // not very performant but more precise (using UNDELETED)
@@ -683,8 +663,8 @@
            }
            else {
                $count = $this->conn->countMessages($folder);
                if ($status) {
                    $this->set_folder_stats($folder,'cnt', $count);
                if ($status && $mode == 'ALL') {
                    $this->set_folder_stats($folder, 'cnt', $count);
                    $this->set_folder_stats($folder, 'maxuid', $count ? $this->id2uid($count, $folder) : 0);
                }
            }
@@ -791,7 +771,7 @@
            $threads = $mcache->get_thread($folder);
        }
        else {
            $threads = $this->fetch_threads($folder);
            $threads = $this->threads($folder);
        }
        return $this->fetch_thread_headers($folder, $threads, $page, $slice);
@@ -800,32 +780,47 @@
    /**
     * Method for fetching threads data
     *
     * @param  string $folder  Folder name
     * @param  bool   $force   Use IMAP server, no cache
     * @param  string $folder Folder name
     *
     * @return rcube_imap_thread Thread data object
     */
    function fetch_threads($folder, $force = false)
    function threads($folder)
    {
        if (!$force && ($mcache = $this->get_mcache_engine())) {
        if ($mcache = $this->get_mcache_engine()) {
            // don't store in self's internal cache, cache has it's own internal cache
            return $mcache->get_thread($folder);
        }
        if (empty($this->icache['threads'])) {
            if (!$this->check_connection()) {
                return new rcube_result_thread();
        if (!empty($this->icache['threads'])) {
            if ($this->icache['threads']->get_parameters('MAILBOX') == $folder) {
                return $this->icache['threads'];
            }
            // get all threads
            $result = $this->conn->thread($folder, $this->threading,
                $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
            // add to internal (fast) cache
            $this->icache['threads'] = $result;
        }
        return $this->icache['threads'];
        // get all threads
        $result = $this->threads_direct($folder);
        // add to internal (fast) cache
        return $this->icache['threads'] = $result;
    }
    /**
     * Method for direct fetching of threads data
     *
     * @param  string $folder Folder name
     *
     * @return rcube_imap_thread Thread data object
     */
    function threads_direct($folder)
    {
        if (!$this->check_connection()) {
            return new rcube_result_thread();
        }
        // get all threads
        return $this->conn->thread($folder, $this->threading,
            $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
    }
@@ -981,7 +976,7 @@
            // use memory less expensive (and quick) method for big result set
            $index = clone $this->index('', $this->sort_field, $this->sort_order);
            // get messages uids for one page...
            $index->slice($start_msg, min($cnt-$from, $this->page_size));
            $index->slice($from, min($cnt-$from, $this->page_size));
            if ($slice) {
                $index->slice(-$slice, $slice);
@@ -1096,16 +1091,17 @@
    /**
     * Returns current status of folder
     * Returns current status of a folder (compared to the last time use)
     *
     * We compare the maximum UID to determine the number of
     * new messages because the RECENT flag is not reliable.
     *
     * @param string $folder Folder name
     * @param array  $diff   Difference data
     *
     * @return int   Folder status
     * @return int Folder status
     */
    public function folder_status($folder = null)
    public function folder_status($folder = null, &$diff = array())
    {
        if (!strlen($folder)) {
            $folder = $this->folder;
@@ -1126,6 +1122,9 @@
        // got new messages
        if ($new['maxuid'] > $old['maxuid']) {
            $result += 1;
            // get new message UIDs range, that can be used for example
            // to get the data of these messages
            $diff['new'] = ($old['maxuid'] + 1 < $new['maxuid'] ? ($old['maxuid']+1).':' : '') . $new['maxuid'];
        }
        // some messages has been deleted
        if ($new['cnt'] < $old['cnt']) {
@@ -1176,12 +1175,15 @@
     * @param string $folder     Folder to get index from
     * @param string $sort_field Sort column
     * @param string $sort_order Sort order [ASC, DESC]
     * @param bool   $no_threads Get not threaded index
     * @param bool   $no_search  Get index not limited to search result (optionally)
     *
     * @return rcube_result_index|rcube_result_thread List of messages (UIDs)
     */
    public function index($folder = '', $sort_field = NULL, $sort_order = NULL)
    {
        if ($this->threading) {
    public function index($folder = '', $sort_field = NULL, $sort_order = NULL,
        $no_threads = false, $no_search = false
    ) {
        if (!$no_threads && $this->threading) {
            return $this->thread_index($folder, $sort_field, $sort_order);
        }
@@ -1193,43 +1195,50 @@
        // we have a saved search result, get index from there
        if ($this->search_string) {
            if ($this->search_threads) {
                $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
            if ($this->search_set->is_empty()) {
                return new rcube_result_index($folder, '* SORT');
            }
            // use message index sort as default sorting
            if (!$this->sort_field || $this->search_sorted) {
                if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
                    $this->search($folder, $this->search_string, $this->search_charset, $this->sort_field);
                }
            // search result is an index with the same sorting?
            if (($this->search_set instanceof rcube_result_index)
                && ((!$this->sort_field && !$this->search_sorted) ||
                    ($this->search_sorted && $this->search_sort_field == $this->sort_field))
            ) {
                $index = $this->search_set;
            }
            else if (!$this->check_connection()) {
                return new rcube_result_index();
            }
            else {
                $index = $this->conn->index($folder, $this->search_set->get(),
                    $this->sort_field, $this->options['skip_deleted'], true, true);
            // $no_search is enabled when we are not interested in
            // fetching index for search result, e.g. to sort
            // threaded search result we can use full mailbox index.
            // This makes possible to use index from cache
            else if (!$no_search) {
                if (!$this->sort_field) {
                    // No sorting needed, just build index from the search result
                    // @TODO: do we need to sort by UID here?
                    $search = $this->search_set->get_compressed();
                    $index  = new rcube_result_index($folder, '* ESEARCH ALL ' . $search);
                }
                else {
                    $index = $this->index_direct($folder, $this->search_charset,
                        $this->sort_field, $this->search_set);
                }
            }
            if ($this->sort_order != $index->get_parameters('ORDER')) {
                $index->revert();
            }
            if (isset($index)) {
                if ($this->sort_order != $index->get_parameters('ORDER')) {
                    $index->revert();
                }
            return $index;
                return $index;
            }
        }
        // check local cache
        if ($mcache = $this->get_mcache_engine()) {
            $index = $mcache->get_index($folder, $this->sort_field, $this->sort_order);
        }
        // fetch from IMAP server
        else {
            $index = $this->index_direct(
                $folder, $this->sort_field, $this->sort_order);
            return $mcache->get_index($folder, $this->sort_field, $this->sort_order);
        }
        return $index;
        // fetch from IMAP server
        return $this->index_direct($folder, $this->sort_field, $this->sort_order);
    }
@@ -1237,22 +1246,24 @@
     * Return sorted list of message UIDs ignoring current search settings.
     * Doesn't uses cache by default.
     *
     * @param string $folder     Folder to get index from
     * @param string $sort_field Sort column
     * @param string $sort_order Sort order [ASC, DESC]
     * @param bool   $skip_cache Disables cache usage
     * @param string         $folder     Folder to get index from
     * @param string         $sort_field Sort column
     * @param string         $sort_order Sort order [ASC, DESC]
     * @param rcube_result_* $search     Optional messages set to limit the result
     *
     * @return rcube_result_index Sorted list of message UIDs
     */
    public function index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
    public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null)
    {
        if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
            $index = $mcache->get_index($folder, $sort_field, $sort_order);
        if (!empty($search)) {
            $search = $this->search_set->get_compressed();
        }
        // use message index sort as default sorting
        else if (!$sort_field) {
        if (!$sort_field) {
            // use search result from count() if possible
            if ($this->options['skip_deleted'] && !empty($this->icache['undeleted_idx'])
            if (empty($search) && $this->options['skip_deleted']
                && !empty($this->icache['undeleted_idx'])
                && $this->icache['undeleted_idx']->get_parameters('ALL') !== null
                && $this->icache['undeleted_idx']->get_parameters('MAILBOX') == $folder
            ) {
@@ -1262,8 +1273,12 @@
                return new rcube_result_index();
            }
            else {
                $index = $this->conn->search($folder,
                    'ALL' .($this->options['skip_deleted'] ? ' UNDELETED' : ''), true);
                $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
                if ($search) {
                    $query = trim($query . ' UID ' . $search);
                }
                $index = $this->conn->search($folder, $query, true);
            }
        }
        else if (!$this->check_connection()) {
@@ -1272,13 +1287,18 @@
        // fetch complete message index
        else {
            if ($this->get_capability('SORT')) {
                $index = $this->conn->sort($folder, $sort_field,
                    $this->options['skip_deleted'] ? 'UNDELETED' : '', true);
                $query = $this->options['skip_deleted'] ? 'UNDELETED' : '';
                if ($search) {
                    $query = trim($query . ' UID ' . $search);
                }
                $index = $this->conn->sort($folder, $sort_field, $query, true);
            }
            if (empty($index) || $index->is_error()) {
                $index = $this->conn->index($folder, "1:*", $sort_field,
                    $this->options['skip_deleted'], false, true);
                $index = $this->conn->index($folder, $search ? $search : "1:*",
                    $sort_field, $this->options['skip_deleted'],
                    $search ? true : false, true);
            }
        }
@@ -1311,7 +1331,7 @@
        }
        else {
            // get all threads (default sort order)
            $threads = $this->fetch_threads($folder);
            $threads = $this->threads($folder);
        }
        $this->set_sort_order($sort_field, $sort_order);
@@ -1322,9 +1342,10 @@
    /**
     * Sort threaded result, using THREAD=REFS method
     * Sort threaded result, using THREAD=REFS method if available.
     * If not, use any method and re-sort the result in THREAD=REFS way.
     *
     * @param rcube_result_thread $threads  Threads result set
     * @param rcube_result_thread $threads Threads result set
     */
    protected function sort_threads($threads)
    {
@@ -1336,17 +1357,16 @@
        // THREAD=REFERENCES:     sorting by sent date of root message
        // THREAD=REFS:           sorting by the most recent date in each thread
        if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
            $index = $this->index_direct($this->folder, $this->sort_field, $this->sort_order, false);
        if ($this->threading != 'REFS' || ($this->sort_field && $this->sort_field != 'date')) {
            $sortby = $this->sort_field ? $this->sort_field : 'date';
            $index  = $this->index($this->folder, $sortby, $this->sort_order, true, true);
            if (!$index->is_empty()) {
                $threads->sort($index);
            }
        }
        else {
            if ($this->sort_order != $threads->get_parameters('ORDER')) {
                $threads->revert();
            }
        else if ($this->sort_order != $threads->get_parameters('ORDER')) {
            $threads->revert();
        }
    }
@@ -1419,8 +1439,6 @@
     */
    protected function search_index($folder, $criteria='ALL', $charset=NULL, $sort_field=NULL)
    {
        $orig_criteria = $criteria;
        if (!$this->check_connection()) {
            if ($this->threading) {
                return new rcube_result_thread();
@@ -1634,9 +1652,15 @@
        // Example of structure for malformed MIME message:
        // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
        if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
            && strtolower($structure[0].'/'.$structure[1]) == 'text/plain'
        ) {
            // A special known case "Content-type: text" (#1488968)
            if ($headers->ctype == 'text') {
                $structure[1]   = 'plain';
                $headers->ctype = 'text/plain';
            }
            // we can handle single-part messages, by simple fix in structure (#1486898)
            if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
            else if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
                $structure[0] = $m[1];
                $structure[1] = $m[2];
            }
@@ -1660,11 +1684,21 @@
            $struct = $this->structure_part($structure, 0, '', $headers);
        }
        // don't trust given content-type
        if (empty($struct->parts) && !empty($headers->ctype)) {
            $struct->mime_id = '1';
            $struct->mimetype = strtolower($headers->ctype);
            list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
        // some workarounds on simple messages...
        if (empty($struct->parts)) {
            // ...don't trust given content-type
            if (!empty($headers->ctype)) {
                $struct->mime_id  = '1';
                $struct->mimetype = strtolower($headers->ctype);
                list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
            }
            // ...and charset (there's a case described in #1488968 where invalid content-type
            // results in invalid charset in BODYSTRUCTURE)
            if (!empty($headers->charset) && $headers->charset != $struct->ctype_parameters['charset']) {
                $struct->charset                     = $headers->charset;
                $struct->ctype_parameters['charset'] = $headers->charset;
            }
        }
        $headers->structure = $struct;
@@ -2045,16 +2079,18 @@
    /**
     * Fetch message body of a specific message from the server
     *
     * @param  int                $uid    Message UID
     * @param  string             $part   Part number
     * @param  rcube_message_part $o_part Part object created by get_structure()
     * @param  mixed              $print  True to print part, ressource to write part contents in
     * @param  resource           $fp     File pointer to save the message part
     * @param  boolean            $skip_charset_conv Disables charset conversion
     * @param int                Message UID
     * @param string             Part number
     * @param rcube_message_part Part object created by get_structure()
     * @param mixed              True to print part, resource to write part contents in
     * @param resource           File pointer to save the message part
     * @param boolean            Disables charset conversion
     * @param int                Only read this number of bytes
     * @param boolean            Enables formatting of text/* parts bodies
     *
     * @return string Message/part body if not printed
     */
    public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
    public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true)
    {
        if (!$this->check_connection()) {
            return null;
@@ -2073,8 +2109,9 @@
        }
        if ($o_part && $o_part->size) {
            $formatted = $formatted && $o_part->ctype_primary == 'text';
            $body = $this->conn->handlePartBody($this->folder, $uid, true,
                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $o_part->ctype_primary == 'text');
                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp, $formatted, $max_bytes);
        }
        if ($fp || $print) {
@@ -2188,10 +2225,10 @@
            $result = $this->conn->flag($folder, $uids, $flag);
        }
        if ($result) {
        if ($result && !$skip_cache) {
            // reload message headers if cached
            // @TODO: update flags instead removing from cache
            if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
            // update flags instead removing from cache
            if ($mcache = $this->get_mcache_engine()) {
                $status = strpos($flag, 'UN') !== 0;
                $mflag  = preg_replace('/^UN/', '', $flag);
                $mcache->change_flag($folder, $all_mode ? null : explode(',', $uids),
@@ -2203,8 +2240,12 @@
                $this->clear_messagecount($folder, 'SEEN');
                $this->clear_messagecount($folder, 'UNSEEN');
            }
            else if ($flag == 'DELETED') {
            else if ($flag == 'DELETED' || $flag == 'UNDELETED') {
                $this->clear_messagecount($folder, 'DELETED');
                // remove cached messages
                if ($this->options['skip_deleted']) {
                    $this->clear_message_cache($folder, $all_mode ? null : explode(',', $uids));
                }
            }
        }
@@ -2215,16 +2256,18 @@
    /**
     * Append a mail message (source) to a specific folder
     *
     * @param string  $folder  Target folder
     * @param string  $message The message source string or filename
     * @param string  $headers Headers string if $message contains only the body
     * @param boolean $is_file True if $message is a filename
     * @param array   $flags   Message flags
     * @param mixed   $date    Message internal date
     * @param string       $folder  Target folder
     * @param string|array $message The message source string or filename
     *                              or array (of strings and file pointers)
     * @param string       $headers Headers string if $message contains only the body
     * @param boolean      $is_file True if $message is a filename
     * @param array        $flags   Message flags
     * @param mixed        $date    Message internal date
     * @param bool         $binary  Enables BINARY append
     *
     * @return int|bool Appended message UID or True on success, False on error
     */
    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null)
    public function save_message($folder, &$message, $headers='', $is_file=false, $flags = array(), $date = null, $binary = false)
    {
        if (!strlen($folder)) {
            $folder = $this->folder;
@@ -2242,10 +2285,10 @@
        $date = $this->date_format($date);
        if ($is_file) {
            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date);
            $saved = $this->conn->appendFromFile($folder, $message, $headers, $flags, $date, $binary);
        }
        else {
            $saved = $this->conn->append($folder, $message, $flags, $date);
            $saved = $this->conn->append($folder, $message, $flags, $date, $binary);
        }
        if ($saved) {
@@ -2311,10 +2354,7 @@
        // move messages
        $moved = $this->conn->move($uids, $from_mbox, $to_mbox);
        // send expunge command in order to have the moved message
        // really deleted from the source folder
        if ($moved) {
            $this->expunge_message($uids, $from_mbox, false);
            $this->clear_messagecount($from_mbox);
            $this->clear_messagecount($to_mbox);
        }
@@ -2616,7 +2656,6 @@
        if ($list_extended) {
            // unsubscribe non-existent folders, remove 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 (($opts = $this->conn->data['LIST'][$folder])
@@ -2629,19 +2668,14 @@
            }
        }
        else {
            // 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 (!isset($this->conn->data['LIST'][$folder])
                        || in_array('\\Noselect', $this->conn->data['LIST'][$folder])
                    ) {
                        // Some servers returns \Noselect for existing folders
                        if (!$this->folder_exists($folder)) {
                            $this->conn->unsubscribe($folder);
                            unset($a_folders[$idx]);
                        }
                    }
            // unsubscribe non-existent folders, remove them from the list
            if (is_array($a_folders) && !empty($a_folders) && $name == '*') {
                $existing    = $this->list_folders($root, $name);
                $nonexisting = array_diff($a_folders, $existing);
                $a_folders   = array_diff($a_folders, $nonexisting);
                foreach ($nonexisting as $folder) {
                    $this->conn->unsubscribe($folder);
                }
            }
        }
@@ -2704,7 +2738,7 @@
        // filter folders list according to rights requirements
        if ($rights && $this->get_capability('ACL')) {
            $a_folders = $this->filter_rights($a_folders, $rights);
            $a_mboxes = $this->filter_rights($a_mboxes, $rights);
        }
        // filter folders and sort them
@@ -2760,7 +2794,6 @@
     */
    private function list_folders_update(&$result, $type = null)
    {
        $delim     = $this->get_hierarchy_delimiter();
        $namespace = $this->get_namespace();
        $search    = array();
@@ -3349,7 +3382,6 @@
    {
        if (!empty($this->options['fetch_headers'])) {
            $headers = explode(' ', $this->options['fetch_headers']);
            $headers = array_map('strtoupper', $headers);
        }
        else {
            $headers = array();
@@ -3359,7 +3391,7 @@
            $headers = array_merge($headers, $this->all_headers);
        }
        return implode(' ', array_unique($headers));
        return $headers;
    }
@@ -3672,7 +3704,7 @@
    {
        if ($this->caching && !$this->cache) {
            $rcube = rcube::get_instance();
            $ttl = $rcube->config->get('message_cache_lifetime', '10d');
            $ttl   = $rcube->config->get('imap_cache_ttl', '10d');
            $this->cache = $rcube->get_cache('IMAP', $this->caching, $ttl);
        }
@@ -3720,21 +3752,6 @@
        }
    }
    /**
     * Delete outdated cache entries
     */
    public function expunge_cache()
    {
        if ($this->mcache) {
            $ttl = rcube::get_instance()->config->get('message_cache_lifetime', '10d');
            $this->mcache->expunge($ttl);
        }
        if ($this->cache) {
            $this->cache->expunge();
        }
    }
    /* --------------------------------
     *   message caching methods
@@ -3743,12 +3760,17 @@
    /**
     * Enable or disable messages caching
     *
     * @param boolean $set Flag
     * @param boolean $set  Flag
     * @param int     $mode Cache mode
     */
    public function set_messages_caching($set)
    public function set_messages_caching($set, $mode = null)
    {
        if ($set) {
            $this->messages_caching = true;
            if ($mode && ($cache = $this->get_mcache_engine())) {
                $cache->set_mode($mode);
            }
        }
        else {
            if ($this->mcache) {
@@ -3768,8 +3790,10 @@
        if ($this->messages_caching && !$this->mcache) {
            $rcube = rcube::get_instance();
            if (($dbh = $rcube->get_dbh()) && ($userid = $rcube->get_user_id())) {
                $ttl       = $rcube->config->get('messages_cache_ttl', '10d');
                $threshold = $rcube->config->get('messages_cache_threshold', 50);
                $this->mcache = new rcube_imap_cache(
                    $dbh, $this, $userid, $this->options['skip_deleted']);
                    $dbh, $this, $userid, $this->options['skip_deleted'], $ttl, $threshold);
            }
        }
@@ -3781,13 +3805,22 @@
     * Clears the messages cache.
     *
     * @param string $folder Folder name
     * @param array  $uids    Optional message UIDs to remove from cache
     * @param array  $uids   Optional message UIDs to remove from cache
     */
    protected function clear_message_cache($folder = null, $uids = null)
    {
        if ($mcache = $this->get_mcache_engine()) {
            $mcache->clear($folder, $uids);
        }
    }
    /**
     * Delete outdated cache entries
     */
    function cache_gc()
    {
        rcube_imap_cache::gc();
    }
@@ -3824,7 +3857,7 @@
        $delimiter = $this->get_hierarchy_delimiter();
        // find default folders and skip folders starting with '.'
        foreach ($a_folders as $i => $folder) {
        foreach ($a_folders as $folder) {
            if ($folder[0] == '.') {
                continue;
            }
@@ -4084,9 +4117,9 @@
        return $this->index($folder, $sort_field, $sort_order);
    }
    public function message_index_direct($folder, $sort_field = null, $sort_order = null, $skip_cache = true)
    public function message_index_direct($folder, $sort_field = null, $sort_order = null)
    {
        return $this->index_direct($folder, $sort_field, $sort_order, $skip_cache);
        return $this->index_direct($folder, $sort_field, $sort_order);
    }
    public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)