From 85f4209074aab255dacd766109af5092017606ae Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Fri, 02 Oct 2015 04:56:35 -0400
Subject: [PATCH] Code improvements: CS fixes, improved internal cache cleanup on folder selection, removed redundant cache

---
 program/lib/Roundcube/rcube_imap_generic.php |  536 ++++++++++++++++++++++++++++++++++++----------------------
 1 files changed, 330 insertions(+), 206 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index a43dfee..85cbfa9 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -3,7 +3,7 @@
 /**
  +-----------------------------------------------------------------------+
  | This file is part of the Roundcube Webmail client                     |
- | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
+ | Copyright (C) 2005-2015, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
  |                                                                       |
  | Licensed under the GNU General Public License version 3 or            |
@@ -48,26 +48,24 @@
         '*'        => '\\*',
     );
 
-    public static $mupdate;
-
     protected $fp;
     protected $host;
-    protected $logged = false;
-    protected $capability = array();
-    protected $capability_readed = false;
     protected $prefs;
     protected $cmd_tag;
     protected $cmd_num = 0;
     protected $resourceid;
-    protected $_debug = false;
-    protected $_debug_handler = false;
+    protected $logged            = false;
+    protected $capability        = array();
+    protected $capability_readed = false;
+    protected $debug             = false;
+    protected $debug_handler     = false;
 
-    const ERROR_OK = 0;
-    const ERROR_NO = -1;
-    const ERROR_BAD = -2;
-    const ERROR_BYE = -3;
-    const ERROR_UNKNOWN = -4;
-    const ERROR_COMMAND = -5;
+    const ERROR_OK       = 0;
+    const ERROR_NO       = -1;
+    const ERROR_BAD      = -2;
+    const ERROR_BYE      = -3;
+    const ERROR_UNKNOWN  = -4;
+    const ERROR_COMMAND  = -5;
     const ERROR_READONLY = -6;
 
     const COMMAND_NORESPONSE = 1;
@@ -77,28 +75,23 @@
 
     const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
 
-    /**
-     * Object constructor
-     */
-    function __construct()
-    {
-    }
 
     /**
      * Send simple (one line) command to the connection stream
      *
-     * @param string $string Command string
-     * @param bool   $endln  True if CRLF need to be added at the end of command
+     * @param string $string     Command string
+     * @param bool   $endln      True if CRLF need to be added at the end of command
      * @param bool   $anonymized Don't write the given data to log but a placeholder
      *
      * @param int Number of bytes sent, False on error
      */
-    function putLine($string, $endln=true, $anonymized=false)
+    function putLine($string, $endln = true, $anonymized = false)
     {
-        if (!$this->fp)
+        if (!$this->fp) {
             return false;
+        }
 
-        if ($this->_debug) {
+        if ($this->debug) {
             // anonymize the sent command for logging
             $cut = $endln ? 2 : 0;
             if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) {
@@ -110,6 +103,7 @@
             else {
                 $log = rtrim($string);
             }
+
             $this->debug('C: ' . $log);
         }
 
@@ -127,8 +121,8 @@
      * Send command to the connection stream with Command Continuation
      * Requests (RFC3501 7.5) and LITERAL+ (RFC2088) support
      *
-     * @param string $string Command string
-     * @param bool   $endln  True if CRLF need to be added at the end of command
+     * @param string $string     Command string
+     * @param bool   $endln      True if CRLF need to be added at the end of command
      * @param bool   $anonymized Don't write the given data to log but a placeholder
      *
      * @return int|bool Number of bytes sent, False on error
@@ -153,23 +147,29 @@
                     }
 
                     $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized);
-                    if ($bytes === false)
+                    if ($bytes === false) {
                         return false;
+                    }
+
                     $res += $bytes;
 
                     // don't wait if server supports LITERAL+ capability
                     if (!$this->prefs['literal+']) {
                         $line = $this->readLine(1000);
                         // handle error in command
-                        if ($line[0] != '+')
+                        if ($line[0] != '+') {
                             return false;
+                        }
                     }
