From ed3e51f1b41d818ae757220fd41cdf0b093b20f6 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 02 Jul 2013 08:49:48 -0400
Subject: [PATCH] Make possible to disable some (broken) IMAP extensions with imap_disable_caps option (#1489184) Add some notes in INSTALL about broken ESEARCH in uw-imap.

---
 program/lib/Roundcube/rcube_imap_generic.php |  304 ++++++++++++++++++++++++++++----------------------
 1 files changed, 171 insertions(+), 133 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 8532cf8..ae390a0 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -72,6 +72,8 @@
     const COMMAND_CAPABILITY = 2;
     const COMMAND_LASTLINE   = 4;
 
+    const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
+
     /**
      * Object constructor
      */
@@ -713,6 +715,10 @@
             $auth_method = 'CHECK';
         }
 
+        if (!empty($this->prefs['disabled_caps'])) {
+            $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']);
+        }
+
         $result = false;
 
         // initialize connection
@@ -746,7 +752,7 @@
         }
 
         if ($this->prefs['timeout'] <= 0) {
-            $this->prefs['timeout'] = ini_get('default_socket_timeout');
+            $this->prefs['timeout'] = max(0, intval(ini_get('default_socket_timeout')));
         }
 
         // Connect
@@ -1077,7 +1083,7 @@
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'EXPUNGE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -1561,11 +1567,12 @@
         }
 
         // message IDs
-        if (!empty($add))
+        if (!empty($add)) {
             $add = $this->compressMessageSet($add);
+        }
 
         list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT',
-            array("($field)", $encoding, 'ALL' . (!empty($add) ? ' '.$add : '')));
+            array("($field)", $encoding, !empty($add) ? $add : 'ALL'));
 
         if ($code != self::ERROR_OK) {
             $response = null;
@@ -1652,7 +1659,6 @@
         }
 
         if (!empty($criteria)) {
-            $modseq = stripos($criteria, 'MODSEQ') !== false;
             $params .= ($params ? ' ' : '') . $criteria;
         }
         else {
@@ -1791,7 +1797,6 @@
                 if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
                     $flags = explode(' ', strtoupper($matches[1]));
                     if (in_array('\\DELETED', $flags)) {
-                        $deleted[$id] = $id;
                         continue;
                     }
                 }
@@ -1936,7 +1941,7 @@
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -1997,7 +2002,7 @@
         }
 
         if (!$this->data['READ-WRITE']) {
-            $this->setError(self::ERROR_READONLY, "Mailbox is read-only", 'STORE');
+            $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
             return false;
         }
 
@@ -2010,31 +2015,32 @@
             unset($this->data['STATUS:'.$to]);
             unset($this->data['STATUS:'.$from]);
 
