Bug 1169044 - Patch 1 - Refactor setting referrer and referrer policy between fetch and XHR. r=khuey
authorNikhil Marathe <nsm.nikhil@gmail.com>
Thu, 04 Jun 2015 14:05:00 -0700
changeset 250200 a8473627ca5f30a8bba16b9bb0a2a99d9d59f3a5
parent 250199 1c071a0d3421544bf127cf1d7b7a3a35c18eb807
child 250201 67b6e55eb4acb234e5d847be5b0d80110dee6862
push id61475
push usernsm.nikhil@gmail.com
push dateThu, 25 Jun 2015 16:52:58 +0000
treeherdermozilla-inbound@d44bc296a927 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs1169044
milestone41.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 1169044 - Patch 1 - Refactor setting referrer and referrer policy between fetch and XHR. r=khuey
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/base/nsXMLHttpRequest.cpp
dom/fetch/Fetch.cpp
dom/fetch/FetchDriver.cpp
dom/tests/mochitest/fetch/test_fetch_cors.js
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7857,8 +7857,59 @@ nsContentUtils::InternalContentPolicyTyp
   case nsIContentPolicy::TYPE_INTERNAL_VIDEO:
   case nsIContentPolicy::TYPE_INTERNAL_TRACK:
     return nsIContentPolicy::TYPE_MEDIA;
 
   default:
     return aType;
   }
 }
+
+
+nsresult
+nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
+                                              nsIDocument* aDoc,
+                                              nsIHttpChannel* aChannel)
+{
+  NS_ENSURE_ARG_POINTER(aPrincipal);
+  NS_ENSURE_ARG_POINTER(aChannel);
+
+  nsCOMPtr<nsIURI> principalURI;
+
+  if (IsSystemPrincipal(aPrincipal)) {
+    return NS_OK;
+  }
+
+  aPrincipal->GetURI(getter_AddRefs(principalURI));
+
+  if (!aDoc) {
+    return aChannel->SetReferrerWithPolicy(principalURI, net::RP_Default);
+  }
+
+  // If it weren't for history.push/replaceState, we could just use the
+  // principal's URI here.  But since we want changes to the URI effected
+  // by push/replaceState to be reflected in the XHR referrer, we have to
+  // be more clever.
+  //
+  // If the document's original URI (before any push/replaceStates) matches
+  // our principal, then we use the document's current URI (after
+  // push/replaceStates).  Otherwise (if the document is, say, a data:
+  // URI), we just use the principal's URI.
+  nsCOMPtr<nsIURI> docCurURI = aDoc->GetDocumentURI();
+  nsCOMPtr<nsIURI> docOrigURI = aDoc->GetOriginalURI();
+
+  nsCOMPtr<nsIURI> referrerURI;
+
+  if (principalURI && docCurURI && docOrigURI) {
+    bool equal = false;
+    principalURI->Equals(docOrigURI, &equal);
+    if (equal) {
+      referrerURI = docCurURI;
+    }
+  }
+
+  if (!referrerURI) {
+    referrerURI = principalURI;
+  }
+
+  net::ReferrerPolicy referrerPolicy = aDoc->GetReferrerPolicy();
+  return aChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
+}
\ No newline at end of file
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2401,16 +2401,35 @@ public:
                                 mozilla::dom::EventTarget* aChromeEventHandler,
                                 bool aFireIfShowing);
 
   static void FirePageHideEvent(nsIDocShellTreeItem* aItem,
                                 mozilla::dom::EventTarget* aChromeEventHandler);
 
   static already_AddRefed<nsPIWindowRoot> GetWindowRoot(nsIDocument* aDoc);
 
+  /*
+   * Implements step 3.1 and 3.3 of the Determine request's Referrer algorithm
+   * from the Referrer Policy specification.
+   *
+   * The referrer policy of the document is applied by Necko when using
+   * channels.
+   *
+   * For documents representing an iframe srcdoc attribute, the document sets
+   * its own URI correctly, so this method simply uses the document's original
+   * or current URI as appropriate.
+   *
+   * aDoc may be null.
+   *
+   * https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
+   */
+  static nsresult SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal,
+                                                nsIDocument* aDoc,
+                                                nsIHttpChannel* aChannel);
+
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
 
