From ecfaed571b2c38f4bcc2b6a0fa39fba15a5126ce Mon Sep 17 00:00:00 2001
From: alecpl <alec@alec.pl>
Date: Fri, 11 Nov 2011 10:04:45 -0500
Subject: [PATCH] - Apply fixes fom trunk up to r5414

---
 program/include/rcube_imap.php |  282 +++++++++++++++++++++++++++++++++++++-------------------
 1 files changed, 187 insertions(+), 95 deletions(-)

diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index f95f7ce..8c1fab8 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -32,7 +32,6 @@
  */
 class rcube_imap
 {
-    public $debug_level = 1;
     public $skip_deleted = false;
     public $page_size = 10;
     public $list_page = 1;
@@ -318,6 +317,19 @@
 
 
     /**
+     * Activate/deactivate debug mode
+     *
+     * @param boolean $dbg True if IMAP conversation should be logged
+     * @access public
+     */
+    function set_debug($dbg = true)
+    {
+        $this->options['debug'] = $dbg;
+        $this->conn->setDebug($dbg, array($this, 'debug_handler'));
+    }
+
+
+    /**
      * Set default message charset
      *
      * This will be used for message decoding if a charset specification is not available
@@ -466,7 +478,7 @@
      */
     function get_mailbox_name()
     {
-        return $this->conn->connected() ? $this->mailbox : '';
+        return $this->mailbox;
     }
 
 
@@ -911,19 +923,18 @@
         }
         // fetch specified header for all messages and sort
         else if ($msg_index = $this->conn->fetchHeaderIndex($mailbox, "1:*",
-            $this->sort_field, $this->skip_deleted, true)
+            $this->sort_field, $this->skip_deleted)
         ) {
             asort($msg_index); // ASC
             $msg_index = array_keys($msg_index);
             list($begin, $end) = $this->_get_message_range(count($msg_index), $page);
             $msg_index = array_slice($msg_index, $begin, $end-$begin);
-            $is_uid    = true;
 
             if ($slice)
                 $msg_index = array_slice($msg_index, ($this->sort_order == 'DESC' ? 0 : -$slice), $slice);
 
             // fetch reqested headers from server
-            $a_msg_headers = $this->fetch_headers($mailbox, $msg_index, true);
+            $a_msg_headers = $this->fetch_headers($mailbox, $msg_index);
         }
 
         // return empty array if no messages found
@@ -1505,8 +1516,11 @@
         // use message index sort as default sorting
         if (!$sort_field) {
             if ($this->skip_deleted) {
-                $a_index = $this->_search_index($mailbox, 'ALL');
-            } else if ($max = $this->_messagecount($mailbox)) {
+                $a_index = $this->conn->search($mailbox, 'ALL UNDELETED');
+                // I didn't found that SEARCH should return sorted IDs
+                if (is_array($a_index))
+                    sort($a_index);
+            } else if ($max = $this->_messagecount($mailbox, 'ALL', true, false)) {
                 $a_index = range(1, $max);
             }
 
@@ -1692,7 +1706,7 @@
         }
 
         if ($orig_criteria == 'ALL') {
-            $max = $this->_messagecount($mailbox);
+            $max = $this->_messagecount($mailbox, 'ALL', true, false);
             $a_messages = $max ? range(1, $max) : array();
         }
         else {
@@ -1959,6 +1973,10 @@
         }
 
         $headers = $this->get_headers($uid, $mailbox);
+
+        // message doesn't exist?
+        if (empty($headers))
+            return null; 
 
         // structure might be cached
         if (!empty($headers->structure))
@@ -2318,9 +2336,14 @@
 
         // decode filename
         if (!empty($filename_mime)) {
-            $part->filename = rcube_imap::decode_mime_string($filename_mime,
-                $part->charset ? $part->charset : ($this->struct_charset ? $this->struct_charset :
-                rc_detect_encoding($filename_mime, $this->default_charset)));
+            if (!empty($part->charset))
+                $charset = $part->charset;
+            else if (!empty($this->struct_charset))
+                $charset = $this->struct_charset;
+            else
+                $charset = rc_detect_encoding($filename_mime, $this->default_charset);
+
+            $part->filename = rcube_imap::decode_mime_string($filename_mime, $charset);
         }
         else if (!empty($filename_encoded)) {
             // decode filename according to RFC 2231, Section 4
@@ -2328,6 +2351,7 @@
                 $filename_charset = $fmatches[1];
                 $filename_encoded = $fmatches[2];
             }
