/* -*- 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 "Fetch.h" #include "FetchConsumer.h" #include "nsIDocument.h" #include "nsIGlobalObject.h" #include "nsIStreamLoader.h" #include "nsIThreadRetargetableRequest.h" #include "nsIUnicodeDecoder.h" #include "nsIUnicodeEncoder.h" #include "nsCharSeparatedTokenizer.h" #include "nsDOMString.h" #include "nsNetUtil.h" #include "nsReadableUtils.h" #include "nsStreamUtils.h" #include "nsStringStream.h" #include "nsProxyRelease.h" #include "mozilla/ErrorResult.h" #include "mozilla/dom/BodyUtil.h" #include "mozilla/dom/EncodingUtils.h" #include "mozilla/dom/Exceptions.h" #include "mozilla/dom/FetchDriver.h" #include "mozilla/dom/File.h" #include "mozilla/dom/FormData.h" #include "mozilla/dom/Headers.h" #include "mozilla/dom/MutableBlobStreamListener.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/PromiseWorkerProxy.h" #include "mozilla/dom/Request.h" #include "mozilla/dom/Response.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/URLSearchParams.h" #include "mozilla/dom/workers/ServiceWorkerManager.h" #include "mozilla/Telemetry.h" #include "InternalRequest.h" #include "InternalResponse.h" #include "WorkerPrivate.h" #include "WorkerRunnable.h" #include "WorkerScope.h" #include "Workers.h" namespace mozilla { namespace dom { using namespace workers; class WorkerFetchResolver final : public FetchDriverObserver { friend class MainThreadFetchRunnable; friend class WorkerFetchResponseEndRunnable; friend class WorkerFetchResponseRunnable; RefPtr<PromiseWorkerProxy> mPromiseProxy; public: // Returns null if worker is shutting down. static already_AddRefed<WorkerFetchResolver> Create(workers::WorkerPrivate* aWorkerPrivate, Promise* aPromise) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(aWorkerPrivate, aPromise); if (!proxy) { return nullptr; } RefPtr<WorkerFetchResolver> r = new WorkerFetchResolver(proxy); return r.forget(); } void OnResponseAvailableInternal(InternalResponse* aResponse) override; void OnResponseEnd() override; private: explicit WorkerFetchResolver(PromiseWorkerProxy* aProxy) : mPromiseProxy(aProxy) { MOZ_ASSERT(!NS_IsMainThread()); MOZ_ASSERT(mPromiseProxy); } ~WorkerFetchResolver() {} virtual void FlushConsoleReport() override; }; class MainThreadFetchResolver final : public FetchDriverObserver { RefPtr<Promise> mPromise; RefPtr<Response> mResponse; nsCOMPtr<nsIDocument> mDocument; NS_DECL_OWNINGTHREAD public: explicit MainThreadFetchResolver(Promise* aPromise); void OnResponseAvailableInternal(InternalResponse* aResponse) override; void SetDocument(nsIDocument* aDocument) { mDocument = aDocument; } virtual void OnResponseEnd() override { FlushConsoleReport(); } private: ~MainThreadFetchResolver(); void FlushConsoleReport() override { mReporter->FlushConsoleReports(mDocument); } }; class MainThreadFetchRunnable : public Runnable { RefPtr<WorkerFetchResolver> mResolver; RefPtr<InternalRequest> mRequest; public: MainThreadFetchRunnable(WorkerFetchResolver* aResolver, InternalRequest* aRequest) : mResolver(aResolver) , mRequest(aRequest) { MOZ_ASSERT(mResolver); } NS_IMETHOD Run() override { AssertIsOnMainThread(); RefPtr<FetchDriver> fetch; RefPtr<PromiseWorkerProxy> proxy = mResolver->mPromiseProxy; { // Acquire the proxy mutex while getting data from the WorkerPrivate... MutexAutoLock lock(proxy->Lock()); if (proxy->CleanedUp()) { NS_WARNING("Aborting Fetch because worker already shut down"); return NS_OK; } nsCOMPtr<nsIPrincipal> principal = proxy->GetWorkerPrivate()->GetPrincipal(); MOZ_ASSERT(principal); nsCOMPtr<nsILoadGroup> loadGroup = proxy->GetWorkerPrivate()->GetLoadGroup(); MOZ_ASSERT(loadGroup); fetch = new FetchDriver(mRequest, principal, loadGroup); nsAutoCString spec; if (proxy->GetWorkerPrivate()->GetBaseURI()) { proxy->GetWorkerPrivate()->GetBaseURI()->GetAsciiSpec(spec); } fetch->SetWorkerScript(spec); } // ...but release it before calling Fetch, because mResolver's callback can // be called synchronously and they want the mutex, too. return fetch->Fetch(mResolver); } }; already_AddRefed<Promise> FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, const RequestInit& aInit, ErrorResult& aRv) { RefPtr<Promise> p = Promise::Create(aGlobal, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } // Double check that we have chrome privileges if the Request's content // policy type has been overridden. Note, we must do this before // entering the global below. Otherwise the IsCallerChrome() will // always fail. MOZ_ASSERT_IF(aInput.IsRequest() && aInput.GetAsRequest().IsContentPolicyTypeOverridden(), nsContentUtils::IsCallerChrome()); AutoJSAPI jsapi; if (!jsapi.Init(aGlobal)) { aRv.Throw(NS_ERROR_NOT_AVAILABLE); return nullptr; } JSContext* cx = jsapi.cx(); JS::Rooted<JSObject*> jsGlobal(cx, aGlobal->GetGlobalJSObject()); GlobalObject global(cx, jsGlobal); RefPtr<Request> request = Request::Constructor(global, aInput, aInit, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } RefPtr<InternalRequest> r = request->GetInternalRequest(); if (NS_IsMainThread()) { nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal); nsCOMPtr<nsIDocument> doc; nsCOMPtr<nsILoadGroup> loadGroup; nsIPrincipal* principal; if (window) { doc = window->GetExtantDoc(); if (!doc) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } principal = doc->NodePrincipal(); loadGroup = doc->GetDocumentLoadGroup(); } else { principal = aGlobal->PrincipalOrNull(); if (NS_WARN_IF(!principal)) { aRv.Throw(NS_ERROR_FAILURE); return nullptr; } nsresult rv = NS_NewLoadGroup(getter_AddRefs(loadGroup), principal); if (NS_WARN_IF(NS_FAILED(rv))) { aRv.Throw(rv); return nullptr; } } Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 1); RefPtr<MainThreadFetchResolver> resolver = new MainThreadFetchResolver(p); RefPtr<FetchDriver> fetch = new FetchDriver(r, principal, loadGroup); fetch->SetDocument(doc); resolver->SetDocument(doc); aRv = fetch->Fetch(resolver); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } } else { WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(worker); Telemetry::Accumulate(Telemetry::FETCH_IS_MAINTHREAD, 0); if (worker->IsServiceWorker()) { r->SetSkipServiceWorker(); } RefPtr<WorkerFetchResolver> resolver = WorkerFetchResolver::Create(worker, p); if (!resolver) { NS_WARNING("Could not add WorkerFetchResolver workerHolder to worker"); aRv.Throw(NS_ERROR_DOM_ABORT_ERR); return nullptr; } RefPtr<MainThreadFetchRunnable> run = new MainThreadFetchRunnable(resolver, r); worker->DispatchToMainThread(run.forget()); } return p.forget(); } MainThreadFetchResolver::MainThreadFetchResolver(Promise* aPromise) : mPromise(aPromise) { } void MainThreadFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); AssertIsOnMainThread(); if (aResponse->Type() != ResponseType::Error) { nsCOMPtr<nsIGlobalObject> go = mPromise->GetParentObject(); mResponse = new Response(go, aResponse); mPromise->MaybeResolve(mResponse); } else { ErrorResult result; result.ThrowTypeError<MSG_FETCH_FAILED>(); mPromise->MaybeReject(result); } } MainThreadFetchResolver::~MainThreadFetchResolver() { NS_ASSERT_OWNINGTHREAD(MainThreadFetchResolver); } class WorkerFetchResponseRunnable final : public MainThreadWorkerRunnable { RefPtr<WorkerFetchResolver> mResolver; // Passed from main thread to worker thread after being initialized. RefPtr<InternalResponse> mInternalResponse; public: WorkerFetchResponseRunnable(WorkerPrivate* aWorkerPrivate, WorkerFetchResolver* aResolver, InternalResponse* aResponse) : MainThreadWorkerRunnable(aWorkerPrivate) , mResolver(aResolver) , mInternalResponse(aResponse) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); RefPtr<Promise> promise = mResolver->mPromiseProxy->WorkerPromise(); if (mInternalResponse->Type() != ResponseType::Error) { RefPtr<nsIGlobalObject> global = aWorkerPrivate->GlobalScope(); RefPtr<Response> response = new Response(global, mInternalResponse); promise->MaybeResolve(response); } else { ErrorResult result; result.ThrowTypeError<MSG_FETCH_FAILED>(); promise->MaybeReject(result); } return true; } }; class WorkerFetchResponseEndBase { RefPtr<PromiseWorkerProxy> mPromiseProxy; public: explicit WorkerFetchResponseEndBase(PromiseWorkerProxy* aPromiseProxy) : mPromiseProxy(aPromiseProxy) { MOZ_ASSERT(mPromiseProxy); } void WorkerRunInternal(WorkerPrivate* aWorkerPrivate) { MOZ_ASSERT(aWorkerPrivate); aWorkerPrivate->AssertIsOnWorkerThread(); mPromiseProxy->CleanUp(); } }; class WorkerFetchResponseEndRunnable final : public MainThreadWorkerRunnable , public WorkerFetchResponseEndBase { public: explicit WorkerFetchResponseEndRunnable(PromiseWorkerProxy* aPromiseProxy) : MainThreadWorkerRunnable(aPromiseProxy->GetWorkerPrivate()) , WorkerFetchResponseEndBase(aPromiseProxy) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { WorkerRunInternal(aWorkerPrivate); return true; } nsresult Cancel() override { // Execute Run anyway to make sure we cleanup our promise proxy to avoid // leaking the worker thread Run(); return WorkerRunnable::Cancel(); } }; class WorkerFetchResponseEndControlRunnable final : public MainThreadWorkerControlRunnable , public WorkerFetchResponseEndBase { public: explicit WorkerFetchResponseEndControlRunnable(PromiseWorkerProxy* aPromiseProxy) : MainThreadWorkerControlRunnable(aPromiseProxy->GetWorkerPrivate()) , WorkerFetchResponseEndBase(aPromiseProxy) { } bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { WorkerRunInternal(aWorkerPrivate); return true; } // Control runnable cancel already calls Run(). }; void WorkerFetchResolver::OnResponseAvailableInternal(InternalResponse* aResponse) { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return; } RefPtr<WorkerFetchResponseRunnable> r = new WorkerFetchResponseRunnable(mPromiseProxy->GetWorkerPrivate(), this, aResponse); if (!r->Dispatch()) { NS_WARNING("Could not dispatch fetch response"); } } void WorkerFetchResolver::OnResponseEnd() { AssertIsOnMainThread(); MutexAutoLock lock(mPromiseProxy->Lock()); if (mPromiseProxy->CleanedUp()) { return; } FlushConsoleReport(); RefPtr<WorkerFetchResponseEndRunnable> r = new WorkerFetchResponseEndRunnable(mPromiseProxy); if (!r->Dispatch()) { RefPtr<WorkerFetchResponseEndControlRunnable> cr = new WorkerFetchResponseEndControlRunnable(mPromiseProxy); // This can fail if the worker thread is canceled or killed causing // the PromiseWorkerProxy to give up its WorkerHolder immediately, // allowing the worker thread to become Dead. if (!cr->Dispatch()) { NS_WARNING("Failed to dispatch WorkerFetchResponseEndControlRunnable"); } } } void WorkerFetchResolver::FlushConsoleReport() { AssertIsOnMainThread(); MOZ_ASSERT(mPromiseProxy); if(!mReporter) { return; } workers::WorkerPrivate* worker = mPromiseProxy->GetWorkerPrivate(); if (!worker) { mReporter->FlushConsoleReports((nsIDocument*)nullptr); return; } if (worker->IsServiceWorker()) { // Flush to service worker RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); if (!swm) { mReporter->FlushConsoleReports((nsIDocument*)nullptr); return; } swm->FlushReportsToAllClients(worker->WorkerName(), mReporter); return; } if (worker->IsSharedWorker()) { // Flush to shared worker worker->FlushReportsToSharedWorkers(mReporter); return; } // Flush to dedicated worker mReporter->FlushConsoleReports(worker->GetDocument()); } namespace { nsresult ExtractFromArrayBuffer(const ArrayBuffer& aBuffer, nsIInputStream** aStream, uint64_t& aContentLength) { aBuffer.ComputeLengthAndData(); aContentLength = aBuffer.Length(); //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. return NS_NewByteInputStream(aStream, reinterpret_cast<char*>(aBuffer.Data()), aBuffer.Length(), NS_ASSIGNMENT_COPY); } nsresult ExtractFromArrayBufferView(const ArrayBufferView& aBuffer, nsIInputStream** aStream, uint64_t& aContentLength) { aBuffer.ComputeLengthAndData(); aContentLength = aBuffer.Length(); //XXXnsm reinterpret_cast<> is used in DOMParser, should be ok. return NS_NewByteInputStream(aStream, reinterpret_cast<char*>(aBuffer.Data()), aBuffer.Length(), NS_ASSIGNMENT_COPY); } nsresult ExtractFromBlob(const Blob& aBlob, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { RefPtr<BlobImpl> impl = aBlob.Impl(); ErrorResult rv; aContentLength = impl->GetSize(rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } impl->GetInternalStream(aStream, rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } nsAutoString type; impl->GetType(type); aContentType = NS_ConvertUTF16toUTF8(type); return NS_OK; } nsresult ExtractFromFormData(FormData& aFormData, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { nsAutoCString unusedCharset; return aFormData.GetSendInfo(aStream, &aContentLength, aContentType, unusedCharset); } nsresult ExtractFromUSVString(const nsString& aStr, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8"); if (!encoder) { return NS_ERROR_OUT_OF_MEMORY; } int32_t destBufferLen; nsresult rv = encoder->GetMaxLength(aStr.get(), aStr.Length(), &destBufferLen); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCString encoded; if (!encoded.SetCapacity(destBufferLen, fallible)) { return NS_ERROR_OUT_OF_MEMORY; } char* destBuffer = encoded.BeginWriting(); int32_t srcLen = (int32_t) aStr.Length(); int32_t outLen = destBufferLen; rv = encoder->Convert(aStr.get(), &srcLen, destBuffer, &outLen); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(outLen <= destBufferLen); encoded.SetLength(outLen); aContentType = NS_LITERAL_CSTRING("text/plain;charset=UTF-8"); aContentLength = outLen; return NS_NewCStringInputStream(aStream, encoded); } nsresult ExtractFromURLSearchParams(const URLSearchParams& aParams, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { nsAutoString serialized; aParams.Stringify(serialized); aContentType = NS_LITERAL_CSTRING("application/x-www-form-urlencoded;charset=UTF-8"); aContentLength = serialized.Length(); return NS_NewCStringInputStream(aStream, NS_ConvertUTF16toUTF8(serialized)); } } // namespace nsresult ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { MOZ_ASSERT(aStream); if (aBodyInit.IsArrayBuffer()) { const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); return ExtractFromArrayBuffer(buf, aStream, aContentLength); } if (aBodyInit.IsArrayBufferView()) { const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); return ExtractFromArrayBufferView(buf, aStream, aContentLength); } if (aBodyInit.IsBlob()) { const Blob& blob = aBodyInit.GetAsBlob(); return ExtractFromBlob(blob, aStream, aContentType, aContentLength); } if (aBodyInit.IsFormData()) { FormData& form = aBodyInit.GetAsFormData(); return ExtractFromFormData(form, aStream, aContentType, aContentLength); } if (aBodyInit.IsUSVString()) { nsAutoString str; str.Assign(aBodyInit.GetAsUSVString()); return ExtractFromUSVString(str, aStream, aContentType, aContentLength); } if (aBodyInit.IsURLSearchParams()) { URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } nsresult ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, nsIInputStream** aStream, nsCString& aContentType, uint64_t& aContentLength) { MOZ_ASSERT(aStream); MOZ_ASSERT(!*aStream); if (aBodyInit.IsArrayBuffer()) { const ArrayBuffer& buf = aBodyInit.GetAsArrayBuffer(); return ExtractFromArrayBuffer(buf, aStream, aContentLength); } if (aBodyInit.IsArrayBufferView()) { const ArrayBufferView& buf = aBodyInit.GetAsArrayBufferView(); return ExtractFromArrayBufferView(buf, aStream, aContentLength); } if (aBodyInit.IsBlob()) { const Blob& blob = aBodyInit.GetAsBlob(); return ExtractFromBlob(blob, aStream, aContentType, aContentLength); } if (aBodyInit.IsFormData()) { FormData& form = aBodyInit.GetAsFormData(); return ExtractFromFormData(form, aStream, aContentType, aContentLength); } if (aBodyInit.IsUSVString()) { nsAutoString str; str.Assign(aBodyInit.GetAsUSVString()); return ExtractFromUSVString(str, aStream, aContentType, aContentLength); } if (aBodyInit.IsURLSearchParams()) { URLSearchParams& params = aBodyInit.GetAsURLSearchParams(); return ExtractFromURLSearchParams(params, aStream, aContentType, aContentLength); } NS_NOTREACHED("Should never reach here"); return NS_ERROR_FAILURE; } template <class Derived> FetchBody<Derived>::FetchBody() : mWorkerPrivate(nullptr) , mBodyUsed(false) { if (!NS_IsMainThread()) { mWorkerPrivate = GetCurrentThreadWorkerPrivate(); MOZ_ASSERT(mWorkerPrivate); } } template FetchBody<Request>::FetchBody(); template FetchBody<Response>::FetchBody(); template <class Derived> FetchBody<Derived>::~FetchBody() { } template <class Derived> already_AddRefed<Promise> FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv) { if (BodyUsed()) { aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); return nullptr; } SetBodyUsed(); RefPtr<Promise> promise = FetchBodyConsumer<Derived>::Create(DerivedClass()->GetParentObject(), this, aType, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } return promise.forget(); } template already_AddRefed<Promise> FetchBody<Request>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv); template already_AddRefed<Promise> FetchBody<Response>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv); template <class Derived> void FetchBody<Derived>::SetMimeType() { // Extract mime type. ErrorResult result; nsCString contentTypeValues; MOZ_ASSERT(DerivedClass()->GetInternalHeaders()); DerivedClass()->GetInternalHeaders()->Get(NS_LITERAL_CSTRING("Content-Type"), contentTypeValues, result); MOZ_ALWAYS_TRUE(!result.Failed()); // HTTP ABNF states Content-Type may have only one value. // This is from the "parse a header value" of the fetch spec. if (!contentTypeValues.IsVoid() && contentTypeValues.Find(",") == -1) { mMimeType = contentTypeValues; ToLowerCase(mMimeType); } } template void FetchBody<Request>::SetMimeType(); template void FetchBody<Response>::SetMimeType(); } // namespace dom } // namespace mozilla