Bug 1112922 - Implement request referrer correctly in Fetch API. r=bkelly

--HG--
extra : rebase_source : 226756fee8b777ed30b07cce0f3c5879d66ccf80
This commit is contained in:
Nikhil Marathe 2014-12-23 07:56:19 -08:00
parent 98a481bdeb
commit bf76a3c4e1
7 changed files with 103 additions and 83 deletions

View File

@ -28,6 +28,7 @@
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/URLSearchParams.h"
#include "InternalRequest.h"
#include "InternalResponse.h"
#include "WorkerPrivate.h"
@ -173,6 +174,11 @@ public:
nsCOMPtr<nsIPrincipal> principal = mResolver->GetWorkerPrivate()->GetPrincipal();
nsCOMPtr<nsILoadGroup> loadGroup = mResolver->GetWorkerPrivate()->GetLoadGroup();
nsRefPtr<FetchDriver> fetch = new FetchDriver(mRequest, principal, loadGroup);
nsIDocument* doc = mResolver->GetWorkerPrivate()->GetDocument();
if (doc) {
fetch->SetReferrerPolicy(doc->GetReferrerPolicy());
}
nsresult rv = fetch->Fetch(mResolver);
// Right now we only support async fetch, which should never directly fail.
if (NS_WARN_IF(NS_FAILED(rv))) {
@ -204,13 +210,10 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
}
nsRefPtr<InternalRequest> r = request->GetInternalRequest();
if (!r->ReferrerIsNone()) {
nsAutoCString ref;
aRv = GetRequestReferrer(aGlobal, r, ref);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
r->SetReferrer(ref);
aRv = UpdateRequestReferrer(aGlobal, r);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
if (NS_IsMainThread()) {
@ -230,6 +233,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
nsCOMPtr<nsILoadGroup> loadGroup = doc->GetDocumentLoadGroup();
nsRefPtr<FetchDriver> fetch =
new FetchDriver(r, doc->NodePrincipal(), loadGroup);
fetch->SetReferrerPolicy(doc->GetReferrerPolicy());
aRv = fetch->Fetch(resolver);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
@ -357,15 +361,19 @@ WorkerFetchResolver::OnResponseEnd()
}
}
// Empty string for no-referrer. FIXME(nsm): Does returning empty string
// actually lead to no-referrer in the base channel?
// 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 returns the full API referrer URL of the relevant global.
// 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
GetRequestReferrer(nsIGlobalObject* aGlobal, const InternalRequest* aRequest, nsCString& aReferrer)
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest)
{
if (aRequest->ReferrerIsURL()) {
aReferrer = aRequest->ReferrerAsURL();
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;
}
@ -373,24 +381,16 @@ GetRequestReferrer(nsIGlobalObject* aGlobal, const InternalRequest* aRequest, ns
if (window) {
nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
if (doc) {
nsCOMPtr<nsIURI> docURI = doc->GetDocumentURI();
nsAutoCString origin;
nsresult rv = nsContentUtils::GetASCIIOrigin(docURI, origin);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsAutoString referrer;
doc->GetReferrer(referrer);
aReferrer = NS_ConvertUTF16toUTF8(referrer);
aRequest->SetReferrer(referrer);
}
} else {
WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(worker);
worker->AssertIsOnWorkerThread();
aReferrer = worker->GetLocationInfo().mHref;
// XXX(nsm): Algorithm says "If source is not a URL..." but when is it
// not a URL?
WorkerPrivate::LocationInfo& info = worker->GetLocationInfo();
aRequest->SetReferrer(NS_ConvertUTF8toUTF16(info.mHref));
}
return NS_OK;

View File

@ -40,7 +40,7 @@ FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput,
const RequestInit& aInit, ErrorResult& aRv);
nsresult
GetRequestReferrer(nsIGlobalObject* aGlobal, const InternalRequest* aRequest, nsCString& aReferrer);
UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest);
/*
* Creates an nsIInputStream based on the fetch specifications 'extract a byte

View File

@ -41,6 +41,7 @@ FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal,
, mLoadGroup(aLoadGroup)
, mRequest(aRequest)
, mFetchRecursionCount(0)
, mReferrerPolicy(net::RP_Default)
, mResponseAvailableCalled(false)
{
}
@ -386,16 +387,19 @@ FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthentica
httpChan->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */);
}
// Step 2. Set the referrer. This is handled better in Bug 1112922.
MOZ_ASSERT(mRequest->ReferrerIsURL());
nsCString referrer = mRequest->ReferrerAsURL();
// 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> uri;
rv = NS_NewURI(getter_AddRefs(uri), referrer, nullptr, nullptr, ios);
nsCOMPtr<nsIURI> refURI;
rv = NS_NewURI(getter_AddRefs(refURI), referrer, nullptr, nullptr);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}
rv = httpChan->SetReferrer(uri);
rv = httpChan->SetReferrerWithPolicy(refURI, mReferrerPolicy);
if (NS_WARN_IF(NS_FAILED(rv))) {
return FailWithNetworkError();
}

