From c1a0b072424568957eb686a049d8419e4d96c476 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Thu, 29 Aug 2013 03:23:52 -0400
Subject: [PATCH] Fix setting of Junk and NonJunk flags by markasjunk plugin (#1489285) Added possibility to register flag mappings by a plugin.

---
 program/lib/Roundcube/rcube_imap_generic.php |  384 +++++++++++++++++++++++++++++++++---------------------
 1 files changed, 232 insertions(+), 152 deletions(-)

diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
index 70fd6eb..1b28c3b 100644
--- a/program/lib/Roundcube/rcube_imap_generic.php
+++ b/program/lib/Roundcube/rcube_imap_generic.php
@@ -2,8 +2,6 @@
 
 /**
  +-----------------------------------------------------------------------+
- | program/include/rcube_imap_generic.php                                |
- |                                                                       |
  | This file is part of the Roundcube Webmail client                     |
  | Copyright (C) 2005-2012, The Roundcube Dev Team                       |
  | Copyright (C) 2011-2012, Kolab Systems AG                             |
@@ -19,13 +17,11 @@
  |   functionality built-in.                                             |
  |                                                                       |
  |   Based on Iloha IMAP Library. See http://ilohamail.org/ for details  |
- |                                                                       |
  +-----------------------------------------------------------------------+
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  | Author: Ryo Chijiiwa <Ryo@IlohaMail.org>                              |
  +-----------------------------------------------------------------------+
 */
-
 
 /**
  * PHP based wrapper class to connect to an IMAP server
@@ -706,18 +702,11 @@
      */
     function connect($host, $user, $password, $options=null)
     {
-        // set options
-        if (is_array($options)) {
-            $this->prefs = $options;
-        }
-        // set auth method
-        if (!empty($this->prefs['auth_type'])) {
-            $auth_method = strtoupper($this->prefs['auth_type']);
-        } else {
-            $auth_method = 'CHECK';
-        }
+        // configure
+        $this->set_prefs($options);
 
-        $result = false;
+        $auth_method = $this->prefs['auth_type'];
+        $result      = false;
 
         // initialize connection
         $this->error    = '';
@@ -750,19 +739,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));
 
@@ -892,6 +885,31 @@
     }
 
     /**
+     * Initializes environment
+     */
+    protected function set_prefs($prefs)
+    {
+        // set preferences
+        if (is_array($prefs)) {
+            $this->prefs = $prefs;
+        }
+
+        // set auth method
+        if (!empty($this->prefs['auth_type'])) {
+            $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']);
+        }
+        else {
+            $this->prefs['auth_type'] = 'CHECK';
+        }
+
+        // additional message flags
+        if (!empty($this->prefs['message_flags'])) {
+            $this->flags = array_merge($this->flags, $this->prefs['message_flags']);
+            unset($this->prefs['message_flags']);
+        }
+    }
+
+    /**
      * Checks connection status
      *
      * @return bool True if connection is active and user is logged in, False otherwise.
@@ -906,7 +924,7 @@
      */
     function closeConnection()
     {
-        if ($this->putLine($this->nextTag() . ' LOGOUT')) {
+        if ($this->logged && $this->putLine($this->nextTag() . ' LOGOUT')) {
             $this->readReply();
         }
 
@@ -1077,7 +1095,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;
         }
 
@@ -1310,6 +1328,11 @@
                 // * LIST (<options>) <delimiter> <mailbox>
                 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) {
@@ -1928,7 +1951,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;
         }
 
@@ -1990,7 +2013,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;
         }
 
@@ -2125,14 +2148,18 @@
                     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;
                     }
                 }
 
@@ -2206,10 +2233,13 @@
                             }
                             break;
                         default:
-                            if (strlen($field) > 2) {
-                                $result[$id]->others[$field] = $string;
+                            if (strlen($field) < 3) {
+                                break;
                             }
-                            break;
+                            if ($result[$id]->others[$field]) {
+                                $string = array_merge((array)$result[$id]->others[$field], (array)$string);
+                            }
+                            $result[$id]->others[$field] = $string;
                         }
                     }
                 }
@@ -2217,7 +2247,6 @@
 
             // VANISHED response (QRESYNC RFC5162)
             // Sample: * VANISHED (EARLIER) 300:310,405,411
