diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/workers/loader/NetworkLoadHandler.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/workers/loader/NetworkLoadHandler.cpp')
-rw-r--r-- | dom/workers/loader/NetworkLoadHandler.cpp | 393 |
1 files changed, 393 insertions, 0 deletions
diff --git a/dom/workers/loader/NetworkLoadHandler.cpp b/dom/workers/loader/NetworkLoadHandler.cpp new file mode 100644 index 0000000000..9c2c243066 --- /dev/null +++ b/dom/workers/loader/NetworkLoadHandler.cpp @@ -0,0 +1,393 @@ +/* -*- 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/. */ + +#include "NetworkLoadHandler.h" +#include "CacheLoadHandler.h" // CachePromiseHandler + +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIPrincipal.h" +#include "nsIScriptError.h" +#include "nsNetUtil.h" + +#include "mozilla/Encoding.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/dom/InternalResponse.h" +#include "mozilla/dom/ServiceWorkerBinding.h" +#include "mozilla/dom/ServiceWorkerManager.h" +#include "mozilla/dom/ScriptLoader.h" +#include "mozilla/dom/Response.h" +#include "mozilla/dom/WorkerScope.h" + +#include "mozilla/dom/workerinternals/ScriptLoader.h" // WorkerScriptLoader + +using mozilla::ipc::PrincipalInfo; + +namespace mozilla { +namespace dom { + +namespace workerinternals::loader { + +NS_IMPL_ISUPPORTS(NetworkLoadHandler, nsIStreamLoaderObserver, + nsIRequestObserver) + +NetworkLoadHandler::NetworkLoadHandler(WorkerScriptLoader* aLoader, + ThreadSafeRequestHandle* aRequestHandle) + : mLoader(aLoader), + mWorkerRef(aLoader->mWorkerRef), + mRequestHandle(aRequestHandle) { + MOZ_ASSERT(mLoader); + + // Worker scripts are always decoded as UTF-8 per spec. + mDecoder = MakeUnique<ScriptDecoder>(UTF_8_ENCODING, + ScriptDecoder::BOMHandling::Remove); +} + +NS_IMETHODIMP +NetworkLoadHandler::OnStreamComplete(nsIStreamLoader* aLoader, + nsISupports* aContext, nsresult aStatus, + uint32_t aStringLen, + const uint8_t* aString) { + // If we have cancelled, or we have no mRequest, it means that the loader has + // shut down and we can exit early. If the cancel result is still NS_OK + if (mRequestHandle->IsEmpty()) { + return NS_OK; + } + nsresult rv = DataReceivedFromNetwork(aLoader, aStatus, aStringLen, aString); + return mRequestHandle->OnStreamComplete(rv); +} + +nsresult NetworkLoadHandler::DataReceivedFromNetwork(nsIStreamLoader* aLoader, + nsresult aStatus, + uint32_t aStringLen, + const uint8_t* aString) { + AssertIsOnMainThread(); + MOZ_ASSERT(!mRequestHandle->IsEmpty()); + WorkerLoadContext* loadContext = mRequestHandle->GetContext(); + + if (!loadContext->mChannel) { + return NS_BINDING_ABORTED; + } + + loadContext->mChannel = nullptr; + + if (NS_FAILED(aStatus)) { + return aStatus; + } + + if (mRequestHandle->IsCancelled()) { + return mRequestHandle->GetCancelResult(); + } + + NS_ASSERTION(aString, "This should never be null!"); + + nsCOMPtr<nsIRequest> request; + nsresult rv = aLoader->GetRequest(getter_AddRefs(request)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + MOZ_ASSERT(channel); + + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + rv = + ssm->GetChannelResultPrincipal(channel, getter_AddRefs(channelPrincipal)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsIPrincipal* principal = mWorkerRef->Private()->GetPrincipal(); + if (!principal) { + WorkerPrivate* parentWorker = mWorkerRef->Private()->GetParent(); + MOZ_ASSERT(parentWorker, "Must have a parent!"); + principal = parentWorker->GetPrincipal(); + } + +#ifdef DEBUG + if (loadContext->IsTopLevel()) { + nsCOMPtr<nsIPrincipal> loadingPrincipal = + mWorkerRef->Private()->GetLoadingPrincipal(); + // if we are not in a ServiceWorker, and the principal is not null, then + // the loading principal must subsume the worker principal if it is not a + // nullPrincipal (sandbox). + MOZ_ASSERT(!loadingPrincipal || loadingPrincipal->GetIsNullPrincipal() || + principal->GetIsNullPrincipal() || + loadingPrincipal->Subsumes(principal)); + } +#endif + + // We don't mute the main worker script becase we've already done + // same-origin checks on them so we should be able to see their errors. + // Note that for data: url, where we allow it through the same-origin check + // but then give it a different origin. + loadContext->mMutedErrorFlag.emplace(!loadContext->IsTopLevel() && + !principal->Subsumes(channelPrincipal)); + + // Make sure we're not seeing the result of a 404 or something by checking + // the 'requestSucceeded' attribute on the http channel. + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request); + nsAutoCString tCspHeaderValue, tCspROHeaderValue, tRPHeaderCValue; + + if (httpChannel) { + bool requestSucceeded; + rv = httpChannel->GetRequestSucceeded(&requestSucceeded); + NS_ENSURE_SUCCESS(rv, rv); + + if (!requestSucceeded) { + return NS_ERROR_NOT_AVAILABLE; + } + + Unused << httpChannel->GetResponseHeader("content-security-policy"_ns, + tCspHeaderValue); + + Unused << httpChannel->GetResponseHeader( + "content-security-policy-report-only"_ns, tCspROHeaderValue); + + Unused << httpChannel->GetResponseHeader("referrer-policy"_ns, + tRPHeaderCValue); + + nsAutoCString sourceMapURL; + if (nsContentUtils::GetSourceMapURL(httpChannel, sourceMapURL)) { + loadContext->mRequest->mSourceMapURL = + Some(NS_ConvertUTF8toUTF16(sourceMapURL)); + } + } + + // May be null. + Document* parentDoc = mWorkerRef->Private()->GetDocument(); + + // Set the Source type to "text" for decoding. + loadContext->mRequest->SetTextSource(loadContext); + + // Use the regular ScriptDecoder Decoder for this grunt work! Should be just + // fine because we're running on the main thread. + rv = mDecoder->DecodeRawData(loadContext->mRequest, aString, aStringLen, + /* aEndOfStream = */ true); + NS_ENSURE_SUCCESS(rv, rv); + + if (!loadContext->mRequest->ScriptTextLength()) { + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, + parentDoc, nsContentUtils::eDOM_PROPERTIES, + "EmptyWorkerSourceWarning"); + } + + // For modules, we need to store the base URI on the module request object, + // rather than on the worker private (as we do for classic scripts). This is + // because module loading is shared across multiple components, with + // ScriptLoadRequests being the common structure among them. This specific + // use of the base url is used when resolving the module specifier for child + // modules. + nsCOMPtr<nsIURI> uri; + rv = channel->GetOriginalURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + loadContext->mRequest->SetBaseURLFromChannelAndOriginalURI(channel, uri); + + // Figure out what we actually loaded. + nsCOMPtr<nsIURI> finalURI; + rv = NS_GetFinalChannelURI(channel, getter_AddRefs(finalURI)); + NS_ENSURE_SUCCESS(rv, rv); + + if (principal->IsSameOrigin(finalURI)) { + nsCString filename; + rv = finalURI->GetSpec(filename); + NS_ENSURE_SUCCESS(rv, rv); + + if (!filename.IsEmpty()) { + // This will help callers figure out what their script url resolved to + // in case of errors, and is used for debugging. + // The full URL shouldn't be exposed to the debugger if cross origin. + // See Bug 1634872. + loadContext->mRequest->mURL = filename; + } + } + + // Update the principal of the worker and its base URI if we just loaded the + // worker's primary script. + bool isDynamic = loadContext->mRequest->IsModuleRequest() && + loadContext->mRequest->AsModuleRequest()->IsDynamicImport(); + if (loadContext->IsTopLevel() && !isDynamic) { + // Take care of the base URI first. + mWorkerRef->Private()->SetBaseURI(finalURI); + + // Store the channel info if needed. + mWorkerRef->Private()->InitChannelInfo(channel); + + // Our final channel principal should match the loading principal + // in terms of the origin. This used to be an assert, but it seems + // there are some rare cases where this check can fail in practice. + // Perhaps some browser script setting nsIChannel.owner, etc. + NS_ENSURE_TRUE(mWorkerRef->Private()->FinalChannelPrincipalIsValid(channel), + NS_ERROR_FAILURE); + + // However, we must still override the principal since the nsIPrincipal + // URL may be different due to same-origin redirects. Unfortunately this + // URL must exactly match the final worker script URL in order to + // properly set the referrer header on fetch/xhr requests. If bug 1340694 + // is ever fixed this can be removed. + rv = mWorkerRef->Private()->SetPrincipalsAndCSPFromChannel(channel); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIContentSecurityPolicy> csp = mWorkerRef->Private()->GetCsp(); + // We did inherit CSP in bug 1223647. If we do not already have a CSP, we + // should get it from the HTTP headers on the worker script. + if (!csp) { + rv = mWorkerRef->Private()->SetCSPFromHeaderValues(tCspHeaderValue, + tCspROHeaderValue); + NS_ENSURE_SUCCESS(rv, rv); + } else { + csp->EnsureEventTarget(mWorkerRef->Private()->MainThreadEventTarget()); + } + + mWorkerRef->Private()->UpdateReferrerInfoFromHeader(tRPHeaderCValue); + + WorkerPrivate* parent = mWorkerRef->Private()->GetParent(); + if (parent) { + // XHR Params Allowed + mWorkerRef->Private()->SetXHRParamsAllowed(parent->XHRParamsAllowed()); + } + + nsCOMPtr<nsILoadInfo> chanLoadInfo = channel->LoadInfo(); + if (chanLoadInfo) { + mLoader->SetController(chanLoadInfo->GetController()); + } + + // If we are loading a blob URL we must inherit the controller + // from the parent. This is a bit odd as the blob URL may have + // been created in a different context with a different controller. + // For now, though, this is what the spec says. See: + // + // https://github.com/w3c/ServiceWorker/issues/1261 + // + if (IsBlobURI(mWorkerRef->Private()->GetBaseURI())) { + MOZ_DIAGNOSTIC_ASSERT(mLoader->GetController().isNothing()); + mLoader->SetController(mWorkerRef->Private()->GetParentController()); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +NetworkLoadHandler::OnStartRequest(nsIRequest* aRequest) { + nsresult rv = PrepareForRequest(aRequest); + + if (NS_WARN_IF(NS_FAILED(rv))) { + aRequest->Cancel(rv); + } + + return rv; +} + +nsresult NetworkLoadHandler::PrepareForRequest(nsIRequest* aRequest) { + AssertIsOnMainThread(); + MOZ_ASSERT(!mRequestHandle->IsEmpty()); + WorkerLoadContext* loadContext = mRequestHandle->GetContext(); + + // If one load info cancels or hits an error, it can race with the start + // callback coming from another load info. + if (mRequestHandle->IsCancelled()) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + + // Checking the MIME type is only required for ServiceWorkers' + // importScripts, per step 10 of + // https://w3c.github.io/ServiceWorker/#importscripts + // + // "Extract a MIME type from the response’s header list. If this MIME type + // (ignoring parameters) is not a JavaScript MIME type, return a network + // error." + if (mWorkerRef->Private()->IsServiceWorker()) { + nsAutoCString mimeType; + channel->GetContentType(mimeType); + + if (!nsContentUtils::IsJavascriptMIMEType( + NS_ConvertUTF8toUTF16(mimeType))) { + const nsCString& scope = mWorkerRef->Private() + ->GetServiceWorkerRegistrationDescriptor() + .Scope(); + + ServiceWorkerManager::LocalizeAndReportToAllClients( + scope, "ServiceWorkerRegisterMimeTypeError2", + nsTArray<nsString>{ + NS_ConvertUTF8toUTF16(scope), NS_ConvertUTF8toUTF16(mimeType), + NS_ConvertUTF8toUTF16(loadContext->mRequest->mURL)}); + + return NS_ERROR_DOM_NETWORK_ERR; + } + } + + // We synthesize the result code, but its never exposed to content. + SafeRefPtr<mozilla::dom::InternalResponse> ir = + MakeSafeRefPtr<mozilla::dom::InternalResponse>(200, "OK"_ns); + ir->SetBody(loadContext->mCacheReadStream, + InternalResponse::UNKNOWN_BODY_SIZE); + + // Drop our reference to the stream now that we've passed it along, so it + // doesn't hang around once the cache is done with it and keep data alive. + loadContext->mCacheReadStream = nullptr; + + // Set the channel info of the channel on the response so that it's + // saved in the cache. + ir->InitChannelInfo(channel); + + // Save the principal of the channel since its URI encodes the script URI + // rather than the ServiceWorkerRegistrationInfo URI. + nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(ssm, "Should never be null!"); + + nsCOMPtr<nsIPrincipal> channelPrincipal; + MOZ_TRY(ssm->GetChannelResultPrincipal(channel, + getter_AddRefs(channelPrincipal))); + + UniquePtr<PrincipalInfo> principalInfo(new PrincipalInfo()); + MOZ_TRY(PrincipalToPrincipalInfo(channelPrincipal, principalInfo.get())); + + ir->SetPrincipalInfo(std::move(principalInfo)); + ir->Headers()->FillResponseHeaders(channel); + + RefPtr<mozilla::dom::Response> response = new mozilla::dom::Response( + mRequestHandle->GetCacheCreator()->Global(), std::move(ir), nullptr); + + mozilla::dom::RequestOrUSVString request; + + MOZ_ASSERT(!loadContext->mFullURL.IsEmpty()); + request.SetAsUSVString().ShareOrDependUpon(loadContext->mFullURL); + + // This JSContext will not end up executing JS code because here there are + // no ReadableStreams involved. + AutoJSAPI jsapi; + jsapi.Init(); + + ErrorResult error; + RefPtr<Promise> cachePromise = + mRequestHandle->GetCacheCreator()->Cache_()->Put(jsapi.cx(), request, + *response, error); + error.WouldReportJSException(); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + RefPtr<CachePromiseHandler> promiseHandler = + new CachePromiseHandler(mLoader, mRequestHandle); + cachePromise->AppendNativeHandler(promiseHandler); + + loadContext->mCachePromise.swap(cachePromise); + loadContext->mCacheStatus = WorkerLoadContext::WritingToCache; + + return NS_OK; +} + +} // namespace workerinternals::loader + +} // namespace dom +} // namespace mozilla |