MDL-52651 htmlpurifier: Append rel=noreferrer to links.
authorCameron Ball <cameron@moodle.com>
Thu, 11 Feb 2016 03:25:53 +0000 (11:25 +0800)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Tue, 8 Mar 2016 01:05:55 +0000 (02:05 +0100)
Thank you to Zachary Durber for originally working on this issue.

lib/htmlpurifier/locallib.php
lib/tests/htmlpurifier_test.php
lib/weblib.php
mod/data/field/url/field.class.php

index b949b74..362ddd5 100644 (file)
@@ -119,3 +119,69 @@ class HTMLPurifier_URIScheme_teamspeak extends HTMLPurifier_URIScheme {
     }
 
 }
+
+/**
+ * A custom HTMLPurifier transformation. Adds rel="noreferrer" to all links with target="_blank".
+ *
+ * @package core
+ * @copyright Cameron Ball
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class HTMLPurifier_AttrTransform_Noreferrer extends HTMLPurifier_AttrTransform {
+    /** @var HTMLPurifier_URIParser $parser */
+    private $parser;
+
+    /**
+     * Constructor.
+     */
+    public function __construct() {
+        $this->parser = new HTMLPurifier_URIParser();
+    }
+
+    /**
+     * Transforms a tags such that when a target attribute is present, rel="noreferrer" is added.
+     *
+     * Note that this will not respect Attr.AllowedRel
+     *
+     * @param array $attr Assoc array of attributes, usually from
+     *              HTMLPurifier_Token_Tag::$attr
+     * @param HTMLPurifier_Config $config Mandatory HTMLPurifier_Config object.
+     * @param HTMLPurifier_Context $context Mandatory HTMLPurifier_Context object
+     * @return array Processed attribute array.
+     */
+    public function transform($attr, $config, $context) {
+        // Nothing to do If we already have noreferrer in the rel attribute
+        if (!empty($attr['rel']) && substr($attr['rel'], 'noreferrer') !== false) {
+            return $attr;
+        }
+
+        // If _blank target attribute exists, add rel=noreferrer
+        if (!empty($attr['target']) && $attr['target'] == '_blank') {
+            $attr['rel'] = !empty($attr['rel']) ? $attr['rel'] . ' noreferrer' : 'noreferrer';
+        }
+
+        return $attr;
+    }
+}
+
+/**
+ * A custom HTMLPurifier module to add rel="noreferrer" attributes a tags.
+ *
+ * @package    core
+ * @copyright  Cameron Ball
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class HTMLPurifier_HTMLModule_Noreferrer extends HTMLPurifier_HTMLModule {
+    /** @var string $name */
+    public $name = 'Noreferrer';
+
+    /**
+     * Module setup
+     *
+     * @param HTMLPurifier_Config $config
+     */
+    public function setup($config) {
+        $a = $this->addBlankElement('a');
+        $a->attr_transform_post[] = new HTMLPurifier_AttrTransform_Noreferrer();
+    }
+}
index 51d22c8..ae22dae 100644 (file)
@@ -40,9 +40,13 @@ class core_htmlpurifier_testcase extends basic_testcase {
      * Verify _blank target is allowed.
      */
     public function test_allow_blank_target() {
+        // See MDL-52651 for an explanation as to why the rel="noreferrer" attribute is expected here.
+        // Also note we do not need to test links with an existing rel attribute as the HTML Purifier is configured to remove
+        // the rel attribute.
         $text = '<a href="http://moodle.org" target="_blank">Some link</a>';
+        $expected = '<a href="http://moodle.org" target="_blank" rel="noreferrer">Some link</a>';
         $result = format_text($text, FORMAT_HTML);
-        $this->assertSame($text, $result);
+        $this->assertSame($expected, $result);
 
         $result = format_text('<a href="http://moodle.org" target="some">Some link</a>', FORMAT_HTML);
         $this->assertSame('<a href="http://moodle.org">Some link</a>', $result);
index 18ce243..b2e7eeb 100644 (file)
@@ -1754,7 +1754,7 @@ function purify_html($text, $options = array()) {
         $config = HTMLPurifier_Config::createDefault();
 
         $config->set('HTML.DefinitionID', 'moodlehtml');
-        $config->set('HTML.DefinitionRev', 3);
+        $config->set('HTML.DefinitionRev', 4);
         $config->set('Cache.SerializerPath', $cachedir);
         $config->set('Cache.SerializerPermissions', $CFG->directorypermissions);
         $config->set('Core.NormalizeNewlines', false);
@@ -1796,6 +1796,9 @@ function purify_html($text, $options = array()) {
 
             // Use the built-in Ruby module to add annotation support.
             $def->manager->addModule(new HTMLPurifier_HTMLModule_Ruby());
+
+            // Use the custom Noreferrer module.
+            $def->manager->addModule(new HTMLPurifier_HTMLModule_Noreferrer());
         }
 
         $purifier = new HTMLPurifier($config);
index 4557a1b..da87cb5 100644 (file)
@@ -143,6 +143,7 @@ class data_field_url extends data_field_base {
                 if ($this->field->param3) {
                     // param3 defines whether this URL should open in a new window.
                     $attributes['target'] = '_blank';
+                    $attributes['rel'] = 'noreferrer';
                 }
 
                 if (empty($text)) {