-
             else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
                 $line   = substr($line, strlen($match[0]));
                 $v_data = $this->tokenizeResponse($line, 1);
@@ -2230,24 +2259,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);
         }
@@ -2408,8 +2466,10 @@
         $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)";
+        $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)) {
@@ -2422,118 +2482,129 @@
             $mode = -1;
         }
 
-        // receive reply line
         do {
-            $line = rtrim($this->readLine(1024));
-            $a    = explode(' ', $line);
-        } while (!($end = $this->startsWith($line, $key, true)) && $a[2] != 'FETCH');
+            $line = trim($this->readLine(1024));
 
-        $len    = strlen($line);
-        $result = false;
-
-        if ($a[2] != 'FETCH') {
-        }
-        // handle empty "* X FETCH ()" response
-        else if ($line[$len-1] == ')' && $line[$len-2] != '(') {
-            // one line response, get everything between first and last quotes
-            if (substr($line, -4, 3) == 'NIL') {
-                // NIL response
-                $result = '';
-            } else {
-                $from = strpos($line, '"') + 1;
-                $to   = strrpos($line, '"');
-                $len  = $to - $from;
-                $result = substr($line, $from, $len);
+            if (!$line) {
+                break;
             }
 
-            if ($mode == 1) {
-                $result = base64_decode($result);
-            }
-            else if ($mode == 2) {
-                $result = quoted_printable_decode($result);
-            }
-            else if ($mode == 3) {
-                $result = convert_uudecode($result);
+            // skip irrelevant untagged responses (we have a result already)
+            if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) {
+                continue;
             }
 
-        } else if ($line[$len-1] == '}') {
-            // multi-line request, find sizes of content and receive that many bytes
-            $from     = strpos($line, '{') + 1;
-            $to       = strrpos($line, '}');
-            $len      = $to - $from;
-            $sizeStr  = substr($line, $from, $len);
-            $bytes    = (int)$sizeStr;
-            $prev     = '';
+            $line = $m[2];
 
-            while ($bytes > 0) {
-                $line = $this->readLine(8192);
+            // handle one line response
+            if ($line[0] == '(' && substr($line, -1) == ')') {
+                // tokenize content inside brackets
+                $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
 
-                if ($line === NULL) {
-                    break;
-                }
-
-                $len = strlen($line);
-
-                if ($len > $bytes) {
-                    $line = substr($line, 0, $bytes);
-                    $len = strlen($line);
-                }
-                $bytes -= $len;
-
-                // BASE64
-                if ($mode == 1) {
-                    $line = rtrim($line, "\t\r\n\0\x0B");
-                    // create chunks with proper length for base64 decoding
-                    $line = $prev.$line;
-                    $length = strlen($line);
-                    if ($length % 4) {
-                        $length = floor($length / 4) * 4;
-                        $prev = substr($line, $length);
-                        $line = substr($line, 0, $length);
-                    }
-                    else
-                        $prev = '';
-                    $line = base64_decode($line);
-                // QUOTED-PRINTABLE
-                } else if ($mode == 2) {
-                    $line = rtrim($line, "\t\r\0\x0B");
-                    $line = quoted_printable_decode($line);
-                // UUENCODE
-                } else if ($mode == 3) {
-                    $line = rtrim($line, "\t\r\n\0\x0B");
-                    if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))
-                        continue;
-                    $line = convert_uudecode($line);
-                // default
-                } else if ($formatted) {
-                    $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
-                }
-
-                if ($file) {
-                    if (fwrite($file, $line) === false)
+                for ($i=0; $i<count($tokens); $i+=2) {
+                    if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
+                        $result = $tokens[$i+1];
+                        $found  = true;
                         break;
+                    }
                 }
-                else if ($print)
-                    echo $line;
-                else
-                    $result .= $line;
-            }
-        }
 
