diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d36af4bda..8c1776ab09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ - Convert images in HTML content pasted into HTML editor to `data:` URIs (and later to attachments) (#6938) - Add possibility to change ATTR_EMULATE_PREPARES via config file (#9213) - Use draft settings (like DSN) on "Edit as new" (#9349) +- Use new HTML5 parser available on PHP >= 8.4 - Mailvelope: Add a button to enable the extension for webmail domain (#9498) - OAuth: Add support for SMTP without authentication (#9183) - OAuth: Add support for OAuth/OpenIDC discovery (#8201) @@ -58,6 +59,9 @@ - Fix attachment name decoding when 'charset' parameter exists in the headers (#9376) - Fix deprecated (in PHP 8.4) use of session_set_save_handler() (#9060) - Fix potential HTTP protocol version mismatch (#8982) + +## Release 1.6.9 + - Fix regression where printing/scaling/rotating image attachments was broken (#9571) - Fix regression where HTML messages were displayed unstyled (#9586) diff --git a/program/lib/Roundcube/rcube_washtml.php b/program/lib/Roundcube/rcube_washtml.php index 95a6371139..5560e890ef 100644 --- a/program/lib/Roundcube/rcube_washtml.php +++ b/program/lib/Roundcube/rcube_washtml.php @@ -1,5 +1,6 @@ getAttribute('attributeName')); + $key = strtolower((string) $node->getAttribute('attributeName')); if ($key && !isset($this->_html_attribs[$key])) { $key = null; } @@ -328,6 +329,8 @@ private function wash_attribs($node) && (in_array($key, ['id', 'class', 'for']) || ($key == 'name' && $node->nodeName == 'a')) ) { $out = preg_replace('/(\S+)/', $this->_css_prefix . '\1', $value); + } elseif ($key == 'xmlns' && !strpos($value, '://')) { + continue; } elseif ($key) { $out = $value; } @@ -579,12 +582,22 @@ private function dumpHtml($node, $level = 20) $this->wash_attribs($node), $this->dumpHtml($node, $level), $this); } elseif (isset($this->_html_elements[$tagName])) { $content = $this->dumpHtml($node, $level); - $tag = '<' . $node->nodeName; + $tag = '<' . $tagName; if ($tagName == 'svg') { - $xpath = new DOMXPath($node->ownerDocument); - foreach ($xpath->query('namespace::*') as $ns) { - if ($ns->nodeName != 'xmlns:xml') { + if (method_exists($node, 'getInScopeNamespaces')) { + $ns_nodes = $node->getInScopeNamespaces(); + } else { + $xpath = new DOMXPath($node->ownerDocument); + $ns_nodes = $xpath->query('namespace::*'); + } + + foreach ($ns_nodes as $ns) { + if (isset($ns->nodeName) && isset($ns->nodeValue) + && $ns->nodeName != 'xmlns:xml' + && preg_match('/^[a-zA-Z:-]+$/', $ns->nodeName) + && strpos($ns->nodeValue, '://') + ) { $tag .= sprintf(' %s="%s"', $ns->nodeName, htmlspecialchars($ns->nodeValue, \ENT_QUOTES, $this->config['charset']) @@ -602,15 +615,15 @@ private function dumpHtml($node, $level = 20) } elseif ($content === '' && ($this->is_xml || isset($this->_void_elements[$tagName]))) { $dump .= $tag . ' />'; } else { - $dump .= $tag . '>' . $content . 'nodeName . '>'; + $dump .= $tag . '>' . $content . ''; } } elseif (isset($this->_ignore_elements[$tagName])) { if ($this->config['add_comments']) { - $dump .= ''; + $dump .= ''; } } else { if ($this->config['add_comments']) { - $dump .= ''; + $dump .= ''; } $dump .= $this->dumpHtml($node, $level); // ignore tags not its content } @@ -657,8 +670,19 @@ public function wash($html) $this->is_xml = !preg_match('/<(html|head|body)/i', $html) && stripos($html, 'is_xml ? 'loadXML' : 'loadHTML'; + // Try HTML5 parser available in PHP >= 8.4 + // TODO: Parse XML also with this new PHP parser (?) + if (!$this->is_xml && class_exists('Dom\HTMLDocument')) { + try { + $options = constant('Dom\HTML_NO_DEFAULT_NS') | \LIBXML_COMPACT | \LIBXML_NOERROR; + $node = HTMLDocument::createFromString($html, $options, $this->config['charset']); + } catch (Exception $e) { + // ignore, fallback to other methods + } + } + // DOMDocument does not support HTML5, try Masterminds parser if available - if (!$this->is_xml && class_exists('Masterminds\HTML5')) { + if (empty($node) && !$this->is_xml && class_exists('Masterminds\HTML5')) { try { // disabled_html_ns=true is a workaround for the performance issue // https://github.com/Masterminds/html5-php/issues/181 diff --git a/tests/Framework/WashtmlTest.php b/tests/Framework/WashtmlTest.php index 0cd49e0ca0..0117e7bf1c 100644 --- a/tests/Framework/WashtmlTest.php +++ b/tests/Framework/WashtmlTest.php @@ -362,7 +362,7 @@ public function test_wash_svg() An example text - + @@ -400,7 +400,7 @@ public static function provide_wash_svg_tests_cases(): iterable ], [ '', - '', + '', ], [ '', @@ -824,23 +824,15 @@ public function test_table_bug7356() '; - $expected = ' - - - - - -
- - - - -
-
'; + $expected = '
'; + + if (class_exists('Dom\HTMLDocument')) { + $expected = '
'; + } $washer = new \rcube_washtml(); $washed = $this->cleanupResult($washer->wash($html)); - $this->assertSame(trim($expected), $washed); + $this->assertSame($expected, preg_replace('/>[^<>]+<', $washed)); } }