Bug 1174913 - anchor and area referrer attributes. r=ckerschb, r=bz
authorFranziskus Kiefer <franziskuskiefer@gmail.com>
Fri, 05 Jun 2015 15:25:24 -0700
changeset 253861 c309c6f04d2e
parent 253860 79619b679f82
child 253862 c55086289922
push id29081
push usercbook@mozilla.com
push dateTue, 21 Jul 2015 14:57:20 +0000
treeherdermozilla-central@512c7e8f0030 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersckerschb, bz
bugs1174913
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1174913 - anchor and area referrer attributes. r=ckerschb, r=bz
browser/base/content/browser.js
browser/base/content/content.js
docshell/base/nsDocShell.cpp
dom/html/HTMLAnchorElement.h
dom/html/HTMLAreaElement.h
dom/webidl/HTMLAnchorElement.webidl
dom/webidl/HTMLAreaElement.webidl
netwerk/base/ReferrerPolicy.h
netwerk/base/nsINetUtil.idl
netwerk/base/nsIOService.cpp
toolkit/modules/Services.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5605,21 +5605,34 @@ function handleLinkClick(event, href, li
     try {
       var targetURI = makeURI(href);
       sm.checkSameOriginURI(referrerURI, targetURI, false);
       persistAllowMixedContentInChildTab = true;
     }
     catch (e) { }
   }
 
+  // first get document wide referrer policy, then
+  // get referrer attribute from clicked link and parse it and
+  // allow per element referrer to overrule the document wide referrer if enabled
+  let referrerPolicy = doc.referrerPolicy;
+  if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer") &&
+      linkNode) {
+    let referrerAttrValue = Services.netUtils.parseAttributePolicyString(linkNode.
+                            getAttribute("referrer"));
+    if (referrerAttrValue != Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) {
+      referrerPolicy = referrerAttrValue;
+    }
+  }
+
   urlSecurityCheck(href, doc.nodePrincipal);
   let params = { charset: doc.characterSet,
                  allowMixedContent: persistAllowMixedContentInChildTab,
                  referrerURI: referrerURI,
-                 referrerPolicy: doc.referrerPolicy,
+                 referrerPolicy: referrerPolicy,
                  noReferrer: BrowserUtils.linkHasNoReferrer(linkNode) };
   openLinkIn(href, where, params);
   event.preventDefault();
   return true;
 }
 
 function middleMousePaste(event) {
   let clipboard = readFromClipboard();
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -100,16 +100,27 @@ let handleContentContextMenu = function 
   let charSet = doc.characterSet;
   let baseURI = doc.baseURI;
   let referrer = doc.referrer;
   let referrerPolicy = doc.referrerPolicy;
   let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                                           .getInterface(Ci.nsIDOMWindowUtils)
                                           .outerWindowID;
 
+  // get referrer attribute from clicked link and parse it
+  // if per element referrer is enabled, the element referrer overrules
+  // the document wide referrer
+  if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer")) {
+    let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target.
+                            getAttribute("referrer"));
+    if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) {
+      referrerPolicy = referrerAttrValue;
+    }
+  }
+
   let disableSetDesktopBg = null;
   // Media related cache info parent needs for saving
   let contentType = null;
   let contentDisposition = null;
   if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
       event.target instanceof Ci.nsIImageLoadingContent &&
       event.target.currentURI) {
     disableSetDesktopBg = disableSetDesktopBackground(event.target);
@@ -346,20 +357,33 @@ let ClickEventHandler = {
       return;
     } else if (ownerDoc.documentURI.startsWith("about:neterror")) {
       this.onAboutNetError(event, ownerDoc.documentURI);
       return;
     }
 
     let [href, node] = this._hrefAndLinkNodeForClickEvent(event);
 
+    // get referrer attribute from clicked link and parse it
+    // if per element referrer is enabled, the element referrer overrules
+    // the document wide referrer
+    let referrerPolicy = ownerDoc.referrerPolicy;
+    if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer") &&
+        node) {
+      let referrerAttrValue = Services.netUtils.parseAttributePolicyString(node.
+                              getAttribute("referrer"));
+      if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT) {
+        referrerPolicy = referrerAttrValue;
+      }
+    }
+
     let json = { button: event.button, shiftKey: event.shiftKey,
                  ctrlKey: event.ctrlKey, metaKey: event.metaKey,
                  altKey: event.altKey, href: null, title: null,
-                 bookmark: false, referrerPolicy: ownerDoc.referrerPolicy };
+                 bookmark: false, referrerPolicy: referrerPolicy };
 
     if (href) {
       try {
         BrowserUtils.urlSecurityCheck(href, node.ownerDocument.nodePrincipal);
       } catch (e) {
         return;
       }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -13510,16 +13510,32 @@ nsDocShell::OnLinkClickSync(nsIContent* 
       mScriptGlobal->GetCurrentInnerWindow() != refererInner) {
     // We're no longer the current inner window
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI();
   uint32_t refererPolicy = refererDoc->GetReferrerPolicy();
 
+  // get referrer attribute from clicked link and parse it
+  // if per element referrer is enabled, the element referrer overrules
+  // the document wide referrer
+  if (IsElementAnchor(aContent)) {
+    MOZ_ASSERT(aContent->IsHTMLElement());
+    if (Preferences::GetBool("network.http.enablePerElementReferrer", false)) {
+      nsAutoString referrerPolicy;
+      if (aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::referrer, referrerPolicy)) {
+        uint32_t refPolEnum = mozilla::net::ReferrerPolicyFromString(referrerPolicy);
+        if (refPolEnum != mozilla::net::RP_Unset) {
+          refererPolicy = refPolEnum;
+        }
+      }
+    }
+  }
+
   // referer could be null here in some odd cases, but that's ok,
   // we'll just load the link w/o sending a referer in those cases.
 
   nsAutoString target(aTargetSpec);
 
   // If this is an anchor element, grab its type property to use as a hint
   nsAutoString typeHint;
   nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(aContent));
