From 4bf322d256d0460a2df5c03b1e173f6ccd9a6eb3 Mon Sep 17 00:00:00 2001
From: Dennis1993 <marvin-wegener@outlook.com>
Date: Fri, 26 Jul 2013 05:19:16 -0400
Subject: [PATCH] Update addressbook.html

---
 program/lib/Roundcube/rcube_imap_generic.php |  373 ++++++++++++++++++++++++++++++++---------------------
 1 files changed, 226 insertions(+), 147 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 112e913..920c718 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,19 +752,23 @@
         }
 
         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
         $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
 
         if (!$this->fp) {
+            if (!$errstr) {
+                $errstr = "Unknown reason (fsockopen() function disabled?)";
+            }
             $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s", $host, $this->prefs['port'], $errstr));
             return false;
         }
 
-        if ($this->prefs['timeout'] > 0)
+        if ($this->prefs['timeout'] > 0) {
             stream_set_timeout($this->fp, $this->prefs['timeout']);
+        }
 
         $line = trim(fgets($this->fp, 8192));
 
@@ -902,7 +912,7 @@
      */
     function closeConnection()
     {
-        if ($this->putLine($this->nextTag() . ' LOGOUT')) {
+        if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) {
             $this->readReply();
         }
 
@@ -1061,8 +1071,8 @@
     /**
      * Executes EXPUNGE command
      *
-     * @param string $mailbox  Mailbox name
-     * @param string $messages Message UIDs to expunge
+     * @param string       $mailbox  Mailbox name
+     * @param string|array $messages Message UIDs to expunge
      *
      * @return boolean True on success, False on error
      */
@@ -1073,17 +1083,20 @@
         }
 
         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;
         }
 
         // Clear internal status cache
         unset($this->data['STATUS:'.$mailbox]);
 
