From d7a5dfa26abe21aa9216fe862225baa2b5caca3e Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Sat, 19 Jun 2010 14:04:48 -0400 Subject: [PATCH] - Fix dot-atom expression in e-mail validation regexp (#1486808) --- program/include/rcube_imap.php | 645 +++++++++++++++++++++++++++++++-------------------------- 1 files changed, 349 insertions(+), 296 deletions(-) diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 9facb32..6f3b402 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -26,7 +26,7 @@ * * @package Mail * @author Thomas Bruederli <roundcube@gmail.com> - * @author Aleksander Machniak <alec@alec.pl> + * @author Aleksander Machniak <alec@alec.pl> * @version 2.0 */ class rcube_imap @@ -53,7 +53,7 @@ private $default_folders = array('INBOX'); private $icache = array(); private $cache = array(); - private $cache_keys = array(); + private $cache_keys = array(); private $cache_changes = array(); private $uid_id_map = array(); private $msg_headers = array(); @@ -92,7 +92,7 @@ */ function connect($host, $user, $pass, $port=143, $use_ssl=null) { - // check for Open-SSL support in PHP build + // check for OpenSSL support in PHP build if ($use_ssl && extension_loaded('openssl')) $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; else if ($use_ssl) { @@ -108,7 +108,7 @@ do { $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); - + if (!empty($data['pass'])) $pass = $data['pass']; @@ -154,7 +154,7 @@ * @access public */ function close() - { + { if ($this->conn && $this->conn->connected()) $this->conn->close(); $this->write_cache(); @@ -171,7 +171,7 @@ { $this->close(); $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl); - + // issue SELECT command to restore connection status if ($this->mailbox) $this->conn->select($this->mailbox); @@ -201,7 +201,7 @@ $this->root_dir = $root; $this->options['rootdir'] = $root; - + if (empty($this->delimiter)) $this->get_hierarchy_delimiter(); } @@ -283,7 +283,7 @@ { $this->page_size = (int)$size; } - + /** * Save a set of message ids for future message listing methods @@ -297,7 +297,9 @@ { if (is_array($str) && $msgs == null) list($str, $msgs, $charset, $sort_field, $threads) = $str; - if ($msgs != null && !is_array($msgs)) + if ($msgs === false) + $msgs = array(); + else if ($msgs != null && !is_array($msgs)) $msgs = explode(',', $msgs); $this->search_string = $str; @@ -358,7 +360,7 @@ function set_threading($enable=false) { $this->threading = false; - + if ($enable) { if ($this->get_capability('THREAD=REFS')) $this->threading = 'REFS'; @@ -412,14 +414,15 @@ * @param string Mailbox/folder name * @param string Mode for count [ALL|THREADS|UNSEEN|RECENT] * @param boolean Force reading from server and update cache - * @param boolean Enables MAXUIDs checking + * @param boolean Enables storing folder status info (max UID/count), + * required for mailbox_status() * @return int Number of messages * @access public */ - function messagecount($mbox_name='', $mode='ALL', $force=false, $maxuid=true) + function messagecount($mbox_name='', $mode='ALL', $force=false, $status=true) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; - return $this->_messagecount($mailbox, $mode, $force, $maxuid); + return $this->_messagecount($mailbox, $mode, $force, $status); } @@ -429,7 +432,7 @@ * @access private * @see rcube_imap::messagecount() */ - private function _messagecount($mailbox='', $mode='ALL', $force=false, $maxuid=true) + private function _messagecount($mailbox='', $mode='ALL', $force=false, $status=true) { $mode = strtoupper($mode); @@ -443,9 +446,9 @@ else return count((array)$this->search_set); } - + $a_mailbox_cache = $this->get_cache('messagecount'); - + // return cached value if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode])) return $a_mailbox_cache[$mailbox][$mode]; @@ -455,8 +458,10 @@ if ($mode == 'THREADS') { $count = $this->_threadcount($mailbox, $msg_count); - if ($maxuid) - $_SESSION['maxuid'][$mailbox] = $msg_count ? $this->_id2uid($msg_count, $mailbox) : 0; + if ($status) { + $this->set_folder_stats($mailbox, 'cnt', $msg_count); + $this->set_folder_stats($mailbox, 'maxuid', $msg_count ? $this->_id2uid($msg_count, $mailbox) : 0); + } } // RECENT count is fetched a bit different else if ($mode == 'RECENT') { @@ -469,27 +474,26 @@ // get message count and store in cache if ($mode == 'UNSEEN') $search_str .= " UNSEEN"; - // get message count using SEARCH // not very performant but more precise (using UNDELETED) - // disable THREADS for this request - $threads = $this->threading; - $this->threading = false; - $index = $this->_search_index($mailbox, $search_str); - $this->threading = $threads; - + $index = $this->conn->search($mailbox, $search_str); + $count = is_array($index) ? count($index) : 0; - if ($mode == 'ALL' && $maxuid) - $_SESSION['maxuid'][$mailbox] = $index ? $this->_id2uid(max($index), $mailbox) : 0; + if ($mode == 'ALL' && $status) { + $this->set_folder_stats($mailbox, 'cnt', $count); + $this->set_folder_stats($mailbox, 'maxuid', $index ? $this->_id2uid(max($index), $mailbox) : 0); + } } else { if ($mode == 'UNSEEN') $count = $this->conn->countUnseen($mailbox); else { $count = $this->conn->countMessages($mailbox); - if ($maxuid) - $_SESSION['maxuid'][$mailbox] = $count ? $this->_id2uid($count, $mailbox) : 0; + if ($status) { + $this->set_folder_stats($mailbox,'cnt', $count); + $this->set_folder_stats($mailbox, 'maxuid', $count ? $this->_id2uid($count, $mailbox) : 0); + } } } @@ -512,13 +516,13 @@ { if (!empty($this->icache['threads'])) return count($this->icache['threads']['tree']); - + list ($thread_tree, $msg_depth, $has_children) = $this->_fetch_threads($mailbox); - + $msg_count = count($msg_depth); // $this->update_thread_cache($mailbox, $thread_tree, $msg_depth, $has_children); - return count($thread_tree); + return count($thread_tree); } @@ -532,7 +536,7 @@ * @param string Sort order [ASC|DESC] * @param boolean Number of slice items to extract from result array * @return array Indexed array with message header objects - * @access public + * @access public */ function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { @@ -601,7 +605,7 @@ else $msg_index = array(); - if ($slice) + if ($slice && $msg_index) $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); // fetch reqested headers from server @@ -647,14 +651,14 @@ // return empty array if no messages found if (!is_array($a_msg_headers) || empty($a_msg_headers)) return array(); - + // use this class for message sorting $sorter = new rcube_header_sorter(); $sorter->set_sequence_numbers($msg_index); $sorter->sort_headers($a_msg_headers); if ($this->sort_order == 'DESC') - $a_msg_headers = array_reverse($a_msg_headers); + $a_msg_headers = array_reverse($a_msg_headers); return array_values($a_msg_headers); } @@ -700,7 +704,7 @@ // get all threads list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : ''); - + // add to internal (fast) cache $this->icache['threads'] = array(); $this->icache['threads']['tree'] = $thread_tree; @@ -749,7 +753,7 @@ // return empty array if no messages found if (!is_array($a_msg_headers) || empty($a_msg_headers)) return array(); - + // use this class for message sorting $sorter = new rcube_header_sorter(); $sorter->set_sequence_numbers($all_ids); @@ -816,9 +820,12 @@ return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); // search set is threaded, we need a new one - if ($this->search_threads) + if ($this->search_threads) { + if (empty($this->search_set['tree'])) + return array(); $this->search('', $this->search_string, $this->search_charset, $sort_field); - + } + $msgs = $this->search_set; $a_msg_headers = array(); $page = $page ? $page : $this->list_page; @@ -902,7 +909,7 @@ else { // for small result set we can fetch all messages headers $this->_fetch_headers($mailbox, join(',', $msgs), $a_msg_headers, NULL); - + // return empty array if no messages found if (!is_array($a_msg_headers) || empty($a_msg_headers)) return array(); @@ -939,8 +946,15 @@ private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { // update search_set if previous data was fetched with disabled threading - if (!$this->search_threads) + if (!$this->search_threads) { + if (empty($this->search_set)) + return array(); $this->search('', $this->search_string, $this->search_charset, $sort_field); + } + + // empty result + if (empty($this->search_set['tree'])) + return array(); $thread_tree = $this->search_set['tree']; $msg_depth = $this->search_set['depth']; @@ -970,7 +984,7 @@ private function _get_message_range($max, $page) { $start_msg = ($page-1) * $this->page_size; - + if ($page=='all') { $begin = 0; $end = $max; @@ -987,10 +1001,10 @@ if ($begin < 0) $begin = 0; if ($end < 0) $end = $max; if ($end > $max) $end = $max; - + return array($begin, $end); } - + /** * Fetches message headers @@ -1032,33 +1046,71 @@ return count($a_msg_headers); } - + + /** - * Fetches IDS of pseudo recent messages. + * Returns current status of mailbox * * We compare the maximum UID to determine the number of * new messages because the RECENT flag is not reliable. * - * @param string Mailbox/folder name - * @return array List of recent message UIDs + * @param string Mailbox/folder name + * @return int Folder status */ - function recent_uids($mbox_name = null, $nofetch = false) + function mailbox_status($mbox_name = null) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; - $old_maxuid = intval($_SESSION['maxuid'][$mailbox]); - - // refresh message count -> will update $_SESSION['maxuid'][$mailbox] + $old = $this->get_folder_stats($mailbox); + + // refresh message count -> will update $this->_messagecount($mailbox, 'ALL', true); - - if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) { - $maxuid = max(1, $old_maxuid+1); - return array_values((array)$this->conn->fetchHeaderIndex( - $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); - } - - return array(); + + $result = 0; + $new = $this->get_folder_stats($mailbox); + + // got new messages + if ($new['maxuid'] > $old['maxuid']) + $result += 1; + // some messages has been deleted + if ($new['cnt'] < $old['cnt']) + $result += 2; + + // @TODO: optional checking for messages flags changes (?) + // @TODO: UIDVALIDITY checking + + return $result; } - + + + /** + * Stores folder statistic data in session + * @TODO: move to separate DB table (cache?) + * + * @param string Mailbox name + * @param string Data name + * @param mixed Data value + */ + private function set_folder_stats($mbox_name, $name, $data) + { + $_SESSION['folders'][$mbox_name][$name] = $data; + } + + + /** + * Gets folder statistic data + * + * @param string Mailbox name + * @return array Stats data + */ + private function get_folder_stats($mbox_name) + { + if ($_SESSION['folders'][$mbox_name]) + return (array) $_SESSION['folders'][$mbox_name]; + else + return array(); + } + + /** * Return sorted array of message IDs (not UIDs) * @@ -1105,7 +1157,7 @@ else { $a_index = $this->conn->fetchHeaderIndex($mailbox, join(',', $this->search_set), $this->sort_field, $this->skip_deleted); - + if (is_array($a_index)) { if ($this->sort_order=="ASC") asort($a_index); @@ -1143,20 +1195,20 @@ $a_index = range(1, $max); } - if ($this->sort_order == 'DESC') + if ($a_index !== false && $this->sort_order == 'DESC') $a_index = array_reverse($a_index); $this->cache[$key] = $a_index; } // fetch complete message index else if ($this->get_capability('SORT')) { - if ($a_index = $this->conn->sort($mailbox, - $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { - if ($this->sort_order == 'DESC') - $a_index = array_reverse($a_index); - - $this->cache[$key] = $a_index; - } + $a_index = $this->conn->sort($mailbox, + $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''); + + if ($a_index !== false && $this->sort_order == 'DESC') + $a_index = array_reverse($a_index); + + $this->cache[$key] = $a_index; } else if ($a_index = $this->conn->fetchHeaderIndex( $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { @@ -1164,11 +1216,11 @@ asort($a_index); else if ($this->sort_order=="DESC") arsort($a_index); - + $this->cache[$key] = array_keys($a_index); } - return $this->cache[$key]; + return $this->cache[$key] !== false ? $this->cache[$key] : array(); } @@ -1213,7 +1265,7 @@ list ($thread_tree) = $this->_fetch_threads($mailbox); $this->cache[$key] = $this->_flatten_threads($mailbox, $thread_tree); - + return $this->cache[$key]; } @@ -1238,7 +1290,7 @@ if ($this->sort_order == 'DESC') $msg_index = array_reverse($msg_index); - + // flatten threads array $all_ids = array(); foreach($msg_index as $root) { @@ -1261,10 +1313,10 @@ // fetch complete message index $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); - + if ($a_message_index === false) return false; - + foreach ($a_message_index as $id => $uid) { // message in cache at correct position if ($cache_index[$id] == $uid) { @@ -1276,20 +1328,20 @@ if (in_array((string)$uid, $cache_index, true)) { unset($cache_index[$id]); } - + // other message at this position if (isset($cache_index[$id])) { $for_remove[] = $cache_index[$id]; unset($cache_index[$id]); } - + $for_update[] = $id; } - // clear messages at wrong positions and those deleted that are still in cache_index + // clear messages at wrong positions and those deleted that are still in cache_index if (!empty($for_remove)) $cache_index = array_merge($cache_index, $for_remove); - + if (!empty($cache_index)) $this->remove_message_cache($cache_key, $cache_index); @@ -1320,36 +1372,10 @@ { if (!$str) return false; - + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; $results = $this->_search_index($mailbox, $str, $charset, $sort_field); - - // try search with US-ASCII charset (should be supported by server) - // only if UTF-8 search is not supported - if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') - { - // convert strings to US_ASCII - if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { - $last = 0; $res = ''; - foreach($matches[1] as $m) - { - $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n - $string = substr($str, $string_offset - 1, $m[0]); - $string = rcube_charset_convert($string, $charset, 'US-ASCII'); - if (!$string) - continue; - $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); - $last = $m[0] + $string_offset - 1; - } - if ($last < strlen($str)) - $res .= substr($str, $last, strlen($str)-$last); - } - else // strings for conversion not found - $res = $str; - - $results = $this->search($mbox_name, $res, NULL, $sort_field); - } $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); @@ -1372,21 +1398,32 @@ $criteria = 'UNDELETED '.$criteria; if ($this->threading) { - list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( - $mailbox, $this->threading, $criteria, $charset); + $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset); - $a_messages = array( - 'tree' => $thread_tree, - 'depth' => $msg_depth, - 'children' => $has_children - ); + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($a_messages === false && $charset && $charset != 'US-ASCII') + $a_messages = $this->conn->thread($mailbox, $this->threading, + $this->convert_criteria($criteria, $charset), 'US-ASCII'); + + if ($a_messages !== false) { + list ($thread_tree, $msg_depth, $has_children) = $a_messages; + $a_messages = array( + 'tree' => $thread_tree, + 'depth' => $msg_depth, + 'children' => $has_children + ); + } } else if ($sort_field && $this->get_capability('SORT')) { $charset = $charset ? $charset : $this->default_charset; $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); - if (!$a_messages) - return array(); + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($a_messages === false && $charset && $charset != 'US-ASCII') + $a_messages = $this->conn->sort($mailbox, $sort_field, + $this->convert_criteria($criteria, $charset), false, 'US-ASCII'); } else { if ($orig_criteria == 'ALL') { @@ -1395,14 +1432,16 @@ } else { $a_messages = $this->conn->search($mailbox, - ($charset ? "CHARSET $charset " : '') . $criteria); + ($charset ? "CHARSET $charset " : '') . $criteria); - if (!$a_messages) - return array(); + // 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)); - // I didn't found that SEARCH always returns sorted IDs - if (!$this->sort_field) - sort($a_messages); + // I didn't found that SEARCH should return sorted IDs + if (is_array($a_messages) && !$this->sort_field) + sort($a_messages); } } @@ -1410,10 +1449,10 @@ // $a_mailbox_cache = get_cache('messagecount'); // $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); // $this->update_cache('messagecount', $a_mailbox_cache); - + return $a_messages; } - + /** * Direct (real and simple) SEARCH request to IMAP server, @@ -1429,13 +1468,46 @@ { if (!$str) return false; - + $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->conn->search($mailbox, $str, $ret_uid); } - + + /** + * Converts charset of search criteria string + * + * @param string Search string + * @param string Original charset + * @param string Destination charset (default US-ASCII) + * @return string Search string + * @access private + */ + private function convert_criteria($str, $charset, $dest_charset='US-ASCII') + { + // convert strings to US_ASCII + if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { + $last = 0; $res = ''; + foreach ($matches[1] as $m) { + $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) + continue; + $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); + $last = $m[0] + $string_offset - 1; + } + if ($last < strlen($str)) + $res .= substr($str, $last, strlen($str)-$last); + } + else // strings for conversion not found + $res = $str; + + return $res; + } + + /** * Sort thread * @@ -1495,7 +1567,7 @@ { if (empty($tree)) return array(); - + $index = array_combine(array_values($index), $index); // assign roots @@ -1508,7 +1580,7 @@ } } - $index = array_values($index); + $index = array_values($index); // create sorted array of roots $msg_index = array(); @@ -1538,13 +1610,12 @@ { if (!empty($this->search_string)) $this->search_set = $this->search('', $this->search_string, $this->search_charset, + $this->search_sort_field, $this->search_threads); - $this->search_sort_field, $this->search_threads); - return $this->get_search_set(); } - - + + /** * Check if the given message ID is part of the current search set * @@ -1567,7 +1638,7 @@ * Return message headers object of a specific message * * @param int Message ID - * @param string Mailbox to read from + * @param string Mailbox to read from * @param boolean True if $id is the message UID * @param boolean True if we need also BODYSTRUCTURE in headers * @return object Message headers representation @@ -1589,7 +1660,7 @@ if ($headers->uid && $headers->id) $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; - $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, true); + $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL); } return $headers; @@ -1631,7 +1702,7 @@ else $this->struct_charset = $this->_structure_charset($structure); - // Here we can recognize malformed BODYSTRUCTURE and + // Here we can recognize malformed BODYSTRUCTURE and // 1. [@TODO] parse the message in other way to create our own message structure // 2. or just show the raw message body. // Example of structure for malformed MIME message: @@ -1659,7 +1730,7 @@ return $struct; } - + /** * Build message part object * @@ -1673,7 +1744,7 @@ // multipart if (is_array($part[0])) { $struct->ctype_primary = 'multipart'; - + // find first non-array entry for ($i=1; $i<count($part); $i++) { if (!is_array($part[$i])) { @@ -1681,12 +1752,12 @@ break; } } - + $struct->mimetype = 'multipart/'.$struct->ctype_secondary; // build parts list for headers pre-fetching for ($i=0, $count=0; $i<count($part); $i++) { - if (is_array($part[$i]) && count($part[$i]) > 3) { + if (is_array($part[$i]) && count($part[$i]) > 4) { // fetch message headers if message/rfc822 // or named part (could contain Content-Location header) if (!is_array($part[$i][0])) { @@ -1701,7 +1772,7 @@ } } } - + // pre-fetch headers of all parts (in one command for better performance) // @TODO: we could do this before _structure_part() call, to fetch // headers for parts on all levels @@ -1716,7 +1787,7 @@ } $struct->parts = array(); for ($i=0, $count=0; $i<count($part); $i++) { - if (is_array($part[$i]) && count($part[$i]) > 3) { + if (is_array($part[$i]) && count($part[$i]) > 4) { $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; $struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id, $mime_part_headers[$tmp_part_id], $raw_part_headers[$tmp_part_id]); @@ -1736,17 +1807,17 @@ $struct->ctype_parameters = array(); for ($i=0; $i<count($part[2]); $i+=2) $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; - + if (isset($struct->ctype_parameters['charset'])) $struct->charset = $struct->ctype_parameters['charset']; } - + // read content encoding if (!empty($part[5]) && $part[5]!='NIL') { $struct->encoding = strtolower($part[5]); $struct->headers['content-transfer-encoding'] = $struct->encoding; } - + // get part size if (!empty($part[6]) && $part[6]!='NIL') $struct->size = intval($part[6]); @@ -1761,7 +1832,7 @@ for ($n=0; $n<count($part[$di][1]); $n+=2) $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; } - + // get child parts if (is_array($part[8]) && $di != 8) { $struct->parts = array(); @@ -1774,11 +1845,11 @@ if (!empty($part[3]) && $part[3]!='NIL') { $struct->content_id = $part[3]; $struct->headers['content-id'] = $part[3]; - + if (empty($struct->disposition)) $struct->disposition = 'inline'; } - + // fetch message headers if message/rfc822 or named part (could contain Content-Location header) if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) { if (empty($mime_headers)) { @@ -1798,7 +1869,7 @@ // get real content-type of message/rfc822 if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { $struct->real_mimetype = strtolower($matches[1]); - } + } } } @@ -1812,10 +1883,10 @@ return $struct; } - + /** - * Set attachment filename from message part structure + * Set attachment filename from message part structure * * @access private * @param object rcube_message_part Part object @@ -1920,10 +1991,10 @@ // decode filename if (!empty($filename_mime)) { - $part->filename = rcube_imap::decode_mime_string($filename_mime, + $part->filename = rcube_imap::decode_mime_string($filename_mime, $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset : rc_detect_encoding($filename_mime, $this->default_charset))); - } + } else if (!empty($filename_encoded)) { // decode filename according to RFC 2231, Section 4 if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) { @@ -1949,7 +2020,7 @@ return $structure[2][1]; $structure = $structure[0]; } - } + } /** @@ -1966,7 +2037,7 @@ { // get part encoding if not provided if (!is_object($o_part)) { - $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); + $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); $structure = new rcube_mime_struct(); // error or message not found if (!$structure->loadStructure($structure_str)) { @@ -1978,7 +2049,7 @@ $o_part->encoding = strtolower($structure->getPartEncoding($part)); $o_part->charset = $structure->getPartCharset($part); } - + // TODO: Add caching for message parts if (!$part) $part = 'TEXT'; @@ -1990,14 +2061,14 @@ return true; // convert charset (if text or message part) - if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { + if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) { // assume default if no charset specified if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') $o_part->charset = $this->default_charset; $body = rcube_charset_convert($body, $o_part->charset); } - + return $body; } @@ -2039,13 +2110,13 @@ { return $this->conn->fetchPartHeader($this->mailbox, $uid, true); } - + /** * Sends the whole message source to stdout * * @param int Message UID - */ + */ function print_raw_body($uid) { $this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true); @@ -2294,7 +2365,7 @@ // unset threads internal cache unset($this->icache['threads']); - + // remove message ids from search set if ($this->search_set && $mailbox == $this->mailbox) { // threads are too complicated to just remove messages from set @@ -2330,21 +2401,21 @@ { $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); - + if (!$msg_count) { return 0; } - + $cleared = $this->conn->clearFolder($mailbox); - + // make sure the message count cache is cleared as well if ($cleared) { - $this->clear_message_cache($mailbox.'.msg'); + $this->clear_message_cache($mailbox.'.msg'); $a_mailbox_cache = $this->get_cache('messagecount'); unset($a_mailbox_cache[$mailbox]); $this->update_cache('messagecount', $a_mailbox_cache); } - + return $cleared; } @@ -2375,7 +2446,7 @@ */ private function _expunge($mailbox, $clear_cache=true, $uids=NULL) { - if ($uids && $this->get_capability('UIDPLUS')) + if ($uids && $this->get_capability('UIDPLUS')) $a_uids = is_array($uids) ? join(',', $uids) : $uids; else $a_uids = NULL; @@ -2386,7 +2457,7 @@ $this->clear_message_cache($mailbox.'.msg'); $this->_clear_messagecount($mailbox); } - + return $result; } @@ -2396,7 +2467,7 @@ * * @param mixed UIDs array or comma-separated list or '*' or '1:*' * @param string Mailbox name - * @return array Two elements array with UIDs converted to list and ALL flag + * @return array Two elements array with UIDs converted to list and ALL flag * @access private */ private function _parse_uids($uids, $mailbox) @@ -2413,7 +2484,7 @@ $uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth'])); else $uids = $this->conn->fetchUIDs($mailbox, $this->search_set); - + // save ID-to-UID mapping in local cache if (is_array($uids)) foreach ($uids as $id => $uid) @@ -2441,7 +2512,7 @@ * @param string Mailbox name * @return int Message ID */ - function get_id($uid, $mbox_name=NULL) + function get_id($uid, $mbox_name=NULL) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_uid2id($uid, $mailbox); @@ -2468,7 +2539,7 @@ * --------------------------------*/ /** - * Public method for mailbox listing. + * Public method for listing subscribed folders * * Converts mailbox name with root dir first * @@ -2482,10 +2553,10 @@ $a_out = array(); $a_mboxes = $this->_list_mailboxes($root, $filter); - foreach ($a_mboxes as $mbox_row) { - $name = $this->mod_mailbox($mbox_row, 'out'); - if (strlen($name)) + foreach ($a_mboxes as $idx => $mbox_row) { + if ($name = $this->mod_mailbox($mbox_row, 'out')) $a_out[] = $name; + unset($a_mboxes[$idx]); } // INBOX should always be available @@ -2508,17 +2579,17 @@ */ private function _list_mailboxes($root='', $filter='*') { - $a_defaults = $a_out = array(); - - // get cached folder list + // get cached folder list $a_mboxes = $this->get_cache('mailboxes'); if (is_array($a_mboxes)) return $a_mboxes; + $a_defaults = $a_out = array(); + // Give plugins a chance to provide a list of mailboxes $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', - array('root'=>$root,'filter'=>$filter)); - + array('root' => $root, 'filter' => $filter, 'mode' => 'LSUB')); + if (isset($data['folders'])) { $a_folders = $data['folders']; } @@ -2526,38 +2597,52 @@ // retrieve list of folders from IMAP server $a_folders = $this->conn->listSubscribed($this->mod_mailbox($root), $filter); } - + if (!is_array($a_folders) || !sizeof($a_folders)) $a_folders = array(); // write mailboxlist to cache $this->update_cache('mailboxes', $a_folders); - + return $a_folders; } /** * Get a list of all folders available on the IMAP server - * + * * @param string IMAP root dir + * @param string Optional filter for mailbox listing * @return array Indexed array with folder names */ - function list_unsubscribed($root='') + function list_unsubscribed($root='', $filter='*') { - static $a_folders; - - if (is_array($a_folders)) - return $a_folders; - - // retrieve list of folders from IMAP server - $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*'); + // Give plugins a chance to provide a list of mailboxes + $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', + array('root' => $root, 'filter' => $filter, 'mode' => 'LIST')); + + if (isset($data['folders'])) { + $a_mboxes = $data['folders']; + } + else { + // retrieve list of folders from IMAP server + $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), $filter); + } + + $a_folders = array(); + if (!is_array($a_mboxes)) + $a_mboxes = array(); // modify names with root dir - foreach ($a_mboxes as $mbox_name) { + foreach ($a_mboxes as $idx => $mbox_name) { if ($name = $this->mod_mailbox($mbox_name, 'out')) $a_folders[] = $name; + unset($a_mboxes[$idx]); } + + // INBOX should always be available + if (!in_array('INBOX', $a_folders)) + array_unshift($a_folders, 'INBOX'); // filter folders and sort them $a_folders = $this->_sort_mailbox_list($a_folders); @@ -2568,14 +2653,14 @@ /** * Get mailbox quota information * added by Nuny - * + * * @return mixed Quota info or False if not supported */ function get_quota() { if ($this->get_capability('QUOTA')) return $this->conn->getQuota(); - + return false; } @@ -2585,7 +2670,7 @@ * * @param array Mailbox name(s) * @return boolean True on success - */ + */ function subscribe($a_mboxes) { if (!is_array($a_mboxes)) @@ -2622,7 +2707,7 @@ function create_mailbox($name, $subscribe=false) { $result = false; - + // reduce mailbox name to 100 chars $name = substr($name, 0, 100); $abs_name = $this->mod_mailbox($name); @@ -2653,11 +2738,11 @@ // make absolute path $mailbox = $this->mod_mailbox($mbox_name); $abs_name = $this->mod_mailbox($name); - + // check if mailbox is subscribed $a_subscribed = $this->_list_mailboxes(); $subscribed = in_array($mailbox, $a_subscribed); - + // unsubscribe folder if ($subscribed) $this->conn->unsubscribe($mailbox); @@ -2667,7 +2752,7 @@ if ($result) { $delm = $this->get_hierarchy_delimiter(); - + // check if mailbox children are subscribed foreach ($a_subscribed as $c_subscribed) if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) { @@ -2678,7 +2763,7 @@ // clear cache $this->clear_message_cache($mailbox.'.msg'); - $this->clear_cache('mailboxes'); + $this->clear_cache('mailboxes'); } // try to subscribe it @@ -2708,23 +2793,23 @@ foreach ($a_mboxes as $mbox_name) { $mailbox = $this->mod_mailbox($mbox_name); $sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''), - $mbox_name . $this->delimiter . '*'); + $mbox_name . $this->delimiter . '*'); // unsubscribe mailbox before deleting $this->conn->unsubscribe($mailbox); // send delete command to server $result = $this->conn->deleteFolder($mailbox); - if ($result >= 0) { + if ($result) { $deleted = true; $this->clear_message_cache($mailbox.'.msg'); } - + foreach ($sub_mboxes as $c_mbox) { if ($c_mbox != 'INBOX') { $this->conn->unsubscribe($c_mbox); $result = $this->conn->deleteFolder($c_mbox); - if ($result >= 0) { + if ($result) { $deleted = true; $this->clear_message_cache($c_mbox.'.msg'); } @@ -2769,14 +2854,19 @@ if ($mbox_name == 'INBOX') return true; + $key = $subscription ? 'subscribed' : 'existing'; + if (is_array($this->icache[$key]) && in_array($mbox_name, $this->icache[$key])) + return true; + if ($subscription) { - if ($a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name)) - return true; + $a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name); } else { $a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_name); - - if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) + } + + if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) { + $this->icache[$key][] = $mbox_name; return true; } } @@ -2803,7 +2893,7 @@ else if (!empty($mbox_name)) // $mode=='out' $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); } - + return $mbox_name; } @@ -2832,7 +2922,7 @@ if (!count($this->cache) && $this->caching_enabled) { return $this->_read_cache_record($key); } - + return $this->cache[$key]; } @@ -2866,7 +2956,7 @@ { if (!$this->caching_enabled) return; - + if ($key===NULL) { foreach ($this->cache as $key => $data) $this->_clear_cache_record($key); @@ -2961,7 +3051,7 @@ "AND cache_key=?", $_SESSION['user_id'], 'IMAP.'.$key); - + unset($this->cache_keys[$key]); } @@ -2970,7 +3060,7 @@ /* -------------------------------- * message caching methods * --------------------------------*/ - + /** * Checks if the cache is up-to-date * @@ -3017,7 +3107,7 @@ // get UID of message with highest index $uid = $this->conn->ID2UID($mailbox, $msg_count); $cache_uid = array_pop($cache_index); - + // uids of highest message matches -> cache seems OK if ($cache_uid == $uid) return 1; @@ -3038,12 +3128,12 @@ private function get_message_cache($key, $from, $to, $sort_field, $sort_order) { $cache_key = "$key:$from:$to:$sort_field:$sort_order"; - + // use idx sort as default sorting if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { $sort_field = 'idx'; } - + if ($this->caching_enabled && !isset($this->cache[$cache_key])) { $this->cache[$cache_key] = array(); $sql_result = $this->db->limitquery( @@ -3077,7 +3167,7 @@ private function &get_cached_message($key, $uid) { $internal_key = 'message'; - + if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { $sql_result = $this->db->query( "SELECT idx, headers, structure". @@ -3102,22 +3192,22 @@ /** * @access private - */ + */ private function get_message_cache_index($key, $force=false, $sort_field='idx', $sort_order='ASC') { static $sa_message_index = array(); - + // empty key -> empty array if (!$this->caching_enabled || empty($key)) return array(); - + if (!empty($sa_message_index[$key]) && !$force) return $sa_message_index[$key]; // use idx sort as default if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) $sort_field = 'idx'; - + $sa_message_index[$key] = array(); $sql_result = $this->db->query( "SELECT idx, uid". @@ -3130,7 +3220,7 @@ while ($sql_arr = $this->db->fetch_assoc($sql_result)) $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid']; - + return $sa_message_index[$key]; } @@ -3149,8 +3239,8 @@ // no further caching if (!$this->caching_enabled) return; - - // check for an existing record (probly headers are cached but structure not) + + // check for an existing record (probably headers are cached but structure not) if (!$force) { $sql_result = $this->db->query( "SELECT message_id". @@ -3201,7 +3291,7 @@ ); } } - + /** * @access private */ @@ -3209,7 +3299,7 @@ { if (!$this->caching_enabled) return; - + $this->db->query( "DELETE FROM ".get_table_name('messages'). " WHERE user_id=?". @@ -3226,7 +3316,7 @@ { if (!$this->caching_enabled) return; - + $this->db->query( "DELETE FROM ".get_table_name('messages'). " WHERE user_id=?". @@ -3242,7 +3332,7 @@ { if (!$this->caching_enabled) return; - + if (!empty($uids) && !is_array($uids)) { if ($uids == '*' || $uids == '1:*') $uids = NULL; @@ -3262,7 +3352,7 @@ if ($sql_arr = $this->db->fetch_assoc($sql_result)) return $sql_arr['minidx']; else - return 0; + return 0; } @@ -3284,7 +3374,7 @@ $out = array(); // Special chars as defined by RFC 822 need to in quoted string (or escaped). $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]'; - + if (!is_array($a)) return $out; @@ -3305,7 +3395,7 @@ $string = $address; else if ($name) $string = $name; - + $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string @@ -3314,45 +3404,8 @@ if ($max && $j==$max) break; } - + return $out; - } - - - /** - * Decode a Microsoft Outlook TNEF part (winmail.dat) - * - * @param object rcube_message_part Message part to decode - * @param string UID of the message - * @return array List of rcube_message_parts extracted from windmail.dat - */ - function tnef_decode(&$part, $uid) - { - if (!isset($part->body)) - $part->body = $this->get_message_part($uid, $part->mime_id, $part); - - require_once('lib/tnef_decoder.inc'); - - $pid = 0; - $tnef_parts = array(); - $tnef_arr = tnef_decode($part->body); - - foreach ($tnef_arr as $winatt) { - $tpart = new rcube_message_part; - $tpart->filename = $winatt["name"]; - $tpart->encoding = 'stream'; - $tpart->ctype_primary = $winatt["type0"]; - $tpart->ctype_secondary = $winatt["type1"]; - $tpart->mimetype = strtolower($winatt["type0"] . "/" . $winatt["type1"]); - $tpart->mime_id = "winmail." . $part->mime_id . ".$pid"; - $tpart->size = $winatt["size"]; - $tpart->body = $winatt['stream']; - - $tnef_parts[] = $tpart; - $pid++; - } - - return $tnef_parts; } @@ -3368,7 +3421,7 @@ $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset); if ($str{0}=='"' && $remove_quotes) $str = str_replace('"', '', $str); - + return $str; } @@ -3388,15 +3441,15 @@ $out = ''; // Iterate instead of recursing, this way if there are too many values we don't have stack overflows - // rfc: all line breaks or other characters not found + // rfc: all line breaks or other characters not found // in the Base64 Alphabet must be ignored by decoding software - // delete all blanks between MIME-lines, differently we can + // delete all blanks between MIME-lines, differently we can // receive unnecessary blanks and broken utf-8 symbols $input = preg_replace("/\?=\s+=\?/", '?==?', $input); // Check if there is stuff to decode if (strpos($input, '=?') !== false) { - // Loop through the string to decode all occurences of =? ?= into the variable $out + // Loop through the string to decode all occurences of =? ?= into the variable $out while(($pos = strpos($input, '=?')) !== false) { // Append everything that is before the text to be decoded $out .= substr($input, 0, $pos); @@ -3424,7 +3477,7 @@ } // no encoding information, use fallback - return rcube_charset_convert($input, + return rcube_charset_convert($input, !empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1')); } @@ -3454,7 +3507,7 @@ return rcube_charset_convert($rest, $a[0]); } - // we dont' know what to do with this + // we dont' know what to do with this return $str; } @@ -3544,15 +3597,15 @@ ksort($a_defaults); $folders = array_merge($a_defaults, array_keys($folders)); - // finally we must rebuild the list to move + // 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) + // set the type of folder name variable (#1485527) $a_out[] = (string) $folder; unset($folders[$key]); - $this->_rsort($folder, $delimiter, $folders, $a_out); + $this->_rsort($folder, $delimiter, $folders, $a_out); } return $a_out; @@ -3566,13 +3619,13 @@ { while (list($key, $name) = each($list)) { if (strpos($name, $folder.$delimiter) === 0) { - // set the type of folder name variable (#1485527) + // set the type of folder name variable (#1485527) $out[] = (string) $name; unset($list[$key]); $this->_rsort($name, $delimiter, $list, $out); } } - reset($list); + reset($list); } @@ -3583,7 +3636,7 @@ { if (!$mbox_name) $mbox_name = $this->mailbox; - + if (!isset($this->uid_id_map[$mbox_name][$uid])) $this->uid_id_map[$mbox_name][$uid] = $this->conn->UID2ID($mbox_name, $uid); @@ -3603,7 +3656,7 @@ $uid = $this->conn->ID2UID($mbox_name, $id); $this->uid_id_map[$mbox_name][$uid] = $id; - + return $uid; } @@ -3658,20 +3711,20 @@ $mode = strtoupper($mode); $a_mailbox_cache = $this->get_cache('messagecount'); - + if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment)) return false; - + // add incremental value to messagecount $a_mailbox_cache[$mailbox][$mode] += $increment; - + // there's something wrong, delete from cache if ($a_mailbox_cache[$mailbox][$mode] < 0) unset($a_mailbox_cache[$mailbox][$mode]); // write back to cache $this->update_cache('messagecount', $a_mailbox_cache); - + return true; } @@ -3713,7 +3766,7 @@ $a_headers[$field] = $value; } } - + return $a_headers; } @@ -3739,13 +3792,13 @@ else $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v)); } - + if (empty($result[$key]['name'])) - $result[$key]['name'] = $result[$key]['address']; + $result[$key]['name'] = $result[$key]['address']; elseif (empty($result[$key]['address'])) $result[$key]['address'] = $result[$key]['name']; } - + return $result; } @@ -3791,7 +3844,7 @@ class rcube_header_sorter { var $sequence_numbers = array(); - + /** * Set the predetermined sort order. * @@ -3813,12 +3866,12 @@ * uksort would work if the keys were the sequence number, but unfortunately * the keys are the UIDs. We'll use uasort instead and dereference the value * to get the sequence number (in the "id" field). - * - * uksort($headers, array($this, "compare_seqnums")); + * + * uksort($headers, array($this, "compare_seqnums")); */ uasort($headers, array($this, "compare_seqnums")); } - + /** * Sort method called by uasort() */ @@ -3827,11 +3880,11 @@ // First get the sequence number from the header object (the 'id' field). $seqa = $a->id; $seqb = $b->id; - + // then find each sequence number in my ordered list $posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1; $posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1; - + // return the relative position as the comparison value return $posa - $posb; } -- Gitblit v1.9.1