From bffca14d964091b3256868bc42bcb9417a72629b Mon Sep 17 00:00:00 2001 From: Aleksander Machniak <alec@alec.pl> Date: Tue, 19 Jan 2016 03:37:29 -0500 Subject: [PATCH] Exit when imagecreatetruecolor() fails --- program/lib/Roundcube/rcube_imap.php | 726 ++++++++++++++++++++++--------------------------------- 1 files changed, 289 insertions(+), 437 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index e4b77a0..5f532a5 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -1,6 +1,6 @@ <?php -/* +/** +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | @@ -56,14 +56,12 @@ */ protected $icache = array(); - protected $list_page = 1; + protected $plugins; protected $delimiter; protected $namespace; protected $sort_field = ''; protected $sort_order = 'DESC'; protected $struct_charset; - protected $uid_id_map = array(); - protected $msg_headers = array(); protected $search_set; protected $search_string = ''; protected $search_charset = ''; @@ -81,7 +79,8 @@ */ public function __construct() { - $this->conn = new rcube_imap_generic(); + $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 @@ -92,7 +91,6 @@ $this->delimiter = $_SESSION['imap_delimiter']; } } - /** * Magic getter for backward compat. @@ -106,17 +104,16 @@ } } - /** * 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 +144,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 +167,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 @@ -189,7 +198,6 @@ return false; } - /** * Close IMAP connection. * Usually done on script shutdown @@ -201,7 +209,6 @@ $this->mcache->close(); } } - /** * Check connection state, connect if not connected. @@ -224,7 +231,6 @@ return $this->is_connected(); } - /** * Checks IMAP connection. * @@ -234,7 +240,6 @@ { return $this->conn->connected(); } - /** * Returns code of last error @@ -246,7 +251,6 @@ return $this->conn->errornum; } - /** * Returns text of last error * @@ -256,7 +260,6 @@ { return $this->conn->error; } - /** * Returns code of last command response @@ -287,7 +290,6 @@ } } - /** * Activate/deactivate debug mode * @@ -299,23 +301,21 @@ $this->conn->setDebug($dbg, array($this, 'debug_handler')); } - /** * Set internal folder reference. * All operations will be perfomed on this folder. * - * @param string $folder Folder name + * @param string $folder Folder name */ public function set_folder($folder) { $this->folder = $folder; } - /** * Save a search result for future message listing methods * - * @param array $set Search set, result from rcube_imap::get_search_set(): + * @param array $set Search set, result from rcube_imap::get_search_set(): * 0 - searching criteria, string * 1 - search result, rcube_result_index|rcube_result_thread * 2 - searching character set, string @@ -338,7 +338,6 @@ } } - /** * Return the saved search set as hash array * @@ -358,7 +357,6 @@ $this->search_sorted, ); } - /** * Returns the IMAP server's capability. @@ -383,14 +381,13 @@ return $_SESSION[$sess_key]; } - /** * Checks the PERMANENTFLAGS capability of the current folder * and returns true if the given flag is supported by the IMAP server * - * @param string $flag Permanentflag name + * @param string $flag Permanentflag name * - * @return boolean True if this flag is supported + * @return boolean True if this flag is supported */ public function check_permflag($flag) { @@ -401,11 +398,10 @@ return $imap_flag && !empty($perm_flags) && in_array_nocase($imap_flag, $perm_flags); } - /** * Returns PERMANENTFLAGS of the specified folder * - * @param string $folder Folder name + * @param string $folder Folder name * * @return array Flags */ @@ -433,25 +429,22 @@ return $permflags; } - /** * Returns the delimiter that is used by the IMAP server for folder separation * - * @return string Delimiter string - * @access public + * @return string Delimiter string */ public function get_hierarchy_delimiter() { return $this->delimiter; } - /** * Get namespace * * @param string $name Namespace array index: personal, other, shared, prefix * - * @return array Namespace data + * @return array Namespace data */ public function get_namespace($name = null) { @@ -465,7 +458,6 @@ return $ns; } - /** * Sets delimiter and namespaces */ @@ -475,7 +467,7 @@ return; } - $config = rcube::get_instance()->config; + $config = rcube::get_instance()->config; $imap_personal = $config->get('imap_ns_personal'); $imap_other = $config->get('imap_ns_other'); $imap_shared = $config->get('imap_ns_shared'); @@ -546,6 +538,51 @@ $_SESSION['imap_delimiter'] = $this->delimiter; } + /** + * Returns IMAP server vendor name + * + * @return string Vendor name + * @since 1.2 + */ + public function get_vendor() + { + if ($_SESSION['imap_vendor'] !== null) { + return $_SESSION['imap_vendor']; + } + + $config = rcube::get_instance()->config; + $imap_vendor = $config->get('imap_vendor'); + + if ($imap_vendor) { + return $imap_vendor; + } + + if (!$this->check_connection()) { + return; + } + + if (($ident = $this->conn->data['ID']) === null) { + $ident = $this->conn->id(array( + 'name' => 'Roundcube', + 'version' => RCUBE_VERSION, + 'php' => PHP_VERSION, + 'os' => PHP_OS, + )); + } + + $vendor = (string) (!empty($ident) ? $ident['name'] : ''); + $ident = strtolower($vendor . ' ' . $this->conn->data['GREETING']); + $vendors = array('cyrus', 'dovecot', 'uw-imap', 'gmail', 'hmail'); + + foreach ($vendors as $v) { + if (strpos($ident, $v) !== false) { + $vendor = $v; + break; + } + } + + return $_SESSION['imap_vendor'] = $vendor; + } /** * Get message count for a specific folder @@ -556,7 +593,7 @@ * @param boolean $status Enables storing folder status info (max UID/count), * required for folder_status() * - * @return int Number of messages + * @return int Number of messages */ public function count($folder='', $mode='ALL', $force=false, $status=true) { @@ -567,29 +604,31 @@ return $this->countmessages($folder, $mode, $force, $status); } - /** - * protected method for getting nr of messages + * Protected method for getting number of messages * - * @param string $folder Folder name - * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] - * @param boolean $force Force reading from server and update cache - * @param boolean $status Enables storing folder status info (max UID/count), - * required for folder_status() + * @param string $folder Folder name + * @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT|EXISTS] + * @param boolean $force Force reading from server and update cache + * @param boolean $status Enables storing folder status info (max UID/count), + * required for folder_status() + * @param boolean $no_search Ignore current search result * * @return int Number of messages * @see rcube_imap::count() */ - protected function countmessages($folder, $mode='ALL', $force=false, $status=true) + protected function countmessages($folder, $mode = 'ALL', $force = false, $status = true, $no_search = false) { $mode = strtoupper($mode); - // count search set, assume search set is always up-to-date (don't check $force flag) - if ($this->search_string && $folder == $this->folder && ($mode == 'ALL' || $mode == 'THREADS')) { + // Count search set, assume search set is always up-to-date (don't check $force flag) + // @TODO: this could be handled in more reliable way, e.g. a separate method + // maybe in rcube_imap_search + if (!$no_search && $this->search_string && $folder == $this->folder) { if ($mode == 'ALL') { return $this->search_set->count_messages(); } - else { + else if ($mode == 'THREADS') { return $this->search_set->count(); } } @@ -682,7 +721,6 @@ return (int)$count; } - /** * Public method for listing message flags * @@ -717,7 +755,6 @@ return $result; } - /** * Public method for listing headers * @@ -727,7 +764,7 @@ * @param string $sort_order Sort order [ASC|DESC] * @param int $slice Number of slice items to extract from result array * - * @return array Indexed array with message header objects + * @return array Indexed array with message header objects */ public function list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { @@ -738,7 +775,6 @@ return $this->_list_messages($folder, $page, $sort_field, $sort_order, $slice); } - /** * protected method for listing message headers * @@ -748,8 +784,8 @@ * @param string $sort_order Sort order [ASC|DESC] * @param int $slice Number of slice items to extract from result array * - * @return array Indexed array with message header objects - * @see rcube_imap::list_messages + * @return array Indexed array with message header objects + * @see rcube_imap::list_messages */ protected function _list_messages($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) { @@ -761,7 +797,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); } @@ -792,16 +828,15 @@ return array_values($a_msg_headers); } - /** * protected method for listing message headers using threads * - * @param string $folder Folder name - * @param int $page Current page to list - * @param int $slice Number of slice items to extract from result array + * @param string $folder Folder name + * @param int $page Current page to list + * @param int $slice Number of slice items to extract from result array * - * @return array Indexed array with message header objects - * @see rcube_imap::list_messages + * @return array Indexed array with message header objects + * @see rcube_imap::list_messages */ protected function list_thread_messages($folder, $page, $slice=0) { @@ -819,7 +854,7 @@ /** * Method for fetching threads data * - * @param string $folder Folder name + * @param string $folder Folder name * * @return rcube_imap_thread Thread data object */ @@ -843,11 +878,10 @@ return $this->icache['threads'] = $result; } - /** * Method for direct fetching of threads data * - * @param string $folder Folder name + * @param string $folder Folder name * * @return rcube_imap_thread Thread data object */ @@ -862,7 +896,6 @@ $this->options['skip_deleted'] ? 'UNDELETED' : '', true); } - /** * protected method for fetching threaded messages headers * @@ -871,7 +904,7 @@ * @param int $page List page number * @param int $slice Number of threads to slice * - * @return array Messages headers + * @return array Messages headers */ protected function fetch_thread_headers($folder, $threads, $page, $slice=0) { @@ -901,13 +934,12 @@ return array_values($a_msg_headers); } - /** * protected method for setting threaded messages flags: * depth, has_children and unread_children * - * @param array $headers Reference to headers array indexed by message UID - * @param rcube_result_thread $threads Threads data object + * @param array $headers Reference to headers array indexed by message UID + * @param rcube_result_thread $threads Threads data object * * @return array Message headers array indexed by message UID */ @@ -933,7 +965,6 @@ } } - /** * protected method for listing a set of message headers (search results) * @@ -941,7 +972,7 @@ * @param int $page Current page to list * @param int $slice Number of slice items to extract from result array * - * @return array Indexed array with message header objects + * @return array Indexed array with message header objects */ protected function list_search_messages($folder, $page, $slice=0) { @@ -951,7 +982,7 @@ // gather messages from a multi-folder search if ($this->search_set->multi) { - $page_size = $this->page_size; + $page_size = $this->page_size; $sort_field = $this->sort_field; $search_set = $this->search_set; @@ -962,23 +993,26 @@ $slice_length = min($page_size, $cnt - $from); // fetch resultset headers, sort and slice them - if (!empty($sort_field)) { + if (!empty($sort_field) && $search_set->get_parameters('SORT') != $sort_field) { $this->sort_field = null; - $this->page_size = 1000; // fetch up to 1000 matching messages per folder - $this->threading = false; + $this->page_size = 1000; // fetch up to 1000 matching messages per folder + $this->threading = false; $a_msg_headers = array(); foreach ($search_set->sets as $resultset) { if (!$resultset->is_empty()) { - $this->search_set = $resultset; + $this->search_set = $resultset; $this->search_threads = $resultset instanceof rcube_result_thread; - $a_msg_headers = array_merge($a_msg_headers, $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1)); + + $a_headers = $this->list_search_messages($resultset->get_parameters('MAILBOX'), 1); + $a_msg_headers = array_merge($a_msg_headers, $a_headers); + unset($a_headers); } } // sort headers if (!empty($a_msg_headers)) { - $a_msg_headers = $this->conn->sortHeaders($a_msg_headers, $sort_field, $this->sort_order); + $a_msg_headers = rcube_imap_generic::sortHeaders($a_msg_headers, $sort_field, $this->sort_order); } // store (sorted) message index @@ -1012,7 +1046,7 @@ // restore members $this->sort_field = $sort_field; - $this->page_size = $page_size; + $this->page_size = $page_size; $this->search_set = $search_set; return $a_msg_headers; @@ -1106,12 +1140,8 @@ return array(); } - if (!$this->check_connection()) { - return array(); - } - // if not already sorted - $a_msg_headers = $this->conn->sortHeaders( + $a_msg_headers = rcube_imap_generic::sortHeaders( $a_msg_headers, $this->sort_field, $this->sort_order); // only return the requested part of the set @@ -1126,7 +1156,6 @@ } } - /** * protected method for listing a set of threaded message headers (search results) * @@ -1134,7 +1163,7 @@ * @param int $page Current page to list * @param int $slice Number of slice items to extract from result array * - * @return array Indexed array with message header objects + * @return array Indexed array with message header objects * @see rcube_imap::list_search_messages() */ protected function list_search_thread_messages($folder, $page, $slice=0) @@ -1149,7 +1178,6 @@ return $this->fetch_thread_headers($folder, clone $this->search_set, $page, $slice); } - /** * Fetches messages headers (by UID) @@ -1198,7 +1226,6 @@ return $a_msg_headers; } - /** * Returns current status of a folder (compared to the last time use) * @@ -1218,7 +1245,7 @@ $old = $this->get_folder_stats($folder); // refresh message count -> will update - $this->countmessages($folder, 'ALL', true); + $this->countmessages($folder, 'ALL', true, true, true); $result = 0; @@ -1246,7 +1273,6 @@ return $result; } - /** * Stores folder statistic data in session * @TODO: move to separate DB table (cache?) @@ -1259,7 +1285,6 @@ { $_SESSION['folders'][$folder][$name] = $data; } - /** * Gets folder statistic data @@ -1276,7 +1301,6 @@ return array(); } - /** * Return sorted list of message UIDs @@ -1355,7 +1379,6 @@ return $this->index_direct($folder, $this->sort_field, $this->sort_order); } - /** * Return sorted list of message UIDs ignoring current search settings. * Doesn't uses cache by default. @@ -1370,7 +1393,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 @@ -1423,7 +1446,6 @@ return $index; } - /** * Return index of threaded message UIDs * @@ -1454,7 +1476,6 @@ return $threads; } - /** * Sort threaded result, using THREAD=REFS method if available. * If not, use any method and re-sort the result in THREAD=REFS way. @@ -1484,7 +1505,6 @@ } } - /** * Invoke search request to IMAP server * @@ -1506,7 +1526,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, @@ -1560,7 +1580,6 @@ return $results; } - /** * Direct (real and simple) SEARCH request (without result sorting and caching). * @@ -1594,7 +1613,6 @@ return $index; } - /** * protected search method @@ -1672,7 +1690,6 @@ return $messages; } - /** * Converts charset of search criteria string * @@ -1712,7 +1729,6 @@ return $res; } - /** * Refresh saved search set * @@ -1744,7 +1760,6 @@ } } } - /** * Return message headers object of a specific message @@ -1784,13 +1799,12 @@ return $headers; } - /** * Fetch message headers and body structure from the IMAP server and build - * an object structure similar to the one generated by PEAR::Mail_mimeDecode + * an object structure. * - * @param int $uid Message UID to fetch - * @param string $folder Folder to read from + * @param int $uid Message UID to fetch + * @param string $folder Folder to read from * * @return object rcube_message_header Message data */ @@ -1869,8 +1883,8 @@ $structure[1] = $m[2]; } else { - // Try to parse the message using Mail_mimeDecode package - // We need a better solution, Mail_mimeDecode parses message + // Try to parse the message using rcube_mime_decode. + // We need a better solution, it parses message // in memory, which wouldn't work for very big messages, // (it uses up to 10x more memory than the message size) // it's also buggy and not actively developed @@ -1910,7 +1924,6 @@ return $this->icache['message'] = $headers; } - /** * Build message part object * @@ -1918,7 +1931,7 @@ * @param int $count * @param string $parent */ - protected function structure_part($part, $count=0, $parent='', $mime_headers=null) + protected function structure_part($part, $count = 0, $parent = '', $mime_headers = null) { $struct = new rcube_message_part; $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; @@ -1943,6 +1956,16 @@ for ($i=1; $i<count($part); $i++) { if (!is_array($part[$i])) { $struct->ctype_secondary = strtolower($part[$i]); + + // read content type parameters + if (is_array($part[$i+1])) { + $struct->ctype_parameters = array(); + for ($j=0; $j<count($part[$i+1]); $j+=2) { + $param = strtolower($part[$i+1][$j]); + $struct->ctype_parameters[$param] = $part[$i+1][$j+1]; + } + } + break; } } @@ -2126,14 +2149,13 @@ return $struct; } - /** * Set attachment filename from message part structure * - * @param rcube_message_part $part Part object - * @param string $headers Part's raw headers + * @param rcube_message_part $part Part object + * @param string $headers Part's raw headers */ - protected function set_part_filename(&$part, $headers=null) + protected function set_part_filename(&$part, $headers = null) { if (!empty($part->d_parameters['filename'])) { $filename_mime = $part->d_parameters['filename']; @@ -2261,11 +2283,10 @@ } } - /** * Get charset name from message structure (first part) * - * @param array $structure Message structure + * @param array $structure Message structure * * @return string Charset name */ @@ -2294,7 +2315,8 @@ * * @return string Message/part body if not printed */ - public function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false, $max_bytes=0, $formatted=true) + public function get_message_part($uid, $part = 1, $o_part = null, $print = null, $fp = null, + $skip_charset_conv = false, $max_bytes = 0, $formatted = true) { if (!$this->check_connection()) { return null; @@ -2346,42 +2368,41 @@ return $body; } - /** * Returns the whole message source as string (or saves to a file) * - * @param int $uid Message UID - * @param resource $fp File pointer to save the message + * @param int $uid Message UID + * @param resource $fp File pointer to save the message + * @param string $part Optional message part ID * * @return string Message source string */ - public function get_raw_body($uid, $fp=null) + public function get_raw_body($uid, $fp=null, $part = null) { if (!$this->check_connection()) { return null; } return $this->conn->handlePartBody($this->folder, $uid, - true, null, null, false, $fp); + true, $part, null, false, $fp); } - /** * Returns the message headers as string * - * @param int $uid Message UID + * @param int $uid Message UID + * @param string $part Optional message part ID * * @return string Message headers string */ - public function get_raw_headers($uid) + public function get_raw_headers($uid, $part = null) { if (!$this->check_connection()) { return null; } - return $this->conn->fetchPartHeader($this->folder, $uid, true); + return $this->conn->fetchPartHeader($this->folder, $uid, true, $part); } - /** * Sends the whole message source to stdout @@ -2398,13 +2419,12 @@ $this->conn->handlePartBody($this->folder, $uid, true, null, null, true, null, $formatted); } - /** * Set message flag to one or several messages * * @param mixed $uids Message UIDs as array or comma-separated string, or '*' * @param string $flag Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT - * @param string $folder Folder name + * @param string $folder Folder name * @param boolean $skip_cache True to skip message cache clean up * * @return boolean Operation status @@ -2458,7 +2478,6 @@ return $result; } - /** * Append a mail message (source) to a specific folder * @@ -2501,7 +2520,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, @@ -2515,7 +2534,6 @@ return $saved; } - /** * Move a message from one folder to another @@ -2594,7 +2612,6 @@ return $moved; } - /** * Copy a message from one folder to another * @@ -2631,7 +2648,6 @@ return $copied; } - /** * Mark messages as deleted and expunge them * @@ -2664,7 +2680,6 @@ // really deleted from the folder $this->expunge_message($uids, $folder, false); $this->clear_messagecount($folder); - unset($this->uid_id_map[$folder]); // unset threads internal cache unset($this->icache['threads']); @@ -2688,7 +2703,6 @@ return $deleted; } - /** * Send IMAP expunge command and clear cache @@ -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'])) { @@ -2797,7 +2811,7 @@ } // INBOX should always be available - if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) { + if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) { array_unshift($a_mboxes, 'INBOX'); } @@ -2812,15 +2826,14 @@ return $a_mboxes; } - /** * Method for direct folders listing (LSUB) * - * @param string $root Optional root folder - * @param string $name Optional name pattern + * @param string $root Optional root folder + * @param string $name Optional name pattern * - * @return array List of subscribed folders - * @see rcube_imap::list_folders_subscribed() + * @return array List of subscribed folders + * @see rcube_imap::list_folders_subscribed() */ public function list_folders_subscribed_direct($root='', $name='*') { @@ -2835,42 +2848,47 @@ $list_extended = !$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED'); if ($list_extended) { // This will also set folder options, LSUB doesn't do that - $a_folders = $this->conn->listMailboxes($root, $name, + $result = $this->conn->listMailboxes($root, $name, NULL, array('SUBSCRIBED')); } else { // retrieve list of folders from IMAP server using LSUB - $a_folders = $this->conn->listSubscribed($root, $name); + $result = $this->conn->listSubscribed($root, $name); } - if (!is_array($a_folders)) { + if (!is_array($result)) { return array(); } // #1486796: some server configurations doesn't return folders in all namespaces if ($root == '' && $name == '*' && $config->get('imap_force_ns')) { - $this->list_folders_update($a_folders, ($list_extended ? 'ext-' : '') . 'subscribed'); + $this->list_folders_update($result, ($list_extended ? 'ext-' : '') . 'subscribed'); + } + + // Remove hidden folders + if ($config->get('imap_skip_hidden_folders')) { + $result = array_filter($result, function($v) { return $v[0] != '.'; }); } if ($list_extended) { // unsubscribe non-existent folders, remove from the list - if (is_array($a_folders) && $name == '*' && !empty($this->conn->data['LIST'])) { - foreach ($a_folders as $idx => $folder) { + if ($name == '*' && !empty($this->conn->data['LIST'])) { + foreach ($result 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]); + unset($result[$idx]); } } } } else { // unsubscribe non-existent folders, remove them from the list - if (is_array($a_folders) && !empty($a_folders) && $name == '*') { + if (!empty($result) && $name == '*') { $existing = $this->list_folders($root, $name); - $nonexisting = array_diff($a_folders, $existing); - $a_folders = array_diff($a_folders, $nonexisting); + $nonexisting = array_diff($result, $existing); + $result = array_diff($result, $nonexisting); foreach ($nonexisting as $folder) { $this->conn->unsubscribe($folder); @@ -2878,9 +2896,8 @@ } } - return $a_folders; + return $result; } - /** * Get a list of all folders available on the server @@ -2909,7 +2926,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'])) { @@ -2925,7 +2942,7 @@ } // INBOX should always be available - if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) { + if (!strlen($root) && (!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) { array_unshift($a_mboxes, 'INBOX'); } @@ -2950,15 +2967,14 @@ return $a_mboxes; } - /** * Method for direct folders listing (LIST) * - * @param string $root Optional root folder - * @param string $name Optional name pattern + * @param string $root Optional root folder + * @param string $name Optional name pattern * - * @return array List of folders - * @see rcube_imap::list_folders() + * @return array List of folders + * @see rcube_imap::list_folders() */ public function list_folders_direct($root='', $name='*') { @@ -2979,18 +2995,22 @@ $this->list_folders_update($result); } + // Remove hidden folders + if ($config->get('imap_skip_hidden_folders')) { + $result = array_filter($result, function($v) { return $v[0] != '.'; }); + } + return $result; } - /** * Fix folders list by adding folders from other namespaces. * Needed on some servers eg. Courier IMAP * - * @param array $result Reference to folders list - * @param string $type Listing type (ext-subscribed, subscribed or all) + * @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(); @@ -3038,7 +3058,6 @@ } } - /** * Filter the given list of folders according to access rights * @@ -3064,22 +3083,21 @@ return $a_folders; } - /** * 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; } - /** * Get folder size (size of all messages in a folder) @@ -3090,8 +3108,22 @@ */ public function folder_size($folder) { + if (!strlen($folder)) { + return false; + } + if (!$this->check_connection()) { return 0; + } + + // On Cyrus we can use special folder annotation, which should be much faster + if ($this->get_vendor() == 'cyrus') { + $idx = '/shared/vendor/cmu/cyrus-imapd/size'; + $result = $this->get_metadata($folder, $idx, array(), true); + + if (!empty($result) && is_numeric($result[$folder][$idx])) { + return $result[$folder][$idx]; + } } // @TODO: could we try to use QUOTA here? @@ -3103,7 +3135,6 @@ return $result; } - /** * Subscribe to a specific folder(s) @@ -3118,7 +3149,6 @@ return $this->change_subscription($folders, 'subscribe'); } - /** * Unsubscribe folder(s) * @@ -3131,7 +3161,6 @@ // let this common function do the main work return $this->change_subscription($folders, 'unsubscribe'); } - /** * Create a new folder on the server and register it in local cache @@ -3162,7 +3191,6 @@ return $result; } - /** * Set a new name to an existing folder @@ -3223,7 +3251,6 @@ return $result; } - /** * Remove folder from server * @@ -3271,7 +3298,6 @@ return $result; } - /** * Detect special folder associations stored in storage backend */ @@ -3297,12 +3323,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; + } } } } @@ -3313,7 +3341,6 @@ return array_merge($result, $special); } - /** * Set special folder associations stored in storage backend @@ -3353,7 +3380,6 @@ return true; } - /** * Checks if folder exists and is subscribed * @@ -3381,7 +3407,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); } @@ -3406,7 +3432,6 @@ return false; } - /** * Returns the namespace where the folder is in @@ -3437,7 +3462,6 @@ return 'personal'; } - /** * Modify folder name according to namespace. @@ -3477,7 +3501,6 @@ return $folder; } - /** * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors * @@ -3509,7 +3532,6 @@ return is_array($opts) ? $opts : array(); } - /** * Gets connection (and current folder) data: UIDVALIDITY, EXISTS, RECENT, @@ -3549,7 +3571,6 @@ return $data; } - /** * Returns extended information about the folder @@ -3646,7 +3667,6 @@ return $options; } - /** * Synchronizes messages cache. * @@ -3658,7 +3678,6 @@ $mcache->synchronize($folder); } } - /** * Get message header names for rcube_imap_generic::fetchHeader(s) @@ -3711,7 +3730,6 @@ return $this->conn->setACL($folder, $user, $acl); } - /** * Removes any <identifier,rights> pair for the * specified user from the ACL for the specified @@ -3736,7 +3754,6 @@ return $this->conn->deleteACL($folder, $user); } - /** * Returns the access control list for folder (GETACL) * @@ -3757,7 +3774,6 @@ return $this->conn->getACL($folder); } - /** * Returns information about what rights can be granted to the @@ -3782,7 +3798,6 @@ return $this->conn->listRights($folder, $user); } - /** * Returns the set of rights that the current user has to * folder (MYRIGHTS) @@ -3804,7 +3819,6 @@ return $this->conn->myRights($folder); } - /** * Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION) @@ -3839,7 +3853,6 @@ return false; } - /** * Unsets IMAP metadata/annotations (SETMETADATA/SETANNOTATION) * @@ -3873,34 +3886,36 @@ return false; } - /** * Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION) * - * @param string $folder Folder name (empty for server metadata) - * @param array $entries Entries - * @param array $options Command options (with MAXSIZE and DEPTH keys) + * @param string $folder Folder name (empty for server metadata) + * @param array $entries Entries + * @param array $options Command options (with MAXSIZE and DEPTH keys) + * @param bool $force Disables cache use * * @return array Metadata entry-value hash array on success, NULL on error * @since 0.5-beta */ - public function get_metadata($folder, $entries, $options=array()) + public function get_metadata($folder, $entries, $options = array(), $force = false) { - $entries = (array)$entries; + $entries = (array) $entries; - // create cache key - // @TODO: this is the simplest solution, but we do the same with folders list - // maybe we should store data per-entry and merge on request - sort($options); - sort($entries); - $cache_key = 'mailboxes.metadata.' . $folder; - $cache_key .= '.' . md5(serialize($options).serialize($entries)); + if (!$force) { + // create cache key + // @TODO: this is the simplest solution, but we do the same with folders list + // maybe we should store data per-entry and merge on request + sort($options); + sort($entries); + $cache_key = 'mailboxes.metadata.' . $folder; + $cache_key .= '.' . md5(serialize($options).serialize($entries)); - // get cached data - $cached_data = $this->get_cache($cache_key); + // get cached data + $cached_data = $this->get_cache($cache_key); - if (is_array($cached_data)) { - return $cached_data; + if (is_array($cached_data)) { + return $cached_data; + } } if (!$this->check_connection()) { @@ -3924,20 +3939,28 @@ // @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); + } } } } if (isset($res)) { - $this->update_cache($cache_key, $res); + if (!$force) { + $this->update_cache($cache_key, $res); + } + return $res; } - - return null; } - /** * Converts the METADATA extension entry name into the correct @@ -3957,7 +3980,6 @@ } // @TODO: log error - return null; } @@ -4068,7 +4090,6 @@ } } - /** * Getter for messages cache object */ @@ -4087,7 +4108,6 @@ return $this->mcache; } - /** * Clears the messages cache. * @@ -4100,7 +4120,6 @@ $mcache->clear($folder, $uids); } } - /** * Delete outdated cache entries @@ -4131,7 +4150,6 @@ } } - /** * Sort folders first by default folders and then in alphabethical order * @@ -4142,20 +4160,14 @@ */ 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 '.' + // convert names to UTF-8 foreach ($a_folders as $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 { - unset($folders[$idx]); - } + // 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'); } // sort folders @@ -4170,29 +4182,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 . $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 @@ -4200,9 +4220,8 @@ */ 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]; @@ -4215,12 +4234,11 @@ } } - /** * Find UID of the specified message sequence ID * - * @param int $id Message (sequence) ID - * @param string $folder Folder name + * @param int $id Message (sequence) ID + * @param string $folder Folder name * * @return int Message UID */ @@ -4230,43 +4248,28 @@ $folder = $this->folder; } - if ($uid = array_search($id, (array)$this->uid_id_map[$folder])) { - return $uid; - } - if (!$this->check_connection()) { return null; } - $uid = $this->conn->ID2UID($folder, $id); - - $this->uid_id_map[$folder][$uid] = $id; - - return $uid; + return $this->conn->ID2UID($folder, $id); } - /** * Subscribe/unsubscribe a list of folders and update local cache */ protected function change_subscription($folders, $mode) { - $updated = false; + $updated = 0; + $folders = (array) $folders; if (!empty($folders)) { if (!$this->check_connection()) { return false; } - foreach ((array)$folders as $i => $folder) { - $folders[$i] = $folder; - - if ($mode == 'subscribe') { - $updated = $this->conn->subscribe($folder); - } - else if ($mode == 'unsubscribe') { - $updated = $this->conn->unsubscribe($folder); - } + foreach ($folders as $folder) { + $updated += (int) $this->conn->{$mode}($folder); } } @@ -4275,9 +4278,8 @@ $this->clear_cache('mailboxes', true); } - return $updated; + return $updated == count($folders) ? true : false; } - /** * Increde/decrese messagecount for a specific folder @@ -4309,7 +4311,6 @@ return true; } - /** * Remove messagecount of a specific folder from cache */ @@ -4327,7 +4328,6 @@ $this->update_cache('messagecount', $a_folder_cache); } } - /** * Converts date string/object into IMAP date/time format @@ -4351,159 +4351,11 @@ return $date->format('d-M-Y H:i:s O'); } - /** * This is our own debug handler for the IMAP connection - * @access public */ public function debug_handler(&$imap, $message) { rcube::write_log('imap', $message); } - - - /** - * Deprecated methods (to be removed) - */ - - public function decode_address_list($input, $max = null, $decode = true, $fallback = null) - { - return rcube_mime::decode_address_list($input, $max, $decode, $fallback); - } - - public function decode_header($input, $fallback = null) - { - return rcube_mime::decode_mime_string((string)$input, $fallback); - } - - public static function decode_mime_string($input, $fallback = null) - { - return rcube_mime::decode_mime_string($input, $fallback); - } - - public function mime_decode($input, $encoding = '7bit') - { - return rcube_mime::decode($input, $encoding); - } - - public static function explode_header_string($separator, $str, $remove_comments = false) - { - return rcube_mime::explode_header_string($separator, $str, $remove_comments); - } - - public function select_mailbox($mailbox) - { - // do nothing - } - - public function set_mailbox($folder) - { - $this->set_folder($folder); - } - - public function get_mailbox_name() - { - return $this->get_folder(); - } - - public function list_headers($folder='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) - { - return $this->list_messages($folder, $page, $sort_field, $sort_order, $slice); - } - - public function get_headers($uid, $folder = null, $force = false) - { - return $this->get_message_headers($uid, $folder, $force); - } - - public function mailbox_status($folder = null) - { - return $this->folder_status($folder); - } - - public function message_index($folder = '', $sort_field = NULL, $sort_order = NULL) - { - return $this->index($folder, $sort_field, $sort_order); - } - - public function message_index_direct($folder, $sort_field = null, $sort_order = null) - { - return $this->index_direct($folder, $sort_field, $sort_order); - } - - public function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false) - { - return $this->list_folders_subscribed($root, $name, $filter, $rights, $skip_sort); - } - - public function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false) - { - return $this->list_folders($root, $name, $filter, $rights, $skip_sort); - } - - public function get_mailbox_size($folder) - { - return $this->folder_size($folder); - } - - public function create_mailbox($folder, $subscribe=false) - { - return $this->create_folder($folder, $subscribe); - } - - public function rename_mailbox($folder, $new_name) - { - return $this->rename_folder($folder, $new_name); - } - - function delete_mailbox($folder) - { - return $this->delete_folder($folder); - } - - function clear_mailbox($folder = null) - { - return $this->clear_folder($folder); - } - - public function mailbox_exists($folder, $subscription=false) - { - return $this->folder_exists($folder, $subscription); - } - - public function mailbox_namespace($folder) - { - return $this->folder_namespace($folder); - } - - public function mod_mailbox($folder, $mode = 'out') - { - return $this->mod_folder($folder, $mode); - } - - public function mailbox_attributes($folder, $force=false) - { - return $this->folder_attributes($folder, $force); - } - - public function mailbox_data($folder) - { - return $this->folder_data($folder); - } - - public function mailbox_info($folder) - { - return $this->folder_info($folder); - } - - public function mailbox_sync($folder) - { - return $this->folder_sync($folder); - } - - public function expunge($folder='', $clear_cache=true) - { - return $this->expunge_folder($folder, $clear_cache); - } - } -- Gitblit v1.9.1