diff options
-rw-r--r-- | .htaccess | 2 | ||||
-rw-r--r-- | CHANGELOG | 8 | ||||
-rw-r--r-- | program/js/app.js | 2 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_base_replacer.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_charset.php | 14 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_image.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_string_replacer.php | 2 | ||||
-rw-r--r-- | program/steps/mail/compose.inc | 16 | ||||
-rw-r--r-- | program/steps/mail/func.inc | 8 | ||||
-rw-r--r-- | program/steps/settings/edit_identity.inc | 3 | ||||
-rw-r--r-- | program/steps/settings/save_identity.inc | 46 | ||||
-rw-r--r-- | tests/Framework/BaseReplacer.php | 14 | ||||
-rw-r--r-- | tests/Framework/StringReplacer.php | 5 |
13 files changed, 110 insertions, 18 deletions
@@ -30,7 +30,7 @@ php_value mbstring.func_overload 0 RewriteEngine On RewriteRule ^favicon\.ico$ skins/larry/images/favicon.ico # security rules -RewriteRule .git - [F] +RewriteRule \.git - [F] RewriteRule ^/?(README(.md)?|INSTALL|LICENSE|CHANGELOG|UPGRADING)$ - [F] RewriteRule ^/?(SQL|bin) - [F] </IfModule> @@ -1,6 +1,14 @@ CHANGELOG Roundcube Webmail =========================== +- Fix purge action in folder manager (#1489280) +- Fix base URL resolving on attribute values with no quotes (#1489275) +- Fix wrong handling of links with '|' character (#1489276) +- Fix colorspace issue on image conversion using ImageMagick (#1489270) +- Fix XSS vulnerability when saving HTML signatures (#1489251) +- Fix XSS vulnerability when editing a message "as new" or draft (#1489251) +- Fix rewrite rule in .htaccess (#1489240) +- Fix detecting Turkish language in ISO-8859-9 encoding (#1489252) - Fix identity-selection using Return-Path headers (#1489241) - Fix parsing of links with ... in URL (#1489192) - Fix compose priority selector when opening in new window (#1489257) diff --git a/program/js/app.js b/program/js/app.js index 98847ad2e..e31e07632 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -388,7 +388,7 @@ function rcube_webmail() } else if (this.env.action == 'edit-folder' && this.gui_objects.editform) { this.enable_command('save', 'folder-size', true); - parent.rcmail.env.messagecount = this.env.messagecount; + parent.rcmail.env.exists = this.env.messagecount; parent.rcmail.enable_command('purge', this.env.messagecount); $("input[type='text']").first().select(); } diff --git a/program/lib/Roundcube/rcube_base_replacer.php b/program/lib/Roundcube/rcube_base_replacer.php index fcd85c2c8..aaaa2028c 100644 --- a/program/lib/Roundcube/rcube_base_replacer.php +++ b/program/lib/Roundcube/rcube_base_replacer.php @@ -44,8 +44,8 @@ class rcube_base_replacer public function replace($body) { return preg_replace_callback(array( - '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', - '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', + '/(src|background|href)=(["\']?)([^"\'\s>]+)(\2|\s|>)/i', + '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/i', ), array($this, 'callback'), $body); } diff --git a/program/lib/Roundcube/rcube_charset.php b/program/lib/Roundcube/rcube_charset.php index a7f26a3f4..19dbf6cbc 100644 --- a/program/lib/Roundcube/rcube_charset.php +++ b/program/lib/Roundcube/rcube_charset.php @@ -674,23 +674,27 @@ class rcube_charset // Prioritize charsets according to current language (#1485669) switch ($language) { - case 'ja_JP': // for Japanese + case 'ja_JP': $prio = array('ISO-2022-JP', 'JIS', 'UTF-8', 'EUC-JP', 'eucJP-win', 'SJIS', 'SJIS-win'); break; - case 'zh_CN': // for Chinese (Simplified) - case 'zh_TW': // for Chinese (Traditional) + case 'zh_CN': + case 'zh_TW': $prio = array('UTF-8', 'BIG-5', 'GB2312', 'EUC-TW'); break; - case 'ko_KR': // for Korean + case 'ko_KR': $prio = array('UTF-8', 'EUC-KR', 'ISO-2022-KR'); break; - case 'ru_RU': // for Russian + case 'ru_RU': $prio = array('UTF-8', 'WINDOWS-1251', 'KOI8-R'); break; + case 'tr_TR': + $prio = array('UTF-8', 'ISO-8859-9', 'WINDOWS-1254'); + break; + default: $prio = array('UTF-8', 'SJIS', 'GB2312', 'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4', diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index 735a0df01..ffcfd4b1d 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -120,7 +120,7 @@ class rcube_image $p['-opts'] = array('-resize' => $p['size'].'>'); if (in_array($type, explode(',', $p['types']))) { // Valid type? - $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace RGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p); + $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -quality {quality} {-opts} {intype}:{in} {type}:{out}', $p); } if ($result === '') { @@ -222,7 +222,7 @@ class rcube_image $p['out'] = $filename; $p['type'] = self::$extensions[$type]; - $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p); + $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -quality 75 {in} {type}:{out}', $p); if ($result === '') { @chmod($filename, 0600); diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index acdc4e317..bd26f8e7d 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -36,7 +36,7 @@ class rcube_string_replacer // Support unicode/punycode in top-level domain part $utf_domain = '[^?&@"\'\\/()<>\s\r\t\n]+\\.?([^\\x00-\\x2f\\x3b-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-zA-Z0-9]{2,})'; $url1 = '.:;,'; - $url2 = 'a-zA-Z0-9%=#$@+?!&\\/_~\\[\\]\\(\\){}\*-'; + $url2 = 'a-zA-Z0-9%=#$@+?|!&\\/_~\\[\\]\\(\\){}\*-'; $this->link_pattern = "/([\w]+:\/\/|\W[Ww][Ww][Ww]\.|^[Ww][Ww][Ww]\.)($utf_domain([$url1]*[$url2]+)*)/"; $this->mailto_pattern = "/(" diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc index 73a7f34ac..fb76b9abb 100644 --- a/program/steps/mail/compose.inc +++ b/program/steps/mail/compose.inc @@ -198,7 +198,10 @@ if (!empty($msg_uid) && empty($COMPOSE['as_attachment'])) if (!empty($MESSAGE->headers->charset)) $RCMAIL->storage->set_charset($MESSAGE->headers->charset); - if ($compose_mode == RCUBE_COMPOSE_REPLY) { + if (!$MESSAGE->headers) { + // error + } + else if ($compose_mode == RCUBE_COMPOSE_REPLY) { $COMPOSE['reply_uid'] = $msg_uid; $COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID; $COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID); @@ -980,10 +983,19 @@ function rcmail_create_draft_body($body, $bodyIsHtml) && count($MESSAGE->mime_parts) > 0) { $cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml); + } + + // clean up HTML tags - XSS prevention (#1489251) + if ($bodyIsHtml) { + $body = rcmail_wash_html($body, array('safe' => 1), $cid_map); + + // remove comments (produced by washtml) + $body = preg_replace('/<!--[^>]+-->/', '', $body); // replace cid with href in inline images links - if ($cid_map) + if (!empty($cid_map)) { $body = str_replace(array_keys($cid_map), array_values($cid_map), $body); + } } return $body; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 16a9f495d..28e6baadf 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -1803,8 +1803,14 @@ function rcmail_identity_select($MESSAGE, $identities = null, $compose_mode = 'r // Try Return-Path if ($from_idx === null && ($return_path = $MESSAGE->headers->others['return-path'])) { foreach ($identities as $idx => $ident) { + // Return-Path header contains an email address, but on some mailing list + // it can be e.g. <pear-dev-return-55250-local=domain.tld@lists.php.net> + // where local@domain.tld is the address we're looking for (#1489241) + $ident1 = $ident['email_ascii']; + $ident2 = str_replace('@', '=', $ident1); + foreach ((array)$return_path as $path) { - if (stripos($path, $ident['email_ascii']) !== false) { + if (stripos($path, $ident1) !== false || stripos($path, $ident2)) { $from_idx = $idx; break 2; } diff --git a/program/steps/settings/edit_identity.inc b/program/steps/settings/edit_identity.inc index d70a7aef7..edd4ba60d 100644 --- a/program/steps/settings/edit_identity.inc +++ b/program/steps/settings/edit_identity.inc @@ -77,7 +77,7 @@ function rcube_identity_form($attrib) 'signature' => array( 'name' => rcube_label('signature'), 'content' => array( - 'signature' => array('type' => 'textarea', 'size' => $t_cols, 'rows' => $t_rows, + 'signature' => array('type' => 'textarea', 'size' => $t_cols, 'rows' => $t_rows, 'spellcheck' => true), 'html_signature' => array('type' => 'checkbox', 'label' => rcube_label('htmlsignature'), 'onclick' => 'return rcmail_toggle_editor(this, \'rcmfd_signature\');'), @@ -138,6 +138,7 @@ function rcube_identity_form($attrib) $label = !empty($colprop['label']) ? $colprop['label'] : rcube_label(str_replace('-', '', $col)); + $value = !empty($colprop['value']) ? $colprop['value'] : rcmail_get_edit_field($col, $IDENTITY_RECORD[$col], $colprop, $colprop['type']); diff --git a/program/steps/settings/save_identity.inc b/program/steps/settings/save_identity.inc index 34d8be268..d3b132f8b 100644 --- a/program/steps/settings/save_identity.inc +++ b/program/steps/settings/save_identity.inc @@ -76,6 +76,15 @@ foreach ($email_checks as $email) { } } +// XSS protection in HTML signature (#1489251) +if (!empty($save_data['signature']) && !empty($save_data['html_signature'])) { + $save_data['signature'] = rcmail_wash_html($save_data['signature']); + + // clear POST data of signature, we want to use safe content + // when the form is displayed again + unset($_POST['_signature']); +} + // update an existing contact if ($_POST['_iid']) { $iid = get_input_value('_iid', RCUBE_INPUT_POST); @@ -167,3 +176,40 @@ if (!empty($_REQUEST['_framed'])) { } else rcmail_overwrite_action('identities'); + + +/** + * Sanity checks/cleanups on HTML body of signature + */ +function rcmail_wash_html($html) +{ + // Add header with charset spec., washtml cannot work without that + $html = '<html><head>' + . '<meta http-equiv="Content-Type" content="text/html; charset='.RCMAIL_CHARSET.'" />' + . '</head><body>' . $html . '</body></html>'; + + // clean HTML with washhtml by Frederic Motte + $wash_opts = array( + 'show_washed' => false, + 'allow_remote' => 1, + 'charset' => RCMAIL_CHARSET, + 'html_elements' => array('body', 'link'), + 'html_attribs' => array('rel', 'type'), + ); + + // initialize HTML washer + $washer = new rcube_washtml($wash_opts); + + //$washer->add_callback('form', 'rcmail_washtml_callback'); + //$washer->add_callback('style', 'rcmail_washtml_callback'); + + // Remove non-UTF8 characters (#1487813) + $html = rc_utf8_clean($html); + + $html = $washer->wash($html); + + // remove unwanted comments and tags (produced by washtml) + $html = preg_replace(array('/<!--[^>]+-->/', '/<\/?body>/'), '', $html); + + return $html; +} diff --git a/tests/Framework/BaseReplacer.php b/tests/Framework/BaseReplacer.php index e00b9e5eb..44a9604ac 100644 --- a/tests/Framework/BaseReplacer.php +++ b/tests/Framework/BaseReplacer.php @@ -17,4 +17,18 @@ class Framework_BaseReplacer extends PHPUnit_Framework_TestCase $this->assertInstanceOf('rcube_base_replacer', $object, "Class constructor"); } + + /** + * Test replace() + */ + function test_replace() + { + $base = 'http://thisshouldntbetheurl.bob.com/'; + $html = '<A href=http://shouldbethislink.com>Test URL</A>'; + + $replacer = new rcube_base_replacer($base); + $response = $replacer->replace($html); + + $this->assertSame('<A href="http://shouldbethislink.com">Test URL</A>', $response); + } } diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php index e8a762a39..5008e3f84 100644 --- a/tests/Framework/StringReplacer.php +++ b/tests/Framework/StringReplacer.php @@ -27,7 +27,7 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2" target="_blank">http://domain.tld/path*path2</a>'), array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\" target=\"_blank\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"), array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo" target="_blank">http://localhost/?foo</a> End'), - array('http://localhost/?foo=bar. Period', '<a href="http://localhost/?foo=bar">http://localhost/?foo=bar</a>. Period'), + array('http://localhost/?foo=bar. Period', '<a href="http://localhost/?foo=bar" target="_blank">http://localhost/?foo=bar</a>. Period'), array('www.domain.tld', '<a href="http://www.domain.tld" target="_blank">www.domain.tld</a>'), array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD" target="_blank">WWW.DOMAIN.TLD</a>'), array('[http://link.com]', '[<a href="http://link.com" target="_blank">http://link.com</a>]'), @@ -36,11 +36,12 @@ class Framework_StringReplacer extends PHPUnit_Framework_TestCase array('(http://link.com)', '(<a href="http://link.com" target="_blank">http://link.com</a>)'), array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c" target="_blank">http://link.com?a(b)c</a>'), array('http://link.com?(link)', '<a href="http://link.com?(link)" target="_blank">http://link.com?(link)</a>'), - array('https://github.com/a/b/compare/3a0f82...1f4b2a after', '<a href="https://github.com/a/b/compare/3a0f82...1f4b2a">https://github.com/a/b/compare/3a0f82...1f4b2a</a> after'), + array('https://github.com/a/b/compare/3a0f82...1f4b2a after', '<a href="https://github.com/a/b/compare/3a0f82...1f4b2a" target="_blank">https://github.com/a/b/compare/3a0f82...1f4b2a</a> after'), array('http://<test>', 'http://<test>'), array('http://', 'http://'), array('1@1.com www.domain.tld', '<a href="mailto:1@1.com">1@1.com</a> <a href="http://www.domain.tld" target="_blank">www.domain.tld</a>'), array(' www.domain.tld ', ' <a href="http://www.domain.tld" target="_blank">www.domain.tld</a> '), + array(' www.domain.tld/#!download|856p1|2 ', ' <a href="http://www.domain.tld/#!download|856p1|2" target="_blank">www.domain.tld/#!download|856p1|2</a> '), ); } |