From 1e32540839683c1309db012c4d5b9aff35ec6ae3 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Tue, 19 Mar 2013 07:47:07 -0400
Subject: [PATCH] Add rel="noreferrer" for links in displayed messages (#1484686)

---
 CHANGELOG                                       |    1 +
 tests/MailFunc.php                              |    8 ++++----
 program/steps/mail/func.inc                     |   14 ++++++++++----
 tests/Framework/StringReplacer.php              |   22 +++++++++++-----------
 program/lib/Roundcube/rcube_string_replacer.php |   13 ++++++++-----
 5 files changed, 34 insertions(+), 24 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 1e3eb77..ed0ea6e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add rel="noreferrer" for links in displayed messages (#1484686)
 - Fix so forward as attachment works if additional attachment is added by message_compose hook (#1489000)
 - Add ability to toggle between HTML and text while viewing a message (#1486939)
 - Better handling of session errors in ajax requests (#1488960)
diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php
index 49a3781..b8768bc 100644
--- a/program/lib/Roundcube/rcube_string_replacer.php
+++ b/program/lib/Roundcube/rcube_string_replacer.php
@@ -28,9 +28,10 @@
     public $mailto_pattern;
     public $link_pattern;
     private $values = array();
+    private $options = array();
 
 
-    function __construct()
+    function __construct($options = array())
     {
         // Simplified domain expression for UTF8 characters handling
         // Support unicode/punycode in top-level domain part
@@ -44,6 +45,8 @@
             ."@$utf_domain"                                                 // domain-part
             ."(\?[$url1$url2]+)?"                                           // e.g. ?subject=test...
             .")/";
+
+        $this->options = $options;
     }
 
     /**
@@ -89,10 +92,10 @@
 
         if ($url) {
             $suffix = $this->parse_url_brackets($url);
-            $i = $this->add($prefix . html::a(array(
-                'href'   => $url_prefix . $url,
-                'target' => '_blank'
-            ), rcube::Q($url)) . $suffix);
+            $attrib = (array)$this->options['link_attribs'];
+            $attrib['href'] = $url_prefix . $url;
+
+            $i = $this->add($prefix . html::a($attrib, rcube::Q($url)) . $suffix);
         }
 
         // Return valid link for recognized schemes, otherwise
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 8c97439..274c40b 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -760,7 +760,8 @@
   global $RCMAIL;
 
   // make links and email-addresses clickable
-  $replacer = new rcmail_string_replacer;
+  $attribs  = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
+  $replacer = new rcmail_string_replacer($attribs);
 
   // search for patterns like links and e-mail addresses and replace with tokens
   $body = $replacer->replace($body);
@@ -1373,7 +1374,7 @@
 
 
 /**
- * parse link attributes and set correct target
+ * parse link (a, link, area) attributes and set correct target
  */
 function rcmail_alter_html_link($matches)
 {
@@ -1382,9 +1383,9 @@
   // Support unicode/punycode in top-level domain part
   $EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
 
-  $tag = $matches[1];
+  $tag    = strtolower($matches[1]);
   $attrib = parse_attrib_string($matches[2]);
-  $end = '>';
+  $end    = '>';
 
   // Remove non-printable characters in URL (#1487805)
   if ($attrib['href'])
@@ -1411,6 +1412,11 @@
     $attrib['target'] = '_blank';
   }
 
+  // Better security by adding rel="noreferrer" (#1484686)
+  if (($tag == 'a' || $tag == 'area') && $attrib['href'] && $attrib['href'][0] != '#') {
+    $attrib['rel'] = 'noreferrer';
+  }
+
   // allowed attributes for a|link|area tags
   $allow = array('href','name','target','onclick','id','class','style','title',
     'rel','type','media','alt','coords','nohref','hreflang','shape');
diff --git a/tests/Framework/StringReplacer.php b/tests/Framework/StringReplacer.php
index e630eba..95c5922 100644
--- a/tests/Framework/StringReplacer.php
+++ b/tests/Framework/StringReplacer.php
@@ -24,17 +24,17 @@
     function data_replace()
     {
         return array(
-            array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2" target="_blank">http://domain.tld/path*path2</a>'),
-            array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\" target=\"_blank\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"),
-            array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo" target="_blank">http://localhost/?foo</a> End'),
-            array('www.domain.tld', '<a href="http://www.domain.tld" target="_blank">www.domain.tld</a>'),
-            array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD" target="_blank">WWW.DOMAIN.TLD</a>'),
-            array('[http://link.com]', '[<a href="http://link.com" target="_blank">http://link.com</a>]'),
-            array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1" target="_blank">http://link.com?a[]=1</a>'),
-            array('http://link.com?a[]', '<a href="http://link.com?a[]" target="_blank">http://link.com?a[]</a>'),
-            array('(http://link.com)', '(<a href="http://link.com" target="_blank">http://link.com</a>)'),
-            array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c" target="_blank">http://link.com?a(b)c</a>'),
-            array('http://link.com?(link)', '<a href="http://link.com?(link)" target="_blank">http://link.com?(link)</a>'),
+            array('http://domain.tld/path*path2', '<a href="http://domain.tld/path*path2">http://domain.tld/path*path2</a>'),
+            array("Click this link:\nhttps://mail.xn--brderli-o2a.ch/rc/ EOF", "Click this link:\n<a href=\"https://mail.xn--brderli-o2a.ch/rc/\">https://mail.xn--brderli-o2a.ch/rc/</a> EOF"),
+            array('Start http://localhost/?foo End', 'Start <a href="http://localhost/?foo">http://localhost/?foo</a> End'),
+            array('www.domain.tld', '<a href="http://www.domain.tld">www.domain.tld</a>'),
+            array('WWW.DOMAIN.TLD', '<a href="http://WWW.DOMAIN.TLD">WWW.DOMAIN.TLD</a>'),
+            array('[http://link.com]', '[<a href="http://link.com">http://link.com</a>]'),
+            array('http://link.com?a[]=1', '<a href="http://link.com?a[]=1">http://link.com?a[]=1</a>'),
+            array('http://link.com?a[]', '<a href="http://link.com?a[]">http://link.com?a[]</a>'),
+            array('(http://link.com)', '(<a href="http://link.com">http://link.com</a>)'),
+            array('http://link.com?a(b)c', '<a href="http://link.com?a(b)c">http://link.com?a(b)c</a>'),
+            array('http://link.com?(link)', '<a href="http://link.com?(link)">http://link.com?(link)</a>'),
             array('http://<test>', 'http://<test>'),
             array('http://', 'http://'),
         );
diff --git a/tests/MailFunc.php b/tests/MailFunc.php
index 38c0bac..319075a 100644
--- a/tests/MailFunc.php
+++ b/tests/MailFunc.php
@@ -54,7 +54,7 @@
         $this->assertNotRegExp('/<form [^>]+>/', $html, "No form tags allowed");
         $this->assertRegExp('/Subscription form/', $html, "Include <form> contents");
         $this->assertRegExp('/<!-- link ignored -->/', $html, "No external links allowed");
-        $this->assertRegExp('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
+        $this->assertRegExp('/<a[^>]+ target="_blank"/', $html, "Set target to _blank");
         $this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
 
         // render HTML in safe mode
@@ -133,8 +133,8 @@
         $html = rcmail_print_body($part, array('safe' => true));
 
         $this->assertRegExp('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
-        $this->assertRegExp('#<a href="http://www.apple.com/legal/privacy" target="_blank">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
-        $this->assertRegExp('#\\[<a href="http://example.com/\\?tx\\[a\\]=5" target="_blank">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
+        $this->assertRegExp('#<a rel="noreferrer" target="_blank" href="http://www.apple.com/legal/privacy">http://www.apple.com/legal/privacy</a>#', $html, "Links with target=_blank");
+        $this->assertRegExp('#\\[<a rel="noreferrer" target="_blank" href="http://example.com/\\?tx\\[a\\]=5">http://example.com/\\?tx\\[a\\]=5</a>\\]#', $html, "Links with square brackets");
     }
 
     /**
@@ -148,7 +148,7 @@
         $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
 
         $mailto = '<a href="mailto:me@me.com?subject=this is the subject&amp;body=this is the body"'
-            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)">e-mail</a>';
+            .' onclick="return rcmail.command(\'compose\',\'me@me.com?subject=this is the subject&amp;body=this is the body\',this)" rel="noreferrer">e-mail</a>';
 
         $this->assertRegExp('|'.preg_quote($mailto, '|').'|', $html, "Extended mailto links");
     }

--
Gitblit v1.9.1