diff options
Diffstat (limited to 'program/lib')
-rw-r--r-- | program/lib/Roundcube/rcube.php | 16 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_image.php | 2 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_imap_cache.php | 10 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_imap_generic.php | 87 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_message.php | 270 |
5 files changed, 287 insertions, 98 deletions
diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 3ab650cb1..03f49637c 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -842,6 +842,7 @@ class rcube * upon decryption; see http://php.net/mcrypt_generic#68082 */ $clear = pack("a*H2", $clear, "80"); + $ckey = $this->config->get_crypto_key($key); if (function_exists('openssl_encrypt')) { $method = 'DES-EDE3-CBC'; @@ -853,7 +854,7 @@ class rcube ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) ) { $iv = $this->create_iv(mcrypt_enc_get_iv_size($td)); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + mcrypt_generic_init($td, $ckey, $iv); $cipher = $iv . mcrypt_generic($td, $clear); mcrypt_generic_deinit($td); mcrypt_module_close($td); @@ -864,7 +865,7 @@ class rcube if (function_exists('des')) { $des_iv_size = 8; $iv = $this->create_iv($des_iv_size); - $cipher = $iv . des($this->config->get_crypto_key($key), $clear, 1, 1, $iv); + $cipher = $iv . des($ckey, $clear, 1, 1, $iv); } else { self::raise_error(array( @@ -895,6 +896,7 @@ class rcube } $cipher = $base64 ? base64_decode($cipher) : $cipher; + $ckey = $this->config->get_crypto_key($key); if (function_exists('openssl_decrypt')) { $method = 'DES-EDE3-CBC'; @@ -914,7 +916,7 @@ class rcube ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_CBC, "")) ) { $iv_size = mcrypt_enc_get_iv_size($td); - $iv = substr($cipher, 0, $iv_size); + $iv = substr($cipher, 0, $iv_size); // session corruption? (#1485970) if (strlen($iv) < $iv_size) { @@ -922,7 +924,7 @@ class rcube } $cipher = substr($cipher, $iv_size); - mcrypt_generic_init($td, $this->config->get_crypto_key($key), $iv); + mcrypt_generic_init($td, $ckey, $iv); $clear = mdecrypt_generic($td, $cipher); mcrypt_generic_deinit($td); mcrypt_module_close($td); @@ -932,15 +934,15 @@ class rcube if (function_exists('des')) { $des_iv_size = 8; - $iv = substr($cipher, 0, $des_iv_size); + $iv = substr($cipher, 0, $des_iv_size); $cipher = substr($cipher, $des_iv_size); - $clear = des($this->config->get_crypto_key($key), $cipher, 0, 1, $iv); + $clear = des($ckey, $cipher, 0, 1, $iv); } else { self::raise_error(array( 'code' => 500, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, - 'message' => "Could not perform decryption; make sure Mcrypt is installed or lib/des.inc is available" + 'message' => "Could not perform decryption; make sure OpenSSL or Mcrypt or lib/des.inc is available" ), true, true); } } diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index d0d0c7437..77ce1647d 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -229,7 +229,7 @@ class rcube_image $image = $new_image; // fix rotation of image if EXIF data exists and specifies rotation (GD strips the EXIF data) - if ($this->image_file && function_exists('exif_read_data')) { + if ($this->image_file && $type == 'jpg' && function_exists('exif_read_data')) { $exif = exif_read_data($this->image_file); if ($exif && $exif['Orientation']) { switch ($exif['Orientation']) { diff --git a/program/lib/Roundcube/rcube_imap_cache.php b/program/lib/Roundcube/rcube_imap_cache.php index 95498e364..81df07641 100644 --- a/program/lib/Roundcube/rcube_imap_cache.php +++ b/program/lib/Roundcube/rcube_imap_cache.php @@ -1245,13 +1245,15 @@ class rcube_imap_cache private function message_object_prepare(&$msg, &$size = 0) { // Remove body too big - if ($msg->body && ($length = strlen($msg->body))) { - $size += $length; + if (isset($msg->body)) { + $length = strlen($msg->body); - if ($size > $this->threshold * 1024) { - $size -= $length; + if ($msg->body_modified || $size + $length > $this->threshold * 1024) { unset($msg->body); } + else { + $size += $length; + } } // Fix mimetype which might be broken by some code when message is displayed diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 734a9311e..d78b526dd 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2569,52 +2569,63 @@ class rcube_imap_generic return false; } - switch ($encoding) { - case 'base64': - $mode = 1; - break; - case 'quoted-printable': - $mode = 2; - break; - case 'x-uuencode': - case 'x-uue': - case 'uue': - case 'uuencode': - $mode = 3; - break; - default: - $mode = 0; - } - - // Use BINARY extension when possible (and safe) - $binary = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY'); - $fetch_mode = $binary ? 'BINARY' : 'BODY'; - $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; + $binary = true; - // format request - $key = $this->nextTag(); - $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; - $result = false; - $found = false; + do { + if (!$initiated) { + switch ($encoding) { + case 'base64': + $mode = 1; + break; + case 'quoted-printable': + $mode = 2; + break; + case 'x-uuencode': + case 'x-uue': + case 'uue': + case 'uuencode': + $mode = 3; + break; + default: + $mode = 0; + } - // send request - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); - return false; - } + // Use BINARY extension when possible (and safe) + $binary = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY'); + $fetch_mode = $binary ? 'BINARY' : 'BODY'; + $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)"; + $result = false; + $found = false; + $initiated = true; + + // send request + if (!$this->putLine($request)) { + $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); + return false; + } - if ($binary) { - // WARNING: Use $formatted argument with care, this may break binary data stream - $mode = -1; - } + if ($binary) { + // WARNING: Use $formatted argument with care, this may break binary data stream + $mode = -1; + } + } - do { $line = trim($this->readLine(1024)); if (!$line) { break; } + // handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request + if ($binary && !$found && preg_match('/^' . $key . ' NO \[UNKNOWN-CTE\]/i', $line)) { + $binary = $initiated = false; + continue; + } + // skip irrelevant untagged responses (we have a result already) if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { continue; @@ -2675,7 +2686,7 @@ class rcube_imap_generic // BASE64 if ($mode == 1) { - $line = rtrim($line, "\t\r\n\0\x0B"); + $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line); // create chunks with proper length for base64 decoding $line = $prev.$line; $length = strlen($line); @@ -2720,7 +2731,7 @@ class rcube_imap_generic } } } - } while (!$this->startsWith($line, $key, true)); + } while (!$this->startsWith($line, $key, true) || !$initiated); if ($result !== false) { if ($file) { diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index a648ae722..0d33ba411 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -3,7 +3,7 @@ /* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | - | Copyright (C) 2008-2010, The Roundcube Dev Team | + | Copyright (C) 2008-2014, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -61,6 +61,8 @@ class rcube_message public $sender = null; public $is_safe = false; + const BODY_MAX_SIZE = 1048576; // 1MB + /** * __construct @@ -176,6 +178,7 @@ class rcube_message * @param boolean $formatted Enables formatting of text/* parts bodies * * @return string Part content + * @deprecated */ public function get_part_content($mime_id, $fp = null, $skip_charset_conv = false, $max_bytes = 0, $formatted = true) { @@ -198,6 +201,125 @@ class rcube_message /** + * Get content of a specific part of this message + * + * @param string $mime_id Part ID + * @param boolean $formatted Enables formatting of text/* parts bodies + * @param int $max_bytes Only return/read this number of bytes + * @param mixed $mode NULL to return a string, -1 to print body + * or file pointer to save the body into + * + * @return string|bool Part content or operation status + */ + public function get_part_body($mime_id, $formatted = false, $max_bytes = 0, $mode = null) + { + if (!($part = $this->mime_parts[$mime_id])) { + return; + } + + // only text parts can be formatted + $formatted = $formatted && $part->ctype_primary == 'text'; + + // part body not fetched yet... save in memory if it's small enough + if ($part->body === null && is_numeric($mime_id) && $part->size < self::BODY_MAX_SIZE) { + // Warning: body here should be always unformatted + $part->body = $this->storage->get_message_part($this->uid, $mime_id, $part, + null, null, true, 0, false); + } + + // body stored in message structure (winmail/inline-uuencode) + if ($part->body !== null || $part->encoding == 'stream') { + $body = $part->body; + + if ($formatted && $body) { + $body = self::format_part_body($body, $part, $this->headers->charset); + } + + if ($max_bytes && strlen($body) > $max_bytes) { + $body = substr($body, 0, $max_bytes); + } + + if (is_resource($mode)) { + if ($body !== false) { + fwrite($mode, $body); + rewind($mode); + } + + return $body !== false; + } + + if ($mode === -1) { + if ($body !== false) { + print($body); + } + + return $body !== false; + } + + return $body; + } + + // get the body from IMAP + $this->storage->set_folder($this->folder); + + $body = $this->storage->get_message_part($this->uid, $mime_id, $part, + $mode === -1, is_resource($mode) ? $mode : null, !$formatted, $max_bytes, $formatted); + + if (!$mode && $body && $formatted) { + $body = self::format_part_body($body, $part, $this->headers->charset); + } + + if (is_resource($mode)) { + rewind($mode); + return $body !== false; + } + + return $body; + } + + + /** + * Format text message part for display + * + * @param string $body Part body + * @param rcube_message_part $part Part object + * @param string $default_charset Fallback charset if part charset is not specified + * + * @return string Formatted body + */ + public static function format_part_body($body, $part, $default_charset = null) + { + // remove useless characters + $body = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $body); + + // remove NULL characters if any (#1486189) + if (strpos($body, "\x00") !== false) { + $body = str_replace("\x00", '', $body); + } + + // detect charset... + if (!$part->charset || strtoupper($part->charset) == 'US-ASCII') { + // try to extract charset information from HTML meta tag (#1488125) + if ($part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m)) { + $part->charset = strtoupper($m[1]); + } + else if ($default_charset) { + $part->charset = $default_charset; + } + else { + $rcube = rcube::get_instance(); + $part->charset = $rcube->config->get('default_charset', RCUBE_CHARSET); + } + } + + // ..convert charset encoding + $body = rcube_charset::convert($body, $part->charset); + + return $body; + } + + + /** * Determine if the message contains a HTML part. This must to be * a real part not an attachment (or its part) * @@ -293,7 +415,7 @@ class rcube_message // check all message parts foreach ($this->mime_parts as $pid => $part) { if ($part->mimetype == 'text/html') { - return $this->get_part_content($pid); + return $this->get_part_body($pid, true); } } } @@ -314,10 +436,10 @@ class rcube_message // check all message parts foreach ($this->mime_parts as $mime_id => $part) { if ($part->mimetype == 'text/plain') { - return $this->get_part_content($mime_id); + return $this->get_part_body($mime_id, true); } else if ($part->mimetype == 'text/html') { - $out = $this->get_part_content($mime_id); + $out = $this->get_part_body($mime_id, true); // create instance of html2text class $txt = new rcube_html2text($out); @@ -371,7 +493,7 @@ class rcube_message // parse headers from message/rfc822 part if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) { - list($headers, ) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768)); + list($headers, ) = explode("\r\n\r\n", $this->get_part_body($structure->mime_id, false, 32768)); $structure->headers = rcube_mime::parse_headers($headers); } } @@ -725,20 +847,18 @@ class rcube_message */ function tnef_decode(&$part) { - // @TODO: attachment may be huge, hadle it via file - if (!isset($part->body)) { - $this->storage->set_folder($this->folder); - $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part); - } + // @TODO: attachment may be huge, handle body via file + $body = $this->get_part_body($part->mime_id); + $tnef = new tnef_decoder; + $tnef_arr = $tnef->decompress($body); + $parts = array(); - $parts = array(); - $tnef = new tnef_decoder; - $tnef_arr = $tnef->decompress($part->body); + unset($body); foreach ($tnef_arr as $pid => $winatt) { $tpart = new rcube_message_part; - $tpart->filename = trim($winatt['name']); + $tpart->filename = $this->fix_attachment_name(trim($winatt['name']), $part); $tpart->encoding = 'stream'; $tpart->ctype_primary = trim(strtolower($winatt['type'])); $tpart->ctype_secondary = trim(strtolower($winatt['subtype'])); @@ -763,55 +883,109 @@ class rcube_message */ function uu_decode(&$part) { - // @TODO: messages may be huge, hadle body via file - if (!isset($part->body)) { - $this->storage->set_folder($this->folder); - $part->body = $this->storage->get_message_part($this->uid, $part->mime_id, $part); - } + // @TODO: messages may be huge, handle body via file + $part->body = $this->get_part_body($part->mime_id); + $parts = array(); + $pid = 0; - $parts = array(); // FIXME: line length is max.65? - $uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n/s'; + $uu_regexp_begin = '/begin [0-7]{3,4} ([^\r\n]+)\r?\n/s'; + $uu_regexp_end = '/`\r?\nend((\r?\n)|($))/s'; - if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) { - // update message content-type - $part->ctype_primary = 'multipart'; - $part->ctype_secondary = 'mixed'; - $part->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; - $uu_endstring = "`\nend\n"; + while (preg_match($uu_regexp_begin, $part->body, $matches, PREG_OFFSET_CAPTURE)) { + $startpos = $matches[0][1]; + + if (!preg_match($uu_regexp_end, $part->body, $m, PREG_OFFSET_CAPTURE, $startpos)) { + break; + } + + $endpos = $m[0][1]; + $begin_len = strlen($matches[0][0]); + $end_len = strlen($m[0][0]); + + // extract attachment body + $filebody = substr($part->body, $startpos + $begin_len, $endpos - $startpos - $begin_len - 1); + $filebody = str_replace("\r\n", "\n", $filebody); + + // remove attachment body from the message body + $part->body = substr_replace($part->body, '', $startpos, $endpos + $end_len - $startpos); + // mark body as modified so it will not be cached by rcube_imap_cache + $part->body_modified = true; // add attachments to the structure - foreach ($matches as $pid => $att) { - $startpos = strpos($part->body, $att[1]) + strlen($att[1]) + 1; // "\n" - $endpos = strpos($part->body, $uu_endstring); - $filebody = substr($part->body, $startpos, $endpos-$startpos); + $uupart = new rcube_message_part; + $uupart->filename = trim($matches[1][0]); + $uupart->encoding = 'stream'; + $uupart->body = convert_uudecode($filebody); + $uupart->size = strlen($uupart->body); + $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid; + + $ctype = rcube_mime::file_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); + $uupart->mimetype = $ctype; + list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype); + + $parts[] = $uupart; + $pid++; + } - // remove attachments bodies from the message body - $part->body = substr_replace($part->body, "", $startpos, $endpos+strlen($uu_endstring)-$startpos); + return $parts; + } - $uupart = new rcube_message_part; + /** + * Fix attachment name encoding if needed/possible + */ + protected function fix_attachment_name($name, $part) + { + if ($name == rcube_charset::clean($name)) { + return $name; + } - $uupart->filename = trim($att[1]); - $uupart->encoding = 'stream'; - $uupart->body = convert_uudecode($filebody); - $uupart->size = strlen($uupart->body); - $uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid; + // find charset from part or its parent(s) + if ($part->charset) { + $charsets[] = $part->charset; + } + else { + // check first part (common case) + $n = strpos($part->mime_id, '.') ? preg_replace('/\.[0-9]+$/', '', $part->mime_id) . '.1' : 1; + if (($_part = $this->mime_parts[$n]) && $_part->charset) { + $charsets[] = $_part->charset; + } - $ctype = rcube_mime::file_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true); - $uupart->mimetype = $ctype; - list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype); + // check parents' charset + $items = explode('.', $part->mime_id); + for ($i = count($items)-1; $i > 0; $i--) { + $last = array_pop($items); + $parent = $this->mime_parts[join('.', $items)]; - $parts[] = $uupart; - unset($matches[$pid]); + if ($parent && $parent->charset) { + $charsets[] = $parent->charset; + } } + } - // remove attachments bodies from the message body - $part->body = preg_replace($uu_regexp, '', $part->body); + if ($this->headers->charset) { + $charsets[] = $this->headers->charset; } - return $parts; - } + if (empty($charsets)) { + $rcube = rcube::get_instance(); + $charsets[] = rcube_charset::detect($name, $rcube->config->get('default_charset', RCUBE_CHARSET)); + } + + foreach (array_unique($charsets) as $charset) { + $_name = rcube_charset::convert($name, $charset); + + if ($_name == rcube_charset::clean($_name)) { + if (!$part->charset) { + $part->charset = $charset; + } + return $_name; + } + } + + return $name; + } /** * Deprecated methods (to be removed) |