nsresult FetchDriver::ContinueFetch(bool aCORSFlag) { workers::AssertIsOnMainThread(); nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> requestURI; nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } // Begin Step 4 of the Fetch algorithm // https://fetch.spec.whatwg.org/#fetching // FIXME(nsm): Bug 1039846: Add CSP checks nsAutoCString scheme; rv = requestURI->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */); if ((!aCORSFlag && NS_SUCCEEDED(rv)) || (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) || scheme.EqualsLiteral("about")) { return BasicFetch(); } if (mRequest->Mode() == RequestMode::Same_origin) { return FailWithNetworkError(); } if (mRequest->Mode() == RequestMode::No_cors) { mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE); return BasicFetch(); } if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { return FailWithNetworkError(); } bool corsPreflight = false; if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) { corsPreflight = true; } mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); return HttpFetch(true /* aCORSFlag */, corsPreflight); }
nsresult FetchDriver::ContinueFetch(bool aCORSFlag) { workers::AssertIsOnMainThread(); MainFetchOp nextOp = SetTaintingAndGetNextOp(aCORSFlag); if (nextOp.mType == NETWORK_ERROR) { return FailWithNetworkError(); } if (nextOp.mType == BASIC_FETCH) { return BasicFetch(); } if (nextOp.mType == HTTP_FETCH) { return HttpFetch(nextOp.mCORSFlag, nextOp.mCORSPreflightFlag); } MOZ_ASSERT_UNREACHABLE("Unexpected main fetch operation!"); return FailWithNetworkError(); }
nsresult FetchDriver::Fetch(AbortSignal* aSignal, FetchDriverObserver* aObserver) { AssertIsOnMainThread(); #ifdef DEBUG MOZ_ASSERT(!mFetchCalled); mFetchCalled = true; #endif mObserver = aObserver; Telemetry::Accumulate(Telemetry::SERVICE_WORKER_REQUEST_PASSTHROUGH, mRequest->WasCreatedByFetchEvent()); // FIXME(nsm): Deal with HSTS. MOZ_RELEASE_ASSERT(!mRequest->IsSynchronous(), "Synchronous fetch not supported"); UniquePtr<mozilla::ipc::PrincipalInfo> principalInfo(new mozilla::ipc::PrincipalInfo()); nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo.get()); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } mRequest->SetPrincipalInfo(std::move(principalInfo)); // If the signal is aborted, it's time to inform the observer and terminate // the operation. if (aSignal) { if (aSignal->Aborted()) { Abort(); return NS_OK; } Follow(aSignal); } rv = HttpFetch(mRequest->GetPreferredAlternativeDataType()); if (NS_FAILED(rv)) { FailWithNetworkError(rv); } // Any failure is handled by FailWithNetworkError notifying the aObserver. return NS_OK; }
NS_IMETHODIMP FetchDriver::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) { workers::AssertIsOnMainThread(); if (mPipeOutputStream) { mPipeOutputStream->Close(); } if (NS_FAILED(aStatusCode)) { FailWithNetworkError(); return aStatusCode; } ContinueHttpFetchAfterNetworkFetch(); return NS_OK; }
NS_IMETHODIMP FetchDriver::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) { workers::AssertIsOnMainThread(); if (NS_FAILED(aStatusCode)) { nsCOMPtr<nsIAsyncOutputStream> outputStream = do_QueryInterface(mPipeOutputStream); if (outputStream) { outputStream->CloseWithStatus(NS_BINDING_FAILED); } // We proceed as usual here, since we've already created a successful response // from OnStartRequest. } else { MOZ_ASSERT(mResponse); MOZ_ASSERT(!mResponse->IsError()); // From "Main Fetch" step 17: SRI-part3. if (mResponse->Type() != ResponseType::Error && !mRequest->GetIntegrity().IsEmpty()) { MOZ_ASSERT(mSRIDataVerifier); nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsIConsoleReportCollector* aReporter = nullptr; if (mObserver) { aReporter = mObserver->GetReporter(); } nsAutoCString sourceUri; if (mDocument && mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } else if (!mWorkerScript.IsEmpty()) { sourceUri.Assign(mWorkerScript); } nsresult rv = mSRIDataVerifier->Verify(mSRIMetadata, channel, sourceUri, aReporter); if (NS_FAILED(rv)) { FailWithNetworkError(); // Cancel request. return rv; } } if (mPipeOutputStream) { mPipeOutputStream->Close(); } } if (mObserver) { if (mResponse->Type() != ResponseType::Error && !mRequest->GetIntegrity().IsEmpty()) { //From "Main Fetch" step 23: Process response. MOZ_ASSERT(mResponse); mObserver->OnResponseAvailable(mResponse); #ifdef DEBUG mResponseAvailableCalled = true; #endif } mObserver->OnResponseEnd(); mObserver = nullptr; } return NS_OK; }
NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); // Note, this can be called multiple times if we are doing an opaqueredirect. // In that case we will get a simulated OnStartRequest() and then the real // channel will call in with an errored OnStartRequest(). nsresult rv; aRequest->GetStatus(&rv); if (NS_FAILED(rv)) { FailWithNetworkError(); return rv; } // We should only get to the following code once. MOZ_ASSERT(!mPipeOutputStream); MOZ_ASSERT(mObserver); RefPtr<InternalResponse> response; nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); // On a successful redirect we perform the following substeps of HTTP Fetch, // step 5, "redirect status", step 11. bool foundOpaqueRedirect = false; int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE; rv = channel->GetContentLength(&contentLength); MOZ_ASSERT_IF(NS_FAILED(rv), contentLength == InternalResponse::UNKNOWN_BODY_SIZE); if (httpChannel) { uint32_t responseStatus; httpChannel->GetResponseStatus(&responseStatus); if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) { if (mRequest->GetRedirectMode() == RequestRedirect::Error) { FailWithNetworkError(); return NS_BINDING_FAILED; } if (mRequest->GetRedirectMode() == RequestRedirect::Manual) { foundOpaqueRedirect = true; } } nsAutoCString statusText; httpChannel->GetResponseStatusText(statusText); response = new InternalResponse(responseStatus, statusText); RefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response); rv = httpChannel->VisitResponseHeaders(visitor); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Failed to visit all headers."); } // If Content-Encoding or Transfer-Encoding headers are set, then the actual // Content-Length (which refer to the decoded data) is obscured behind the encodings. ErrorResult result; if (response->Headers()->Has(NS_LITERAL_CSTRING("content-encoding"), result) || response->Headers()->Has(NS_LITERAL_CSTRING("transfer-encoding"), result)) { NS_WARNING("Cannot know response Content-Length due to presence of Content-Encoding " "or Transfer-Encoding headers."); contentLength = InternalResponse::UNKNOWN_BODY_SIZE; } MOZ_ASSERT(!result.Failed()); } else { response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; nsAutoCString contentType; rv = channel->GetContentType(contentType); if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) { nsAutoCString contentCharset; channel->GetContentCharset(contentCharset); if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) { contentType += NS_LITERAL_CSTRING(";charset=") + contentCharset; } response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); MOZ_ASSERT(!result.Failed()); } if (contentLength > 0) { nsAutoCString contentLenStr; contentLenStr.AppendInt(contentLength); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), contentLenStr, result); MOZ_ASSERT(!result.Failed()); } } // We open a pipe so that we can immediately set the pipe's read end as the // response's body. Setting the segment size to UINT32_MAX means that the // pipe has infinite space. The nsIChannel will continue to buffer data in // xpcom events even if we block on a fixed size pipe. It might be possible // to suspend the channel and then resume when there is space available, but // for now use an infinite pipe to avoid blocking. nsCOMPtr<nsIInputStream> pipeInputStream; rv = NS_NewPipe(getter_AddRefs(pipeInputStream), getter_AddRefs(mPipeOutputStream), 0, /* default segment size */ UINT32_MAX /* infinite pipe */, true /* non-blocking input, otherwise you deadlock */, false /* blocking output, since the pipe is 'in'finite */ ); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } response->SetBody(pipeInputStream, contentLength); response->InitChannelInfo(channel); nsCOMPtr<nsIURI> channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } nsCOMPtr<nsILoadInfo> loadInfo; rv = channel->GetLoadInfo(getter_AddRefs(loadInfo)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Propagate any tainting from the channel back to our response here. This // step is not reflected in the spec because the spec is written such that // FetchEvent.respondWith() just passes the already-tainted Response back to // the outer fetch(). In gecko, however, we serialize the Response through // the channel and must regenerate the tainting from the channel in the // interception case. mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting()); // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect); // From "Main Fetch" step 17: SRI-part1. if (mResponse->Type() != ResponseType::Error && !mRequest->GetIntegrity().IsEmpty() && mSRIMetadata.IsEmpty()) { nsIConsoleReportCollector* aReporter = nullptr; if (mObserver) { aReporter = mObserver->GetReporter(); } nsAutoCString sourceUri; if (mDocument && mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } else if (!mWorkerScript.IsEmpty()) { sourceUri.Assign(mWorkerScript); } SRICheck::IntegrityMetadata(mRequest->GetIntegrity(), sourceUri, aReporter, &mSRIMetadata); mSRIDataVerifier = new SRICheckDataVerifier(mSRIMetadata, sourceUri, aReporter); // Do not retarget off main thread when using SRI API. return NS_OK; } nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } // Try to retarget off main thread. if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) { Unused << NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts))); } return NS_OK; }
NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { AssertIsOnMainThread(); // Note, this can be called multiple times if we are doing an opaqueredirect. // In that case we will get a simulated OnStartRequest() and then the real // channel will call in with an errored OnStartRequest(). if (!mChannel) { MOZ_ASSERT(!mObserver); return NS_BINDING_ABORTED; } nsresult rv; aRequest->GetStatus(&rv); if (NS_FAILED(rv)) { FailWithNetworkError(rv); return rv; } // We should only get to the following code once. MOZ_ASSERT(!mPipeOutputStream); MOZ_ASSERT(mObserver); mNeedToObserveOnDataAvailable = mObserver->NeedOnDataAvailable(); RefPtr<InternalResponse> response; nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); // On a successful redirect we perform the following substeps of HTTP Fetch, // step 5, "redirect status", step 11. bool foundOpaqueRedirect = false; int64_t contentLength = InternalResponse::UNKNOWN_BODY_SIZE; rv = channel->GetContentLength(&contentLength); MOZ_ASSERT_IF(NS_FAILED(rv), contentLength == InternalResponse::UNKNOWN_BODY_SIZE); if (httpChannel) { uint32_t responseStatus; rv = httpChannel->GetResponseStatus(&responseStatus); MOZ_ASSERT(NS_SUCCEEDED(rv)); if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) { if (mRequest->GetRedirectMode() == RequestRedirect::Error) { FailWithNetworkError(NS_BINDING_ABORTED); return NS_BINDING_FAILED; } if (mRequest->GetRedirectMode() == RequestRedirect::Manual) { foundOpaqueRedirect = true; } } nsAutoCString statusText; rv = httpChannel->GetResponseStatusText(statusText); MOZ_ASSERT(NS_SUCCEEDED(rv)); response = new InternalResponse(responseStatus, statusText); response->Headers()->FillResponseHeaders(httpChannel); // If Content-Encoding or Transfer-Encoding headers are set, then the actual // Content-Length (which refer to the decoded data) is obscured behind the encodings. ErrorResult result; if (response->Headers()->Has(NS_LITERAL_CSTRING("content-encoding"), result) || response->Headers()->Has(NS_LITERAL_CSTRING("transfer-encoding"), result)) { // We cannot trust the content-length when content-encoding or // transfer-encoding are set. There are many servers which just // get this wrong. contentLength = InternalResponse::UNKNOWN_BODY_SIZE; } MOZ_ASSERT(!result.Failed()); } else { response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; nsAutoCString contentType; rv = channel->GetContentType(contentType); if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) { nsAutoCString contentCharset; channel->GetContentCharset(contentCharset); if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) { contentType += NS_LITERAL_CSTRING(";charset=") + contentCharset; } response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); MOZ_ASSERT(!result.Failed()); } if (contentLength > 0) { nsAutoCString contentLenStr; contentLenStr.AppendInt(contentLength); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), contentLenStr, result); MOZ_ASSERT(!result.Failed()); } } nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(aRequest); if (cic && mAltDataListener) { // Skip the case that mAltDataListener->Status() equals to FALLBACK, that means // the opened channel for alternative data loading is reused for loading the // main data. if (mAltDataListener->Status() != AlternativeDataStreamListener::FALLBACK) { // Verify the cache ID is the same with from alternative data cache. // If the cache ID is different, droping the alternative data loading, // otherwise setup the response's alternative body and cacheInfoChannel. uint64_t cacheEntryId = 0; if (NS_SUCCEEDED(cic->GetCacheEntryId(&cacheEntryId)) && cacheEntryId != mAltDataListener->GetAlternativeDataCacheEntryId()) { mAltDataListener->Cancel(); } else { // AlternativeDataStreamListener::OnStartRequest had already been called, // the alternative data input stream and cacheInfo channel must be created. nsCOMPtr<nsICacheInfoChannel> cacheInfo = mAltDataListener->GetCacheInfoChannel(); nsCOMPtr<nsIInputStream> altInputStream = mAltDataListener->GetAlternativeInputStream(); MOZ_ASSERT(altInputStream && cacheInfo); response->SetAlternativeBody(altInputStream); nsMainThreadPtrHandle<nsICacheInfoChannel> handle( new nsMainThreadPtrHolder<nsICacheInfoChannel>("nsICacheInfoChannel", cacheInfo, false)); response->SetCacheInfoChannel(handle); } } else if (!mAltDataListener->GetAlternativeDataType().IsEmpty()) { // If the status is FALLBACK and the mAltDataListener::mAlternativeDataType // is not empty, that means the data need to be saved into cache, setup the // response's nsICacheInfoChannel for caching the data after loading. nsMainThreadPtrHandle<nsICacheInfoChannel> handle( new nsMainThreadPtrHolder<nsICacheInfoChannel>("nsICacheInfoChannel", cic, false)); response->SetCacheInfoChannel(handle); } } // We open a pipe so that we can immediately set the pipe's read end as the // response's body. Setting the segment size to UINT32_MAX means that the // pipe has infinite space. The nsIChannel will continue to buffer data in // xpcom events even if we block on a fixed size pipe. It might be possible // to suspend the channel and then resume when there is space available, but // for now use an infinite pipe to avoid blocking. nsCOMPtr<nsIInputStream> pipeInputStream; rv = NS_NewPipe(getter_AddRefs(pipeInputStream), getter_AddRefs(mPipeOutputStream), 0, /* default segment size */ UINT32_MAX /* infinite pipe */, true /* non-blocking input, otherwise you deadlock */, false /* blocking output, since the pipe is 'in'finite */ ); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(rv); // Cancel request. return rv; } response->SetBody(pipeInputStream, contentLength); response->InitChannelInfo(channel); nsCOMPtr<nsIURI> channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(rv); // Cancel request. return rv; } nsCOMPtr<nsILoadInfo> loadInfo; rv = channel->GetLoadInfo(getter_AddRefs(loadInfo)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(rv); return rv; } // Propagate any tainting from the channel back to our response here. This // step is not reflected in the spec because the spec is written such that // FetchEvent.respondWith() just passes the already-tainted Response back to // the outer fetch(). In gecko, however, we serialize the Response through // the channel and must regenerate the tainting from the channel in the // interception case. mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting()); // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. mResponse = BeginAndGetFilteredResponse(response, foundOpaqueRedirect); if (NS_WARN_IF(!mResponse)) { // Fail to generate a paddingInfo for opaque response. MOZ_DIAGNOSTIC_ASSERT(mResponse->Type() == ResponseType::Opaque); FailWithNetworkError(NS_ERROR_UNEXPECTED); return rv; } // From "Main Fetch" step 19: SRI-part1. if (ShouldCheckSRI(mRequest, mResponse) && mSRIMetadata.IsEmpty()) { nsIConsoleReportCollector* reporter = nullptr; if (mObserver) { reporter = mObserver->GetReporter(); } nsAutoCString sourceUri; if (mDocument && mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } else if (!mWorkerScript.IsEmpty()) { sourceUri.Assign(mWorkerScript); } SRICheck::IntegrityMetadata(mRequest->GetIntegrity(), sourceUri, reporter, &mSRIMetadata); mSRIDataVerifier = new SRICheckDataVerifier(mSRIMetadata, sourceUri, reporter); // Do not retarget off main thread when using SRI API. return NS_OK; } nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(rv); // Cancel request. return rv; } // Try to retarget off main thread. if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) { Unused << NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts))); } return NS_OK; }
NS_IMETHODIMP FetchDriver::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatusCode) { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(!mOnStopRequestCalled); mOnStopRequestCalled = true; // main data loading is going to finish, breaking the reference cycle. RefPtr<AlternativeDataStreamListener> altDataListener = mAltDataListener.forget(); // We need to check mObserver, which is nulled by FailWithNetworkError(), // because in the case of "error" redirect mode, aStatusCode may be NS_OK but // mResponse will definitely be null so we must not take the else branch. if (NS_FAILED(aStatusCode) || !mObserver) { nsCOMPtr<nsIAsyncOutputStream> outputStream = do_QueryInterface(mPipeOutputStream); if (outputStream) { outputStream->CloseWithStatus(NS_BINDING_FAILED); } if (altDataListener) { altDataListener->Cancel(); } // We proceed as usual here, since we've already created a successful response // from OnStartRequest. } else { MOZ_ASSERT(mResponse); MOZ_ASSERT(!mResponse->IsError()); // From "Main Fetch" step 19: SRI-part3. if (ShouldCheckSRI(mRequest, mResponse)) { MOZ_ASSERT(mSRIDataVerifier); nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsIConsoleReportCollector* reporter = nullptr; if (mObserver) { reporter = mObserver->GetReporter(); } nsAutoCString sourceUri; if (mDocument && mDocument->GetDocumentURI()) { mDocument->GetDocumentURI()->GetAsciiSpec(sourceUri); } else if (!mWorkerScript.IsEmpty()) { sourceUri.Assign(mWorkerScript); } nsresult rv = mSRIDataVerifier->Verify(mSRIMetadata, channel, sourceUri, reporter); if (NS_FAILED(rv)) { if (altDataListener) { altDataListener->Cancel(); } FailWithNetworkError(rv); // Cancel request. return rv; } } if (mPipeOutputStream) { mPipeOutputStream->Close(); } } return FinishOnStopRequest(altDataListener); }
NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); // Note, this can be called multiple times if we are doing an opaqueredirect. // In that case we will get a simulated OnStartRequest() and then the real // channel will call in with an errored OnStartRequest(). nsresult rv; aRequest->GetStatus(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // We should only get to the following code once. MOZ_ASSERT(!mPipeOutputStream); MOZ_ASSERT(mObserver); nsRefPtr<InternalResponse> response; nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); if (httpChannel) { uint32_t responseStatus; httpChannel->GetResponseStatus(&responseStatus); nsAutoCString statusText; httpChannel->GetResponseStatusText(statusText); response = new InternalResponse(responseStatus, statusText); nsRefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response); rv = httpChannel->VisitResponseHeaders(visitor); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Failed to visit all headers."); } } else { nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aRequest); // If it is not an http channel, it has to be a jar one. MOZ_ASSERT(jarChannel); // We simulate the http protocol for jar/app requests uint32_t responseStatus = 200; nsAutoCString statusText; response = new InternalResponse(responseStatus, NS_LITERAL_CSTRING("OK")); ErrorResult result; nsAutoCString contentType; jarChannel->GetContentType(contentType); response->Headers()->Append(NS_LITERAL_CSTRING("content-type"), contentType, result); MOZ_ASSERT(!result.Failed()); } // We open a pipe so that we can immediately set the pipe's read end as the // response's body. Setting the segment size to UINT32_MAX means that the // pipe has infinite space. The nsIChannel will continue to buffer data in // xpcom events even if we block on a fixed size pipe. It might be possible // to suspend the channel and then resume when there is space available, but // for now use an infinite pipe to avoid blocking. nsCOMPtr<nsIInputStream> pipeInputStream; rv = NS_NewPipe(getter_AddRefs(pipeInputStream), getter_AddRefs(mPipeOutputStream), 0, /* default segment size */ UINT32_MAX /* infinite pipe */, true /* non-blocking input, otherwise you deadlock */, false /* blocking output, since the pipe is 'in'finite */ ); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } response->SetBody(pipeInputStream); nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); response->InitChannelInfo(channel); nsCOMPtr<nsIURI> channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. mResponse = BeginAndGetFilteredResponse(response, channelURI); nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } // Try to retarget off main thread. if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) { NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts))); } return NS_OK; }
// This function implements the "HTTP Fetch" algorithm from the Fetch spec. // Functionality is often split between here, the CORS listener proxy and the // Necko HTTP implementation. nsresult FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag) { // Step 1. "Let response be null." mResponse = nullptr; nsresult rv; // We need to track the CORS flag through redirects. Since there is no way // for us to go from CORS mode to non-CORS mode, we just need to remember // if it has ever been set. mCORSFlagEverSet = mCORSFlagEverSet || aCORSFlag; nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> uri; rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr, ios); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Step 2 deals with letting ServiceWorkers intercept requests. This is // handled by Necko after the channel is opened. // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be // set based on the Request's flag. // Step 3.1 "If the CORS preflight flag is set and one of these conditions is // true..." is handled by the CORS proxy. // // Step 3.2 "Set request's skip service worker flag." This isn't required // since Necko will fall back to the network if the ServiceWorker does not // respond with a valid Response. // // NS_StartCORSPreflight() will automatically kick off the original request // if it succeeds, so we need to have everything setup for the original // request too. // Step 3.3 "Let credentials flag be set if one of // - request's credentials mode is "include" // - request's credentials mode is "same-origin" and either the CORS flag // is unset or response tainting is "opaque" // is true, and unset otherwise." bool useCredentials = false; if (mRequest->GetCredentialsMode() == RequestCredentials::Include || (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && !aCORSFlag && mRequest->GetResponseTainting() != InternalRequest::RESPONSETAINT_OPAQUE)) { useCredentials = true; } // This is effectivetly the opposite of the use credentials flag in "HTTP // network or cache fetch" in the spec and decides whether to transmit // cookies and other identifying information. LOAD_ANONYMOUS also prevents // new cookies sent by the server from being stored. This value will // propagate across redirects, which is what we want. const nsLoadFlags credentialsFlag = useCredentials ? 0 : nsIRequest::LOAD_ANONYMOUS; // Set skip serviceworker flag. // While the spec also gates on the client being a ServiceWorker, we can't // infer that here. Instead we rely on callers to set the flag correctly. const nsLoadFlags bypassFlag = mRequest->SkipServiceWorker() ? nsIChannel::LOAD_BYPASS_SERVICE_WORKER : 0; // From here on we create a channel and set its properties with the // information from the InternalRequest. This is an implementation detail. MOZ_ASSERT(mLoadGroup); nsCOMPtr<nsIChannel> chan; rv = NS_NewChannel(getter_AddRefs(chan), uri, mPrincipal, nsILoadInfo::SEC_NORMAL, mRequest->ContentPolicyType(), mLoadGroup, nullptr, /* aCallbacks */ nsIRequest::LOAD_NORMAL | credentialsFlag | bypassFlag, ios); mLoadGroup = nullptr; if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Insert ourselves into the notification callbacks chain so we can handle // cross-origin redirects. chan->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); chan->SetNotificationCallbacks(this); // FIXME(nsm): Bug 1120715. // Step 3.4 "If request's cache mode is default and request's header list // contains a header named `If-Modified-Since`, `If-None-Match`, // `If-Unmodified-Since`, `If-Match`, or `If-Range`, set request's cache mode // to no-store." // Step 3.5 begins "HTTP network or cache fetch". // HTTP network or cache fetch // --------------------------- // Step 1 "Let HTTPRequest..." The channel is the HTTPRequest. nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan); if (httpChan) { // Copy the method. nsAutoCString method; mRequest->GetMethod(method); rv = httpChan->SetRequestMethod(method); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Set the same headers. nsAutoTArray<InternalHeaders::Entry, 5> headers; mRequest->Headers()->GetEntries(headers); for (uint32_t i = 0; i < headers.Length(); ++i) { if (headers[i].mValue.IsEmpty()) { httpChan->SetEmptyRequestHeader(headers[i].mName); } else { httpChan->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */); } } // Step 2. Set the referrer. nsAutoString referrer; mRequest->GetReferrer(referrer); 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." // // XXXnsm - We never actually hit this from a fetch() call since both // fetch and Request() create a new internal request whose referrer is // always set to about:client. Should we just crash here instead until // someone tries to use FetchDriver for non-fetch() APIs? nsCOMPtr<nsIURI> referrerURI; rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } rv = httpChan->SetReferrerWithPolicy(referrerURI, 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; rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"), NS_ConvertUTF16toUTF8(origin), false /* merge */); } // Bug 1120722 - Authorization will be handled later. // Auth may require prompting, we don't support it yet. // The next patch in this same bug prevents this from aborting the request. // Credentials checks for CORS are handled by nsCORSListenerProxy, nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan); // Conversion between enumerations is safe due to static asserts in // dom/workers/ServiceWorkerManager.cpp internalChan->SetCorsMode(static_cast<uint32_t>(mRequest->Mode())); internalChan->SetRedirectMode(static_cast<uint32_t>(mRequest->GetRedirectMode())); } // Step 5. Proxy authentication will be handled by Necko. // FIXME(nsm): Bug 1120715. // Step 7-10. "If request's cache mode is neither no-store nor reload..." // Continue setting up 'HTTPRequest'. Content-Type and body data. nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan); if (uploadChan) { nsAutoCString contentType; ErrorResult result; mRequest->Headers()->Get(NS_LITERAL_CSTRING("content-type"), contentType, result); // This is an error because the Request constructor explicitly extracts and // sets a content-type per spec. if (result.Failed()) { return FailWithNetworkError(); } nsCOMPtr<nsIInputStream> bodyStream; mRequest->GetBody(getter_AddRefs(bodyStream)); if (bodyStream) { nsAutoCString method; mRequest->GetMethod(method); rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType, -1, method, false /* aStreamHasHeaders */); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } } } nsCOMPtr<nsIStreamListener> listener = this; MOZ_ASSERT_IF(aCORSFlag, mRequest->Mode() == RequestMode::Cors); // Only use nsCORSListenerProxy if we are in CORS mode. Otherwise it // will overwrite the CorsMode flag unconditionally to "cors" or // "cors-with-forced-preflight". if (mRequest->Mode() == RequestMode::Cors) { // Passing false for the credentials flag to nsCORSListenerProxy is semantically // the same as the "same-origin" RequestCredentials value. We implement further // blocking of credentials for "omit" by setting LOAD_ANONYMOUS manually above. bool corsCredentials = mRequest->GetCredentialsMode() == RequestCredentials::Include; // Set up a CORS proxy that will handle the various requirements of the CORS // protocol. It handles the preflight cache and CORS response headers. // If the request is allowed, it will start our original request // and our observer will be notified. On failure, our observer is notified // directly. nsRefPtr<nsCORSListenerProxy> corsListener = new nsCORSListenerProxy(this, mPrincipal, corsCredentials); rv = corsListener->Init(chan, DataURIHandling::Allow); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } listener = corsListener.forget(); } // If preflight is required, start a "CORS preflight fetch" // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the // implementation is handled by NS_StartCORSPreflight, we just set up the // unsafeHeaders so they can be verified against the response's // "Access-Control-Allow-Headers" header. if (aCORSPreflightFlag) { MOZ_ASSERT(mRequest->Mode() != RequestMode::No_cors, "FetchDriver::ContinueFetch() should ensure that the request is not no-cors"); MOZ_ASSERT(httpChan, "CORS preflight can only be used with HTTP channels"); nsAutoTArray<nsCString, 5> unsafeHeaders; mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders); nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan); rv = internalChan->SetCorsPreflightParameters(unsafeHeaders, useCredentials, mPrincipal); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } } rv = chan->AsyncOpen(listener, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } // Step 4 onwards of "HTTP Fetch" is handled internally by Necko. return NS_OK; }
nsresult FetchDriver::BasicFetch() { nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsAutoCString scheme; rv = uri->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } if (scheme.LowerCaseEqualsLiteral("about")) { if (url.EqualsLiteral("about:blank")) { nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; response->Headers()->Append(NS_LITERAL_CSTRING("content-type"), NS_LITERAL_CSTRING("text/html;charset=utf-8"), result); MOZ_ASSERT(!result.Failed()); nsCOMPtr<nsIInputStream> body; rv = NS_NewCStringInputStream(getter_AddRefs(body), EmptyCString()); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } response->SetBody(body); BeginResponse(response); return SucceedWithResponse(); } return FailWithNetworkError(); } if (scheme.LowerCaseEqualsLiteral("blob")) { nsRefPtr<BlobImpl> blobImpl; rv = NS_GetBlobForBlobURI(uri, getter_AddRefs(blobImpl)); BlobImpl* blob = static_cast<BlobImpl*>(blobImpl.get()); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; uint64_t size = blob->GetSize(result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsAutoString sizeStr; sizeStr.AppendInt(size); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), NS_ConvertUTF16toUTF8(sizeStr), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsAutoString type; blob->GetType(type); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), NS_ConvertUTF16toUTF8(type), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsCOMPtr<nsIInputStream> stream; blob->GetInternalStream(getter_AddRefs(stream), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } response->SetBody(stream); BeginResponse(response); return SucceedWithResponse(); } if (scheme.LowerCaseEqualsLiteral("data")) { nsAutoCString method; mRequest->GetMethod(method); if (method.LowerCaseEqualsASCII("get")) { nsresult rv; nsCOMPtr<nsIProtocolHandler> dataHandler = do_GetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "data", &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } nsCOMPtr<nsIChannel> channel; rv = dataHandler->NewChannel(uri, getter_AddRefs(channel)); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } nsCOMPtr<nsIInputStream> stream; rv = channel->Open(getter_AddRefs(stream)); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } // nsDataChannel will parse the data URI when it is Open()ed and set the // correct content type and charset. nsAutoCString contentType; if (NS_SUCCEEDED(channel->GetContentType(contentType))) { nsAutoCString charset; if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty()) { contentType.AppendLiteral(";charset="); contentType.Append(charset); } } else { NS_WARNING("Could not get content type from data channel"); } nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } response->SetBody(stream); BeginResponse(response); return SucceedWithResponse(); } return FailWithNetworkError(); } if (scheme.LowerCaseEqualsLiteral("http") || scheme.LowerCaseEqualsLiteral("https") || scheme.LowerCaseEqualsLiteral("app")) { return HttpFetch(); } return FailWithNetworkError(); }
NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); // Note, this can be called multiple times if we are doing an opaqueredirect. // In that case we will get a simulated OnStartRequest() and then the real // channel will call in with an errored OnStartRequest(). nsresult rv; aRequest->GetStatus(&rv); if (NS_FAILED(rv)) { FailWithNetworkError(); return rv; } // We should only get to the following code once. MOZ_ASSERT(!mPipeOutputStream); MOZ_ASSERT(mObserver); RefPtr<InternalResponse> response; nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); // On a successful redirect we perform the following substeps of HTTP Fetch, // step 5, "redirect status", step 11. // Step 11.5 "Append locationURL to request's url list." so that when we set the // Response's URL from the Request's URL in Main Fetch, step 15, we get the // final value. Note, we still use a single URL value instead of a list. // Because of that we only need to do this after the request finishes. nsCOMPtr<nsIURI> newURI; rv = NS_GetFinalChannelURI(channel, getter_AddRefs(newURI)); if (NS_FAILED(rv)) { FailWithNetworkError(); return rv; } nsAutoCString newUrl; newURI->GetSpec(newUrl); mRequest->SetURL(newUrl); bool foundOpaqueRedirect = false; if (httpChannel) { uint32_t responseStatus; httpChannel->GetResponseStatus(&responseStatus); if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) { if (mRequest->GetRedirectMode() == RequestRedirect::Error) { FailWithNetworkError(); return NS_BINDING_FAILED; } if (mRequest->GetRedirectMode() == RequestRedirect::Manual) { foundOpaqueRedirect = true; } } nsAutoCString statusText; httpChannel->GetResponseStatusText(statusText); response = new InternalResponse(responseStatus, statusText); RefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response); rv = httpChannel->VisitResponseHeaders(visitor); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Failed to visit all headers."); } } else { response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; nsAutoCString contentType; rv = channel->GetContentType(contentType); if (NS_SUCCEEDED(rv) && !contentType.IsEmpty()) { nsAutoCString contentCharset; channel->GetContentCharset(contentCharset); if (NS_SUCCEEDED(rv) && !contentCharset.IsEmpty()) { contentType += NS_LITERAL_CSTRING(";charset=") + contentCharset; } response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); MOZ_ASSERT(!result.Failed()); } int64_t contentLength; rv = channel->GetContentLength(&contentLength); if (NS_SUCCEEDED(rv) && contentLength) { nsAutoCString contentLenStr; contentLenStr.AppendInt(contentLength); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), contentLenStr, result); MOZ_ASSERT(!result.Failed()); } } // We open a pipe so that we can immediately set the pipe's read end as the // response's body. Setting the segment size to UINT32_MAX means that the // pipe has infinite space. The nsIChannel will continue to buffer data in // xpcom events even if we block on a fixed size pipe. It might be possible // to suspend the channel and then resume when there is space available, but // for now use an infinite pipe to avoid blocking. nsCOMPtr<nsIInputStream> pipeInputStream; rv = NS_NewPipe(getter_AddRefs(pipeInputStream), getter_AddRefs(mPipeOutputStream), 0, /* default segment size */ UINT32_MAX /* infinite pipe */, true /* non-blocking input, otherwise you deadlock */, false /* blocking output, since the pipe is 'in'finite */ ); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } response->SetBody(pipeInputStream); response->InitChannelInfo(channel); nsCOMPtr<nsIURI> channelURI; rv = channel->GetURI(getter_AddRefs(channelURI)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } nsCOMPtr<nsILoadInfo> loadInfo; rv = channel->GetLoadInfo(getter_AddRefs(loadInfo)); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Propagate any tainting from the channel back to our response here. This // step is not reflected in the spec because the spec is written such that // FetchEvent.respondWith() just passes the already-tainted Response back to // the outer fetch(). In gecko, however, we serialize the Response through // the channel and must regenerate the tainting from the channel in the // interception case. mRequest->MaybeIncreaseResponseTainting(loadInfo->GetTainting()); // Resolves fetch() promise which may trigger code running in a worker. Make // sure the Response is fully initialized before calling this. mResponse = BeginAndGetFilteredResponse(response, channelURI, foundOpaqueRedirect); nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } // Try to retarget off main thread. if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) { NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(sts))); } return NS_OK; }
nsresult FetchDriver::ContinueFetch(bool aCORSFlag) { workers::AssertIsOnMainThread(); nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> requestURI; nsresult rv = NS_NewURI(getter_AddRefs(requestURI), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } // CSP/mixed content checks. int16_t shouldLoad; rv = NS_CheckContentLoadPolicy(mRequest->ContentPolicyType(), requestURI, mPrincipal, mDocument, // FIXME(nsm): Should MIME be extracted from // Content-Type header? EmptyCString(), /* mime guess */ nullptr, /* extra */ &shouldLoad, nsContentUtils::GetContentPolicy(), nsContentUtils::GetSecurityManager()); if (NS_WARN_IF(NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad))) { // Disallowed by content policy. return FailWithNetworkError(); } // Begin Step 4 of the Fetch algorithm // https://fetch.spec.whatwg.org/#fetching nsAutoCString scheme; rv = requestURI->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } rv = mPrincipal->CheckMayLoad(requestURI, false /* report */, false /* allowIfInheritsPrincipal */); if ((!aCORSFlag && NS_SUCCEEDED(rv)) || (scheme.EqualsLiteral("data") && mRequest->SameOriginDataURL()) || scheme.EqualsLiteral("about")) { return BasicFetch(); } if (mRequest->Mode() == RequestMode::Same_origin) { return FailWithNetworkError(); } if (mRequest->Mode() == RequestMode::No_cors) { mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_OPAQUE); return BasicFetch(); } if (!scheme.EqualsLiteral("http") && !scheme.EqualsLiteral("https")) { return FailWithNetworkError(); } bool corsPreflight = false; if (mRequest->Mode() == RequestMode::Cors_with_forced_preflight || (mRequest->UnsafeRequest() && (!mRequest->HasSimpleMethod() || !mRequest->Headers()->HasOnlySimpleHeaders()))) { corsPreflight = true; } mRequest->SetResponseTainting(InternalRequest::RESPONSETAINT_CORS); return HttpFetch(true /* aCORSFlag */, corsPreflight); }
// This function implements the "HTTP Fetch" algorithm from the Fetch spec. // Functionality is often split between here, the CORS listener proxy and the // Necko HTTP implementation. nsresult FetchDriver::HttpFetch(bool aCORSFlag, bool aCORSPreflightFlag, bool aAuthenticationFlag) { // Step 1. "Let response be null." mResponse = nullptr; nsresult rv; nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> uri; rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr, ios); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Step 2 deals with letting ServiceWorkers intercept requests. This is // handled by Necko after the channel is opened. // FIXME(nsm): Bug 1119026: The channel's skip service worker flag should be // set based on the Request's flag. // Step 3.1 "If the CORS preflight flag is set and one of these conditions is // true..." is handled by the CORS proxy. // // Step 3.2 "Set request's skip service worker flag." This isn't required // since Necko will fall back to the network if the ServiceWorker does not // respond with a valid Response. // // NS_StartCORSPreflight() will automatically kick off the original request // if it succeeds, so we need to have everything setup for the original // request too. // Step 3.3 "Let credentials flag be set if either request's credentials mode // is include, or request's credentials mode is same-origin and the CORS flag // is unset, and unset otherwise." bool useCredentials = false; if (mRequest->GetCredentialsMode() == RequestCredentials::Include || (mRequest->GetCredentialsMode() == RequestCredentials::Same_origin && !aCORSFlag)) { useCredentials = true; } // This is effectivetly the opposite of the use credentials flag in "HTTP // network or cache fetch" in the spec and decides whether to transmit // cookies and other identifying information. LOAD_ANONYMOUS also prevents // new cookies sent by the server from being stored. const nsLoadFlags credentialsFlag = useCredentials ? 0 : nsIRequest::LOAD_ANONYMOUS; // From here on we create a channel and set its properties with the // information from the InternalRequest. This is an implementation detail. MOZ_ASSERT(mLoadGroup); nsCOMPtr<nsIChannel> chan; rv = NS_NewChannel(getter_AddRefs(chan), uri, mPrincipal, nsILoadInfo::SEC_NORMAL, mRequest->ContentPolicyType(), mLoadGroup, nullptr, /* aCallbacks */ nsIRequest::LOAD_NORMAL | credentialsFlag, ios); mLoadGroup = nullptr; if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Insert ourselves into the notification callbacks chain so we can handle // cross-origin redirects. chan->GetNotificationCallbacks(getter_AddRefs(mNotificationCallbacks)); chan->SetNotificationCallbacks(this); // FIXME(nsm): Bug 1120715. // Step 3.4 "If request's cache mode is default and request's header list // contains a header named `If-Modified-Since`, `If-None-Match`, // `If-Unmodified-Since`, `If-Match`, or `If-Range`, set request's cache mode // to no-store." // Step 3.5 begins "HTTP network or cache fetch". // HTTP network or cache fetch // --------------------------- // Step 1 "Let HTTPRequest..." The channel is the HTTPRequest. nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan); if (httpChan) { // Copy the method. nsAutoCString method; mRequest->GetMethod(method); rv = httpChan->SetRequestMethod(method); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } // Set the same headers. nsAutoTArray<InternalHeaders::Entry, 5> headers; 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 (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } net::ReferrerPolicy referrerPolicy = net::RP_Default; if (mDocument) { referrerPolicy = mDocument->GetReferrerPolicy(); } rv = httpChan->SetReferrerWithPolicy(refURI, referrerPolicy); 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; rv = nsContentUtils::GetUTFOrigin(mPrincipal, origin); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } httpChan->SetRequestHeader(NS_LITERAL_CSTRING("origin"), NS_ConvertUTF16toUTF8(origin), false /* merge */); } // Bug 1120722 - Authorization will be handled later. // Auth may require prompting, we don't support it yet. // The next patch in this same bug prevents this from aborting the request. // Credentials checks for CORS are handled by nsCORSListenerProxy, } // Step 5. Proxy authentication will be handled by Necko. // FIXME(nsm): Bug 1120715. // Step 7-10. "If request's cache mode is neither no-store nor reload..." // Continue setting up 'HTTPRequest'. Content-Type and body data. nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan); if (uploadChan) { nsAutoCString contentType; ErrorResult result; mRequest->Headers()->Get(NS_LITERAL_CSTRING("content-type"), contentType, result); // This is an error because the Request constructor explicitly extracts and // sets a content-type per spec. if (result.Failed()) { return FailWithNetworkError(); } nsCOMPtr<nsIInputStream> bodyStream; mRequest->GetBody(getter_AddRefs(bodyStream)); if (bodyStream) { nsAutoCString method; mRequest->GetMethod(method); rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType, -1, method, false /* aStreamHasHeaders */); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } } } // Set skip serviceworker flag. // While the spec also gates on the client being a ServiceWorker, we can't // infer that here. Instead we rely on callers to set the flag correctly. if (mRequest->SkipServiceWorker()) { if (httpChan) { nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(httpChan); internalChan->ForceNoIntercept(); } } nsCOMPtr<nsIStreamListener> listener = this; // Unless the cors mode is explicitly no-cors, we set up a cors proxy even in // the same-origin case, since the proxy does not enforce cors header checks // in the same-origin case. if (mRequest->Mode() != RequestMode::No_cors) { // Set up a CORS proxy that will handle the various requirements of the CORS // protocol. It handles the preflight cache and CORS response headers. // If the request is allowed, it will start our original request // and our observer will be notified. On failure, our observer is notified // directly. nsRefPtr<nsCORSListenerProxy> corsListener = new nsCORSListenerProxy(this, mPrincipal, useCredentials); rv = corsListener->Init(chan, DataURIHandling::Allow); if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } listener = corsListener.forget(); } // If preflight is required, start a "CORS preflight fetch" // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the // implementation is handled by NS_StartCORSPreflight, we just set up the // unsafeHeaders so they can be verified against the response's // "Access-Control-Allow-Headers" header. if (aCORSPreflightFlag) { nsCOMPtr<nsIChannel> preflightChannel; nsAutoTArray<nsCString, 5> unsafeHeaders; mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders); rv = NS_StartCORSPreflight(chan, listener, mPrincipal, useCredentials, unsafeHeaders, getter_AddRefs(preflightChannel)); } else { rv = chan->AsyncOpen(listener, nullptr); } if (NS_WARN_IF(NS_FAILED(rv))) { return FailWithNetworkError(); } // Step 4 onwards of "HTTP Fetch" is handled internally by Necko. return NS_OK; }
nsresult FetchDriver::BasicFetch() { nsAutoCString url; mRequest->GetURL(url); nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), url, nullptr, nullptr); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsAutoCString scheme; rv = uri->GetScheme(scheme); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } if (scheme.LowerCaseEqualsLiteral("about")) { if (url.EqualsLiteral("about:blank")) { nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; response->Headers()->Append(NS_LITERAL_CSTRING("content-type"), NS_LITERAL_CSTRING("text/html;charset=utf-8"), result); MOZ_ASSERT(!result.Failed()); nsCOMPtr<nsIInputStream> body; rv = NS_NewCStringInputStream(getter_AddRefs(body), EmptyCString()); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } response->SetBody(body); BeginResponse(response); return SucceedWithResponse(); } return FailWithNetworkError(); } if (scheme.LowerCaseEqualsLiteral("blob")) { nsRefPtr<BlobImpl> blobImpl; rv = NS_GetBlobForBlobURI(uri, getter_AddRefs(blobImpl)); BlobImpl* blob = static_cast<BlobImpl*>(blobImpl.get()); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); ErrorResult result; uint64_t size = blob->GetSize(result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsAutoString sizeStr; sizeStr.AppendInt(size); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Length"), NS_ConvertUTF16toUTF8(sizeStr), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsAutoString type; blob->GetType(type); response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), NS_ConvertUTF16toUTF8(type), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } nsCOMPtr<nsIInputStream> stream; blob->GetInternalStream(getter_AddRefs(stream), result); if (NS_WARN_IF(result.Failed())) { FailWithNetworkError(); return result.StealNSResult(); } response->SetBody(stream); BeginResponse(response); return SucceedWithResponse(); } if (scheme.LowerCaseEqualsLiteral("data")) { nsAutoCString method; mRequest->GetMethod(method); if (method.LowerCaseEqualsASCII("get")) { // Use nsDataHandler directly so that we can extract the content type. // XXX(nsm): Is there a way to acquire the charset without such tight // coupling with the DataHandler? nsIProtocolHandler does not provide // anything similar. nsAutoCString contentType, contentCharset, dataBuffer, hashRef; bool isBase64; rv = nsDataHandler::ParseURI(url, contentType, contentCharset, isBase64, dataBuffer, hashRef); if (NS_SUCCEEDED(rv)) { ErrorResult result; nsRefPtr<InternalResponse> response = new InternalResponse(200, NS_LITERAL_CSTRING("OK")); if (!contentCharset.IsEmpty()) { contentType.Append(";charset="); contentType.Append(contentCharset); } response->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, result); if (!result.Failed()) { nsCOMPtr<nsIInputStream> stream; rv = NS_NewCStringInputStream(getter_AddRefs(stream), dataBuffer); if (NS_SUCCEEDED(rv)) { response->SetBody(stream); BeginResponse(response); return SucceedWithResponse(); } } } } return FailWithNetworkError(); } if (scheme.LowerCaseEqualsLiteral("http") || scheme.LowerCaseEqualsLiteral("https") || scheme.LowerCaseEqualsLiteral("app")) { return HttpFetch(); } return FailWithNetworkError(); }
NS_IMETHODIMP FetchDriver::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) { workers::AssertIsOnMainThread(); MOZ_ASSERT(!mPipeOutputStream); MOZ_ASSERT(mObserver); nsresult rv; aRequest->GetStatus(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest); // For now we only support HTTP. MOZ_ASSERT(channel); aRequest->GetStatus(&rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); return rv; } uint32_t responseStatus; channel->GetResponseStatus(&responseStatus); nsAutoCString statusText; channel->GetResponseStatusText(statusText); nsRefPtr<InternalResponse> response = new InternalResponse(responseStatus, statusText); nsRefPtr<FillResponseHeaders> visitor = new FillResponseHeaders(response); rv = channel->VisitResponseHeaders(visitor); if (NS_WARN_IF(NS_FAILED(rv))) { NS_WARNING("Failed to visit all headers."); } mResponse = BeginAndGetFilteredResponse(response); // We open a pipe so that we can immediately set the pipe's read end as the // response's body. Setting the segment size to UINT32_MAX means that the // pipe has infinite space. The nsIChannel will continue to buffer data in // xpcom events even if we block on a fixed size pipe. It might be possible // to suspend the channel and then resume when there is space available, but // for now use an infinite pipe to avoid blocking. nsCOMPtr<nsIInputStream> pipeInputStream; rv = NS_NewPipe(getter_AddRefs(pipeInputStream), getter_AddRefs(mPipeOutputStream), 0, /* default segment size */ UINT32_MAX /* infinite pipe */); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } mResponse->SetBody(pipeInputStream); nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { FailWithNetworkError(); // Cancel request. return rv; } // Try to retarget off main thread. nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest); if (rr) { rr->RetargetDeliveryTo(sts); } return NS_OK; }