From 963499a17e98cdab4a6e872a711ce9badb8c3b0b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 15 May 2014 13:54:45 +0200 Subject: Plugin API: added imap_search_before hook --- program/lib/Roundcube/rcube_imap.php | 51 +++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 18 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 4204354b3..4b32c466b 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1489,23 +1489,39 @@ class rcube_imap extends rcube_storage * Invoke search request to IMAP server * * @param string $folder Folder name to search in - * @param string $str Search criteria + * @param string $search Search criteria * @param string $charset Search charset * @param string $sort_field Header field to sort by + * * @return rcube_result_index Search result object * @todo: Search criteria should be provided in non-IMAP format, eg. array */ - public function search($folder='', $str='ALL', $charset=NULL, $sort_field=NULL) + public function search($folder = '', $search = 'ALL', $charset = null, $sort_field = null) { - if (!$str) { - $str = 'ALL'; + if (!$search) { + $search = 'ALL'; } - // multi-folder search - if (is_array($folder) && count($folder) > 1 && $str != 'ALL') { - new rcube_result_index; // trigger autoloader and make these classes available for threaded context - new rcube_result_thread; + if ((is_array($folder) && empty($folder)) || (!is_array($folder) && !strlen($folder))) { + $folder = $this->folder; + } + + $plugin = rcube::get_instance()->plugins->exec_hook('imap_search_before', array( + 'folder' => $folder, + 'search' => $search, + 'charset' => $charset, + 'sort_field' => $sort_field, + 'threading' => $this->threading, + )); + + $folder = $plugin['folder']; + $search = $plugin['search']; + $charset = $plugin['charset']; + $sort_field = $plugin['sort_field']; + $results = $plugin['result']; + // multi-folder search + if (!$results && is_array($folder) && count($folder) > 1 && $search != 'ALL') { // connect IMAP to have all the required classes and settings loaded $this->check_connection(); @@ -1518,29 +1534,28 @@ class rcube_imap extends rcube_storage $searcher->set_timelimit(60); // continue existing incomplete search - if (!empty($this->search_set) && $this->search_set->incomplete && $str == $this->search_string) { + if (!empty($this->search_set) && $this->search_set->incomplete && $search == $this->search_string) { $searcher->set_results($this->search_set); } // execute the search $results = $searcher->exec( $folder, - $str, + $search, $charset ? $charset : $this->default_charset, $sort_field && $this->get_capability('SORT') ? $sort_field : null, $this->threading ); } - else { - $folder = is_array($folder) ? $folder[0] : $folder; - if (!strlen($folder)) { - $folder = $this->folder; - } - $results = $this->search_index($folder, $str, $charset, $sort_field); + else if (!$results) { + $folder = is_array($folder) ? $folder[0] : $folder; + $search = is_array($search) ? $search[$folder] : $search; + $results = $this->search_index($folder, $search, $charset, $sort_field); } - $this->set_search_set(array($str, $results, $charset, $sort_field, - $this->threading || $this->search_sorted ? true : false)); + $sorted = $this->threading || $this->search_sorted || $plugin['search_sorted'] ? true : false; + + $this->set_search_set(array($search, $results, $charset, $sort_field, $sorted)); return $results; } -- cgit v1.2.3 From 079be2c2fbdc9decbbf5bd123c3800ba001423b1 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 22 May 2014 12:34:33 +0200 Subject: If search string cannot be converted to ASCII (result is an empty string) use the original string (#1489911) --- program/lib/Roundcube/rcube_imap.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 4b32c466b..bf588cacf 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1691,12 +1691,15 @@ class rcube_imap extends rcube_storage $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 === false) { + + if ($string === false || !strlen($string)) { continue; } + $res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string); $last = $m[0] + $string_offset - 1; } + if ($last < strlen($str)) { $res .= substr($str, $last, strlen($str)-$last); } -- cgit v1.2.3 From 693612d396ede5c7ed8dc36e792d0e5653ccb8de Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 22 May 2014 20:21:15 +0200 Subject: Improve performance of sort_folder_list() method. Now sorting 25k folders takes around 3 seconds. --- program/lib/Roundcube/rcube_imap.php | 82 +++++++++++++++++++++--------------- 1 file changed, 49 insertions(+), 33 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index bf588cacf..41d1c373f 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -4142,59 +4142,75 @@ class rcube_imap extends rcube_storage */ public function sort_folder_list($a_folders, $skip_default = false) { - $a_out = $a_defaults = $folders = array(); - $delimiter = $this->get_hierarchy_delimiter(); $specials = array_merge(array('INBOX'), array_values($this->get_special_folders())); + $folders = array_flip($a_folders); - // find default folders and skip folders starting with '.' + // convert names to UTF-8 and skip folders starting with '.' foreach ($a_folders as $folder) { - if ($folder[0] == '.') { - continue; - } - - if (!$skip_default && ($p = array_search($folder, $specials)) !== false && !$a_defaults[$p]) { - $a_defaults[$p] = $folder; + if ($folder[0] != '.') { + // for better performance skip encoding conversion + // if the string does not look like UTF7-IMAP + $folders[$folder] = strpos($folder, '+') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP'); } else { - $folders[$folder] = rcube_charset::convert($folder, 'UTF7-IMAP'); + unset($folders[$idx]); } } - // sort folders and place defaults on the top - asort($folders, SORT_LOCALE_STRING); - ksort($a_defaults); - $folders = array_merge($a_defaults, array_keys($folders)); + // sort folders + // asort($folders, SORT_LOCALE_STRING) is not properly sorting case sensitive names + uasort($folders, array($this, 'sort_folder_comparator')); + + $folders = array_keys($folders); - // finally we must rebuild the list to move - // subfolders of default folders to their place... - // ...also do this for the rest of folders because - // asort() is not properly sorting case sensitive names - while (list($key, $folder) = each($folders)) { - // set the type of folder name variable (#1485527) - $a_out[] = (string) $folder; - unset($folders[$key]); - $this->rsort($folder, $delimiter, $folders, $a_out); + if ($skip_default) { + return $folders; } - return $a_out; + $specials = array_unique(array_intersect($specials, $folders)); + $head = array(); + + // place default folders on the top + foreach ($specials as $special) { + $prefix = $special . $delimiter; + + foreach ($folders as $idx => $folder) { + if ($folder === $special) { + $head[] = (string) $special; + unset($folders[$idx]); + } + // put subfolders of default folders on their place... + else if (strpos($folder, $prefix) === 0) { + $head[] = (string) $folder; + unset($folders[$idx]); + } + } + } + + return array_merge($head, $folders); } /** - * Recursive method for sorting folders + * Callback for uasort() that implements correct + * locale-aware case-sensitive sorting */ - protected function rsort($folder, $delimiter, &$list, &$out) + protected function sort_folder_comparator($str1, $str2) { - while (list($key, $name) = each($list)) { - if (strpos($name, $folder.$delimiter) === 0) { - // set the type of folder name variable (#1485527) - $out[] = (string) $name; - unset($list[$key]); - $this->rsort($name, $delimiter, $list, $out); + $delimiter = $this->get_hierarchy_delimiter(); + $path1 = explode($delimiter, $str1); + $path2 = explode($delimiter, $str2); + + foreach ($path1 as $idx => $folder1) { + $folder2 = $path2[$idx]; + + if ($folder1 === $folder2) { + continue; } + + return strcoll($folder1, $folder2); } - reset($list); } -- cgit v1.2.3 From 081f3b7d52340125c6a68c5587ed496167332544 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Thu, 22 May 2014 20:38:28 +0200 Subject: Bring back forcing of string type for folders list items (removed unintentionally by last commit) --- program/lib/Roundcube/rcube_imap.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 41d1c373f..2ff48c743 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -4168,21 +4168,23 @@ class rcube_imap extends rcube_storage return $folders; } + // force the type of folder name variable (#1485527) + $folders = array_map('strval', $folders); $specials = array_unique(array_intersect($specials, $folders)); $head = array(); - // place default folders on the top + // place default folders on top foreach ($specials as $special) { $prefix = $special . $delimiter; foreach ($folders as $idx => $folder) { if ($folder === $special) { - $head[] = (string) $special; + $head[] = $special; unset($folders[$idx]); } // put subfolders of default folders on their place... else if (strpos($folder, $prefix) === 0) { - $head[] = (string) $folder; + $head[] = $folder; unset($folders[$idx]); } } -- cgit v1.2.3 From ccf5a4f18de31c2839959f7f0b079e5b029ef307 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 25 May 2014 12:55:38 +0200 Subject: Silence PHP Warning: strtolower() expects parameter 1 to be string --- program/lib/Roundcube/rcube_imap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 2ff48c743..e4b77a0a3 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1848,7 +1848,7 @@ class rcube_imap extends rcube_storage $this->struct_charset = $this->structure_charset($structure); } - $headers->ctype = strtolower($headers->ctype); + $headers->ctype = @strtolower($headers->ctype); // Here we can recognize malformed BODYSTRUCTURE and // 1. [@TODO] parse the message in other way to create our own message structure -- cgit v1.2.3 From 20ef295ba8ff0e9fce57ee24dd7f467a366a5c88 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 28 May 2014 09:05:52 +0200 Subject: Fix UTF7-IMAP encoding detection (fixes folders list sorting) --- program/lib/Roundcube/rcube_imap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index e4b77a0a3..8bdc92bee 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -4151,7 +4151,7 @@ class rcube_imap extends rcube_storage if ($folder[0] != '.') { // for better performance skip encoding conversion // if the string does not look like UTF7-IMAP - $folders[$folder] = strpos($folder, '+') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP'); + $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP'); } else { unset($folders[$idx]); -- cgit v1.2.3 From a62cc30cb4186e89c4d33fdf80f9aa95b9def7cc Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 28 May 2014 10:58:47 +0200 Subject: Small code improvements --- program/lib/Roundcube/rcube_imap.php | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 8bdc92bee..78073abd6 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -4142,9 +4142,8 @@ class rcube_imap extends rcube_storage */ public function sort_folder_list($a_folders, $skip_default = false) { - $delimiter = $this->get_hierarchy_delimiter(); $specials = array_merge(array('INBOX'), array_values($this->get_special_folders())); - $folders = array_flip($a_folders); + $folders = array(); // convert names to UTF-8 and skip folders starting with '.' foreach ($a_folders as $folder) { @@ -4153,9 +4152,6 @@ class rcube_imap extends rcube_storage // if the string does not look like UTF7-IMAP $folders[$folder] = strpos($folder, '&') === false ? $folder : rcube_charset::convert($folder, 'UTF7-IMAP'); } - else { - unset($folders[$idx]); - } } // sort folders @@ -4175,7 +4171,7 @@ class rcube_imap extends rcube_storage // place default folders on top foreach ($specials as $special) { - $prefix = $special . $delimiter; + $prefix = $special . $this->delimiter; foreach ($folders as $idx => $folder) { if ($folder === $special) { @@ -4200,9 +4196,8 @@ class rcube_imap extends rcube_storage */ protected function sort_folder_comparator($str1, $str2) { - $delimiter = $this->get_hierarchy_delimiter(); - $path1 = explode($delimiter, $str1); - $path2 = explode($delimiter, $str2); + $path1 = explode($this->delimiter, $str1); + $path2 = explode($this->delimiter, $str2); foreach ($path1 as $idx => $folder1) { $folder2 = $path2[$idx]; -- cgit v1.2.3 From 35c135bce14d6c2260ff396b7087cd5ceed074b7 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Sun, 1 Jun 2014 18:54:30 +0200 Subject: Change private method to protected --- program/lib/Roundcube/rcube_imap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 78073abd6..6dcf528e0 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -2990,7 +2990,7 @@ class rcube_imap extends rcube_storage * @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(); -- cgit v1.2.3 From 354c7d3c6c457f93baae64c6df64ad99f4050b81 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 4 Jun 2014 10:44:26 +0200 Subject: Fix folders sorting in case when special folders are subfolders of INBOX --- program/lib/Roundcube/rcube_imap.php | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 6dcf528e0..e29bfc46b 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -4166,30 +4166,38 @@ class rcube_imap extends rcube_storage // 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 * locale-aware case-sensitive sorting -- cgit v1.2.3 From 109bcce470b789dac498d0fba8fb9673b988f867 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 16 Jun 2014 14:13:58 +0200 Subject: Add config option to specify IMAP connection socket parameters - imap_conn_options (#1489948) --- CHANGELOG | 1 + config/defaults.inc.php | 37 ++++-- program/lib/Roundcube/rcube.php | 19 +-- program/lib/Roundcube/rcube_imap.php | 12 +- program/lib/Roundcube/rcube_imap_generic.php | 181 +++++++++++++++------------ 5 files changed, 145 insertions(+), 105 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/CHANGELOG b/CHANGELOG index 9e26b9501..d2fc4faaf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG Roundcube Webmail =========================== +- Add config option to specify IMAP connection socket parameters - imap_conn_options (#1489948) - Password: Add option to force new users to change their password (#1486884) - Improve support for screen readers and assistive technology using WCAG 2.0 and WAI ARIA standards - Enable basic keyboard navigation throughout the UI (#1487845) diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 859378624..c20a06bf7 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -123,6 +123,27 @@ $config['default_port'] = 143; // best server supported one) $config['imap_auth_type'] = null; +// IMAP socket context options +// See http://php.net/manual/en/context.ssl.php +// The example below enables server certificate validation +//$config['imap_conn_options'] = array( +// 'ssl' => array( +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', +// ), +// ); +$config['imap_conn_options'] = null; + +// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout) +$config['imap_timeout'] = 0; + +// Optional IMAP authentication identifier to be used as authorization proxy +$config['imap_auth_cid'] = null; + +// Optional IMAP authentication password to be used for imap_auth_cid +$config['imap_auth_pw'] = null; + // If you know your imap's folder delimiter, you can specify it here. // Otherwise it will be determined automatically $config['imap_delimiter'] = null; @@ -160,15 +181,6 @@ $config['imap_force_ns'] = false; // Note: Because the list is cached, re-login is required after change. $config['imap_disabled_caps'] = array(); -// IMAP connection timeout, in seconds. Default: 0 (use default_socket_timeout) -$config['imap_timeout'] = 0; - -// Optional IMAP authentication identifier to be used as authorization proxy -$config['imap_auth_cid'] = null; - -// Optional IMAP authentication password to be used for imap_auth_cid -$config['imap_auth_pw'] = null; - // Type of IMAP indexes cache. Supported values: 'db', 'apc' and 'memcache'. $config['imap_cache'] = null; @@ -244,13 +256,14 @@ $config['smtp_timeout'] = 0; // requires 'smtp_timeout' to be non zero. // $config['smtp_conn_options'] = array( // 'ssl' => array( -// 'verify_peer' => true, -// 'verify_depth => 3, -// 'cafile' => '/etc/openssl/certs/ca.crt', +// 'verify_peer' => true, +// 'verify_depth' => 3, +// 'cafile' => '/etc/openssl/certs/ca.crt', // ), // ); $config['smtp_conn_options'] = null; + // ---------------------------------- // LDAP // ---------------------------------- diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index d618fb64d..cf54c3c12 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -357,15 +357,16 @@ class rcube // set class options $options = array( - 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), - 'auth_cid' => $this->config->get("{$driver}_auth_cid"), - 'auth_pw' => $this->config->get("{$driver}_auth_pw"), - 'debug' => (bool) $this->config->get("{$driver}_debug"), - 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), - 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), - 'timeout' => (int) $this->config->get("{$driver}_timeout"), - 'skip_deleted' => (bool) $this->config->get('skip_deleted'), - 'driver' => $driver, + 'auth_type' => $this->config->get("{$driver}_auth_type", 'check'), + 'auth_cid' => $this->config->get("{$driver}_auth_cid"), + 'auth_pw' => $this->config->get("{$driver}_auth_pw"), + 'debug' => (bool) $this->config->get("{$driver}_debug"), + 'force_caps' => (bool) $this->config->get("{$driver}_force_caps"), + 'disabled_caps' => $this->config->get("{$driver}_disabled_caps"), + 'socket_options' => $this->config->get("{$driver}_conn_options"), + 'timeout' => (int) $this->config->get("{$driver}_timeout"), + 'skip_deleted' => (bool) $this->config->get('skip_deleted'), + 'driver' => $driver, ); if (!empty($_SESSION['storage_host'])) { diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index e29bfc46b..109886f8d 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -110,13 +110,13 @@ class rcube_imap extends rcube_storage /** * 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) { diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index e4c9b7eb8..5aaad0a00 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -723,110 +723,38 @@ class rcube_imap_generic // configure $this->set_prefs($options); - $auth_method = $this->prefs['auth_type']; - $result = false; - - // initialize connection - $this->error = ''; - $this->errornum = self::ERROR_OK; - $this->selected = null; - $this->user = $user; $this->host = $host; + $this->user = $user; $this->logged = false; + $this->selected = null; // check input if (empty($host)) { $this->setError(self::ERROR_BAD, "Empty host"); return false; } + if (empty($user)) { $this->setError(self::ERROR_NO, "Empty user"); return false; } + if (empty($password)) { $this->setError(self::ERROR_NO, "Empty password"); return false; } - if (!$this->prefs['port']) { - $this->prefs['port'] = 143; - } - // check for SSL - if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { - $host = $this->prefs['ssl_mode'] . '://' . $host; - } - - if ($this->prefs['timeout'] <= 0) { - $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); - } - // Connect - $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); - - if (!$this->fp) { - if (!$errstr) { - $errstr = "Unknown reason (fsockopen() function disabled?)"; - } - $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr)); - return false; - } - - if ($this->prefs['timeout'] > 0) { - stream_set_timeout($this->fp, $this->prefs['timeout']); - } - - $line = trim(fgets($this->fp, 8192)); - - if ($this->_debug) { - // set connection identifier for debug output - preg_match('/#([0-9]+)/', (string)$this->fp, $m); - $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4)); - - if ($line) - $this->debug('S: '. $line); - } - - // Connected to wrong port or connection error? - if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { - if ($line) - $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); - else - $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); - - $this->setError(self::ERROR_BAD, $error); - $this->closeConnection(); + if (!$this->_connect($host)) { return false; } - // RFC3501 [7.1] optional CAPABILITY response - if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { - $this->parseCapability($matches[1], true); - } - - // TLS connection - if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { - $res = $this->execute('STARTTLS'); - - if ($res[0] != self::ERROR_OK) { - $this->closeConnection(); - return false; - } - - if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); - $this->closeConnection(); - return false; - } - - // Now we're secure, capabilities need to be reread - $this->clearCapability(); - } - // Send ID info if (!empty($this->prefs['ident']) && $this->getCapability('ID')) { $this->id($this->prefs['ident']); } + $auth_method = $this->prefs['auth_type']; $auth_methods = array(); $result = null; @@ -900,6 +828,103 @@ class rcube_imap_generic return false; } + /** + * Connects to IMAP server. + * + * @param string $host Server hostname or IP + * + * @return bool True on success, False on failure + */ + protected function _connect($host) + { + // initialize connection + $this->error = ''; + $this->errornum = self::ERROR_OK; + + if (!$this->prefs['port']) { + $this->prefs['port'] = 143; + } + + // check for SSL + if ($this->prefs['ssl_mode'] && $this->prefs['ssl_mode'] != 'tls') { + $host = $this->prefs['ssl_mode'] . '://' . $host; + } + + if ($this->prefs['timeout'] <= 0) { + $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout'))); + } + + if (!empty($this->prefs['socket_options'])) { + $context = stream_context_create($this->prefs['socket_options']); + $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr, + $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context); + } + else { + $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']); + } + + if (!$this->fp) { + $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", + $host, $this->prefs['port'], $errstr ?: "Unknown reason")); + + return false; + } + + if ($this->prefs['timeout'] > 0) { + stream_set_timeout($this->fp, $this->prefs['timeout']); + } + + $line = trim(fgets($this->fp, 8192)); + + if ($this->_debug) { + // set connection identifier for debug output + preg_match('/#([0-9]+)/', (string) $this->fp, $m); + $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4)); + + if ($line) { + $this->debug('S: '. $line); + } + } + + // Connected to wrong port or connection error? + if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { + if ($line) + $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); + else + $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); + + $this->setError(self::ERROR_BAD, $error); + $this->closeConnection(); + return false; + } + + // RFC3501 [7.1] optional CAPABILITY response + if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { + $this->parseCapability($matches[1], true); + } + + // TLS connection + if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { + $res = $this->execute('STARTTLS'); + + if ($res[0] != self::ERROR_OK) { + $this->closeConnection(); + return false; + } + + if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); + $this->closeConnection(); + return false; + } + + // Now we're secure, capabilities need to be reread + $this->clearCapability(); + } + + return true; + } + /** * Initializes environment */ -- cgit v1.2.3 From 6fa1a0da1f0902f10be8fc4eb24180f8e3453c17 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Tue, 24 Jun 2014 19:16:18 +0200 Subject: Extend get_quota() so it's possible to specify GETQUOTAROOT folder and return full quota info (including all roots and types, e.g. MESSAGE) - for future use --- program/include/rcmail.php | 1 + program/lib/Roundcube/rcube_imap.php | 7 ++- program/lib/Roundcube/rcube_imap_generic.php | 91 +++++++++++++++------------- program/lib/Roundcube/rcube_storage.php | 4 +- 4 files changed, 57 insertions(+), 46 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/include/rcmail.php b/program/include/rcmail.php index f4689215c..29ed66a43 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -1678,6 +1678,7 @@ class rcmail extends rcube $quota = $this->storage->get_quota(); $quota = $this->plugins->exec_hook('quota', $quota); + unset($quota['abort']); $quota_result = (array) $quota; $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : ''; diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 109886f8d..858db7b5b 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3067,14 +3067,15 @@ class rcube_imap extends rcube_storage /** * 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; diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 7b9ba2ec8..032506412 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2873,59 +2873,66 @@ class rcube_imap_generic /** * Returns QUOTA information * + * @param string $mailbox Mailbox name + * * @return array Quota information */ - function getQuota() - { - /* - * GETQUOTAROOT "INBOX" - * QUOTAROOT INBOX user/rchijiiwa1 - * QUOTA user/rchijiiwa1 (STORAGE 654 9765) - * OK Completed - */ - $result = false; - $quota_lines = array(); - $key = $this->nextTag(); - $command = $key . ' GETQUOTAROOT INBOX'; - - // get line(s) containing quota info - if ($this->putLine($command)) { - do { - $line = rtrim($this->readLine(5000)); - if (preg_match('/^\* QUOTA /', $line)) { - $quota_lines[] = $line; - } - } while (!$this->startsWith($line, $key, true, true)); - } - else { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); + function getQuota($mailbox = null) + { + if ($mailbox === null || $mailbox === '') { + $mailbox = 'INBOX'; } - // return false if not found, parse if found + // a0001 GETQUOTAROOT INBOX + // * QUOTAROOT INBOX user/sample + // * QUOTA user/sample (STORAGE 654 9765) + // a0001 OK Completed + + list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox))); + + $result = false; $min_free = PHP_INT_MAX; - foreach ($quota_lines as $key => $quota_line) { - $quota_line = str_replace(array('(', ')'), '', $quota_line); - $parts = explode(' ', $quota_line); - $storage_part = array_search('STORAGE', $parts); + $all = array(); - if (!$storage_part) { - continue; - } + if ($code == self::ERROR_OK) { + foreach (explode("\n", $response) as $line) { + if (preg_match('/^\* QUOTA /', $line)) { + list(, , $quota_root) = $this->tokenizeResponse($line, 3); + + while ($line) { + list($type, $used, $total) = $this->tokenizeResponse($line, 1); + $type = strtolower($type); + + if ($type && $total) { + $all[$quota_root][$type]['used'] = intval($used); + $all[$quota_root][$type]['total'] = intval($total); + } + } + + if (empty($all[$quota_root]['storage'])) { + continue; + } - $used = intval($parts[$storage_part+1]); - $total = intval($parts[$storage_part+2]); - $free = $total - $used; + $used = $all[$quota_root]['storage']['used']; + $total = $all[$quota_root]['storage']['total']; + $free = $total - $used; - // return lowest available space from all quotas - if ($free < $min_free) { - $min_free = $free; - $result['used'] = $used; - $result['total'] = $total; - $result['percent'] = min(100, round(($used/max(1,$total))*100)); - $result['free'] = 100 - $result['percent']; + // calculate lowest available space from all storage quotas + if ($free < $min_free) { + $min_free = $free; + $result['used'] = $used; + $result['total'] = $total; + $result['percent'] = min(100, round(($used/max(1,$total))*100)); + $result['free'] = 100 - $result['percent']; + } + } } } + if (!empty($result)) { + $result['all'] = $all; + } + return $result; } diff --git a/program/lib/Roundcube/rcube_storage.php b/program/lib/Roundcube/rcube_storage.php index c1293961c..ccb28c680 100644 --- a/program/lib/Roundcube/rcube_storage.php +++ b/program/lib/Roundcube/rcube_storage.php @@ -918,9 +918,11 @@ abstract class rcube_storage /** * Get mailbox quota information. * + * @param string $folder Folder name + * * @return mixed Quota info or False if not supported */ - abstract function get_quota(); + abstract function get_quota($folder = null); /* ----------------------------------------- -- cgit v1.2.3 From e0492d213b4c087b7092fa6bdc3dfecbc14f9bcf Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 14 Jul 2014 08:56:59 +0200 Subject: Fix "Illegal offset type" error (#1489985) --- program/lib/Roundcube/rcube_imap.php | 14 ++++++++------ program/lib/Roundcube/rcube_imap_generic.php | 16 +++++++++------- 2 files changed, 17 insertions(+), 13 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 858db7b5b..9a07711c2 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3298,12 +3298,14 @@ class rcube_imap extends rcube_storage // 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; + } } } } diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 99fb6d861..d76014f89 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -1296,8 +1296,8 @@ class rcube_imap_generic * @param array $return_opts (see self::_listMailboxes) * @param array $select_opts (see self::_listMailboxes) * - * @return array List of mailboxes or hash of options if $return_opts argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ function listMailboxes($ref, $mailbox, $return_opts=array(), $select_opts=array()) { @@ -1311,8 +1311,8 @@ class rcube_imap_generic * @param string $mailbox Mailbox name * @param array $return_opts (see self::_listMailboxes) * - * @return array List of mailboxes or hash of options if $return_opts argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ function listSubscribed($ref, $mailbox, $return_opts=array()) { @@ -1332,8 +1332,8 @@ class rcube_imap_generic * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE, * SPECIAL-USE (RFC6154) * - * @return array List of mailboxes or hash of options if $status_ops argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ protected function _listMailboxes($ref, $mailbox, $subscribed=false, $return_opts=array(), $select_opts=array()) @@ -1355,7 +1355,9 @@ class rcube_imap_generic $args[] = $this->escape($mailbox); if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) { - $rets = array_intersect($return_opts, array('SUBSCRIBED', 'CHILDREN')); + $ext_opts = array('SUBSCRIBED', 'CHILDREN'); + $rets = array_intersect($return_opts, $ext_opts); + $return_opts = array_diff($return_opts, $rets); } if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) { -- cgit v1.2.3 From 5b592d17ef078ebb101b47402dc6c3381249ba78 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 30 Jul 2014 10:49:18 -0400 Subject: Remove redundant folder check when using search set --- program/lib/Roundcube/rcube_imap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 9a07711c2..79a8973db 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -761,7 +761,7 @@ class rcube_imap extends rcube_storage $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); } -- cgit v1.2.3 From f954922c03e30bfe06b60f35336f9274fa45ee4e Mon Sep 17 00:00:00 2001 From: Thomas Bruederli Date: Thu, 7 Aug 2014 17:04:05 +0200 Subject: - Implemented 'storage_connected' API hook after successful IMAP login (#1490025) - Added config option 'imap_log_session' to enable Roundcube <-> IMAP session ID logging - Added config option 'log_session_id' to control the lengh of the session identifer in logs --- program/lib/Roundcube/rcube.php | 28 +++++++++++++++++++++++----- program/lib/Roundcube/rcube_imap.php | 24 +++++++++++++++++++----- 2 files changed, 42 insertions(+), 10 deletions(-) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index e3e26d8b9..eedc46c7a 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -389,8 +389,12 @@ class rcube $this->storage->set_options($options); $this->set_storage_prop(); - } + // subscribe to 'storage_connected' hook for session logging + if ($this->config->get('imap_log_session', false)) { + $this->plugins->register_hook('storage_connected', array($this, 'storage_log_session')); + } + } /** * Set storage parameters. @@ -457,6 +461,16 @@ class rcube } + /** + * Callback for IMAP connection events to log session identifiers + */ + public function storage_log_session($args) + { + if (!empty($args['session']) && session_id()) { + $this->write_log('imap_session', $args['session']); + } + } + /** * Create session object and start the session. */ @@ -1138,8 +1152,12 @@ class rcube $line = var_export($line, true); } - $date_format = self::$instance ? self::$instance->config->get('log_date_format') : null; - $log_driver = self::$instance ? self::$instance->config->get('log_driver') : null; + $date_format = $log_driver = $session_key = null; + if (self::$instance) { + $date_format = self::$instance->config->get('log_date_format'); + $log_driver = self::$instance->config->get('log_driver'); + $session_key = intval(self::$instance->config->get('log_session_id', 8)); + } if (empty($date_format)) { $date_format = 'd-M-Y H:i:s O'; @@ -1158,8 +1176,8 @@ class rcube } // add session ID to the log - if ($sess = session_id()) { - $line = '<' . substr($sess, 0, 8) . '> ' . $line; + if ($session_key > 0 && ($sess = session_id())) { + $line = '<' . substr($sess, 0, $session_key) . '> ' . $line; } if ($log_driver == 'syslog') { diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 79a8973db..ec961c8d1 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -56,6 +56,7 @@ class rcube_imap extends rcube_storage */ protected $icache = array(); + protected $plugins; protected $list_page = 1; protected $delimiter; protected $namespace; @@ -82,6 +83,7 @@ class rcube_imap extends rcube_storage 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 @@ -147,7 +149,7 @@ class rcube_imap extends rcube_storage $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 @@ class rcube_imap extends rcube_storage $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 @@ -1506,7 +1520,7 @@ class rcube_imap extends rcube_storage $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 @@ class rcube_imap extends rcube_storage // 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 @@ class rcube_imap extends rcube_storage } // 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'])) { @@ -2909,7 +2923,7 @@ class rcube_imap extends rcube_storage } // 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'])) { -- cgit v1.2.3 From fb5e2fb96f4cda26936f599f1a15a49afb53178e Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Mon, 18 Aug 2014 10:46:18 -0400 Subject: Don't fail when trying to create and subscribe a folder that already exists --- program/lib/Roundcube/rcube_imap.php | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'program/lib/Roundcube/rcube_imap.php') diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index ec961c8d1..a66d2064d 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3165,6 +3165,16 @@ class rcube_imap extends rcube_storage $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) { // clear cache -- cgit v1.2.3