]> git.donarmstrong.com Git - roundcube.git/blobdiff - program/include/rcube_imap.php
Imported Debian patch 0.5.2+dfsg-1
[roundcube.git] / program / include / rcube_imap.php
index e22bbfc579a99956fd91d7008a96d916e93c7ee3..9a09180989a7a8d94ef8d84dd0d3d40a6cfcae3f 100644 (file)
@@ -16,7 +16,7 @@
  | Author: Aleksander Machniak <alec@alec.pl>                            |
  +-----------------------------------------------------------------------+
 
- $Id: rcube_imap.php 4389 2011-01-04 11:16:54Z alec $
+ $Id: rcube_imap.php 4643 2011-04-11 12:24:00Z alec $
 
 */
 
@@ -148,9 +148,18 @@ class rcube_imap
 
         $this->options['port'] = $port;
 
-        if ($this->options['debug'])
+        if ($this->options['debug']) {
             $this->conn->setDebug(true, array($this, 'debug_handler'));
 
+            $this->options['ident'] = array(
+                'name' => 'Roundcube Webmail',
+                'version' => RCMAIL_VERSION,
+                'php' => PHP_VERSION,
+                'os' => PHP_OS,
+                'command' => $_SERVER['REQUEST_URI'],
+            );
+        }
+
         $attempt = 0;
         do {
             $data = rcmail::get_instance()->plugins->exec_hook('imap_connect',
@@ -2053,7 +2062,7 @@ class rcube_imap
                 return false;
         }
 
-        $struct = &$this->_structure_part($structure);
+        $struct = &$this->_structure_part($structure, 0, '', $headers);
         $struct->headers = get_object_vars($headers);
 
         // don't trust given content-type
@@ -2185,6 +2194,11 @@ class rcube_imap
                 $struct->charset = $struct->ctype_parameters['charset'];
         }
 
+        // #1487700: workaround for lack of charset in malformed structure
+        if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) {
+            $struct->charset = $mime_headers->charset;
+        }
+
         // read content encoding
         if (!empty($part[5]) && $part[5]!='NIL') {
             $struct->encoding = strtolower($part[5]);
@@ -2233,7 +2247,11 @@ class rcube_imap
                 $mime_headers = $this->conn->fetchPartHeader(
                     $this->mailbox, $this->_msg_id, false, $struct->mime_id);
             }
