From 19a61851ae7bc9492504c4884db8a53afa5d06c5 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Wed, 29 Jul 2015 14:39:07 -0400 Subject: [PATCH] Fix so imap folder attribute comparisons are case-insensitive (#1490466) + make in_array_nocase() much faster for ASCII strings --- program/lib/Roundcube/rcube_imap.php | 127 ++++++++++++++++++++++++++++-------------- 1 files changed, 85 insertions(+), 42 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 78073ab..6f58fdc 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -56,6 +56,7 @@ */ protected $icache = array(); + protected $plugins; protected $list_page = 1; protected $delimiter; protected $namespace; @@ -82,6 +83,7 @@ public function __construct() { $this->conn = new rcube_imap_generic(); + $this->plugins = rcube::get_instance()->plugins; // Set namespace and delimiter from session, // so some methods would work before connection @@ -110,13 +112,13 @@ /** * Connect to an IMAP server * - * @param string $host Host to connect - * @param string $user Username for IMAP account - * @param string $pass Password for IMAP account - * @param integer $port Port to connect to - * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection + * @param string $host Host to connect + * @param string $user Username for IMAP account + * @param string $pass Password for IMAP account + * @param integer $port Port to connect to + * @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection * - * @return boolean TRUE on success, FALSE on failure + * @return boolean True on success, False on failure */ public function connect($host, $user, $pass, $port=143, $use_ssl=null) { @@ -147,7 +149,7 @@ $attempt = 0; do { - $data = rcube::get_instance()->plugins->exec_hook('storage_connect', + $data = $this->plugins->exec_hook('storage_connect', array_merge($this->options, array('host' => $host, 'user' => $user, 'attempt' => ++$attempt))); @@ -170,8 +172,20 @@ $this->connect_done = true; if ($this->conn->connected()) { + // check for session identifier + $session = null; + if (preg_match('/\s+SESSIONID=([^=\s]+)/', $this->conn->result, $m)) { + $session = $m[1]; + } + // get namespace and delimiter $this->set_env(); + + // trigger post-connect hook + $this->plugins->exec_hook('storage_connected', array( + 'host' => $host, 'user' => $user, 'session' => $session + )); + return true; } // write error log @@ -761,7 +775,7 @@ $page = $page ? $page : $this->list_page; // use saved message set - if ($this->search_string && $folder == $this->folder) { + if ($this->search_string) { return $this->list_search_messages($folder, $page, $slice); } @@ -1370,7 +1384,7 @@ public function index_direct($folder, $sort_field = null, $sort_order = null, $search = null) { if (!empty($search)) { - $search = $this->search_set->get_compressed(); + $search = $search->get_compressed(); } // use message index sort as default sorting @@ -1506,7 +1520,7 @@ $folder = $this->folder; } - $plugin = rcube::get_instance()->plugins->exec_hook('imap_search_before', array( + $plugin = $this->plugins->exec_hook('imap_search_before', array( 'folder' => $folder, 'search' => $search, 'charset' => $charset, @@ -2501,7 +2515,7 @@ // increase messagecount of the target folder $this->set_messagecount($folder, 'ALL', 1); - rcube::get_instance()->plugins->exec_hook('message_saved', array( + $this->plugins->exec_hook('message_saved', array( 'folder' => $folder, 'message' => $message, 'headers' => $headers, @@ -2777,7 +2791,7 @@ } // Give plugins a chance to provide a list of folders - $data = rcube::get_instance()->plugins->exec_hook('storage_folders', + $data = $this->plugins->exec_hook('storage_folders', array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB')); if (isset($data['folders'])) { @@ -2857,7 +2871,7 @@ if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { foreach ($a_folders as $idx => $folder) { if (($opts = $this->conn->data['LIST'][$folder]) - && in_array('\\NonExistent', $opts) + && in_array_nocase('\\NonExistent', $opts) ) { $this->conn->unsubscribe($folder); unset($a_folders[$idx]); @@ -2909,7 +2923,7 @@ } // Give plugins a chance to provide a list of folders - $data = rcube::get_instance()->plugins->exec_hook('storage_folders', + $data = $this->plugins->exec_hook('storage_folders', array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST')); if (isset($data['folders'])) { @@ -2990,7 +3004,7 @@ * @param array $result Reference to folders list * @param string $type Listing type (ext-subscribed, subscribed or all) */ - private function list_folders_update(&$result, $type = null) + protected function list_folders_update(&$result, $type = null) { $namespace = $this->get_namespace(); $search = array(); @@ -3067,14 +3081,15 @@ /** * Get mailbox quota information - * added by Nuny + * + * @param string $folder Folder name * * @return mixed Quota info or False if not supported */ - public function get_quota() + public function get_quota($folder = null) { if ($this->get_capability('QUOTA') && $this->check_connection()) { - return $this->conn->getQuota(); + return $this->conn->getQuota($folder); } return false; @@ -3149,6 +3164,16 @@ } $result = $this->conn->createFolder($folder, $type ? array("\\" . ucfirst($type)) : null); + + // it's quite often situation that we're trying to create and subscribe + // a folder that already exist, but is unsubscribed + if (!$result) { + if ($this->get_response_code() == rcube_storage::ALREADYEXISTS + || preg_match('/already exists/i', $this->get_error_str()) + ) { + $result = true; + } + } // try to subscribe it if ($result) { @@ -3297,12 +3322,14 @@ // request \Subscribed flag in LIST response as performance improvement for folder_exists() $folders = $this->conn->listMailboxes('', '*', array('SUBSCRIBED'), array('SPECIAL-USE')); - foreach ($folders as $folder) { - if ($flags = $this->conn->data['LIST'][$folder]) { - foreach ($types as $type) { - if (in_array($type, $flags)) { - $type = strtolower(substr($type, 1)); - $special[$type] = $folder; + if (!empty($folders)) { + foreach ($folders as $folder) { + if ($flags = $this->conn->data['LIST'][$folder]) { + foreach ($types as $type) { + if (in_array($type, $flags)) { + $type = strtolower(substr($type, 1)); + $special[$type] = $folder; + } } } } @@ -3381,7 +3408,7 @@ if ($subscription) { // It's possible we already called LIST command, check LIST data if (!empty($this->conn->data['LIST']) && !empty($this->conn->data['LIST'][$folder]) - && in_array('\\Subscribed', $this->conn->data['LIST'][$folder]) + && in_array_nocase('\\Subscribed', $this->conn->data['LIST'][$folder]) ) { $a_folders = array($folder); } @@ -3924,8 +3951,16 @@ // @TODO: Honor MAXSIZE and DEPTH options foreach ($queries as $attrib => $entry) { - if ($result = $this->conn->getAnnotation($folder, $entry, $attrib)) { - $res = array_merge_recursive($res, $result); + $result = $this->conn->getAnnotation($folder, $entry, $attrib); + + // an error, invalidate any previous getAnnotation() results + if (!is_array($result)) { + return null; + } + else { + foreach ($result as $fldr => $data) { + $res[$fldr] = array_merge((array) $res[$fldr], $data); + } } } } @@ -4166,29 +4201,37 @@ // force the type of folder name variable (#1485527) $folders = array_map('strval', $folders); + $out = array(); + + // finally we must put special folders on top and rebuild the list + // to move their subfolders where they belong... $specials = array_unique(array_intersect($specials, $folders)); - $head = array(); + $folders = array_merge($specials, array_diff($folders, $specials)); - // place default folders on top - foreach ($specials as $special) { - $prefix = $special . $this->delimiter; + $this->sort_folder_specials(null, $folders, $specials, $out); - foreach ($folders as $idx => $folder) { - if ($folder === $special) { - $head[] = $special; - unset($folders[$idx]); - } - // put subfolders of default folders on their place... - else if (strpos($folder, $prefix) === 0) { - $head[] = $folder; - unset($folders[$idx]); + return $out; + } + + /** + * Recursive function to put subfolders of special folders in place + */ + protected function sort_folder_specials($folder, &$list, &$specials, &$out) + { + while (list($key, $name) = each($list)) { + if ($folder === null || strpos($name, $folder.$this->delimiter) === 0) { + $out[] = $name; + unset($list[$key]); + + if (!empty($specials) && ($found = array_search($name, $specials)) !== false) { + unset($specials[$found]); + $this->sort_folder_specials($name, $list, $specials, $out); } } } - return array_merge($head, $folders); + reset($list); } - /** * Callback for uasort() that implements correct -- Gitblit v1.9.1