--- a/dom/base/nsXMLHttpRequest.cpp
+++ b/dom/base/nsXMLHttpRequest.cpp
@@ -2677,60 +2677,20 @@ nsXMLHttpRequest::Send(nsIVariant* aVari
   // upload anything
   nsAutoCString method;
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel));
 
   if (httpChannel) {
     httpChannel->GetRequestMethod(method); // If GET, method name will be uppercase
 
     if (!IsSystemXHR()) {
-      // Get the referrer for the request.
-      //
-      // If it weren't for history.push/replaceState, we could just use the
-      // principal's URI here.  But since we want changes to the URI effected
-      // by push/replaceState to be reflected in the XHR referrer, we have to
-      // be more clever.
-      //
-      // If the document's original URI (before any push/replaceStates) matches
-      // our principal, then we use the document's current URI (after
-      // push/replaceStates).  Otherwise (if the document is, say, a data:
-      // URI), we just use the principal's URI.
-
-      nsCOMPtr<nsIURI> principalURI;
-      mPrincipal->GetURI(getter_AddRefs(principalURI));
-
-      nsIScriptContext* sc = GetContextForEventHandlers(&rv);
-      NS_ENSURE_SUCCESS(rv, rv);
-      nsCOMPtr<nsIDocument> doc =
-        nsContentUtils::GetDocumentFromScriptContext(sc);
-
-      nsCOMPtr<nsIURI> docCurURI;
-      nsCOMPtr<nsIURI> docOrigURI;
-      net::ReferrerPolicy referrerPolicy = net::RP_Default;
-
-      if (doc) {
-        docCurURI = doc->GetDocumentURI();
-        docOrigURI = doc->GetOriginalURI();
-        referrerPolicy = doc->GetReferrerPolicy();
-      }
-
-      nsCOMPtr<nsIURI> referrerURI;
-
-      if (principalURI && docCurURI && docOrigURI) {
-        bool equal = false;
-        principalURI->Equals(docOrigURI, &equal);
-        if (equal) {
-          referrerURI = docCurURI;
-        }
-      }
-
-      if (!referrerURI)
-        referrerURI = principalURI;
-
-      httpChannel->SetReferrerWithPolicy(referrerURI, referrerPolicy);
+      nsCOMPtr<nsPIDOMWindow> owner = GetOwner();
+      nsCOMPtr<nsIDocument> doc = owner ? owner->GetExtantDoc() : nullptr;
+      nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, doc,
+                                                    httpChannel);
     }
 
     // Some extensions override the http protocol handler and provide their own
     // implementation. The channels returned from that implementation doesn't
     // seem to always implement the nsIUploadChannel2 interface, presumably
     // because it's a new interface.
     // Eventually we should remove this and simply require that http channels
     // implement the new interface.
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -212,21 +212,16 @@ FetchRequest(nsIGlobalObject* aGlobal, c
 
   nsRefPtr<Request> request = Request::Constructor(global, aInput, aInit, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   nsRefPtr<InternalRequest> r = request->GetInternalRequest();
 
-  aRv = UpdateRequestReferrer(aGlobal, r);
-  if (NS_WARN_IF(aRv.Failed())) {
-    return nullptr;
-  }
-
   if (NS_IsMainThread()) {
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
     nsCOMPtr<nsIDocument> doc;
     nsCOMPtr<nsILoadGroup> loadGroup;
     nsIPrincipal* principal;
     if (window) {
       doc = window->GetExtantDoc();
       if (!doc) {
@@ -393,68 +388,16 @@ WorkerFetchResolver::OnResponseEnd()
     new WorkerFetchResponseEndRunnable(this);
 
   AutoSafeJSContext cx;
   if (!r->Dispatch(cx)) {
     NS_WARNING("Could not dispatch fetch resolve end");
   }
 }
 
-// This method sets the request's referrerURL, as specified by the "determine
-// request's referrer" steps from Referrer Policy [1].
-// The actual referrer policy and stripping is dealt with by HttpBaseChannel,
-// this always sets the full API referrer URL of the relevant global if it is
-// not already a url or no-referrer.
-// [1]: https://w3c.github.io/webappsec/specs/referrer-policy/#determine-requests-referrer
-nsresult
-UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest)
-{
-  nsAutoString originalReferrer;
-  aRequest->GetReferrer(originalReferrer);
-  // If it is no-referrer ("") or a URL, don't modify.
-  if (!originalReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
-  if (window) {
-    nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
-    if (doc) {
-      nsAutoString referrer;
-      doc->GetReferrer(referrer);
-      aRequest->SetReferrer(referrer);
-    }
-  } else if (NS_IsMainThread()) {
-    // Pull the principal from the global for non-worker scripts.
-    nsIPrincipal *principal = aGlobal->PrincipalOrNull();
-    bool isNull;
-    // Only set the referrer if the principal is present,
-    // and the principal is not null or the system principal.
-    if (principal &&
-        NS_SUCCEEDED(principal->GetIsNullPrincipal(&isNull)) && !isNull &&
-        !nsContentUtils::IsSystemPrincipal(principal)) {
-      nsCOMPtr<nsIURI> uri;
-      if (NS_SUCCEEDED(principal->GetURI(getter_AddRefs(uri))) && uri) {
-        nsAutoCString referrer;
-        if (NS_SUCCEEDED(uri->GetSpec(referrer))) {
-          aRequest->SetReferrer(NS_ConvertUTF8toUTF16(referrer));
-        }
-      }
-    }
-  } else {
-    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
-    MOZ_ASSERT(worker);
-    worker->AssertIsOnWorkerThread();
-    WorkerPrivate::LocationInfo& info = worker->GetLocationInfo();
-    aRequest->SetReferrer(NS_ConvertUTF8toUTF16(info.mHref));
-  }
-
-  return NS_OK;
-}
-
 namespace {
 nsresult
 ExtractFromArrayBuffer(const ArrayBuffer& aBuffer,
                        nsIInputStream** aStream)
 {
   aBuffer.ComputeLengthAndData();
   //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok.
   return NS_NewByteInputStream(aStream,
--- a/dom/fetch/FetchDriver.cpp
+++ b/dom/fetch/FetchDriver.cpp
@@ -411,31 +411,47 @@ FetchDriver::HttpFetch(bool aCORSFlag, b
     mRequest->Headers()->GetEntries(headers);
     for (uint32_t i = 0; i < headers.Length(); ++i) {
       httpChan->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */);
     }
 
     // Step 2. Set the referrer.
     nsAutoString referrer;
     mRequest->GetReferrer(referrer);
-    // The referrer should have already been resolved to a URL by the caller.
-    MOZ_ASSERT(!referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR));
-    if (!referrer.IsEmpty()) {
-      nsCOMPtr<nsIURI> refURI;
-      rv = NS_NewURI(getter_AddRefs(refURI), referrer, nullptr, nullptr);
+    if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
+      rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal,
+                                                         mDocument,
+                                                         httpChan);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return FailWithNetworkError();
+      }
+    } else if (referrer.IsEmpty()) {
+      rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return FailWithNetworkError();
+      }
+    } else {
+      // From "Determine request's Referrer" step 3
+      // "If request's referrer is a URL, let referrerSource be request's
+      // referrer."
+      //
+      // This allows ServiceWorkers to function transparently when the referrer
+      // of the intercepted request is already set.
+      nsCOMPtr<nsIURI> referrerURI;
+      rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return FailWithNetworkError();
       }
 
-      net::ReferrerPolicy referrerPolicy = net::RP_Default;
-      if (mDocument) {
-        referrerPolicy = mDocument->GetReferrerPolicy();
-      }
-
-      rv = httpChan->SetReferrerWithPolicy(refURI, referrerPolicy);
+      // FIXME(nsm): Can we assert that this case can only happen in
+      // ServiceWorkers and assume null mDocument?
+      rv =
+        httpChan->SetReferrerWithPolicy(nullptr,
+                                        mDocument ? mDocument->GetReferrerPolicy() :
+                                                    net::RP_Default);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return FailWithNetworkError();
       }
     }
 
     // Step 3 "If HTTPRequest's force Origin header flag is set..."
     if (mRequest->ForceOriginHeader()) {
       nsAutoString origin;
--- a/dom/tests/mochitest/fetch/test_fetch_cors.js
+++ b/dom/tests/mochitest/fetch/test_fetch_cors.js
@@ -1245,20 +1245,40 @@ function testRedirects() {
         ok(e instanceof TypeError, "Exception should be TypeError for " + test.toSource());
       });
     })(request, test));
   }
 
   return Promise.all(fetches);
 }
 
+function testReferrer() {
+  var referrer;
+  if (self && self.location) {
+    referrer = self.location.href;
+  } else {
+    referrer = document.documentURI;
+  }
+
+  var dict = {
+    'Referer': referrer
+  };
+  return fetch(corsServerPath + "headers=" + dict.toSource()).then(function(res) {
+    is(res.status, 200, "expected correct referrer header to be sent");
+    dump(res.statusText);
+  }, function(e) {
+    ok(false, "expected correct referrer header to be sent");
+  });
+}
+
 function runTest() {
   testNoCorsCtor();
 
   return Promise.resolve()
     .then(testModeSameOrigin)
     .then(testModeNoCors)
     .then(testModeCors)
     .then(testSameOriginCredentials)
     .then(testCrossOriginCredentials)
     .then(testRedirects)
+    .then(testReferrer)
     // Put more promise based tests here.
 }