-            $r = $this->execute('UID MOVE', array(
+            $result = $this->execute('UID MOVE', array(
                 $this->compressMessageSet($messages), $this->escape($to)),
                 self::COMMAND_NORESPONSE);
+
+            return ($result == self::ERROR_OK);
         }
+
         // use COPY + STORE +FLAGS.SILENT \Deleted + EXPUNGE
-        else {
-            $r = $this->copy($messages, $from, $to);
+        $result = $this->copy($messages, $from, $to);
 
-            if ($r) {
-                // Clear internal status cache
-                unset($this->data['STATUS:'.$from]);
+        if ($result) {
+            // Clear internal status cache
+            unset($this->data['STATUS:'.$from]);
 
-                $r = $this->flag($from, $messages, 'DELETED');
+            $result = $this->flag($from, $messages, 'DELETED');
 
-                if ($messages == '*') {
-                    // CLOSE+SELECT should be faster than EXPUNGE
-                    $this->close();
-                }
-                else {
-                    $this->expunge($from, $messages);
-                }
+            if ($messages == '*') {
+                // CLOSE+SELECT should be faster than EXPUNGE
+                $this->close();
+            }
+            else {
+                $this->expunge($from, $messages);
             }
         }
 
-        return $r;
+        return $result;
     }
 
     /**
@@ -2171,7 +2177,7 @@
                 // create array with header field:data
                 if (!empty($headers)) {
                     $headers = explode("\n", trim($headers));
-                    foreach ($headers as $hid => $resln) {
+                    foreach ($headers as $resln) {
                         if (ord($resln[0]) <= 32) {
                             $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
                         } else {
@@ -2179,7 +2185,7 @@
                         }
                     }
 
-                    while (list($lines_key, $str) = each($lines)) {
+                    foreach ($lines as $str) {
                         list($field, $string) = explode(':', $str, 2);
 
                         $field  = strtolower($field);
@@ -2264,24 +2270,53 @@
         return $result;
     }
 
-    function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '')
+    /**
+     * Returns message(s) data (flags, headers, etc.)
+     *
+     * @param string $mailbox     Mailbox name
+     * @param mixed  $message_set Message(s) sequence identifier(s) or UID(s)
+     * @param bool   $is_uid      True if $message_set contains UIDs
+     * @param bool   $bodystr     Enable to add BODYSTRUCTURE data to the result
+     * @param array  $add_headers List of additional headers
+     *
+     * @return bool|array List of rcube_message_header elements, False on error
+     */
+    function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array())
     {
         $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE');
-        if ($bodystr)
+        $headers     = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
+            'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY');
+
+        if (!empty($add_headers)) {
+            $add_headers = array_map('strtoupper', $add_headers);
+            $headers     = array_unique(array_merge($headers, $add_headers));
+        }
+
+        if ($bodystr) {
             $query_items[] = 'BODYSTRUCTURE';
-        $query_items[] = 'BODY.PEEK[HEADER.FIELDS ('
-            . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY'
-            . ($add ? ' ' . trim($add) : '')
-            . ')]';
+        }
+
+        $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]';
 
         $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items);
 
         return $result;
     }
 
-    function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='')
+    /**
+     * Returns message data (flags, headers, etc.)
+     *
+     * @param string $mailbox     Mailbox name
+     * @param int    $id          Message sequence identifier or UID
+     * @param bool   $is_uid      True if $id is an UID
+     * @param bool   $bodystr     Enable to add BODYSTRUCTURE data to the result
+     * @param array  $add_headers List of additional headers
+     *
+     * @return bool|rcube_message_header Message data, False on error
+     */
+    function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array())
     {
-        $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add);
+        $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
         if (is_array($a)) {
             return array_shift($a);
         }
@@ -2445,6 +2480,7 @@
         $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)) {
@@ -2464,18 +2500,27 @@
                 break;
             }
 
-            if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
+            // skip irrelevant untagged responses (we have a result already)
+            if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
                 continue;
             }
 
             $line = $m[2];
-            $last = substr($line, -1);
 
             // handle one line response
