[Pkg-roundcube-maintainers] CVE-2024-42009/roundcube: bookworm-security upload
Guilhem Moulin
guilhem at debian.org
Tue Aug 6 17:31:37 BST 2024
Dear security team,
I misjudged the severity of CVE-2024-42009 when filing #1077969. The
XSS appears to be critical, see the reporter's report at
https://www.sonarsource.com/blog/government-emails-at-risk-critical-cross-site-scripting-vulnerability-in-roundcube-webmail/
I'd like to propose the attached tested debdiffs to fix that
vulnerability as well as CVE-2024-42008, CVE-2024-42010, and a couple of
of other issues. All patches were cherry-picked from upstream's 1.6.8
release.
Unfortunately for bullseye the backport requires more work. I'll try to
do that ASAP but I'm not yet sure that I will have time to finalize it
before the suite is handed over to the LTS team.
I suggest not to wait for the bookworm-security upload. Unless you
don't want to issue a bookworm-only DSA while bullseye is still under
your watch, that is. :-)
Cheers
--
Guilhem.
-------------- next part --------------
diffstat for roundcube-1.6.5+dfsg roundcube-1.6.5+dfsg
changelog | 18
patches/CVE-2024-42008.patch | 71 +
patches/CVE-2024-42009.patch | 624 ++++++++++
patches/CVE-2024-42010.patch | 408 ++++++
patches/Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch | 223 +++
patches/Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch | 25
patches/Fix-fatal-error-when-parsing-some-TNEF-attachments.patch | 128 ++
patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch | 54
patches/series | 7
9 files changed, 1558 insertions(+)
diff -Nru roundcube-1.6.5+dfsg/debian/changelog roundcube-1.6.5+dfsg/debian/changelog
--- roundcube-1.6.5+dfsg/debian/changelog 2024-06-17 03:15:26.000000000 +0200
+++ roundcube-1.6.5+dfsg/debian/changelog 2024-08-06 16:02:54.000000000 +0200
@@ -1,3 +1,21 @@
+roundcube (1.6.5+dfsg-1+deb12u3) bookworm-security; urgency=high
+
+ * Cherry pick upstream security fixes from v1.6.8 (closes: #1077969):
+ + CVE-2024-42008: Cross-site scripting (XSS) vulnerability in serving of
+ attachments other than HTML or SVG.
+ + CVE-2024-42009: Cross-site scripting (XSS) vulnerability in
+ post-processing of sanitized HTML content.
+ + CVE-2024-42010: Fix information leak (access to remote content) via
+ insufficient CSS filtering.
+ * Cherry pick further upstream changes from v1.6.8:
+ + Fix fatal error when parsing some TNEF attachments.
+ + Fix bug where an unhandled exception was caused by an invalid image
+ attachment.
+ + Fix infinite loop when parsing malformed Sieve script.
+ + Fix bug where imap_conn_option's 'socket' was ignored.
+
+ -- Guilhem Moulin <guilhem at debian.org> Tue, 06 Aug 2024 16:02:54 +0200
+
roundcube (1.6.5+dfsg-1+deb12u2) bookworm-security; urgency=high
* Fix CVE-2024-37384: Cross-site scripting (XSS) vulnerability in handling
diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42008.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42008.patch
--- roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42008.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42008.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,71 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:10:55 +0200
+Subject: Fix XSS vulnerability in serving of attachments other than HTML or
+ SVG
+
+Credits to Oskar Zeino-Mahmalat (Sonar) https://www.sonarsource.com
+
+Origin: https://github.com/roundcube/roundcubemail/commit/89c8fe9ae9318c015807fbcbf7e39555fb30885d
+Bug-Debian: https://bugs.debian.org/1077969
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42008
+---
+ program/actions/mail/get.php | 5 +++++
+ program/lib/Roundcube/rcube_output.php | 20 +++++++++++++++-----
+ 2 files changed, 20 insertions(+), 5 deletions(-)
+
+diff --git a/program/actions/mail/get.php b/program/actions/mail/get.php
+index 4cf4797..335f417 100644
+--- a/program/actions/mail/get.php
++++ b/program/actions/mail/get.php
+@@ -259,6 +259,11 @@ class rcmail_action_mail_get extends rcmail_action_mail_index
+ $rcmail->gettext('allow'),
+ $rcmail->url(array_merge($_GET, ['_safe' => 1]))
+ );
++ } else {
++ // Use strict security policy to make sure no javascript is executed
++ // TODO: Make the above "blocked resources button" working with strict policy
++ // TODO: Move this to rcmail_html_page::write()?
++ header("Content-Security-Policy: script-src 'none'");
+ }
+ }
+
+diff --git a/program/lib/Roundcube/rcube_output.php b/program/lib/Roundcube/rcube_output.php
+index 8aa2bda..0a8b664 100644
+--- a/program/lib/Roundcube/rcube_output.php
++++ b/program/lib/Roundcube/rcube_output.php
+@@ -242,13 +242,20 @@ abstract class rcube_output
+ $ctype = $params['type'];
+ }
+
+- if ($disposition == 'inline' && stripos($ctype, 'text') === 0) {
+- $charset = $this->charset;
+- if (!empty($params['type_charset']) && rcube_charset::is_valid($params['type_charset'])) {
+- $charset = $params['type_charset'];
++ // Send unsafe content as plain text
++ if ($disposition == 'inline') {
++ if (preg_match('~(javascript|jscript|ecmascript|xml|html|text/)~i', $ctype)) {
++ $ctype = 'text/plain';
+ }
+
+- $ctype .= "; charset={$charset}";
++ if (stripos($ctype, 'text') === 0) {
++ $charset = $this->charset;
++ if (!empty($params['type_charset']) && rcube_charset::is_valid($params['type_charset'])) {
++ $charset = $params['type_charset'];
++ }
++
++ $ctype .= "; charset={$charset}";
++ }
+ }
+
+ if (is_string($filename) && strlen($filename) > 0 && strlen($filename) <= 1024) {
+@@ -278,6 +285,9 @@ abstract class rcube_output
+ header("Content-Length: " . $params['length']);
+ }
+
++ // Use strict security policy to make sure no javascript content is executed
++ header("Content-Security-Policy: default-src 'none'");
++
+ // don't kill the connection if download takes more than 30 sec.
+ if (!array_key_exists('time_limit', $params)) {
+ $params['time_limit'] = 3600;
diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42009.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42009.patch
--- roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42009.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42009.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,624 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:09:09 +0200
+Subject: Fix XSS vulnerability in post-processing of sanitized HTML content
+
+Credits to Oskar Zeino-Mahmalat (https://www.sonarsource.com)
+
+Origin: https://github.com/roundcube/roundcubemail/commit/68af7c864a36e1941764238dac440ab0d99a8d26
+Bug-Debian: https://bugs.debian.org/1077969
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42009
+---
+ program/actions/mail/compose.php | 36 ++----
+ program/actions/mail/index.php | 195 +++++++++++++-------------------
+ program/actions/mail/show.php | 4 -
+ program/actions/settings/index.php | 9 +-
+ program/lib/Roundcube/rcube_washtml.php | 50 ++++----
+ tests/Actions/Mail/Index.php | 71 +++++++-----
+ 6 files changed, 169 insertions(+), 196 deletions(-)
+
+diff --git a/program/actions/mail/compose.php b/program/actions/mail/compose.php
+index 1d5d33f..0494084 100644
+--- a/program/actions/mail/compose.php
++++ b/program/actions/mail/compose.php
+@@ -396,7 +396,7 @@ class rcmail_action_mail_compose extends rcmail_action_mail_index
+ // clean HTML message body which can be submitted by URL
+ if (!empty($COMPOSE['param']['body'])) {
+ if ($COMPOSE['param']['html'] = strpos($COMPOSE['param']['body'], '<') !== false) {
+- $wash_params = ['safe' => false, 'inline_html' => true];
++ $wash_params = ['safe' => false];
+ $COMPOSE['param']['body'] = self::prepare_html_body($COMPOSE['param']['body'], $wash_params);
+ }
+ }
+@@ -985,39 +985,25 @@ class rcmail_action_mail_compose extends rcmail_action_mail_index
+ static $part_no;
+
+ // Set attributes of the part container
+- $container_id = self::$COMPOSE['mode'] . 'body' . (++$part_no);
+- $container_attrib = ['id' => $container_id];
+- $body_args = [
+- 'safe' => self::$MESSAGE->is_safe,
+- 'plain' => false,
+- 'css_prefix' => 'v' . $part_no,
++ $container_id = self::$COMPOSE['mode'] . 'body' . (++$part_no);
++ $wash_params += [
++ 'safe' => self::$MESSAGE->is_safe,
++ 'css_prefix' => 'v' . $part_no,
++ 'add_comments' => false,
+ ];
+
+- // remove comments (produced by washtml)
+- $replace = ['/<!--[^>]+-->/' => ''];
+-
+ if (self::$COMPOSE['mode'] == rcmail_sendmail::MODE_DRAFT) {
+ // convert TinyMCE's empty-line sequence (#1490463)
+- $replace['/<p>\xC2\xA0<\/p>/'] = '<p><br /></p>';
+- // remove <body> tags
+- $replace['/<body([^>]*)>/i'] = '';
+- $replace['/<\/body>/i'] = '';
++ $body = preg_replace('/<p>\xC2\xA0<\/p>/', '<p><br /></p>', $body);
++ // remove <body> tags (not their content)
++ $wash_params['ignore_elements'] = ['body'];
+ }
+ else {
+- $body_args['container_id'] = $container_id;
+- $body_args['container_attrib'] = $container_attrib;
++ $wash_params['container_id'] = $container_id;
+ }
+
+ // Make the HTML content safe and clean
+- $body = self::wash_html($body, $wash_params + $body_args, self::$CID_MAP);
+- $body = preg_replace(array_keys($replace), array_values($replace), $body);
+- $body = self::html4inline($body, $body_args);
+-
+- if (self::$COMPOSE['mode'] != rcmail_sendmail::MODE_DRAFT) {
+- $body = html::div($container_attrib, $body);
+- }
+-
+- return $body;
++ return self::wash_html($body, $wash_params, self::$CID_MAP);
+ }
+
+ // Removes signature from the message body
+diff --git a/program/actions/mail/index.php b/program/actions/mail/index.php
+index e075359..9c54955 100644
+--- a/program/actions/mail/index.php
++++ b/program/actions/mail/index.php
+@@ -35,6 +35,7 @@ class rcmail_action_mail_index extends rcmail_action
+ protected static $PRINT_MODE = false;
+ protected static $REMOTE_OBJECTS;
+ protected static $SUSPICIOUS_EMAIL = false;
++ protected static $wash_html_body_attrs = [];
+
+ /**
+ * Request handler.
+@@ -936,13 +937,17 @@ class rcmail_action_mail_index extends rcmail_action
+ // clean HTML with washtml by Frederic Motte
+ $wash_opts = [
+ 'show_washed' => false,
++ 'add_comments' => $p['add_comments'] ?? true,
+ 'allow_remote' => $p['safe'],
+ 'blocked_src' => $rcmail->output->asset_url('program/resources/blocked.gif'),
+ 'charset' => RCUBE_CHARSET,
+ 'cid_map' => $cid_replaces,
+ 'html_elements' => ['body'],
+ 'css_prefix' => $p['css_prefix'],
++ 'ignore_elements' => $p['ignore_elements'] ?? [],
++ // internal configuration
+ 'container_id' => $p['container_id'],
++ 'body_class' => $p['body_class'] ?? '',
+ ];
+
+ if (empty($p['inline_html'])) {
+@@ -963,11 +968,25 @@ class rcmail_action_mail_index extends rcmail_action
+ // initialize HTML washer
+ $washer = new rcube_washtml($wash_opts);
+
++ self::$wash_html_body_attrs = [];
++
++ if (!empty($p['inline_html'])) {
++ $washer->add_callback('body', 'rcmail_action_mail_index::washtml_callback');
++
++ if ($wash_opts['body_class']) {
++ self::$wash_html_body_attrs['class'] = $wash_opts['body_class'];
++ }
++
++ if ($wash_opts['container_id']) {
++ self::$wash_html_body_attrs['id'] = $wash_opts['container_id'];
++ }
++ }
++
+ if (empty($p['skip_washer_form_callback'])) {
+ $washer->add_callback('form', 'rcmail_action_mail_index::washtml_callback');
+ }
+
+- // allow CSS styles, will be sanitized by rcmail_washtml_callback()
++ // allow CSS styles, will be sanitized by self::washtml_callback()
+ if (empty($p['skip_washer_style_callback'])) {
+ $washer->add_callback('style', 'rcmail_action_mail_index::washtml_callback');
+ }
+@@ -985,6 +1004,11 @@ class rcmail_action_mail_index extends rcmail_action
+ $html = $washer->wash($html);
+ self::$REMOTE_OBJECTS = $washer->extlinks;
+
++ // There was no <body>, but a wrapper element is required
++ if (!empty($p['inline_html']) && !empty(self::$wash_html_body_attrs)) {
++ $html = html::tag('div', self::$wash_html_body_attrs, $html);
++ }
++
+ return $html;
+ }
+
+@@ -1113,9 +1137,64 @@ class rcmail_action_mail_index extends rcmail_action
+ $washtml->extlinks = true;
+ }
+ else {
+- $out = html::tag('style', ['type' => 'text/css'], $decoded);
++ $out = $decoded;
++ }
++ }
++
++ if (strlen($out)) {
++ $css_prefix = $washtml->get_config('css_prefix');
++ $is_safe = $washtml->get_config('allow_remote');
++ $body_class = $washtml->get_config('body_class') ?: '';
++ $cont_id = $washtml->get_config('container_id') ?: '';
++ $cont_id = trim($cont_id . ($body_class ? " div.{$body_class}" : ''));
++
++ $out = rcube_utils::mod_css_styles($out, $cont_id, $is_safe, $css_prefix);
++
++ $out = html::tag('style', ['type' => 'text/css'], $out);
++ }
++
++ break;
++
++ case 'body':
++ $style = [];
++ $attrs = self::$wash_html_body_attrs;
++
++ foreach (html::parse_attrib_string($attrib) as $attr_name => $value) {
++ switch (strtolower($attr_name)) {
++ case 'bgcolor':
++ // Get bgcolor, we'll set it as background-color of the message container
++ if (preg_match('/^([a-z0-9#]+)$/i', $value, $m)) {
++ $style['background-color'] = $value;
++ }
++ break;
++ case 'text':
++ // Get text color, we'll set it as font color of the message container
++ if (preg_match('/^([a-z0-9#]+)$/i', $value, $m)) {
++ $style['color'] = $value;
++ }
++ break;
++ case 'background':
++ // Get background, we'll set it as background-image of the message container
++ if (preg_match('/^([^\s]+)$/', $value, $m)) {
++ $style['background-image'] = "url({$value})";
++ }
++ break;
++ default:
++ $attrs[$attr_name] = $value;
++ }
++ }
++
++ if (!empty($style)) {
++ foreach ($style as $idx => $val) {
++ $style[$idx] = $idx . ': ' . $val;
+ }
++
++ $attrs['style'] = ($attrs['style'] ? trim($attrs['style'], ';') . '; ' : '') . implode('; ', $style);
+ }
++
++ $out = html::tag('div', $attrs, $content);
++ self::$wash_html_body_attrs = [];
++ break;
+ }
+
+ return $out;
+@@ -1179,118 +1258,6 @@ class rcmail_action_mail_index extends rcmail_action
+ }
+ }
+
+- /**
+- * Modify a HTML message that it can be displayed inside a HTML page
+- */
+- public static function html4inline($body, &$args)
+- {
+- $last_pos = 0;
+- $is_safe = !empty($args['safe']);
+- $prefix = $args['css_prefix'] ?? null;
+- $cont_id = trim(
+- (!empty($args['container_id']) ? $args['container_id'] : '')
+- . (!empty($args['body_class']) ? ' div.' . $args['body_class'] : '')
+- );
+-
+- // find STYLE tags
+- while (($pos = stripos($body, '<style', $last_pos)) !== false && ($pos2 = stripos($body, '</style>', $pos+1))) {
+- $pos = strpos($body, '>', $pos) + 1;
+- $len = $pos2 - $pos;
+-
+- // replace all css definitions with #container [def]
+- $styles = substr($body, $pos, $len);
+- $styles = rcube_utils::mod_css_styles($styles, $cont_id, $is_safe, $prefix);
+-
+- $body = substr_replace($body, $styles, $pos, $len);
+- $last_pos = $pos2 + strlen($styles) - $len;
+- }
+-
+- $replace = [
+- // add comments around html and other tags
+- '/(<!DOCTYPE[^>]*>)/i' => '<!--\\1-->',
+- '/(<\?xml[^>]*>)/i' => '<!--\\1-->',
+- '/(<\/?html[^>]*>)/i' => '<!--\\1-->',
+- '/(<\/?head[^>]*>)/i' => '<!--\\1-->',
+- '/(<title[^>]*>.*<\/title>)/Ui' => '<!--\\1-->',
+- '/(<\/?meta[^>]*>)/i' => '<!--\\1-->',
+- // quote <? of php and xml files that are specified as text/html
+- '/<\?/' => '<?',
+- '/\?>/' => '?>',
+- ];
+-
+- $regexp = '/<body([^>]*)/';
+-
+- // Handle body attributes that doesn't play nicely with div elements
+- if (preg_match($regexp, $body, $m)) {
+- $style = [];
+- $attrs = $m[0];
+-
+- // Get bgcolor, we'll set it as background-color of the message container
+- if (!empty($m[1]) && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/i', $attrs, $mb)) {
+- $style['background-color'] = $mb[1];
+- $attrs = preg_replace('/\s?bgcolor=["\']*[a-z0-9#]+["\']*/i', '', $attrs);
+- }
+-
+- // Get text color, we'll set it as font color of the message container
+- if (!empty($m[1]) && preg_match('/text=["\']*([a-z0-9#]+)["\']*/i', $attrs, $mb)) {
+- $style['color'] = $mb[1];
+- $attrs = preg_replace('/\s?text=["\']*[a-z0-9#]+["\']*/i', '', $attrs);
+- }
+-
+- // Get background, we'll set it as background-image of the message container
+- if (!empty($m[1]) && preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) {
+- $style['background-image'] = 'url('.$mb[1].')';
+- $attrs = preg_replace('/\s?background=["\']*([^"\'>\s]+)["\']*/', '', $attrs);
+- }
+-
+- if (!empty($style)) {
+- $body = preg_replace($regexp, rtrim($attrs), $body, 1);
+- }
+-
+- // handle body styles related to background image
+- if (!empty($style['background-image'])) {
+- // get body style
+- if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) {
+- // get background related style
+- $regexp = '/(background-position|background-repeat)\s*:\s*([^;]+);/i';
+- if (preg_match_all($regexp, $m[1], $matches, PREG_SET_ORDER)) {
+- foreach ($matches as $m) {
+- $style[$m[1]] = $m[2];
+- }
+- }
+- }
+- }
+-
+- if (!empty($style)) {
+- foreach ($style as $idx => $val) {
+- $style[$idx] = $idx . ': ' . $val;
+- }
+-
+- $args['container_attrib']['style'] = implode('; ', $style);
+- }
+-
+- // replace <body> with <div>
+- if (!empty($args['body_class'])) {
+- $replace['/<body([^>]*)>/i'] = '<div class="' . $args['body_class'] . '"\\1>';
+- }
+- else {
+- $replace['/<body/i'] = '<div';
+- }
+-
+- $replace['/<\/body>/i'] = '</div>';
+- }
+- // make sure there's 'rcmBody' div, we need it for proper css modification
+- // its name is hardcoded in self::message_body() also
+- else if (!empty($args['body_class'])) {
+- $body = '<div class="' . $args['body_class'] . '">' . $body . '</div>';
+- }
+-
+- // Clean up, and replace <body> with <div>
+- $body = preg_replace(array_keys($replace), array_values($replace), $body);
+-
+- return $body;
+- }
+-
+ /**
+ * Parse link (a, link, area) attributes and set correct target
+ */
+diff --git a/program/actions/mail/show.php b/program/actions/mail/show.php
+index ff798b8..14af62f 100644
+--- a/program/actions/mail/show.php
++++ b/program/actions/mail/show.php
+@@ -742,10 +742,6 @@ class rcmail_action_mail_show extends rcmail_action_mail_index
+ $rcmail->output->set_env('is_pgp_content', '#' . $container_id);
+ }
+
+- if ($part->ctype_secondary == 'html') {
+- $body = self::html4inline($body, $body_args);
+- }
+-
+ $out .= html::div($body_args['container_attrib'], $plugin['prefix'] . $body);
+ }
+ }
+diff --git a/program/actions/settings/index.php b/program/actions/settings/index.php
+index fdc3471..8e67e19 100644
+--- a/program/actions/settings/index.php
++++ b/program/actions/settings/index.php
+@@ -1774,6 +1774,8 @@ class rcmail_action_settings_index extends rcmail_action
+ 'charset' => RCUBE_CHARSET,
+ 'html_elements' => ['body', 'link'],
+ 'html_attribs' => ['rel', 'type'],
++ 'ignore_elements' => ['body'],
++ 'add_comments' => false,
+ ];
+
+ // initialize HTML washer
+@@ -1782,11 +1784,6 @@ class rcmail_action_settings_index extends rcmail_action
+ // Remove non-UTF8 characters (#1487813)
+ $html = rcube_charset::clean($html);
+
+- $html = $washer->wash($html);
+-
+- // remove unwanted comments and tags (produced by washtml)
+- $html = preg_replace(['/<!--[^>]+-->/', '/<\/?body>/'], '', $html);
+-
+- return $html;
++ return $washer->wash($html);
+ }
+ }
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index 3041842..a07b2d2 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -164,7 +164,14 @@ class rcube_washtml
+ public $extlinks = false;
+
+ /** @var array Current settings */
+- private $config = [];
++ private $config = [
++ 'add_comments' => true,
++ 'allow_remote' => false,
++ 'base_url' => '',
++ 'charset' => RCUBE_CHARSET,
++ 'cid_map' => [],
++ 'show_washed' => true,
++ ];
+
+ /** @var array Registered callback functions for tags */
+ private $handlers = [];
+@@ -225,11 +232,7 @@ class rcube_washtml
+
+ unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['void_elements'], $p['css_prefix']);
+
+- $this->config = $p + ['show_washed' => true, 'allow_remote' => false, 'cid_map' => [], 'base_url' => ''];
+-
+- if (!isset($this->config['charset'])) {
+- $this->config['charset'] = RCUBE_CHARSET;
+- }
++ $this->config = array_merge($this->config, $p);
+ }
+
+ /**
+@@ -579,7 +582,7 @@ class rcube_washtml
+ );
+ }
+
+- return '<!-- ignored -->';
++ return $this->config['add_comments'] ? '<!-- ignored -->' : '';
+ }
+
+ $node = $node->firstChild;
+@@ -593,7 +596,9 @@ class rcube_washtml
+ if ($tagName == 'link') {
+ $uri = $this->wash_uri($node->getAttribute('href'), false, false);
+ if (!$uri) {
+- $dump .= '<!-- link ignored -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- link ignored -->';
++ }
+ break;
+ }
+
+@@ -603,7 +608,9 @@ class rcube_washtml
+ && self::attribute_value($node, 'attributename', 'href')
+ ) {
+ // Insecure svg tags
+- $dump .= "<!-- $tagName blocked -->";
++ if ($this->config['add_comments']) {
++ $dump .= "<!-- {$tagName} blocked -->";
++ }
+ break;
+ }
+
+@@ -614,13 +621,13 @@ class rcube_washtml
+ }
+ else if (isset($this->_html_elements[$tagName])) {
+ $content = $this->dumpHtml($node, $level);
+- $dump .= '<' . $node->nodeName;
++ $tag = '<' . $node->nodeName;
+
+ if ($tagName == 'svg') {
+ $xpath = new DOMXPath($node->ownerDocument);
+ foreach ($xpath->query('namespace::*') as $ns) {
+ if ($ns->nodeName != 'xmlns:xml') {
+- $dump .= sprintf(' %s="%s"',
++ $tag .= sprintf(' %s="%s"',
+ $ns->nodeName,
+ htmlspecialchars($ns->nodeValue, ENT_QUOTES, $this->config['charset'])
+ );
+@@ -631,20 +638,25 @@ class rcube_washtml
+ $content = htmlspecialchars($content, ENT_QUOTES | ENT_SUBSTITUTE, $this->config['charset']);
+ }
+
+- $dump .= $this->wash_attribs($node);
++ $tag .= $this->wash_attribs($node);
+
+- if ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) {
+- $dump .= ' />';
+- }
+- else {
+- $dump .= '>' . $content . '</' . $node->nodeName . '>';
++ if (isset($this->_ignore_elements[$tagName])) {
++ $dump .= $content;
++ } elseif ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) {
++ $dump .= $tag . ' />';
++ } else {
++ $dump .= $tag . '>' . $content . '</' . $node->nodeName . '>';
+ }
+ }
+ else if (isset($this->_ignore_elements[$tagName])) {
+- $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' not allowed -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' not allowed -->';
++ }
+ }
+ else {
+- $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' ignored -->';
++ if ($this->config['add_comments']) {
++ $dump .= '<!-- ' . htmlspecialchars($node->nodeName, ENT_QUOTES, $this->config['charset']) . ' ignored -->';
++ }
+ $dump .= $this->dumpHtml($node, $level); // ignore tags not its content
+ }
+ break;
+diff --git a/tests/Actions/Mail/Index.php b/tests/Actions/Mail/Index.php
+index f719793..b3ae049 100644
+--- a/tests/Actions/Mail/Index.php
++++ b/tests/Actions/Mail/Index.php
+@@ -327,12 +327,11 @@ class Actions_Mail_Index extends ActionTestCase
+ $part = $this->get_html_part('src/htmlbody.txt');
+ $part->replaces = ['ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg'];
+
+- $params = ['container_id' => 'foo'];
++ $params = ['container_id' => 'foo', 'safe' => false];
+
+ // render HTML in normal mode
+- $body = rcmail_action_mail_index::print_body($part->body, $part, ['safe' => false]);
+- $html = rcmail_action_mail_index::html4inline($body, $params);
+-
++ $html = \rcmail_action_mail_index::print_body($part->body, $part, $params);
++
+ $this->assertMatchesRegularExpression('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
+ $this->assertMatchesRegularExpression('#background="program/resources/blocked.gif"#', $html, "Replace external background image");
+ $this->assertDoesNotMatchRegularExpression('/ex3.jpg/', $html, "No references to external images");
+@@ -344,8 +343,8 @@ class Actions_Mail_Index extends ActionTestCase
+ // $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
+
+ // render HTML in safe mode
+- $body = rcmail_action_mail_index::print_body($part->body, $part, ['safe' => true]);
+- $html = rcmail_action_mail_index::html4inline($body, $params);
++ $params['safe'] = true;
++ $html = \rcmail_action_mail_index::print_body($part->body, $part, $params);
+
+ $this->assertMatchesRegularExpression('/<style [^>]+>/', $html, "Allow styles in safe mode");
+ $this->assertMatchesRegularExpression('#src="http://evilsite.net/mailings/ex3.jpg"#', $html, "Allow external images in HTML (safe mode)");
+@@ -362,15 +361,12 @@ class Actions_Mail_Index extends ActionTestCase
+ $this->initOutput(rcmail_action::MODE_HTTP, 'mail', '');
+
+ $part = $this->get_html_part('src/htmlxss.txt');
+- $washed = rcmail_action_mail_index::print_body($part->body, $part, ['safe' => true]);
+-
+- $this->assertDoesNotMatchRegularExpression('/src="skins/', $washed, "Remove local references");
+- $this->assertDoesNotMatchRegularExpression('/\son[a-z]+/', $washed, "Remove on* attributes");
+- $this->assertStringNotContainsString('onload', $washed, "Handle invalid style");
+-
+- $params = ['container_id' => 'foo'];
+- $html = rcmail_action_mail_index::html4inline($washed, $params);
++ $params = ['container_id' => 'foo', 'safe' => true];
++ $html = \rcmail_action_mail_index::print_body($part->body, $part, $params);
+
++ $this->assertDoesNotMatchRegularExpression('/src="skins/', $html, 'Remove local references');
++ $this->assertDoesNotMatchRegularExpression('/\son[a-z]+/', $html, 'Remove on* attributes');
++ $this->assertStringNotContainsString('onload', $html, 'Handle invalid style');
+ $this->assertDoesNotMatchRegularExpression('/onclick="return rcmail.command(\'compose\',\'xss at somehost.net\',this)"/', $html, "Clean mailto links");
+ $this->assertDoesNotMatchRegularExpression('/alert/', $html, "Remove alerts");
+ }
+@@ -385,8 +381,7 @@ class Actions_Mail_Index extends ActionTestCase
+
+ $part = $this->get_html_part('src/BID-26800.txt');
+ $params = ['container_id' => 'dabody', 'safe' => true];
+- $body = rcmail_action_mail_index::print_body($part->body, $part, ['safe' => true]);
+- $washed = rcmail_action_mail_index::html4inline($body, $params);
++ $washed = \rcmail_action_mail_index::print_body($part->body, $part, $params);
+
+ $this->assertDoesNotMatchRegularExpression('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
+ $this->assertDoesNotMatchRegularExpression('/font-style:italic/', $washed, "Allow valid styles");
+@@ -408,20 +403,40 @@ class Actions_Mail_Index extends ActionTestCase
+ $this->assertDoesNotMatchRegularExpression('/vbscript:/', $washed, "Remove vbscript: links");
+ }
+
++ /**
++ * Test that HTML sanitization does not change attribute (evil) values
++ */
++ public function test_html_body_attributes()
++ {
++ $part = $this->get_html_part();
++ $part->body = '<body title="bgcolor=foo" name="bar style=animation-name:progress-bar-stripes onanimationstart=alert(origin) foo=bar">Foo</body>';
++
++ $params = ['safe' => true, 'add_comments' => false];
++ $washed = \rcmail_action_mail_index::print_body($part->body, $part, $params);
++
++ $this->assertSame(str_replace('body', 'div', $part->body), $washed);
++
++ $params['inline_html'] = false;
++ $washed = \rcmail_action_mail_index::print_body($part->body, $part, $params);
++
++ $this->assertSame('<html><head></head>' . $part->body . '</html>', $washed);
++ }
++
+ /**
+ * Test handling of body style attributes
+ */
+- function test_html4inline_body_style()
++ public function test_wash_html_body_style()
+ {
+- $html = '<body background="test" bgcolor="#fff" style="font-size:11px" text="#000"><p>test</p></body>';
+- $params = ['container_id' => 'foo'];
+- $html = rcmail_action_mail_index::html4inline($html, $params);
++ $html = '<body background="http://test.com/image" bgcolor="#fff" style="font-size: 11px" text="#000"><p>test</p></body>';
++ $params = ['container_id' => 'foo', 'add_comments' => false, 'safe' => false];
++ $washed = \rcmail_action_mail_index::wash_html($html, $params, []);
++
++ $this->assertSame('<div id="foo" style="font-size: 11px; background-image: url(program/resources/blocked.gif); background-color: #fff; color: #000"><p>test</p></div>', $washed);
+
+- $this->assertMatchesRegularExpression('/<div style="font-size:11px">/', $html, "Body attributes");
+- $this->assertArrayHasKey('container_attrib', $params, "'container_attrib' param set");
+- $this->assertMatchesRegularExpression('/background-color: #fff;/', $params['container_attrib']['style'], "Body style (bgcolor)");
+- $this->assertMatchesRegularExpression('/background-image: url\(test\)/', $params['container_attrib']['style'], "Body style (background)");
+- $this->assertMatchesRegularExpression('/color: #000/', $params['container_attrib']['style'], "Body style (text)");
++ $params['safe'] = true;
++ $washed = \rcmail_action_mail_index::wash_html($html, $params, []);
++
++ $this->assertSame('<div id="foo" style="font-size: 11px; background-image: url(http://test.com/image); background-color: #fff; color: #000"><p>test</p></div>', $washed);
+ }
+
+ /**
+@@ -447,6 +462,7 @@ class Actions_Mail_Index extends ActionTestCase
+
+ $meta = '<meta charset="'.RCUBE_CHARSET.'" />';
+ $args = [
++ 'inline_html' => false,
+ 'html_elements' => ['html', 'body', 'meta', 'head'],
+ 'html_attribs' => ['charset'],
+ ];
+@@ -515,11 +531,10 @@ class Actions_Mail_Index extends ActionTestCase
+ $this->initOutput(rcmail_action::MODE_HTTP, 'mail', '');
+
+ $part = $this->get_html_part('src/mailto.txt');
+- $params = ['container_id' => 'foo'];
++ $params = ['container_id' => 'foo', 'safe' => false];
+
+ // render HTML in normal mode
+- $body = rcmail_action_mail_index::print_body($part->body, $part, ['safe' => false]);
+- $html = rcmail_action_mail_index::html4inline($body, $params);
++ $html = \rcmail_action_mail_index::print_body($part->body, $part, $params);
+
+ $mailto = '<a href="mailto:me at me.com"'
+ .' onclick="return rcmail.command(\'compose\',\'me at me.com?subject=this is the subject&body=this is the body\',this)" rel="noreferrer">e-mail</a>';
diff -Nru roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42010.patch roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42010.patch
--- roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42010.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/CVE-2024-42010.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,408 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sat, 3 Aug 2024 09:24:19 +0200
+Subject: Fix information leak (access to remote content) via insufficient CSS
+ filtering
+
+Credits to Oskar Zeino-Mahmalat (Sonar) https://www.sonarsource.com
+
+Origin: https://github.com/roundcube/roundcubemail/commit/602d0f566eb39b6dcb739ad78323ec434a3b92ce
+Bug-Debian: https://bugs.debian.org/1077969
+Bug-Debian: https://security-tracker.debian.org/tracker/CVE-2024-42010
+---
+ program/lib/Roundcube/rcube_utils.php | 157 ++++++++++++++++++++++++--------
+ program/lib/Roundcube/rcube_washtml.php | 80 ++--------------
+ tests/Framework/Utils.php | 52 ++++++++---
+ tests/Framework/Washtml.php | 2 +-
+ 4 files changed, 166 insertions(+), 125 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php
+index 2724c01..6811553 100644
+--- a/program/lib/Roundcube/rcube_utils.php
++++ b/program/lib/Roundcube/rcube_utils.php
+@@ -416,23 +416,41 @@ class rcube_utils
+ */
+ public static function mod_css_styles($source, $container_id, $allow_remote = false, $prefix = '')
+ {
+- $last_pos = 0;
+- $replacements = new rcube_string_replacer;
+-
+- // ignore the whole block if evil styles are detected
+ $source = self::xss_entity_decode($source);
+- $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
+- $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\((?!data:image)' : '');
+
+- if (preg_match("/$evilexpr/i", $stripped)) {
++ // No @import allowed
++ // TODO: We should just remove it, not invalidate the whole content
++ if (stripos($source, '@import') !== false) {
+ return '/* evil! */';
+ }
+
+- $strict_url_regexp = '!url\s*\(\s*["\']?(https?:)//[a-z0-9/._+-]+["\']?\s*\)!Uims';
++ // Incomplete style expression
++ if (strpos($source, '{') === false) {
++ return '/* invalid! */';
++ }
++
++ // To prevent from a double-escaping tricks we consider a script with
++ // any escape sequences (after de-escaping them above) an evil script.
++ // This probably catches many valid scripts, but we\'re on the safe side.
++ if (preg_match('/\\\[0-9a-fA-F]{2}/', $source)) {
++ return '/* evil! */';
++ }
+
+ // remove html comments
+ $source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
+
++ $url_callback = static function ($url) use ($allow_remote) {
++ if (strpos($url, 'data:image') === 0) {
++ return $url;
++ }
++ if ($allow_remote && preg_match('|^https?://[a-z0-9/._+-]+$|i', $url)) {
++ return $url;
++ }
++ };
++
++ $last_pos = 0;
++ $replacements = new rcube_string_replacer();
++
+ // cut out all contents between { and }
+ while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+ $nested = strpos($source, '{', $pos+1);
+@@ -441,36 +459,9 @@ class rcube_utils
+ }
+ $length = $pos2 - $pos - 1;
+ $styles = substr($source, $pos+1, $length);
+- $output = '';
+-
+- // check every css rule in the style block...
+- foreach (self::parse_css_block($styles) as $rule) {
+- // Remove 'page' attributes (#7604)
+- if ($rule[0] == 'page') {
+- continue;
+- }
+-
+- // Convert position:fixed to position:absolute (#5264)
+- if ($rule[0] == 'position' && strcasecmp($rule[1], 'fixed') === 0) {
+- $rule[1] = 'absolute';
+- }
+- else if ($allow_remote) {
+- $stripped = preg_replace('/[^a-z\(:;]/i', '', $rule[1]);
+-
+- // allow data:image and strict url() values only
+- if (
+- stripos($stripped, 'url(') !== false
+- && stripos($stripped, 'url(data:image') === false
+- && !preg_match($strict_url_regexp, $rule[1])
+- ) {
+- $rule[1] = '/* evil! */';
+- }
+- }
+-
+- $output .= sprintf(" %s: %s;", $rule[0] , $rule[1]);
+- }
++ $styles = self::sanitize_css_block($styles, $url_callback);
+
+- $key = $replacements->add($output . ' ');
++ $key = $replacements->add(strlen($styles) ? " {$styles} " : '');
+ $repl = $replacements->get_replacement($key);
+ $source = substr_replace($source, $repl, $pos+1, $length);
+ $last_pos = $pos2 - ($length - strlen($repl));
+@@ -518,6 +509,63 @@ class rcube_utils
+ return $source;
+ }
+
++ /**
++ * Parse and sanitize single CSS block
++ *
++ * @param string $styles CSS styles block
++ * @param ?callable $url_callback URL validator callback
++ *
++ * @return string
++ */
++ public static function sanitize_css_block($styles, $url_callback = null)
++ {
++ $output = [];
++
++ // check every css rule in the style block...
++ foreach (self::parse_css_block($styles) as $rule) {
++ $property = $rule[0];
++ $value = $rule[1];
++
++ if ($property == 'page') {
++ // Remove 'page' attributes (#7604)
++ continue;
++ } elseif ($property == 'position' && strcasecmp($value, 'fixed') === 0) {
++ // Convert position:fixed to position:absolute (#5264)
++ $value = 'absolute';
++ } elseif (preg_match('/expression|image-set/i', $value)) {
++ continue;
++ } else {
++ $value = '';
++ foreach (self::explode_css_property_block($rule[1]) as $val) {
++ if ($url_callback && preg_match('/^url\s*\(/i', $val)) {
++ if (preg_match('/^url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
++ if ($url = $url_callback($match[1])) {
++ $value .= ' url(' . $url . ')';
++ }
++ }
++ } else {
++ // whitelist ?
++ $value .= ' ' . $val;
++
++ // #1488535: Fix size units, so width:800 would be changed to width:800px
++ if ($val
++ && preg_match('/^(left|right|top|bottom|width|height)/i', $property)
++ && preg_match('/^[0-9]+$/', $val)
++ ) {
++ $value .= 'px';
++ }
++ }
++ }
++ }
++
++ if (strlen($value)) {
++ $output[] = $property . ': ' . trim($value);
++ }
++ }
++
++ return count($output) > 0 ? implode('; ', $output) . ';' : '';
++ }
++
+ /**
+ * Explode css style. Property names will be lower-cased and trimmed.
+ * Values will be trimmed. Invalid entries will be skipped.
+@@ -590,6 +638,41 @@ class rcube_utils
+ return $result;
+ }
+
++ /**
++ * Explode css style value
++ *
++ * @param string $style CSS style
++ *
++ * @return array List of CSS values
++ */
++ public static function explode_css_property_block($style)
++ {
++ $style = preg_replace('/\s+/', ' ', $style);
++ $result = [];
++ $strlen = strlen($style);
++ $q = false;
++
++ // explode value
++ for ($p = $i = 0; $i < $strlen; $i++) {
++ if (($style[$i] == '"' || $style[$i] == "'") && ($i == 0 || $style[$i - 1] != '\\')) {
++ if ($q == $style[$i]) {
++ $q = false;
++ } elseif (!$q) {
++ $q = $style[$i];
++ }
++ }
++
++ if (!$q && $style[$i] == ' ' && ($i == 0 || !preg_match('/[,\(]/', $style[$i - 1]))) {
++ $result[] = substr($style, $p, $i - $p);
++ $p = $i + 1;
++ }
++ }
++
++ $result[] = (string) substr($style, $p);
++
++ return $result;
++ }
++
+ /**
+ * Generate CSS classes from mimetype and filename extension
+ *
+diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php
+index a07b2d2..e9dcea4 100644
+--- a/program/lib/Roundcube/rcube_washtml.php
++++ b/program/lib/Roundcube/rcube_washtml.php
+@@ -257,48 +257,19 @@ class rcube_washtml
+ {
+ $result = [];
+
+- // Remove unwanted white-space characters so regular expressions below work better
+- $style = preg_replace('/[\n\r\s\t]+/', ' ', $style);
+-
+ // Decode insecure character sequences
+ $style = rcube_utils::xss_entity_decode($style);
+
+- foreach (rcube_utils::parse_css_block($style) as $rule) {
+- $cssid = $rule[0];
+- $value = '';
+-
+- foreach ($this->explode_style($rule[1]) as $val) {
+- if (preg_match('/^url\(/i', $val)) {
+- if (preg_match('/^url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
+- if ($url = $this->wash_uri($match[1])) {
+- $value .= ' url(' . htmlspecialchars($url, ENT_QUOTES, $this->config['charset']) . ')';
+- }
+- }
+- }
+- else if (!preg_match('/^(behavior|expression)/i', $val)) {
+- // Set position:fixed to position:absolute for security (#5264)
+- if (!strcasecmp($cssid, 'position') && !strcasecmp($val, 'fixed')) {
+- $val = 'absolute';
+- }
+-
+- // whitelist ?
+- $value .= ' ' . $val;
+-
+- // #1488535: Fix size units, so width:800 would be changed to width:800px
+- if (preg_match('/^(left|right|top|bottom|width|height)/i', $cssid)
+- && preg_match('/^[0-9]+$/', $val)
+- ) {
+- $value .= 'px';
+- }
+- }
+- }
++ // Remove unwanted white-space characters
++ $style = preg_replace('/[\n\r\t]+/', ' ', $style);
+
+- if (isset($value[0])) {
+- $result[] = $cssid . ': ' . trim($value);
++ $uri_callback = function ($uri) {
++ if ($uri = $this->wash_uri($uri)) {
++ return htmlspecialchars($uri, \ENT_QUOTES, $this->config['charset']);
+ }
+- }
+-
+- return implode('; ', $result);
++ };
++
++ return rtrim(rcube_utils::sanitize_css_block($style, $uri_callback), ';');
+ }
+
+ /**
+@@ -990,39 +961,4 @@ class rcube_washtml
+
+ return $html;
+ }
+-
+- /**
+- * Explode css style value
+- *
+- * @param string $style CSS style
+- *
+- * @return array List of CSS rules
+- */
+- protected function explode_style($style)
+- {
+- $result = [];
+- $strlen = strlen($style);
+- $q = false;
+-
+- // explode value
+- for ($p = $i = 0; $i < $strlen; $i++) {
+- if (($style[$i] == "\"" || $style[$i] == "'") && ($i == 0 || $style[$i-1] != "\\")) {
+- if ($q == $style[$i]) {
+- $q = false;
+- }
+- else if (!$q) {
+- $q = $style[$i];
+- }
+- }
+-
+- if (!$q && $style[$i] == ' ' && ($i == 0 || !preg_match('/[,\(]/', $style[$i-1]))) {
+- $result[] = substr($style, $p, $i - $p);
+- $p = $i + 1;
+- }
+- }
+-
+- $result[] = (string) substr($style, $p);
+-
+- return $result;
+- }
+ }
+diff --git a/tests/Framework/Utils.php b/tests/Framework/Utils.php
+index 7ea6ffa..4cd2750 100644
+--- a/tests/Framework/Utils.php
++++ b/tests/Framework/Utils.php
+@@ -217,26 +217,48 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+ */
+ function test_mod_css_styles_xss()
+ {
+- $mod = rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No url() values allowed");
++ $mod = \rcube_utils::mod_css_styles('font-size: 1em;', 'rcmbody');
++ $this->assertSame('/* invalid! */', $mod);
+
+ $mod = rcube_utils::mod_css_styles("@import url('http://localhost/somestuff/css/master.css');", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No import statements");
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("left:expression(document.body.offsetWidth-20)", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "No expression properties");
++ $mod = \rcube_utils::mod_css_styles("@\\69mport url('http://localhost/somestuff/css/master.css');", 'rcmbody');
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("left:exp/* */ression( alert('xss3') )", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks");
++ $mod = \rcube_utils::mod_css_styles("@\\5C 69mport url('http://localhost/somestuff/css/master.css'); a { color: red; }", '');
++ $this->assertSame('/* evil! */', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background:\\0075\\0072\\00006c( javascript:alert('xss') )", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (2)");
++ $mod = \rcube_utils::mod_css_styles("body.main2cols { background-image: url('../images/leftcol.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody.main2cols {}', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background: \\75 \\72 \\6C ('/images/img.png')", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (3)");
++ $mod = \rcube_utils::mod_css_styles('p { left:expression(document.body.offsetWidth-20); }', 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
+
+- $mod = rcube_utils::mod_css_styles("background: u\\r\\l('/images/img.png')", 'rcmbody');
+- $this->assertEquals("/* evil! */", $mod, "Don't allow encoding quirks (4)");
++ $mod = \rcube_utils::mod_css_styles('p { left:exp/* */ression( alert('xss3') ); }', 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background:\\0075\\0072\\00006c('//evil.com/test'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: \\75 \\72 \\6C ('/images/img.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: u\\r\\l('/images/img2.png'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: url('//żą.ść?data:image&leak') }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles("p { background: url('//data:image&leak'); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ // Note: This looks to me like a bug in browsers, for now we don't allow image-set at all
++ $mod = \rcube_utils::mod_css_styles("p { background: image-set('//evil.com/img.png' 1x); }", 'rcmbody');
++ $this->assertSame('#rcmbody p {}', $mod);
++
++ $mod = \rcube_utils::mod_css_styles('p { background: none !important; }', 'rcmbody');
++ $this->assertSame('#rcmbody p { background: none !important; }', $mod);
+
+ // position: fixed (#5264)
+ $mod = rcube_utils::mod_css_styles(".test { position: fixed; }", 'rcmbody');
+@@ -278,9 +300,9 @@ class Framework_Utils extends PHPUnit\Framework\TestCase
+ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
+ $this->assertSame("#rcmbody { color: red; }", $mod);
+
+- $style = "body { background:url(alert('URL!') ) }";
++ $style = "body { background:url(alert('URL!')); }";
+ $mod = rcube_utils::mod_css_styles($style, 'rcmbody', true);
+- $this->assertSame("#rcmbody { background: /* evil! */; }", $mod);
++ $this->assertSame("#rcmbody {}", $mod);
+ }
+
+ /**
+diff --git a/tests/Framework/Washtml.php b/tests/Framework/Washtml.php
+index 6bfbc14..4fdae1a 100644
+--- a/tests/Framework/Washtml.php
++++ b/tests/Framework/Washtml.php
+@@ -289,7 +289,7 @@ class Framework_Washtml extends PHPUnit\Framework\TestCase
+ $washer = new rcube_washtml;
+ $washed = $washer->wash($html);
+
+- $this->assertTrue(strpos($washed, $expected) !== false, "White-space and new-line characters handling");
++ $this->assertSame($this->cleanupResult($washed), $expected, 'White-space and new-line characters handling');
+ }
+
+ /**
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,223 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sun, 21 Jul 2024 14:23:28 +0200
+Subject: Fix bug where an unhandled exception was caused by an invalid image
+ attachment
+
+GD functions may throw ValueError in some cases since PHP 8.0.
+We wrap them in try/catch blocks.
+
+Origin: https://github.com/roundcube/roundcubemail/commit/9d9f4d6926e16e9acd46231ee6d03695d058565a
+Bug: https://github.com/roundcube/roundcubemail/issues/9475
+---
+ program/lib/Roundcube/rcube_image.php | 179 +++++++++++++++++-----------------
+ 1 file changed, 88 insertions(+), 91 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php
+index 1b980c8..d034c17 100644
+--- a/program/lib/Roundcube/rcube_image.php
++++ b/program/lib/Roundcube/rcube_image.php
+@@ -217,87 +217,85 @@ class rcube_image
+
+ // use GD extension
+ if ($props['gd_type'] && $props['width'] > 0 && $props['height'] > 0) {
+- if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
+- $image = @imagecreatefromjpeg($this->image_file);
+- $type = 'jpg';
+- }
+- else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
+- $image = @imagecreatefromgif($this->image_file);
+- $type = 'gif';
+- }
+- else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
+- $image = @imagecreatefrompng($this->image_file);
+- $type = 'png';
+- }
+- else {
+- // @TODO: print error to the log?
+- return false;
+- }
++ try {
++ if ($props['gd_type'] == \IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
++ $image = imagecreatefromjpeg($this->image_file);
++ $type = 'jpg';
++ } elseif ($props['gd_type'] == \IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
++ $image = imagecreatefromgif($this->image_file);
++ $type = 'gif';
++ } elseif ($props['gd_type'] == \IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
++ $image = imagecreatefrompng($this->image_file);
++ $type = 'png';
++ } else {
++ // @TODO: print error to the log?
++ return false;
++ }
+
+- if ($image === false) {
+- return false;
+- }
++ if ($image === false) {
++ return false;
++ }
+
+- $scale = $size / max($props['width'], $props['height']);
++ $scale = $size / max($props['width'], $props['height']);
+
+- // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
+- // we do the same here, if an image is smaller than specified size
+- // we do nothing but copy original file to destination file
+- if ($scale >= 1) {
+- $result = $this->image_file == $filename || copy($this->image_file, $filename);
+- }
+- else {
+- $width = intval($props['width'] * $scale);
+- $height = intval($props['height'] * $scale);
+- $new_image = imagecreatetruecolor($width, $height);
++ // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
++ // we do the same here, if an image is smaller than specified size
++ // we do nothing but copy original file to destination file
++ if ($scale >= 1) {
++ $result = $this->image_file == $filename || copy($this->image_file, $filename);
++ } else {
++ $width = intval($props['width'] * $scale);
++ $height = intval($props['height'] * $scale);
++ $new_image = imagecreatetruecolor($width, $height);
+
+- if ($new_image === false) {
+- return false;
+- }
++ if ($new_image === false) {
++ return false;
++ }
+
+- // Fix transparency of gif/png image
+- if ($props['gd_type'] != IMAGETYPE_JPEG) {
+- imagealphablending($new_image, false);
+- imagesavealpha($new_image, true);
+- $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
+- imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
+- }
++ // Fix transparency of gif/png image
++ if ($props['gd_type'] != \IMAGETYPE_JPEG) {
++ imagealphablending($new_image, false);
++ imagesavealpha($new_image, true);
++ $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
++ imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
++ }
+
+- imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
+- $image = $new_image;
+-
+- // fix orientation of image if EXIF data exists and specifies orientation (GD strips the EXIF data)
+- if ($this->image_file && $type == 'jpg' && function_exists('exif_read_data')) {
+- $exif = @exif_read_data($this->image_file);
+- if ($exif && !empty($exif['Orientation'])) {
+- switch ($exif['Orientation']) {
+- case 3:
+- $image = imagerotate($image, 180, 0);
+- break;
+- case 6:
+- $image = imagerotate($image, -90, 0);
+- break;
+- case 8:
+- $image = imagerotate($image, 90, 0);
+- break;
++ imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
++ $image = $new_image;
++
++ // fix orientation of image if EXIF data exists and specifies orientation (GD strips the EXIF data)
++ if ($this->image_file && $type == 'jpg' && function_exists('exif_read_data')) {
++ $exif = @exif_read_data($this->image_file);
++ if ($exif && !empty($exif['Orientation'])) {
++ switch ($exif['Orientation']) {
++ case 3:
++ $image = imagerotate($image, 180, 0);
++ break;
++ case 6:
++ $image = imagerotate($image, -90, 0);
++ break;
++ case 8:
++ $image = imagerotate($image, 90, 0);
++ break;
++ }
+ }
+ }
+- }
+
+- if ($props['gd_type'] == IMAGETYPE_JPEG) {
+- $result = imagejpeg($image, $filename, 75);
+- }
+- elseif($props['gd_type'] == IMAGETYPE_GIF) {
+- $result = imagegif($image, $filename);
+- }
+- elseif($props['gd_type'] == IMAGETYPE_PNG) {
+- $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
++ if ($props['gd_type'] == \IMAGETYPE_JPEG) {
++ $result = imagejpeg($image, $filename, 75);
++ } elseif ($props['gd_type'] == \IMAGETYPE_GIF) {
++ $result = imagegif($image, $filename);
++ } elseif ($props['gd_type'] == \IMAGETYPE_PNG) {
++ $result = imagepng($image, $filename, 6, \PNG_ALL_FILTERS);
++ }
+ }
+- }
+
+- if ($result) {
+- @chmod($filename, 0600);
+- return $type;
++ if ($result) {
++ @chmod($filename, 0600);
++ return $type;
++ }
++ } catch (Throwable $e) {
++ rcube::raise_error($e, true, false);
+ }
+ }
+
+@@ -371,28 +369,27 @@ class rcube_image
+ }
+
+ if ($props['gd_type']) {
+- if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
+- $image = imagecreatefromjpeg($this->image_file);
+- }
+- else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
+- $image = imagecreatefromgif($this->image_file);
+- }
+- else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
+- $image = imagecreatefrompng($this->image_file);
+- }
+- else {
+- // @TODO: print error to the log?
+- return false;
+- }
++ try {
++ if ($props['gd_type'] == \IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
++ $image = imagecreatefromjpeg($this->image_file);
++ } elseif ($props['gd_type'] == \IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
++ $image = imagecreatefromgif($this->image_file);
++ } elseif ($props['gd_type'] == \IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
++ $image = imagecreatefrompng($this->image_file);
++ } else {
++ // @TODO: print error to the log?
++ return false;
++ }
+
+- if ($type == self::TYPE_JPG) {
+- $result = imagejpeg($image, $filename, 75);
+- }
+- else if ($type == self::TYPE_GIF) {
+- $result = imagegif($image, $filename);
+- }
+- else if ($type == self::TYPE_PNG) {
+- $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
++ if ($type == self::TYPE_JPG) {
++ $result = imagejpeg($image, $filename, 75);
++ } elseif ($type == self::TYPE_GIF) {
++ $result = imagegif($image, $filename);
++ } elseif ($type == self::TYPE_PNG) {
++ $result = imagepng($image, $filename, 6, \PNG_ALL_FILTERS);
++ }
++ } catch (Throwable $e) {
++ rcube::raise_error($e, true, false);
+ }
+
+ if (!empty($result)) {
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,25 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Fri, 2 Aug 2024 12:16:01 +0200
+Subject: Fix bug where imap_conn_option's 'socket' was ignored
+
+Origin: https://github.com/roundcube/roundcubemail/commit/b5ed0e49464ecee70756ad6d1b96d38279b3916e
+Bug: https://github.com/roundcube/roundcubemail/issues/9566
+---
+ program/lib/Roundcube/rcube_imap_generic.php | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php
+index afe7c23..f10d823 100644
+--- a/program/lib/Roundcube/rcube_imap_generic.php
++++ b/program/lib/Roundcube/rcube_imap_generic.php
+@@ -1054,8 +1054,8 @@ class rcube_imap_generic
+ }
+
+ if (!empty($this->prefs['socket_options'])) {
+- $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1]);
+- $context = stream_context_create($options);
++ $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1, 'socket' => 1]);
++ $context = stream_context_create($options);
+ $this->fp = stream_socket_client($host . ':' . $port, $errno, $errstr,
+ $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context);
+ }
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-fatal-error-when-parsing-some-TNEF-attachments.patch roundcube-1.6.5+dfsg/debian/patches/Fix-fatal-error-when-parsing-some-TNEF-attachments.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-fatal-error-when-parsing-some-TNEF-attachments.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-fatal-error-when-parsing-some-TNEF-attachments.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,128 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Sun, 2 Jun 2024 15:13:42 +0200
+Subject: Fix fatal error when parsing some TNEF attachments
+
+Origin: https://github.com/roundcube/roundcubemail/commit/22d403d5fdea1846319389d3d65ef60726434712
+Bug: https://github.com/roundcube/roundcubemail/issues/9462
+---
+ program/lib/Roundcube/rcube_tnef_decoder.php | 3 +-
+ tests/Framework/Message.php | 87 ++++++++++++++++++++++++++++
+ 2 files changed, 89 insertions(+), 1 deletion(-)
+
+diff --git a/program/lib/Roundcube/rcube_tnef_decoder.php b/program/lib/Roundcube/rcube_tnef_decoder.php
+index 092e6c5..8308303 100644
+--- a/program/lib/Roundcube/rcube_tnef_decoder.php
++++ b/program/lib/Roundcube/rcube_tnef_decoder.php
+@@ -162,7 +162,7 @@ class rcube_tnef_decoder
+ }
+
+ // Return the message body as HTML
+- if ($message && $as_html) {
++ if ($as_html) {
+ // HTML body
+ if (!empty($message['size']) && $message['subtype'] == 'html') {
+ $message = $message['stream'];
+@@ -180,6 +180,7 @@ class rcube_tnef_decoder
+ }
+ catch (Exception $e) {
+ // ignore the body
++ $message = null;
+ rcube::raise_error([
+ 'file' => __FILE__,
+ 'line' => __LINE__,
+diff --git a/tests/Framework/Message.php b/tests/Framework/Message.php
+index 16b4661..3e40e93 100644
+--- a/tests/Framework/Message.php
++++ b/tests/Framework/Message.php
+@@ -18,4 +18,91 @@ class Framework_Message extends PHPUnit\Framework\TestCase
+
+ $this->assertSame('test', $result);
+ }
++
++ /**
++ * Test tnef_decode() method
++ */
++ public function test_tnef_decode()
++ {
++ $message = new rcube_message_test(123);
++ $part = new rcube_message_part();
++ $part->mime_id = 1;
++
++ $message->set_part_body(1, '');
++ $result = $message->tnef_decode($part);
++
++ $this->assertSame([], $result);
++
++ $message->set_part_body(1, file_get_contents(TESTS_DIR . 'src/body.tnef'));
++ $result = $message->tnef_decode($part);
++
++ $this->assertCount(1, $result);
++ $this->assertInstanceOf('rcube_message_part', $result[0]);
++ $this->assertSame('winmail.1.html', $result[0]->mime_id);
++ $this->assertSame('text/html', $result[0]->mimetype);
++ $this->assertSame(5360, $result[0]->size);
++ $this->assertStringStartsWith('<!DOCTYPE HTML', $result[0]->body);
++ $this->assertSame([], $result[0]->parts);
++
++ $message->set_part_body(1, file_get_contents(TESTS_DIR . 'src/one-file.tnef'));
++ $result = $message->tnef_decode($part);
++
++ $this->assertCount(1, $result);
++ $this->assertInstanceOf('rcube_message_part', $result[0]);
++ $this->assertSame('winmail.1.0', $result[0]->mime_id);
++ $this->assertSame('application/octet-stream', $result[0]->mimetype);
++ $this->assertSame(244, $result[0]->size);
++ $this->assertStringContainsString(' Authors of', $result[0]->body);
++ $this->assertSame([], $result[0]->parts);
++ }
++
++ /**
++ * Test uu_decode() method
++ */
++ public function test_uu_decode()
++ {
++ $message = new rcube_message_test(123);
++ $part = new rcube_message_part();
++ $part->mime_id = 1;
++
++ $message->set_part_body(1, '');
++ $result = $message->uu_decode($part);
++
++ $this->assertSame([], $result);
++
++ $content = "begin 644 /dev/stdout\n" . convert_uuencode('test') . "end";
++ $message->set_part_body(1, $content);
++
++ $result = $message->uu_decode($part);
++
++ $this->assertCount(1, $result);
++ $this->assertInstanceOf('rcube_message_part', $result[0]);
++ $this->assertSame('uu.1.0', $result[0]->mime_id);
++ $this->assertSame('text/plain', $result[0]->mimetype);
++ $this->assertSame(4, $result[0]->size);
++ $this->assertSame('test', $result[0]->body);
++ $this->assertSame([], $result[0]->parts);
++ }
++}
++
++/**
++ * rcube_message wrapper for easier testing (without accessing IMAP)
++ */
++class rcube_message_test extends rcube_message
++{
++ private $part_bodies = [];
++
++ public function __construct($uid, $folder = null, $is_safe = false)
++ {
++ }
++
++ public function get_part_body($mime_id, $formatted = false, $max_bytes = 0, $mode = null)
++ {
++ return $this->part_bodies[$mime_id] ?? null;
++ }
++
++ public function set_part_body($mime_id, $body)
++ {
++ $this->part_bodies[$mime_id] = $body;
++ }
+ }
diff -Nru roundcube-1.6.5+dfsg/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch roundcube-1.6.5+dfsg/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch
--- roundcube-1.6.5+dfsg/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch 1970-01-01 01:00:00.000000000 +0100
+++ roundcube-1.6.5+dfsg/debian/patches/Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch 2024-08-06 16:02:54.000000000 +0200
@@ -0,0 +1,54 @@
+From: Aleksander Machniak <alec at alec.pl>
+Date: Wed, 31 Jul 2024 18:11:31 +0200
+Subject: Fix infinite loop when parsing malformed Sieve script
+
+Origin: https://github.com/roundcube/roundcubemail/commit/3567090a997e95aac6bb052bfb48bb301d0c03c3
+Bug: https://github.com/roundcube/roundcubemail/issues/9562
+---
+ plugins/managesieve/lib/Roundcube/rcube_sieve_script.php | 7 ++++---
+ plugins/managesieve/tests/Script.php | 14 ++++++++++++++
+ 2 files changed, 18 insertions(+), 3 deletions(-)
+
+diff --git a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
+index 05a53a2..3c9d7fc 100644
+--- a/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
++++ b/plugins/managesieve/lib/Roundcube/rcube_sieve_script.php
+@@ -1353,9 +1353,10 @@ class rcube_sieve_script
+
+ // bracket-comment
+ case '/':
+- if ($str[$position + 1] == '*') {
+- if ($end_pos = strpos($str, '*/', $position + 2)) {
+- $position = $end_pos + 2;
++ $position++;
++ if ($str[$position] == '*') {
++ if ($end_pos = strpos($str, '*/', $position + 1)) {
++ $position = $end_pos + 1;
+ }
+ else {
+ // error
+diff --git a/plugins/managesieve/tests/Script.php b/plugins/managesieve/tests/Script.php
+index 41982d1..f9ea3f4 100644
+--- a/plugins/managesieve/tests/Script.php
++++ b/plugins/managesieve/tests/Script.php
+@@ -59,6 +59,20 @@ class Managesieve_Script extends PHPUnit\Framework\TestCase
+ return $result;
+ }
+
++ /**
++ * Sieve script parsing
++ */
++ public function test_parser_bug9562()
++ {
++ // This is an obviously invalid script
++ $input = "vacation :subject \"a\" :from \"b\"\n<a href=\"https://test.org/\">test</a>";
++ $script = new \rcube_sieve_script($input);
++ $result = $script->as_text();
++
++ // TODO: The output still is BS, but it at least does not cause an infinite loop
++ $this->assertSame("require [\"vacation\"];\r\nvacation :subject \"a\" :from \"b\" \"a\";\r\n", $result);
++ }
++
+ function data_tokenizer()
+ {
+ return [
diff -Nru roundcube-1.6.5+dfsg/debian/patches/series roundcube-1.6.5+dfsg/debian/patches/series
--- roundcube-1.6.5+dfsg/debian/patches/series 2024-06-17 03:15:26.000000000 +0200
+++ roundcube-1.6.5+dfsg/debian/patches/series 2024-08-06 16:02:54.000000000 +0200
@@ -18,3 +18,10 @@
fix-upstream-test-suite.patch
CVE-2024-37384.patch
CVE-2024-37383.patch
+Fix-fatal-error-when-parsing-some-TNEF-attachments.patch
+Fix-bug-where-an-unhandled-exception-was-caused-by-an-inv.patch
+Fix-infinite-loop-when-parsing-malformed-Sieve-script.patch
+Fix-bug-where-imap_conn_option-s-socket-was-ignored.patch
+CVE-2024-42009.patch
+CVE-2024-42008.patch
+CVE-2024-42010.patch
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <http://alioth-lists.debian.net/pipermail/pkg-roundcube-maintainers/attachments/20240806/09cd3762/attachment-0001.sig>
More information about the Pkg-roundcube-maintainers
mailing list