From 918ed053a757f7b9df5c7890f23b555ec22828c2 Mon Sep 17 00:00:00 2001 From: Ben Kelly Date: Fri, 10 Jul 2015 14:28:32 -0700 Subject: [PATCH] Bug 1173912 Fail opaque responses for client requests. r=ehsan --- docshell/base/nsDocShell.cpp | 2 ++ dom/base/nsContentUtils.cpp | 2 ++ dom/fetch/InternalRequest.cpp | 39 +++++++++++++++++++++ dom/fetch/InternalRequest.h | 10 ++++++ dom/locales/en-US/chrome/dom/dom.properties | 2 ++ dom/workers/ServiceWorkerEvents.cpp | 27 ++++++++++---- xpcom/base/ErrorList.h | 2 ++ 7 files changed, 78 insertions(+), 6 deletions(-) diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index afb398bee95..ff9d7954f3b 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -5203,6 +5203,7 @@ nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, case NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE: case NS_ERROR_INTERCEPTED_ERROR_RESPONSE: case NS_ERROR_INTERCEPTED_USED_RESPONSE: + case NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION: // ServiceWorker intercepted request, but something went wrong. nsContentUtils::MaybeReportInterceptionErrorToConsole(GetDocument(), aError); @@ -7840,6 +7841,7 @@ nsDocShell::EndPageLoad(nsIWebProgress* aProgress, aStatus == NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE || aStatus == NS_ERROR_INTERCEPTED_ERROR_RESPONSE || aStatus == NS_ERROR_INTERCEPTED_USED_RESPONSE || + aStatus == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame DisplayLoadError(aStatus, url, nullptr, aChannel); diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 3521e06a26b..2ec458ea041 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -3431,6 +3431,8 @@ nsContentUtils::MaybeReportInterceptionErrorToConsole(nsIDocument* aDocument, messageName = "InterceptedErrorResponse"; } else if (aError == NS_ERROR_INTERCEPTED_USED_RESPONSE) { messageName = "InterceptedUsedResponse"; + } else if (aError == NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION) { + messageName = "ClientRequestOpaqueInterception"; } if (messageName) { diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp index 448d5d4ff07..86b5c42e563 100644 --- a/dom/fetch/InternalRequest.cpp +++ b/dom/fetch/InternalRequest.cpp @@ -195,5 +195,44 @@ InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aConte return context; } +bool +InternalRequest::IsNavigationRequest() const +{ + // https://fetch.spec.whatwg.org/#navigation-request-context + // + // A navigation request context is one of "form", "frame", "hyperlink", + // "iframe", "internal" (as long as context frame type is not "none"), + // "location", "metarefresh", and "prerender". + // + // TODO: include equivalent check for "form" context + // TODO: include equivalent check for "prerender" context + return mContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + mContentPolicyType == nsIContentPolicy::TYPE_REFRESH; +} + +bool +InternalRequest::IsWorkerRequest() const +{ + // https://fetch.spec.whatwg.org/#worker-request-context + // + // A worker request context is one of "serviceworker", "sharedworker", and + // "worker". + // + // Note, service workers are not included here because currently there is + // no way to generate a Request with a "serviceworker" RequestContext. + // ServiceWorker scripts cannot be intercepted. + return mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + mContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; +} + +bool +InternalRequest::IsClientRequest() const +{ + return IsNavigationRequest() || IsWorkerRequest(); +} + } // namespace dom } // namespace mozilla diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h index 92b6cc3bab1..d20ac226f00 100644 --- a/dom/fetch/InternalRequest.h +++ b/dom/fetch/InternalRequest.h @@ -356,6 +356,16 @@ public: mCreatedByFetchEvent = false; } + bool + IsNavigationRequest() const; + + bool + IsWorkerRequest() const; + + bool + IsClientRequest() const; + + private: // Does not copy mBodyStream. Use fallible Clone() for complete copy. explicit InternalRequest(const InternalRequest& aOther); diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 3c4b63fb43d..c76539dbf82 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -176,3 +176,5 @@ BadOpaqueInterceptionRequestMode=A ServiceWorker passed an opaque Response to Fe InterceptedErrorResponse=A ServiceWorker passed an Error Response to FetchEvent.respondWith(). This typically means the ServiceWorker performed an invalid fetch() call. # LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", or "Response.clone()". InterceptedUsedResponse=A ServiceWorker passed a used Response to FetchEvent.respondWith(). The body of a Response may only be read once. Use Response.clone() to access the body multiple times. +# LOCALIZATION NOTE: Do not translate "ServiceWorker", "Response", "FetchEvent.respondWith()", "FetchEvent.request", or "Worker". +ClientRequestOpaqueInterception=A ServiceWorker passed an opaque Response to FetchEvent.respondWith() while FetchEvent.request was a client request. A client request is generally a browser navigation or top-level Worker script. diff --git a/dom/workers/ServiceWorkerEvents.cpp b/dom/workers/ServiceWorkerEvents.cpp index 84df31ad58c..1d12c7498c5 100644 --- a/dom/workers/ServiceWorkerEvents.cpp +++ b/dom/workers/ServiceWorkerEvents.cpp @@ -149,13 +149,15 @@ class RespondWithHandler final : public PromiseNativeHandler nsMainThreadPtrHandle mInterceptedChannel; nsMainThreadPtrHandle mServiceWorker; RequestMode mRequestMode; + bool mIsClientRequest; public: RespondWithHandler(nsMainThreadPtrHandle& aChannel, nsMainThreadPtrHandle& aServiceWorker, - RequestMode aRequestMode) + RequestMode aRequestMode, bool aIsClientRequest) : mInterceptedChannel(aChannel) , mServiceWorker(aServiceWorker) , mRequestMode(aRequestMode) + , mIsClientRequest(aIsClientRequest) { } @@ -256,15 +258,26 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle aValu return; } - // Section 4.2, step 2.2 "If either response's type is "opaque" and request's - // mode is not "no-cors" or response's type is error, return a network error." + // Section 4.2, step 2.2: + // If one of the following conditions is true, return a network error: + // * response's type is "error". + // * request's mode is not "no-cors" and response's type is "opaque". + // * request is a client request and response's type is neither "basic" + // nor "default". + + if (response->Type() == ResponseType::Error) { + autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + return; + } + if (response->Type() == ResponseType::Opaque && mRequestMode != RequestMode::No_cors) { autoCancel.SetCancelStatus(NS_ERROR_BAD_OPAQUE_INTERCEPTION_REQUEST_MODE); return; } - if (response->Type() == ResponseType::Error) { - autoCancel.SetCancelStatus(NS_ERROR_INTERCEPTED_ERROR_RESPONSE); + if (mIsClientRequest && response->Type() != ResponseType::Basic && + response->Type() != ResponseType::Default) { + autoCancel.SetCancelStatus(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION); return; } @@ -351,9 +364,11 @@ FetchEvent::RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv) } else if (aArg.IsPromise()) { promise = &aArg.GetAsPromise(); } + nsRefPtr ir = mRequest->GetInternalRequest(); mWaitToRespond = true; nsRefPtr handler = - new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode()); + new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(), + ir->IsClientRequest()); promise->AppendNativeHandler(handler); } diff --git a/xpcom/base/ErrorList.h b/xpcom/base/ErrorList.h index 13a42de1990..e05c9dd70b2 100644 --- a/xpcom/base/ErrorList.h +++ b/xpcom/base/ErrorList.h @@ -331,6 +331,8 @@ ERROR(NS_ERROR_INTERCEPTED_ERROR_RESPONSE, FAILURE(103)), /* Service worker intercepted with a response with bodyUsed set to true */ ERROR(NS_ERROR_INTERCEPTED_USED_RESPONSE, FAILURE(104)), + /* Service worker intercepted a client request with an opaque response */ + ERROR(NS_ERROR_CLIENT_REQUEST_OPAQUE_INTERCEPTION, FAILURE(105)), #undef MODULE