+
             $part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
         }
     }
@@ -2364,40 +2388,42 @@
      */
     function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
     {
-        // get part encoding if not provided
+        // get part data if not provided
         if (!is_object($o_part)) {
             $structure = $this->conn->getStructure($this->mailbox, $uid, true);
+            $part_data = rcube_imap_generic::getStructurePartData($structure, $part);
 
             $o_part = new rcube_message_part;
-            $o_part->ctype_primary = strtolower(rcube_imap_generic::getStructurePartType($structure, $part));
-            $o_part->encoding      = strtolower(rcube_imap_generic::getStructurePartEncoding($structure, $part));
-            $o_part->charset       = rcube_imap_generic::getStructurePartCharset($structure, $part);
+            $o_part->ctype_primary = $part_data['type'];
+            $o_part->encoding      = $part_data['encoding'];
+            $o_part->charset       = $part_data['charset'];
+            $o_part->size          = $part_data['size'];
         }
 
-        // TODO: Add caching for message parts
-
-        if (!$part) {
-            $part = 'TEXT';
+        if ($o_part && $o_part->size) {
+            $body = $this->conn->handlePartBody($this->mailbox, $uid, true,
+                $part ? $part : 'TEXT', $o_part->encoding, $print, $fp);
         }
-
-        $body = $this->conn->handlePartBody($this->mailbox, $uid, true, $part,
-            $o_part->encoding, $print, $fp);
 
         if ($fp || $print) {
             return true;
         }
 
-        // Remove NULL characters (#1486189)
-        $body = str_replace("\x00", '', $body);
-
         // convert charset (if text or message part)
-        if ($body && !$skip_charset_conv &&
-            preg_match('/^(text|message)$/', $o_part->ctype_primary)
-        ) {
-            if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
-                $o_part->charset = $this->default_charset;
+        if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
+            // Remove NULL characters (#1486189)
+            $body = str_replace("\x00", '', $body);
+
+           if (!$skip_charset_conv) {
+                if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
+                    // try to extract charset information from HTML meta tag (#1488125)
+                    if ($o_part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m))
+                        $o_part->charset = strtoupper($m[1]);
+                    else
+                        $o_part->charset = $this->default_charset;
+                }
+                $body = rcube_charset_convert($body, $o_part->charset);
             }
-            $body = rcube_charset_convert($body, $o_part->charset);
         }
 
         return $body;
@@ -2529,7 +2555,7 @@
      * @param string  $headers Headers string if $message contains only the body
      * @param boolean $is_file True if $message is a filename
      *
-     * @return boolean True on success, False on error
+     * @return int|bool Appended message UID or True on success, False on error
      */
     function save_message($mailbox, &$message, $headers='', $is_file=false)
     {
@@ -2580,10 +2606,14 @@
 
         // make sure mailbox exists
         if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) {
-            if (in_array($to_mbox, $this->default_folders))
-                $this->create_mailbox($to_mbox, true);
-            else
+            if (in_array($to_mbox, $this->default_folders)) {
+                if (!$this->create_mailbox($to_mbox, true)) {
+                    return false;
+                }
+            }
+            else {
                 return false;
+            }
         }
 
         $config = rcmail::get_instance()->config;
@@ -2661,10 +2691,14 @@
 
         // make sure mailbox exists
         if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) {
-            if (in_array($to_mbox, $this->default_folders))
-                $this->create_mailbox($to_mbox, true);
-            else
+            if (in_array($to_mbox, $this->default_folders)) {
+                if (!$this->create_mailbox($to_mbox, true)) {
+                    return false;
+                }
+            }
+            else {
                 return false;
+            }
         }
 
         // copy messages
@@ -2912,24 +2946,53 @@
     /**
      * Public method for listing subscribed folders
      *
-     * @param   string  $root   Optional root folder
-     * @param   string  $name   Optional name pattern
-     * @param   string  $filter Optional filter
+     * @param   string  $root      Optional root folder
+     * @param   string  $name      Optional name pattern
+     * @param   string  $filter    Optional filter
+     * @param   string  $rights    Optional ACL requirements
+     * @param   bool    $skip_sort Enable to return unsorted list (for better performance)
      *
-     * @return  array   List of mailboxes/folders
+     * @return  array   List of folders
      * @access  public
      */
