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