diff options
28 files changed, 621 insertions, 218 deletions
@@ -49,8 +49,16 @@ CHANGELOG Roundcube Webmail - Don't remove links when html signature is converted to text (#1489621) - Fix page title when using search filter (#1490023) - Fix mbox files import +- Fix so attachment charset is set in headers of forward/draft message (#1490109) +- Fix bug where wrong charset could be used for text attachment preview page (#1490106) - Fix setting flags on servers with no PERMANENTFLAGS response (#1490087) - Fix regression in SHAA password generation in ldap driver of password plugin (#1490094) +- Fix displaying of HTML messages with absolutely positioned elements in Larry skin (#1490103) +- Fix font style display issue in HTML messages with styled <span> elements (#1490101) +- Fix download of attachments that are part of TNEF message (#1490091) +- Fix handling of uuencoded messages if messages_cache is enabled (#1490108) +- Fix handling of base64-encoded attachments with extra spaces (#1490111) +- Fix handling of UNKNOWN-CTE response, try do decode content client-side (#1490046) RELEASE 1.0.3 ------------- diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php index 33bd91e22..349f7e518 100644 --- a/plugins/acl/acl.php +++ b/plugins/acl/acl.php @@ -114,14 +114,16 @@ class acl extends rcube_plugin } if ($this->rc->config->get('acl_groups')) { - $prefix = $this->rc->config->get('acl_group_prefix'); - $result = $this->ldap->list_groups($search, $mode); + $prefix = $this->rc->config->get('acl_group_prefix'); + $group_field = $this->rc->config->get('acl_group_field', 'name'); + $result = $this->ldap->list_groups($search, $mode); foreach ($result as $record) { - $group = $record['name']; + $group = $record['name']; + $group_id = is_array($record[$group_field]) ? $record[$group_field][0] : $record[$group_field]; if ($group) { - $users[] = array('name' => ($prefix ? $prefix : '') . $group, 'display' => $group); + $users[] = array('name' => ($prefix ? $prefix : '') . $group_id, 'display' => $group); $keys[] = $group; } } diff --git a/plugins/acl/config.inc.php.dist b/plugins/acl/config.inc.php.dist index de1f8b5d2..ed7000267 100644 --- a/plugins/acl/config.inc.php.dist +++ b/plugins/acl/config.inc.php.dist @@ -23,6 +23,9 @@ $config['acl_groups'] = false; // Prefix added to the group name to build IMAP ACL identifier $config['acl_group_prefix'] = 'group:'; +// The LDAP attribute (or field name) which will be used as ACL group identifier +$config['acl_group_field'] = 'name'; + // Include the following 'special' access control subjects in the ACL dialog; // Defaults to array('anyone', 'anonymous') (not when set to an empty array) // Example: array('anyone') to exclude 'anonymous'. diff --git a/plugins/enigma/lib/enigma_driver_phpssl.php b/plugins/enigma/lib/enigma_driver_phpssl.php index 50af44762..fcd15db73 100644 --- a/plugins/enigma/lib/enigma_driver_phpssl.php +++ b/plugins/enigma/lib/enigma_driver_phpssl.php @@ -95,7 +95,7 @@ class enigma_driver_phpssl extends enigma_driver $fh = fopen($msg_file, "w"); if ($struct->mime_id) { - $message->get_part_content($struct->mime_id, $fh, true, 0, false); + $message->get_part_body($struct->mime_id, false, 0, $fh); } else { $this->rc->storage->get_raw_body($message->uid, $fh); diff --git a/plugins/new_user_identity/config.inc.php.dist b/plugins/new_user_identity/config.inc.php.dist new file mode 100644 index 000000000..b2fd76408 --- /dev/null +++ b/plugins/new_user_identity/config.inc.php.dist @@ -0,0 +1,10 @@ +<?php + +// The id of the address book to use to automatically set a new +// user's full name in their new identity. (This should be an +// string, which refers to the $config['ldap_public'] array.) +$config['new_user_identity_addressbook'] = 'People'; + +// When automatically setting a new users's full name in their +// new identity, match the user's login name against this field. +$config['new_user_identity_match'] = 'uid'; diff --git a/plugins/new_user_identity/new_user_identity.php b/plugins/new_user_identity/new_user_identity.php index 3943134b2..d1d1d9f24 100644 --- a/plugins/new_user_identity/new_user_identity.php +++ b/plugins/new_user_identity/new_user_identity.php @@ -9,17 +9,6 @@ * @version @package_version@ * @author Kris Steinhoff * @license GNU GPLv3+ - * - * Example configuration: - * - * // The id of the address book to use to automatically set a new - * // user's full name in their new identity. (This should be an - * // string, which refers to the $config['ldap_public'] array.) - * $config['new_user_identity_addressbook'] = 'People'; - * - * // When automatically setting a new users's full name in their - * // new identity, match the user's login name against this field. - * $config['new_user_identity_match'] = 'uid'; */ class new_user_identity extends rcube_plugin { @@ -36,16 +25,33 @@ class new_user_identity extends rcube_plugin { if ($this->init_ldap($args['host'])) { $results = $this->ldap->search('*', $args['user'], true); + if (count($results->records) == 1) { $user_name = is_array($results->records[0]['name']) ? $results->records[0]['name'][0] : $results->records[0]['name']; $user_email = is_array($results->records[0]['email']) ? $results->records[0]['email'][0] : $results->records[0]['email']; - $args['user_name'] = $user_name; + $args['user_name'] = $user_name; + $args['email_list'] = array(); + if (!$args['user_email'] && strpos($user_email, '@')) { $args['user_email'] = rcube_utils::idn_to_ascii($user_email); } + + foreach (array_keys($results[0]) as $key) { + if (!preg_match('/^email($|:)/', $key)) { + continue; + } + + foreach ((array) $results->records[0][$key] as $alias) { + if (strpos($alias, '@')) { + $args['email_list'][] = rcube_utils::idn_to_ascii($alias); + } + } + } + } } + return $args; } @@ -56,6 +62,7 @@ class new_user_identity extends rcube_plugin } $rcmail = rcmail::get_instance(); + $this->load_config(); $addressbook = $rcmail->config->get('new_user_identity_addressbook'); $ldap_config = (array)$rcmail->config->get('ldap_public'); diff --git a/plugins/password/README b/plugins/password/README index 936aa53cc..8c3a2afbd 100644 --- a/plugins/password/README +++ b/plugins/password/README @@ -317,31 +317,25 @@ 2.20. Plesk (Plesk RPC-API) --------------------------- - + Driver for changing Passwords via Plesk RPC-API. This Driver also works with Parallels Plesk Automation (PPA). - + You need to allow the IP of the Roundcube-Server for RPC-Calls in the Panel. - - + Set $config['password_plesk_host'] to the Hostname / IP where Plesk runs - Set your Admin or RPC User: $config['password_plesk_user'] - Set the Password of the User: $config['password_plesk_pass'] - Set $config['password_plesk_rpc_port'] for the RPC-Port. Usually its 8443 - - Set the RPC-Path in $config['password_plesk_rpc_path']. Normally this is: enterprise/control/agent.php; + Set the RPC-Path in $config['password_plesk_rpc_path']. Normally this is: enterprise/control/agent.php. - 3. Driver API ------------- - Driver file (<driver_name>.php) must define 'password_save' function with - two arguments. First - current password, second - new password. Function - should return PASSWORD_SUCCESS on success or any of PASSWORD_CONNECT_ERROR, + Driver file (<driver_name>.php) must define rcube_<driver_name>_password class + with public save() method that has two arguments. First - current password, second - new password. + This method should return PASSWORD_SUCCESS on success or any of PASSWORD_CONNECT_ERROR, PASSWORD_CRYPT_ERROR, PASSWORD_ERROR when driver was unable to change password. Extended result (as a hash-array with 'message' and 'code' items) can be returned too. See existing drivers in drivers/ directory for examples. diff --git a/plugins/password/config.inc.php.dist b/plugins/password/config.inc.php.dist index 6610b4dae..94c4368fe 100644 --- a/plugins/password/config.inc.php.dist +++ b/plugins/password/config.inc.php.dist @@ -204,10 +204,11 @@ $config['password_ldap_search_filter'] = '(uid=%login)'; // LDAP password hash type // Standard LDAP encryption type which must be one of: crypt, -// ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, ad or clear. +// ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, ad, cram-md5 (dovecot style) or clear. // Please note that most encodage types require external libraries // to be included in your PHP installation, see function hashPassword in drivers/ldap.php for more info. -// Default: 'crypt' +// Multiple password Values can be generated by concatenating encodings with a +. E.g. 'cram-md5+crypt' +// Default: 'crypt'. $config['password_ldap_encodage'] = 'crypt'; // LDAP password attribute diff --git a/plugins/password/drivers/ldap.php b/plugins/password/drivers/ldap.php index 340dd29f8..ac2ea3bd3 100644 --- a/plugins/password/drivers/ldap.php +++ b/plugins/password/drivers/ldap.php @@ -38,7 +38,8 @@ class rcube_ldap_password // Building user DN if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) { $userDN = self::substitute_vars($userDN); - } else { + } + else { $userDN = $this->search_userdn($rcmail); } @@ -78,13 +79,25 @@ class rcube_ldap_password return PASSWORD_CONNECT_ERROR; } - $crypted_pass = self::hash_password($passwd, $rcmail->config->get('password_ldap_encodage')); $force = $rcmail->config->get('password_ldap_force_replace'); $pwattr = $rcmail->config->get('password_ldap_pwattr'); $lchattr = $rcmail->config->get('password_ldap_lchattr'); $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr'); $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr'); $samba = $rcmail->config->get('password_ldap_samba'); + $encodage = $rcmail->config->get('password_ldap_encodage'); + + // Support multiple userPassword values where desired. + // multiple encodings can be specified separated by '+' (e.g. "cram-md5+ssha") + $encodages = explode('+', $encodage); + $crypted_pass = array(); + + foreach ($encodages as $enc) { + $cpw = self::hash_password($passwd, $enc); + if (!empty($cpw)) { + $crypted_pass[] = $cpw; + } + } // Support password_ldap_samba option for backward compat. if ($samba && !$smbpwattr) { @@ -93,7 +106,7 @@ class rcube_ldap_password } // Crypt new password - if (!$crypted_pass) { + if (empty($crypted_pass)) { return PASSWORD_CRYPT_ERROR; } @@ -297,6 +310,7 @@ class rcube_ldap_password } break; + case 'smd5': mt_srand((double) microtime() * 1000000); $salt = substr(pack('h*', md5(mt_rand())), 0, 8); @@ -332,6 +346,11 @@ class rcube_ldap_password $crypted_password = rcube_charset::convert('"' . $password_clear . '"', RCUBE_CHARSET, 'UTF-16LE'); break; + case 'cram-md5': + require_once __DIR__ . '/../helpers/dovecot_hmacmd5.php'; + return dovecot_hmacmd5($password_clear); + break; + case 'clear': default: $crypted_password = $password_clear; diff --git a/plugins/password/drivers/plesk.php b/plugins/password/drivers/plesk.php index d0a511fbe..db9ad9efd 100644 --- a/plugins/password/drivers/plesk.php +++ b/plugins/password/drivers/plesk.php @@ -49,7 +49,7 @@ class rcube_plesk_password * @param string $newpass New password * @returns int PASSWORD_SUCCESS|PASSWORD_ERROR */ - function save($currpass, $newpass)\ + function save($currpass, $newpass) { // get config $rcmail = rcmail::get_instance(); diff --git a/plugins/password/helpers/dovecot_hmacmd5.php b/plugins/password/helpers/dovecot_hmacmd5.php new file mode 100644 index 000000000..644b5377e --- /dev/null +++ b/plugins/password/helpers/dovecot_hmacmd5.php @@ -0,0 +1,191 @@ +<?php + +/** + * + * dovecot_hmacmd5.php V1.01 + * + * Generates HMAC-MD5 'contexts' for Dovecot's password files. + * + * (C) 2008 Hajo Noerenberg + * + * http://www.noerenberg.de/hajo/pub/dovecot_hmacmd5.php.txt + * + * Most of the code has been shamelessly stolen from various sources: + * + * (C) Paul Johnston 1999 - 2000 / http://pajhome.org.uk/crypt/md5/ + * (C) William K. Cole 2008 / http://www.scconsult.com/bill/crampass.pl + * (C) Borfast 2002 / http://www.zend.com/code/codex.php?ozid=962&single=1 + * (C) Thomas Weber / http://pajhome.org.uk/crypt/md5/contrib/md5.java.txt + * + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3.0 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/gpl-3.0.txt>. + * + */ + +/* Convert a 32-bit number to a hex string with ls-byte first + */ + +function rhex($n) { + $hex_chr = "0123456789abcdef"; $r = ''; + for($j = 0; $j <= 3; $j++) + $r .= $hex_chr[($n >> ($j * 8 + 4)) & 0x0F] . $hex_chr[($n >> ($j * 8)) & 0x0F]; + return $r; +} + +/* zeroFill() is needed because PHP doesn't have a zero-fill + * right shift operator like JavaScript's >>> + */ + +function zeroFill($a, $b) { + $z = hexdec(80000000); + if ($z & $a) { + $a >>= 1; + $a &= (~$z); + $a |= 0x40000000; + $a >>= ($b-1); + } else { + $a >>= $b; + } + return $a; +} + +/* Bitwise rotate a 32-bit number to the left + */ + +function bit_rol($num, $cnt) { + return ($num << $cnt) | (zeroFill($num, (32 - $cnt))); +} + +/* Add integers, wrapping at 2^32 + */ + +function safe_add($x, $y) { + return (($x&0x7FFFFFFF) + ($y&0x7FFFFFFF)) ^ ($x&0x80000000) ^ ($y&0x80000000); +} + +/* These functions implement the four basic operations the algorithm uses. + */ + +function md5_cmn($q, $a, $b, $x, $s, $t) { + return safe_add(bit_rol(safe_add(safe_add($a, $q), safe_add($x, $t)), $s), $b); +} +function md5_ff($a, $b, $c, $d, $x, $s, $t) { + return md5_cmn(($b & $c) | ((~$b) & $d), $a, $b, $x, $s, $t); +} +function md5_gg($a, $b, $c, $d, $x, $s, $t) { + return md5_cmn(($b & $d) | ($c & (~$d)), $a, $b, $x, $s, $t); +} +function md5_hh($a, $b, $c, $d, $x, $s, $t) { + return md5_cmn($b ^ $c ^ $d, $a, $b, $x, $s, $t); +} +function md5_ii($a, $b, $c, $d, $x, $s, $t) { + return md5_cmn($c ^ ($b | (~$d)), $a, $b, $x, $s, $t); +} + +/* Calculate the first round of the MD5 algorithm + */ + +function md5_oneround($s, $io) { + + $s = str_pad($s, 64, chr(0x00)); + + $x = array_fill(0, 16, 0); + + for($i = 0; $i < 64; $i++) + $x[$i >> 2] |= (($io ? 0x36 : 0x5c) ^ ord($s[$i])) << (($i % 4) * 8); + + $a = $olda = 1732584193; + $b = $oldb = -271733879; + $c = $oldc = -1732584194; + $d = $oldd = 271733878; + + $a = md5_ff($a, $b, $c, $d, $x[ 0], 7 , -680876936); + $d = md5_ff($d, $a, $b, $c, $x[ 1], 12, -389564586); + $c = md5_ff($c, $d, $a, $b, $x[ 2], 17, 606105819); + $b = md5_ff($b, $c, $d, $a, $x[ 3], 22, -1044525330); + $a = md5_ff($a, $b, $c, $d, $x[ 4], 7 , -176418897); + $d = md5_ff($d, $a, $b, $c, $x[ 5], 12, 1200080426); + $c = md5_ff($c, $d, $a, $b, $x[ 6], 17, -1473231341); + $b = md5_ff($b, $c, $d, $a, $x[ 7], 22, -45705983); + $a = md5_ff($a, $b, $c, $d, $x[ 8], 7 , 1770035416); + $d = md5_ff($d, $a, $b, $c, $x[ 9], 12, -1958414417); + $c = md5_ff($c, $d, $a, $b, $x[10], 17, -42063); + $b = md5_ff($b, $c, $d, $a, $x[11], 22, -1990404162); + $a = md5_ff($a, $b, $c, $d, $x[12], 7 , 1804603682); + $d = md5_ff($d, $a, $b, $c, $x[13], 12, -40341101); + $c = md5_ff($c, $d, $a, $b, $x[14], 17, -1502002290); + $b = md5_ff($b, $c, $d, $a, $x[15], 22, 1236535329); + + $a = md5_gg($a, $b, $c, $d, $x[ 1], 5 , -165796510); + $d = md5_gg($d, $a, $b, $c, $x[ 6], 9 , -1069501632); + $c = md5_gg($c, $d, $a, $b, $x[11], 14, 643717713); + $b = md5_gg($b, $c, $d, $a, $x[ 0], 20, -373897302); + $a = md5_gg($a, $b, $c, $d, $x[ 5], 5 , -701558691); + $d = md5_gg($d, $a, $b, $c, $x[10], 9 , 38016083); + $c = md5_gg($c, $d, $a, $b, $x[15], 14, -660478335); + $b = md5_gg($b, $c, $d, $a, $x[ 4], 20, -405537848); + $a = md5_gg($a, $b, $c, $d, $x[ 9], 5 , 568446438); + $d = md5_gg($d, $a, $b, $c, $x[14], 9 , -1019803690); + $c = md5_gg($c, $d, $a, $b, $x[ 3], 14, -187363961); + $b = md5_gg($b, $c, $d, $a, $x[ 8], 20, 1163531501); + $a = md5_gg($a, $b, $c, $d, $x[13], 5 , -1444681467); + $d = md5_gg($d, $a, $b, $c, $x[ 2], 9 , -51403784); + $c = md5_gg($c, $d, $a, $b, $x[ 7], 14, 1735328473); + $b = md5_gg($b, $c, $d, $a, $x[12], 20, -1926607734); + + $a = md5_hh($a, $b, $c, $d, $x[ 5], 4 , -378558); + $d = md5_hh($d, $a, $b, $c, $x[ 8], 11, -2022574463); + $c = md5_hh($c, $d, $a, $b, $x[11], 16, 1839030562); + $b = md5_hh($b, $c, $d, $a, $x[14], 23, -35309556); + $a = md5_hh($a, $b, $c, $d, $x[ 1], 4 , -1530992060); + $d = md5_hh($d, $a, $b, $c, $x[ 4], 11, 1272893353); + $c = md5_hh($c, $d, $a, $b, $x[ 7], 16, -155497632); + $b = md5_hh($b, $c, $d, $a, $x[10], 23, -1094730640); + $a = md5_hh($a, $b, $c, $d, $x[13], 4 , 681279174); + $d = md5_hh($d, $a, $b, $c, $x[ 0], 11, -358537222); + $c = md5_hh($c, $d, $a, $b, $x[ 3], 16, -722521979); + $b = md5_hh($b, $c, $d, $a, $x[ 6], 23, 76029189); + $a = md5_hh($a, $b, $c, $d, $x[ 9], 4 , -640364487); + $d = md5_hh($d, $a, $b, $c, $x[12], 11, -421815835); + $c = md5_hh($c, $d, $a, $b, $x[15], 16, 530742520); + $b = md5_hh($b, $c, $d, $a, $x[ 2], 23, -995338651); + + $a = md5_ii($a, $b, $c, $d, $x[ 0], 6 , -198630844); + $d = md5_ii($d, $a, $b, $c, $x[ 7], 10, 1126891415); + $c = md5_ii($c, $d, $a, $b, $x[14], 15, -1416354905); + $b = md5_ii($b, $c, $d, $a, $x[ 5], 21, -57434055); + $a = md5_ii($a, $b, $c, $d, $x[12], 6 , 1700485571); + $d = md5_ii($d, $a, $b, $c, $x[ 3], 10, -1894986606); + $c = md5_ii($c, $d, $a, $b, $x[10], 15, -1051523); + $b = md5_ii($b, $c, $d, $a, $x[ 1], 21, -2054922799); + $a = md5_ii($a, $b, $c, $d, $x[ 8], 6 , 1873313359); + $d = md5_ii($d, $a, $b, $c, $x[15], 10, -30611744); + $c = md5_ii($c, $d, $a, $b, $x[ 6], 15, -1560198380); + $b = md5_ii($b, $c, $d, $a, $x[13], 21, 1309151649); + $a = md5_ii($a, $b, $c, $d, $x[ 4], 6 , -145523070); + $d = md5_ii($d, $a, $b, $c, $x[11], 10, -1120210379); + $c = md5_ii($c, $d, $a, $b, $x[ 2], 15, 718787259); + $b = md5_ii($b, $c, $d, $a, $x[ 9], 21, -343485551); + + $a = safe_add($a, $olda); + $b = safe_add($b, $oldb); + $c = safe_add($c, $oldc); + $d = safe_add($d, $oldd); + + return rhex($a) . rhex($b) . rhex($c) . rhex($d); +} + +function dovecot_hmacmd5 ($s) { + if (strlen($s) > 64) $s=pack("H*", md5($s)); + return "{CRAM-MD5}" . md5_oneround($s, 0) . md5_oneround($s, 1); +} diff --git a/plugins/vcard_attachments/vcard_attachments.php b/plugins/vcard_attachments/vcard_attachments.php index cf7e22d3a..74718be6f 100644 --- a/plugins/vcard_attachments/vcard_attachments.php +++ b/plugins/vcard_attachments/vcard_attachments.php @@ -65,7 +65,7 @@ class vcard_attachments extends rcube_plugin $attach_script = false; foreach ($this->vcard_parts as $part) { - $vcards = rcube_vcard::import($this->message->get_part_content($part, null, true)); + $vcards = rcube_vcard::import($this->message->get_part_body($part, true)); // successfully parsed vcards? if (empty($vcards)) { diff --git a/plugins/zipdownload/zipdownload.php b/plugins/zipdownload/zipdownload.php index edb8188cc..2e103ceb0 100644 --- a/plugins/zipdownload/zipdownload.php +++ b/plugins/zipdownload/zipdownload.php @@ -144,20 +144,14 @@ class zipdownload extends rcube_plugin } } - $disp_name = $this->_convert_filename($filename); + $disp_name = $this->_convert_filename($filename); + $tmpfn = tempnam($temp_dir, 'zipattach'); + $tmpfp = fopen($tmpfn, 'w'); + $tempfiles[] = $tmpfn; - if ($part->body) { - $orig_message_raw = $part->body; - $zip->addFromString($disp_name, $orig_message_raw); - } - else { - $tmpfn = tempnam($temp_dir, 'zipattach'); - $tmpfp = fopen($tmpfn, 'w'); - $imap->get_message_part($message->uid, $part->mime_id, $part, null, $tmpfp, true); - $tempfiles[] = $tmpfn; - fclose($tmpfp); - $zip->addFile($tmpfn, $disp_name); - } + $message->get_part_body($part->mime_id, false, 0, $tmpfp); + $zip->addFile($tmpfn, $disp_name); + fclose($tmpfp); } $zip->close(); diff --git a/program/include/rcmail.php b/program/include/rcmail.php index 221b50afe..8ea42b600 100644 --- a/program/include/rcmail.php +++ b/program/include/rcmail.php @@ -106,14 +106,14 @@ class rcmail extends rcube // reset some session parameters when changing task if ($this->task != 'utils') { // we reset list page when switching to another task - // but only to the main task interface - empty action (#1489076) + // but only to the main task interface - empty action (#1489076, #1490116) // this will prevent from unintentional page reset on cross-task requests if ($this->session && $_SESSION['task'] != $this->task && empty($this->action)) { $this->session->remove('page'); - } - // set current task to session - $_SESSION['task'] = $this->task; + // set current task to session + $_SESSION['task'] = $this->task; + } } // init output class (not in CLI mode) diff --git a/program/js/list.js b/program/js/list.js index d1dbbcb6c..dbc14d6b8 100644 --- a/program/js/list.js +++ b/program/js/list.js @@ -150,9 +150,9 @@ init_row: function(row) var self = this, uid = row.uid; this.rows[uid] = {uid:uid, id:row.id, obj:row}; - // set eventhandlers to table row + // set eventhandlers to table row (only left-button-clicks in mouseup) row.onmousedown = function(e){ return self.drag_row(e, this.uid); }; - row.onmouseup = function(e){ return self.click_row(e, this.uid); }; + row.onmouseup = function(e){ if (e.which == 1) return self.click_row(e, this.uid); }; if (bw.touch) { row.addEventListener('touchstart', function(e) { 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) diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 1770a1bcb..9b6d0dcc9 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -802,22 +802,14 @@ function rcmail_compose_part_body($part, $isHtml = false) return ''; } - if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) { - $part->ctype_parameters['charset'] = $MESSAGE->headers->charset; - } - // fetch part if not available - if (!isset($part->body)) { - $part->body = $MESSAGE->get_part_content($part->mime_id); - } + $body = $MESSAGE->get_part_body($part->mime_id, true); // message is cached but not exists (#1485443), or other error - if ($part->body === false) { + if ($body === false) { return ''; } - $body = $part->body; - if ($isHtml) { if ($part->ctype_secondary == 'html') { } @@ -1363,7 +1355,7 @@ function rcmail_save_attachment(&$message, $pid) $path = tempnam($temp_dir, 'rcmAttmnt'); if ($fp = fopen($path, 'w')) { - $message->get_part_content($pid, $fp, true, 0, false); + $message->get_part_body($pid, false, 0, $fp); fclose($fp); } else { @@ -1371,7 +1363,7 @@ function rcmail_save_attachment(&$message, $pid) } } else { - $data = $message->get_part_content($pid, null, true, 0, false); + $data = $message->get_part_body($pid); } $mimetype = $part->ctype_primary . '/' . $part->ctype_secondary; @@ -1385,6 +1377,7 @@ function rcmail_save_attachment(&$message, $pid) 'data' => $data, 'path' => $path, 'size' => $path ? filesize($path) : strlen($data), + 'charset' => $part->charset, ); $attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment); diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index cbeeb05fb..8dced9b18 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -864,17 +864,19 @@ function rcmail_wash_html($html, $p, $cid_replaces) * Convert the given message part to proper HTML * which can be displayed the message view * - * @param object rcube_message_part Message part - * @param array Display parameters array + * @param string Message part body + * @param rcube_message_part Message part + * @param array Display parameters array + * * @return string Formatted HTML string */ -function rcmail_print_body($part, $p = array()) +function rcmail_print_body($body, $part, $p = array()) { global $RCMAIL; // trigger plugin hook $data = $RCMAIL->plugins->exec_hook('message_part_before', - array('type' => $part->ctype_secondary, 'body' => $part->body, 'id' => $part->mime_id) + array('type' => $part->ctype_secondary, 'body' => $body, 'id' => $part->mime_id) + $p + array('safe' => false, 'plain' => false, 'inline_html' => true)); // convert html to text/plain @@ -900,7 +902,7 @@ function rcmail_print_body($part, $p = array()) } else { // assert plaintext - $body = $part->body; + $body = $data['body']; $part->ctype_secondary = $data['type'] = 'plain'; } @@ -1072,8 +1074,10 @@ function rcmail_message_headers($attrib, $headers=null) } else if ($hkey == 'subject' && empty($value)) $header_value = $RCMAIL->gettext('nosubject'); - else + else { + $value = is_array($value) ? implode(' ', $value) : $value; $header_value = trim(rcube_mime::decode_header($value, $headers['charset'])); + } $output_headers[$hkey] = array( 'title' => $header_title, @@ -1204,18 +1208,12 @@ function rcmail_message_body($attrib) continue; } - if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset'])) { - $part->ctype_parameters['charset'] = $MESSAGE->headers->charset; - } - - // fetch part if not available - if (!isset($part->body)) { - $part->body = $MESSAGE->get_part_content($part->mime_id); - } + // fetch part body + $body = $MESSAGE->get_part_body($part->mime_id, true); // extract headers from message/rfc822 parts if ($part->mimetype == 'message/rfc822') { - $msgpart = rcube_mime::parse_message($part->body); + $msgpart = rcube_mime::parse_message($body); if (!empty($msgpart->headers)) { $part = $msgpart; $out .= html::div('message-partheaders', rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : null, $part->headers)); @@ -1223,14 +1221,14 @@ function rcmail_message_body($attrib) } // message is cached but not exists (#1485443), or other error - if ($part->body === false) { + if ($body === false) { rcmail_message_error($MESSAGE->uid); } $plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array('part' => $part, 'prefix' => '')); - $body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$RCMAIL->config->get('prefer_html'))); + $body = rcmail_print_body($body, $part, array('safe' => $safe_mode, 'plain' => !$RCMAIL->config->get('prefer_html'))); if ($part->ctype_secondary == 'html') { $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs, $safe_mode); diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index b260d2c85..948465604 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -130,7 +130,7 @@ else if (strlen($part_id)) { $extensions = rcube_mime::get_mime_extensions($mimetype); if ($plugin['body']) { - $part->body = $plugin['body']; + $body = $plugin['body']; } // compare file mimetype with the stated content-type headers and file extension to avoid malicious operations @@ -142,15 +142,10 @@ else if (strlen($part_id)) { // 2. detect the real mimetype of the attachment part and compare it with the stated mimetype and filename extension if ($valid || !$file_extension || $mimetype == 'application/octet-stream' || stripos($mimetype, 'text/') === 0) { - if ($part->body) // part body is already loaded - $body = $part->body; - else if ($part->size && $part->size < 1024*1024) // load the entire part if it's small enough - $body = $part->body = $MESSAGE->get_part_content($part->mime_id); - else // fetch the first 2K of the message part - $body = $MESSAGE->get_part_content($part->mime_id, null, true, 2048); + $tmp_body = $body ?: $MESSAGE->get_part_body($part->mime_id, false, 2048); // detect message part mimetype - $real_mimetype = rcube_mime::file_content_type($body, $part->filename, $mimetype, true, true); + $real_mimetype = rcube_mime::file_content_type($tmp_body, $part->filename, $mimetype, true, true); list($real_ctype_primary, $real_ctype_secondary) = explode('/', $real_mimetype); // accept text/plain with any extension @@ -251,15 +246,15 @@ else if (strlen($part_id)) { } else { // get part body if not available - if (!$part->body) { - $part->body = $MESSAGE->get_part_content($part->mime_id); + if (!isset($body)) { + $body = $MESSAGE->get_part_body($part->mime_id, true); } // show images? rcmail_check_safe($MESSAGE); // render HTML body - $out = rcmail_print_body($part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false)); + $out = rcmail_print_body($body, $part, array('safe' => $MESSAGE->is_safe, 'inline_html' => false)); // insert remote objects warning into HTML body if ($REMOTE_OBJECTS) { @@ -280,7 +275,7 @@ else if (strlen($part_id)) { } // check connection status - if ($part->size && empty($part->body)) { + if ($part->size && empty($body)) { check_storage_status(); } @@ -320,12 +315,12 @@ else if (strlen($part_id)) { $file_path = tempnam($temp_dir, 'rcmAttmnt'); // write content to temp file - if ($part->body) { - $saved = file_put_contents($file_path, $part->body); + if ($body) { + $saved = file_put_contents($file_path, $body); } else if ($part->size) { $fd = fopen($file_path, 'w'); - $saved = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $fd); + $saved = $MESSAGE->get_part_body($part->mime_id, false, 0, $fd); fclose($fd); } @@ -341,22 +336,22 @@ else if (strlen($part_id)) { } // do content filtering to avoid XSS through fake images else if (!empty($_REQUEST['_embed']) && $browser->ie && $browser->ver <= 8) { - if ($part->body) { - echo preg_match('/<(script|iframe|object)/i', $part->body) ? '' : $part->body; + if ($body) { + echo preg_match('/<(script|iframe|object)/i', $body) ? '' : $body; $sent = true; } else if ($part->size) { $stdout = fopen('php://output', 'w'); stream_filter_register('rcube_content', 'rcube_content_filter') or die('Failed to register content filter'); stream_filter_append($stdout, 'rcube_content'); - $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $stdout); + $sent = $MESSAGE->get_part_body($part->mime_id, true, 0, $stdout); } } // send part as-it-is else { - if ($part->body && empty($plugin['download'])) { - header("Content-Length: " . strlen($part->body)); - echo $part->body; + if ($body && empty($plugin['download'])) { + header("Content-Length: " . strlen($body)); + echo $body; $sent = true; } else if ($part->size) { @@ -364,8 +359,7 @@ else if (strlen($part_id)) { header("Content-Length: $size"); } - // 8th argument disables re-formatting of text/* parts (#1489267) - $sent = $RCMAIL->storage->get_message_part($MESSAGE->uid, $part->mime_id, $part, true, null, false, 0, false); + $sent = $MESSAGE->get_part_body($part->mime_id, false, 0, -1); } } diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc index bac751298..68878d790 100644 --- a/program/steps/mail/sendmail.inc +++ b/program/steps/mail/sendmail.inc @@ -469,7 +469,8 @@ if (is_array($COMPOSE['attachments'])) { $attachment['data'] ? false : true, $ctype == 'message/rfc822' ? '8bit' : 'base64', 'attachment', - '', '', '', + $attachment['charset'], + '', '', $folding ? 'quoted-printable' : NULL, $folding == 2 ? 'quoted-printable' : NULL, '', RCUBE_CHARSET diff --git a/skins/classic/print.css b/skins/classic/print.css index 8eac41f53..349b224cb 100644 --- a/skins/classic/print.css +++ b/skins/classic/print.css @@ -8,7 +8,7 @@ body margin: 2mm; } -body, td, th, span, div, p +body, td, th, div, p { font-size: 9pt; color: #000000; diff --git a/skins/larry/mail.css b/skins/larry/mail.css index 4ec258552..0f1eaa8a7 100644 --- a/skins/larry/mail.css +++ b/skins/larry/mail.css @@ -857,7 +857,6 @@ div.hide-headers { #messagecontent .leftcol, #messagepreview .leftcol { margin-right: 252px; - overflow-x: auto; } #messagecontent .rightcol, diff --git a/skins/larry/print.css b/skins/larry/print.css index 22d6c5288..d3a6cd802 100644 --- a/skins/larry/print.css +++ b/skins/larry/print.css @@ -15,7 +15,7 @@ body { margin: 2mm; } -body, td, th, span, div, p { +body, td, th, div, p { font-size: 9pt; color: #000; } diff --git a/tests/MailFunc.php b/tests/MailFunc.php index 05f26324e..7fb78ef9e 100644 --- a/tests/MailFunc.php +++ b/tests/MailFunc.php @@ -42,7 +42,7 @@ class MailFunc extends PHPUnit_Framework_TestCase $part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg'); // render HTML in normal mode - $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo'); + $html = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => false)), 'foo'); $this->assertRegExp('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image"); $this->assertRegExp('#background="./program/resources/blocked.gif"#', $html, "Replace external background image"); @@ -56,7 +56,7 @@ class MailFunc extends PHPUnit_Framework_TestCase $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected"); // render HTML in safe mode - $html2 = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'foo'); + $html2 = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => true)), 'foo'); $this->assertRegExp('/<style [^>]+>/', $html2, "Allow styles in safe mode"); $this->assertRegExp('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)"); @@ -71,7 +71,7 @@ class MailFunc extends PHPUnit_Framework_TestCase function test_html_xss() { $part = $this->get_html_part('src/htmlxss.txt'); - $washed = rcmail_print_body($part, array('safe' => true)); + $washed = rcmail_print_body($part->body, $part, array('safe' => true)); $this->assertNotRegExp('/src="skins/', $washed, "Remove local references"); $this->assertNotRegExp('/\son[a-z]+/', $washed, "Remove on* attributes"); @@ -88,7 +88,7 @@ class MailFunc extends PHPUnit_Framework_TestCase function test_html_xss2() { $part = $this->get_html_part('src/BID-26800.txt'); - $washed = rcmail_html4inline(rcmail_print_body($part, array('safe' => true)), 'dabody', '', $attr, true); + $washed = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => true)), 'dabody', '', $attr, true); $this->assertNotRegExp('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks"); $this->assertNotRegExp('/font-style:italic/', $washed, "Allow valid styles"); @@ -114,7 +114,7 @@ class MailFunc extends PHPUnit_Framework_TestCase function test_washtml_utf8() { $part = $this->get_html_part('src/invalidchars.html'); - $washed = rcmail_print_body($part); + $washed = rcmail_print_body($part->body, $part); $this->assertRegExp('/<p>символ<\/p>/', $washed, "Remove non-unicode characters from HTML message body"); } @@ -128,7 +128,7 @@ class MailFunc extends PHPUnit_Framework_TestCase $part->ctype_primary = 'text'; $part->ctype_secondary = 'plain'; $part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt')); - $html = rcmail_print_body($part, array('safe' => true)); + $html = rcmail_print_body($part->body, $part, array('safe' => true)); $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick"); $this->assertRegExp('#<a rel="noreferrer" target="_blank" href="http://www.apple.com/legal/privacy">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank"); @@ -143,7 +143,7 @@ class MailFunc extends PHPUnit_Framework_TestCase $part = $this->get_html_part('src/mailto.txt'); // render HTML in normal mode - $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo'); + $html = rcmail_html4inline(rcmail_print_body($part->body, $part, array('safe' => false)), 'foo'); $mailto = '<a href="mailto:me@me.com"' .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&body=this is the body\',this)" rel="noreferrer">e-mail</a>'; @@ -157,7 +157,7 @@ class MailFunc extends PHPUnit_Framework_TestCase function test_html_comments() { $part = $this->get_html_part('src/htmlcom.txt'); - $washed = rcmail_print_body($part, array('safe' => true)); + $washed = rcmail_print_body($part->body, $part, array('safe' => true)); // #1487759 $this->assertRegExp('|<p>test1</p>|', $washed, "Buggy HTML comments"); |