--- a/dom/html/HTMLAnchorElement.h
+++ b/dom/html/HTMLAnchorElement.h
@@ -118,16 +118,24 @@ public:
   void GetRel(DOMString& aValue)
   {
     GetHTMLAttr(nsGkAtoms::rel, aValue);
   }
   void SetRel(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::rel, aValue, rv);
   }
+  void SetReferrer(const nsAString& aValue, mozilla::ErrorResult& rv)
+  {
+    SetHTMLAttr(nsGkAtoms::referrer, aValue, rv);
+  }
+  void GetReferrer(nsAString& aReferrer)
+  {
+    GetHTMLAttr(nsGkAtoms::referrer, aReferrer);
+  }
   nsDOMTokenList* RelList();
   void GetHreflang(DOMString& aValue)
   {
     GetHTMLAttr(nsGkAtoms::hreflang, aValue);
   }
   void SetHreflang(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::hreflang, aValue, rv);
--- a/dom/html/HTMLAreaElement.h
+++ b/dom/html/HTMLAreaElement.h
@@ -123,16 +123,25 @@ public:
   }
 
   void SetRel(const nsAString& aRel, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::rel, aRel, aError);
   } 
   nsDOMTokenList* RelList();
 
+  void SetReferrer(const nsAString& aValue, mozilla::ErrorResult& rv)
+  {
+    SetHTMLAttr(nsGkAtoms::referrer, aValue, rv);
+  }
+  void GetReferrer(nsAString& aReferrer)
+  {
+    GetHTMLAttr(nsGkAtoms::referrer, aReferrer);
+  }
+
   // The Link::GetOrigin is OK for us
 
   using Link::GetProtocol;
   using Link::SetProtocol;
 
   // The Link::GetUsername is OK for us
   // The Link::SetUsername is OK for us
 