-    function list_mailboxes($root='', $name='*', $filter=null)
+    function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
     {
-        $a_mboxes = $this->_list_mailboxes($root, $name, $filter);
+        $cache_key = $root.':'.$name;
+        if (!empty($filter)) {
+            $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
+        }
+        $cache_key .= ':'.$rights;
+        $cache_key = 'mailboxes.'.md5($cache_key);
+
+        // get cached folder list
+        $a_mboxes = $this->get_cache($cache_key);
+        if (is_array($a_mboxes)) {
+            return $a_mboxes;
+        }
+
+        $a_mboxes = $this->_list_mailboxes($root, $name, $filter, $rights);
+
+        if (!is_array($a_mboxes)) {
+            return array();
+        }
+
+        // filter folders list according to rights requirements
+        if ($rights && $this->get_capability('ACL')) {
+            $a_mboxes = $this->filter_rights($a_mboxes, $rights);
+        }
 
         // INBOX should always be available
         if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
             array_unshift($a_mboxes, 'INBOX');
         }
 
-        // sort mailboxes
-        $a_mboxes = $this->_sort_mailbox_list($a_mboxes);
+        // sort mailboxes (always sort for cache)
+        if (!$skip_sort || $this->cache) {
+            $a_mboxes = $this->_sort_mailbox_list($a_mboxes);
+        }
+
+        // write mailboxlist to cache
+        $this->update_cache($cache_key, $a_mboxes);
 
         return $a_mboxes;
     }
@@ -2941,26 +3004,14 @@
      * @param   string  $root   Optional root folder
      * @param   string  $name   Optional name pattern
      * @param   mixed   $filter Optional filter
+     * @param   string  $rights Optional ACL requirements
      *
      * @return  array   List of mailboxes/folders
      * @see     rcube_imap::list_mailboxes()
      * @access  private
      */
-    private function _list_mailboxes($root='', $name='*', $filter=null)
+    private function _list_mailboxes($root='', $name='*', $filter=null, $rights=null)
     {
-        $cache_key = $root.':'.$name;
-        if (!empty($filter)) {
-            $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
-        }
-
-        $cache_key = 'mailboxes.'.md5($cache_key);
-
-        // get cached folder list
-        $a_mboxes = $this->get_cache($cache_key);
-        if (is_array($a_mboxes)) {
-            return $a_mboxes;
-        }
-
         $a_defaults = $a_out = array();
 
         // Give plugins a chance to provide a list of mailboxes
@@ -2971,7 +3022,7 @@
             $a_folders = $data['folders'];
         }
         else if (!$this->conn->connected()) {
-           return array();
+           return null;
         }
         else {
             // Server supports LIST-EXTENDED, we can use selection options
@@ -3019,9 +3070,6 @@
             $a_folders = array();
         }
 
-        // write mailboxlist to cache
-        $this->update_cache($cache_key, $a_folders);
-
         return $a_folders;
     }
 
@@ -3029,15 +3077,29 @@
     /**
      * Get a list of all folders available on the IMAP server
      *
-     * @param string $root   IMAP root dir
-     * @param string  $name   Optional name pattern
-     * @param mixed   $filter Optional filter
+     * @param string  $root      IMAP root dir
+     * @param string  $name      Optional name pattern
+     * @param mixed   $filter    Optional filter
+     * @param string  $rights    Optional ACL requirements
+     * @param bool    $skip_sort Enable to return unsorted list (for better performance)
      *
      * @return array Indexed array with folder names
      */
-    function list_unsubscribed($root='', $name='*', $filter=null)
+    function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
     {
-        // @TODO: caching
+        $cache_key = $root.':'.$name;
+        if (!empty($filter)) {
+            $cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
+        }
+        $cache_key .= ':'.$rights;
+        $cache_key = 'mailboxes.list.'.md5($cache_key);
+
+        // get cached folder list
+        $a_mboxes = $this->get_cache($cache_key);
+        if (is_array($a_mboxes)) {
+            return $a_mboxes;
+        }
+
         // Give plugins a chance to provide a list of mailboxes
         $data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
             array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
@@ -3059,10 +3121,41 @@
             array_unshift($a_mboxes, 'INBOX');
         }
 