-        if ($messages)
-            $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
-        else
+        if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
+            $messages = self::compressMessageSet($messages);
+            $result   = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
+        }
+        else {
             $result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
+        }
 
         if ($result == self::ERROR_OK) {
             $this->selected = null; // state has changed, need to reselect
@@ -1307,6 +1320,11 @@
                 if ($cmd == 'LIST' || $cmd == 'LSUB') {
                     list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
 
+                    // Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
+                    if ($delim) {
+                        $mailbox = rtrim($mailbox, $delim);
+                    }
+
                     // Add to result array
                     if (!$lstatus) {
                         $folders[] = $mailbox;
@@ -1315,9 +1333,8 @@
                         $folders[$mailbox] = array();
                     }
 
-                    // store LSUB options only if not empty, this way
-                    // we can detect a situation when LIST doesn't return specified folder
-                    if (!empty($opts) || $cmd == 'LIST') {
+                    // store folder options
+                    if ($cmd == 'LIST') {
                         // Add to options array
                         if (empty($this->data['LIST'][$mailbox]))
                             $this->data['LIST'][$mailbox] = $opts;
@@ -1549,11 +1566,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;
@@ -1640,7 +1658,6 @@
         }
 
         if (!empty($criteria)) {
-            $modseq = stripos($criteria, 'MODSEQ') !== false;
             $params .= ($params ? ' ' : '') . $criteria;
         }
         else {
@@ -1779,7 +1796,6 @@
                 if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
                     $flags = explode(' ', strtoupper($matches[1]));
                     if (in_array('\\DELETED', $flags)) {
-                        $deleted[$id] = $id;
                         continue;
                     }
                 }
@@ -1924,7 +1940,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;
         }
 
@@ -1971,7 +1987,6 @@
 
     /**
      * Moves message(s) from one folder to another.
-     * Original message(s) will be marked as deleted.
      *
      * @param string|array  $messages  Message UID(s)
      * @param string        $from      Mailbox name
@@ -1986,19 +2001,45 @@
         }
 
         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;
         }
 
-        $r = $this->copy($messages, $from, $to);
+        // use MOVE command (RFC 6851)
+        if ($this->hasCapability('MOVE')) {
+            // Clear last COPYUID data
+            unset($this->data['COPYUID']);
 
-        if ($r) {
+            // Clear internal status cache
+            unset($this->data['STATUS:'.$to]);
+            unset($this->data['STATUS:'.$from]);
+
+            $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
+        $result = $this->copy($messages, $from, $to);
+
+        if ($result) {
             // Clear internal status cache
             unset($this->data['STATUS:'.$from]);
 
-            return $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);
+            }
         }
-        return $r;
+
+        return $result;
     }
 
     /**
@@ -2121,21 +2162,25 @@
                     else if ($name == 'RFC822') {
                         $result[$id]->body = $value;
                     }
-                    else if ($name == 'BODY') {
-                        $body = $this->tokenizeResponse($line, 1);
-                        if ($value[0] == 'HEADER.FIELDS')
-                            $headers = $body;
-                        else if (!empty($value))
-                            $result[$id]->bodypart[$value[0]] = $body;
+                    else if (stripos($name, 'BODY[') === 0) {
+                        $name = str_replace(']', '', substr($name, 5));
+
+                        if ($name == 'HEADER.FIELDS') {
+                            // skip ']' after headers list
+                            $this->tokenizeResponse($line, 1);
+                            $headers = $this->tokenizeResponse($line, 1);
+                        }
+                        else if (strlen($name))
+                            $result[$id]->bodypart[$name] = $value;
                         else
-                            $result[$id]->body = $body;
+                            $result[$id]->body = $value;
                     }
                 }
 
                 // 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 {
@@ -2143,7 +2188,7 @@
                         }
                     }
 
-                    while (list($lines_key, $str) = each($lines)) {
+                    foreach ($lines as $str) {
                         list($field, $string) = explode(':', $str, 2);
 
                         $field  = strtolower($field);
@@ -2228,24 +2273,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);
         }
@@ -2409,6 +2483,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)) {
@@ -2428,18 +2503,26 @@
                 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])) {
+                        $result = $tokens[$i+1];
+                        $found  = true;
+                        break;
+                    }
+                }
 
                 if ($result !== false) {
                     if ($mode == 1) {
@@ -2457,8 +2540,13 @@
             else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
                 $bytes = (int) $m[1];
                 $prev  = '';
+                $found = true;
 
-                while ($bytes > 0) {
+                // empty body
+                if (!$bytes) {
+                    $result = '';
+                }
+                else while ($bytes > 0) {
                     $line = $this->readLine(8192);
 
                     if ($line === NULL) {
@@ -2540,11 +2628,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
      */
@@ -2558,13 +2646,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;
         }
@@ -2589,7 +2692,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;
             }
 
@@ -2628,94 +2756,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);
     }
 
     /**
@@ -2933,7 +2990,7 @@
         }
 
         foreach ($entries as $name => $value) {
-            $entries[$name] = $this->escape($name) . ' ' . $this->escape($value);
+            $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true);
         }
 
         $entries = implode(' ', $entries);
@@ -3430,25 +3487,24 @@
 
             // Parenthesized list
             case '(':
-            case '[':
                 $str = substr($str, 1);
                 $result[] = self::tokenizeResponse($str);
                 break;
             case ')':
-            case ']':
                 $str = substr($str, 1);
                 return $result;
                 break;
 
-            // String atom, number, NIL, *, %
+            // String atom, number, astring, NIL, *, %
             default:
                 // empty string
                 if ($str === '' || $str === null) {
                     break 2;
                 }
 
-                // excluded chars: SP, CTL, ), [, ]
-                if (preg_match('/^([^\x00-\x20\x29\x5B\x5D\x7F]+)/', $str, $m)) {
+                // 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];
                     $str = substr($str, strlen($m[1]));
                 }
@@ -3465,7 +3521,7 @@
 
         if (is_array($element)) {
             reset($element);
-            while (list($key, $value) = each($element)) {
+            foreach ($element as $value) {
                 $string .= ' ' . self::r_implode($value);
             }
         }
@@ -3493,7 +3549,7 @@
             // if less than 255 bytes long, let's not bother
             if (!$force && strlen($messages)<255) {
                 return $messages;
-           }
+            }
 
             // see if it's already been compressed
             if (strpos($messages, ':') !== false) {
@@ -3601,8 +3657,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
@@ -3626,6 +3694,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;
@@ -3672,9 +3744,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)
     {
@@ -3685,12 +3758,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