--- a/dom/webidl/HTMLAnchorElement.webidl
+++ b/dom/webidl/HTMLAnchorElement.webidl
@@ -16,16 +16,18 @@ interface HTMLAnchorElement : HTMLElemen
            [SetterThrows]
            attribute DOMString target;
            [SetterThrows]
            attribute DOMString download;
            [SetterThrows]
            attribute DOMString ping;
            [SetterThrows]
            attribute DOMString rel;
+           [SetterThrows, Pref="network.http.enablePerElementReferrer"]
+           attribute DOMString referrer;
   readonly attribute DOMTokenList relList;
            [SetterThrows]
            attribute DOMString hreflang;
            [SetterThrows]
            attribute DOMString type;
 
            [SetterThrows]
            attribute DOMString text;
--- a/dom/webidl/HTMLAreaElement.webidl
+++ b/dom/webidl/HTMLAreaElement.webidl
@@ -23,16 +23,18 @@ interface HTMLAreaElement : HTMLElement 
            [SetterThrows]
            attribute DOMString target;
            [SetterThrows]
            attribute DOMString download;
            [SetterThrows]
            attribute DOMString ping;
            [SetterThrows]
            attribute DOMString rel;
+           [SetterThrows, Pref="network.http.enablePerElementReferrer"]
+           attribute DOMString referrer;
   readonly attribute DOMTokenList relList;
 
   // not implemented.
   //
   //       [SetterThrows]
   //       attribute DOMString hreflang;
   //       [SetterThrows]
   //       attribute DOMString type;
--- a/netwerk/base/ReferrerPolicy.h
+++ b/netwerk/base/ReferrerPolicy.h
@@ -26,55 +26,101 @@ enum ReferrerPolicy {
 
   /* spec tokens: always unsafe-url */
   RP_Unsafe_URL                  = nsIHttpChannel::REFERRER_POLICY_UNSAFE_URL,
 
   /* referrer policy is not set */
   RP_Unset                       = nsIHttpChannel::REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE
 };
 
