CHANGELOG | ●●●●● patch | view | raw | blame | history | |
installer/check.php | ●●●●● patch | view | raw | blame | history | |
program/include/rcmail.php | ●●●●● patch | view | raw | blame | history | |
program/include/rcube_imap.php | ●●●●● patch | view | raw | blame | history | |
program/include/rcube_imap_generic.php | ●●●●● patch | view | raw | blame | history | |
program/lib/imap.inc | ●●●●● patch | view | raw | blame | history |
CHANGELOG
@@ -1,6 +1,8 @@ CHANGELOG RoundCube Webmail =========================== - Fix bugs on unexpected IMAP connection close (#1486190, #1486270) - Iloha's imap.inc rewritten into rcube_imap_generic class - Added contact groups in address book (not finished yet) - Added PageUp/PageDown/Home/End keys support on lists (#1486430) - Added possibility to select all messages in a folder (#1484756) installer/check.php
@@ -1,25 +1,47 @@ <form action="index.php" method="get"> <?php $required_php_exts = array('PCRE' => 'pcre', 'DOM' => 'dom', 'Session' => 'session', 'XML' => 'xml', 'JSON' => 'json'); $optional_php_exts = array('FileInfo' => 'fileinfo', 'Libiconv' => 'iconv', 'Multibyte' => 'mbstring', 'OpenSSL' => 'openssl', 'Mcrypt' => 'mcrypt', $required_php_exts = array( 'PCRE' => 'pcre', 'DOM' => 'dom', 'Session' => 'session', 'XML' => 'xml', 'JSON' => 'json' ); $required_libs = array('PEAR' => 'PEAR.php', 'MDB2' => 'MDB2.php', 'Net_SMTP' => 'Net/SMTP.php', 'Mail_mime' => 'Mail/mime.php', 'iilConnection' => 'lib/imap.inc'); $optional_php_exts = array( 'FileInfo' => 'fileinfo', 'Libiconv' => 'iconv', 'Multibyte' => 'mbstring', 'OpenSSL' => 'openssl', 'Mcrypt' => 'mcrypt', ); $supported_dbs = array('MySQL' => 'mysql', 'MySQLi' => 'mysqli', 'PostgreSQL' => 'pgsql', 'SQLite (v2)' => 'sqlite'); $required_libs = array( 'PEAR' => 'PEAR.php', 'MDB2' => 'MDB2.php', 'Net_SMTP' => 'Net/SMTP.php', 'Mail_mime' => 'Mail/mime.php', ); $ini_checks = array('file_uploads' => 1, 'session.auto_start' => 0, 'zend.ze1_compatibility_mode' => 0, 'mbstring.func_overload' => 0, 'suhosin.session.encrypt' => 0); $supported_dbs = array( 'MySQL' => 'mysql', 'MySQLi' => 'mysqli', 'PostgreSQL' => 'pgsql', 'SQLite (v2)' => 'sqlite', ); $optional_checks = array('date.timezone' => '-NOTEMPTY-'); $ini_checks = array( 'file_uploads' => 1, 'session.auto_start' => 0, 'zend.ze1_compatibility_mode' => 0, 'mbstring.func_overload' => 0, 'suhosin.session.encrypt' => 0, ); $optional_checks = array( 'date.timezone' => '-NOTEMPTY-', ); $source_urls = array( 'Sockets' => 'http://www.php.net/manual/en/book.sockets.php', program/include/rcmail.php
@@ -456,21 +456,21 @@ */ public function imap_connect() { $conn = false; if (!$this->imap) $this->imap_init(); if ($_SESSION['imap_host'] && !$this->imap->conn) { if (!($conn = $this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']))) { if ($_SESSION['imap_host'] && !$this->imap->conn->connected()) { if (!$this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl'])) { if ($this->output) $this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error'); } else { $this->set_imap_prop(); return $this->imap->conn; } } return $conn; return false; } @@ -957,10 +957,8 @@ */ public function shutdown() { if (is_object($this->imap)) { if (is_object($this->imap)) $this->imap->close(); $this->imap->write_cache(); } if (is_object($this->smtp)) $this->smtp->disconnect(); program/include/rcube_imap.php
@@ -9,11 +9,11 @@ | Licensed under the GNU GPL | | | | PURPOSE: | | IMAP wrapper that implements the Iloha IMAP Library (IIL) | | See http://ilohamail.org/ for details | | IMAP Engine | | | +-----------------------------------------------------------------------+ | Author: Thomas Bruederli <roundcube@gmail.com> | | Author: Aleksander Machniak <alec@alec.pl> | +-----------------------------------------------------------------------+ $Id$ @@ -21,10 +21,6 @@ */ /* * Obtain classes from the Iloha IMAP library */ require_once('lib/imap.inc'); require_once('lib/mime.inc'); require_once('lib/tnef_decoder.inc'); @@ -32,12 +28,9 @@ /** * Interface class for accessing an IMAP server * * This is a wrapper that implements the Iloha IMAP Library (IIL) * * @package Mail * @author Thomas Bruederli <roundcube@gmail.com> * @version 1.5 * @link http://ilohamail.org * @version 1.6 */ class rcube_imap { @@ -50,7 +43,7 @@ public $delimiter = NULL; public $threading = false; public $fetch_add_headers = ''; public $conn; public $conn; // rcube_imap_generic object private $db; private $root_ns = ''; @@ -61,7 +54,6 @@ private $default_charset = 'ISO-8859-1'; private $struct_charset = NULL; private $default_folders = array('INBOX'); private $default_folders_lc = array('inbox'); private $icache = array(); private $cache = array(); private $cache_keys = array(); @@ -86,6 +78,7 @@ function __construct($db_conn) { $this->db = $db_conn; $this->conn = new rcube_imap_generic(); } @@ -102,11 +95,9 @@ */ function connect($host, $user, $pass, $port=143, $use_ssl=null) { global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE; // check for Open-SSL support in PHP build if ($use_ssl && extension_loaded('openssl')) $ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; $this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl; else if ($use_ssl) { raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'line' => __LINE__, @@ -114,17 +105,18 @@ $port = 143; } $ICL_PORT = $port; $IMAP_USE_INTERNAL_DATE = false; $this->options['port'] = $port; $attempt = 0; do { $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); $data = rcmail::get_instance()->plugins->exec_hook('imap_connect', array('host' => $host, 'user' => $user, 'attempt' => ++$attempt)); if (!empty($data['pass'])) $pass = $data['pass']; $this->conn = iil_Connect($data['host'], $data['user'], $pass, $this->options); } while(!$this->conn && $data['retry']); $this->conn->connect($data['host'], $data['user'], $pass, $this->options); } while(!$this->conn->connected() && $data['retry']); $this->host = $data['host']; $this->user = $data['user']; @@ -133,25 +125,23 @@ $this->ssl = $use_ssl; // print trace messages if ($this->conn && ($this->debug_level & 8)) if ($this->conn->connected()) { if ($this->conn->message && ($this->debug_level & 8)) { console($this->conn->message); // write error log else if (!$this->conn && $GLOBALS['iil_error']) { $this->error_code = $GLOBALS['iil_errornum']; raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'line' => __LINE__, 'message' => $GLOBALS['iil_error']), true, false); } // get server properties if ($this->conn) { if (!empty($this->conn->rootdir)) $this->set_rootdir($this->conn->rootdir); if (empty($this->delimiter)) $this->get_hierarchy_delimiter(); } // write error log else if ($this->conn->error) { $this->error_code = $this->conn->errornum; raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__, 'line' => __LINE__, 'message' => $this->conn->error), true, false); } return $this->conn ? true : false; @@ -166,8 +156,9 @@ */ function close() { if ($this->conn) iil_Close($this->conn); if ($this->conn && $this->conn->connected()) $this->conn->close(); $this->write_cache(); } @@ -184,11 +175,11 @@ // issue SELECT command to restore connection status if ($this->mailbox) iil_C_Select($this->conn, $this->mailbox); $this->conn->select($this->mailbox); } /** * Set options to be used in iil_Connect() * Set options to be used in rcube_imap_generic::connect() */ function set_options($opt) { @@ -239,8 +230,7 @@ */ function set_default_mailboxes($arr) { if (is_array($arr)) { if (is_array($arr)) { $this->default_folders = $arr; // add inbox if not included @@ -342,7 +332,7 @@ */ function get_mailbox_name() { return $this->conn ? $this->mod_mailbox($this->mailbox, 'out') : ''; return $this->conn->connected() ? $this->mod_mailbox($this->mailbox, 'out') : ''; } @@ -355,7 +345,7 @@ */ function get_capability($cap) { return iil_C_GetCapability($this->conn, strtoupper($cap)); return $this->conn->getCapability(strtoupper($cap)); } @@ -394,7 +384,7 @@ function check_permflag($flag) { $flag = strtoupper($flag); $imap_flag = $GLOBALS['IMAP_FLAGS'][$flag]; $imap_flag = $this->conn->flags[$flag]; return (in_array_nocase($imap_flag, $this->conn->permanentflags)); } @@ -408,81 +398,12 @@ function get_hierarchy_delimiter() { if ($this->conn && empty($this->delimiter)) $this->delimiter = iil_C_GetHierarchyDelimiter($this->conn); $this->delimiter = $this->conn->getHierarchyDelimiter(); if (empty($this->delimiter)) $this->delimiter = '/'; return $this->delimiter; } /** * Public method for mailbox listing. * * Converts mailbox name with root dir first * * @param string Optional root folder * @param string Optional filter for mailbox listing * @return array List of mailboxes/folders * @access public */ function list_mailboxes($root='', $filter='*') { $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)) $a_out[] = $name; } // INBOX should always be available if (!in_array('INBOX', $a_out)) array_unshift($a_out, 'INBOX'); // sort mailboxes $a_out = $this->_sort_mailbox_list($a_out); return $a_out; } /** * Private method for mailbox listing * * @return array List of mailboxes/folders * @see rcube_imap::list_mailboxes() * @access private */ private function _list_mailboxes($root='', $filter='*') { $a_defaults = $a_out = array(); // get cached folder list $a_mboxes = $this->get_cache('mailboxes'); if (is_array($a_mboxes)) return $a_mboxes; // Give plugins a chance to provide a list of mailboxes $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes',array('root'=>$root,'filter'=>$filter)); if (isset($data['folders'])) { $a_folders = $data['folders']; } else { // retrieve list of folders from IMAP server $a_folders = iil_C_ListSubscribed($this->conn, $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; } @@ -538,7 +459,7 @@ } // RECENT count is fetched a bit different else if ($mode == 'RECENT') { $count = iil_C_CheckForRecent($this->conn, $mailbox); $count = $this->conn->checkForRecent($mailbox); } // use SEARCH for message counting else if ($this->skip_deleted) { @@ -563,9 +484,9 @@ } else { if ($mode == 'UNSEEN') $count = iil_C_CountUnseen($this->conn, $mailbox); $count = $this->conn->countUnseen($mailbox); else { $count = iil_C_CountMessages($this->conn, $mailbox); $count = $this->conn->countMessages($mailbox); $_SESSION['maxuid'][$mailbox] = $count ? $this->_id2uid($count) : 0; } } @@ -643,18 +564,17 @@ $cache_status = $this->check_cache_status($mailbox, $cache_key); // cache is OK, we can get all messages from local cache if ($cache_status>0) { if ($cache_status>0) { $start_msg = ($page-1) * $this->page_size; $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); $a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order); $result = array_values($a_msg_headers); if ($slice) $result = array_slice($result, -$slice, $slice); return $result; } // cache is dirty, sync it else if ($this->caching_enabled && $cache_status==-1 && !$recursive) { else if ($this->caching_enabled && $cache_status==-1 && !$recursive) { $this->sync_header_index($mailbox); return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); } @@ -663,8 +583,7 @@ $a_msg_headers = array(); // use message index sort as default sorting (for better performance) if (!$this->sort_field) { if (!$this->sort_field) { if ($this->skip_deleted) { // @TODO: this could be cached if ($msg_index = $this->_search_index($mailbox, 'ALL UNDELETED')) { @@ -673,7 +592,7 @@ $msg_index = array_slice($msg_index, $begin, $end-$begin); } } else if ($max = iil_C_CountMessages($this->conn, $mailbox)) { else if ($max = $this->conn->countMessages($mailbox)) { list($begin, $end) = $this->_get_message_range($max, $page); $msg_index = range($begin+1, $end); } @@ -688,9 +607,8 @@ $this->_fetch_headers($mailbox, join(",", $msg_index), $a_msg_headers, $cache_key); } // use SORT command else if ($this->get_capability('SORT')) { if ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { else if ($this->get_capability('SORT')) { if ($msg_index = $this->conn->sort($mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { list($begin, $end) = $this->_get_message_range(count($msg_index), $page); $max = max($msg_index); $msg_index = array_slice($msg_index, $begin, $end-$begin); @@ -703,8 +621,7 @@ } } // fetch specified header for all messages and sort else if ($a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { else if ($a_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { asort($a_index); // ASC $msg_index = array_keys($a_index); $max = max($msg_index); @@ -763,8 +680,8 @@ $msg_index = $this->_sort_threads($mailbox, $thread_tree); return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice); return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice); } @@ -779,7 +696,7 @@ { if (empty($this->icache['threads'])) { // get all threads list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( $mailbox, $this->threading, $this->skip_deleted ? 'UNDELETED' : ''); // add to internal (fast) cache @@ -816,7 +733,7 @@ $msg_index = array_reverse($msg_index); // flatten threads array // @TODO: fetch children only in expanded mode // @TODO: fetch children only in expanded mode (?) $all_ids = array(); foreach($msg_index as $root) { $all_ids[] = $root; @@ -908,8 +825,7 @@ $this->_set_sort_order($sort_field, $sort_order); // quickest method (default sorting) if (!$this->search_sort_field && !$this->sort_field) { if (!$this->search_sort_field && !$this->sort_field) { if ($sort_order == 'DESC') $msgs = array_reverse($msgs); @@ -931,8 +847,7 @@ } // sorted messages, so we can first slice array and then fetch only wanted headers if ($this->get_capability('SORT')) // SORT searching result { if ($this->get_capability('SORT')) { // SORT searching result // reset search set if sorting field has been changed if ($this->sort_field && $this->search_sort_field != $this->sort_field) $msgs = $this->search('', $this->search_string, $this->search_charset, $this->sort_field); @@ -991,10 +906,13 @@ return array(); // if not already sorted $a_msg_headers = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order); $a_msg_headers = $this->conn->sortHeaders( $a_msg_headers, $this->sort_field, $this->sort_order); // only return the requested part of the set $a_msg_headers = array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); $a_msg_headers = array_slice(array_values($a_msg_headers), $start_msg, min($cnt-$start_msg, $this->page_size)); if ($slice) $a_msg_headers = array_slice($a_msg_headers, -$slice, $slice); @@ -1034,7 +952,8 @@ $msg_index = $this->_sort_threads($mailbox, $thread_tree, array_keys($msg_depth)); return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); return $this->_fetch_thread_headers($mailbox, $thread_tree, $msg_depth, $has_children, $msg_index, $page, $slice=0); } @@ -1050,18 +969,15 @@ { $start_msg = ($page-1) * $this->page_size; if ($page=='all') { if ($page=='all') { $begin = 0; $end = $max; } else if ($this->sort_order=='DESC') { else if ($this->sort_order=='DESC') { $begin = $max - $this->page_size - $start_msg; $end = $max - $start_msg; } else { else { $begin = $start_msg; $end = $start_msg + $this->page_size; } @@ -1088,7 +1004,8 @@ private function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key) { // fetch reqested headers from server $a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs, false, false, $this->fetch_add_headers); $a_header_index = $this->conn->fetchHeaders( $mailbox, $msgs, false, false, $this->fetch_add_headers); if (empty($a_header_index)) return 0; @@ -1133,7 +1050,8 @@ if ($_SESSION['maxuid'][$mailbox] > $old_maxuid) { $maxuid = max(1, $old_maxuid+1); return array_values((array)iil_C_FetchHeaderIndex($this->conn, $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); return array_values((array)$this->conn->fetchHeaderIndex( $mailbox, "$maxuid:*", 'UID', $this->skip_deleted, true)); } return array(); @@ -1159,13 +1077,9 @@ // we have a saved search result, get index from there if (!isset($this->cache[$key]) && $this->search_string && !$this->search_threads && $mailbox == $this->mailbox) { $this->cache[$key] = array(); && !$this->search_threads && $mailbox == $this->mailbox) { // use message index sort as default sorting if (!$this->sort_field) { if (!$this->sort_field) { $msgs = $this->search_set; if ($this->search_sort_field != 'date') @@ -1177,8 +1091,7 @@ $this->cache[$key] = $msgs; } // sort with SORT command else if ($this->get_capability('SORT')) { else if ($this->get_capability('SORT')) { if ($this->sort_field && $this->search_sort_field != $this->sort_field) $this->search('', $this->search_string, $this->search_charset, $this->sort_field); @@ -1187,17 +1100,21 @@ else $this->cache[$key] = $this->search_set; } else { $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, 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); else if ($this->sort_order=="DESC") arsort($a_index); $this->cache[$key] = array_keys($a_index); } else { $this->cache[$key] = array(); } } } @@ -1210,15 +1127,14 @@ $cache_status = $this->check_cache_status($mailbox, $cache_key); // cache is OK if ($cache_status>0) { $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); if ($cache_status>0) { $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); return array_keys($a_index); } // use message index sort as default sorting if (!$this->sort_field) { if (!$this->sort_field) { if ($this->skip_deleted) { $a_index = $this->_search_index($mailbox, 'ALL'); } else if ($max = $this->_messagecount($mailbox)) { @@ -1231,16 +1147,17 @@ $this->cache[$key] = $a_index; } // fetch complete message index else if ($this->get_capability('SORT')) { if ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { 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; } } else if ($a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { else if ($a_index = $this->conn->fetchHeaderIndex( $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { if ($this->sort_order=="ASC") asort($a_index); else if ($this->sort_order=="DESC") @@ -1270,8 +1187,7 @@ // we have a saved search result, get index from there if (!isset($this->cache[$key]) && $this->search_string && $this->search_threads && $mailbox == $this->mailbox) { && $this->search_threads && $mailbox == $this->mailbox) { // use message IDs for better performance $ids = array_keys_recursive($this->search_set['tree']); $this->cache[$key] = $this->_flatten_threads($mailbox, $this->search_set['tree'], $ids); @@ -1286,8 +1202,7 @@ $cache_status = $this->check_cache_status($mailbox, $cache_key); // cache is OK if ($cache_status>0) { if ($cache_status>0) { $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); return array_keys($a_index); } @@ -1343,29 +1258,25 @@ $cache_index = $this->get_message_cache_index($cache_key); // fetch complete message index $a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); $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) { foreach ($a_message_index as $id => $uid) { // message in cache at correct position if ($cache_index[$id] == $uid) { if ($cache_index[$id] == $uid) { unset($cache_index[$id]); continue; } // message in cache but in wrong position if (in_array((string)$uid, $cache_index, true)) { if (in_array((string)$uid, $cache_index, true)) { unset($cache_index[$id]); } // other message at this position if (isset($cache_index[$id])) { if (isset($cache_index[$id])) { $for_remove[] = $cache_index[$id]; unset($cache_index[$id]); } @@ -1382,10 +1293,13 @@ // fetch complete headers and add to cache if (!empty($for_update)) { if ($headers = iil_C_FetchHeader($this->conn, $mailbox, join(',', $for_update), false, $this->fetch_add_headers)) foreach ($headers as $header) if ($headers = $this->conn->fetchHeader($mailbox, join(',', $for_update), false, $this->fetch_add_headers)) { foreach ($headers as $header) { $this->add_message_cache($cache_key, $header->id, $header, NULL, in_array($header->uid, (array)$for_remove)); } } } } @@ -1414,15 +1328,15 @@ 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)) { 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; 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; } @@ -1456,7 +1370,7 @@ $criteria = 'UNDELETED '.$criteria; if ($this->threading) { list ($thread_tree, $msg_depth, $has_children) = iil_C_Thread($this->conn, list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( $mailbox, $this->threading, $criteria, $charset); $a_messages = array( @@ -1467,7 +1381,7 @@ } else if ($sort_field && $this->get_capability('SORT')) { $charset = $charset ? $charset : $this->default_charset; $a_messages = iil_C_Sort($this->conn, $mailbox, $sort_field, $criteria, false, $charset); $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); if (!$a_messages) return array(); @@ -1478,7 +1392,8 @@ $a_messages = $max ? range(1, $max) : array(); } else { $a_messages = iil_C_Search($this->conn, $mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); $a_messages = $this->conn->search($mailbox, ($charset ? "CHARSET $charset " : '') . $criteria); if (!$a_messages) return array(); @@ -1488,6 +1403,7 @@ sort($a_messages); } } // update messagecount cache ? // $a_mailbox_cache = get_cache('messagecount'); // $a_mailbox_cache[$mailbox][$criteria] = sizeof($a_messages); @@ -1501,7 +1417,7 @@ * Sort thread * * @param string Mailbox name * @param array Unsorted thread tree (iil_C_Thread() result) * @param array Unsorted thread tree (rcube_imap_generic::thread() result) * @param array Message IDs if we know what we need (e.g. search result) * @return array Sorted roots IDs * @access private @@ -1519,7 +1435,7 @@ else { // ($sort_field == 'date' && $this->threading != 'REFS') // use SORT command if ($this->get_capability('SORT')) { $a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $a_index = $this->conn->sort($mailbox, $this->sort_field, !empty($ids) ? $ids : ($this->skip_deleted ? 'UNDELETED' : '')); // return unsorted tree if we've got no index data @@ -1528,7 +1444,7 @@ } else { // fetch specified headers for all messages and sort them $a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, !empty($ids) ? $ids : "1:*", $a_index = $this->conn->fetchHeaderIndex($mailbox, !empty($ids) ? $ids : "1:*", $this->sort_field, $this->skip_deleted); // return unsorted tree if we've got no index data @@ -1599,6 +1515,7 @@ { if (!empty($this->search_string)) $this->search_set = $this->search('', $this->search_string, $this->search_charset, $this->search_sort_field, $this->search_threads); return $this->get_search_set(); @@ -1612,8 +1529,12 @@ */ function in_searchset($msgid) { if (!empty($this->search_string)) if (!empty($this->search_string)) { if ($this->search_threads) return isset($this->search_set['depth']["$msgid"]); else return in_array("$msgid", (array)$this->search_set, true); } else return true; } @@ -1637,11 +1558,11 @@ if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid))) return $headers; $headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); $headers = $this->conn->fetchHeader( $mailbox, $id, $is_uid, $bodystr, $this->fetch_add_headers); // write headers cache if ($headers) { if ($headers) { if ($headers->uid && $headers->id) $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; @@ -1671,13 +1592,12 @@ } if (!$structure_str) $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); $structure = iml_GetRawStructureArray($structure_str); $struct = false; // parse structure and add headers if (!empty($structure)) { if (!empty($structure)) { $headers = $this->get_headers($uid); $this->_msg_id = $headers->id; @@ -1701,8 +1621,7 @@ $struct->headers = get_object_vars($headers); // don't trust given content-type if (empty($struct->parts) && !empty($struct->headers['ctype'])) { if (empty($struct->parts) && !empty($struct->headers['ctype'])) { $struct->mime_id = '1'; $struct->mimetype = strtolower($struct->headers['ctype']); list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype); @@ -1728,24 +1647,24 @@ $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; // multipart if (is_array($part[0])) { 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])) { for ($i=1; $i<count($part); $i++) { if (!is_array($part[$i])) { $struct->ctype_secondary = strtolower($part[$i]); break; } } $struct->mimetype = 'multipart/'.$struct->ctype_secondary; // build parts list for headers pre-fetching for ($i=0, $count=0; $i<count($part); $i++) for ($i=0, $count=0; $i<count($part); $i++) { if (is_array($part[$i]) && count($part[$i]) > 3) { // fetch message headers if message/rfc822 or named part (could contain Content-Location header) // fetch message headers if message/rfc822 // or named part (could contain Content-Location header) if (!is_array($part[$i][0])) { $tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1; if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') { @@ -1757,29 +1676,31 @@ } } } } // 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 if ($mime_part_headers) $mime_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, if ($mime_part_headers) { $mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, $this->_msg_id, $mime_part_headers); } // we'll need a real content-type of message/rfc822 part if ($raw_part_headers) $raw_part_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, if ($raw_part_headers) { $raw_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox, $this->_msg_id, $raw_part_headers, false); } $struct->parts = array(); for ($i=0, $count=0; $i<count($part); $i++) for ($i=0, $count=0; $i<count($part); $i++) { if (is_array($part[$i]) && count($part[$i]) > 3) { $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]); } } return $struct; } // regular part $struct->ctype_primary = strtolower($part[0]); @@ -1787,8 +1708,7 @@ $struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary; // read content type parameters if (is_array($part[2])) { if (is_array($part[2])) { $struct->ctype_parameters = array(); for ($i=0; $i<count($part[2]); $i+=2) $struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1]; @@ -1798,8 +1718,7 @@ } // read content encoding if (!empty($part[5]) && $part[5]!='NIL') { if (!empty($part[5]) && $part[5]!='NIL') { $struct->encoding = strtolower($part[5]); $struct->headers['content-transfer-encoding'] = $struct->encoding; } @@ -1811,8 +1730,7 @@ // read part disposition $di = count($part) - 2; if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) || (is_array($part[--$di]) && count($part[$di]) == 2)) { (is_array($part[--$di]) && count($part[$di]) == 2)) { $struct->disposition = strtolower($part[$di][0]); if (is_array($part[$di][1])) @@ -1821,8 +1739,7 @@ } // get child parts if (is_array($part[8]) && $di != 8) { if (is_array($part[8]) && $di != 8) { $struct->parts = array(); for ($i=0, $count=0; $i<count($part[8]); $i++) if (is_array($part[8][$i]) && count($part[8][$i]) > 5) @@ -1830,8 +1747,7 @@ } // get part ID if (!empty($part[3]) && $part[3]!='NIL') { if (!empty($part[3]) && $part[3]!='NIL') { $struct->content_id = $part[3]; $struct->headers['content-id'] = $part[3]; @@ -1841,14 +1757,18 @@ // 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)) $mime_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $struct->mime_id); if (empty($mime_headers)) { $mime_headers = $this->conn->fetchPartHeader( $this->mailbox, $this->_msg_id, false, $struct->mime_id); } $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; // get real headers for message of type 'message/rfc822' if ($struct->mimetype == 'message/rfc822') { if (empty($raw_headers)) $raw_headers = iil_C_FetchMIMEHeaders($this->conn, $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); if (empty($raw_headers)) { $raw_headers = $this->conn->fetchMIMEHeaders( $this->mailbox, $this->_msg_id, (array)$struct->mime_id, false); } $struct->real_headers = $this->_parse_headers($raw_headers); // get real content-type of message/rfc822 @@ -1896,8 +1816,10 @@ // some servers (eg. dovecot-1.x) have no support for parameter value continuations // we must fetch and parse headers "manually" if ($i<2) { if (!$headers) $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); if (!$headers) { $headers = $this->conn->fetchPartHeader( $this->mailbox, $this->_msg_id, false, $part->mime_id); } $filename_mime = ''; $i = 0; while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1913,8 +1835,10 @@ $i++; } if ($i<2) { if (!$headers) $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); if (!$headers) { $headers = $this->conn->fetchPartHeader( $this->mailbox, $this->_msg_id, false, $part->mime_id); } $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1930,8 +1854,10 @@ $i++; } if ($i<2) { if (!$headers) $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); if (!$headers) { $headers = $this->conn->fetchPartHeader( $this->mailbox, $this->_msg_id, false, $part->mime_id); } $filename_mime = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -1947,8 +1873,10 @@ $i++; } if ($i<2) { if (!$headers) $headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, false, $part->mime_id); if (!$headers) { $headers = $this->conn->fetchPartHeader( $this->mailbox, $this->_msg_id, false, $part->mime_id); } $filename_encoded = ''; $i = 0; $matches = array(); while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) { @@ -2013,9 +1941,8 @@ function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL) { // get part encoding if not provided if (!is_object($o_part)) { $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $uid, true); if (!is_object($o_part)) { $structure_str = $this->conn->fetchStructureString($this->mailbox, $uid, true); $structure = iml_GetRawStructureArray($structure_str); // error or message not found if (empty($structure)) @@ -2032,7 +1959,7 @@ if (!$part) $part = 'TEXT'; $body = iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, $part, $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part, $o_part->encoding, $print, $fp); if ($fp || $print) @@ -2074,7 +2001,7 @@ */ function &get_raw_body($uid) { return iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true); return $this->conn->handlePartBody($this->mailbox, $uid, true); } @@ -2086,7 +2013,7 @@ */ function &get_raw_headers($uid) { return iil_C_FetchPartHeader($this->conn, $this->mailbox, $uid, true); return $this->conn->fetchPartHeader($this->mailbox, $uid, true); } @@ -2097,7 +2024,7 @@ */ function print_raw_body($uid) { iil_C_HandlePartBody($this->conn, $this->mailbox, $uid, true, NULL, NULL, true); $this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true); } @@ -2118,9 +2045,9 @@ list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox); if (strpos($flag, 'UN') === 0) $result = iil_C_UnFlag($this->conn, $mailbox, $uids, substr($flag, 2)); $result = $this->conn->unflag($mailbox, $uids, substr($flag, 2)); else $result = iil_C_Flag($this->conn, $mailbox, $uids, $flag); $result = $this->conn->flag($mailbox, $uids, $flag); if ($result >= 0) { // reload message headers if cached @@ -2177,15 +2104,14 @@ if ($this->mailbox_exists($mbox_name, true)) { if ($is_file) { $separator = rcmail::get_instance()->config->header_delimiter(); $saved = iil_C_AppendFromFile($this->conn, $mailbox, $message, $saved = $this->conn->appendFromFile($mailbox, $message, $headers, $separator.$separator); } else $saved = iil_C_Append($this->conn, $mailbox, $message); $saved = $this->conn->append($mailbox, $message); } if ($saved) { if ($saved) { // increase messagecount of the target mailbox $this->_set_messagecount($mailbox, 'ALL', 1); } @@ -2216,8 +2142,7 @@ return false; // make sure mailbox exists if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { if (in_array($tbox, $this->default_folders)) $this->create_mailbox($tbox, true); else @@ -2232,8 +2157,8 @@ } // move messages $iil_move = iil_C_Move($this->conn, $uids, $from_mbox, $to_mbox); $moved = !($iil_move === false || $iil_move < 0); $move = $this->conn->move($uids, $from_mbox, $to_mbox); $moved = !($move === false || $move < 0); // send expunge command in order to have the moved message // really deleted from the source mailbox @@ -2298,8 +2223,7 @@ return false; // make sure mailbox exists if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { if ($to_mbox != 'INBOX' && !$this->mailbox_exists($tbox, true)) { if (in_array($tbox, $this->default_folders)) $this->create_mailbox($tbox, true); else @@ -2307,8 +2231,8 @@ } // copy messages $iil_copy = iil_C_Copy($this->conn, $uids, $from_mbox, $to_mbox); $copied = !($iil_copy === false || $iil_copy < 0); $copy = $this->conn->copy($uids, $from_mbox, $to_mbox); $copied = !($copy === false || $copy < 0); if ($copied) { $this->_clear_messagecount($to_mbox); @@ -2335,7 +2259,7 @@ if (empty($uids)) return false; $deleted = iil_C_Delete($this->conn, $mailbox, $uids); $deleted = $this->conn->delete($mailbox, $uids); if ($deleted) { // send expunge command in order to have the deleted message @@ -2383,13 +2307,14 @@ $mailbox = !empty($mbox_name) ? $this->mod_mailbox($mbox_name) : $this->mailbox; $msg_count = $this->_messagecount($mailbox, 'ALL'); if ($msg_count>0) { $cleared = iil_C_ClearFolder($this->conn, $mailbox); if (!$msg_count) { return 0; } $cleared = $this->conn->clearFolder($mailbox); // make sure the message count cache is cleared as well if ($cleared) { if ($cleared) { $this->clear_message_cache($mailbox.'.msg'); $a_mailbox_cache = $this->get_cache('messagecount'); unset($a_mailbox_cache[$mailbox]); @@ -2397,9 +2322,6 @@ } return $cleared; } else return 0; } @@ -2434,10 +2356,9 @@ else $a_uids = NULL; $result = iil_C_Expunge($this->conn, $mailbox, $a_uids); $result = $this->conn->expunge($mailbox, $a_uids); if ($result>=0 && $clear_cache) { if ($result>=0 && $clear_cache) { $this->clear_message_cache($mailbox.'.msg'); $this->_clear_messagecount($mailbox); } @@ -2462,12 +2383,12 @@ $all = true; } // get UIDs from current search set // @TODO: skip iil_C_FetchUIDs() and work with IDs instead of UIDs (?) // @TODO: skip fetchUIDs() and work with IDs instead of UIDs (?) else { if ($this->search_threads) $uids = iil_C_FetchUIDs($this->conn, $mailbox, array_keys($this->search_set['depth'])); $uids = $this->conn->fetchUIDs($mailbox, array_keys($this->search_set['depth'])); else $uids = iil_C_FetchUIDs($this->conn, $mailbox, $this->search_set); $uids = $this->conn->fetchUIDs($mailbox, $this->search_set); // save ID-to-UID mapping in local cache if (is_array($uids)) @@ -2489,9 +2410,108 @@ } /** * Translate UID to message ID * * @param int Message UID * @param string Mailbox name * @return int Message ID */ function get_id($uid, $mbox_name=NULL) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_uid2id($uid, $mailbox); } /** * Translate message number to UID * * @param int Message ID * @param string Mailbox name * @return int Message UID */ function get_uid($id,$mbox_name=NULL) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_id2uid($id, $mailbox); } /* -------------------------------- * folder managment * --------------------------------*/ /** * Public method for mailbox listing. * * Converts mailbox name with root dir first * * @param string Optional root folder * @param string Optional filter for mailbox listing * @return array List of mailboxes/folders * @access public */ function list_mailboxes($root='', $filter='*') { $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)) $a_out[] = $name; } // INBOX should always be available if (!in_array('INBOX', $a_out)) array_unshift($a_out, 'INBOX'); // sort mailboxes $a_out = $this->_sort_mailbox_list($a_out); return $a_out; } /** * Private method for mailbox listing * * @return array List of mailboxes/folders * @see rcube_imap::list_mailboxes() * @access private */ private function _list_mailboxes($root='', $filter='*') { $a_defaults = $a_out = array(); // get cached folder list $a_mboxes = $this->get_cache('mailboxes'); if (is_array($a_mboxes)) return $a_mboxes; // Give plugins a chance to provide a list of mailboxes $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', array('root'=>$root,'filter'=>$filter)); if (isset($data['folders'])) { $a_folders = $data['folders']; } else { // 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 @@ -2507,11 +2527,10 @@ return $sa_unsubscribed; // retrieve list of folders from IMAP server $a_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox($root), '*'); $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), '*'); // modify names with root dir foreach ($a_mboxes as $mbox_name) { foreach ($a_mboxes as $mbox_name) { $name = $this->mod_mailbox($mbox_name, 'out'); if (strlen($name)) $a_folders[] = $name; @@ -2532,7 +2551,7 @@ function get_quota() { if ($this->get_capability('QUOTA')) return iil_C_GetQuota($this->conn); return $this->conn->getQuota(); return false; } @@ -2584,7 +2603,7 @@ // reduce mailbox name to 100 chars $name = substr($name, 0, 100); $abs_name = $this->mod_mailbox($name); $result = iil_C_CreateFolder($this->conn, $abs_name); $result = $this->conn->createFolder($abs_name); // try to subscribe it if ($result && $subscribe) @@ -2618,21 +2637,20 @@ // unsubscribe folder if ($subscribed) iil_C_UnSubscribe($this->conn, $mailbox); $this->conn->unsubscribe($mailbox); if (strlen($abs_name)) $result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name); $result = $this->conn->renameFolder($mailbox, $abs_name); if ($result) { 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)) { iil_C_UnSubscribe($this->conn, $c_subscribed); iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed)); if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed)) { $this->conn->unsubscribe($c_subscribed); $this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed)); } // clear cache @@ -2642,7 +2660,7 @@ // try to subscribe it if ($result && $subscribed) iil_C_Subscribe($this->conn, $abs_name); $this->conn->subscribe($abs_name); return $result ? $name : false; } @@ -2663,30 +2681,31 @@ else if (is_string($mbox_name) && strlen($mbox_name)) $a_mboxes = explode(',', $mbox_name); if (is_array($a_mboxes)) foreach ($a_mboxes as $mbox_name) { if (is_array($a_mboxes)) { foreach ($a_mboxes as $mbox_name) { $mailbox = $this->mod_mailbox($mbox_name); $sub_mboxes = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), $sub_mboxes = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_name . $this->delimiter . '*'); // unsubscribe mailbox before deleting iil_C_UnSubscribe($this->conn, $mailbox); $this->conn->unsubscribe($mailbox); // send delete command to server $result = iil_C_DeleteFolder($this->conn, $mailbox); $result = $this->conn->deleteFolder($mailbox); if ($result >= 0) { $deleted = true; $this->clear_message_cache($mailbox.'.msg'); } foreach ($sub_mboxes as $c_mbox) foreach ($sub_mboxes as $c_mbox) { if ($c_mbox != 'INBOX') { iil_C_UnSubscribe($this->conn, $c_mbox); $result = iil_C_DeleteFolder($this->conn, $c_mbox); $this->conn->unsubscribe($c_mbox); $result = $this->conn->deleteFolder($c_mbox); if ($result >= 0) { $deleted = true; $this->clear_message_cache($c_mbox.'.msg'); } } } } } @@ -2705,8 +2724,7 @@ function create_default_folders() { // create default folders if they do not exist foreach ($this->default_folders as $folder) { foreach ($this->default_folders as $folder) { if (!$this->mailbox_exists($folder)) $this->create_mailbox($folder, true); else if (!$this->mailbox_exists($folder, true)) @@ -2729,11 +2747,11 @@ return true; if ($subscription) { if ($a_folders = iil_C_ListSubscribed($this->conn, $this->mod_mailbox(''), $mbox_name)) if ($a_folders = $this->conn->listSubscribed($this->mod_mailbox(''), $mbox_name)) return true; } else { $a_folders = iil_C_ListMailboxes($this->conn, $this->mod_mailbox(''), $mbox_mbox); $a_folders = $this->conn->listMailboxes($this->mod_mailbox(''), $mbox_mbox); if (is_array($a_folders) && in_array($this->mod_mailbox($mbox_name), $a_folders)) return true; @@ -2744,13 +2762,35 @@ } /** * Modify folder name for input/output according to root dir and namespace * * @param string Folder name * @param string Mode * @return string Folder name */ function mod_mailbox($mbox_name, $mode='in') { if ($mbox_name == 'INBOX') return $mbox_name; if (!empty($this->root_dir)) { if ($mode=='in') $mbox_name = $this->root_dir.$this->delimiter.$mbox_name; else if (!empty($mbox_name)) // $mode=='out' $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); } return $mbox_name; } /* -------------------------------- * internal caching methods * --------------------------------*/ /** * @access private * @access public */ function set_caching($set) { @@ -2761,13 +2801,12 @@ } /** * @access private * @access public */ function get_cache($key) { // read cache (if it was not read before) if (!count($this->cache) && $this->caching_enabled) { if (!count($this->cache) && $this->caching_enabled) { return $this->_read_cache_record($key); } @@ -2777,7 +2816,7 @@ /** * @access private */ function update_cache($key, $data) private function update_cache($key, $data) { $this->cache[$key] = $data; $this->cache_changed = true; @@ -2787,12 +2826,10 @@ /** * @access private */ function write_cache() private function write_cache() { if ($this->caching_enabled && $this->cache_changed) { foreach ($this->cache as $key => $data) { if ($this->caching_enabled && $this->cache_changed) { foreach ($this->cache as $key => $data) { if ($this->cache_changes[$key]) $this->_write_cache_record($key, serialize($data)); } @@ -2800,15 +2837,14 @@ } /** * @access private * @access public */ function clear_cache($key=NULL) { if (!$this->caching_enabled) return; if ($key===NULL) { if ($key===NULL) { foreach ($this->cache as $key => $data) $this->_clear_cache_record($key); @@ -2816,8 +2852,7 @@ $this->cache_changed = false; $this->cache_changes = array(); } else { else { $this->_clear_cache_record($key); $this->cache_changes[$key] = false; unset($this->cache[$key]); @@ -2829,18 +2864,16 @@ */ private function _read_cache_record($key) { if ($this->db) { if ($this->db) { // get cached data from DB $sql_result = $this->db->query( "SELECT cache_id, data, cache_key FROM ".get_table_name('cache')." WHERE user_id=? AND cache_key LIKE 'IMAP.%'", "SELECT cache_id, data, cache_key ". "FROM ".get_table_name('cache'). " WHERE user_id=? ". "AND cache_key LIKE 'IMAP.%'", $_SESSION['user_id']); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { while ($sql_arr = $this->db->fetch_assoc($sql_result)) { $sql_key = preg_replace('/^IMAP\./', '', $sql_arr['cache_key']); $this->cache_keys[$sql_key] = $sql_arr['cache_id']; if (!isset($this->cache[$sql_key])) @@ -2860,34 +2893,32 @@ return false; // update existing cache record if ($this->cache_keys[$key]) { if ($this->cache_keys[$key]) { $this->db->query( "UPDATE ".get_table_name('cache')." SET created=". $this->db->now().", data=? WHERE user_id=? AND cache_key=?", "UPDATE ".get_table_name('cache'). " SET created=". $this->db->now().", data=? ". "WHERE user_id=? ". "AND cache_key=?", $data, $_SESSION['user_id'], 'IMAP.'.$key); } // add new cache record else { else { $this->db->query( "INSERT INTO ".get_table_name('cache')." (created, user_id, cache_key, data) VALUES (".$this->db->now().", ?, ?, ?)", "INSERT INTO ".get_table_name('cache'). " (created, user_id, cache_key, data) ". "VALUES (".$this->db->now().", ?, ?, ?)", $_SESSION['user_id'], 'IMAP.'.$key, $data); // get cache entry ID for this key $sql_result = $this->db->query( "SELECT cache_id FROM ".get_table_name('cache')." WHERE user_id=? AND cache_key=?", "SELECT cache_id ". "FROM ".get_table_name('cache'). " WHERE user_id=? ". "AND cache_key=?", $_SESSION['user_id'], 'IMAP.'.$key); @@ -2902,9 +2933,9 @@ private function _clear_cache_record($key) { $this->db->query( "DELETE FROM ".get_table_name('cache')." WHERE user_id=? AND cache_key=?", "DELETE FROM ".get_table_name('cache'). " WHERE user_id=? ". "AND cache_key=?", $_SESSION['user_id'], 'IMAP.'.$key); @@ -2917,13 +2948,12 @@ * message caching methods * --------------------------------*/ /** * Checks if the cache is up-to-date * * @param string Mailbox name * @param string Internal cache key * @return int Cache status: -3 = off, -2 = incomplete, -1 = dirty * @return int Cache status: -3 = off, -2 = incomplete, -1 = dirty, 1 = OK */ private function check_cache_status($mailbox, $cache_key) { @@ -2946,7 +2976,10 @@ if ($cache_count==$msg_count) { if ($this->skip_deleted) { $h_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:*", 'UID', $this->skip_deleted); $h_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); if (empty($h_index)) return -2; if (sizeof($h_index) == $cache_count) { $cache_index = array_flip($cache_index); @@ -2959,7 +2992,7 @@ return -2; } else { // get UID of message with highest index $uid = iil_C_ID2UID($this->conn, $mailbox, $msg_count); $uid = $this->conn->ID2UID($mailbox, $msg_count); $cache_uid = array_pop($cache_index); // uids of highest message matches -> cache seems OK @@ -2983,35 +3016,32 @@ { $cache_key = "$key:$from:$to:$sort_field:$sort_order"; $config = rcmail::get_instance()->config; // 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])) { if ($this->caching_enabled && !isset($this->cache[$cache_key])) { $this->cache[$cache_key] = array(); $sql_result = $this->db->limitquery( "SELECT idx, uid, headers FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), "SELECT idx, uid, headers". " FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".strtoupper($sort_order), $from, $to-$from, $_SESSION['user_id'], $key); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { while ($sql_arr = $this->db->fetch_assoc($sql_result)) { $uid = $sql_arr['uid']; $this->cache[$cache_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); // featch headers if unserialize failed if (empty($this->cache[$cache_key][$uid])) $this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); $this->cache[$cache_key][$uid] = $this->conn->fetchHeader( preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); } } @@ -3025,20 +3055,18 @@ { $internal_key = 'message'; if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { $sql_result = $this->db->query( "SELECT idx, headers, structure FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? AND uid=?", "SELECT idx, headers, structure". " FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " AND uid=?", $_SESSION['user_id'], $key, $uid); if ($sql_arr = $this->db->fetch_assoc($sql_result)) { if ($sql_arr = $this->db->fetch_assoc($sql_result)) { $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; $this->icache[$internal_key][$uid] = $this->db->decode(unserialize($sql_arr['headers'])); if (is_object($this->icache[$internal_key][$uid]) && !empty($sql_arr['structure'])) @@ -3069,11 +3097,11 @@ $sa_message_index[$key] = array(); $sql_result = $this->db->query( "SELECT idx, uid FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order, "SELECT idx, uid". " FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " ORDER BY ".$this->db->quote_identifier($sort_field)." ".$sort_order, $_SESSION['user_id'], $key); @@ -3102,37 +3130,40 @@ // check for an existing record (probly headers are cached but structure not) if (!$force) { $sql_result = $this->db->query( "SELECT message_id FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? AND uid=?", "SELECT message_id". " FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " AND uid=?", $_SESSION['user_id'], $key, $headers->uid); if ($sql_arr = $this->db->fetch_assoc($sql_result)) $message_id = $sql_arr['message_id']; } // update cache record if ($message_id) { if ($message_id) { $this->db->query( "UPDATE ".get_table_name('messages')." SET idx=?, headers=?, structure=? WHERE message_id=?", "UPDATE ".get_table_name('messages'). " SET idx=?, headers=?, structure=?". " WHERE message_id=?", $index, serialize($this->db->encode(clone $headers)), is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL, $message_id ); } else // insert new record { else { // insert new record $this->db->query( "INSERT INTO ".get_table_name('messages')." (user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure) VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)", "INSERT INTO ".get_table_name('messages'). " (user_id, del, cache_key, created, idx, uid, subject, ". $this->db->quoteIdentifier('from').", ". $this->db->quoteIdentifier('to').", ". "cc, date, size, headers, structure)". " VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ". $this->db->fromunixtime($headers->timestamp).", ?, ?, ?)", $_SESSION['user_id'], $key, $index, @@ -3157,10 +3188,10 @@ return; $this->db->query( "DELETE FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")", "DELETE FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")", $_SESSION['user_id'], $key); } @@ -3174,10 +3205,10 @@ return; $this->db->query( "DELETE FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=? AND idx>=?", "DELETE FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?". " AND idx>=?", $_SESSION['user_id'], $key, $start_index); } @@ -3197,10 +3228,10 @@ } $sql_result = $this->db->query( "SELECT MIN(idx) AS minidx FROM ".get_table_name('messages')." WHERE user_id=? AND cache_key=?" "SELECT MIN(idx) AS minidx". " FROM ".get_table_name('messages'). " WHERE user_id=?". " AND cache_key=?" .(!empty($uids) ? " AND uid IN (".$this->db->array2list($uids, 'integer').")" : ''), $_SESSION['user_id'], $key); @@ -3237,8 +3268,7 @@ $c = count($a); $j = 0; foreach ($a as $val) { foreach ($a as $val) { $j++; $address = trim($val['address']); $name = trim($val['name']); @@ -3255,7 +3285,8 @@ $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string); 'string' => $string ); if ($max && $j==$max) break; @@ -3280,6 +3311,7 @@ $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"]; @@ -3383,23 +3415,22 @@ $count = count($a); // should be in format "charset?encoding?base64_string" if ($count >= 3) { if ($count >= 3) { for ($i=2; $i<$count; $i++) $rest.=$a[$i]; if (($a[1]=="B")||($a[1]=="b")) if (($a[1]=='B') || ($a[1]=='b')) $rest = base64_decode($rest); else if (($a[1]=="Q")||($a[1]=="q")) { $rest = str_replace("_", " ", $rest); else if (($a[1]=='Q') || ($a[1]=='q')) { $rest = str_replace('_', ' ', $rest); $rest = quoted_printable_decode($rest); } return rcube_charset_convert($rest, $a[0]); } else return $str; // we dont' know what to do with this // we dont' know what to do with this return $str; } @@ -3412,23 +3443,16 @@ */ function mime_decode($input, $encoding='7bit') { switch (strtolower($encoding)) { switch (strtolower($encoding)) { case 'quoted-printable': return quoted_printable_decode($input); break; case 'base64': return base64_decode($input); break; case 'x-uuencode': case 'x-uue': case 'uue': case 'uuencode': return convert_uudecode($input); break; case '7bit': default: return $input; @@ -3450,57 +3474,6 @@ // defaults to what is specified in the class header return rcube_charset_convert($body, $this->default_charset); } /** * Translate UID to message ID * * @param int Message UID * @param string Mailbox name * @return int Message ID */ function get_id($uid, $mbox_name=NULL) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_uid2id($uid, $mailbox); } /** * Translate message number to UID * * @param int Message ID * @param string Mailbox name * @return int Message UID */ function get_uid($id,$mbox_name=NULL) { $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; return $this->_id2uid($id, $mailbox); } /** * Modify folder name for input/output according to root dir and namespace * * @param string Folder name * @param string Mode * @return string Folder name */ function mod_mailbox($mbox_name, $mode='in') { if ($mbox_name == 'INBOX') return $mbox_name; if (!empty($this->root_dir)) { if ($mode=='in') $mbox_name = $this->root_dir.$this->delimiter.$mbox_name; else if (!empty($mbox_name)) // $mode=='out' $mbox_name = substr($mbox_name, strlen($this->root_dir)+1); } return $mbox_name; } @@ -3531,9 +3504,8 @@ $delimiter = $this->get_hierarchy_delimiter(); // find default folders and skip folders starting with '.' foreach ($a_folders as $i => $folder) { if ($folder{0}=='.') foreach ($a_folders as $i => $folder) { if ($folder[0] == '.') continue; if (($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p]) @@ -3588,7 +3560,7 @@ $mbox_name = $this->mailbox; if (!isset($this->uid_id_map[$mbox_name][$uid])) $this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid); $this->uid_id_map[$mbox_name][$uid] = $this->conn->UID2ID($mbox_name, $uid); return $this->uid_id_map[$mbox_name][$uid]; } @@ -3604,7 +3576,7 @@ if ($uid = array_search($id, (array)$this->uid_id_map[$mbox_name])) return $uid; $uid = iil_C_ID2UID($this->conn, $mbox_name, $id); $uid = $this->conn->ID2UID($mbox_name, $id); $this->uid_id_map[$mbox_name][$uid] = $id; return $uid; @@ -3620,20 +3592,18 @@ $updated = false; if (is_array($a_mboxes)) foreach ($a_mboxes as $i => $mbox_name) { foreach ($a_mboxes as $i => $mbox_name) { $mailbox = $this->mod_mailbox($mbox_name); $a_mboxes[$i] = $mailbox; if ($mode=='subscribe') $updated = iil_C_Subscribe($this->conn, $mailbox); $updated = $this->conn->subscribe($mailbox); else if ($mode=='unsubscribe') $updated = iil_C_UnSubscribe($this->conn, $mailbox); $updated = $this->conn->unsubscribe($mailbox); } // get cached mailbox list if ($updated) { if ($updated) { $a_mailbox_cache = $this->get_cache('mailboxes'); if (!is_array($a_mailbox_cache)) return $updated; @@ -3692,8 +3662,7 @@ $a_mailbox_cache = $this->get_cache('messagecount'); if (is_array($a_mailbox_cache[$mailbox])) { if (is_array($a_mailbox_cache[$mailbox])) { unset($a_mailbox_cache[$mailbox]); $this->update_cache('messagecount', $a_mailbox_cache); } @@ -3710,10 +3679,9 @@ $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers); $lines = explode("\n", $headers); $c = count($lines); for ($i=0; $i<$c; $i++) { if ($p = strpos($lines[$i], ': ')) { for ($i=0; $i<$c; $i++) { if ($p = strpos($lines[$i], ': ')) { $field = strtolower(substr($lines[$i], 0, $p)); $value = trim(substr($lines[$i], $p+1)); if (!empty($value)) @@ -3734,14 +3702,12 @@ $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str)); $result = array(); foreach ($a as $key => $val) { foreach ($a as $key => $val) { $val = preg_replace("/([\"\w])</", "$1 <", $val); $sub_a = rcube_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val); $result[$key]['name'] = ''; foreach ($sub_a as $k => $v) { foreach ($sub_a as $k => $v) { // use angle brackets in regexp to not handle names with @ sign if (preg_match('/^<\S+@\S+>$/', $v)) $result[$key]['address'] = trim($v, '<>'); @@ -3792,7 +3758,7 @@ /** * Class for sorting an array of iilBasicHeader objects in a predetermined order. * Class for sorting an array of rcube_mail_header objects in a predetermined order. * * @package Mail * @author Eric Stadtherr @@ -3814,7 +3780,7 @@ /** * Sort the array of header objects * * @param array Array of iilBasicHeader objects indexed by UID * @param array Array of rcube_mail_header objects indexed by UID */ function sort_headers(&$headers) { program/include/rcube_imap_generic.php
New file @@ -0,0 +1,2242 @@ <?php /* +-----------------------------------------------------------------------+ | program/include/rcube_imap_generic.php | | | | This file is part of the RoundCube Webmail client | | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland | | Licensed under the GNU GPL | | | | PURPOSE: | | Provide alternative IMAP library that doesn't rely on the standard | | C-Client based version. This allows to function regardless | | of whether or not the PHP build it's running on has IMAP | | functionality built-in. | | | | Based on Iloha IMAP Library. See http://ilohamail.org/ for details | | | +-----------------------------------------------------------------------+ | Author: Aleksander Machniak <alec@alec.pl> | | Author: Ryo Chijiiwa <Ryo@IlohaMail.org> | +-----------------------------------------------------------------------+ $Id$ */ class rcube_mail_header { public $id; public $uid; public $subject; public $from; public $to; public $cc; public $replyto; public $in_reply_to; public $date; public $messageID; public $size; public $encoding; public $charset; public $ctype; public $flags; public $timestamp; public $f; public $body_structure; public $internaldate; public $references; public $priority; public $mdn_to; public $mdn_sent = false; public $is_draft = false; public $seen = false; public $deleted = false; public $recent = false; public $answered = false; public $forwarded = false; public $junk = false; public $flagged = false; public $has_children = false; public $depth = 0; public $unread_children = 0; public $others = array(); } class rcube_imap_generic { public $error; public $errornum; public $message; public $rootdir; public $delimiter; public $permanentflags = array(); public $flags = array( 'SEEN' => '\\Seen', 'DELETED' => '\\Deleted', 'RECENT' => '\\Recent', 'ANSWERED' => '\\Answered', 'DRAFT' => '\\Draft', 'FLAGGED' => '\\Flagged', 'FORWARDED' => '$Forwarded', 'MDNSENT' => '$MDNSent', '*' => '\\*', ); private $exists; private $recent; private $selected; private $fp; private $host; private $logged = false; private $capability = array(); private $capability_readed = false; private $prefs; /** * Object constructor */ function __construct() { } private function putLine($string, $endln=true) { if (!$this->fp) return false; if (!empty($this->prefs['debug_mode'])) { write_log('imap', 'C: '. rtrim($string)); } return fputs($this->fp, $string . ($endln ? "\r\n" : '')); } // $this->putLine replacement with Command Continuation Requests (RFC3501 7.5) support private function putLineC($string, $endln=true) { if (!$this->fp) return NULL; if ($endln) $string .= "\r\n"; $res = 0; if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) { for ($i=0, $cnt=count($parts); $i<$cnt; $i++) { if (preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) { $bytes = $this->putLine($parts[$i].$parts[$i+1], false); if ($bytes === false) return false; $res += $bytes; $line = $this->readLine(1000); // handle error in command if ($line[0] != '+') return false; $i++; } else { $bytes = $this->putLine($parts[$i], false); if ($bytes === false) return false; $res += $bytes; } } } return $res; } private function readLine($size=1024) { $line = ''; if (!$this->fp) { return NULL; } if (!$size) { $size = 1024; } do { if (feof($this->fp)) { return $line ? $line : NULL; } $buffer = fgets($this->fp, $size); if ($buffer === false) { fclose($this->fp); $this->fp = null; break; } if (!empty($this->prefs['debug_mode'])) { write_log('imap', 'S: '. chop($buffer)); } $line .= $buffer; } while ($buffer[strlen($buffer)-1] != "\n"); return $line; } private function multLine($line, $escape=false) { $line = chop($line); if (preg_match('/\{[0-9]+\}$/', $line)) { $out = ''; preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a); $bytes = $a[2][0]; while (strlen($out) < $bytes) { $line = $this->readBytes($bytes); if ($line === NULL) break; $out .= $line; } $line = $a[1][0] . '"' . ($escape ? $this->Escape($out) : $out) . '"'; } return $line; } private function readBytes($bytes) { $data = ''; $len = 0; while ($len < $bytes && !feof($this->fp)) { $d = fread($this->fp, $bytes-$len); if (!empty($this->prefs['debug_mode'])) { write_log('imap', 'S: '. $d); } $data .= $d; $data_len = strlen($data); if ($len == $data_len) { break; // nothing was read -> exit to avoid apache lockups } $len = $data_len; } return $data; } // don't use it in loops, until you exactly know what you're doing private function readReply() { do { $line = trim($this->readLine(1024)); } while ($line[0] == '*'); return $line; } private function parseResult($string) { $a = explode(' ', trim($string)); if (count($a) >= 2) { $res = strtoupper($a[1]); if ($res == 'OK') { return 0; } else if ($res == 'NO') { return -1; } else if ($res == 'BAD') { return -2; } else if ($res == 'BYE') { fclose($this->fp); $this->fp = null; return -3; } } return -4; } // check if $string starts with $match (or * BYE/BAD) private function startsWith($string, $match, $error=false, $nonempty=false) { $len = strlen($match); if ($len == 0) { return false; } if (!$this->fp) { return true; } if (strncmp($string, $match, $len) == 0) { return true; } if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { if (strtoupper($m[1]) == 'BYE') { fclose($this->fp); $this->fp = null; } return true; } if ($nonempty && !strlen($string)) { return true; } return false; } private function startsWithI($string, $match, $error=false, $nonempty=false) { $len = strlen($match); if ($len == 0) { return false; } if (!$this->fp) { return true; } if (strncasecmp($string, $match, $len) == 0) { return true; } if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) { if (strtoupper($m[1]) == 'BYE') { fclose($this->fp); $this->fp = null; } return true; } if ($nonempty && !strlen($string)) { return true; } return false; } function getCapability($name) { if (in_array($name, $this->capability)) { return true; } else if ($this->capability_readed) { return false; } // get capabilities (only once) because initial // optional CAPABILITY response may differ $this->capability = array(); if (!$this->putLine("cp01 CAPABILITY")) { return false; } do { $line = trim($this->readLine(1024)); $a = explode(' ', $line); if ($line[0] == '*') { while (list($k, $w) = each($a)) { if ($w != '*' && $w != 'CAPABILITY') $this->capability[] = strtoupper($w); } } } while ($a[0] != 'cp01'); $this->capability_readed = true; if (in_array($name, $this->capability)) { return true; } return false; } function clearCapability() { $this->capability = array(); $this->capability_readed = false; } function authenticate($user, $pass, $encChallenge) { $ipad = ''; $opad = ''; // initialize ipad, opad for ($i=0; $i<64; $i++) { $ipad .= chr(0x36); $opad .= chr(0x5C); } // pad $pass so it's 64 bytes $padLen = 64 - strlen($pass); for ($i=0; $i<$padLen; $i++) { $pass .= chr(0); } // generate hash $hash = md5($this->xor($pass,$opad) . pack("H*", md5($this->xor($pass, $ipad) . base64_decode($encChallenge)))); // generate reply $reply = base64_encode($user . ' ' . $hash); // send result, get reply $this->putLine($reply); $line = $this->readLine(1024); // process result $result = $this->parseResult($line); if ($result == 0) { $this->error .= ''; $this->errornum = 0; return $this->fp; } $this->error .= 'Authentication for ' . $user . ' failed (AUTH): "'; $this->error .= htmlspecialchars($line) . '"'; $this->errornum = $result; return $result; } function login($user, $password) { $this->putLine('a001 LOGIN "'.$this->escape($user).'" "'.$this->escape($password).'"'); $line = $this->readReply(); // process result $result = $this->parseResult($line); if ($result == 0) { $this->error .= ''; $this->errornum = 0; return $this->fp; } fclose($this->fp); $this->fp = false; $this->error .= 'Authentication for ' . $user . ' failed (LOGIN): "'; $this->error .= htmlspecialchars($line)."\""; $this->errornum = $result; return $result; } function namespace() { if (isset($this->prefs['rootdir']) && is_string($this->prefs['rootdir'])) { $this->rootdir = $this->prefs['rootdir']; return true; } if (!$this->getCapability('NAMESPACE')) { return false; } if (!$this->putLine("ns1 NAMESPACE")) { return false; } do { $line = $this->readLine(1024); if ($this->startsWith($line, '* NAMESPACE')) { $i = 0; $line = $this->unEscape($line); $data = $this->parseNamespace(substr($line,11), $i, 0, 0); } } while (!$this->startsWith($line, 'ns1', true, true)); if (!is_array($data)) { return false; } $user_space_data = $data[0]; if (!is_array($user_space_data)) { return false; } $first_userspace = $user_space_data[0]; if (count($first_userspace)!=2) { return false; } $this->rootdir = $first_userspace[0]; $this->delimiter = $first_userspace[1]; $this->prefs['rootdir'] = substr($this->rootdir, 0, -1); $this->prefs['delimiter'] = $this->delimiter; return true; } /** * Gets the delimiter, for example: * INBOX.foo -> . * INBOX/foo -> / * INBOX\foo -> \ * * @return mixed A delimiter (string), or false. * @see connect() */ function getHierarchyDelimiter() { if ($this->delimiter) { return $this->delimiter; } if (!empty($this->prefs['delimiter'])) { return ($this->delimiter = $this->prefs['delimiter']); } $delimiter = false; // try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8) if (!$this->putLine('ghd LIST "" ""')) { return false; } do { $line = $this->readLine(500); if ($line[0] == '*') { $line = rtrim($line); $a = rcube_explode_quoted_string(' ', $this->unEscape($line)); if ($a[0] == '*') { $delimiter = str_replace('"', '', $a[count($a)-2]); } } } while (!$this->startsWith($line, 'ghd', true, true)); if (strlen($delimiter)>0) { return $delimiter; } // if that fails, try namespace extension // try to fetch namespace data if (!$this->putLine("ns1 NAMESPACE")) { return false; } do { $line = $this->readLine(1024); if ($this->startsWith($line, '* NAMESPACE')) { $i = 0; $line = $this->unEscape($line); $data = $this->parseNamespace(substr($line,11), $i, 0, 0); } } while (!$this->startsWith($line, 'ns1', true, true)); if (!is_array($data)) { return false; } // extract user space data (opposed to global/shared space) $user_space_data = $data[0]; if (!is_array($user_space_data)) { return false; } // get first element $first_userspace = $user_space_data[0]; if (!is_array($first_userspace)) { return false; } // extract delimiter $delimiter = $first_userspace[1]; return $delimiter; } function connect($host, $user, $password, $options=null) { // set options if (is_array($options)) { $this->prefs = $options; } // set auth method if (!empty($this->prefs['auth_method'])) { $auth_method = strtoupper($this->prefs['auth_method']); } else { $auth_method = 'CHECK'; } $message = "INITIAL: $auth_method\n"; $result = false; // initialize connection $this->error = ''; $this->errornum = 0; $this->selected = ''; $this->user = $user; $this->host = $host; $this->logged = false; // check input if (empty($host)) { $this->error = "Empty host"; $this->errornum = -1; return false; } if (empty($user)) { $this->error = "Empty user"; $this->errornum = -1; return false; } if (empty($password)) { $this->error = "Empty password"; $this->errornum = -1; 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; } $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, 10); if (!$this->fp) { $this->error = sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr); $this->errornum = -2; return false; } stream_set_timeout($this->fp, 10); $line = trim(fgets($this->fp, 8192)); if ($this->prefs['debug_mode'] && $line) { write_log('imap', 'S: '. $line); } // Connected to wrong port or connection error? if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) { if ($line) $this->error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line); else $this->error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']); $this->errornum = -2; return false; } // RFC3501 [7.1] optional CAPABILITY response if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) { $this->capability = explode(' ', strtoupper($matches[1])); } $this->message .= $line; // TLS connection if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { if (version_compare(PHP_VERSION, '5.1.0', '>=')) { $this->putLine("tls0 STARTTLS"); $line = $this->readLine(4096); if (!$this->startsWith($line, "tls0 OK")) { $this->error = "Server responded to STARTTLS with: $line"; $this->errornum = -2; return false; } if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { $this->error = "Unable to negotiate TLS"; $this->errornum = -2; return false; } // Now we're authenticated, capabilities need to be reread $this->clearCapability(); } } $orig_method = $auth_method; if ($auth_method == 'CHECK') { // check for supported auth methods if ($this->getCapability('AUTH=CRAM-MD5') || $this->getCapability('AUTH=CRAM_MD5')) { $auth_method = 'AUTH'; } else { // default to plain text auth $auth_method = 'PLAIN'; } } if ($auth_method == 'AUTH') { // do CRAM-MD5 authentication $this->putLine("a000 AUTHENTICATE CRAM-MD5"); $line = trim($this->readLine(1024)); if ($line[0] == '+') { // got a challenge string, try CRAM-MD5 $result = $this->authenticate($user, $password, substr($line,2)); // stop if server sent BYE response if ($result == -3) { return false; } } if (!is_resource($result) && $orig_method == 'CHECK') { $auth_method = 'PLAIN'; } } if ($auth_method == 'PLAIN') { // do plain text auth $result = $this->login($user, $password); } if (is_resource($result)) { if ($this->prefs['force_caps']) { $this->clearCapability(); } $this->namespace(); $this->logged = true; return true; } else { return false; } } function connected() { return ($this->fp && $this->logged) ? true : false; } function close() { if ($this->putLine("I LOGOUT")) { if (!feof($this->fp)) fgets($this->fp, 1024); } @fclose($this->fp); $this->fp = false; } function select($mailbox) { if (empty($mailbox)) { return false; } if ($this->selected == $mailbox) { return true; } if ($this->putLine("sel1 SELECT \"".$this->escape($mailbox).'"')) { do { $line = chop($this->readLine(300)); $a = explode(' ', $line); if (count($a) == 3) { $token = strtoupper($a[2]); if ($token == 'EXISTS') { $this->exists = (int) $a[1]; } else if ($token == 'RECENT') { $this->recent = (int) $a[1]; } } else if (preg_match('/\[?PERMANENTFLAGS\s+\(([^\)]+)\)\]/U', $line, $match)) { $this->permanentflags = explode(' ', $match[1]); } } while (!$this->startsWith($line, 'sel1', true, true)); if (strcasecmp($a[1], 'OK') == 0) { $this->selected = $mailbox; return true; } else { $this->error = "Couldn't select $mailbox"; } } return false; } function checkForRecent($mailbox) { if (empty($mailbox)) { $mailbox = 'INBOX'; } $this->select($mailbox); if ($this->selected == $mailbox) { return $this->recent; } return false; } function countMessages($mailbox, $refresh = false) { if ($refresh) { $this->selected = ''; } $this->select($mailbox); if ($this->selected == $mailbox) { return $this->exists; } return false; } function sort($mailbox, $field, $add='', $is_uid=FALSE, $encoding = 'US-ASCII') { $field = strtoupper($field); if ($field == 'INTERNALDATE') { $field = 'ARRIVAL'; } $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); if (!$fields[$field]) { return false; } /* Do "SELECT" command */ if (!$this->select($mailbox)) { return false; } $is_uid = $is_uid ? 'UID ' : ''; // message IDs if (is_array($add)) $add = $this->compressMessageSet(join(',', $add)); $command = "s ".$is_uid."SORT ($field) $encoding ALL"; $line = $data = ''; if (!empty($add)) $command .= ' '.$add; if (!$this->putLineC($command)) { return false; } do { $line = chop($this->readLine()); if ($this->startsWith($line, '* SORT')) { $data .= substr($line, 7); } else if (preg_match('/^[0-9 ]+$/', $line)) { $data .= $line; } } while (!$this->startsWith($line, 's ', true, true)); $result_code = $this->parseResult($line); if ($result_code != 0) { $this->error = "Sort: $line"; return false; } return preg_split('/\s+/', $data, -1, PREG_SPLIT_NO_EMPTY); } function fetchHeaderIndex($mailbox, $message_set, $index_field='', $skip_deleted=true, $uidfetch=false) { if (is_array($message_set)) { if (!($message_set = $this->compressMessageSet(join(',', $message_set)))) return false; } else { list($from_idx, $to_idx) = explode(':', $message_set); if (empty($message_set) || (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)) { return false; } } $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); $fields_a['DATE'] = 1; $fields_a['INTERNALDATE'] = 4; $fields_a['ARRIVAL'] = 4; $fields_a['FROM'] = 1; $fields_a['REPLY-TO'] = 1; $fields_a['SENDER'] = 1; $fields_a['TO'] = 1; $fields_a['CC'] = 1; $fields_a['SUBJECT'] = 1; $fields_a['UID'] = 2; $fields_a['SIZE'] = 2; $fields_a['SEEN'] = 3; $fields_a['RECENT'] = 3; $fields_a['DELETED'] = 3; if (!($mode = $fields_a[$index_field])) { return false; } /* Do "SELECT" command */ if (!$this->select($mailbox)) { return false; } // build FETCH command string $key = 'fhi0'; $cmd = $uidfetch ? 'UID FETCH' : 'FETCH'; $deleted = $skip_deleted ? ' FLAGS' : ''; if ($mode == 1 && $index_field == 'DATE') $request = " $cmd $message_set (INTERNALDATE BODY.PEEK[HEADER.FIELDS (DATE)]$deleted)"; else if ($mode == 1) $request = " $cmd $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)]$deleted)"; else if ($mode == 2) { if ($index_field == 'SIZE') $request = " $cmd $message_set (RFC822.SIZE$deleted)"; else $request = " $cmd $message_set ($index_field$deleted)"; } else if ($mode == 3) $request = " $cmd $message_set (FLAGS)"; else // 4 $request = " $cmd $message_set (INTERNALDATE$deleted)"; $request = $key . $request; if (!$this->putLine($request)) { return false; } $result = array(); do { $line = chop($this->readLine(200)); $line = $this->multLine($line); if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { $id = $m[1]; $flags = NULL; if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { $flags = explode(' ', strtoupper($matches[1])); if (in_array('\\DELETED', $flags)) { $deleted[$id] = $id; continue; } } if ($mode == 1 && $index_field == 'DATE') { if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) { $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]); $value = trim($value); $result[$id] = $this->strToTime($value); } // non-existent/empty Date: header, use INTERNALDATE if (empty($result[$id])) { if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) $result[$id] = $this->strToTime($matches[1]); else $result[$id] = 0; } } else if ($mode == 1) { if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) { $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]); $result[$id] = trim($value); } else { $result[$id] = ''; } } else if ($mode == 2) { if (preg_match('/\((UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { $result[$id] = trim($matches[2]); } else { $result[$id] = 0; } } else if ($mode == 3) { if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { $flags = explode(' ', $matches[1]); } $result[$id] = in_array('\\'.$index_field, $flags) ? 1 : 0; } else if ($mode == 4) { if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) { $result[$id] = $this->strToTime($matches[1]); } else { $result[$id] = 0; } } } } while (!$this->startsWith($line, $key, true, true)); return $result; } private function compressMessageSet($message_set) { // given a comma delimited list of independent mid's, // compresses by grouping sequences together // if less than 255 bytes long, let's not bother if (strlen($message_set)<255) { return $message_set; } // see if it's already been compress if (strpos($message_set, ':') !== false) { return $message_set; } // separate, then sort $ids = explode(',', $message_set); sort($ids); $result = array(); $start = $prev = $ids[0]; foreach ($ids as $id) { $incr = $id - $prev; if ($incr > 1) { //found a gap if ($start == $prev) { $result[] = $prev; //push single id } else { $result[] = $start . ':' . $prev; //push sequence as start_id:end_id } $start = $id; //start of new sequence } $prev = $id; } // handle the last sequence/id if ($start==$prev) { $result[] = $prev; } else { $result[] = $start.':'.$prev; } // return as comma separated string return implode(',', $result); } function UID2ID($folder, $uid) { if ($uid > 0) { $id_a = $this->search($folder, "UID $uid"); if (is_array($id_a) && count($id_a) == 1) { return $id_a[0]; } } return false; } function ID2UID($folder, $id) { if (empty($id)) { return -1; } if (!$this->select($folder)) { return -1; } $result = -1; if ($this->putLine("fuid FETCH $id (UID)")) { do { $line = chop($this->readLine(1024)); if (preg_match("/^\* $id FETCH \(UID (.*)\)/i", $line, $r)) { $result = $r[1]; } } while (!$this->startsWith($line, 'fuid', true, true)); } return $result; } function fetchUIDs($mailbox, $message_set=null) { if (is_array($message_set)) $message_set = join(',', $message_set); else if (empty($message_set)) $message_set = '1:*'; return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false); } function fetchHeaders($mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') { $result = array(); if (!$this->select($mailbox)) { return false; } if (is_array($message_set)) $message_set = join(',', $message_set); $message_set = $this->compressMessageSet($message_set); if ($add) $add = ' '.strtoupper(trim($add)); /* FETCH uid, size, flags and headers */ $key = 'FH12'; $request = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set "; $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE "; if ($bodystr) $request .= "BODYSTRUCTURE "; $request .= "BODY.PEEK[HEADER.FIELDS "; $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC "; $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID "; $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY "; $request .= "X-DRAFT-INFO".$add.")])"; if (!$this->putLine($request)) { return false; } do { $line = $this->readLine(1024); $line = $this->multLine($line); if (!$line) break; $a = explode(' ', $line); if (($line[0] == '*') && ($a[2] == 'FETCH')) { $id = $a[1]; $result[$id] = new rcube_mail_header; $result[$id]->id = $id; $result[$id]->subject = ''; $result[$id]->messageID = 'mid:' . $id; $lines = array(); $ln = 0; // Sample reply line: // * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen) // INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...) // BODY[HEADER.FIELDS ... if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) { $str = $matches[1]; // swap parents with quotes, then explode $str = preg_replace('/[()]/', '"', $str); $a = rcube_explode_quoted_string(' ', $str); // did we get the right number of replies? $parts_count = count($a); if ($parts_count>=6) { for ($i=0; $i<$parts_count; $i=$i+2) { if ($a[$i] == 'UID') $result[$id]->uid = $a[$i+1]; else if ($a[$i] == 'RFC822.SIZE') $result[$id]->size = $a[$i+1]; else if ($a[$i] == 'INTERNALDATE') $time_str = $a[$i+1]; else if ($a[$i] == 'FLAGS') $flags_str = $a[$i+1]; } $time_str = str_replace('"', '', $time_str); // if time is gmt... $time_str = str_replace('GMT','+0000',$time_str); $result[$id]->internaldate = $time_str; $result[$id]->timestamp = $this->StrToTime($time_str); $result[$id]->date = $time_str; } // BODYSTRUCTURE if($bodystr) { while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { $line2 = $this->readLine(1024); $line .= $this->multLine($line2, true); } $result[$id]->body_structure = $m[1]; } // the rest of the result preg_match('/ BODY\[HEADER.FIELDS \(.*?\)\]\s*(.*)$/s', $line, $m); $reslines = explode("\n", trim($m[1], '"')); // re-parse (see below) foreach ($reslines as $resln) { if (ord($resln[0])<=32) { $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($resln); } else { $lines[++$ln] = trim($resln); } } } // Start parsing headers. The problem is, some header "lines" take up multiple lines. // So, we'll read ahead, and if the one we're reading now is a valid header, we'll // process the previous line. Otherwise, we'll keep adding the strings until we come // to the next valid header line. do { $line = chop($this->readLine(300), "\r\n"); // The preg_match below works around communigate imap, which outputs " UID <number>)". // Without this, the while statement continues on and gets the "FH0 OK completed" message. // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin // An alternative might be: // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break; // however, unsure how well this would work with all imap clients. if (preg_match("/^\s*UID [0-9]+\)$/", $line)) { break; } // handle FLAGS reply after headers (AOL, Zimbra?) if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) { $flags_str = $matches[1]; break; } if (ord($line[0])<=32) { $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line); } else { $lines[++$ln] = trim($line); } // patch from "Maksim Rubis" <siburny@hotmail.com> } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); if (strncmp($line, $key, strlen($key))) { // process header, fill rcube_mail_header obj. // initialize if (is_array($headers)) { reset($headers); while (list($k, $bar) = each($headers)) { $headers[$k] = ''; } } // create array with header field:data while ( list($lines_key, $str) = each($lines) ) { list($field, $string) = $this->splitHeaderLine($str); $field = strtolower($field); $string = preg_replace('/\n\s*/', ' ', $string); switch ($field) { case 'date'; $result[$id]->date = $string; $result[$id]->timestamp = $this->strToTime($string); break; case 'from': $result[$id]->from = $string; break; case 'to': $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string); break; case 'subject': $result[$id]->subject = $string; break; case 'reply-to': $result[$id]->replyto = $string; break; case 'cc': $result[$id]->cc = $string; break; case 'bcc': $result[$id]->bcc = $string; break; case 'content-transfer-encoding': $result[$id]->encoding = $string; break; case 'content-type': $ctype_parts = preg_split('/[; ]/', $string); $result[$id]->ctype = array_shift($ctype_parts); if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) { $result[$id]->charset = $regs[1]; } break; case 'in-reply-to': $result[$id]->in_reply_to = preg_replace('/[\n<>]/', '', $string); break; case 'references': $result[$id]->references = $string; break; case 'return-receipt-to': case 'disposition-notification-to': case 'x-confirm-reading-to': $result[$id]->mdn_to = $string; break; case 'message-id': $result[$id]->messageID = $string; break; case 'x-priority': if (preg_match('/^(\d+)/', $string, $matches)) $result[$id]->priority = intval($matches[1]); break; default: if (strlen($field) > 2) $result[$id]->others[$field] = $string; break; } // end switch () } // end while () } else { $a = explode(' ', $line); } // process flags if (!empty($flags_str)) { $flags_str = preg_replace('/[\\\"]/', '', $flags_str); $flags_a = explode(' ', $flags_str); if (is_array($flags_a)) { // reset($flags_a); foreach($flags_a as $flag) { $flag = strtoupper($flag); if ($flag == 'SEEN') { $result[$id]->seen = true; } else if ($flag == 'DELETED') { $result[$id]->deleted = true; } else if ($flag == 'RECENT') { $result[$id]->recent = true; } else if ($flag == 'ANSWERED') { $result[$id]->answered = true; } else if ($flag == '$FORWARDED') { $result[$id]->forwarded = true; } else if ($flag == 'DRAFT') { $result[$id]->is_draft = true; } else if ($flag == '$MDNSENT') { $result[$id]->mdn_sent = true; } else if ($flag == 'FLAGGED') { $result[$id]->flagged = true; } } $result[$id]->flags = $flags_a; } } } } while (!$this->startsWith($line, $key, true)); return $result; } function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') { $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); if (is_array($a)) { return array_shift($a); } return false; } function sortHeaders($a, $field, $flag) { if (empty($field)) { $field = 'uid'; } else { $field = strtolower($field); } if ($field == 'date' || $field == 'internaldate') { $field = 'timestamp'; } if (empty($flag)) { $flag = 'ASC'; } else { $flag = strtoupper($flag); } $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"'); $c = count($a); if ($c > 0) { // Strategy: // First, we'll create an "index" array. // Then, we'll use sort() on that array, // and use that to sort the main array. // create "index" array $index = array(); reset($a); while (list($key, $val) = each($a)) { if ($field == 'timestamp') { $data = $this->strToTime($val->date); if (!$data) { $data = $val->timestamp; } } else { $data = $val->$field; if (is_string($data)) { $data = strtoupper(str_replace($stripArr, '', $data)); } } $index[$key]=$data; } // sort index $i = 0; if ($flag == 'ASC') { asort($index); } else { arsort($index); } // form new array based on index $result = array(); reset($index); while (list($key, $val) = each($index)) { $result[$key]=$a[$key]; $i++; } } return $result; } function expunge($mailbox, $messages=NULL) { if (!$this->select($mailbox)) { return -1; } $c = 0; $command = $messages ? "UID EXPUNGE $messages" : "EXPUNGE"; if (!$this->putLine("exp1 $command")) { return -1; } do { $line = $this->readLine(100); if ($line[0] == '*') { $c++; } } while (!$this->startsWith($line, 'exp1', true, true)); if ($this->parseResult($line) == 0) { $this->selected = ''; // state has changed, need to reselect return $c; } $this->error = $line; return -1; } function modFlag($mailbox, $messages, $flag, $mod) { if ($mod != '+' && $mod != '-') { return -1; } $flag = $this->flags[strtoupper($flag)]; if (!$this->select($mailbox)) { return -1; } $c = 0; if (!$this->putLine("flg UID STORE $messages {$mod}FLAGS ($flag)")) { return false; } do { $line = $this->readLine(1000); if ($line[0] == '*') { $c++; } } while (!$this->startsWith($line, 'flg', true, true)); if ($this->parseResult($line) == 0) { return $c; } $this->error = $line; return -1; } function flag($mailbox, $messages, $flag) { return $this->modFlag($mailbox, $messages, $flag, '+'); } function unflag($mailbox, $messages, $flag) { return $this->modFlag($mailbox, $messages, $flag, '-'); } function delete($mailbox, $messages) { return $this->modFlag($mailbox, $messages, 'DELETED', '+'); } function copy($messages, $from, $to) { if (empty($from) || empty($to)) { return -1; } if (!$this->select($from)) { return -1; } $this->putLine("cpy1 UID COPY $messages \"".$this->escape($to)."\""); $line = $this->readReply(); return $this->parseResult($line); } function countUnseen($folder) { $index = $this->search($folder, 'ALL UNSEEN'); if (is_array($index)) return count($index); return false; } // Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about // 7 times instead :-) See comments on http://uk2.php.net/references and this article: // http://derickrethans.nl/files/phparch-php-variables-article.pdf private function parseThread($str, $begin, $end, $root, $parent, $depth, &$depthmap, &$haschildren) { $node = array(); if ($str[$begin] != '(') { $stop = $begin + strspn($str, "1234567890", $begin, $end - $begin); $msg = substr($str, $begin, $stop - $begin); if ($msg == 0) return $node; if (is_null($root)) $root = $msg; $depthmap[$msg] = $depth; $haschildren[$msg] = false; if (!is_null($parent)) $haschildren[$parent] = true; if ($stop + 1 < $end) $node[$msg] = $this->parseThread($str, $stop + 1, $end, $root, $msg, $depth + 1, $depthmap, $haschildren); else $node[$msg] = array(); } else { $off = $begin; while ($off < $end) { $start = $off; $off++; $n = 1; while ($n > 0) { $p = strpos($str, ')', $off); if ($p === false) { error_log('Mismatched brackets parsing IMAP THREAD response:'); error_log(substr($str, ($begin < 10) ? 0 : ($begin - 10), $end - $begin + 20)); error_log(str_repeat(' ', $off - (($begin < 10) ? 0 : ($begin - 10)))); return $node; } $p1 = strpos($str, '(', $off); if ($p1 !== false && $p1 < $p) { $off = $p1 + 1; $n++; } else { $off = $p + 1; $n--; } } $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); } } return $node; } function thread($folder, $algorithm='REFERENCES', $criteria='', $encoding='US-ASCII') { if (!$this->select($folder)) { return false; } $encoding = $encoding ? trim($encoding) : 'US-ASCII'; $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; if (!$this->putLineC("thrd1 THREAD $algorithm $encoding $criteria")) { return false; } do { $line = trim($this->readLine(10000)); if (preg_match('/^\* THREAD/', $line)) { $str = trim(substr($line, 8)); $depthmap = array(); $haschildren = array(); $tree = $this->parseThread($str, 0, strlen($str), null, null, 0, $depthmap, $haschildren); } } while (!$this->startsWith($line, 'thrd1', true, true)); $result_code = $this->parseResult($line); if ($result_code == 0) { return array($tree, $depthmap, $haschildren); } $this->error = "Thread: $line"; return false; } function search($folder, $criteria) { if (!$this->select($folder)) { return false; } $data = ''; $query = "srch1 SEARCH " . chop($criteria); if (!$this->putLineC($query)) { return false; } do { $line = trim($this->readLine()); if ($this->startsWith($line, '* SEARCH')) { $data .= substr($line, 8); } else if (preg_match('/^[0-9 ]+$/', $line)) { $data .= $line; } } while (!$this->startsWith($line, 'srch1', true, true)); $result_code = $this->parseResult($line); if ($result_code == 0) { return preg_split('/\s+/', $data, -1, PREG_SPLIT_NO_EMPTY); } $this->error = "Search: $line"; return false; } function move($messages, $from, $to) { if (!$from || !$to) { return -1; } $r = $this->copy($messages, $from, $to); if ($r==0) { return $this->delete($from, $messages); } return $r; } function listMailboxes($ref, $mailbox) { if (empty($mailbox)) { $mailbox = '*'; } if (empty($ref) && $this->rootdir) { $ref = $this->rootdir; } // send command if (!$this->putLine("lmb LIST \"". $this->escape($ref) ."\" \"". $this->escape($mailbox) ."\"")) { return false; } $i = 0; // get folder list do { $line = $this->readLine(500); $line = $this->multLine($line, true); $a = explode(' ', $line); if (($line[0] == '*') && ($a[1] == 'LIST')) { $line = rtrim($line); // split one line $a = rcube_explode_quoted_string(' ', $line); // last string is folder name $folders[$i] = preg_replace(array('/^"/', '/"$/'), '', $this->unEscape($a[count($a)-1])); // second from last is delimiter $delim = trim($a[count($a)-2], '"'); // is it a container? $i++; } } while (!$this->startsWith($line, 'lmb', true)); if (is_array($folders)) { if (!empty($ref)) { // if rootdir was specified, make sure it's the first element // some IMAP servers (i.e. Courier) won't return it if ($ref[strlen($ref)-1]==$delim) $ref = substr($ref, 0, strlen($ref)-1); if ($folders[0]!=$ref) array_unshift($folders, $ref); } return $folders; } else if ($this->parseResult($line) == 0) { return array('INBOX'); } $this->error = $line; return false; } function listSubscribed($ref, $mailbox) { if (empty($mailbox)) { $mailbox = '*'; } if (empty($ref) && $this->rootdir) { $ref = $this->rootdir; } $folders = array(); // send command if (!$this->putLine('lsb LSUB "'. $this->escape($ref) . '" "' . $this->escape($mailbox).'"')) { $this->error = "Couldn't send LSUB command"; return false; } $i = 0; // get folder list do { $line = $this->readLine(500); $line = $this->multLine($line, true); $a = explode(' ', $line); if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) { $line = rtrim($line); // split one line $a = rcube_explode_quoted_string(' ', $line); // last string is folder name $folder = preg_replace(array('/^"/', '/"$/'), '', $this->UnEscape($a[count($a)-1])); // @TODO: do we need this check??? if (!in_array($folder, $folders)) { $folders[$i] = $folder; } // second from last is delimiter $delim = trim($a[count($a)-2], '"'); // is it a container? $i++; } } while (!$this->startsWith($line, 'lsb', true)); if (is_array($folders)) { if (!empty($ref)) { // if rootdir was specified, make sure it's the first element // some IMAP servers (i.e. Courier) won't return it if ($ref[strlen($ref)-1]==$delim) { $ref = substr($ref, 0, strlen($ref)-1); } if ($folders[0]!=$ref) { array_unshift($folders, $ref); } } return $folders; } $this->error = $line; return false; } function fetchMIMEHeaders($mailbox, $id, $parts, $mime=true) { if (!$this->select($mailbox)) { return false; } $result = false; $parts = (array) $parts; $key = 'fmh0'; $peeks = ''; $idx = 0; $type = $mime ? 'MIME' : 'HEADER'; // format request foreach($parts as $part) $peeks[] = "BODY.PEEK[$part.$type]"; $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; // send request if (!$this->putLine($request)) { return false; } do { $line = $this->readLine(1000); $line = $this->multLine($line); if (preg_match('/BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) { $idx = $matches[1]; $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.'.$type.'\]\s+/', '', $line); $result[$idx] = trim($result[$idx], '"'); $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B"); } } while (!$this->startsWith($line, $key, true)); return $result; } function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL) { $part = empty($part) ? 'HEADER' : $part.'.MIME'; return $this->handlePartBody($mailbox, $id, $is_uid, $part); } function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL) { if (!$this->select($mailbox)) { return false; } switch ($encoding) { case 'base64': $mode = 1; break; case 'quoted-printable': $mode = 2; break; case 'x-uuencode': case 'x-uue': case 'uue': case 'uuencode': $mode = 3; break; default: $mode = 0; } $reply_key = '* ' . $id; $result = false; // format request $key = 'ftch0'; $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id (BODY.PEEK[$part])"; // send request if (!$this->putLine($request)) { return false; } // receive reply line do { $line = chop($this->readLine(1000)); $a = explode(' ', $line); } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH'); $len = strlen($line); // handle empty "* X FETCH ()" response if ($line[$len-1] == ')' && $line[$len-2] != '(') { // one line response, get everything between first and last quotes if (substr($line, -4, 3) == 'NIL') { // NIL response $result = ''; } else { $from = strpos($line, '"') + 1; $to = strrpos($line, '"'); $len = $to - $from; $result = substr($line, $from, $len); } if ($mode == 1) $result = base64_decode($result); else if ($mode == 2) $result = quoted_printable_decode($result); else if ($mode == 3) $result = convert_uudecode($result); } else if ($line[$len-1] == '}') { // multi-line request, find sizes of content and receive that many bytes $from = strpos($line, '{') + 1; $to = strrpos($line, '}'); $len = $to - $from; $sizeStr = substr($line, $from, $len); $bytes = (int)$sizeStr; $prev = ''; while ($bytes > 0) { $line = $this->readLine(1024); $len = strlen($line); if ($len > $bytes) { $line = substr($line, 0, $bytes); $len = strlen($line); } $bytes -= $len; if ($mode == 1) { $line = rtrim($line, "\t\r\n\0\x0B"); // create chunks with proper length for base64 decoding $line = $prev.$line; $length = strlen($line); if ($length % 4) { $length = floor($length / 4) * 4; $prev = substr($line, $length); $line = substr($line, 0, $length); } else $prev = ''; if ($file) fwrite($file, base64_decode($line)); else if ($print) echo base64_decode($line); else $result .= base64_decode($line); } else if ($mode == 2) { $line = rtrim($line, "\t\r\0\x0B"); if ($file) fwrite($file, quoted_printable_decode($line)); else if ($print) echo quoted_printable_decode($line); else $result .= quoted_printable_decode($line); } else if ($mode == 3) { $line = rtrim($line, "\t\r\n\0\x0B"); if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) continue; if ($file) fwrite($file, convert_uudecode($line)); else if ($print) echo convert_uudecode($line); else $result .= convert_uudecode($line); } else { $line = rtrim($line, "\t\r\n\0\x0B"); if ($file) fwrite($file, $line . "\n"); else if ($print) echo $line . "\n"; else $result .= $line . "\n"; } } } // read in anything up until last line if (!$end) do { $line = $this->readLine(1024); } while (!$this->startsWith($line, $key, true)); if ($result) { if ($file) { fwrite($file, $result); } else if ($print) { echo $result; } else return $result; return true; } return false; } function createFolder($folder) { if ($this->putLine('c CREATE "' . $this->escape($folder) . '"')) { do { $line = $this->readLine(300); } while (!$this->startsWith($line, 'c ', true, true)); return ($this->parseResult($line) == 0); } return false; } function renameFolder($from, $to) { if ($this->putLine('r RENAME "' . $this->escape($from) . '" "' . $this->escape($to) . '"')) { do { $line = $this->readLine(300); } while (!$this->startsWith($line, 'r ', true, true)); return ($this->parseResult($line) == 0); } return false; } function deleteFolder($folder) { if ($this->putLine('d DELETE "' . $this->escape($folder). '"')) { do { $line = $this->readLine(300); } while (!$this->startsWith($line, 'd ', true, true)); return ($this->parseResult($line) == 0); } return false; } function clearFolder($folder) { $num_in_trash = $this->countMessages($folder); if ($num_in_trash > 0) { $this->delete($folder, '1:*'); } return ($this->expunge($folder) >= 0); } function subscribe($folder) { $query = 'sub1 SUBSCRIBE "' . $this->escape($folder). '"'; $this->putLine($query); $line = trim($this->readLine(512)); return ($this->parseResult($line) == 0); } function unsubscribe($folder) { $query = 'usub1 UNSUBSCRIBE "' . $this->escape($folder) . '"'; $this->putLine($query); $line = trim($this->readLine(512)); return ($this->parseResult($line) == 0); } function append($folder, &$message) { if (!$folder) { return false; } $message = str_replace("\r", '', $message); $message = str_replace("\n", "\r\n", $message); $len = strlen($message); if (!$len) { return false; } $request = 'a APPEND "' . $this->escape($folder) .'" (\\Seen) {' . $len . '}'; if ($this->putLine($request)) { $line = $this->readLine(512); if ($line[0] != '+') { // $errornum = $this->parseResult($line); $this->error = "Cannot write to folder: $line"; return false; } if (!$this->putLine($message)) { return false; } do { $line = $this->readLine(); } while (!$this->startsWith($line, 'a ', true, true)); $result = ($this->parseResult($line) == 0); if (!$result) { $this->error = $line; } return $result; } $this->error = "Couldn't send command \"$request\""; return false; } function appendFromFile($folder, $path, $headers=null, $separator="\n\n") { if (!$folder) { return false; } // open message file $in_fp = false; if (file_exists(realpath($path))) { $in_fp = fopen($path, 'r'); } if (!$in_fp) { $this->error = "Couldn't open $path for reading"; return false; } $len = filesize($path); if (!$len) { return false; } if ($headers) { $headers = preg_replace('/[\r\n]+$/', '', $headers); $len += strlen($headers) + strlen($separator); } // send APPEND command $request = 'a APPEND "' . $this->escape($folder) . '" (\\Seen) {' . $len . '}'; if ($this->putLine($request)) { $line = $this->readLine(512); if ($line[0] != '+') { //$errornum = $this->parseResult($line); $this->error = "Cannot write to folder: $line"; return false; } // send headers with body separator if ($headers) { $this->putLine($headers . $separator, false); } // send file while (!feof($in_fp) && $this->fp) { $buffer = fgets($in_fp, 4096); $this->putLine($buffer, false); } fclose($in_fp); if (!$this->putLine('')) { // \r\n return false; } // read response do { $line = $this->readLine(); } while (!$this->startsWith($line, 'a ', true, true)); $result = ($this->parseResult($line) == 0); if (!$result) { $this->error = $line; } return $result; } $this->error = "Couldn't send command \"$request\""; return false; } function fetchStructureString($folder, $id, $is_uid=false) { if (!$this->select($folder)) { return false; } $key = 'F1247'; $result = false; if ($this->putLine($key . ($is_uid ? ' UID' : '') ." FETCH $id (BODYSTRUCTURE)")) { do { $line = $this->readLine(5000); $line = $this->multLine($line, true); if (!preg_match("/^$key/", $line)) $result .= $line; } while (!$this->startsWith($line, $key, true, true)); $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -1)); } return $result; } function getQuota() { /* * GETQUOTAROOT "INBOX" * QUOTAROOT INBOX user/rchijiiwa1 * QUOTA user/rchijiiwa1 (STORAGE 654 9765) * OK Completed */ $result = false; $quota_lines = array(); // get line(s) containing quota info if ($this->putLine('QUOT1 GETQUOTAROOT "INBOX"')) { do { $line = chop($this->readLine(5000)); if ($this->startsWith($line, '* QUOTA ')) { $quota_lines[] = $line; } } while (!$this->startsWith($line, 'QUOT1', true, true)); } // return false if not found, parse if found $min_free = PHP_INT_MAX; foreach ($quota_lines as $key => $quota_line) { $quota_line = preg_replace('/[()]/', '', $quota_line); $parts = explode(' ', $quota_line); $storage_part = array_search('STORAGE', $parts); if (!$storage_part) continue; $used = intval($parts[$storage_part+1]); $total = intval($parts[$storage_part+2]); $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']; } } return $result; } private function iil_xor($string, $string2) { $result = ''; $size = strlen($string); for ($i=0; $i<$size; $i++) { $result .= chr(ord($string[$i]) ^ ord($string2[$i])); } return $result; } private function strToTime($date) { // support non-standard "GMTXXXX" literal $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); // if date parsing fails, we have a date in non-rfc format. // remove token from the end and try again while ((($ts = @strtotime($date))===false) || ($ts < 0)) { $d = explode(' ', $date); array_pop($d); if (!$d) break; $date = implode(' ', $d); } $ts = (int) $ts; return $ts < 0 ? 0 : $ts; } private function SplitHeaderLine($string) { $pos = strpos($string, ':'); if ($pos>0) { $res[0] = substr($string, 0, $pos); $res[1] = trim(substr($string, $pos+1)); return $res; } return $string; } private function parseNamespace($str, &$i, $len=0, $l) { if (!$l) { $str = str_replace('NIL', '()', $str); } if (!$len) { $len = strlen($str); } $data = array(); $in_quotes = false; $elem = 0; for ($i;$i<$len;$i++) { $c = (string)$str[$i]; if ($c == '(' && !$in_quotes) { $i++; $data[$elem] = $this->parseNamespace($str, $i, $len, $l++); $elem++; } else if ($c == ')' && !$in_quotes) { return $data; } else if ($c == '\\') { $i++; if ($in_quotes) { $data[$elem] .= $c.$str[$i]; } } else if ($c == '"') { $in_quotes = !$in_quotes; if (!$in_quotes) { $elem++; } } else if ($in_quotes) { $data[$elem].=$c; } } return $data; } private function escape($string) { return strtr($string, array('"'=>'\\"', '\\' => '\\\\')); } private function unEscape($string) { return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); } } ?> program/lib/imap.inc
File was deleted