+
                     $i++;
                 }
                 else {
                     $bytes = $this->putLine($parts[$i], false, $anonymized);
-                    if ($bytes === false)
+                    if ($bytes === false) {
                         return false;
+                    }
+
                     $res += $bytes;
                 }
             }
@@ -180,11 +180,11 @@
     /**
      * Reads line from the connection stream
      *
-     * @param int  $size  Buffer size
+     * @param int $size Buffer size
      *
      * @return string Line of text response
      */
-    function readLine($size=1024)
+    function readLine($size = 1024)
     {
         $line = '';
 
@@ -194,7 +194,7 @@
 
         do {
             if ($this->eof()) {
-                return $line ? $line : NULL;
+                return $line ? $line : null;
             }
 
             $buffer = fgets($this->fp, $size);
@@ -203,9 +203,11 @@
                 $this->closeSocket();
                 break;
             }
-            if ($this->_debug) {
+
+            if ($this->debug) {
                 $this->debug('S: '. rtrim($buffer));
             }
+
             $line .= $buffer;
         } while (substr($buffer, -1) != "\n");
 
@@ -231,8 +233,10 @@
 
             while (strlen($out) < $bytes) {
                 $line = $this->readBytes($bytes);
-                if ($line === NULL)
+                if ($line === null) {
                     break;
+                }
+
                 $out .= $line;
             }
 
@@ -245,7 +249,7 @@
     /**
      * Reads specified number of bytes from the connection stream
      *
-     * @param int  $bytes  Number of bytes to get
+     * @param int $bytes Number of bytes to get
      *
      * @return string Response text
      */
@@ -253,10 +257,10 @@
     {
         $data = '';
         $len  = 0;
-        while ($len < $bytes && !$this->eof())
-        {
+
+        while ($len < $bytes && !$this->eof()) {
             $d = fread($this->fp, $bytes-$len);
-            if ($this->_debug) {
+            if ($this->debug) {
                 $this->debug('S: '. $d);
             }
             $data .= $d;
@@ -273,21 +277,23 @@
     /**
      * Reads complete response to the IMAP command
      *
-     * @param array  $untagged  Will be filled with untagged response lines
+     * @param array $untagged Will be filled with untagged response lines
      *
      * @return string Response text
      */
-    function readReply(&$untagged=null)
+    function readReply(&$untagged = null)
     {
         do {
             $line = trim($this->readLine(1024));
             // store untagged response lines
-            if ($line[0] == '*')
+            if ($line[0] == '*') {
                 $untagged[] = $line;
+            }
         } while ($line[0] == '*');
 
-        if ($untagged)
+        if ($untagged) {
             $untagged = join("\n", $untagged);
+        }
 
         return $line;
     }
@@ -344,6 +350,7 @@
 
             return $this->errornum;
         }
+
         return self::ERROR_UNKNOWN;
     }
 
@@ -384,7 +391,7 @@
     /**
      * Error code/message setter.
      */
-    function setError($code, $msg='')
+    function setError($code, $msg = '')
     {
         $this->errornum = $code;
         $this->error    = $msg;
@@ -401,23 +408,27 @@
      *
      * @return bool True any check is true or connection is closed.
      */
-    function startsWith($string, $match, $error=false, $nonempty=false)
+    function startsWith($string, $match, $error = false, $nonempty = false)
     {
         if (!$this->fp) {
             return true;
         }
+
         if (strncmp($string, $match, strlen($match)) == 0) {
             return true;
         }
+
         if ($error && preg_match('/^\* (BYE|BAD) /i', $string, $m)) {
             if (strtoupper($m[1]) == 'BYE') {
                 $this->closeSocket();
             }
             return true;
         }
+
         if ($nonempty && !strlen($string)) {
             return true;
         }
+
         return false;
     }
 
@@ -478,20 +489,20 @@
 
     function clearCapability()
     {
-        $this->capability = array();
+        $this->capability        = array();
         $this->capability_readed = false;
     }
 
     /**
      * DIGEST-MD5/CRAM-MD5/PLAIN Authentication
      *
-     * @param string $user
-     * @param string $pass
+     * @param string $user Username
+     * @param string $pass Password
      * @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5)
      *
      * @return resource Connection resourse on success, error code on error
      */
-    function authenticate($user, $pass, $type='PLAIN')
+    function authenticate($user, $pass, $type = 'PLAIN')
     {
         if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') {
             if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) {
@@ -554,14 +565,12 @@
                 $this->putLine($reply, true, true);
                 $line = trim($this->readReply());
 
-                if ($line[0] == '+') {
-                    $challenge = substr($line, 2);
-                }
-                else {
+                if ($line[0] != '+') {
                     return $this->parseResult($line);
                 }
 
                 // check response
+                $challenge = substr($line, 2);
                 $challenge = base64_decode($challenge);
                 if (strpos($challenge, 'rspauth=') === false) {
                     $this->setError(self::ERROR_BAD,
@@ -573,6 +582,66 @@
             }
 
             $line = $this->readReply();
+            $result = $this->parseResult($line);
+        }
+        elseif ($type == 'GSSAPI') {
+            if (!extension_loaded('krb5')) {
+                $this->setError(self::ERROR_BYE,
+                    "The krb5 extension is required for GSSAPI authentication");
+                return self::ERROR_BAD;
+            }
+
+            if (empty($this->prefs['gssapi_cn'])) {
+                $this->setError(self::ERROR_BYE,
+                    "The gssapi_cn parameter is required for GSSAPI authentication");
+                return self::ERROR_BAD;
+            }
+
+            if (empty($this->prefs['gssapi_context'])) {
+                $this->setError(self::ERROR_BYE,
+                    "The gssapi_context parameter is required for GSSAPI authentication");
+                return self::ERROR_BAD;
+            }
+
+            putenv('KRB5CCNAME=' . $this->prefs['gssapi_cn']);
+
+            try {
+                $ccache = new KRB5CCache();
+                $ccache->open($this->prefs['gssapi_cn']);
+                $gssapicontext = new GSSAPIContext();
+                $gssapicontext->acquireCredentials($ccache);
+
+                $token   = '';
+                $success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token);
+                $token   = base64_encode($token);
+            }
+            catch (Exception $e) {
+                trigger_error($e->getMessage(), E_USER_WARNING);
+                $this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
+                return self::ERROR_BAD;
+            }
+
+            $this->putLine($this->nextTag() . " AUTHENTICATE GSSAPI " . $token);
+            $line = trim($this->readReply());
+
+            if ($line[0] != '+') {
+                return $this->parseResult($line);
+            }
+
+            try {
+                $challenge = base64_decode(substr($line, 2));
+                $gssapicontext->unwrap($challenge, $challenge);
+                $gssapicontext->wrap($challenge, $challenge, true);
+            }
+            catch (Exception $e) {
+                trigger_error($e->getMessage(), E_USER_WARNING);
+                $this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
+                return self::ERROR_BAD;
+            }
+
+            $this->putLine(base64_encode($challenge));
+
+            $line   = $this->readReply();
             $result = $this->parseResult($line);
         }
         else { // PLAIN
@@ -670,8 +739,6 @@
                 return ($this->prefs['delimiter'] = $delimiter);
             }
         }
-
-        return NULL;
     }
 
     /**
@@ -692,7 +759,8 @@
         list($code, $response) = $this->execute('NAMESPACE');
 
         if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {
-            $data = $this->tokenizeResponse(substr($response, 11));
+            $response = substr($response, 11);
+            $data     = $this->tokenizeResponse($response);
         }
 
         if (!is_array($data)) {
@@ -711,14 +779,14 @@
     /**
      * Connects to IMAP server and authenticates.
      *
-     * @param string $host      Server hostname or IP
-     * @param string $user      User name
-     * @param string $password  Password
-     * @param array  $options   Connection and class options
+     * @param string $host     Server hostname or IP
+     * @param string $user     User name
+     * @param string $password Password
+     * @param array  $options  Connection and class options
      *
      * @return bool True on success, False on failure
      */
-    function connect($host, $user, $password, $options=null)
+    function connect($host, $user, $password, $options = null)
     {
         // configure
         $this->set_prefs($options);
@@ -739,7 +807,7 @@
             return false;
         }
 
-        if (empty($password)) {
+        if (empty($password) && empty($options['gssapi_cn'])) {
             $this->setError(self::ERROR_NO, "Empty password");
             return false;
         }
@@ -751,7 +819,7 @@
 
         // Send ID info
         if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
-            $this->id($this->prefs['ident']);
+            $this->data['ID'] = $this->id($this->prefs['ident']);
         }
 
         $auth_method  = $this->prefs['auth_type'];
@@ -775,7 +843,8 @@
             }
 
             // Use best (for security) supported authentication method
-            foreach (array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN') as $auth_method) {
+            $all_methods = array('GSSAPI', 'DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN');
+            foreach ($all_methods as $auth_method) {
                 if (in_array($auth_method, $auth_methods)) {
                     break;
                 }
@@ -804,6 +873,7 @@
             case 'CRAM-MD5':
             case 'DIGEST-MD5':
             case 'PLAIN':
+            case 'GSSAPI':
                 $result = $this->authenticate($user, $password, $auth_method);
                 break;
             case 'LOGIN':
@@ -876,7 +946,7 @@
 
         $line = trim(fgets($this->fp, 8192));
 
-        if ($this->_debug) {
+        if ($this->debug) {
             // set connection identifier for debug output
             preg_match('/#([0-9]+)/', (string) $this->fp, $m);
             $this->resourceid = strtoupper(substr(md5($m[1].$this->user.microtime()), 0, 4));
@@ -897,6 +967,8 @@
             $this->closeConnection();
             return false;
         }
+
+        $this->data['GREETING'] = trim(preg_replace('/\[[^\]]+\]\s*/', '', $line));
 
         // RFC3501 [7.1] optional CAPABILITY response
         if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
@@ -1013,14 +1085,18 @@
         //    3. an optional parenthesized list of known sequence ranges and their
         //       corresponding UIDs.
         if (!empty($qresync_data)) {
-            if (!empty($qresync_data[2]))
+            if (!empty($qresync_data[2])) {
                 $qresync_data[2] = self::compressMessageSet($qresync_data[2]);
+            }
+
             $params[] = array('QRESYNC', $qresync_data);
         }
 
         list($code, $response) = $this->execute('SELECT', $params);
 
         if ($code == self::ERROR_OK) {
+            $this->clear_mailbox_cache();
+
             $response = explode("\r\n", $response);
             foreach ($response as $line) {
                 if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT)$/i', $line, $m)) {
@@ -1063,8 +1139,8 @@
             }
 
             $this->data['READ-WRITE'] = $this->resultcode != 'READ-ONLY';
-
             $this->selected = $mailbox;
+
             return true;
         }
 
@@ -1082,7 +1158,7 @@
      * @return array Status item-value hash
      * @since 0.5-beta
      */
-    function status($mailbox, $items=array())
+    function status($mailbox, $items = array())
     {
         if (!strlen($mailbox)) {
             return false;
@@ -1108,7 +1184,8 @@
             // folder name with spaces. Let's try to handle this situation
             if (!is_array($items) && ($pos = strpos($response, '(')) !== false) {
                 $response = substr($response, $pos);
-                $items = $this->tokenizeResponse($response, 1);
+                $items    = $this->tokenizeResponse($response, 1);
+
                 if (!is_array($items)) {
                     return $result;
                 }
@@ -1134,7 +1211,7 @@
      *
      * @return boolean True on success, False on error
      */
-    function expunge($mailbox, $messages=NULL)
+    function expunge($mailbox, $messages = null)
     {
         if (!$this->select($mailbox)) {
             return false;
@@ -1146,7 +1223,7 @@
         }
 
         // Clear internal status cache
-        unset($this->data['STATUS:'.$mailbox]);
+        $this->clear_status_cache($mailbox);
 
         if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
             $messages = self::compressMessageSet($messages);
@@ -1172,7 +1249,7 @@
      */
     function close()
     {
-        $result = $this->execute('CLOSE', NULL, self::COMMAND_NORESPONSE);
+        $result = $this->execute('CLOSE', null, self::COMMAND_NORESPONSE);
 
         if ($result == self::ERROR_OK) {
             $this->selected = null;
@@ -1314,9 +1391,9 @@
      * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
      *                    is requested, False on error.
      */
-    function listSubscribed($ref, $mailbox, $return_opts=array())
+    function listSubscribed($ref, $mailbox, $return_opts = array())
     {
-        return $this->_listMailboxes($ref, $mailbox, true, $return_opts, NULL);
+        return $this->_listMailboxes($ref, $mailbox, true, $return_opts, null);
     }
 
     /**
@@ -1461,13 +1538,9 @@
      *
      * @return int Number of messages, False on error
      */
-    function countMessages($mailbox, $refresh = false)
+    function countMessages($mailbox)
     {
-        if ($refresh) {
-            $this->selected = null;
-        }
-
-        if ($this->selected === $mailbox) {
+        if ($this->selected === $mailbox && isset($this->data['EXISTS'])) {
             return $this->data['EXISTS'];
         }
 
@@ -1495,14 +1568,20 @@
      */
     function countRecent($mailbox)
     {
-        if (!strlen($mailbox)) {
-            $mailbox = 'INBOX';
+        if ($this->selected === $mailbox && isset($this->data['RECENT'])) {
+            return $this->data['RECENT'];
         }
 
-        $this->select($mailbox);
+        // Check internal cache
+        $cache = $this->data['STATUS:'.$mailbox];
+        if (!empty($cache) && isset($cache['RECENT'])) {
+            return (int) $cache['RECENT'];
+        }
 
-        if ($this->selected === $mailbox) {
-            return $this->data['RECENT'];
+        // Try STATUS (should be faster than SELECT)
+        $counts = $this->status($mailbox, array('RECENT'));
+        if (is_array($counts)) {
+            return (int) $counts['RECENT'];
         }
 
         return false;
@@ -1546,7 +1625,7 @@
      * @return array Server identification information key/value hash
      * @since 0.6
      */
-    function id($items=array())
+    function id($items = array())
     {
         if (is_array($items) && !empty($items)) {
             foreach ($items as $key => $value) {
@@ -1558,7 +1637,6 @@
         list($code, $response) = $this->execute('ID', array(
             !empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)
         ));
-
 
         if ($code == self::ERROR_OK && preg_match('/\* ID /i', $response)) {
             $response = substr($response, 5); // remove prefix "* ID "
@@ -1704,7 +1782,6 @@
         $encoding  = $encoding ? trim($encoding) : 'US-ASCII';
         $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
         $criteria  = $criteria ? 'ALL '.trim($criteria) : 'ALL';
-        $data      = '';
 
         list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
             array($algorithm, $encoding, $criteria));
@@ -1882,7 +1959,7 @@
 
             if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
                 $id     = $m[1];
-                $flags  = NULL;
+                $flags  = null;
 
                 if ($return_uid) {
                     if (preg_match('/UID ([0-9]+)/', $line, $matches))
@@ -1959,7 +2036,6 @@
                 return (int) $arr[0];
             }
         }
-        return null;
     }
 
     /**
@@ -1980,14 +2056,20 @@
             return null;
         }
 
+        if ($uid = $this->data['UID-MAP'][$id]) {
+            return $uid;
+        }
+
+        if (isset($this->data['EXISTS']) && $id > $this->data['EXISTS']) {
+            return null;
+        }
+
         $index = $this->search($mailbox, $id, true);
 
         if ($index->count() == 1) {
             $arr = $index->get();
-            return (int) $arr[0];
+            return $this->data['UID-MAP'][$id] = (int) $arr[0];
         }
-
-        return null;
     }
 
     /**
@@ -2041,8 +2123,14 @@
             $flag = $this->flags[strtoupper($flag)];
         }
 
-        if (!$flag || !in_array($flag, (array) $this->data['PERMANENTFLAGS'])
-            || !in_array('\\*', (array) $this->data['PERMANENTFLAGS'])
+        if (!$flag) {
+            return false;
+        }
+
+        // if PERMANENTFLAGS is not specified all flags are allowed
+        if (!empty($this->data['PERMANENTFLAGS'])
+            && !in_array($flag, (array) $this->data['PERMANENTFLAGS'])
+            && !in_array('\\*', (array) $this->data['PERMANENTFLAGS'])
         ) {
             return false;
         }
@@ -2118,7 +2206,7 @@
 
             // Clear internal status cache
             unset($this->data['STATUS:'.$to]);
-            unset($this->data['STATUS:'.$from]);
+            $this->clear_status_cache($from);
 
             $result = $this->execute('UID MOVE', array(
                 $this->compressMessageSet($messages), $this->escape($to)),
@@ -2215,7 +2303,7 @@
 
                     while (strlen($out) < $bytes) {
                         $out = $this->readBytes($bytes);
-                        if ($out === NULL)
+                        if ($out === null)
                             break;
                         $line .= $out;
                     }
@@ -2432,7 +2520,16 @@
         return false;
     }
 
-    function sortHeaders($a, $field, $flag)
+    /**
+     * Sort messages by specified header field
+     *
+     * @param array  $messages Array of rcube_message_header objects
+     * @param string $field    Name of the property to sort by
+     * @param string $flag     Sorting order (ASC|DESC)
+     *
+     * @return array Sorted input array
+     */
+    public static function sortHeaders($messages, $field, $flag)
     {
         if (empty($field)) {
             $field = 'uid';
@@ -2441,57 +2538,65 @@
             $field = strtolower($field);
         }
 
-        if ($field == 'date' || $field == 'internaldate') {
-            $field = 'timestamp';
-        }
-
         if (empty($flag)) {
             $flag = 'ASC';
-        } else {
+        }
+        else {
             $flag = strtoupper($flag);
         }
 
-        $c = count($a);
-        if ($c > 0) {
-            // Strategy:
-            // First, we'll create an "index" array.
-            // Then, we'll use sort() on that array,
-            // and use that to sort the main array.
+        // Strategy: First, we'll create an "index" array.
+        // Then, we'll use sort() on that array, and use that to sort the main array.
 
-            // create "index" array
-            $index = array();
-            reset($a);
-            while (list($key, $val) = each($a)) {
-                if ($field == 'timestamp') {
-                    $data = $this->strToTime($val->date);
-                    if (!$data) {
-                        $data = $val->timestamp;
-                    }
-                } else {
-                    $data = $val->$field;
-                    if (is_string($data)) {
-                        $data = str_replace('"', '', $data);
-                        if ($field == 'subject') {
-                            $data = preg_replace('/^(Re: \s*|Fwd:\s*|Fw:\s*)+/i', '', $data);
-                        }
-                        $data = strtoupper($data);
-                    }
+        $index  = array();
+        $result = array();
+
+        reset($messages);
+
+        while (list($key, $headers) = each($messages)) {
+            $value = null;
+
+            switch ($field) {
+            case 'arrival':
+                $field = 'internaldate';
+            case 'date':
+            case 'internaldate':
+            case 'timestamp':
+                $value = self::strToTime($headers->$field);
+                if (!$value && $field != 'timestamp') {
+                    $value = $headers->timestamp;
                 }
-                $index[$key] = $data;
+
+                break;
+
+            default:
+                // @TODO: decode header value, convert to UTF-8
+                $value = $headers->$field;
+                if (is_string($value)) {
+                    $value = str_replace('"', '', $value);
+                    if ($field == 'subject') {
+                        $value = preg_replace('/^(Re:\s*|Fwd:\s*|Fw:\s*)+/i', '', $value);
+                    }
+
+                    $data = strtoupper($value);
+                }
             }
 
+            $index[$key] = $value;
+        }
+
+        if (!empty($index)) {
             // sort index
             if ($flag == 'ASC') {
                 asort($index);
-            } else {
+            }
+            else {
                 arsort($index);
             }
 
             // form new array based on index
-            $result = array();
-            reset($index);
             while (list($key, $val) = each($index)) {
-                $result[$key] = $a[$key];
+                $result[$key] = $messages[$key];
             }
         }
 
@@ -2550,63 +2655,74 @@
         return $result;
     }
 
-    function fetchPartHeader($mailbox, $id, $is_uid=false, $part=NULL)
+    function fetchPartHeader($mailbox, $id, $is_uid = false, $part = null)
     {
         $part = empty($part) ? 'HEADER' : $part.'.MIME';
 
         return $this->handlePartBody($mailbox, $id, $is_uid, $part);
     }
 
-    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=NULL, $print=NULL, $file=NULL, $formatted=false, $max_bytes=0)
+    function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=null, $print=null, $file=null, $formatted=false, $max_bytes=0)
     {
         if (!$this->select($mailbox)) {
             return false;
         }
 
-        switch ($encoding) {
-        case 'base64':
-            $mode = 1;
-            break;
-        case 'quoted-printable':
-            $mode = 2;
-            break;
-        case 'x-uuencode':
-        case 'x-uue':
-        case 'uue':
-        case 'uuencode':
-            $mode = 3;
-            break;
-        default:
-            $mode = 0;
-        }
-
-        // Use BINARY extension when possible (and safe)
-        $binary     = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
-        $fetch_mode = $binary ? 'BINARY' : 'BODY';
-        $partial    = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
-
-        // format request
-        $key     = $this->nextTag();
-        $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
-        $result  = false;
-        $found   = false;
-
-        // send request
-        if (!$this->putLine($request)) {
-            $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
-            return false;
-        }
-
-        if ($binary) {
-            // WARNING: Use $formatted argument with care, this may break binary data stream
-            $mode = -1;
-        }
+        $binary    = true;
 
         do {
+            if (!$initiated) {
+                switch ($encoding) {
+                case 'base64':
+                    $mode = 1;
+                    break;
+                case 'quoted-printable':
+                    $mode = 2;
+                    break;
+                case 'x-uuencode':
+                case 'x-uue':
+                case 'uue':
+                case 'uuencode':
+                    $mode = 3;
+                    break;
+                default:
+                    $mode = 0;
+                }
+
+                // Use BINARY extension when possible (and safe)
+                $binary     = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
+                $fetch_mode = $binary ? 'BINARY' : 'BODY';
+                $partial    = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
+
+                // format request
+                $key       = $this->nextTag();
+                $request   = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)";
+                $result    = false;
+                $found     = false;
+                $initiated = true;
+
+                // send request
+                if (!$this->putLine($request)) {
+                    $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
+                    return false;
+                }
+
+                if ($binary) {
+                    // WARNING: Use $formatted argument with care, this may break binary data stream
+                    $mode = -1;
+                }
+            }
+
             $line = trim($this->readLine(1024));
 
             if (!$line) {
                 break;
+            }
+
+            // handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request
+            if ($binary && !$found && preg_match('/^' . $key . ' NO \[UNKNOWN-CTE\]/i', $line)) {
+                $binary = $initiated = false;
+                continue;
             }
 
             // skip irrelevant untagged responses (we have a result already)
@@ -2655,7 +2771,7 @@
                 else while ($bytes > 0) {
                     $line = $this->readLine(8192);
 
-                    if ($line === NULL) {
+                    if ($line === null) {
                         break;
                     }
 
@@ -2669,7 +2785,7 @@
 
                     // BASE64
                     if ($mode == 1) {
-                        $line = rtrim($line, "\t\r\n\0\x0B");
+                        $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line);
                         // create chunks with proper length for base64 decoding
                         $line = $prev.$line;
                         $length = strlen($line);
@@ -2714,7 +2830,7 @@
                     }
                 }
             }
-        } while (!$this->startsWith($line, $key, true));
+        } while (!$this->startsWith($line, $key, true) || !$initiated);
 
         if ($result !== false) {
             if ($file) {
@@ -3023,10 +3139,7 @@
             }
 
             $this->setError(self::ERROR_COMMAND, "Incomplete ACL response");
-            return NULL;
         }
-
-        return NULL;
     }
 
     /**
@@ -3057,8 +3170,6 @@
                 'optional' => explode(' ', $optional),
             );
         }
-
-        return NULL;
     }
 
     /**
@@ -3082,8 +3193,6 @@
 
             return str_split($rights);
         }
-
-        return NULL;
     }
 
     /**
@@ -3136,7 +3245,7 @@
         }
 
         foreach ($entries as $entry) {
-            $data[$entry] = NULL;
+            $data[$entry] = null;
         }
 
         return $this->setMetadata($mailbox, $data);
@@ -3199,9 +3308,9 @@
                 for ($i=0; $i<$size; $i++) {
                     if (isset($mbox) && is_array($data[$i])) {
                         $size_sub = count($data[$i]);
-                        for ($x=0; $x<$size_sub; $x++) {
+                        for ($x=0; $x<$size_sub; $x+=2) {
                             if ($data[$i][$x+1] !== null)
-                                $result[$mbox][$data[$i][$x]] = $data[$i][++$x];
+                                $result[$mbox][$data[$i][$x]] = $data[$i][$x+1];
                         }
                         unset($data[$i]);
                     }
@@ -3219,8 +3328,8 @@
                         }
                     }
                     else if (isset($mbox)) {
-                        if ($data[$i+1] !== null)
-                            $result[$mbox][$data[$i]] = $data[++$i];
+                        if ($data[++$i] !== null)
+                            $result[$mbox][$data[$i-1]] = $data[$i];
                         unset($data[$i]);
                         unset($data[$i-1]);
                     }
@@ -3232,8 +3341,6 @@
 
             return $result;
         }
-
-        return NULL;
     }
 
     /**
@@ -3254,11 +3361,6 @@
         }
 
         foreach ($data as $entry) {
-            // Workaround cyrus-murder bug, the entry[2] string needs to be escaped
-            if (self::$mupdate) {
-                $entry[2] = addcslashes($entry[2], '\\"');
-            }
-
             // ANNOTATEMORE drafts before version 08 require quoted parameters
             $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true),
                 $this->escape($entry[1], true), $this->escape($entry[2], true));
@@ -3380,8 +3482,6 @@
 
             return $result;
         }
-
-        return NULL;
     }
 
     /**
@@ -3625,7 +3725,7 @@
                 // excluded chars: SP, CTL, ), DEL
                 // we do not exclude [ and ] (#1489223)
                 if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
-                    $result[] = $m[1] == 'NIL' ? NULL : $m[1];
+                    $result[] = $m[1] == 'NIL' ? null : $m[1];
                     $str = substr($str, strlen($m[1]));
                 }
                 break;
@@ -3751,6 +3851,35 @@
     }
 
     /**
+     * Clear internal status cache
+     */
+    protected function clear_status_cache($mailbox)
+    {
+        unset($this->data['STATUS:' . $mailbox]);
+
+        $keys = array('EXISTS', 'RECENT', 'UNSEEN', 'UID-MAP');
+
+        foreach ($keys as $key) {
+            unset($this->data[$key]);
+        }
+    }
+
+    /**
+     * Clear internal cache of the current mailbox
+     */
+    protected function clear_mailbox_cache()
+    {
+        $this->clear_status_cache($this->selected);
+
+        $keys = array('UIDNEXT', 'UIDVALIDITY', 'HIGHESTMODSEQ', 'NOMODSEQ',
+            'PERMANENTFLAGS', 'QRESYNC', 'VANISHED', 'READ-WRITE');
+
+        foreach ($keys as $key) {
+            unset($this->data[$key]);
+        }
+    }
+
+    /**
      * Converts flags array into string for inclusion in IMAP command
      *
      * @param array $flags Flags (see self::flags)
@@ -3823,10 +3952,6 @@
             $this->prefs['literal+'] = true;
         }
 
-        if (preg_match('/(\[| )MUPDATE=.*/', $str)) {
-            self::$mupdate = true;
-        }
-
         if ($trusted) {
             $this->capability_readed = true;
         }
@@ -3875,8 +4000,8 @@
      */
     function setDebug($debug, $handler = null)
     {
-        $this->_debug = $debug;
-        $this->_debug_handler = $handler;
+        $this->debug = $debug;
+        $this->debug_handler = $handler;
     }
 
     /**
@@ -3898,11 +4023,10 @@
             $message = sprintf('[%s] %s', $this->resourceid, $message);
         }
 
-        if ($this->_debug_handler) {
-            call_user_func_array($this->_debug_handler, array(&$this, $message));
+        if ($this->debug_handler) {
+            call_user_func_array($this->debug_handler, array(&$this, $message));
         } else {
             echo "DEBUG: $message\n";
         }
     }
-
 }

--
Gitblit v1.9.1