Bug 1173912 Fail opaque responses for client requests. r=ehsan

This commit is contained in:
Ben Kelly 2015-07-14 13:11:26 -07:00
parent cc4c8b3dd4
commit 1d8798cbc9
9 changed files with 84 additions and 10 deletions

View File

@ -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);

View File

@ -3432,6 +3432,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) {

View File

@ -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

View File

@ -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);

View File

@ -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.

View File

@ -149,15 +149,17 @@ class RespondWithHandler final : public PromiseNativeHandler
nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
RequestMode mRequestMode;
bool mIsClientRequest;
public:
NS_DECL_ISUPPORTS
RespondWithHandler(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
RequestMode aRequestMode)
RequestMode aRequestMode, bool aIsClientRequest)
: mInterceptedChannel(aChannel)
, mServiceWorker(aServiceWorker)
, mRequestMode(aRequestMode)
, mIsClientRequest(aIsClientRequest)
{
}
@ -262,15 +264,26 @@ RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> 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;
}
@ -357,9 +370,11 @@ FetchEvent::RespondWith(const ResponseOrPromise& aArg, ErrorResult& aRv)
} else if (aArg.IsPromise()) {
promise = &aArg.GetAsPromise();
}
nsRefPtr<InternalRequest> ir = mRequest->GetInternalRequest();
mWaitToRespond = true;
nsRefPtr<RespondWithHandler> handler =
new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode());
new RespondWithHandler(mChannel, mServiceWorker, mRequest->Mode(),
ir->IsClientRequest());
promise->AppendNativeHandler(handler);
}

View File

@ -21,10 +21,12 @@ function runTests() {
return testFetchAppResource('test_custom_content_type',
'customContentType', 'text/html');
})
.then(testRedirectedResponse)
.then(testRedirectedHttpsResponse)
.then(testCachedRedirectedResponse)
.then(testCachedRedirectedHttpsResponse)
// XXX: Cross-origin interceptions without CORS result in opaque responses
// which are illegal for navigations like iframes. (bug 1183313)
//.then(testRedirectedResponse)
//.then(testRedirectedHttpsResponse)
//.then(testCachedRedirectedResponse)
//.then(testCachedRedirectedHttpsResponse)
.then(done);
}
</script>

View File

@ -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