| | |
| | | +-----------------------------------------------------------------------+ |
| | | | program/include/rcube_imap.php | |
| | | | | |
| | | | This file is part of the RoundCube Webmail client | |
| | | | Copyright (C) 2005-2010, RoundCube Dev. - Switzerland | |
| | | | This file is part of the Roundcube Webmail client | |
| | | | Copyright (C) 2005-2010, Roundcube Dev. - Switzerland | |
| | | | Licensed under the GNU GPL | |
| | | | | |
| | | | PURPOSE: | |
| | |
| | | // 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); |
| | | if ($pass && $user) |
| | | raise_error(array('code' => 403, 'type' => 'imap', |
| | | 'file' => __FILE__, 'line' => __LINE__, |
| | | 'message' => $this->conn->error), true, false); |
| | | } |
| | | |
| | | return false; |
| | |
| | | */ |
| | | function close() |
| | | { |
| | | if ($this->conn && $this->conn->connected()) |
| | | $this->conn->close(); |
| | | $this->conn->close(); |
| | | $this->write_cache(); |
| | | } |
| | | |
| | |
| | | { |
| | | if (is_array($str) && $msgs == null) |
| | | list($str, $msgs, $charset, $sort_field, $threads) = $str; |
| | | if ($msgs != null && !is_array($msgs)) |
| | | if ($msgs === false) |
| | | $msgs = array(); |
| | | else if ($msgs != null && !is_array($msgs)) |
| | | $msgs = explode(',', $msgs); |
| | | |
| | | $this->search_string = $str; |
| | |
| | | // get message count and store in cache |
| | | if ($mode == 'UNSEEN') |
| | | $search_str .= " UNSEEN"; |
| | | |
| | | // get message count using SEARCH |
| | | // not very performant but more precise (using UNDELETED) |
| | | // disable THREADS for this request |
| | | $threads = $this->threading; |
| | | $this->threading = false; |
| | | $index = $this->_search_index($mailbox, $search_str); |
| | | $this->threading = $threads; |
| | | $index = $this->conn->search($mailbox, $search_str); |
| | | |
| | | $count = is_array($index) ? count($index) : 0; |
| | | |
| | |
| | | |
| | | $page = $page ? $page : $this->list_page; |
| | | $cache_key = $mailbox.'.msg'; |
| | | $cache_status = $this->check_cache_status($mailbox, $cache_key); |
| | | |
| | | // cache is OK, we can get all messages from local cache |
| | | 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); |
| | | $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) { |
| | | $this->sync_header_index($mailbox); |
| | | return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); |
| | | if ($this->caching_enabled) { |
| | | // cache is OK, we can get messages from local cache |
| | | // (assume cache is in sync when in recursive mode) |
| | | if ($recursive || $this->check_cache_status($mailbox, $cache_key)>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); |
| | | $result = array_values($a_msg_headers); |
| | | if ($slice) |
| | | $result = array_slice($result, -$slice, $slice); |
| | | return $result; |
| | | } |
| | | // cache is incomplete, sync it (all messages in the folder) |
| | | else if (!$recursive) { |
| | | $this->sync_header_index($mailbox); |
| | | return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, true, $slice); |
| | | } |
| | | } |
| | | |
| | | // retrieve headers from IMAP |
| | |
| | | else |
| | | $msg_index = array(); |
| | | |
| | | if ($slice) |
| | | if ($slice && $msg_index) |
| | | $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); |
| | | |
| | | // fetch reqested headers from server |
| | |
| | | return $this->_list_thread_header_set($mailbox, $page, $sort_field, $sort_order, $slice); |
| | | |
| | | // search set is threaded, we need a new one |
| | | if ($this->search_threads) |
| | | if ($this->search_threads) { |
| | | if (empty($this->search_set['tree'])) |
| | | return array(); |
| | | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| | | } |
| | | |
| | | $msgs = $this->search_set; |
| | | $a_msg_headers = array(); |
| | |
| | | private function _list_thread_header_set($mailbox, $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0) |
| | | { |
| | | // update search_set if previous data was fetched with disabled threading |
| | | if (!$this->search_threads) |
| | | if (!$this->search_threads) { |
| | | if (empty($this->search_set)) |
| | | return array(); |
| | | $this->search('', $this->search_string, $this->search_charset, $sort_field); |
| | | } |
| | | |
| | | // empty result |
| | | if (empty($this->search_set['tree'])) |
| | | return array(); |
| | | |
| | | $thread_tree = $this->search_set['tree']; |
| | | $msg_depth = $this->search_set['depth']; |
| | |
| | | * @param string Mailbox name |
| | | * @param string Message index to fetch |
| | | * @param array Reference to message headers array |
| | | * @param array Array with cache index |
| | | * @param string Cache index string |
| | | * @return int Messages count |
| | | * @access private |
| | | */ |
| | |
| | | if (empty($a_header_index)) |
| | | return 0; |
| | | |
| | | // cache is incomplete |
| | | $cache_index = $this->get_message_cache_index($cache_key); |
| | | |
| | | foreach ($a_header_index as $i => $headers) { |
| | | if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid) { |
| | | // prevent index duplicates |
| | | $a_msg_headers[$headers->uid] = $headers; |
| | | } |
| | | |
| | | // Update cache |
| | | if ($this->caching_enabled && $cache_key) { |
| | | // cache is incomplete? |
| | | $cache_index = $this->get_message_cache_index($cache_key); |
| | | |
| | | foreach ($a_header_index as $headers) { |
| | | // message in cache |
| | | if ($cache_index[$headers->id] == $headers->uid) { |
| | | unset($cache_index[$headers->id]); |
| | | continue; |
| | | } |
| | | // wrong UID at this position |
| | | if ($cache_index[$headers->id]) { |
| | | $this->remove_message_cache($cache_key, $headers->id, true); |
| | | $for_remove[] = $cache_index[$headers->id]; |
| | | unset($cache_index[$headers->id]); |
| | | } |
| | | // add message to cache |
| | | $this->add_message_cache($cache_key, $headers->id, $headers, NULL, |
| | | !in_array($headers->uid, $cache_index)); |
| | | } |
| | | // message UID in cache but at wrong position |
| | | if (is_int($key = array_search($headers->uid, $cache_index))) { |
| | | $for_remove[] = $cache_index[$key]; |
| | | unset($cache_index[$key]); |
| | | } |
| | | |
| | | $a_msg_headers[$headers->uid] = $headers; |
| | | $for_create[] = $headers->uid; |
| | | } |
| | | |
| | | if ($for_remove) |
| | | $this->remove_message_cache($cache_key, $for_remove); |
| | | |
| | | // add messages to cache |
| | | foreach ((array)$for_create as $uid) { |
| | | $headers = $a_msg_headers[$uid]; |
| | | $this->add_message_cache($cache_key, $headers->id, $headers, NULL, true); |
| | | } |
| | | } |
| | | |
| | | return count($a_msg_headers); |
| | |
| | | // cache is OK |
| | | if ($cache_status>0) { |
| | | $a_index = $this->get_message_cache_index($cache_key, |
| | | true, $this->sort_field, $this->sort_order); |
| | | $this->sort_field, $this->sort_order); |
| | | return array_keys($a_index); |
| | | } |
| | | |
| | |
| | | $a_index = range(1, $max); |
| | | } |
| | | |
| | | if ($this->sort_order == 'DESC') |
| | | if ($a_index !== false && $this->sort_order == 'DESC') |
| | | $a_index = array_reverse($a_index); |
| | | |
| | | $this->cache[$key] = $a_index; |
| | | } |
| | | // fetch complete message index |
| | | else if ($this->get_capability('SORT')) { |
| | | if ($a_index = $this->conn->sort($mailbox, |
| | | $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { |
| | | if ($this->sort_order == 'DESC') |
| | | $a_index = array_reverse($a_index); |
| | | $a_index = $this->conn->sort($mailbox, |
| | | $this->sort_field, $this->skip_deleted ? 'UNDELETED' : ''); |
| | | |
| | | $this->cache[$key] = $a_index; |
| | | } |
| | | if ($a_index !== false && $this->sort_order == 'DESC') |
| | | $a_index = array_reverse($a_index); |
| | | |
| | | $this->cache[$key] = $a_index; |
| | | } |
| | | else if ($a_index = $this->conn->fetchHeaderIndex( |
| | | $mailbox, "1:*", $this->sort_field, $this->skip_deleted)) { |
| | |
| | | $this->cache[$key] = array_keys($a_index); |
| | | } |
| | | |
| | | return $this->cache[$key]; |
| | | return $this->cache[$key] !== false ? $this->cache[$key] : array(); |
| | | } |
| | | |
| | | |
| | |
| | | |
| | | // cache is OK |
| | | if ($cache_status>0) { |
| | | $a_index = $this->get_message_cache_index($cache_key, true, $this->sort_field, $this->sort_order); |
| | | $a_index = $this->get_message_cache_index($cache_key, $this->sort_field, $this->sort_order); |
| | | return array_keys($a_index); |
| | | } |
| | | */ |
| | |
| | | $all_ids = array(); |
| | | foreach($msg_index as $root) { |
| | | $all_ids[] = $root; |
| | | if (!empty($thread_tree[$root])) |
| | | $all_ids = array_merge($all_ids, array_keys_recursive($thread_tree[$root])); |
| | | if (!empty($thread_tree[$root])) { |
| | | foreach (array_keys_recursive($thread_tree[$root]) as $val) |
| | | $all_ids[] = $val; |
| | | } |
| | | } |
| | | |
| | | return $all_ids; |
| | |
| | | { |
| | | $cache_key = $mailbox.'.msg'; |
| | | $cache_index = $this->get_message_cache_index($cache_key); |
| | | $chunk_size = 1000; |
| | | |
| | | // cache is empty, get all messages |
| | | if (is_array($cache_index) && empty($cache_index)) { |
| | | $max = $this->_messagecount($mailbox); |
| | | // syncing a big folder maybe slow |
| | | @set_time_limit(0); |
| | | $start = 1; |
| | | $end = min($chunk_size, $max); |
| | | while (true) { |
| | | // do this in loop to save memory (1000 msgs ~= 10 MB) |
| | | if ($headers = $this->conn->fetchHeaders($mailbox, |
| | | "$start:$end", false, false, $this->fetch_add_headers) |
| | | ) { |
| | | foreach ($headers as $header) { |
| | | $this->add_message_cache($cache_key, $header->id, $header, NULL, true); |
| | | } |
| | | } |
| | | if ($end - $start < $chunk_size - 1) |
| | | break; |
| | | |
| | | $end = min($end+$chunk_size, $max); |
| | | $start += $chunk_size; |
| | | } |
| | | return; |
| | | } |
| | | |
| | | // fetch complete message index |
| | | $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); |
| | | if (isset($this->icache['folder_index'])) |
| | | $a_message_index = &$this->icache['folder_index']; |
| | | else |
| | | $a_message_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); |
| | | |
| | | if ($a_message_index === false) |
| | | return false; |
| | | if ($a_message_index === false || $cache_index === null) |
| | | return; |
| | | |
| | | // compare cache index with real index |
| | | foreach ($a_message_index as $id => $uid) { |
| | | // message in cache at correct position |
| | | 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)) { |
| | | unset($cache_index[$id]); |
| | | } |
| | | |
| | | // other message at this position |
| | |
| | | unset($cache_index[$id]); |
| | | } |
| | | |
| | | // message in cache but at wrong position |
| | | if (is_int($key = array_search($uid, $cache_index))) { |
| | | $for_remove[] = $uid; |
| | | unset($cache_index[$key]); |
| | | } |
| | | |
| | | $for_update[] = $id; |
| | | } |
| | | |
| | | // clear messages at wrong positions and those deleted that are still in cache_index |
| | | // remove messages at wrong positions and those deleted that are still in cache_index |
| | | if (!empty($for_remove)) |
| | | $cache_index = array_merge($cache_index, $for_remove); |
| | | |
| | |
| | | |
| | | // fetch complete headers and add to cache |
| | | if (!empty($for_update)) { |
| | | 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)); |
| | | // syncing a big folder maybe slow |
| | | @set_time_limit(0); |
| | | // To save memory do this in chunks |
| | | $for_update = array_chunk($for_update, $chunk_size); |
| | | foreach ($for_update as $uids) { |
| | | if ($headers = $this->conn->fetchHeaders($mailbox, |
| | | $uids, false, false, $this->fetch_add_headers) |
| | | ) { |
| | | foreach ($headers as $header) { |
| | | $this->add_message_cache($cache_key, $header->id, $header, NULL, true); |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | |
| | | $results = $this->_search_index($mailbox, $str, $charset, $sort_field); |
| | | |
| | | // try search with US-ASCII charset (should be supported by server) |
| | | // only if UTF-8 search is not supported |
| | | if (empty($results) && !is_array($results) && !empty($charset) && $charset != 'US-ASCII') |
| | | { |
| | | // convert strings to US_ASCII |
| | | if(preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { |
| | | $last = 0; $res = ''; |
| | | foreach($matches[1] as $m) |
| | | { |
| | | $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n |
| | | $string = substr($str, $string_offset - 1, $m[0]); |
| | | $string = rcube_charset_convert($string, $charset, 'US-ASCII'); |
| | | if (!$string) |
| | | continue; |
| | | $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); |
| | | $last = $m[0] + $string_offset - 1; |
| | | } |
| | | if ($last < strlen($str)) |
| | | $res .= substr($str, $last, strlen($str)-$last); |
| | | } |
| | | else // strings for conversion not found |
| | | $res = $str; |
| | | |
| | | $results = $this->search($mbox_name, $res, NULL, $sort_field); |
| | | } |
| | | |
| | | $this->set_search_set($str, $results, $charset, $sort_field, (bool)$this->threading); |
| | | |
| | | return $results; |
| | |
| | | $criteria = 'UNDELETED '.$criteria; |
| | | |
| | | if ($this->threading) { |
| | | list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( |
| | | $mailbox, $this->threading, $criteria, $charset); |
| | | $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset); |
| | | |
| | | $a_messages = array( |
| | | 'tree' => $thread_tree, |
| | | 'depth' => $msg_depth, |
| | | 'children' => $has_children |
| | | ); |
| | | // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, |
| | | // but I've seen that Courier doesn't support UTF-8) |
| | | if ($a_messages === false && $charset && $charset != 'US-ASCII') |
| | | $a_messages = $this->conn->thread($mailbox, $this->threading, |
| | | $this->convert_criteria($criteria, $charset), 'US-ASCII'); |
| | | |
| | | if ($a_messages !== false) { |
| | | list ($thread_tree, $msg_depth, $has_children) = $a_messages; |
| | | $a_messages = array( |
| | | 'tree' => $thread_tree, |
| | | 'depth' => $msg_depth, |
| | | 'children' => $has_children |
| | | ); |
| | | } |
| | | } |
| | | else if ($sort_field && $this->get_capability('SORT')) { |
| | | $charset = $charset ? $charset : $this->default_charset; |
| | | $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); |
| | | |
| | | if (!$a_messages) |
| | | return array(); |
| | | // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, |
| | | // but I've seen that Courier doesn't support UTF-8) |
| | | if ($a_messages === false && $charset && $charset != 'US-ASCII') |
| | | $a_messages = $this->conn->sort($mailbox, $sort_field, |
| | | $this->convert_criteria($criteria, $charset), false, 'US-ASCII'); |
| | | } |
| | | else { |
| | | if ($orig_criteria == 'ALL') { |
| | |
| | | } |
| | | else { |
| | | $a_messages = $this->conn->search($mailbox, |
| | | ($charset ? "CHARSET $charset " : '') . $criteria); |
| | | ($charset ? "CHARSET $charset " : '') . $criteria); |
| | | |
| | | if (!$a_messages) |
| | | return array(); |
| | | // Error, try with US-ASCII (some servers may support only US-ASCII) |
| | | if ($a_messages === false && $charset && $charset != 'US-ASCII') |
| | | $a_messages = $this->conn->search($mailbox, |
| | | 'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset)); |
| | | |
| | | // I didn't found that SEARCH always returns sorted IDs |
| | | if (!$this->sort_field) |
| | | sort($a_messages); |
| | | // I didn't found that SEARCH should return sorted IDs |
| | | if (is_array($a_messages) && !$this->sort_field) |
| | | sort($a_messages); |
| | | } |
| | | } |
| | | |
| | |
| | | $mailbox = $mbox_name ? $this->mod_mailbox($mbox_name) : $this->mailbox; |
| | | |
| | | return $this->conn->search($mailbox, $str, $ret_uid); |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Converts charset of search criteria string |
| | | * |
| | | * @param string Search string |
| | | * @param string Original charset |
| | | * @param string Destination charset (default US-ASCII) |
| | | * @return string Search string |
| | | * @access private |
| | | */ |
| | | private function convert_criteria($str, $charset, $dest_charset='US-ASCII') |
| | | { |
| | | // convert strings to US_ASCII |
| | | if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) { |
| | | $last = 0; $res = ''; |
| | | foreach ($matches[1] as $m) { |
| | | $string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n |
| | | $string = substr($str, $string_offset - 1, $m[0]); |
| | | $string = rcube_charset_convert($string, $charset, $dest_charset); |
| | | if (!$string) |
| | | continue; |
| | | $res .= sprintf("%s{%d}\r\n%s", substr($str, $last, $m[1] - $last - 1), strlen($string), $string); |
| | | $last = $m[0] + $string_offset - 1; |
| | | } |
| | | if ($last < strlen($str)) |
| | | $res .= substr($str, $last, strlen($str)-$last); |
| | | } |
| | | else // strings for conversion not found |
| | | $res = $str; |
| | | |
| | | return $res; |
| | | } |
| | | |
| | | |
| | |
| | | if ($headers->uid && $headers->id) |
| | | $this->uid_id_map[$mailbox][$headers->uid] = $headers->id; |
| | | |
| | | $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL); |
| | | $this->add_message_cache($mailbox.'.msg', $headers->id, $headers, NULL, false, true); |
| | | } |
| | | |
| | | return $headers; |
| | |
| | | else |
| | | $this->struct_charset = $this->_structure_charset($structure); |
| | | |
| | | $headers->ctype = strtolower($headers->ctype); |
| | | |
| | | // Here we can recognize malformed BODYSTRUCTURE and |
| | | // 1. [@TODO] parse the message in other way to create our own message structure |
| | | // 2. or just show the raw message body. |
| | | // Example of structure for malformed MIME message: |
| | | // ("text" "plain" ("charset" "us-ascii") NIL NIL "7bit" 2154 70 NIL NIL NIL) |
| | | if ($headers->ctype && $headers->ctype != 'text/plain' |
| | | && $structure[0] == 'text' && $structure[1] == 'plain') { |
| | | return false; |
| | | // ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL) |
| | | if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain' |
| | | && strtolower($structure[0].'/'.$structure[1]) == 'text/plain') { |
| | | // we can handle single-part messages, by simple fix in structure (#1486898) |
| | | if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) { |
| | | $structure[0] = $m[1]; |
| | | $structure[1] = $m[2]; |
| | | } |
| | | else |
| | | return false; |
| | | } |
| | | |
| | | $struct = &$this->_structure_part($structure); |
| | |
| | | |
| | | // write structure to cache |
| | | if ($this->caching_enabled) |
| | | $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct); |
| | | $this->add_message_cache($cache_key, $this->_msg_id, $headers, $struct, |
| | | $this->icache['message.id'][$uid], true); |
| | | } |
| | | |
| | | return $struct; |
| | |
| | | * |
| | | * @access private |
| | | */ |
| | | function &_structure_part($part, $count=0, $parent='', $mime_headers=null, $raw_headers=null) |
| | | function &_structure_part($part, $count=0, $parent='', $mime_headers=null) |
| | | { |
| | | $struct = new rcube_message_part; |
| | | $struct->mime_id = empty($parent) ? (string)$count : "$parent.$count"; |
| | |
| | | // multipart |
| | | if (is_array($part[0])) { |
| | | $struct->ctype_primary = 'multipart'; |
| | | |
| | | /* RFC3501: BODYSTRUCTURE fields of multipart part |
| | | part1 array |
| | | part2 array |
| | | part3 array |
| | | .... |
| | | 1. subtype |
| | | 2. parameters (optional) |
| | | 3. description (optional) |
| | | 4. language (optional) |
| | | 5. location (optional) |
| | | */ |
| | | |
| | | // find first non-array entry |
| | | for ($i=1; $i<count($part); $i++) { |
| | |
| | | $struct->mimetype = 'multipart/'.$struct->ctype_secondary; |
| | | |
| | | // build parts list for headers pre-fetching |
| | | for ($i=0, $count=0; $i<count($part); $i++) { |
| | | if (is_array($part[$i]) && count($part[$i]) > 3) { |
| | | // 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') { |
| | | $raw_part_headers[] = $tmp_part_id; |
| | | $mime_part_headers[] = $tmp_part_id; |
| | | } |
| | | else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { |
| | | $mime_part_headers[] = $tmp_part_id; |
| | | } |
| | | for ($i=0; $i<count($part); $i++) { |
| | | if (!is_array($part[$i])) |
| | | break; |
| | | // 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') { |
| | | $mime_part_headers[] = $tmp_part_id; |
| | | } |
| | | else if (in_array('name', (array)$part[$i][2]) && (empty($part[$i][3]) || $part[$i][3]=='NIL')) { |
| | | $mime_part_headers[] = $tmp_part_id; |
| | | } |
| | | } |
| | | } |
| | |
| | | $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 = $this->conn->fetchMIMEHeaders($this->mailbox, |
| | | $this->_msg_id, $raw_part_headers, false); |
| | | } |
| | | |
| | | $struct->parts = array(); |
| | | 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]); |
| | | } |
| | | if (!is_array($part[$i])) |
| | | break; |
| | | $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]); |
| | | } |
| | | |
| | | return $struct; |
| | | } |
| | | |
| | | /* RFC3501: BODYSTRUCTURE fields of non-multipart part |
| | | 0. type |
| | | 1. subtype |
| | | 2. parameters |
| | | 3. id |
| | | 4. description |
| | | 5. encoding |
| | | 6. size |
| | | -- text |
| | | 7. lines |
| | | -- message/rfc822 |
| | | 7. envelope structure |
| | | 8. body structure |
| | | 9. lines |
| | | -- |
| | | x. md5 (optional) |
| | | x. disposition (optional) |
| | | x. language (optional) |
| | | x. location (optional) |
| | | */ |
| | | |
| | | // regular part |
| | | $struct->ctype_primary = strtolower($part[0]); |
| | |
| | | $struct->size = intval($part[6]); |
| | | |
| | | // 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)) { |
| | | $di = 8; |
| | | if ($struct->ctype_primary == 'text') $di += 1; |
| | | else if ($struct->mimetype == 'message/rfc822') $di += 3; |
| | | |
| | | if (is_array($part[$di]) && count($part[$di]) == 2) { |
| | | $struct->disposition = strtolower($part[$di][0]); |
| | | |
| | | if (is_array($part[$di][1])) |
| | |
| | | $struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1]; |
| | | } |
| | | |
| | | // get child parts |
| | | // get message/rfc822's child-parts |
| | | 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) |
| | | $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); |
| | | for ($i=0, $count=0; $i<count($part[8]); $i++) { |
| | | if (!is_array($part[8][$i])) |
| | | break; |
| | | $struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id); |
| | | } |
| | | } |
| | | |
| | | // get part ID |
| | |
| | | } |
| | | $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers; |
| | | |
| | | // get real headers for message of type 'message/rfc822' |
| | | // get real content-type of message/rfc822 |
| | | if ($struct->mimetype == 'message/rfc822') { |
| | | 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 |
| | | if (preg_match('/^([a-z0-9_\/-]+)/i', $struct->real_headers['content-type'], $matches)) { |
| | | $struct->real_mimetype = strtolower($matches[1]); |
| | | // single-part |
| | | if (!is_array($part[8][0])) |
| | | $struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]); |
| | | // multi-part |
| | | else { |
| | | for ($n=0; $n<count($part[8]); $n++) |
| | | if (!is_array($part[8][$n])) |
| | | break; |
| | | $struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if ($struct->ctype_primary=='message') { |
| | | if (is_array($part[8]) && $di != 8 && empty($struct->parts)) |
| | | $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); |
| | | if ($struct->ctype_primary == 'message' && empty($struct->parts)) { |
| | | if (is_array($part[8]) && $di != 8) |
| | | $struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id); |
| | | } |
| | | } |
| | | |
| | | // normalize filename property |
| | |
| | | return true; |
| | | |
| | | // convert charset (if text or message part) |
| | | if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message') { |
| | | if ($body && ($o_part->ctype_primary == 'text' || $o_part->ctype_primary == 'message')) { |
| | | // assume default if no charset specified |
| | | if (empty($o_part->charset) || strtolower($o_part->charset) == 'us-ascii') |
| | | $o_part->charset = $this->default_charset; |
| | |
| | | |
| | | // make sure mailbox exists |
| | | if ($this->mailbox_exists($mbox_name, true)) { |
| | | if ($is_file) { |
| | | $separator = rcmail::get_instance()->config->header_delimiter(); |
| | | $saved = $this->conn->appendFromFile($mailbox, $message, |
| | | $headers, $separator.$separator); |
| | | } |
| | | if ($is_file) |
| | | $saved = $this->conn->appendFromFile($mailbox, $message, $headers); |
| | | else |
| | | $saved = $this->conn->append($mailbox, $message); |
| | | } |
| | |
| | | $a_defaults = $a_out = array(); |
| | | |
| | | // Give plugins a chance to provide a list of mailboxes |
| | | $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', |
| | | $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list', |
| | | array('root' => $root, 'filter' => $filter, 'mode' => 'LSUB')); |
| | | |
| | | if (isset($data['folders'])) { |
| | |
| | | function list_unsubscribed($root='', $filter='*') |
| | | { |
| | | // Give plugins a chance to provide a list of mailboxes |
| | | $data = rcmail::get_instance()->plugins->exec_hook('list_mailboxes', |
| | | $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list', |
| | | array('root' => $root, 'filter' => $filter, 'mode' => 'LIST')); |
| | | |
| | | if (isset($data['folders'])) { |
| | |
| | | // retrieve list of folders from IMAP server |
| | | $a_mboxes = $this->conn->listMailboxes($this->mod_mailbox($root), $filter); |
| | | } |
| | | |
| | | |
| | | $a_folders = array(); |
| | | if (!is_array($a_mboxes)) |
| | | $a_mboxes = array(); |
| | |
| | | if (!$msg_count) |
| | | return $cache_count ? -2 : 1; |
| | | |
| | | // @TODO: We've got one big performance problem in cache status checking method |
| | | // E.g. mailbox contains 1000 messages, in cache table we've got first 100 |
| | | // of them. Now if we want to display only that 100 (which we've got) |
| | | // check_cache_status returns 'incomplete' and messages are fetched |
| | | // from IMAP instead of DB. |
| | | |
| | | if ($cache_count==$msg_count) { |
| | | if ($this->skip_deleted) { |
| | | $h_index = $this->conn->fetchHeaderIndex($mailbox, "1:*", 'UID', $this->skip_deleted); |
| | | |
| | | // Save index in internal cache, will be used when syncing the cache |
| | | $this->icache['folder_index'] = $h_index; |
| | | |
| | | if (empty($h_index)) |
| | | return -2; |
| | |
| | | } |
| | | return -2; |
| | | } else { |
| | | // get UID of message with highest index |
| | | $uid = $this->conn->ID2UID($mailbox, $msg_count); |
| | | // get UID of the message with highest index |
| | | $uid = $this->_id2uid($msg_count, $mailbox); |
| | | $cache_uid = array_pop($cache_index); |
| | | |
| | | // uids of highest message matches -> cache seems OK |
| | |
| | | */ |
| | | private function get_message_cache($key, $from, $to, $sort_field, $sort_order) |
| | | { |
| | | $cache_key = "$key:$from:$to:$sort_field:$sort_order"; |
| | | if (!$this->caching_enabled) |
| | | return NULL; |
| | | |
| | | // use idx sort as default sorting |
| | | if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) { |
| | | $sort_field = 'idx'; |
| | | } |
| | | |
| | | if ($this->caching_enabled && !isset($this->cache[$cache_key])) { |
| | | $this->cache[$cache_key] = array(); |
| | | $sql_result = $this->db->limitquery( |
| | | $result = array(); |
| | | |
| | | $sql_result = $this->db->limitquery( |
| | | "SELECT idx, uid, headers". |
| | | " FROM ".get_table_name('messages'). |
| | | " WHERE user_id=?". |
| | |
| | | $_SESSION['user_id'], |
| | | $key); |
| | | |
| | | 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'])); |
| | | while ($sql_arr = $this->db->fetch_assoc($sql_result)) { |
| | | $uid = intval($sql_arr['uid']); |
| | | $result[$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] = $this->conn->fetchHeader( |
| | | preg_replace('/.msg$/', '', $key), $uid, true, $this->fetch_add_headers); |
| | | } |
| | | // featch headers if unserialize failed |
| | | if (empty($result[$uid])) |
| | | $result[$uid] = $this->conn->fetchHeader( |
| | | preg_replace('/.msg$/', '', $key), $uid, true, false, $this->fetch_add_headers); |
| | | } |
| | | |
| | | return $this->cache[$cache_key]; |
| | | return $result; |
| | | } |
| | | |
| | | /** |
| | |
| | | |
| | | if ($this->caching_enabled && !isset($this->icache[$internal_key][$uid])) { |
| | | $sql_result = $this->db->query( |
| | | "SELECT idx, headers, structure". |
| | | "SELECT idx, headers, structure, message_id". |
| | | " FROM ".get_table_name('messages'). |
| | | " WHERE user_id=?". |
| | | " AND cache_key=?". |
| | |
| | | $uid); |
| | | |
| | | if ($sql_arr = $this->db->fetch_assoc($sql_result)) { |
| | | $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = $sql_arr['idx']; |
| | | $this->icache['message.id'][$uid] = intval($sql_arr['message_id']); |
| | | $this->uid_id_map[preg_replace('/\.msg$/', '', $key)][$uid] = intval($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'])) |
| | | $this->icache[$internal_key][$uid]->structure = $this->db->decode(unserialize($sql_arr['structure'])); |
| | | } |
| | |
| | | /** |
| | | * @access private |
| | | */ |
| | | private function get_message_cache_index($key, $force=false, $sort_field='idx', $sort_order='ASC') |
| | | private function get_message_cache_index($key, $sort_field='idx', $sort_order='ASC') |
| | | { |
| | | static $sa_message_index = array(); |
| | | |
| | | // empty key -> empty array |
| | | if (!$this->caching_enabled || empty($key)) |
| | | return array(); |
| | | |
| | | if (!empty($sa_message_index[$key]) && !$force) |
| | | return $sa_message_index[$key]; |
| | | return NULL; |
| | | |
| | | // use idx sort as default |
| | | if (!$sort_field || !in_array($sort_field, $this->db_header_fields)) |
| | | $sort_field = 'idx'; |
| | | |
| | | $sa_message_index[$key] = array(); |
| | | $ord = $sort_field . $sort_order; |
| | | |
| | | if (array_key_exists('index', $this->icache) |
| | | && $this->icache['index']['key'] == $key |
| | | && $this->icache['index']['ord'] == $ord |
| | | ) { |
| | | return $this->icache['index']['result']; |
| | | } |
| | | |
| | | $this->icache['index'] = array( |
| | | 'result' => array(), |
| | | 'ord' => $ord, |
| | | 'key' => $key, |
| | | ); |
| | | |
| | | $sql_result = $this->db->query( |
| | | "SELECT idx, uid". |
| | | " FROM ".get_table_name('messages'). |
| | |
| | | $key); |
| | | |
| | | while ($sql_arr = $this->db->fetch_assoc($sql_result)) |
| | | $sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid']; |
| | | $this->icache['index']['result'][$sql_arr['idx']] = intval($sql_arr['uid']); |
| | | |
| | | return $sa_message_index[$key]; |
| | | return $this->icache['index']['result']; |
| | | } |
| | | |
| | | /** |
| | | * @access private |
| | | */ |
| | | private function add_message_cache($key, $index, $headers, $struct=null, $force=false) |
| | | private function add_message_cache($key, $index, $headers, $struct=null, $force=false, $internal_cache=false) |
| | | { |
| | | if (empty($key) || !is_object($headers) || empty($headers->uid)) |
| | | return; |
| | | |
| | | // add to internal (fast) cache |
| | | $this->icache['message'][$headers->uid] = clone $headers; |
| | | $this->icache['message'][$headers->uid]->structure = $struct; |
| | | if ($internal_cache) { |
| | | $this->icache['message'][$headers->uid] = clone $headers; |
| | | $this->icache['message'][$headers->uid]->structure = $struct; |
| | | } |
| | | |
| | | // no further caching |
| | | if (!$this->caching_enabled) |
| | | return; |
| | | |
| | | // known message id |
| | | if (is_int($force) && $force > 0) { |
| | | $message_id = $force; |
| | | } |
| | | // check for an existing record (probably headers are cached but structure not) |
| | | if (!$force) { |
| | | else if (!$force) { |
| | | $sql_result = $this->db->query( |
| | | "SELECT message_id". |
| | | " FROM ".get_table_name('messages'). |
| | |
| | | is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL |
| | | ); |
| | | } |
| | | |
| | | unset($this->icache['index']); |
| | | } |
| | | |
| | | /** |
| | |
| | | " AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")", |
| | | $_SESSION['user_id'], |
| | | $key); |
| | | |
| | | unset($this->icache['index']); |
| | | } |
| | | |
| | | /** |
| | |
| | | " AND cache_key=?". |
| | | " AND idx>=?", |
| | | $_SESSION['user_id'], $key, $start_index); |
| | | |
| | | unset($this->icache['index']); |
| | | } |
| | | |
| | | /** |
| | |
| | | $address = trim($val['address']); |
| | | $name = trim($val['name']); |
| | | |
| | | if (preg_match('/^[\'"]/', $name) && preg_match('/[\'"]$/', $name)) |
| | | $name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', $name); |
| | | if ($name && preg_match('/^[\'"]/', $name) && preg_match('/[\'"]$/', $name)) |
| | | $name = trim($name, '\'"'); |
| | | |
| | | if ($name && $address && $name != $address) |
| | | $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); |
| | |
| | | else if ($name) |
| | | $string = $name; |
| | | |
| | | $out[$j] = array('name' => $name, |
| | | $out[$j] = array( |
| | | 'name' => $name, |
| | | 'mailto' => $address, |
| | | 'string' => $string |
| | | ); |
| | |
| | | |
| | | |
| | | /** |
| | | * Decode a Microsoft Outlook TNEF part (winmail.dat) |
| | | * |
| | | * @param object rcube_message_part Message part to decode |
| | | * @param string UID of the message |
| | | * @return array List of rcube_message_parts extracted from windmail.dat |
| | | */ |
| | | function tnef_decode(&$part, $uid) |
| | | { |
| | | if (!isset($part->body)) |
| | | $part->body = $this->get_message_part($uid, $part->mime_id, $part); |
| | | |
| | | require_once('lib/tnef_decoder.inc'); |
| | | |
| | | $pid = 0; |
| | | $tnef_parts = array(); |
| | | $tnef_arr = tnef_decode($part->body); |
| | | |
| | | foreach ($tnef_arr as $winatt) { |
| | | $tpart = new rcube_message_part; |
| | | $tpart->filename = trim($winatt['name']); |
| | | $tpart->encoding = 'stream'; |
| | | $tpart->ctype_primary = trim(strtolower($winatt['type0'])); |
| | | $tpart->ctype_secondary = trim(strtolower($winatt['type1'])); |
| | | $tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary; |
| | | $tpart->mime_id = "winmail." . $part->mime_id . ".$pid"; |
| | | $tpart->size = $winatt['size']; |
| | | $tpart->body = $winatt['stream']; |
| | | |
| | | $tnef_parts[] = $tpart; |
| | | $pid++; |
| | | } |
| | | |
| | | return $tnef_parts; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * Decode a message header value |
| | | * |
| | | * @param string Header value |
| | |
| | | function decode_header($input, $remove_quotes=false) |
| | | { |
| | | $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset); |
| | | if ($str{0}=='"' && $remove_quotes) |
| | | if ($str[0] == '"' && $remove_quotes) |
| | | $str = str_replace('"', '', $str); |
| | | |
| | | return $str; |
| | |
| | | $result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v)); |
| | | } |
| | | |
| | | if (empty($result[$key]['name'])) |
| | | $result[$key]['name'] = $result[$key]['address']; |
| | | elseif (empty($result[$key]['address'])) |
| | | // if (empty($result[$key]['name'])) |
| | | // $result[$key]['name'] = $result[$key]['address']; |
| | | if (empty($result[$key]['address'])) |
| | | $result[$key]['address'] = $result[$key]['name']; |
| | | } |
| | | |