0
0
mirror of https://github.com/ezyang/htmlpurifier.git synced 2024-09-18 18:25:18 +00:00

Add HTML.Noopener to add a noopener rel to every external link

This has performance benefits https://jakearchibald.com/2016/performance-benefits-of-rel-noopener/ but most importantly also security benefits https://mathiasbynens.github.io/rel-noopener/

Adresses https://github.com/ezyang/htmlpurifier/issues/96
This commit is contained in:
Bastian Hofmann 2017-01-13 13:44:58 +01:00 committed by Edward Z. Yang
parent d4a96463ef
commit c82051c3e1
10 changed files with 129 additions and 2 deletions

1
NEWS
View File

@ -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

View File

@ -222,14 +222,19 @@
<line>268</line>
</file>
</directive>
<directive id="HTML.TargetBlank">
<directive id="HTML.Noopener">
<file name="HTMLPurifier/HTMLModuleManager.php">
<line>271</line>
</file>
</directive>
<directive id="HTML.TargetBlank">
<file name="HTMLPurifier/HTMLModuleManager.php">
<line>274</line>
</file>
</directive>
<directive id="HTML.TargetNoreferrer">
<file name="HTMLPurifier/HTMLModuleManager.php">
<line>276</line>
<line>279</line>
</file>
</directive>
<directive id="Attr.IDBlacklist">

View File

@ -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';

View File

@ -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';

View File

@ -0,0 +1,52 @@
<?php
// must be called POST validation
/**
* Adds rel="noopener" to all outbound links. This transform is
* only attached if Attr.Noopener is TRUE.
*/
class HTMLPurifier_AttrTransform_Noopener extends HTMLPurifier_AttrTransform
{
/**
* @type HTMLPurifier_URIParser
*/
private $parser;
public function __construct()
{
$this->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

View File

@ -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

View File

@ -0,0 +1,25 @@
<?php
/**
* Module adds the noopener attribute transformation to a tags. It
* is enabled by HTML.Noopener
*/
class HTMLPurifier_HTMLModule_Noopener extends HTMLPurifier_HTMLModule
{
/**
* @type string
*/
public $name = 'Noopener';
/**
* @param HTMLPurifier_Config $config
*/
public function setup($config)
{
$a = $this->addBlankElement('a');
$a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Noopener();
}
}
// vim: et sw=4 sts=4

View File

@ -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';
}

View File

@ -0,0 +1,30 @@
<?php
class HTMLPurifier_HTMLModule_NoopenerTest extends HTMLPurifier_HTMLModuleHarness
{
public function setUp()
{
parent::setUp();
$this->config->set('HTML.Noopener', true);
$this->config->set('Attr.AllowedRel', array("noopener", "blah"));
}
public function testNoopener()
{
$this->assertResult(
'<a href="http://google.com">x</a><a href="http://google.com" rel="blah">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>',
'<a href="http://google.com" rel="noopener">x</a><a href="http://google.com" rel="blah noopener">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>'
);
}
public function testNoopenerDupe()
{
$this->assertResult(
'<a href="http://google.com" rel="noopener">x</a><a href="http://google.com" rel="blah noopener">a</a><a href="/local">b</a><a href="mailto:foo@example.com">c</a>'
);
}
}
// vim: et sw=4 sts=4