+        // cache folder attributes
+        if ($root == '' && $name == '*' && empty($filter)) {
+            $this->update_cache('mailboxes.attributes', $this->conn->data['LIST']);
+        }
+
+        // filter folders list according to rights requirements
+        if ($rights && $this->get_capability('ACL')) {
+            $a_folders = $this->filter_rights($a_folders, $rights);
+        }
+
         // filter folders and sort them
-        $a_mboxes = $this->_sort_mailbox_list($a_mboxes);
+        if (!$skip_sort) {
+            $a_mboxes = $this->_sort_mailbox_list($a_mboxes);
+        }
+
+        // write mailboxlist to cache
+        $this->update_cache($cache_key, $a_mboxes);
 
         return $a_mboxes;
+    }
+
+
+    /**
+     * Filter the given list of folders according to access rights
+     */
+    private function filter_rights($a_folders, $rights)
+    {
+        $regex = '/('.$rights.')/';
+        foreach ($a_folders as $idx => $folder) {
+            $myrights = join('', (array)$this->my_rights($folder));
+            if ($myrights !== null && !preg_match($regex, $myrights))
+                unset($a_folders[$idx]);
+        }
+
+        return $a_folders;
     }
 
 
@@ -3377,30 +3470,29 @@
 
 
     /**
-     * Gets folder options from LIST response, e.g. \Noselect, \Noinferiors
+     * Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors
      *
      * @param string $mailbox Folder name
-     * @param bool   $force   Set to True if options should be refreshed
-     *                        Options are available after LIST command only
+     * @param bool   $force   Set to True if attributes should be refreshed
      *
      * @return array Options list
      */
-    function mailbox_options($mailbox, $force=false)
+    function mailbox_attributes($mailbox, $force=false)
     {
-        if ($mailbox == 'INBOX') {
-            return array();
+        // get attributes directly from LIST command
+        if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$mailbox])) {
+            $opts = $this->conn->data['LIST'][$mailbox];
+        }
+        // get cached folder attributes
+        else if (!$force) {
+            $opts = $this->get_cache('mailboxes.attributes');
+            $opts = $opts[$mailbox];
         }
 
-        if (!is_array($this->conn->data['LIST']) || !is_array($this->conn->data['LIST'][$mailbox])) {
-            if ($force) {
-                $this->conn->listMailboxes('', $mailbox);
-            }
-            else {
-                return array();
-            }
+        if (!is_array($opts)) {
+            $this->conn->listMailboxes('', $mailbox);
+            $opts = $this->conn->data['LIST'][$mailbox];
         }
-
-        $opts = $this->conn->data['LIST'][$mailbox];
 
         return is_array($opts) ? $opts : array();
     }
@@ -3483,17 +3575,17 @@
             }
         }
 
-        $options['name']      = $mailbox;
-        $options['options']   = $this->mailbox_options($mailbox, true);
-        $options['namespace'] = $this->mailbox_namespace($mailbox);
-        $options['rights']    = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array();
-        $options['special']   = in_array($mailbox, $this->default_folders);
+        $options['name']       = $mailbox;
+        $options['attributes'] = $this->mailbox_attributes($mailbox, true);
+        $options['namespace']  = $this->mailbox_namespace($mailbox);
+        $options['rights']     = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array();
+        $options['special']    = in_array($mailbox, $this->default_folders);
 
         // Set 'noselect' and 'norename' flags
-        if (is_array($options['options'])) {
-            foreach ($options['options'] as $opt) {
-                $opt = strtolower($opt);
-                if ($opt == '\noselect' || $opt == '\nonexistent') {
+        if (is_array($options['attributes'])) {
+            foreach ($options['attributes'] as $attrib) {
+                $attrib = strtolower($attrib);
+                if ($attrib == '\noselect' || $attrib == '\nonexistent') {
                     $options['noselect'] = true;
                 }
             }
@@ -3744,7 +3836,7 @@
             // @TODO: Honor MAXSIZE and DEPTH options
             foreach ($queries as $attrib => $entry)
                 if ($result = $this->conn->getAnnotation($mailbox, $entry, $attrib))
-                    $res = array_merge($res, $result);
+                    $res = array_merge_recursive($res, $result);
 
             return $res;
         }

--
Gitblit v1.9.1