diff --git a/NEWS b/NEWS index 39dd522e..b12c63ad 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ NEWS ( CHANGELOG and HISTORY ) HTMLPurifier - Deleted some asserts to avoid linters from choking (#97) - Rework Serializer cache behavior to avoid chmod'ing if possible (#32) - Embedded semicolons in strings in CSS are now handled correctly! +! Added %HTML.Noopener to add rel="noopener" to external links. 4.8.0, released 2016-07-16 # By default, when a link has a target attribute associated diff --git a/configdoc/usage.xml b/configdoc/usage.xml index d80ab51e..67020803 100644 --- a/configdoc/usage.xml +++ b/configdoc/usage.xml @@ -222,14 +222,19 @@ 268 - + 271 + + + 274 + + - 276 + 279 diff --git a/library/HTMLPurifier.includes.php b/library/HTMLPurifier.includes.php index b1131ef9..0a4c6096 100644 --- a/library/HTMLPurifier.includes.php +++ b/library/HTMLPurifier.includes.php @@ -132,6 +132,7 @@ require 'HTMLPurifier/AttrTransform/Length.php'; require 'HTMLPurifier/AttrTransform/Name.php'; require 'HTMLPurifier/AttrTransform/NameSync.php'; require 'HTMLPurifier/AttrTransform/Nofollow.php'; +require 'HTMLPurifier/AttrTransform/Noopener.php'; require 'HTMLPurifier/AttrTransform/SafeEmbed.php'; require 'HTMLPurifier/AttrTransform/SafeObject.php'; require 'HTMLPurifier/AttrTransform/SafeParam.php'; @@ -163,6 +164,7 @@ require 'HTMLPurifier/HTMLModule/Legacy.php'; require 'HTMLPurifier/HTMLModule/List.php'; require 'HTMLPurifier/HTMLModule/Name.php'; require 'HTMLPurifier/HTMLModule/Nofollow.php'; +require 'HTMLPurifier/HTMLModule/Noopener.php'; require 'HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require 'HTMLPurifier/HTMLModule/Object.php'; require 'HTMLPurifier/HTMLModule/Presentation.php'; diff --git a/library/HTMLPurifier.safe-includes.php b/library/HTMLPurifier.safe-includes.php index fe587c78..a81ea5c6 100644 --- a/library/HTMLPurifier.safe-includes.php +++ b/library/HTMLPurifier.safe-includes.php @@ -126,6 +126,7 @@ require_once $__dir . '/HTMLPurifier/AttrTransform/Length.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Name.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/NameSync.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/Nofollow.php'; +require_once $__dir . '/HTMLPurifier/AttrTransform/Noopener.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeEmbed.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeObject.php'; require_once $__dir . '/HTMLPurifier/AttrTransform/SafeParam.php'; @@ -157,6 +158,7 @@ require_once $__dir . '/HTMLPurifier/HTMLModule/Legacy.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/List.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Name.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Nofollow.php'; +require_once $__dir . '/HTMLPurifier/HTMLModule/Noopener.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/NonXMLCommonAttributes.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Object.php'; require_once $__dir . '/HTMLPurifier/HTMLModule/Presentation.php'; diff --git a/library/HTMLPurifier/AttrTransform/Noopener.php b/library/HTMLPurifier/AttrTransform/Noopener.php new file mode 100644 index 00000000..fb72d307 --- /dev/null +++ b/library/HTMLPurifier/AttrTransform/Noopener.php @@ -0,0 +1,52 @@ +parser = new HTMLPurifier_URIParser(); + } + + /** + * @param array $attr + * @param HTMLPurifier_Config $config + * @param HTMLPurifier_Context $context + * @return array + */ + public function transform($attr, $config, $context) + { + if (!isset($attr['href'])) { + return $attr; + } + + // XXX Kind of inefficient + $url = $this->parser->parse($attr['href']); + $scheme = $url->getSchemeObj($config, $context); + + if ($scheme->browsable && !$url->isLocal($config, $context)) { + if (isset($attr['rel'])) { + $rels = explode(' ', $attr['rel']); + if (!in_array('noopener', $rels)) { + $rels[] = 'noopener'; + } + $attr['rel'] = implode(' ', $rels); + } else { + $attr['rel'] = 'noopener'; + } + } + return $attr; + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/ConfigSchema/schema.ser b/library/HTMLPurifier/ConfigSchema/schema.ser index 0def14c8..1dc1b510 100644 Binary files a/library/HTMLPurifier/ConfigSchema/schema.ser and b/library/HTMLPurifier/ConfigSchema/schema.ser differ diff --git a/library/HTMLPurifier/ConfigSchema/schema/HTML.Noopener.txt b/library/HTMLPurifier/ConfigSchema/schema/HTML.Noopener.txt new file mode 100644 index 00000000..59245688 --- /dev/null +++ b/library/HTMLPurifier/ConfigSchema/schema/HTML.Noopener.txt @@ -0,0 +1,7 @@ +HTML.Noopener +TYPE: bool +VERSION: 4.9.0 +DEFAULT: FALSE +--DESCRIPTION-- +If enabled, noopener rel attributes are added to all outgoing links. +--# vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModule/Noopener.php b/library/HTMLPurifier/HTMLModule/Noopener.php new file mode 100644 index 00000000..1fa629ba --- /dev/null +++ b/library/HTMLPurifier/HTMLModule/Noopener.php @@ -0,0 +1,25 @@ +addBlankElement('a'); + $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Noopener(); + } +} + +// vim: et sw=4 sts=4 diff --git a/library/HTMLPurifier/HTMLModuleManager.php b/library/HTMLPurifier/HTMLModuleManager.php index 2546c043..4d0fc6ea 100644 --- a/library/HTMLPurifier/HTMLModuleManager.php +++ b/library/HTMLPurifier/HTMLModuleManager.php @@ -268,6 +268,9 @@ class HTMLPurifier_HTMLModuleManager if ($config->get('HTML.Nofollow')) { $modules[] = 'Nofollow'; } + if ($config->get('HTML.Noopener')) { + $modules[] = 'Noopener'; + } if ($config->get('HTML.TargetBlank')) { $modules[] = 'TargetBlank'; } diff --git a/tests/HTMLPurifier/HTMLModule/NoopenerTest.php b/tests/HTMLPurifier/HTMLModule/NoopenerTest.php new file mode 100644 index 00000000..a53fb64e --- /dev/null +++ b/tests/HTMLPurifier/HTMLModule/NoopenerTest.php @@ -0,0 +1,30 @@ +config->set('HTML.Noopener', true); + $this->config->set('Attr.AllowedRel', array("noopener", "blah")); + } + + public function testNoopener() + { + $this->assertResult( + 'xabc', + 'xabc' + ); + } + + public function testNoopenerDupe() + { + $this->assertResult( + 'xabc' + ); + } + +} + +// vim: et sw=4 sts=4