From 2537686d1dc06c7b0588bc4663342a052ebaca6e Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Fri, 30 Jul 2010 08:46:00 -0400 Subject: [PATCH] - Performance fix: Determine real mimetype of message/rfc822 part from bodystructure instead of fetched headers --- program/include/rcube_imap.php | 266 +++++++++++++++++++++++++++++++--------------------- 1 files changed, 159 insertions(+), 107 deletions(-) diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index 926a864..4dd46e9 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -297,7 +297,9 @@ { if (is_array($str) && $msgs == null) list($str, $msgs, $charset, $sort_field, $threads) = $str; - if ($msgs != null && !is_array($msgs)) + if ($msgs === false) + $msgs = array(); + else if ($msgs != null && !is_array($msgs)) $msgs = explode(',', $msgs); $this->search_string = $str; @@ -474,11 +476,7 @@ $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; @@ -607,7 +605,7 @@ else $msg_index = array(); - if ($slice) + if ($slice && $msg_index) $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice); // fetch reqested headers from server @@ -1197,20 +1195,20 @@ $a_index = range(1, $max); } - if ($this->sort_order == 'DESC') + if ($a_index !== false && $this->sort_order == 'DESC') $a_index = array_reverse($a_index); $this->cache[$key] = $a_index; } // fetch complete message index else if ($this->get_capability('SORT')) { - if ($a_index = $this->conn->sort($mailbox, - $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')) { - if ($this->sort_order == 'DESC') - $a_index = array_reverse($a_index); + $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)) { @@ -1222,7 +1220,7 @@ $this->cache[$key] = array_keys($a_index); } - return $this->cache[$key]; + return $this->cache[$key] !== false ? $this->cache[$key] : array(); } @@ -1297,8 +1295,10 @@ $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; @@ -1379,32 +1379,6 @@ $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; @@ -1426,21 +1400,32 @@ $criteria = 'UNDELETED '.$criteria; if ($this->threading) { - list ($thread_tree, $msg_depth, $has_children) = $this->conn->thread( - $mailbox, $this->threading, $criteria, $charset); + $a_messages = $this->conn->thread($mailbox, $this->threading, $criteria, $charset); - $a_messages = array( - 'tree' => $thread_tree, - 'depth' => $msg_depth, - 'children' => $has_children - ); + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($a_messages === false && $charset && $charset != 'US-ASCII') + $a_messages = $this->conn->thread($mailbox, $this->threading, + $this->convert_criteria($criteria, $charset), 'US-ASCII'); + + if ($a_messages !== false) { + list ($thread_tree, $msg_depth, $has_children) = $a_messages; + $a_messages = array( + 'tree' => $thread_tree, + 'depth' => $msg_depth, + 'children' => $has_children + ); + } } else if ($sort_field && $this->get_capability('SORT')) { $charset = $charset ? $charset : $this->default_charset; $a_messages = $this->conn->sort($mailbox, $sort_field, $criteria, false, $charset); - if (!$a_messages) - return array(); + // Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8, + // but I've seen that Courier doesn't support UTF-8) + if ($a_messages === false && $charset && $charset != 'US-ASCII') + $a_messages = $this->conn->sort($mailbox, $sort_field, + $this->convert_criteria($criteria, $charset), false, 'US-ASCII'); } else { if ($orig_criteria == 'ALL') { @@ -1449,14 +1434,16 @@ } else { $a_messages = $this->conn->search($mailbox, - ($charset ? "CHARSET $charset " : '') . $criteria); + ($charset ? "CHARSET $charset " : '') . $criteria); - if (!$a_messages) - return array(); + // Error, try with US-ASCII (some servers may support only US-ASCII) + if ($a_messages === false && $charset && $charset != 'US-ASCII') + $a_messages = $this->conn->search($mailbox, + 'CHARSET US-ASCII ' . $this->convert_criteria($criteria, $charset)); - // I didn't found that SEARCH always returns sorted IDs - if (!$this->sort_field) - sort($a_messages); + // I didn't found that SEARCH should return sorted IDs + if (is_array($a_messages) && !$this->sort_field) + sort($a_messages); } } @@ -1487,6 +1474,39 @@ $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; } @@ -1718,7 +1738,7 @@ * * @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"; @@ -1726,6 +1746,18 @@ // 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++) { @@ -1738,19 +1770,18 @@ $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; } } } @@ -1762,22 +1793,39 @@ $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]); @@ -1805,9 +1853,11 @@ $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])) @@ -1815,12 +1865,14 @@ $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 @@ -1840,24 +1892,24 @@ } $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 @@ -2043,7 +2095,7 @@ 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; @@ -2569,7 +2621,7 @@ $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'])) { @@ -2600,7 +2652,7 @@ 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'])) { @@ -2610,7 +2662,7 @@ // 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(); -- Gitblit v1.9.1