X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=program%2Finclude%2Frcube_imap.php;h=9a09180989a7a8d94ef8d84dd0d3d40a6cfcae3f;hb=511160353722ebec8c258ddd6fa963ec8851eede;hp=e22bbfc579a99956fd91d7008a96d916e93c7ee3;hpb=e4e7e688fab1ecd11476957e33ed709ac6326538;p=roundcube.git diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php index e22bbfc..9a09180 100644 --- a/program/include/rcube_imap.php +++ b/program/include/rcube_imap.php @@ -16,7 +16,7 @@ | Author: Aleksander Machniak | +-----------------------------------------------------------------------+ - $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