-        // read in anything up until last line
-        if (!$end)
-            do {
-                $line = $this->readLine(1024);
-            } while (!$this->startsWith($line, $key, true));
+                if ($result !== false) {
+                    if ($mode == 1) {
+                        $result = base64_decode($result);
+                    }
+                    else if ($mode == 2) {
+                        $result = quoted_printable_decode($result);
+                    }
+                    else if ($mode == 3) {
+                        $result = convert_uudecode($result);
+                    }
+                }
+            }
+            // response with string literal
+            else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
+                $bytes = (int) $m[1];
+                $prev  = '';
+                $found = true;
+
+                // empty body
+                if (!$bytes) {
+                    $result = '';
+                }
+                else while ($bytes > 0) {
+                    $line = $this->readLine(8192);
+
+                    if ($line === NULL) {
+                        break;
+                    }
+
+                    $len = strlen($line);
+
+                    if ($len > $bytes) {
+                        $line = substr($line, 0, $bytes);
+                        $len  = strlen($line);
+                    }
+                    $bytes -= $len;
+
+                    // BASE64
+                    if ($mode == 1) {
+                        $line = rtrim($line, "\t\r\n\0\x0B");
+                        // create chunks with proper length for base64 decoding
+                        $line = $prev.$line;
+                        $length = strlen($line);
+                        if ($length % 4) {
+                            $length = floor($length / 4) * 4;
+                            $prev = substr($line, $length);
+                            $line = substr($line, 0, $length);
+                        }
+                        else {
+                            $prev = '';
+                        }
+                        $line = base64_decode($line);
+                    }
+                    // QUOTED-PRINTABLE
+                    else if ($mode == 2) {
+                        $line = rtrim($line, "\t\r\0\x0B");
+                        $line = quoted_printable_decode($line);
+                    }
+                    // UUENCODE
+                    else if ($mode == 3) {
+                        $line = rtrim($line, "\t\r\n\0\x0B");
+                        if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line)) {
+                            continue;
+                        }
+                        $line = convert_uudecode($line);
+                    }
+                    // default
+                    else if ($formatted) {
+                        $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
+                    }
+
+                    if ($file) {
+                        if (fwrite($file, $line) === false) {
+                            break;
+                        }
+                    }
+                    else if ($print) {
+                        echo $line;
+                    }
+                    else {
+                        $result .= $line;
+                    }
+                }
+            }
+        } while (!$this->startsWith($line, $key, true));
 
         if ($result !== false) {
             if ($file) {
                 return fwrite($file, $result);
-            } else if ($print) {
+            }
+            else if ($print) {
                 echo $result;
-            } else
-                return $result;
-            return true;
+                return true;
+            }
+
+            return $result;
         }
 
         return false;
@@ -2546,10 +2617,11 @@
      * @param string $message Message content
      * @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
      */
-    function append($mailbox, &$message, $flags = array(), $date = null)
+    function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2557,8 +2629,13 @@
             return false;
         }
 
-        $message = str_replace("\r", '', $message);
-        $message = str_replace("\n", "\r\n", $message);
+        $binary       = $binary && $this->getCapability('BINARY');
+        $literal_plus = !$binary && $this->prefs['literal+'];
+
+        if (!$binary) {
+            $message = str_replace("\r", '', $message);
+            $message = str_replace("\n", "\r\n", $message);
+        }
 
         $len = strlen($message);
         if (!$len) {
@@ -2571,12 +2648,12 @@
         if (!empty($date)) {
             $request .= ' ' . $this->escape($date);
         }
-        $request .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Do not wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
@@ -2618,10 +2695,11 @@
      * @param string $headers Message headers
      * @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
      */
-    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null)
+    function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
     {
         unset($this->data['APPENDUID']);
 
@@ -2652,18 +2730,21 @@
             $len += strlen($headers) + strlen($body_separator);
         }
 
+        $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 .= ' {' . $len . ($this->prefs['literal+'] ? '+' : '') . '}';
+        $request .= ' ' . ($binary ? '~' : '') . '{' . $len . ($literal_plus ? '+' : '') . '}';
 
         // send APPEND command
         if ($this->putLine($request)) {
             // Don't wait when LITERAL+ is supported
-            if (!$this->prefs['literal+']) {
+            if (!$literal_plus) {
                 $line = $this->readReply();
 
                 if ($line[0] != '+') {
@@ -2925,7 +3006,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);
@@ -3422,25 +3503,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]));
                 }

--
Gitblit v1.9.1