View File

@ -14,6 +14,7 @@
#include "nsRefPtr.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/net/ReferrerPolicy.h"
class nsIOutputStream;
class nsILoadGroup;
@ -56,6 +57,14 @@ public:
nsILoadGroup* aLoadGroup);
NS_IMETHOD Fetch(FetchDriverObserver* aObserver);
void
SetReferrerPolicy(net::ReferrerPolicy aPolicy)
{
// Cannot set policy after Fetch() has been called.
MOZ_ASSERT(mFetchRecursionCount == 0);
mReferrerPolicy = aPolicy;
}
private:
nsCOMPtr<nsIPrincipal> mPrincipal;
nsCOMPtr<nsILoadGroup> mLoadGroup;
@ -68,6 +77,7 @@ private:
nsCOMPtr<nsIChannel> mOldRedirectChannel;
nsCOMPtr<nsIChannel> mNewRedirectChannel;
uint32_t mFetchRecursionCount;
net::ReferrerPolicy mReferrerPolicy;
DebugOnly<bool> mResponseAvailableCalled;

View File

@ -13,6 +13,11 @@
#include "nsIContentPolicy.h"
#include "nsIInputStream.h"
#include "nsISupportsImpl.h"
#ifdef DEBUG
#include "nsIURLParser.h"
#include "nsNetCID.h"
#include "nsServiceManagerUtils.h"
#endif
class nsIDocument;
class nsPIDOMWindow;
@ -23,6 +28,8 @@ namespace dom {
class FetchBodyStream;
class Request;
#define kFETCH_CLIENT_REFERRER_STR "about:client"
class InternalRequest MOZ_FINAL
{
friend class Request;
@ -38,14 +45,6 @@ public:
FRAMETYPE_NONE,
};
// Since referrer type can be none, client or a URL.
enum ReferrerType
{
REFERRER_NONE = 0,
REFERRER_CLIENT,
REFERRER_URL,
};
enum ResponseTainting
{
RESPONSETAINT_BASIC,
@ -57,7 +56,7 @@ public:
: mMethod("GET")
, mHeaders(new InternalHeaders(HeadersGuardEnum::None))
, mContextFrameType(FRAMETYPE_NONE)
, mReferrerType(REFERRER_CLIENT)
, mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR))
, mMode(RequestMode::No_cors)
, mCredentialsMode(RequestCredentials::Omit)
, mResponseTainting(RESPONSETAINT_BASIC)
@ -84,8 +83,7 @@ public:
, mBodyStream(aOther.mBodyStream)
, mContext(aOther.mContext)
, mContextFrameType(aOther.mContextFrameType)
, mReferrerType(aOther.mReferrerType)
, mReferrerURL(aOther.mReferrerURL)
, mReferrer(aOther.mReferrer)
, mMode(aOther.mMode)
, mCredentialsMode(aOther.mCredentialsMode)
, mResponseTainting(aOther.mResponseTainting)
@ -134,38 +132,51 @@ public:
mURL.Assign(aURL);
}
bool
ReferrerIsNone() const
void
GetReferrer(nsAString& aReferrer) const
{
return mReferrerType == REFERRER_NONE;
}
bool
ReferrerIsURL() const
{
return mReferrerType == REFERRER_URL;
}
bool
ReferrerIsClient() const
{
return mReferrerType == REFERRER_CLIENT;
}
nsCString
ReferrerAsURL() const
{
MOZ_ASSERT(ReferrerIsURL());
return mReferrerURL;
aReferrer.Assign(mReferrer);
}
void
SetReferrer(const nsACString& aReferrer)
SetReferrer(const nsAString& aReferrer)
{
// May be removed later.
MOZ_ASSERT(!ReferrerIsNone());
mReferrerType = REFERRER_URL;
mReferrerURL.Assign(aReferrer);
#ifdef DEBUG
bool validReferrer = false;
if (aReferrer.IsEmpty() ||
aReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) {
validReferrer = true;
} else {
nsCOMPtr<nsIURLParser> parser = do_GetService(NS_STDURLPARSER_CONTRACTID);
if (!parser) {
NS_WARNING("Could not get parser to validate URL!");
} else {
uint32_t schemePos;
int32_t schemeLen;
uint32_t authorityPos;
int32_t authorityLen;
uint32_t pathPos;
int32_t pathLen;
NS_ConvertUTF16toUTF8 ref(aReferrer);
nsresult rv = parser->ParseURL(ref.get(), ref.Length(),
&schemePos, &schemeLen,
&authorityPos, &authorityLen,
&pathPos, &pathLen);
if (NS_FAILED(rv)) {
NS_WARNING("Invalid referrer URL!");
} else if (schemeLen < 0 || authorityLen < 0) {
NS_WARNING("Invalid referrer URL!");
} else {
validReferrer = true;
}
}
}
MOZ_ASSERT(validReferrer);
#endif
mReferrer.Assign(aReferrer);
}
bool
@ -292,10 +303,11 @@ private:
nsContentPolicyType mContext;
ContextFrameType mContextFrameType;
ReferrerType mReferrerType;
// When mReferrerType is REFERRER_URL.
nsCString mReferrerURL;
// Empty string: no-referrer
// "about:client": client (default)
// URL: an URL
nsString mReferrer;
RequestMode mMode;
RequestCredentials mCredentialsMode;

