/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
//----------------------------------------------------------------------------- // AlternativeDataStreamListener //----------------------------------------------------------------------------- class AlternativeDataStreamListener final
: public nsIThreadRetargetableStreamListener { public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
// The status of AlternativeDataStreamListener // LOADING: is the initial status, loading the alternative data // COMPLETED: Alternative data loading is completed // CANCELED: Alternative data loading is canceled, this would make // AlternativeDataStreamListener ignore all channel callbacks // FALLBACK: fallback the channel callbacks to FetchDriver // Depends on different situaions, the status transition could be followings // 1. LOADING->COMPLETED // This is the normal status transition for alternative data loading // // 2. LOADING->CANCELED // LOADING->COMPLETED->CANCELED // Alternative data loading could be canceled when cacheId from alternative // data channel does not match with from main data channel(The cacheID // checking is in FetchDriver::OnStartRequest). // Notice the alternative data loading could finish before the cacheID // checking, so the statust transition could be // LOADING->COMPLETED->CANCELED // // 3. LOADING->FALLBACK // For the case that alternative data loading could not be initialized, // i.e. alternative data does not exist or no preferred alternative data // type is requested. Once the status becomes FALLBACK, // AlternativeDataStreamListener transits the channel callback request to // FetchDriver, and the status should not go back to LOADING, COMPLETED, or // CANCELED anymore. enum eStatus { LOADING = 0, COMPLETED, CANCELED, FALLBACK };
// This creates a strong reference cycle with FetchDriver and its // mAltDataListener. We need to clear at least one reference of them once the // data loading finishes.
RefPtr<FetchDriver> mFetchDriver;
nsCString mAlternativeDataType;
nsCOMPtr<nsIInputStream> mPipeAlternativeInputStream;
nsCOMPtr<nsIOutputStream> mPipeAlternativeOutputStream;
uint64_t mAlternativeDataCacheEntryId;
nsCOMPtr<nsICacheInfoChannel> mCacheInfoChannel;
nsCOMPtr<nsIChannel> mChannel;
Atomic<eStatus> mStatus;
};
NS_IMETHODIMP
AlternativeDataStreamListener::OnStartRequest(nsIRequest* aRequest) {
AssertIsOnMainThread();
MOZ_ASSERT(!mAlternativeDataType.IsEmpty()); // Checking the alternative data type is the same between we asked and the // saved in the channel.
nsAutoCString alternativeDataType;
nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(aRequest);
mStatus = AlternativeDataStreamListener::LOADING; if (cic && NS_SUCCEEDED(cic->GetAlternativeDataType(alternativeDataType)) &&
mAlternativeDataType.Equals(alternativeDataType) &&
NS_SUCCEEDED(cic->GetCacheEntryId(&mAlternativeDataCacheEntryId))) {
MOZ_DIAGNOSTIC_ASSERT(!mPipeAlternativeInputStream);
MOZ_DIAGNOSTIC_ASSERT(!mPipeAlternativeOutputStream);
NS_NewPipe(getter_AddRefs(mPipeAlternativeInputStream),
getter_AddRefs(mPipeAlternativeOutputStream),
0 /* default segment size */, UINT32_MAX /* infinite pipe */, true/* non-blocking input, otherwise you deadlock */, false/* blocking output, since the pipe is 'in'finite */);
// call FetchDriver::HttpFetch to load main body
MOZ_ASSERT(mFetchDriver); return mFetchDriver->HttpFetch();
} // Needn't load alternative data, since alternative data does not exist. // Set status to FALLBACK to reuse the opened channel to load main body, // then call FetchDriver::OnStartRequest to continue the work. Unfortunately // can't change the stream listener to mFetchDriver, need to keep // AlternativeDataStreamListener alive to redirect OnDataAvailable and // OnStopRequest to mFetchDriver.
MOZ_ASSERT(alternativeDataType.IsEmpty());
mStatus = AlternativeDataStreamListener::FALLBACK;
mAlternativeDataCacheEntryId = 0;
MOZ_ASSERT(mFetchDriver); return mFetchDriver->OnStartRequest(aRequest);
}
// Alternative data loading is going to finish, breaking the reference cycle // here by taking the ownership to a loacl variable.
RefPtr<FetchDriver> fetchDriver = std::move(mFetchDriver);
if (mStatus == AlternativeDataStreamListener::CANCELED) { // do nothing return NS_OK;
}
if (mStatus == AlternativeDataStreamListener::FALLBACK) {
MOZ_ASSERT(fetchDriver); return fetchDriver->OnStopRequest(aRequest, aStatusCode);
}
// Cleanup the states for alternative data if needed. if (NS_FAILED(aStatusCode)) {
mAlternativeDataCacheEntryId = 0;
mCacheInfoChannel = nullptr;
mPipeAlternativeInputStream = nullptr;
}
mStatus = AlternativeDataStreamListener::COMPLETED; // alternative data loading finish, call FetchDriver::FinishOnStopRequest to // continue the final step for the case FetchDriver::OnStopRequest is called // earlier than AlternativeDataStreamListener::OnStopRequest
MOZ_ASSERT(fetchDriver);
fetchDriver->FinishOnStopRequest(this); return NS_OK;
}
// We assert this since even on failures, we should call // FailWithNetworkError().
MOZ_ASSERT(mResponseAvailableCalled);
}
already_AddRefed<PreloaderBase> FetchDriver::FindPreload(nsIURI* aURI) { // Decide if we allow reuse of an existing <link rel=preload as=fetch> // response for this request. First examine this fetch requets itself if it // is 'pure' enough to use the response and then try to find a preload.
if (!mDocument) { // Preloads are mapped on the document, no document, no preload. return nullptr;
}
CORSMode cors; switch (mRequest->Mode()) { case RequestMode::No_cors:
cors = CORSMode::CORS_NONE; break; case RequestMode::Cors:
cors = mRequest->GetCredentialsMode() == RequestCredentials::Include
? CORSMode::CORS_USE_CREDENTIALS
: CORSMode::CORS_ANONYMOUS; break; default: // Can't be satisfied by a preload because preload cannot define any of // remaining modes. return nullptr;
} if (!mRequest->Headers()->HasOnlySimpleHeaders()) { // Preload can't set any headers. return nullptr;
} if (!mRequest->GetIntegrity().IsEmpty()) { // There is currently no support for SRI checking in the fetch preloader. return nullptr;
} if (mRequest->GetCacheMode() != RequestCache::Default) { // Preload can only go with the default caching mode. return nullptr;
} if (mRequest->SkipServiceWorker()) { // Preload can't be forbidden interception. return nullptr;
} if (mRequest->GetRedirectMode() != RequestRedirect::Follow) { // Preload always follows redirects. return nullptr;
}
nsAutoCString method;
mRequest->GetMethod(method); if (!method.EqualsLiteral("GET")) { // Preload can only do GET, this also eliminates the case we do upload, so // no need to check if the request has any body to send out. return nullptr;
}
// OK, this request can be satisfied by a preloaded response, try to find one.
auto preloadKey = PreloadHashKey::CreateAsFetch(aURI, cors); return mDocument->Preloads().LookupPreload(preloadKey);
}
// If the signal is aborted, it's time to inform the observer and terminate // the operation. if (aSignalImpl) { if (aSignalImpl->Aborted()) {
FetchDriverAbortActions(aSignalImpl); return NS_OK;
}
Follow(aSignalImpl);
}
rv = HttpFetch(mRequest->GetPreferredAlternativeDataType()); if (NS_FAILED(rv)) {
FailWithNetworkError(rv);
}
// Any failure is handled by FailWithNetworkError notifying the aObserver. 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( const nsACString& aPreferredAlternativeDataType) {
MOZ_ASSERT(NS_IsMainThread());
// Unsafe requests aren't allowed with when using no-core mode. if (mRequest->Mode() == RequestMode::No_cors && mRequest->UnsafeRequest() &&
(!mRequest->HasSimpleMethod() ||
!mRequest->Headers()->HasOnlySimpleHeaders())) {
MOZ_ASSERT(false, "The API should have caught this"); return NS_ERROR_DOM_BAD_URI;
}
// non-GET requests aren't allowed for blob. if (IsBlobURI(uri)) {
nsAutoCString method;
mRequest->GetMethod(method); if (!method.EqualsLiteral("GET")) { return NS_ERROR_DOM_NETWORK_ERR;
}
}
RefPtr<PreloaderBase> fetchPreload = FindPreload(uri); if (fetchPreload) {
fetchPreload->RemoveSelf(mDocument);
fetchPreload->NotifyUsage(mDocument, PreloaderBase::LoadBackground::Keep);
rv = fetchPreload->AsyncConsume(this); if (NS_SUCCEEDED(rv)) {
mFromPreload = true;
// Copied from AsyncOnChannelRedirect. for (constauto& redirect : fetchPreload->Redirects()) { if (redirect.Flags() & nsIChannelEventSink::REDIRECT_INTERNAL) {
mRequest->SetURLForInternalRedirect(redirect.Flags(), redirect.Spec(),
redirect.Fragment());
} else {
mRequest->AddURL(redirect.Spec(), redirect.Fragment());
}
}
return NS_OK;
}
// The preload failed to be consumed. Behave like there were no preload.
fetchPreload = nullptr;
}
// 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."
// 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;
if (mRequest->GetRedirectMode() != RequestRedirect::Follow) {
secFlags |= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS;
}
// This handles the use credentials flag in "HTTP // network or cache fetch" in the spec and decides whether to transmit // cookies and other identifying information. if (mRequest->GetCredentialsMode() == RequestCredentials::Include) {
secFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
} elseif (mRequest->GetCredentialsMode() == RequestCredentials::Omit) {
secFlags |= nsILoadInfo::SEC_COOKIES_OMIT;
} elseif (mRequest->GetCredentialsMode() ==
RequestCredentials::Same_origin) {
secFlags |= nsILoadInfo::SEC_COOKIES_SAME_ORIGIN;
} else {
MOZ_ASSERT_UNREACHABLE("Unexpected credentials mode!"); return NS_ERROR_UNEXPECTED;
}
// 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;
// If the fetch is created by FetchEvent.request or NavigationPreload request, // corresponding InterceptedHttpChannel information need to propagate to the // channel of the fetch. if (mRequest->GetInterceptionTriggeringPrincipalInfo()) { auto principalOrErr = mozilla::ipc::PrincipalInfoToPrincipal(
*(mRequest->GetInterceptionTriggeringPrincipalInfo().get())); if (!principalOrErr.isErr()) {
nsCOMPtr<nsIPrincipal> principal = principalOrErr.unwrap();
nsTArray<nsCOMPtr<nsIRedirectHistoryEntry>> redirectChain; if (!mRequest->InterceptionRedirectChain().IsEmpty()) { for (const RedirectHistoryEntryInfo& entryInfo :
mRequest->InterceptionRedirectChain()) {
nsCOMPtr<nsIRedirectHistoryEntry> entry =
mozilla::ipc::RHEntryInfoToRHEntry(entryInfo);
redirectChain.AppendElement(entry);
}
}
// Insert ourselves into the notification callbacks chain so we can set // headers on redirects. #ifdef DEBUG
{
nsCOMPtr<nsIInterfaceRequestor> notificationCallbacks;
chan->GetNotificationCallbacks(getter_AddRefs(notificationCallbacks));
MOZ_ASSERT(!notificationCallbacks);
} #endif
chan->SetNotificationCallbacks(this);
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(chan)); // Mark channel as urgent-start if the Fetch is triggered by user input // events. if (cos && UserActivation::IsHandlingUserInput()) {
cos->AddClassFlags(nsIClassOfService::UrgentStart);
}
// 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);
NS_ENSURE_SUCCESS(rv, rv);
// Set the same headers.
SetRequestHeaders(httpChan, false, false);
// Step 5 of https://fetch.spec.whatwg.org/#main-fetch // If request's referrer policy is the empty string and request's client is // non-null, then set request's referrer policy to request's client's // associated referrer policy. // Basically, "client" is not in our implementation, we use // EnvironmentReferrerPolicy of the worker or document context
ReferrerPolicy referrerPolicy = mRequest->GetEnvironmentReferrerPolicy(); if (mRequest->ReferrerPolicy_() == ReferrerPolicy::_empty) {
mRequest->SetReferrerPolicy(referrerPolicy);
} // Step 6 of https://fetch.spec.whatwg.org/#main-fetch // If request’s referrer policy is the empty string, // then set request’s referrer policy to the user-set default policy. if (mRequest->ReferrerPolicy_() == ReferrerPolicy::_empty) {
nsCOMPtr<nsILoadInfo> loadInfo = httpChan->LoadInfo(); bool isPrivate = loadInfo->GetOriginAttributes().IsPrivateBrowsing();
referrerPolicy =
ReferrerInfo::GetDefaultReferrerPolicy(httpChan, uri, isPrivate);
mRequest->SetReferrerPolicy(referrerPolicy);
}
// 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,
rv = internalChan->SetRequestMode(mRequest->Mode());
MOZ_ASSERT(NS_SUCCEEDED(rv)); // Conversion between enumerations is safe due to static asserts in // dom/workers/ServiceWorkerManager.cpp
rv = internalChan->SetRedirectMode( static_cast<uint32_t>(mRequest->GetRedirectMode()));
MOZ_ASSERT(NS_SUCCEEDED(rv));
mRequest->MaybeSkipCacheIfPerformingRevalidation();
rv = internalChan->SetFetchCacheMode( static_cast<uint32_t>(mRequest->GetCacheMode()));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = internalChan->SetIntegrityMetadata(mRequest->GetIntegrity());
MOZ_ASSERT(NS_SUCCEEDED(rv));
// Set the initiator type
nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan)); if (timedChannel) {
timedChannel->SetInitiatorType(u"fetch"_ns);
}
}
// Step 5. Proxy authentication will be handled by Necko.
// Continue setting up 'HTTPRequest'. Content-Type and body data.
nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(chan); if (uploadChan) {
nsAutoCString contentType;
ErrorResult result;
mRequest->Headers()->GetFirst("content-type"_ns, contentType, result); // We don't actually expect "result" to have failed here: that only happens // for invalid header names. But if for some reason it did, just propagate // it out. if (result.Failed()) { return result.StealNSResult();
}
// Now contentType is the header that was set in mRequest->Headers(), or a // void string if no header was set. #ifdef DEBUG bool hasContentTypeHeader =
mRequest->Headers()->Has("content-type"_ns, result);
MOZ_ASSERT(!result.Failed());
MOZ_ASSERT_IF(!hasContentTypeHeader, contentType.IsVoid()); #endif// DEBUG
// If preflight is required, start a "CORS preflight fetch" // https://fetch.spec.whatwg.org/#cors-preflight-fetch-0. All the // implementation is handled by the http channel calling into // nsCORSListenerProxy. We just inform it which unsafe headers are included // in the request. if (mRequest->Mode() == RequestMode::Cors) {
AutoTArray<nsCString, 5> unsafeHeaders;
mRequest->Headers()->GetUnsafeHeaders(unsafeHeaders);
nsCOMPtr<nsILoadInfo> loadInfo = chan->LoadInfo();
loadInfo->SetCorsPreflightInfo(unsafeHeaders, false);
}
constauto fetchPriority = ToFetchPriority(mRequest->GetPriorityMode()); if (cos) {
cos->SetFetchPriorityDOM(fetchPriority);
}
if (nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(chan)) { if (mIsTrackingFetch &&
StaticPrefs::privacy_trackingprotection_lower_network_priority()) {
p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
} elseif (StaticPrefs::network_fetchpriority_enabled()) { // According to step 15 of https://fetch.spec.whatwg.org/#concept-fetch // request’s priority, initiator, destination, and render-blocking are // used in an implementation-defined manner to set the internal priority. // See corresponding preferences in StaticPrefList.yaml for more context. const int32_t supportsPriorityDelta = [this, &fetchPriority]() { auto destination = mRequest->GetInterceptionTriggeringPrincipalInfo()
? mRequest->InterceptionDestination()
: mRequest->Destination(); switch (destination) { case RequestDestination::Font: return FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_font,
fetchPriority); case RequestDestination::Style: return FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_style,
fetchPriority); case RequestDestination::Script: case RequestDestination::Audioworklet: case RequestDestination::Paintworklet: case RequestDestination::Sharedworker: case RequestDestination::Worker: case RequestDestination::Xslt: case RequestDestination::Json: return FETCH_PRIORITY_ADJUSTMENT_FOR(link_preload_script,
fetchPriority); case RequestDestination::Image: return FETCH_PRIORITY_ADJUSTMENT_FOR(images, fetchPriority); case RequestDestination::Audio: case RequestDestination::Track: case RequestDestination::Video: return FETCH_PRIORITY_ADJUSTMENT_FOR(media, fetchPriority); case RequestDestination::Document: case RequestDestination::Embed: case RequestDestination::Frame: case RequestDestination::Iframe: case RequestDestination::Manifest: case RequestDestination::Object: case RequestDestination::Report: case RequestDestination::_empty: return FETCH_PRIORITY_ADJUSTMENT_FOR(global_fetch_api,
fetchPriority);
};
MOZ_ASSERT_UNREACHABLE("Unknown destination"); return 0;
}();
p->SetPriority(mRequest->InternalPriority());
p->AdjustPriority(supportsPriorityDelta);
}
}
NotifyNetworkMonitorAlternateStack(chan, std::move(mOriginStack)); if (mObserver && httpChan) {
mObserver->OnNotifyNetworkMonitorAlternateStack(httpChan->ChannelId());
}
// Should set a Content-Range header for blob scheme, and also slice the // blob appropriately, so we process the Range header here for later use. if (IsBlobURI(uri)) {
ErrorResult result;
nsAutoCString range;
mRequest->Headers()->Get("Range"_ns, range, result);
MOZ_ASSERT(!result.Failed()); if (!range.IsVoid()) {
rv = NS_SetChannelContentRangeForBlobURI(chan, uri, range); if (NS_FAILED(rv)) { return rv;
}
}
}
// if the preferred alternative data type in InternalRequest is not empty, set // the data type on the created channel and also create a // AlternativeDataStreamListener to be the stream listener of the channel. if (!aPreferredAlternativeDataType.IsEmpty()) {
nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan); if (cic) {
cic->PreferAlternativeDataType(
aPreferredAlternativeDataType, ""_ns,
nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::ASYNC);
MOZ_ASSERT(!mAltDataListener);
mAltDataListener = new AlternativeDataStreamListener( this, chan, aPreferredAlternativeDataType);
rv = chan->AsyncOpen(mAltDataListener);
} else {
rv = chan->AsyncOpen(this);
}
} else { // Integrity check cannot be done on alt-data yet. if (mRequest->GetIntegrity().IsEmpty()) {
nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(chan); if (cic && StaticPrefs::javascript_options_wasm_caching() &&
!mRequest->SkipWasmCaching()) {
cic->PreferAlternativeDataType(
FetchUtil::GetWasmAltDataType(),
nsLiteralCString(WASM_CONTENT_TYPE),
nsICacheInfoChannel::PreferredAlternativeDataDeliveryType::
SERIALIZE);
}
}
rv = chan->AsyncOpen(this);
}
if (NS_FAILED(rv)) { return rv;
}
// Step 4 onwards of "HTTP Fetch" is handled internally by Necko.
MOZ_ASSERT(filteredResponse);
MOZ_ASSERT(mObserver);
MOZ_ASSERT(filteredResponse); if (!ShouldCheckSRI(*mRequest, *filteredResponse)) { // Need to keep mObserver alive.
RefPtr<FetchDriverObserver> observer = mObserver;
observer->OnResponseAvailable(filteredResponse.clonePtr()); #ifdef DEBUG
mResponseAvailableCalled = true; #endif
}
return filteredResponse;
}
void FetchDriver::FailWithNetworkError(nsresult rv) {
AssertIsOnMainThread(); if (mObserver) { // Need to keep mObserver alive.
RefPtr<FetchDriverObserver> observer = mObserver;
observer->OnResponseAvailable(InternalResponse::NetworkError(rv)); #ifdef DEBUG
mResponseAvailableCalled = true; #endif
}
// mObserver could be null after OnResponseAvailable(). if (mObserver) {
mObserver->OnReportPerformanceTiming();
mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking,
JS::UndefinedHandleValue);
mObserver = nullptr;
}
// 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 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("content-encoding"_ns, result) ||
response->Headers()->Has("transfer-encoding"_ns, 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 { // Should set a Content-Range header for blob scheme // (https://fetch.spec.whatwg.org/#scheme-fetch)
nsAutoCString contentRange(VoidCString());
nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(mChannel); if (baseChan) {
RefPtr<mozilla::net::ContentRange> range = baseChan->ContentRange(); if (range) {
range->AsHeader(contentRange);
}
}
nsCOMPtr<nsICacheInfoChannel> cic = do_QueryInterface(aRequest); if (cic) { if (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);
}
} elseif (!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);
}
} elseif (!cic->PreferredAlternativeDataTypes().IsEmpty()) {
MOZ_ASSERT(cic->PreferredAlternativeDataTypes().Length() == 1);
MOZ_ASSERT(cic->PreferredAlternativeDataTypes()[0].type().Equals(
FetchUtil::GetWasmAltDataType()));
MOZ_ASSERT(
cic->PreferredAlternativeDataTypes()[0].contentType().EqualsLiteral(
WASM_CONTENT_TYPE));
if (contentType.EqualsLiteral(WASM_CONTENT_TYPE)) { // We want to attach the CacheInfoChannel to the response object such // that we can track its origin when the Response object is manipulated // by JavaScript code. This is important for WebAssembly, which uses // fetch to query its sources in JavaScript and transfer the Response // object to other function responsible for storing the alternate data // using the CacheInfoChannel.
nsMainThreadPtrHandle<nsICacheInfoChannel> handle( new nsMainThreadPtrHolder<nsICacheInfoChannel>( "nsICacheInfoChannel", cic, false));
response->SetCacheInfoChannel(handle);
}
}
}
// Fetch spec Main Fetch step 21: ignore body for head/connect methods.
nsAutoCString method;
mRequest->GetMethod(method); if (!(method.EqualsLiteral("HEAD") || method.EqualsLiteral("CONNECT"))) { // 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;
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 */);
response->SetBody(pipeInputStream, contentLength);
}
// If the request is a file channel, then remember the local path to // that file so we can later create File blobs rather than plain ones.
nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aRequest); if (fc) {
nsCOMPtr<nsIFile> file;
rv = fc->GetFile(getter_AddRefs(file)); if (!NS_WARN_IF(NS_FAILED(rv))) {
nsAutoString path;
file->GetPath(path);
response->SetBodyLocalPath(path);
}
} else { // If the request is a blob URI, then remember that URI so that we // can later just use that blob instance instead of cloning it.
nsCString blobURISpec;
GetBlobURISpecFromChannel(aRequest, blobURISpec); if (!blobURISpec.IsVoid()) {
response->SetBodyBlobURISpec(blobURISpec);
}
}
response->InitChannelInfo(channel);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); // 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(std::move(response), foundOpaqueRedirect); if (NS_WARN_IF(!mResponse)) { // Fail to generate a paddingInfo for opaque response.
MOZ_DIAGNOSTIC_ASSERT(mRequest->GetResponseTainting() ==
LoadTainting::Opaque &&
!foundOpaqueRedirect);
FailWithNetworkError(NS_ERROR_UNEXPECTED); return NS_ERROR_UNEXPECTED;
}
// From "Main Fetch" step 19: SRI-part1. if (ShouldCheckSRI(*mRequest, *mResponse) && mSRIMetadata.IsEmpty()) {
nsIConsoleReportCollector* reporter = nullptr; if (mObserver) {
reporter = mObserver->GetReporter();
}
FETCH_LOG(("FetchDriver retargeting: request %p", aRequest)); // Try to retarget off main thread. if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) {
RefPtr<TaskQueue> queue =
TaskQueue::Create(sts.forget(), "FetchDriver STS Delivery Queue");
Unused << NS_WARN_IF(NS_FAILED(rr->RetargetDeliveryTo(queue)));
} return NS_OK;
}
namespace {
// Runnable to call the observer OnDataAvailable on the main-thread. class DataAvailableRunnable final : public Runnable {
RefPtr<FetchDriverObserver> mObserver;
// Just like NS_CopySegmentToStream, but also sends the data into an // SRICheckDataVerifier.
nsresult CopySegmentToStreamAndSRI(nsIInputStream* aInStr, void* aClosure, constchar* aBuffer, uint32_t aOffset,
uint32_t aCount, uint32_t* aCountWritten) { auto holder = static_cast<SRIVerifierAndOutputHolder*>(aClosure);
MOZ_DIAGNOSTIC_ASSERT(holder && holder->mVerifier && holder->mOutputStream, "Bogus holder");
nsresult rv = holder->mVerifier->Update(
aCount, reinterpret_cast<const uint8_t*>(aBuffer));
NS_ENSURE_SUCCESS(rv, rv);
// The rest is just like NS_CopySegmentToStream.
*aCountWritten = 0; while (aCount) {
uint32_t n = 0;
rv = holder->mOutputStream->Write(aBuffer, aCount, &n); if (NS_FAILED(rv)) { return rv;
}
aBuffer += n;
aCount -= n;
*aCountWritten += n;
} return NS_OK;
}
} // anonymous namespace
NS_IMETHODIMP
FetchDriver::OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) { // NB: This can be called on any thread! But we're guaranteed that it is // called between OnStartRequest and OnStopRequest, so we don't need to worry // about races for the members accessed in OnStartRequest, OnStopRequest, // FailWithNetworkError and member functions accessed before opening the // channel. However, we have a possibility of a race from // FetchDriverAbortActions
if (!mPipeOutputStream) { // We ignore the body for HEAD/CONNECT requests. // nsIStreamListener mandates reading from the stream before returning.
uint32_t totalRead;
nsresult rv = aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
&totalRead);
NS_ENSURE_SUCCESS(rv, rv); return NS_OK;
}
if (mNeedToObserveOnDataAvailable) {
mNeedToObserveOnDataAvailable = false;
RefPtr<FetchDriverObserver> observer;
{
MutexAutoLock lock(mODAMutex); // Need to keep mObserver alive.
observer = mObserver;
} if (observer) { if (NS_IsMainThread()) {
observer->OnDataAvailable();
} else {
RefPtr<Runnable> runnable = new DataAvailableRunnable(observer);
nsresult rv = mMainThreadEventTarget->Dispatch(runnable.forget(),
NS_DISPATCH_NORMAL); if (NS_WARN_IF(NS_FAILED(rv))) { return rv;
}
}
}
}
if (!mResponse) {
MOZ_ASSERT(false); return NS_ERROR_UNEXPECTED;
}
// Needs to be initialized to 0 because in some cases nsStringInputStream may // not write to aRead.
uint32_t aRead = 0;
MOZ_ASSERT(mPipeOutputStream);
// From "Main Fetch" step 19: SRI-part2. // Note: Avoid checking the hidden opaque body.
nsresult rv; if (mResponse->Type() != ResponseType::Opaque &&
ShouldCheckSRI(*mRequest, *mResponse)) {
MOZ_ASSERT(mSRIDataVerifier);
// If no data was read, it's possible the output stream is closed but the // ReadSegments call followed its contract of returning NS_OK despite write // errors. Unfortunately, nsIOutputStream has an ill-conceived contract when // taken together with ReadSegments' contract, because the pipe will just // NS_OK if we try and invoke its Write* functions ourselves with a 0 count. // So we must just assume the pipe is broken. if (aRead == 0 && aCount != 0) { return NS_BASE_STREAM_CLOSED;
} return rv;
}
// main data loading is going to finish, breaking the reference cycle.
RefPtr<AlternativeDataStreamListener> altDataListener =
std::move(mAltDataListener);
// For PFetch and ServiceWorker navigationPreload, resource timing should be // reported before the body stream closing. if (mObserver) {
mObserver->OnReportPerformanceTiming();
}
// 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_FAILED(aStatusCode) ? aStatusCode
: 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);
void FetchDriver::FinishOnStopRequest(
AlternativeDataStreamListener* aAltDataListener) {
AssertIsOnMainThread(); // OnStopRequest is not called from channel, that means the main data loading // does not finish yet. Reaching here since alternative data loading finishes. if (!mOnStopRequestCalled) { return;
}
MOZ_DIAGNOSTIC_ASSERT(!mAltDataListener); // Wait for alternative data loading finish if we needed it. if (aAltDataListener &&
aAltDataListener->Status() == AlternativeDataStreamListener::LOADING) { // For LOADING case, channel holds the reference of altDataListener, no need // to restore it to mAltDataListener. return;
}
if (mObserver) { // From "Main Fetch" step 19.1, 19.2: Process response. if (ShouldCheckSRI(*mRequest, *mResponse)) {
MOZ_ASSERT(mResponse); // Need to keep mObserver alive.
RefPtr<FetchDriverObserver> observer = mObserver;
observer->OnResponseAvailable(mResponse.clonePtr()); #ifdef DEBUG
mResponseAvailableCalled = true; #endif
}
}
if (mObserver) {
mObserver->OnResponseEnd(FetchDriverObserver::eByNetworking,
JS::UndefinedHandleValue);
mObserver = nullptr;
}
// we need to strip Authentication headers for cross-origin requests // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch bool skipAuthHeader =
NS_ShouldRemoveAuthHeaderOnRedirect(aOldChannel, aNewChannel, aFlags);
// "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list." // However, ignore internal redirects here. We don't want to flip // Response.redirected to true if an internal redirect occurs. These // should be transparent to script.
nsCOMPtr<nsIURI> uri;
MOZ_ALWAYS_SUCCEEDS(NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri)));
if (!(aFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
mRequest->AddURL(spec, fragment);
} else { // Overwrite the URL only when the request is redirected by a service // worker.
mRequest->SetURLForInternalRedirect(aFlags, spec, fragment);
}
// In redirect, httpChannel already took referrer-policy into account, so // updates request’s associated referrer policy from channel.
UpdateReferrerInfoFromNewChannel(aNewChannel);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.