+/* spec tokens: never no-referrer */
+const char kRPS_Never[]                       = "never";
+const char kRPS_No_Referrer[]                 = "no-referrer";
+
+/* spec tokens: origin */
+const char kRPS_Origin[]                      = "origin";
+
+/* spec tokens: default no-referrer-when-downgrade */
+const char kRPS_Default[]                     = "default";
+const char kRPS_No_Referrer_When_Downgrade[]  = "no-referrer-when-downgrade";
+
+/* spec tokens: origin-when-cross-origin */
+const char kRPS_Origin_When_Cross_Origin[]    = "origin-when-cross-origin";
+const char kRPS_Origin_When_Crossorigin[]     = "origin-when-crossorigin";
+
+/* spec tokens: always unsafe-url */
+const char kRPS_Always[]                      = "always";
+const char kRPS_Unsafe_URL[]                  = "unsafe-url";
+
 inline ReferrerPolicy
 ReferrerPolicyFromString(const nsAString& content)
 {
   // This is implemented step by step as described in the Referrer Policy
   // specification, section 6.4 "Determine token's Policy".
-  if (content.LowerCaseEqualsLiteral("never") ||
-      content.LowerCaseEqualsLiteral("no-referrer")) {
+  if (content.LowerCaseEqualsLiteral(kRPS_Never) ||
+      content.LowerCaseEqualsLiteral(kRPS_No_Referrer)) {
     return RP_No_Referrer;
   }
-  if (content.LowerCaseEqualsLiteral("origin")) {
+  if (content.LowerCaseEqualsLiteral(kRPS_Origin)) {
     return RP_Origin;
   }
-  if (content.LowerCaseEqualsLiteral("default") ||
-      content.LowerCaseEqualsLiteral("no-referrer-when-downgrade")) {
+  if (content.LowerCaseEqualsLiteral(kRPS_Default) ||
+      content.LowerCaseEqualsLiteral(kRPS_No_Referrer_When_Downgrade)) {
     return RP_No_Referrer_When_Downgrade;
   }
-  if (content.LowerCaseEqualsLiteral("origin-when-cross-origin") ||
-      content.LowerCaseEqualsLiteral("origin-when-crossorigin")) {
+  if (content.LowerCaseEqualsLiteral(kRPS_Origin_When_Cross_Origin) ||
+      content.LowerCaseEqualsLiteral(kRPS_Origin_When_Crossorigin)) {
     return RP_Origin_When_Crossorigin;
   }
-  if (content.LowerCaseEqualsLiteral("always") ||
-      content.LowerCaseEqualsLiteral("unsafe-url")) {
+  if (content.LowerCaseEqualsLiteral(kRPS_Always) ||
+      content.LowerCaseEqualsLiteral(kRPS_Unsafe_URL)) {
     return RP_Unsafe_URL;
   }
   // Spec says if none of the previous match, use No_Referrer.
   return RP_No_Referrer;
 
 }
 
 inline bool
 IsValidReferrerPolicy(const nsAString& content)
 {
-  return content.LowerCaseEqualsLiteral("never")
-      || content.LowerCaseEqualsLiteral("no-referrer")
-      || content.LowerCaseEqualsLiteral("origin")
-      || content.LowerCaseEqualsLiteral("default")
-      || content.LowerCaseEqualsLiteral("no-referrer-when-downgrade")
-      || content.LowerCaseEqualsLiteral("origin-when-cross-origin")
-      || content.LowerCaseEqualsLiteral("origin-when-crossorigin")
-      || content.LowerCaseEqualsLiteral("always")
-      || content.LowerCaseEqualsLiteral("unsafe-url");
+  return content.LowerCaseEqualsLiteral(kRPS_Never)
+      || content.LowerCaseEqualsLiteral(kRPS_No_Referrer)
+      || content.LowerCaseEqualsLiteral(kRPS_Origin)
+      || content.LowerCaseEqualsLiteral(kRPS_Default)
+      || content.LowerCaseEqualsLiteral(kRPS_No_Referrer_When_Downgrade)
+      || content.LowerCaseEqualsLiteral(kRPS_Origin_When_Cross_Origin)
+      || content.LowerCaseEqualsLiteral(kRPS_Origin_When_Crossorigin)
+      || content.LowerCaseEqualsLiteral(kRPS_Always)
+      || content.LowerCaseEqualsLiteral(kRPS_Unsafe_URL);
+}
+
+inline bool
+IsValidAttributeReferrerPolicy(const nsAString& aContent)
+{
+  // Spec allows only these three policies at the moment
+  // See bug 1178337
+  return aContent.LowerCaseEqualsLiteral(kRPS_No_Referrer)
+      || aContent.LowerCaseEqualsLiteral(kRPS_Origin)
+      || aContent.LowerCaseEqualsLiteral(kRPS_Unsafe_URL);
+}
+
+inline ReferrerPolicy
+AttributeReferrerPolicyFromString(const nsAString& aContent)
+{
+  // if the referrer attribute string is empty, return RP_Unset
+  if (aContent.IsEmpty()) {
+    return RP_Unset;
+  }
+  // if the referrer attribute string is not empty and contains a valid
+  // referrer policy, return the according enum value
+  if (IsValidAttributeReferrerPolicy(aContent)) {
+    return ReferrerPolicyFromString(aContent);
+  }
+  // in any other case the referrer attribute contains an invalid
+  // policy value, we thus return RP_No_Referrer
+  return RP_No_Referrer;
 }
 
 } // namespace net
 } // namespace mozilla
 
 #endif
--- a/netwerk/base/nsINetUtil.idl
+++ b/netwerk/base/nsINetUtil.idl
@@ -6,17 +6,17 @@
 #include "nsISupports.idl"
 
 interface nsIURI;
 interface nsIPrefBranch;
 
 /**
  * nsINetUtil provides various network-related utility methods.
  */
-[scriptable, uuid(ca68c485-9db3-4c12-82a6-4fab7948e2d5)]
+[scriptable, uuid(885d6940-1001-46e7-92ec-d494a78d7784)]
 interface nsINetUtil : nsISupports
 {
   /**
    * Parse a content-type header and return the content type and
    * charset (if any).
    *
    * @param aTypeHeader the header string to parse
    * @param [out] aCharset the charset parameter specified in the
@@ -187,9 +187,21 @@ interface nsINetUtil : nsISupports
    * @return whether a charset parameter was found.  This can be false even in
    * cases when parseContentType would claim to have a charset, if the type
    * that won out does not have a charset parameter specified.
    */
   boolean extractCharsetFromContentType(in AUTF8String aTypeHeader,
                                         out AUTF8String aCharset,
                                         out long aCharsetStart,
                                         out long aCharsetEnd);
+
+/**
+   * Parse an attribute referrer policy string (no-referrer, origin, unsafe-url)
+   * and return the according integer code (defined in nsIHttpChannel.idl)
+   *
+   * @param aPolicyString
+   *        the policy string given as attribute
+   * @return aPolicyEnum
+   *         referrer policy code from nsIHttpChannel.idl, (see parser in
+   *         ReferrerPolicy.h for details)
+   */
+  unsigned long parseAttributePolicyString(in AString aPolicyString);
 };
--- a/netwerk/base/nsIOService.cpp
+++ b/netwerk/base/nsIOService.cpp
@@ -40,16 +40,17 @@
 #include "MainThreadUtils.h"
 #include "nsIWidget.h"
 #include "nsThreadUtils.h"
 #include "mozilla/LoadInfo.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/net/DNS.h"
 #include "CaptivePortalService.h"
+#include "ReferrerPolicy.h"
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsINetworkManager.h"
 #endif
 
 #if defined(XP_WIN)
 #include "nsNativeConnectionHelper.h"
 #endif
@@ -1685,16 +1686,26 @@ nsIOService::ExtractCharsetFromContentTy
     net_ParseContentType(aTypeHeader, ignored, aCharset, aHadCharset,
                          aCharsetStart, aCharsetEnd);
     if (*aHadCharset && *aCharsetStart == *aCharsetEnd) {
         *aHadCharset = false;
     }
     return NS_OK;
 }
 
+// parse policyString to policy enum value (see ReferrerPolicy.h)
+NS_IMETHODIMP
+nsIOService::ParseAttributePolicyString(const nsAString& policyString,
+                                                uint32_t *outPolicyEnum)
+{
+  NS_ENSURE_ARG(outPolicyEnum);
+  *outPolicyEnum = (uint32_t)mozilla::net::AttributeReferrerPolicyFromString(policyString);
+  return NS_OK;
+}
+
 // nsISpeculativeConnect
 class IOServiceProxyCallback final : public nsIProtocolProxyCallback
 {
     ~IOServiceProxyCallback() {}
 
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIPROTOCOLPROXYCALLBACK
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -95,16 +95,17 @@ let initTable = [
   ["ww", "@mozilla.org/embedcomp/window-watcher;1", "nsIWindowWatcher"],
   ["startup", "@mozilla.org/toolkit/app-startup;1", "nsIAppStartup"],
   ["sysinfo", "@mozilla.org/system-info;1", "nsIPropertyBag2"],
   ["clipboard", "@mozilla.org/widget/clipboard;1", "nsIClipboard"],
   ["DOMRequest", "@mozilla.org/dom/dom-request-service;1", "nsIDOMRequestService"],
   ["focus", "@mozilla.org/focus-manager;1", "nsIFocusManager"],
   ["uriFixup", "@mozilla.org/docshell/urifixup;1", "nsIURIFixup"],
   ["blocklist", "@mozilla.org/extensions/blocklist;1", "nsIBlocklistService"],
+  ["netUtils", "@mozilla.org/network/util;1", "nsINetUtil"],
 ];
 
 initTable.forEach(([name, contract, intf, enabled = true]) => {
   if (enabled) {
     XPCOMUtils.defineLazyServiceGetter(Services, name, contract, intf);
   }
 });