-            if ($line[0] == '(' && $last == ')') {
+            if ($line[0] == '(' && substr($line, -1) == ')') {
                 // tokenize content inside brackets
-                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line));
-                $result = count($tokens) == 1 ? $tokens[0] : false;
+                // the content can be e.g.: (UID 9844 BODY[2.4] NIL)
+                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
+
+                for ($i=0; $i<count($tokens); $i+=2) {
+                    if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
+                        $i += 2; // skip BODY|BINARY and part number
+                        $result = $tokens[$i];
+                        $found  = true;
+                        break;
+                    }
+                }
 
                 if ($result !== false) {
                     if ($mode == 1) {
@@ -2493,6 +2538,7 @@
             else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
                 $bytes = (int) $m[1];
                 $prev  = '';
+                $found = true;
 
                 while ($bytes > 0) {
                     $line = $this->readLine(8192);
@@ -2576,11 +2622,11 @@
     /**
      * Handler for IMAP APPEND command
      *
-     * @param string $mailbox Mailbox name
-     * @param string $message Message content
-     * @param array  $flags   Message flags
-     * @param string $date    Message internal date
-     * @param bool   $binary  Enable BINARY append (RFC3516)
+     * @param string       $mailbox Mailbox name
+     * @param string|array $message The message source string or array (of strings and file pointers)
+     * @param array        $flags   Message flags
+     * @param string       $date    Message internal date
+     * @param bool         $binary  Enable BINARY append (RFC3516)
      *
      * @return string|bool On success APPENDUID response (if available) or True, False on failure
      */
@@ -2594,13 +2640,28 @@
 
         $binary       = $binary && $this->getCapability('BINARY');
         $literal_plus = !$binary && $this->prefs['literal+'];
+        $len          = 0;
+        $msg          = is_array($message) ? $message : array(&$message);
+        $chunk_size   = 512000;
 
-        if (!$binary) {
-            $message = str_replace("\r", '', $message);
-            $message = str_replace("\n", "\r\n", $message);
+        for ($i=0, $cnt=count($msg); $i<$cnt; $i++) {
+            if (is_resource($msg[$i])) {
+                $stat = fstat($msg[$i]);
+                if ($stat === false) {
+                    return false;
+                }
+                $len += $stat['size'];
+            }
+            else {
+                if (!$binary) {
+                    $msg[$i] = str_replace("\r", '', $msg[$i]);
+                    $msg[$i] = str_replace("\n", "\r\n", $msg[$i]);
+                }
+
+                $len += strlen($msg[$i]);
+            }
         }
 
-        $len = strlen($message);
         if (!$len) {
             return false;
         }
@@ -2625,7 +2686,32 @@
                 }
             }
 
-            if (!$this->putLine($message)) {
+            foreach ($msg as $msg_part) {
+                // file pointer
+                if (is_resource($msg_part)) {
+                    rewind($msg_part);
+                    while (!feof($msg_part) && $this->fp) {
+                        $buffer = fread($msg_part, $chunk_size);
+                        $this->putLine($buffer, false);
+                    }
+                    fclose($msg_part);
+                }
+                // string
+                else {
+                    $size = strlen($msg_part);
+
+                    // Break up the data by sending one chunk (up to 512k) at a time.
+                    // This approach reduces our peak memory usage
+                    for ($offset = 0; $offset < $size; $offset += $chunk_size) {
+                        $chunk = substr($msg_part, $offset, $chunk_size);
+                        if (!$this->putLine($chunk, false)) {
+                            return false;
+                        }
+                    }
+                }
+            }
+
+            if (!$this->putLine('')) { // \r\n
                 return false;
             }
 
@@ -2664,94 +2750,23 @@
      */
     function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
     {
-        unset($this->data['APPENDUID']);
-
-        if ($mailbox === null || $mailbox === '') {
-            return false;
-        }
-
         // open message file
-        $in_fp = false;
         if (file_exists(realpath($path))) {
-            $in_fp = fopen($path, 'r');
+            $fp = fopen($path, 'r');
         }
 
-        if (!$in_fp) {
+        if (!$fp) {
             $this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
             return false;
         }
 
-        $body_separator = "\r\n\r\n";
-        $len = filesize($path);
-
-        if (!$len) {
-            return false;
-        }
-
+        $message = array();
         if ($headers) {
-            $headers = preg_replace('/[\r\n]+$/', '', $headers);
-            $len += strlen($headers) + strlen($body_separator);
+            $message[] = trim($headers, "\r\n") . "\r\n\r\n";
         }
+        $message[] = $fp;
 
-        $binary       = $binary && $this->getCapability('BINARY');
-        $literal_plus = !$binary && $this->prefs['literal+'];
-
-        // build APPEND command
-        $key = $this->nextTag();
-        $request = "$key APPEND " . $this->escape($mailbox) . ' (' . $this->flagsToStr($flags) . ')';
-        if (!empty($date)) {
-            $request .= ' ' . $this->escape($date);
-        }
-        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
-
-        // send APPEND command
-        if ($this->putLine($request)) {
-            // Don't wait when LITERAL+ is supported
-            if (!$literal_plus) {
-                $line = $this->readReply();
-
-                if ($line[0] != '+') {
-                    $this->parseResult($line, 'APPEND: ');
-                    return false;
-                }
-            }
-
-            // send headers with body separator
-            if ($headers) {
-                $this->putLine($headers . $body_separator, false);
-            }
-
-            // send file
-            while (!feof($in_fp) && $this->fp) {
-                $buffer = fgets($in_fp, 4096);
-                $this->putLine($buffer, false);
-            }
-            fclose($in_fp);
-
-            if (!$this->putLine('')) { // \r\n
-                return false;
-            }
-
-            // read response
-            do {
-                $line = $this->readLine();
-            } while (!$this->startsWith($line, $key, true, true));
-
-            // Clear internal status cache
-            unset($this->data['STATUS:'.$mailbox]);
-
-            if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK)
-                return false;
-            else if (!empty($this->data['APPENDUID']))
-                return $this->data['APPENDUID'];
-            else
-                return true;
-        }
-        else {
-            $this->setError(self::ERROR_COMMAND, "Unable to send command: $request");
-        }
-
-        return false;
+        return $this->append($mailbox, $message, $flags, $date, $binary);
     }
 
     /**
@@ -3483,7 +3498,7 @@
                     break 2;
                 }
 
-                // excluded chars: SP, CTL, ), [, ]
+                // excluded chars: SP, CTL, ), [, ], DEL
                 if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
                     $result[] = $m[1] == 'NIL' ? NULL : $m[1];
                     $str = substr($str, strlen($m[1]));
@@ -3501,7 +3516,7 @@
 
         if (is_array($element)) {
             reset($element);
-            while (list($key, $value) = each($element)) {
+            foreach ($element as $value) {
                 $string .= ' ' . self::r_implode($value);
             }
         }
@@ -3637,8 +3652,20 @@
      */
     static function strToTime($date)
     {
-        // support non-standard "GMTXXXX" literal
-        $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
+        // Clean malformed data
+        $date = preg_replace(
+            array(
+                '/GMT\s*([+-][0-9]+)/',                     // support non-standard "GMTXXXX" literal
+                '/[^a-z0-9\x20\x09:+-]/i',                  // remove any invalid characters
+                '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i',   // remove weekday names
+            ),
+            array(
+                '\\1',
+                '',
+                '',
+            ), $date);
+
+        $date = trim($date);
 
         // if date parsing fails, we have a date in non-rfc format
         // remove token from the end and try again
@@ -3662,6 +3689,10 @@
         $str = preg_replace('/^\* CAPABILITY /i', '', $str);
 
         $this->capability = explode(' ', strtoupper($str));
+
+        if (!empty($this->prefs['disabled_caps'])) {
+            $this->capability = array_diff($this->capability, $this->prefs['disabled_caps']);
+        }
 
         if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
             $this->prefs['literal+'] = true;
@@ -3708,9 +3739,10 @@
     /**
      * Set the value of the debugging flag.
      *
-     * @param   boolean $debug      New value for the debugging flag.
+     * @param boolean  $debug   New value for the debugging flag.
+     * @param callback $handler Logging handler function
      *
-     * @since   0.5-stable
+     * @since 0.5-stable
      */
     function setDebug($debug, $handler = null)
     {
@@ -3721,12 +3753,18 @@
     /**
      * Write the given debug text to the current debug output handler.
      *
-     * @param   string  $message    Debug mesage text.
+     * @param string $message Debug mesage text.
      *
-     * @since   0.5-stable
+     * @since 0.5-stable
      */
     private function debug($message)
     {
+        if (($len = strlen($message)) > self::DEBUG_LINE_LENGTH) {
+            $diff    = $len - self::DEBUG_LINE_LENGTH;
+            $message = substr($message, 0, self::DEBUG_LINE_LENGTH)
+                . "... [truncated $diff bytes]";
+        }
+
         if ($this->resourceid) {
             $message = sprintf('[%s] %s', $this->resourceid, $message);
         }

--
Gitblit v1.9.1