-            $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers;
+
+            if (is_string($mime_headers))
+                $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers;
+            else if (is_object($mime_headers))
+                $struct->headers = get_object_vars($mime_headers) + $struct->headers;
 
             // get real content-type of message/rfc822
             if ($struct->mimetype == 'message/rfc822') {
@@ -2408,9 +2426,11 @@ class rcube_imap
      * @param  rcube_message_part $o_part Part object created by get_structure()
      * @param  mixed              $print  True to print part, ressource to write part contents in
      * @param  resource           $fp     File pointer to save the message part
+     * @param  boolean            $skip_charset_conv Disables charset conversion
+     *
      * @return string Message/part body if not printed
      */
-    function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL)
+    function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
     {
         // get part encoding if not provided
         if (!is_object($o_part)) {
@@ -2440,10 +2460,13 @@ class rcube_imap
             return true;
         }
 
-        // convert charset (if text or message part) and part's charset is specified
-        if ($body && $o_part->charset
-            && preg_match('/^(text|message)$/', $o_part->ctype_primary)
+        // convert charset (if text or message part)
+        if ($body && !$skip_charset_conv &&
+            preg_match('/^(text|message)$/', $o_part->ctype_primary)
         ) {
+            if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
+                $o_part->charset = $this->default_charset;
+            }
             $body = rcube_charset_convert($body, $o_part->charset);
         }
 
@@ -4326,80 +4349,95 @@ class rcube_imap
      */
     public static function decode_mime_string($input, $fallback=null)
     {
-        // Initialize variable
-        $out = '';
+        if (!empty($fallback)) {
+            $default_charset = $fallback;
+        }
+        else {
+            $default_charset = rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1');
+        }
 
-        // Iterate instead of recursing, this way if there are too many values we don't have stack overflows
         // rfc: all line breaks or other characters not found
         // in the Base64 Alphabet must be ignored by decoding software
         // delete all blanks between MIME-lines, differently we can
         // receive unnecessary blanks and broken utf-8 symbols
         $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
 
-        // Check if there is stuff to decode
-        if (strpos($input, '=?') !== false) {
-            // Loop through the string to decode all occurences of =? ?= into the variable $out
-            while(($pos = strpos($input, '=?')) !== false) {
-                // Append everything that is before the text to be decoded
-                $out .= substr($input, 0, $pos);
+        // encoded-word regexp
+        $re = '/=\?([^?]+)\?([BbQq])\?([^?\n]*)\?=/';
 
-                // Get the location of the text to decode
-                $end_cs_pos = strpos($input, "?", $pos+2);
-                $end_en_pos = strpos($input, "?", $end_cs_pos+1);
-                $end_pos = strpos($input, "?=", $end_en_pos+1);
+        // Find all RFC2047's encoded words
+        if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
+            // Initialize variables
+            $tmp   = array();
+            $out   = '';
+            $start = 0;
 
-                // Extract the encoded string
-                $encstr = substr($input, $pos+2, ($end_pos-$pos-2));
-                // Extract the remaining string
-                $input = substr($input, $end_pos+2);
-
-                // Decode the string fragement
-                $out .= rcube_imap::_decode_mime_string_part($encstr);
-            }
+            foreach ($matches as $idx => $m) {
+                $pos      = $m[0][1];
+                $charset  = $m[1][0];
+                $encoding = $m[2][0];
+                $text     = $m[3][0];
+                $length   = strlen($m[0][0]);
 
-            // Deocde the rest (if any)
-            if (strlen($input) != 0)
-                $out .= rcube_imap::decode_mime_string($input, $fallback);
-
-            // return the results
-            return $out;
-        }
+                // Append everything that is before the text to be decoded
+                if ($start != $pos) {
+                    $substr = substr($input, $start, $pos-$start);
+                    $out   .= rcube_charset_convert($substr, $default_charset);
+                    $start  = $pos;
+                }
+                $start += $length;
+
+                // Per RFC2047, each string part "MUST represent an integral number
+                // of characters . A multi-octet character may not be split across
+                // adjacent encoded-words." However, some mailers break this, so we
+                // try to handle characters spanned across parts anyway by iterating
+                // through and aggregating sequential encoded parts with the same
+                // character set and encoding, then perform the decoding on the
+                // aggregation as a whole.
+
+                $tmp[] = $text;
+                if ($next_match = $matches[$idx+1]) {
+                    if ($next_match[0][1] == $start
+                        && $next_match[1][0] == $charset
+                        && $next_match[2][0] == $encoding
+                    ) {
+                        continue;
+                    }
+                }
 
-        // no encoding information, use fallback
-        return rcube_charset_convert($input,
-            !empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
-    }
+                $count = count($tmp);
+                $text  = '';
 
+                // Decode and join encoded-word's chunks
+                if ($encoding == 'B' || $encoding == 'b') {
+                    // base64 must be decoded a segment at a time
+                    for ($i=0; $i<$count; $i++)
+                        $text .= base64_decode($tmp[$i]);
+                }
+                else { //if ($encoding == 'Q' || $encoding == 'q') {
+                    // quoted printable can be combined and processed at once
+                    for ($i=0; $i<$count; $i++)
+                        $text .= $tmp[$i];
 
-    /**
-     * Decode a part of a mime-encoded string
-     *
-     * @param string $str String to decode
-     * @return string Decoded string
-     * @access private
-     */
-    private function _decode_mime_string_part($str)
-    {
-        $a = explode('?', $str);
-        $count = count($a);
+                    $text = str_replace('_', ' ', $text);
+                    $text = quoted_printable_decode($text);
+                }
 
-        // should be in format "charset?encoding?base64_string"
-        if ($count >= 3) {
-            for ($i=2; $i<$count; $i++)
-                $rest .= $a[$i];
+                $out .= rcube_charset_convert($text, $charset);
+                $tmp = array();
+            }
 
-            if (($a[1]=='B') || ($a[1]=='b'))
-                $rest = base64_decode($rest);
-            else if (($a[1]=='Q') || ($a[1]=='q')) {
-                $rest = str_replace('_', ' ', $rest);
-                $rest = quoted_printable_decode($rest);
+            // add the last part of the input string
+            if ($start != strlen($input)) {
+                $out .= rcube_charset_convert(substr($input, $start), $default_charset);
             }
 
-            return rcube_charset_convert($rest, $a[0]);
+            // return the results
+            return $out;
         }
 
-        // we dont' know what to do with this
-        return $str;
+        // no encoding information, use fallback
+        return rcube_charset_convert($input, $default_charset);
     }
 
 
@@ -4691,10 +4729,13 @@ class rcube_imap
     private function _parse_address_list($str, $decode=true)
     {
         // remove any newlines and carriage returns before
-        $a = rcube_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
+        $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
+
+        // extract list items, remove comments
+        $str = self::explode_header_string(',;', $str, true);
         $result = array();
 
-        foreach ($a as $key => $val) {
+        foreach ($str as $key => $val) {
             $name    = '';
             $address = '';
             $val     = trim($val);
@@ -4735,6 +4776,81 @@ class rcube_imap
     }
 
 
+    /**
+     * Explodes header (e.g. address-list) string into array of strings
+     * using specified separator characters with proper handling
+     * of quoted-strings and comments (RFC2822)
+     *
+     * @param string $separator       String containing separator characters
+     * @param string $str             Header string
+     * @param bool   $remove_comments Enable to remove comments
+     *
+     * @return array Header items
+     */
+    static function explode_header_string($separator, $str, $remove_comments=false)
+    {
+        $length  = strlen($str);
+        $result  = array();
+        $quoted  = false;
+        $comment = 0;
+        $out     = '';
+
+        for ($i=0; $i<$length; $i++) {
+            // we're inside a quoted string
+            if ($quoted) {
+                if ($str[$i] == '"') {
+                    $quoted = false;
+                }
+                else if ($str[$i] == '\\') {
+                    if ($comment <= 0) {
+                        $out .= '\\';
+                    }
+                    $i++;
+                }
+            }
+            // we're inside a comment string
+            else if ($comment > 0) {
+                    if ($str[$i] == ')') {
+                        $comment--;
+                    }
+                    else if ($str[$i] == '(') {
+                        $comment++;
+                    }
+                    else if ($str[$i] == '\\') {
+                        $i++;
+                    }
+                    continue;
+            }
+            // separator, add to result array
+            else if (strpos($separator, $str[$i]) !== false) {
+                    if ($out) {
+                        $result[] = $out;
+                    }
+                    $out = '';
+                    continue;
+            }
+            // start of quoted string
+            else if ($str[$i] == '"') {
+                    $quoted = true;
+            }
+            // start of comment
+            else if ($remove_comments && $str[$i] == '(') {
+                    $comment++;
+            }
+
+            if ($comment <= 0) {
+                $out .= $str[$i];
+            }
+        }
+
+        if ($out && $comment <= 0) {
+            $result[] = $out;
+        }
+
+        return $result;
+    }
+
+
     /**
      * This is our own debug handler for the IMAP connection
      * @access public