View File

@ -75,15 +75,9 @@ public:
}
void
GetReferrer(DOMString& aReferrer) const
GetReferrer(nsAString& aReferrer) const
{
if (mRequest->ReferrerIsNone()) {
aReferrer.AsAString() = EmptyString();
return;
}
// FIXME(nsm): Spec doesn't say what to do if referrer is client.
aReferrer.AsAString() = NS_ConvertUTF8toUTF16(mRequest->mReferrerURL);
mRequest->GetReferrer(aReferrer);
}
InternalHeaders*

View File

@ -11,7 +11,7 @@ function testDefaultCtor() {
is(req.method, "GET", "Default Request method is GET");
ok(req.headers instanceof Headers, "Request should have non-null Headers object");
is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client.");
is(req.mode, "cors", "Request mode for string input is cors");
is(req.credentials, "omit", "Default Request credentials is omit");
@ -19,7 +19,7 @@ function testDefaultCtor() {
is(req.method, "GET", "Default Request method is GET");
ok(req.headers instanceof Headers, "Request should have non-null Headers object");
is(req.url, self.location.href, "URL should be resolved with entry settings object's API base URL");
is(req.referrer, "", "Default referrer is `client` which serializes to empty string.");
is(req.referrer, "about:client", "Default referrer is `client` which serializes to about:client.");
is(req.mode, "cors", "Request mode string input is cors");
is(req.credentials, "omit", "Default Request credentials is omit");
}
@ -37,7 +37,7 @@ function testClone() {
is(req.headers.get('content-length'), "5", "Request content-length should be 5.");
ok(req.url === (new URL("./cloned_request.txt", self.location.href)).href,
"URL should be resolved with entry settings object's API base URL");
ok(req.referrer === "", "Default referrer is `client` which serializes to empty string.");
ok(req.referrer === "about:client", "Default referrer is `client` which serializes to about:client.");
ok(req.mode === "same-origin", "Request mode is same-origin");
ok(req.credentials === "same-origin", "Default credentials is same-origin");
}