From 11bcac5802dbdd01ee37b97e84f9a91c5777d9e6 Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Wed, 29 Sep 2010 09:32:41 -0400
Subject: [PATCH] - Fix HTML to plain text conversion doesn't handle citation blocks (#1486921)

---
 program/include/rcube_imap.php |  616 +++++++++++++++++++++++++++++++++-----------------------
 1 files changed, 363 insertions(+), 253 deletions(-)

diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index cdf346a..62996e2 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -4,8 +4,8 @@
  +-----------------------------------------------------------------------+
  | 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:                                                              |
@@ -138,9 +138,10 @@
         // 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;
@@ -155,8 +156,7 @@
      */
     function close()
     {
-        if ($this->conn && $this->conn->connected())
-            $this->conn->close();
+        $this->conn->close();
         $this->write_cache();
     }
 
@@ -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;
@@ -472,14 +474,9 @@
             // 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;
 
@@ -570,22 +567,24 @@
 
         $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
@@ -608,7 +607,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
@@ -823,8 +822,11 @@
             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();
@@ -946,8 +948,15 @@
     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'];
@@ -1006,7 +1015,7 @@
      * @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
      */
@@ -1019,22 +1028,43 @@
         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);
@@ -1176,7 +1206,7 @@
         // 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);
         }
 
@@ -1188,20 +1218,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)) {
@@ -1213,7 +1243,7 @@
             $this->cache[$key] = array_keys($a_index);
         }
 
-        return $this->cache[$key];
+        return $this->cache[$key] !== false ? $this->cache[$key] : array();
     }
 
 
@@ -1250,7 +1280,7 @@
 
         // 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);
         }
 */
@@ -1288,8 +1318,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;
@@ -1303,23 +1335,48 @@
     {
         $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
@@ -1328,10 +1385,16 @@
                 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);
 
@@ -1340,11 +1403,17 @@
 
         // 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);
+                    }
                 }
             }
         }
@@ -1370,32 +1439,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;
@@ -1417,21 +1460,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') {
@@ -1440,14 +1494,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);
             }
         }
 
@@ -1478,6 +1534,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;
     }
 
 
@@ -1633,7 +1722,7 @@
             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;
@@ -1675,14 +1764,22 @@
         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);
@@ -1697,7 +1794,8 @@
 
         // 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;
@@ -1709,7 +1807,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";
@@ -1717,6 +1815,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++) {
@@ -1729,19 +1839,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;
                     }
                 }
             }
@@ -1753,22 +1862,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]);
@@ -1796,9 +1922,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]))
@@ -1806,12 +1934,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
@@ -1831,24 +1961,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
@@ -2034,7 +2164,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;
@@ -2170,11 +2300,8 @@
 
         // 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);
         }
@@ -2560,7 +2687,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'])) {
@@ -2591,7 +2718,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'])) {
@@ -2601,7 +2728,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();
@@ -3054,15 +3181,12 @@
         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;
@@ -3077,8 +3201,8 @@
 	            }
 	            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
@@ -3100,16 +3224,17 @@
      */
     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=?".
@@ -3120,18 +3245,17 @@
                 $_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;
     }
 
     /**
@@ -3143,7 +3267,7 @@
 
         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=?".
@@ -3153,8 +3277,10 @@
                 $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']));
             }
@@ -3166,22 +3292,30 @@
     /**
      * @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').
@@ -3192,29 +3326,35 @@
             $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').
@@ -3263,6 +3403,8 @@
                 is_object($struct) ? serialize($this->db->encode(clone $struct)) : NULL
             );
         }
+
+        unset($this->icache['index']);
     }
 
     /**
@@ -3280,6 +3422,8 @@
             " AND ".($idx ? "idx" : "uid")." IN (".$this->db->array2list($ids, 'integer').")",
             $_SESSION['user_id'],
             $key);
+
+        unset($this->icache['index']);
     }
 
     /**
@@ -3296,6 +3440,8 @@
             " AND cache_key=?".
             " AND idx>=?",
             $_SESSION['user_id'], $key, $start_index);
+
+        unset($this->icache['index']);
     }
 
     /**
@@ -3359,8 +3505,8 @@
             $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);
@@ -3369,7 +3515,8 @@
             else if ($name)
                 $string = $name;
 
-            $out[$j] = array('name' => $name,
+            $out[$j] = array(
+                'name'   => $name,
                 'mailto' => $address,
                 'string' => $string
             );
@@ -3383,43 +3530,6 @@
 
 
     /**
-     * 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
@@ -3429,7 +3539,7 @@
     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;
@@ -3803,9 +3913,9 @@
                     $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'];
         }
 

--
Gitblit v1.9.1