diff options
Diffstat (limited to 'dom/fetch')
-rw-r--r-- | dom/fetch/ChannelInfo.cpp | 124 | ||||
-rw-r--r-- | dom/fetch/ChannelInfo.h | 93 | ||||
-rw-r--r-- | dom/fetch/ChannelInfo.ipdlh | 14 | ||||
-rw-r--r-- | dom/fetch/Fetch.cpp | 759 | ||||
-rw-r--r-- | dom/fetch/Fetch.h | 205 | ||||
-rw-r--r-- | dom/fetch/FetchConsumer.cpp | 705 | ||||
-rw-r--r-- | dom/fetch/FetchConsumer.h | 128 | ||||
-rw-r--r-- | dom/fetch/FetchDriver.cpp | 938 | ||||
-rw-r--r-- | dom/fetch/FetchDriver.h | 136 | ||||
-rw-r--r-- | dom/fetch/FetchIPCTypes.h | 57 | ||||
-rw-r--r-- | dom/fetch/FetchTypes.ipdlh | 61 | ||||
-rw-r--r-- | dom/fetch/FetchUtil.cpp | 114 | ||||
-rw-r--r-- | dom/fetch/FetchUtil.h | 42 | ||||
-rw-r--r-- | dom/fetch/Headers.cpp | 97 | ||||
-rw-r--r-- | dom/fetch/Headers.h | 144 | ||||
-rw-r--r-- | dom/fetch/InternalHeaders.cpp | 423 | ||||
-rw-r--r-- | dom/fetch/InternalHeaders.h | 160 | ||||
-rw-r--r-- | dom/fetch/InternalRequest.cpp | 495 | ||||
-rw-r--r-- | dom/fetch/InternalRequest.h | 536 | ||||
-rw-r--r-- | dom/fetch/InternalResponse.cpp | 261 | ||||
-rw-r--r-- | dom/fetch/InternalResponse.h | 313 | ||||
-rw-r--r-- | dom/fetch/Request.cpp | 627 | ||||
-rw-r--r-- | dom/fetch/Request.h | 167 | ||||
-rw-r--r-- | dom/fetch/Response.cpp | 281 | ||||
-rw-r--r-- | dom/fetch/Response.h | 149 | ||||
-rw-r--r-- | dom/fetch/moz.build | 52 |
26 files changed, 7081 insertions, 0 deletions
diff --git a/dom/fetch/ChannelInfo.cpp b/dom/fetch/ChannelInfo.cpp new file mode 100644 index 000000000..5ecc7f762 --- /dev/null +++ b/dom/fetch/ChannelInfo.cpp @@ -0,0 +1,124 @@ +/* -*- 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 "mozilla/dom/ChannelInfo.h" +#include "nsCOMPtr.h" +#include "nsContentUtils.h" +#include "nsIChannel.h" +#include "nsIDocument.h" +#include "nsIHttpChannel.h" +#include "nsSerializationHelper.h" +#include "mozilla/net/HttpBaseChannel.h" +#include "mozilla/ipc/ChannelInfo.h" +#include "nsNetUtil.h" + +using namespace mozilla; +using namespace mozilla::dom; + +void +ChannelInfo::InitFromDocument(nsIDocument* aDoc) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + + nsCOMPtr<nsISupports> securityInfo = aDoc->GetSecurityInfo(); + if (securityInfo) { + SetSecurityInfo(securityInfo); + } + + mInited = true; +} + +void +ChannelInfo::InitFromChannel(nsIChannel* aChannel) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + + nsCOMPtr<nsISupports> securityInfo; + aChannel->GetSecurityInfo(getter_AddRefs(securityInfo)); + if (securityInfo) { + SetSecurityInfo(securityInfo); + } + + mInited = true; +} + +void +ChannelInfo::InitFromChromeGlobal(nsIGlobalObject* aGlobal) +{ + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + MOZ_ASSERT(aGlobal); + + MOZ_RELEASE_ASSERT( + nsContentUtils::IsSystemPrincipal(aGlobal->PrincipalOrNull())); + + mSecurityInfo.Truncate(); + mInited = true; +} + +void +ChannelInfo::InitFromIPCChannelInfo(const mozilla::ipc::IPCChannelInfo& aChannelInfo) +{ + MOZ_ASSERT(!mInited, "Cannot initialize the object twice"); + + mSecurityInfo = aChannelInfo.securityInfo(); + + mInited = true; +} + +void +ChannelInfo::SetSecurityInfo(nsISupports* aSecurityInfo) +{ + MOZ_ASSERT(mSecurityInfo.IsEmpty(), "security info should only be set once"); + nsCOMPtr<nsISerializable> serializable = do_QueryInterface(aSecurityInfo); + if (!serializable) { + NS_WARNING("A non-serializable object was passed to InternalResponse::SetSecurityInfo"); + return; + } + NS_SerializeToString(serializable, mSecurityInfo); +} + +nsresult +ChannelInfo::ResurrectInfoOnChannel(nsIChannel* aChannel) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mInited); + + if (!mSecurityInfo.IsEmpty()) { + nsCOMPtr<nsISupports> infoObj; + nsresult rv = NS_DeserializeObject(mSecurityInfo, getter_AddRefs(infoObj)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + nsCOMPtr<nsIHttpChannel> httpChannel = + do_QueryInterface(aChannel); + MOZ_ASSERT(httpChannel); + net::HttpBaseChannel* httpBaseChannel = + static_cast<net::HttpBaseChannel*>(httpChannel.get()); + rv = httpBaseChannel->OverrideSecurityInfo(infoObj); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return NS_OK; +} + +mozilla::ipc::IPCChannelInfo +ChannelInfo::AsIPCChannelInfo() const +{ + // This may be called when mInited is false, for example if we try to store + // a synthesized Response object into the Cache. Uninitialized and empty + // ChannelInfo objects are indistinguishable at the IPC level, so this is + // fine. + + IPCChannelInfo ipcInfo; + + ipcInfo.securityInfo() = mSecurityInfo; + + return ipcInfo; +} diff --git a/dom/fetch/ChannelInfo.h b/dom/fetch/ChannelInfo.h new file mode 100644 index 000000000..a8acacfe0 --- /dev/null +++ b/dom/fetch/ChannelInfo.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_ChannelInfo_h +#define mozilla_dom_ChannelInfo_h + +#include "nsString.h" +#include "nsCOMPtr.h" + +class nsIChannel; +class nsIDocument; +class nsIGlobalObject; +class nsIURI; + +namespace mozilla { +namespace ipc { +class IPCChannelInfo; +} // namespace ipc + +namespace dom { + +// This class represents the information related to a Response that we +// retrieve from the corresponding channel that is used to perform the fetch. +// +// When adding new members to this object, the following code needs to be +// updated: +// * IPCChannelInfo +// * InitFromChannel and InitFromIPCChannelInfo members +// * ResurrectInfoOnChannel member +// * AsIPCChannelInfo member +// * constructors and assignment operators for this class. +// * DOM Cache schema code (in dom/cache/DBSchema.cpp) to ensure that the newly +// added member is saved into the DB and loaded from it properly. +// +// Care must be taken when initializing this object, or when calling +// ResurrectInfoOnChannel(). This object cannot be initialized twice, and +// ResurrectInfoOnChannel() cannot be called on it before it has been +// initialized. There are assertions ensuring these invariants. +class ChannelInfo final +{ +public: + typedef mozilla::ipc::IPCChannelInfo IPCChannelInfo; + + ChannelInfo() + : mInited(false) + { + } + + ChannelInfo(const ChannelInfo& aRHS) + : mSecurityInfo(aRHS.mSecurityInfo) + , mInited(aRHS.mInited) + { + } + + ChannelInfo& + operator=(const ChannelInfo& aRHS) + { + mSecurityInfo = aRHS.mSecurityInfo; + mInited = aRHS.mInited; + return *this; + } + + void InitFromDocument(nsIDocument* aDoc); + void InitFromChannel(nsIChannel* aChannel); + void InitFromChromeGlobal(nsIGlobalObject* aGlobal); + void InitFromIPCChannelInfo(const IPCChannelInfo& aChannelInfo); + + // This restores every possible information stored from a previous channel + // object on a new one. + nsresult ResurrectInfoOnChannel(nsIChannel* aChannel); + + bool IsInitialized() const + { + return mInited; + } + + IPCChannelInfo AsIPCChannelInfo() const; + +private: + void SetSecurityInfo(nsISupports* aSecurityInfo); + +private: + nsCString mSecurityInfo; + bool mInited; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_ChannelInfo_h diff --git a/dom/fetch/ChannelInfo.ipdlh b/dom/fetch/ChannelInfo.ipdlh new file mode 100644 index 000000000..605009e12 --- /dev/null +++ b/dom/fetch/ChannelInfo.ipdlh @@ -0,0 +1,14 @@ +/* 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/. */ + +namespace mozilla { +namespace ipc { + +struct IPCChannelInfo +{ + nsCString securityInfo; +}; + +} // namespace ipc +} // namespace mozilla diff --git a/dom/fetch/Fetch.cpp b/dom/fetch/Fetch.cpp new file mode 100644 index 000000000..5c05f3602 --- /dev/null +++ b/dom/fetch/Fetch.cpp @@ -0,0 +1,759 @@ +/* -*- 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 diff --git a/dom/fetch/Fetch.h b/dom/fetch/Fetch.h new file mode 100644 index 000000000..fc50d3fda --- /dev/null +++ b/dom/fetch/Fetch.h @@ -0,0 +1,205 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_Fetch_h +#define mozilla_dom_Fetch_h + +#include "nsAutoPtr.h" +#include "nsIStreamLoader.h" + +#include "nsCOMPtr.h" +#include "nsError.h" +#include "nsProxyRelease.h" +#include "nsString.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/RequestBinding.h" + +class nsIGlobalObject; + +namespace mozilla { +namespace dom { + +class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; +class BlobImpl; +class InternalRequest; +class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams; +class RequestOrUSVString; + +namespace workers { +class WorkerPrivate; +} // namespace workers + +already_AddRefed<Promise> +FetchRequest(nsIGlobalObject* aGlobal, const RequestOrUSVString& aInput, + const RequestInit& aInit, ErrorResult& aRv); + +nsresult +UpdateRequestReferrer(nsIGlobalObject* aGlobal, InternalRequest* aRequest); + +/* + * Creates an nsIInputStream based on the fetch specifications 'extract a byte + * stream algorithm' - http://fetch.spec.whatwg.org/#concept-bodyinit-extract. + * Stores content type in out param aContentType. + */ +nsresult +ExtractByteStreamFromBody(const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, + nsIInputStream** aStream, + nsCString& aContentType, + uint64_t& aContentLength); + +/* + * Non-owning version. + */ +nsresult +ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& aBodyInit, + nsIInputStream** aStream, + nsCString& aContentType, + uint64_t& aContentLength); + +template <class Derived> class FetchBodyConsumer; + +enum FetchConsumeType +{ + CONSUME_ARRAYBUFFER, + CONSUME_BLOB, + CONSUME_FORMDATA, + CONSUME_JSON, + CONSUME_TEXT, +}; + +/* + * FetchBody's body consumption uses nsIInputStreamPump to read from the + * underlying stream to a block of memory, which is then adopted by + * ContinueConsumeBody() and converted to the right type based on the JS + * function called. + * + * Use of the nsIInputStreamPump complicates things on the worker thread. + * The solution used here is similar to WebSockets. + * The difference is that we are only interested in completion and not data + * events, and nsIInputStreamPump can only deliver completion on the main thread. + * + * Before starting the pump on the main thread, we addref the FetchBody to keep + * it alive. Then we add a feature, to track the status of the worker. + * + * ContinueConsumeBody() is the function that cleans things up in both success + * and error conditions and so all callers call it with the appropriate status. + * + * Once the read is initiated on the main thread there are two possibilities. + * + * 1) Pump finishes before worker has finished Running. + * In this case we adopt the data and dispatch a runnable to the worker, + * which derefs FetchBody and removes the feature and resolves the Promise. + * + * 2) Pump still working while worker has stopped Running. + * The feature is Notify()ed and ContinueConsumeBody() is called with + * NS_BINDING_ABORTED. We first Cancel() the pump using a sync runnable to + * ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly + * held by it) until pump->Cancel() is called. OnStreamComplete() will not + * do anything if the error code is NS_BINDING_ABORTED, so we don't have to + * worry about keeping anything alive. + * + * The pump is always released on the main thread. + */ +template <class Derived> +class FetchBody +{ +public: + friend class FetchBodyConsumer<Derived>; + + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0; + NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0; + + bool + BodyUsed() const { return mBodyUsed; } + + already_AddRefed<Promise> + ArrayBuffer(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_ARRAYBUFFER, aRv); + } + + already_AddRefed<Promise> + Blob(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_BLOB, aRv); + } + + already_AddRefed<Promise> + FormData(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_FORMDATA, aRv); + } + + already_AddRefed<Promise> + Json(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_JSON, aRv); + } + + already_AddRefed<Promise> + Text(ErrorResult& aRv) + { + return ConsumeBody(CONSUME_TEXT, aRv); + } + + // Utility public methods accessed by various runnables. + + void + SetBodyUsed() + { + mBodyUsed = true; + } + + const nsCString& + MimeType() const + { + return mMimeType; + } + +protected: + FetchBody(); + + // Always set whenever the FetchBody is created on the worker thread. + workers::WorkerPrivate* mWorkerPrivate; + + virtual ~FetchBody(); + + void + SetMimeType(); +private: + Derived* + DerivedClass() const + { + return static_cast<Derived*>(const_cast<FetchBody*>(this)); + } + + already_AddRefed<Promise> + ConsumeBody(FetchConsumeType aType, ErrorResult& aRv); + + bool + IsOnTargetThread() + { + return NS_IsMainThread() == !mWorkerPrivate; + } + + void + AssertIsOnTargetThread() + { + MOZ_ASSERT(IsOnTargetThread()); + } + + // Only ever set once, always on target thread. + bool mBodyUsed; + nsCString mMimeType; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Fetch_h diff --git a/dom/fetch/FetchConsumer.cpp b/dom/fetch/FetchConsumer.cpp new file mode 100644 index 000000000..42dfcbaba --- /dev/null +++ b/dom/fetch/FetchConsumer.cpp @@ -0,0 +1,705 @@ +/* -*- 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 "nsIInputStreamPump.h" +#include "nsProxyRelease.h" +#include "WorkerPrivate.h" +#include "WorkerRunnable.h" +#include "WorkerScope.h" +#include "Workers.h" + +namespace mozilla { +namespace dom { + +using namespace workers; + +namespace { + +template <class Derived> +class FetchBodyWorkerHolder final : public workers::WorkerHolder +{ + RefPtr<FetchBodyConsumer<Derived>> mConsumer; + bool mWasNotified; + +public: + explicit FetchBodyWorkerHolder(FetchBodyConsumer<Derived>* aConsumer) + : mConsumer(aConsumer) + , mWasNotified(false) + { + MOZ_ASSERT(aConsumer); + } + + ~FetchBodyWorkerHolder() = default; + + bool Notify(workers::Status aStatus) override + { + MOZ_ASSERT(aStatus > workers::Running); + if (!mWasNotified) { + mWasNotified = true; + mConsumer->ShutDownMainThreadConsuming(); + } + + return true; + } +}; + +template <class Derived> +class BeginConsumeBodyRunnable final : public Runnable +{ + RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer; + +public: + explicit BeginConsumeBodyRunnable(FetchBodyConsumer<Derived>* aConsumer) + : mFetchBodyConsumer(aConsumer) + { } + + NS_IMETHOD + Run() override + { + mFetchBodyConsumer->BeginConsumeBodyMainThread(); + return NS_OK; + } +}; + +/* + * Called on successfully reading the complete stream. + */ +template <class Derived> +class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable +{ + RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer; + nsresult mStatus; + uint32_t mLength; + uint8_t* mResult; + +public: + ContinueConsumeBodyRunnable(FetchBodyConsumer<Derived>* aFetchBodyConsumer, + nsresult aStatus, uint32_t aLength, + uint8_t* aResult) + : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate()) + , mFetchBodyConsumer(aFetchBodyConsumer) + , mStatus(aStatus) + , mLength(aLength) + , mResult(aResult) + { + MOZ_ASSERT(NS_IsMainThread()); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mFetchBodyConsumer->ContinueConsumeBody(mStatus, mLength, mResult); + return true; + } +}; + +template <class Derived> +class FailConsumeBodyWorkerRunnable : public MainThreadWorkerControlRunnable +{ + RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer; + +public: + explicit FailConsumeBodyWorkerRunnable(FetchBodyConsumer<Derived>* aBodyConsumer) + : MainThreadWorkerControlRunnable(aBodyConsumer->GetWorkerPrivate()) + , mBodyConsumer(aBodyConsumer) + { + AssertIsOnMainThread(); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr); + return true; + } +}; + +/* + * In case of failure to create a stream pump or dispatch stream completion to + * worker, ensure we cleanup properly. Thread agnostic. + */ +template <class Derived> +class MOZ_STACK_CLASS AutoFailConsumeBody final +{ + RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer; + +public: + explicit AutoFailConsumeBody(FetchBodyConsumer<Derived>* aBodyConsumer) + : mBodyConsumer(aBodyConsumer) + {} + + ~AutoFailConsumeBody() + { + AssertIsOnMainThread(); + + if (mBodyConsumer) { + if (mBodyConsumer->GetWorkerPrivate()) { + RefPtr<FailConsumeBodyWorkerRunnable<Derived>> r = + new FailConsumeBodyWorkerRunnable<Derived>(mBodyConsumer); + if (!r->Dispatch()) { + MOZ_CRASH("We are going to leak"); + } + } else { + mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr); + } + } + } + + void + DontFail() + { + mBodyConsumer = nullptr; + } +}; + +/* + * Called on successfully reading the complete stream for Blob. + */ +template <class Derived> +class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable +{ + RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer; + RefPtr<BlobImpl> mBlobImpl; + +public: + ContinueConsumeBlobBodyRunnable(FetchBodyConsumer<Derived>* aFetchBodyConsumer, + BlobImpl* aBlobImpl) + : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate()) + , mFetchBodyConsumer(aFetchBodyConsumer) + , mBlobImpl(aBlobImpl) + { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBlobImpl); + } + + bool + WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override + { + mFetchBodyConsumer->ContinueConsumeBlobBody(mBlobImpl); + return true; + } +}; + +template <class Derived> +class ConsumeBodyDoneObserver : public nsIStreamLoaderObserver + , public MutableBlobStorageCallback +{ + RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit ConsumeBodyDoneObserver(FetchBodyConsumer<Derived>* aFetchBodyConsumer) + : mFetchBodyConsumer(aFetchBodyConsumer) + { } + + NS_IMETHOD + OnStreamComplete(nsIStreamLoader* aLoader, + nsISupports* aCtxt, + nsresult aStatus, + uint32_t aResultLength, + const uint8_t* aResult) override + { + MOZ_ASSERT(NS_IsMainThread()); + + // The loading is completed. Let's nullify the pump before continuing the + // consuming of the body. + mFetchBodyConsumer->NullifyConsumeBodyPump(); + + uint8_t* nonconstResult = const_cast<uint8_t*>(aResult); + if (mFetchBodyConsumer->GetWorkerPrivate()) { + RefPtr<ContinueConsumeBodyRunnable<Derived>> r = + new ContinueConsumeBodyRunnable<Derived>(mFetchBodyConsumer, + aStatus, + aResultLength, + nonconstResult); + if (!r->Dispatch()) { + NS_WARNING("Could not dispatch ConsumeBodyRunnable"); + // Return failure so that aResult is freed. + return NS_ERROR_FAILURE; + } + } else { + mFetchBodyConsumer->ContinueConsumeBody(aStatus, aResultLength, + nonconstResult); + } + + // FetchBody is responsible for data. + return NS_SUCCESS_ADOPTED_DATA; + } + + virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage, + Blob* aBlob, + nsresult aRv) override + { + // On error. + if (NS_FAILED(aRv)) { + OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr); + return; + } + + // The loading is completed. Let's nullify the pump before continuing the + // consuming of the body. + mFetchBodyConsumer->NullifyConsumeBodyPump(); + + MOZ_ASSERT(aBlob); + + if (mFetchBodyConsumer->GetWorkerPrivate()) { + RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r = + new ContinueConsumeBlobBodyRunnable<Derived>(mFetchBodyConsumer, + aBlob->Impl()); + + if (!r->Dispatch()) { + NS_WARNING("Could not dispatch ConsumeBlobBodyRunnable"); + return; + } + } else { + mFetchBodyConsumer->ContinueConsumeBlobBody(aBlob->Impl()); + } + } + +private: + virtual ~ConsumeBodyDoneObserver() + { } +}; + +template <class Derived> +NS_IMPL_ADDREF(ConsumeBodyDoneObserver<Derived>) +template <class Derived> +NS_IMPL_RELEASE(ConsumeBodyDoneObserver<Derived>) +template <class Derived> +NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver<Derived>) + NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver) +NS_INTERFACE_MAP_END + +} // anonymous + +template <class Derived> +/* static */ already_AddRefed<Promise> +FetchBodyConsumer<Derived>::Create(nsIGlobalObject* aGlobal, + FetchBody<Derived>* aBody, + FetchConsumeType aType, + ErrorResult& aRv) +{ + MOZ_ASSERT(aBody); + + nsCOMPtr<nsIInputStream> bodyStream; + aBody->DerivedClass()->GetBody(getter_AddRefs(bodyStream)); + if (!bodyStream) { + aRv = NS_NewCStringInputStream(getter_AddRefs(bodyStream), EmptyCString()); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + RefPtr<Promise> promise = Promise::Create(aGlobal, aRv); + if (aRv.Failed()) { + return nullptr; + } + + WorkerPrivate* workerPrivate = nullptr; + if (!NS_IsMainThread()) { + workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + } + + RefPtr<FetchBodyConsumer<Derived>> consumer = + new FetchBodyConsumer<Derived>(aGlobal, workerPrivate, aBody, bodyStream, + promise, aType); + + if (!NS_IsMainThread()) { + MOZ_ASSERT(workerPrivate); + if (NS_WARN_IF(!consumer->RegisterWorkerHolder())) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + } else { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (NS_WARN_IF(!os)) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + aRv = os->AddObserver(consumer, DOM_WINDOW_DESTROYED_TOPIC, true); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + aRv = os->AddObserver(consumer, DOM_WINDOW_FROZEN_TOPIC, true); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + nsCOMPtr<nsIRunnable> r = new BeginConsumeBodyRunnable<Derived>(consumer); + + if (workerPrivate) { + aRv = workerPrivate->DispatchToMainThread(r.forget()); + } else { + aRv = NS_DispatchToMainThread(r.forget()); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return promise.forget(); +} + +template <class Derived> +void +FetchBodyConsumer<Derived>::ReleaseObject() +{ + AssertIsOnTargetThread(); + + if (NS_IsMainThread()) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(this, DOM_WINDOW_DESTROYED_TOPIC); + os->RemoveObserver(this, DOM_WINDOW_FROZEN_TOPIC); + } + } + + mGlobal = nullptr; + mWorkerHolder = nullptr; + +#ifdef DEBUG + mBody = nullptr; +#endif +} + +template <class Derived> +FetchBodyConsumer<Derived>::FetchBodyConsumer(nsIGlobalObject* aGlobalObject, + WorkerPrivate* aWorkerPrivate, + FetchBody<Derived>* aBody, + nsIInputStream* aBodyStream, + Promise* aPromise, + FetchConsumeType aType) + : mTargetThread(NS_GetCurrentThread()) +#ifdef DEBUG + , mBody(aBody) +#endif + , mBodyStream(aBodyStream) + , mBlobStorageType(MutableBlobStorage::eOnlyInMemory) + , mGlobal(aGlobalObject) + , mWorkerPrivate(aWorkerPrivate) + , mConsumeType(aType) + , mConsumePromise(aPromise) + , mBodyConsumed(false) + , mShuttingDown(false) +{ + MOZ_ASSERT(aBody); + MOZ_ASSERT(aBodyStream); + MOZ_ASSERT(aPromise); + + const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo = + aBody->DerivedClass()->GetPrincipalInfo(); + // We support temporary file for blobs only if the principal is known and + // it's system or content not in private Browsing. + if (principalInfo && + (principalInfo->type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo || + (principalInfo->type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo && + principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId == 0))) { + mBlobStorageType = MutableBlobStorage::eCouldBeInTemporaryFile; + } + + mBodyMimeType = aBody->MimeType(); +} + +template <class Derived> +FetchBodyConsumer<Derived>::~FetchBodyConsumer() +{ +} + +template <class Derived> +void +FetchBodyConsumer<Derived>::AssertIsOnTargetThread() const +{ + MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread); +} + +template <class Derived> +bool +FetchBodyConsumer<Derived>::RegisterWorkerHolder() +{ + MOZ_ASSERT(mWorkerPrivate); + mWorkerPrivate->AssertIsOnWorkerThread(); + + MOZ_ASSERT(!mWorkerHolder); + mWorkerHolder.reset(new FetchBodyWorkerHolder<Derived>(this)); + + if (!mWorkerHolder->HoldWorker(mWorkerPrivate, Closing)) { + NS_WARNING("Failed to add workerHolder"); + mWorkerHolder = nullptr; + return false; + } + + return true; +} + +/* + * BeginConsumeBodyMainThread() will automatically reject the consume promise + * and clean up on any failures, so there is no need for callers to do so, + * reflected in a lack of error return code. + */ +template <class Derived> +void +FetchBodyConsumer<Derived>::BeginConsumeBodyMainThread() +{ + AssertIsOnMainThread(); + + AutoFailConsumeBody<Derived> autoReject(this); + + if (mShuttingDown) { + // We haven't started yet, but we have been terminated. AutoFailConsumeBody + // will dispatch a runnable to release resources. + return; + } + + nsCOMPtr<nsIInputStreamPump> pump; + nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), + mBodyStream, -1, -1, 0, 0, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + RefPtr<ConsumeBodyDoneObserver<Derived>> p = + new ConsumeBodyDoneObserver<Derived>(this); + + nsCOMPtr<nsIStreamListener> listener; + if (mConsumeType == CONSUME_BLOB) { + listener = new MutableBlobStreamListener(mBlobStorageType, nullptr, + mBodyMimeType, p); + } else { + nsCOMPtr<nsIStreamLoader> loader; + rv = NS_NewStreamLoader(getter_AddRefs(loader), p); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + listener = loader; + } + + rv = pump->AsyncRead(listener, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + // Now that everything succeeded, we can assign the pump to a pointer that + // stays alive for the lifetime of the FetchConsumer. + mConsumeBodyPump = pump; + + // It is ok for retargeting to fail and reads to happen on the main thread. + autoReject.DontFail(); + + // Try to retarget, otherwise fall back to main thread. + nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump); + if (rr) { + nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + rv = rr->RetargetDeliveryTo(sts); + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("Retargeting failed"); + } + } +} + +template <class Derived> +void +FetchBodyConsumer<Derived>::ContinueConsumeBody(nsresult aStatus, + uint32_t aResultLength, + uint8_t* aResult) +{ + AssertIsOnTargetThread(); + + if (mBodyConsumed) { + return; + } + mBodyConsumed = true; + + // Just a precaution to ensure ContinueConsumeBody is not called out of + // sync with a body read. + MOZ_ASSERT(mBody->BodyUsed()); + + auto autoFree = mozilla::MakeScopeExit([&] { + free(aResult); + }); + + MOZ_ASSERT(mConsumePromise); + RefPtr<Promise> localPromise = mConsumePromise.forget(); + + RefPtr<FetchBodyConsumer<Derived>> self = this; + auto autoReleaseObject = mozilla::MakeScopeExit([&] { + self->ReleaseObject(); + }); + + if (NS_WARN_IF(NS_FAILED(aStatus))) { + localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } + + // Don't warn here since we warned above. + if (NS_FAILED(aStatus)) { + return; + } + + // Finish successfully consuming body according to type. + MOZ_ASSERT(aResult); + + AutoJSAPI jsapi; + if (!jsapi.Init(mGlobal)) { + localPromise->MaybeReject(NS_ERROR_UNEXPECTED); + return; + } + + JSContext* cx = jsapi.cx(); + ErrorResult error; + + switch (mConsumeType) { + case CONSUME_ARRAYBUFFER: { + JS::Rooted<JSObject*> arrayBuffer(cx); + BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult, + error); + + if (!error.Failed()) { + JS::Rooted<JS::Value> val(cx); + val.setObjectOrNull(arrayBuffer); + + localPromise->MaybeResolve(cx, val); + // ArrayBuffer takes over ownership. + aResult = nullptr; + } + break; + } + case CONSUME_BLOB: { + MOZ_CRASH("This should not happen."); + break; + } + case CONSUME_FORMDATA: { + nsCString data; + data.Adopt(reinterpret_cast<char*>(aResult), aResultLength); + aResult = nullptr; + + RefPtr<dom::FormData> fd = + BodyUtil::ConsumeFormData(mGlobal, mBodyMimeType, data, error); + if (!error.Failed()) { + localPromise->MaybeResolve(fd); + } + break; + } + case CONSUME_TEXT: + // fall through handles early exit. + case CONSUME_JSON: { + nsString decoded; + if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) { + if (mConsumeType == CONSUME_TEXT) { + localPromise->MaybeResolve(decoded); + } else { + JS::Rooted<JS::Value> json(cx); + BodyUtil::ConsumeJson(cx, &json, decoded, error); + if (!error.Failed()) { + localPromise->MaybeResolve(cx, json); + } + } + }; + break; + } + default: + NS_NOTREACHED("Unexpected consume body type"); + } + + error.WouldReportJSException(); + if (error.Failed()) { + localPromise->MaybeReject(error); + } +} + +template <class Derived> +void +FetchBodyConsumer<Derived>::ContinueConsumeBlobBody(BlobImpl* aBlobImpl) +{ + AssertIsOnTargetThread(); + MOZ_ASSERT(mConsumeType == CONSUME_BLOB); + + if (mBodyConsumed) { + return; + } + mBodyConsumed = true; + + // Just a precaution to ensure ContinueConsumeBody is not called out of + // sync with a body read. + MOZ_ASSERT(mBody->BodyUsed()); + + MOZ_ASSERT(mConsumePromise); + RefPtr<Promise> localPromise = mConsumePromise.forget(); + + RefPtr<dom::Blob> blob = dom::Blob::Create(mGlobal, aBlobImpl); + MOZ_ASSERT(blob); + + localPromise->MaybeResolve(blob); + + ReleaseObject(); +} + +template <class Derived> +void +FetchBodyConsumer<Derived>::ShutDownMainThreadConsuming() +{ + if (!NS_IsMainThread()) { + RefPtr<FetchBodyConsumer<Derived>> self = this; + + nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction( + [self] () { self->ShutDownMainThreadConsuming(); }); + + WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(workerPrivate); + workerPrivate->DispatchToMainThread(r.forget()); + return; + } + + // We need this because maybe, mConsumeBodyPump has not been created yet. We + // must be sure that we don't try to do it. + mShuttingDown = true; + + if (mConsumeBodyPump) { + mConsumeBodyPump->Cancel(NS_BINDING_ABORTED); + mConsumeBodyPump = nullptr; + } +} + +template <class Derived> +NS_IMETHODIMP +FetchBodyConsumer<Derived>::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + AssertIsOnMainThread(); + + MOZ_ASSERT((strcmp(aTopic, DOM_WINDOW_FROZEN_TOPIC) == 0) || + (strcmp(aTopic, DOM_WINDOW_DESTROYED_TOPIC) == 0)); + + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal); + if (SameCOMIdentity(aSubject, window)) { + ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr); + } + + return NS_OK; +} + +template <class Derived> +NS_IMPL_ADDREF(FetchBodyConsumer<Derived>) + +template <class Derived> +NS_IMPL_RELEASE(FetchBodyConsumer<Derived>) + +template <class Derived> +NS_IMPL_QUERY_INTERFACE(FetchBodyConsumer<Derived>, + nsIObserver, + nsISupportsWeakReference) + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/FetchConsumer.h b/dom/fetch/FetchConsumer.h new file mode 100644 index 000000000..a7509a0ae --- /dev/null +++ b/dom/fetch/FetchConsumer.h @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_FetchConsumer_h +#define mozilla_dom_FetchConsumer_h + +#include "Fetch.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" +#include "mozilla/dom/MutableBlobStorage.h" + +class nsIThread; + +namespace mozilla { +namespace dom { + +class Promise; + +namespace workers { +class WorkerPrivate; +class WorkerHolder; +} + +template <class Derived> class FetchBody; + +// FetchBody is not thread-safe but we need to move it around threads. +// In order to keep it alive all the time, we use a WorkerHolder, if created on +// workers, plus a this consumer. +template <class Derived> +class FetchBodyConsumer final : public nsIObserver + , public nsSupportsWeakReference +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + static already_AddRefed<Promise> + Create(nsIGlobalObject* aGlobal, + FetchBody<Derived>* aBody, + FetchConsumeType aType, + ErrorResult& aRv); + + void + ReleaseObject(); + + void + BeginConsumeBodyMainThread(); + + void + ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult); + + void + ContinueConsumeBlobBody(BlobImpl* aBlobImpl); + + void + ShutDownMainThreadConsuming(); + + workers::WorkerPrivate* + GetWorkerPrivate() const + { + return mWorkerPrivate; + } + + void + NullifyConsumeBodyPump() + { + mShuttingDown = true; + mConsumeBodyPump = nullptr; + } + +private: + FetchBodyConsumer(nsIGlobalObject* aGlobalObject, + workers::WorkerPrivate* aWorkerPrivate, + FetchBody<Derived>* aBody, + nsIInputStream* aBodyStream, + Promise* aPromise, + FetchConsumeType aType); + + ~FetchBodyConsumer(); + + void + AssertIsOnTargetThread() const; + + bool + RegisterWorkerHolder(); + + nsCOMPtr<nsIThread> mTargetThread; + +#ifdef DEBUG + // This is used only to check if the body has been correctly consumed. + RefPtr<FetchBody<Derived>> mBody; +#endif + + nsCOMPtr<nsIInputStream> mBodyStream; + MutableBlobStorage::MutableBlobStorageType mBlobStorageType; + nsCString mBodyMimeType; + + // Set when consuming the body is attempted on a worker. + // Unset when consumption is done/aborted. + // This WorkerHolder keeps alive the consumer via a cycle. + UniquePtr<workers::WorkerHolder> mWorkerHolder; + + nsCOMPtr<nsIGlobalObject> mGlobal; + + // Always set whenever the FetchBodyConsumer is created on the worker thread. + workers::WorkerPrivate* mWorkerPrivate; + + // Touched on the main-thread only. + nsCOMPtr<nsIInputStreamPump> mConsumeBodyPump; + + // Only ever set once, always on target thread. + FetchConsumeType mConsumeType; + RefPtr<Promise> mConsumePromise; + + // touched only on the target thread. + bool mBodyConsumed; + + // touched only on the main-thread. + bool mShuttingDown; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FetchConsumer_h diff --git a/dom/fetch/FetchDriver.cpp b/dom/fetch/FetchDriver.cpp new file mode 100644 index 000000000..aac79b829 --- /dev/null +++ b/dom/fetch/FetchDriver.cpp @@ -0,0 +1,938 @@ +/* -*- 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 "mozilla/DebugOnly.h" +#include "mozilla/dom/FetchDriver.h" + +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIDocument.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIHttpChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIHttpHeaderVisitor.h" +#include "nsIScriptSecurityManager.h" +#include "nsIThreadRetargetableRequest.h" +#include "nsIUploadChannel2.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPipe.h" + +#include "nsContentPolicyUtils.h" +#include "nsDataHandler.h" +#include "nsHostObjectProtocolHandler.h" +#include "nsNetUtil.h" +#include "nsPrintfCString.h" +#include "nsStreamUtils.h" +#include "nsStringStream.h" +#include "nsHttpChannel.h" + +#include "mozilla/dom/File.h" +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/Unused.h" + +#include "Fetch.h" +#include "InternalRequest.h" +#include "InternalResponse.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_ISUPPORTS(FetchDriver, + nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor, + nsIThreadRetargetableStreamListener) + +FetchDriver::FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, + nsILoadGroup* aLoadGroup) + : mPrincipal(aPrincipal) + , mLoadGroup(aLoadGroup) + , mRequest(aRequest) +#ifdef DEBUG + , mResponseAvailableCalled(false) + , mFetchCalled(false) +#endif +{ + MOZ_ASSERT(aRequest); + MOZ_ASSERT(aPrincipal); +} + +FetchDriver::~FetchDriver() +{ + // We assert this since even on failures, we should call + // FailWithNetworkError(). + MOZ_ASSERT(mResponseAvailableCalled); +} + +nsresult +FetchDriver::Fetch(FetchDriverObserver* aObserver) +{ + workers::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(Move(principalInfo)); + if (NS_FAILED(HttpFetch())) { + FailWithNetworkError(); + } + + // 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() +{ + // Step 1. "Let response be null." + mResponse = nullptr; + nsresult rv; + + nsCOMPtr<nsIIOService> ios = do_GetIOService(&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString url; + mRequest->GetURL(url); + nsCOMPtr<nsIURI> uri; + rv = NS_NewURI(getter_AddRefs(uri), + url, + nullptr, + nullptr, + ios); + NS_ENSURE_SUCCESS(rv, rv); + + // 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; + } + } + + // 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; + + nsSecurityFlags secFlags = nsILoadInfo::SEC_ABOUT_BLANK_INHERITS; + if (mRequest->Mode() == RequestMode::Cors) { + secFlags |= nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS; + } else if (mRequest->Mode() == RequestMode::Same_origin || + mRequest->Mode() == RequestMode::Navigate) { + secFlags |= nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS; + } else if (mRequest->Mode() == RequestMode::No_cors) { + secFlags |= nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS; + } else { + MOZ_ASSERT_UNREACHABLE("Unexpected request mode!"); + return NS_ERROR_UNEXPECTED; + } + + if (mRequest->GetRedirectMode() != RequestRedirect::Follow) { + secFlags |= nsILoadInfo::SEC_DONT_FOLLOW_REDIRECTS; + } + + // This is 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; + } else if (mRequest->GetCredentialsMode() == RequestCredentials::Omit) { + secFlags |= nsILoadInfo::SEC_COOKIES_OMIT; + } else if (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; + + nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | + bypassFlag | nsIChannel::LOAD_CLASSIFY_URI; + if (mDocument) { + MOZ_ASSERT(mDocument->NodePrincipal() == mPrincipal); + rv = NS_NewChannel(getter_AddRefs(chan), + uri, + mDocument, + secFlags, + mRequest->ContentPolicyType(), + mLoadGroup, + nullptr, /* aCallbacks */ + loadFlags, + ios); + } else { + rv = NS_NewChannel(getter_AddRefs(chan), + uri, + mPrincipal, + secFlags, + mRequest->ContentPolicyType(), + mLoadGroup, + nullptr, /* aCallbacks */ + loadFlags, + ios); + } + NS_ENSURE_SUCCESS(rv, rv); + + mLoadGroup = nullptr; + + // 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); + + // 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); + + // Step 2. Set the referrer. + nsAutoString referrer; + mRequest->GetReferrer(referrer); + + // The Referrer Policy in Request can be used to override a referrer policy + // associated with an environment settings object. + // If there's no Referrer Policy in the request, it should be inherited + // from environment. + ReferrerPolicy referrerPolicy = mRequest->ReferrerPolicy_(); + net::ReferrerPolicy net_referrerPolicy = mRequest->GetEnvironmentReferrerPolicy(); + switch (referrerPolicy) { + case ReferrerPolicy::_empty: + break; + case ReferrerPolicy::No_referrer: + net_referrerPolicy = net::RP_No_Referrer; + break; + case ReferrerPolicy::No_referrer_when_downgrade: + net_referrerPolicy = net::RP_No_Referrer_When_Downgrade; + break; + case ReferrerPolicy::Origin: + net_referrerPolicy = net::RP_Origin; + break; + case ReferrerPolicy::Origin_when_cross_origin: + net_referrerPolicy = net::RP_Origin_When_Crossorigin; + break; + case ReferrerPolicy::Unsafe_url: + net_referrerPolicy = net::RP_Unsafe_URL; + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy enum value?"); + break; + } + if (referrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) { + rv = nsContentUtils::SetFetchReferrerURIWithPolicy(mPrincipal, + mDocument, + httpChan, + net_referrerPolicy); + NS_ENSURE_SUCCESS(rv, rv); + } else if (referrer.IsEmpty()) { + rv = httpChan->SetReferrerWithPolicy(nullptr, net::RP_No_Referrer); + NS_ENSURE_SUCCESS(rv, rv); + } else { + // From "Determine request's Referrer" step 3 + // "If request's referrer is a URL, let referrerSource be request's + // referrer." + nsCOMPtr<nsIURI> referrerURI; + rv = NS_NewURI(getter_AddRefs(referrerURI), referrer, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + rv = + httpChan->SetReferrerWithPolicy(referrerURI, + referrerPolicy == ReferrerPolicy::_empty ? + mRequest->GetEnvironmentReferrerPolicy() : + net_referrerPolicy); + NS_ENSURE_SUCCESS(rv, rv); + } + + // 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())); + mRequest->MaybeSkipCacheIfPerformingRevalidation(); + internalChan->SetFetchCacheMode(static_cast<uint32_t>(mRequest->GetCacheMode())); + internalChan->SetIntegrityMetadata(mRequest->GetIntegrity()); + } + + // 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(NS_LITERAL_CSTRING("content-type"), 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(NS_LITERAL_CSTRING("content-type"), result); + MOZ_ASSERT(!result.Failed()); + MOZ_ASSERT_IF(!hasContentTypeHeader, contentType.IsVoid()); +#endif // DEBUG + + nsCOMPtr<nsIInputStream> bodyStream; + mRequest->GetBody(getter_AddRefs(bodyStream)); + if (bodyStream) { + nsAutoCString method; + mRequest->GetMethod(method); + rv = uploadChan->ExplicitSetUploadStream(bodyStream, contentType, -1, method, false /* aStreamHasHeaders */); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // 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->GetLoadInfo(); + loadInfo->SetCorsPreflightInfo(unsafeHeaders, false); + } + + rv = chan->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, rv); + + // Step 4 onwards of "HTTP Fetch" is handled internally by Necko. + return NS_OK; +} +already_AddRefed<InternalResponse> +FetchDriver::BeginAndGetFilteredResponse(InternalResponse* aResponse, + bool aFoundOpaqueRedirect) +{ + MOZ_ASSERT(aResponse); + AutoTArray<nsCString, 4> reqURLList; + mRequest->GetURLListWithoutFragment(reqURLList); + MOZ_ASSERT(!reqURLList.IsEmpty()); + aResponse->SetURLList(reqURLList); + RefPtr<InternalResponse> filteredResponse; + if (aFoundOpaqueRedirect) { + filteredResponse = aResponse->OpaqueRedirectResponse(); + } else { + switch (mRequest->GetResponseTainting()) { + case LoadTainting::Basic: + filteredResponse = aResponse->BasicResponse(); + break; + case LoadTainting::CORS: + filteredResponse = aResponse->CORSResponse(); + break; + case LoadTainting::Opaque: + filteredResponse = aResponse->OpaqueResponse(); + break; + default: + MOZ_CRASH("Unexpected case"); + } + } + + MOZ_ASSERT(filteredResponse); + MOZ_ASSERT(mObserver); + if (filteredResponse->Type() == ResponseType::Error || + mRequest->GetIntegrity().IsEmpty()) { + mObserver->OnResponseAvailable(filteredResponse); + #ifdef DEBUG + mResponseAvailableCalled = true; + #endif + } + + return filteredResponse.forget(); +} + +void +FetchDriver::FailWithNetworkError() +{ + workers::AssertIsOnMainThread(); + RefPtr<InternalResponse> error = InternalResponse::NetworkError(); + if (mObserver) { + mObserver->OnResponseAvailable(error); +#ifdef DEBUG + mResponseAvailableCalled = true; +#endif + mObserver->OnResponseEnd(); + mObserver = nullptr; + } +} + +namespace { +class FillResponseHeaders final : public nsIHttpHeaderVisitor { + InternalResponse* mResponse; + + ~FillResponseHeaders() + { } +public: + NS_DECL_ISUPPORTS + + explicit FillResponseHeaders(InternalResponse* aResponse) + : mResponse(aResponse) + { + } + + NS_IMETHOD + VisitHeader(const nsACString & aHeader, const nsACString & aValue) override + { + ErrorResult result; + mResponse->Headers()->Append(aHeader, aValue, result); + if (result.Failed()) { + NS_WARNING(nsPrintfCString("Fetch ignoring illegal header - '%s': '%s'", + PromiseFlatCString(aHeader).get(), + PromiseFlatCString(aValue).get()).get()); + result.SuppressException(); + } + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(FillResponseHeaders, nsIHttpHeaderVisitor) +} // namespace + +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::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + 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. + + uint32_t aRead; + MOZ_ASSERT(mResponse); + MOZ_ASSERT(mPipeOutputStream); + + // From "Main Fetch" step 17: SRI-part2. + if (mResponse->Type() != ResponseType::Error && + !mRequest->GetIntegrity().IsEmpty()) { + MOZ_ASSERT(mSRIDataVerifier); + + uint32_t aWrite; + nsTArray<uint8_t> buffer; + nsresult rv; + buffer.SetCapacity(aCount); + while (aCount > 0) { + rv = aInputStream->Read(reinterpret_cast<char*>(buffer.Elements()), + aCount, &aRead); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mSRIDataVerifier->Update(aRead, (uint8_t*)buffer.Elements()); + NS_ENSURE_SUCCESS(rv, rv); + + while (aRead > 0) { + rv = mPipeOutputStream->Write(reinterpret_cast<char*>(buffer.Elements()), + aRead, &aWrite); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (aRead < aWrite) { + return NS_ERROR_FAILURE; + } + + aRead -= aWrite; + } + + + aCount -= aWrite; + } + + return NS_OK; + } + + nsresult rv = aInputStream->ReadSegments(NS_CopySegmentToStream, + mPipeOutputStream, + aCount, &aRead); + return rv; +} + +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::AsyncOnChannelRedirect(nsIChannel* aOldChannel, + nsIChannel* aNewChannel, + uint32_t aFlags, + nsIAsyncVerifyRedirectCallback *aCallback) +{ + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aNewChannel); + if (httpChannel) { + SetRequestHeaders(httpChannel); + } + + nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(aOldChannel); + nsAutoCString tRPHeaderCValue; + if (oldHttpChannel) { + oldHttpChannel->GetResponseHeader(NS_LITERAL_CSTRING("referrer-policy"), + tRPHeaderCValue); + } + + // "HTTP-redirect fetch": step 14 "Append locationURL to request's URL list." + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(aNewChannel->GetURI(getter_AddRefs(uri))); + + nsCOMPtr<nsIURI> uriClone; + nsresult rv = uri->CloneIgnoringRef(getter_AddRefs(uriClone)); + if(NS_WARN_IF(NS_FAILED(rv))){ + return rv; + } + nsCString spec; + rv = uriClone->GetSpec(spec); + if(NS_WARN_IF(NS_FAILED(rv))){ + return rv; + } + nsCString fragment; + rv = uri->GetRef(fragment); + if(NS_WARN_IF(NS_FAILED(rv))){ + return rv; + } + + mRequest->AddURL(spec, fragment); + NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue); + // updates request’s associated referrer policy according to the + // Referrer-Policy header (if any). + if (!tRPHeaderValue.IsEmpty()) { + net::ReferrerPolicy net_referrerPolicy = + nsContentUtils::GetReferrerPolicyFromHeader(tRPHeaderValue); + if (net_referrerPolicy != net::RP_Unset) { + ReferrerPolicy referrerPolicy = mRequest->ReferrerPolicy_(); + switch (net_referrerPolicy) { + case net::RP_No_Referrer: + referrerPolicy = ReferrerPolicy::No_referrer; + break; + case net::RP_No_Referrer_When_Downgrade: + referrerPolicy = ReferrerPolicy::No_referrer_when_downgrade; + break; + case net::RP_Origin: + referrerPolicy = ReferrerPolicy::Origin; + break; + case net::RP_Origin_When_Crossorigin: + referrerPolicy = ReferrerPolicy::Origin_when_cross_origin; + break; + case net::RP_Unsafe_URL: + referrerPolicy = ReferrerPolicy::Unsafe_url; + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid ReferrerPolicy value"); + break; + } + + mRequest->SetReferrerPolicy(referrerPolicy); + } + } + + aCallback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +FetchDriver::CheckListenerChain() +{ + return NS_OK; +} + +NS_IMETHODIMP +FetchDriver::GetInterface(const nsIID& aIID, void **aResult) +{ + if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { + *aResult = static_cast<nsIChannelEventSink*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + if (aIID.Equals(NS_GET_IID(nsIStreamListener))) { + *aResult = static_cast<nsIStreamListener*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + if (aIID.Equals(NS_GET_IID(nsIRequestObserver))) { + *aResult = static_cast<nsIRequestObserver*>(this); + NS_ADDREF_THIS(); + return NS_OK; + } + + return QueryInterface(aIID, aResult); +} + +void +FetchDriver::SetDocument(nsIDocument* aDocument) +{ + // Cannot set document after Fetch() has been called. + MOZ_ASSERT(!mFetchCalled); + mDocument = aDocument; +} + +void +FetchDriver::SetRequestHeaders(nsIHttpChannel* aChannel) const +{ + MOZ_ASSERT(aChannel); + + AutoTArray<InternalHeaders::Entry, 5> headers; + mRequest->Headers()->GetEntries(headers); + bool hasAccept = false; + for (uint32_t i = 0; i < headers.Length(); ++i) { + if (!hasAccept && headers[i].mName.EqualsLiteral("accept")) { + hasAccept = true; + } + if (headers[i].mValue.IsEmpty()) { + aChannel->SetEmptyRequestHeader(headers[i].mName); + } else { + aChannel->SetRequestHeader(headers[i].mName, headers[i].mValue, false /* merge */); + } + } + + if (!hasAccept) { + aChannel->SetRequestHeader(NS_LITERAL_CSTRING("accept"), + NS_LITERAL_CSTRING("*/*"), + false /* merge */); + } + + if (mRequest->ForceOriginHeader()) { + nsAutoString origin; + if (NS_SUCCEEDED(nsContentUtils::GetUTFOrigin(mPrincipal, origin))) { + aChannel->SetRequestHeader(NS_LITERAL_CSTRING("origin"), + NS_ConvertUTF16toUTF8(origin), + false /* merge */); + } + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/FetchDriver.h b/dom/fetch/FetchDriver.h new file mode 100644 index 000000000..f74298a48 --- /dev/null +++ b/dom/fetch/FetchDriver.h @@ -0,0 +1,136 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_FetchDriver_h +#define mozilla_dom_FetchDriver_h + +#include "nsIChannelEventSink.h" +#include "nsIInterfaceRequestor.h" +#include "nsIStreamListener.h" +#include "nsIThreadRetargetableStreamListener.h" +#include "mozilla/ConsoleReportCollector.h" +#include "mozilla/dom/SRIMetadata.h" +#include "mozilla/RefPtr.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/net/ReferrerPolicy.h" + +class nsIConsoleReportCollector; +class nsIDocument; +class nsIOutputStream; +class nsILoadGroup; +class nsIPrincipal; + +namespace mozilla { +namespace dom { + +class InternalRequest; +class InternalResponse; + +/** + * Provides callbacks to be called when response is available or on error. + * Implemenations usually resolve or reject the promise returned from fetch(). + * The callbacks can be called synchronously or asynchronously from FetchDriver::Fetch. + */ +class FetchDriverObserver +{ +public: + FetchDriverObserver() : mReporter(new ConsoleReportCollector()) + , mGotResponseAvailable(false) + { } + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchDriverObserver); + void OnResponseAvailable(InternalResponse* aResponse) + { + MOZ_ASSERT(!mGotResponseAvailable); + mGotResponseAvailable = true; + OnResponseAvailableInternal(aResponse); + } + virtual void OnResponseEnd() + { }; + + nsIConsoleReportCollector* GetReporter() const + { + return mReporter; + } + + virtual void FlushConsoleReport() = 0; +protected: + virtual ~FetchDriverObserver() + { }; + + virtual void OnResponseAvailableInternal(InternalResponse* aResponse) = 0; + + nsCOMPtr<nsIConsoleReportCollector> mReporter; +private: + bool mGotResponseAvailable; +}; + +class FetchDriver final : public nsIStreamListener, + public nsIChannelEventSink, + public nsIInterfaceRequestor, + public nsIThreadRetargetableStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSICHANNELEVENTSINK + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER + + explicit FetchDriver(InternalRequest* aRequest, nsIPrincipal* aPrincipal, + nsILoadGroup* aLoadGroup); + NS_IMETHOD Fetch(FetchDriverObserver* aObserver); + + void + SetDocument(nsIDocument* aDocument); + + void + SetWorkerScript(const nsACString& aWorkerScirpt) + { + MOZ_ASSERT(!aWorkerScirpt.IsEmpty()); + mWorkerScript = aWorkerScirpt; + } + +private: + nsCOMPtr<nsIPrincipal> mPrincipal; + nsCOMPtr<nsILoadGroup> mLoadGroup; + RefPtr<InternalRequest> mRequest; + RefPtr<InternalResponse> mResponse; + nsCOMPtr<nsIOutputStream> mPipeOutputStream; + RefPtr<FetchDriverObserver> mObserver; + nsCOMPtr<nsIDocument> mDocument; + nsAutoPtr<SRICheckDataVerifier> mSRIDataVerifier; + SRIMetadata mSRIMetadata; + nsCString mWorkerScript; + +#ifdef DEBUG + bool mResponseAvailableCalled; + bool mFetchCalled; +#endif + + FetchDriver() = delete; + FetchDriver(const FetchDriver&) = delete; + FetchDriver& operator=(const FetchDriver&) = delete; + ~FetchDriver(); + + nsresult HttpFetch(); + // Returns the filtered response sent to the observer. + already_AddRefed<InternalResponse> + BeginAndGetFilteredResponse(InternalResponse* aResponse, + bool aFoundOpaqueRedirect); + // Utility since not all cases need to do any post processing of the filtered + // response. + void FailWithNetworkError(); + + void SetRequestHeaders(nsIHttpChannel* aChannel) const; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_FetchDriver_h diff --git a/dom/fetch/FetchIPCTypes.h b/dom/fetch/FetchIPCTypes.h new file mode 100644 index 000000000..faedceff2 --- /dev/null +++ b/dom/fetch/FetchIPCTypes.h @@ -0,0 +1,57 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_fetch_IPCUtils_h +#define mozilla_dom_fetch_IPCUtils_h + +#include "ipc/IPCMessageUtils.h" + +// Fix X11 header brain damage that conflicts with HeadersGuardEnum::None +#undef None + +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/dom/ResponseBinding.h" + +namespace IPC { + template<> + struct ParamTraits<mozilla::dom::HeadersGuardEnum> : + public ContiguousEnumSerializer<mozilla::dom::HeadersGuardEnum, + mozilla::dom::HeadersGuardEnum::None, + mozilla::dom::HeadersGuardEnum::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::ReferrerPolicy> : + public ContiguousEnumSerializer<mozilla::dom::ReferrerPolicy, + mozilla::dom::ReferrerPolicy::_empty, + mozilla::dom::ReferrerPolicy::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestMode> : + public ContiguousEnumSerializer<mozilla::dom::RequestMode, + mozilla::dom::RequestMode::Same_origin, + mozilla::dom::RequestMode::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestCredentials> : + public ContiguousEnumSerializer<mozilla::dom::RequestCredentials, + mozilla::dom::RequestCredentials::Omit, + mozilla::dom::RequestCredentials::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestCache> : + public ContiguousEnumSerializer<mozilla::dom::RequestCache, + mozilla::dom::RequestCache::Default, + mozilla::dom::RequestCache::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::RequestRedirect> : + public ContiguousEnumSerializer<mozilla::dom::RequestRedirect, + mozilla::dom::RequestRedirect::Follow, + mozilla::dom::RequestRedirect::EndGuard_> {}; + template<> + struct ParamTraits<mozilla::dom::ResponseType> : + public ContiguousEnumSerializer<mozilla::dom::ResponseType, + mozilla::dom::ResponseType::Basic, + mozilla::dom::ResponseType::EndGuard_> {}; +} // namespace IPC + +#endif // mozilla_dom_fetch_IPCUtils_h diff --git a/dom/fetch/FetchTypes.ipdlh b/dom/fetch/FetchTypes.ipdlh new file mode 100644 index 000000000..8944252fd --- /dev/null +++ b/dom/fetch/FetchTypes.ipdlh @@ -0,0 +1,61 @@ +/* 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 IPCStream; +include ChannelInfo; +include PBackgroundSharedTypes; + +using HeadersGuardEnum from "mozilla/dom/FetchIPCTypes.h"; +using ReferrerPolicy from "mozilla/dom/FetchIPCTypes.h"; +using RequestCredentials from "mozilla/dom/FetchIPCTypes.h"; +using RequestMode from "mozilla/dom/FetchIPCTypes.h"; +using ResponseType from "mozilla/dom/FetchIPCTypes.h"; +using RequestRedirect from "mozilla/dom/FetchIPCTypes.h"; +using RequestCache from "mozilla/dom/FetchIPCTypes.h"; + +namespace mozilla { +namespace dom { + +struct HeadersEntry +{ + nsCString name; + nsCString value; +}; + +// Note, this does not yet serialize *all* of InternalRequest +// Make sure that it contains the fields that you care about +struct IPCInternalRequest +{ + nsCString[] urls; + nsCString method; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + nsString referrer; + ReferrerPolicy referrerPolicy; + RequestMode mode; + RequestCredentials credentials; + uint32_t contentPolicyType; + RequestCache requestCache; + RequestRedirect requestRedirect; +}; + +// Note, this does not yet serialize *all* of InternalResponse +// Make sure that it contains the fields that you care about +struct IPCInternalResponse +{ + ResponseType type; + nsCString[] urlList; + uint32_t status; + nsCString statusText; + HeadersEntry[] headers; + HeadersGuardEnum headersGuard; + IPCChannelInfo channelInfo; + OptionalPrincipalInfo principalInfo; + OptionalIPCStream body; + int64_t bodySize; +}; + + +} // namespace ipc +} // namespace mozilla diff --git a/dom/fetch/FetchUtil.cpp b/dom/fetch/FetchUtil.cpp new file mode 100644 index 000000000..601d99216 --- /dev/null +++ b/dom/fetch/FetchUtil.cpp @@ -0,0 +1,114 @@ +#include "FetchUtil.h" + +#include "nsError.h" +#include "nsIUnicodeDecoder.h" +#include "nsString.h" + +#include "mozilla/dom/EncodingUtils.h" + +namespace mozilla { +namespace dom { + +// static +nsresult +FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod) +{ + nsAutoCString upperCaseMethod(aMethod); + ToUpperCase(upperCaseMethod); + if (!NS_IsValidHTTPToken(aMethod)) { + outMethod.SetIsVoid(true); + return NS_ERROR_DOM_SYNTAX_ERR; + } + + if (upperCaseMethod.EqualsLiteral("CONNECT") || + upperCaseMethod.EqualsLiteral("TRACE") || + upperCaseMethod.EqualsLiteral("TRACK")) { + outMethod.SetIsVoid(true); + return NS_ERROR_DOM_SECURITY_ERR; + } + + if (upperCaseMethod.EqualsLiteral("DELETE") || + upperCaseMethod.EqualsLiteral("GET") || + upperCaseMethod.EqualsLiteral("HEAD") || + upperCaseMethod.EqualsLiteral("OPTIONS") || + upperCaseMethod.EqualsLiteral("POST") || + upperCaseMethod.EqualsLiteral("PUT")) { + outMethod = upperCaseMethod; + } + else { + outMethod = aMethod; // Case unchanged for non-standard methods + } + return NS_OK; +} + +static bool +FindCRLF(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd) +{ + nsACString::const_iterator end(aEnd); + return FindInReadable(NS_LITERAL_CSTRING("\r\n"), aStart, end); +} + +// Reads over a CRLF and positions start after it. +static bool +PushOverLine(nsACString::const_iterator& aStart, + const nsACString::const_iterator& aEnd) +{ + if (*aStart == nsCRT::CR && (aEnd - aStart > 1) && *(++aStart) == nsCRT::LF) { + ++aStart; // advance to after CRLF + return true; + } + + return false; +} + +// static +bool +FetchUtil::ExtractHeader(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd, + nsCString& aHeaderName, + nsCString& aHeaderValue, + bool* aWasEmptyHeader) +{ + MOZ_ASSERT(aWasEmptyHeader); + // Set it to a valid value here so we don't forget later. + *aWasEmptyHeader = false; + + const char* beginning = aStart.get(); + nsACString::const_iterator end(aEnd); + if (!FindCRLF(aStart, end)) { + return false; + } + + if (aStart.get() == beginning) { + *aWasEmptyHeader = true; + return true; + } + + nsAutoCString header(beginning, aStart.get() - beginning); + + nsACString::const_iterator headerStart, iter, headerEnd; + header.BeginReading(headerStart); + header.EndReading(headerEnd); + iter = headerStart; + if (!FindCharInReadable(':', iter, headerEnd)) { + return false; + } + + aHeaderName.Assign(StringHead(header, iter - headerStart)); + aHeaderName.CompressWhitespace(); + if (!NS_IsValidHTTPToken(aHeaderName)) { + return false; + } + + aHeaderValue.Assign(Substring(++iter, headerEnd)); + if (!NS_IsReasonableHTTPHeaderValue(aHeaderValue)) { + return false; + } + aHeaderValue.CompressWhitespace(); + + return PushOverLine(aStart, aEnd); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/FetchUtil.h b/dom/fetch/FetchUtil.h new file mode 100644 index 000000000..d99aa39b4 --- /dev/null +++ b/dom/fetch/FetchUtil.h @@ -0,0 +1,42 @@ +#ifndef mozilla_dom_FetchUtil_h +#define mozilla_dom_FetchUtil_h + +#include "nsString.h" +#include "nsError.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/FormData.h" + +namespace mozilla { +namespace dom { + +class FetchUtil final +{ +private: + FetchUtil() = delete; + +public: + /** + * Sets outMethod to a valid HTTP request method string based on an input method. + * Implements checks and normalization as specified by the Fetch specification. + * Returns NS_ERROR_DOM_SECURITY_ERR if the method is invalid. + * Otherwise returns NS_OK and the normalized method via outMethod. + */ + static nsresult + GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod); + + /** + * Extracts an HTTP header from a substring range. + */ + static bool + ExtractHeader(nsACString::const_iterator& aStart, + nsACString::const_iterator& aEnd, + nsCString& aHeaderName, + nsCString& aHeaderValue, + bool* aWasEmptyHeader); +}; + +} // namespace dom +} // namespace mozilla +#endif diff --git a/dom/fetch/Headers.cpp b/dom/fetch/Headers.cpp new file mode 100644 index 000000000..1e1a46c62 --- /dev/null +++ b/dom/fetch/Headers.cpp @@ -0,0 +1,97 @@ +/* -*- 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 "mozilla/dom/Headers.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/Preferences.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Headers) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Headers) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Headers, mOwner) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Headers) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +// static +already_AddRefed<Headers> +Headers::Constructor(const GlobalObject& aGlobal, + const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit, + ErrorResult& aRv) +{ + RefPtr<InternalHeaders> ih = new InternalHeaders(); + RefPtr<Headers> headers = new Headers(aGlobal.GetAsSupports(), ih); + + if (!aInit.WasPassed()) { + return headers.forget(); + } + + if (aInit.Value().IsHeaders()) { + ih->Fill(*aInit.Value().GetAsHeaders().mInternalHeaders, aRv); + } else if (aInit.Value().IsByteStringSequenceSequence()) { + ih->Fill(aInit.Value().GetAsByteStringSequenceSequence(), aRv); + } else if (aInit.Value().IsByteStringMozMap()) { + ih->Fill(aInit.Value().GetAsByteStringMozMap(), aRv); + } + + if (aRv.Failed()) { + return nullptr; + } + + return headers.forget(); +} + +// static +already_AddRefed<Headers> +Headers::Constructor(const GlobalObject& aGlobal, + const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit, + ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + return Create(global, aInit, aRv); +} + +/* static */ already_AddRefed<Headers> +Headers::Create(nsIGlobalObject* aGlobal, + const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit, + ErrorResult& aRv) +{ + RefPtr<InternalHeaders> ih = new InternalHeaders(); + RefPtr<Headers> headers = new Headers(aGlobal, ih); + + if (aInit.IsHeaders()) { + ih->Fill(*(aInit.GetAsHeaders().get()->mInternalHeaders), aRv); + } else if (aInit.IsByteStringSequenceSequence()) { + ih->Fill(aInit.GetAsByteStringSequenceSequence(), aRv); + } else if (aInit.IsByteStringMozMap()) { + ih->Fill(aInit.GetAsByteStringMozMap(), aRv); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + return headers.forget(); +} + +JSObject* +Headers::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) +{ + return mozilla::dom::HeadersBinding::Wrap(aCx, this, aGivenProto); +} + +Headers::~Headers() +{ +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/Headers.h b/dom/fetch/Headers.h new file mode 100644 index 000000000..b603dc836 --- /dev/null +++ b/dom/fetch/Headers.h @@ -0,0 +1,144 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_Headers_h +#define mozilla_dom_Headers_h + +#include "mozilla/dom/HeadersBinding.h" + +#include "nsClassHashtable.h" +#include "nsWrapperCache.h" + +#include "InternalHeaders.h" + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +template<typename T> class MozMap; +class HeadersOrByteStringSequenceSequenceOrByteStringMozMap; +class OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap; + +/** + * This Headers class is only used to represent the content facing Headers + * object. It is actually backed by an InternalHeaders implementation. Gecko + * code should NEVER use this, except in the Request and Response + * implementations, where they must always be created from the backing + * InternalHeaders object. + */ +class Headers final : public nsISupports + , public nsWrapperCache +{ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Headers) + + friend class Request; + friend class Response; + +private: + nsCOMPtr<nsISupports> mOwner; + RefPtr<InternalHeaders> mInternalHeaders; + +public: + explicit Headers(nsISupports* aOwner, InternalHeaders* aInternalHeaders) + : mOwner(aOwner) + , mInternalHeaders(aInternalHeaders) + { + } + + explicit Headers(const Headers& aOther) = delete; + + static bool PrefEnabled(JSContext* cx, JSObject* obj); + + static already_AddRefed<Headers> + Constructor(const GlobalObject& aGlobal, + const Optional<HeadersOrByteStringSequenceSequenceOrByteStringMozMap>& aInit, + ErrorResult& aRv); + + static already_AddRefed<Headers> + Constructor(const GlobalObject& aGlobal, + const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit, + ErrorResult& aRv); + + static already_AddRefed<Headers> + Create(nsIGlobalObject* aGlobalObject, + const OwningHeadersOrByteStringSequenceSequenceOrByteStringMozMap& aInit, + ErrorResult& aRv); + + void Append(const nsACString& aName, const nsACString& aValue, + ErrorResult& aRv) + { + mInternalHeaders->Append(aName, aValue, aRv); + } + + void Delete(const nsACString& aName, ErrorResult& aRv) + { + mInternalHeaders->Delete(aName, aRv); + } + + void Get(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const + { + mInternalHeaders->Get(aName, aValue, aRv); + } + + void GetFirst(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const + { + mInternalHeaders->GetFirst(aName, aValue, aRv); + } + + bool Has(const nsACString& aName, ErrorResult& aRv) const + { + return mInternalHeaders->Has(aName, aRv); + } + + void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv) + { + mInternalHeaders->Set(aName, aValue, aRv); + } + + uint32_t GetIterableLength() const + { + return mInternalHeaders->GetIterableLength(); + } + const nsString GetKeyAtIndex(unsigned aIndex) const + { + return mInternalHeaders->GetKeyAtIndex(aIndex); + } + const nsString GetValueAtIndex(unsigned aIndex) const + { + return mInternalHeaders->GetValueAtIndex(aIndex); + } + + // ChromeOnly + HeadersGuardEnum Guard() const + { + return mInternalHeaders->Guard(); + } + + void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) + { + mInternalHeaders->SetGuard(aGuard, aRv); + } + + virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override; + nsISupports* GetParentObject() const { return mOwner; } + +private: + virtual ~Headers(); + + InternalHeaders* + GetInternalHeaders() const + { + return mInternalHeaders; + } +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Headers_h diff --git a/dom/fetch/InternalHeaders.cpp b/dom/fetch/InternalHeaders.cpp new file mode 100644 index 000000000..e81863173 --- /dev/null +++ b/dom/fetch/InternalHeaders.cpp @@ -0,0 +1,423 @@ +/* -*- 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 "mozilla/dom/InternalHeaders.h" + +#include "mozilla/dom/FetchTypes.h" +#include "mozilla/ErrorResult.h" + +#include "nsCharSeparatedTokenizer.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" +#include "nsReadableUtils.h" + +namespace mozilla { +namespace dom { + +InternalHeaders::InternalHeaders(const nsTArray<Entry>&& aHeaders, + HeadersGuardEnum aGuard) + : mGuard(aGuard) + , mList(aHeaders) +{ +} + +InternalHeaders::InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList, + HeadersGuardEnum aGuard) + : mGuard(aGuard) +{ + for (const HeadersEntry& headersEntry : aHeadersEntryList) { + mList.AppendElement(Entry(headersEntry.name(), headersEntry.value())); + } +} + +void +InternalHeaders::ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, + HeadersGuardEnum& aGuard) +{ + aGuard = mGuard; + + aIPCHeaders.Clear(); + for (Entry& entry : mList) { + aIPCHeaders.AppendElement(HeadersEntry(entry.mName, entry.mValue)); + } +} + +void +InternalHeaders::Append(const nsACString& aName, const nsACString& aValue, + ErrorResult& aRv) +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidMutableHeader(lowerName, aValue, aRv)) { + return; + } + + mList.AppendElement(Entry(lowerName, aValue)); +} + +void +InternalHeaders::Delete(const nsACString& aName, ErrorResult& aRv) +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidMutableHeader(lowerName, aRv)) { + return; + } + + // remove in reverse order to minimize copying + for (int32_t i = mList.Length() - 1; i >= 0; --i) { + if (lowerName == mList[i].mName) { + mList.RemoveElementAt(i); + } + } +} + +void +InternalHeaders::Get(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidName(lowerName, aRv)) { + return; + } + + const char* delimiter = ","; + bool firstValueFound = false; + + for (uint32_t i = 0; i < mList.Length(); ++i) { + if (lowerName == mList[i].mName) { + if (firstValueFound) { + aValue += delimiter; + } + aValue += mList[i].mValue; + firstValueFound = true; + } + } + + // No value found, so return null to content + if (!firstValueFound) { + aValue.SetIsVoid(true); + } +} + +void +InternalHeaders::GetFirst(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidName(lowerName, aRv)) { + return; + } + + for (uint32_t i = 0; i < mList.Length(); ++i) { + if (lowerName == mList[i].mName) { + aValue = mList[i].mValue; + return; + } + } + + // No value found, so return null to content + aValue.SetIsVoid(true); +} + +bool +InternalHeaders::Has(const nsACString& aName, ErrorResult& aRv) const +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidName(lowerName, aRv)) { + return false; + } + + for (uint32_t i = 0; i < mList.Length(); ++i) { + if (lowerName == mList[i].mName) { + return true; + } + } + return false; +} + +void +InternalHeaders::Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv) +{ + nsAutoCString lowerName; + ToLowerCase(aName, lowerName); + + if (IsInvalidMutableHeader(lowerName, aValue, aRv)) { + return; + } + + int32_t firstIndex = INT32_MAX; + + // remove in reverse order to minimize copying + for (int32_t i = mList.Length() - 1; i >= 0; --i) { + if (lowerName == mList[i].mName) { + firstIndex = std::min(firstIndex, i); + mList.RemoveElementAt(i); + } + } + + if (firstIndex < INT32_MAX) { + Entry* entry = mList.InsertElementAt(firstIndex); + entry->mName = lowerName; + entry->mValue = aValue; + } else { + mList.AppendElement(Entry(lowerName, aValue)); + } +} + +void +InternalHeaders::Clear() +{ + mList.Clear(); +} + +void +InternalHeaders::SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv) +{ + // The guard is only checked during ::Set() and ::Append() in the spec. It + // does not require revalidating headers already set. + mGuard = aGuard; +} + +InternalHeaders::~InternalHeaders() +{ +} + +// static +bool +InternalHeaders::IsSimpleHeader(const nsACString& aName, const nsACString& aValue) +{ + // Note, we must allow a null content-type value here to support + // get("content-type"), but the IsInvalidValue() check will prevent null + // from being set or appended. + return aName.EqualsLiteral("accept") || + aName.EqualsLiteral("accept-language") || + aName.EqualsLiteral("content-language") || + (aName.EqualsLiteral("content-type") && + nsContentUtils::IsAllowedNonCorsContentType(aValue)); +} + +// static +bool +InternalHeaders::IsRevalidationHeader(const nsACString& aName) +{ + return aName.EqualsLiteral("if-modified-since") || + aName.EqualsLiteral("if-none-match") || + aName.EqualsLiteral("if-unmodified-since") || + aName.EqualsLiteral("if-match") || + aName.EqualsLiteral("if-range"); +} + +//static +bool +InternalHeaders::IsInvalidName(const nsACString& aName, ErrorResult& aRv) +{ + if (!NS_IsValidHTTPToken(aName)) { + NS_ConvertUTF8toUTF16 label(aName); + aRv.ThrowTypeError<MSG_INVALID_HEADER_NAME>(label); + return true; + } + + return false; +} + +// static +bool +InternalHeaders::IsInvalidValue(const nsACString& aValue, ErrorResult& aRv) +{ + if (!NS_IsReasonableHTTPHeaderValue(aValue)) { + NS_ConvertUTF8toUTF16 label(aValue); + aRv.ThrowTypeError<MSG_INVALID_HEADER_VALUE>(label); + return true; + } + return false; +} + +bool +InternalHeaders::IsImmutable(ErrorResult& aRv) const +{ + if (mGuard == HeadersGuardEnum::Immutable) { + aRv.ThrowTypeError<MSG_HEADERS_IMMUTABLE>(); + return true; + } + return false; +} + +bool +InternalHeaders::IsForbiddenRequestHeader(const nsACString& aName) const +{ + return mGuard == HeadersGuardEnum::Request && + nsContentUtils::IsForbiddenRequestHeader(aName); +} + +bool +InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName) const +{ + return mGuard == HeadersGuardEnum::Request_no_cors && + !IsSimpleHeader(aName, EmptyCString()); +} + +bool +InternalHeaders::IsForbiddenRequestNoCorsHeader(const nsACString& aName, + const nsACString& aValue) const +{ + return mGuard == HeadersGuardEnum::Request_no_cors && + !IsSimpleHeader(aName, aValue); +} + +bool +InternalHeaders::IsForbiddenResponseHeader(const nsACString& aName) const +{ + return mGuard == HeadersGuardEnum::Response && + nsContentUtils::IsForbiddenResponseHeader(aName); +} + +void +InternalHeaders::Fill(const InternalHeaders& aInit, ErrorResult& aRv) +{ + const nsTArray<Entry>& list = aInit.mList; + for (uint32_t i = 0; i < list.Length() && !aRv.Failed(); ++i) { + const Entry& entry = list[i]; + Append(entry.mName, entry.mValue, aRv); + } +} + +void +InternalHeaders::Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv) +{ + for (uint32_t i = 0; i < aInit.Length() && !aRv.Failed(); ++i) { + const Sequence<nsCString>& tuple = aInit[i]; + if (tuple.Length() != 2) { + aRv.ThrowTypeError<MSG_INVALID_HEADER_SEQUENCE>(); + return; + } + Append(tuple[0], tuple[1], aRv); + } +} + +void +InternalHeaders::Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv) +{ + nsTArray<nsString> keys; + aInit.GetKeys(keys); + for (uint32_t i = 0; i < keys.Length() && !aRv.Failed(); ++i) { + Append(NS_ConvertUTF16toUTF8(keys[i]), aInit.Get(keys[i]), aRv); + } +} + +bool +InternalHeaders::HasOnlySimpleHeaders() const +{ + for (uint32_t i = 0; i < mList.Length(); ++i) { + if (!IsSimpleHeader(mList[i].mName, mList[i].mValue)) { + return false; + } + } + + return true; +} + +bool +InternalHeaders::HasRevalidationHeaders() const +{ + for (uint32_t i = 0; i < mList.Length(); ++i) { + if (IsRevalidationHeader(mList[i].mName)) { + return true; + } + } + + return false; +} + +// static +already_AddRefed<InternalHeaders> +InternalHeaders::BasicHeaders(InternalHeaders* aHeaders) +{ + RefPtr<InternalHeaders> basic = new InternalHeaders(*aHeaders); + ErrorResult result; + // The Set-Cookie headers cannot be invalid mutable headers, so the Delete + // must succeed. + basic->Delete(NS_LITERAL_CSTRING("Set-Cookie"), result); + MOZ_ASSERT(!result.Failed()); + basic->Delete(NS_LITERAL_CSTRING("Set-Cookie2"), result); + MOZ_ASSERT(!result.Failed()); + return basic.forget(); +} + +// static +already_AddRefed<InternalHeaders> +InternalHeaders::CORSHeaders(InternalHeaders* aHeaders) +{ + RefPtr<InternalHeaders> cors = new InternalHeaders(aHeaders->mGuard); + ErrorResult result; + + nsAutoCString acExposedNames; + aHeaders->GetFirst(NS_LITERAL_CSTRING("Access-Control-Expose-Headers"), acExposedNames, result); + MOZ_ASSERT(!result.Failed()); + + AutoTArray<nsCString, 5> exposeNamesArray; + nsCCharSeparatedTokenizer exposeTokens(acExposedNames, ','); + while (exposeTokens.hasMoreTokens()) { + const nsDependentCSubstring& token = exposeTokens.nextToken(); + if (token.IsEmpty()) { + continue; + } + + if (!NS_IsValidHTTPToken(token)) { + NS_WARNING("Got invalid HTTP token in Access-Control-Expose-Headers. Header value is:"); + NS_WARNING(acExposedNames.get()); + exposeNamesArray.Clear(); + break; + } + + exposeNamesArray.AppendElement(token); + } + + nsCaseInsensitiveCStringArrayComparator comp; + for (uint32_t i = 0; i < aHeaders->mList.Length(); ++i) { + const Entry& entry = aHeaders->mList[i]; + if (entry.mName.EqualsASCII("cache-control") || + entry.mName.EqualsASCII("content-language") || + entry.mName.EqualsASCII("content-type") || + entry.mName.EqualsASCII("expires") || + entry.mName.EqualsASCII("last-modified") || + entry.mName.EqualsASCII("pragma") || + exposeNamesArray.Contains(entry.mName, comp)) { + cors->Append(entry.mName, entry.mValue, result); + MOZ_ASSERT(!result.Failed()); + } + } + + return cors.forget(); +} + +void +InternalHeaders::GetEntries(nsTArray<InternalHeaders::Entry>& aEntries) const +{ + MOZ_ASSERT(aEntries.IsEmpty()); + aEntries.AppendElements(mList); +} + +void +InternalHeaders::GetUnsafeHeaders(nsTArray<nsCString>& aNames) const +{ + MOZ_ASSERT(aNames.IsEmpty()); + for (uint32_t i = 0; i < mList.Length(); ++i) { + const Entry& header = mList[i]; + if (!InternalHeaders::IsSimpleHeader(header.mName, header.mValue)) { + aNames.AppendElement(header.mName); + } + } +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/InternalHeaders.h b/dom/fetch/InternalHeaders.h new file mode 100644 index 000000000..e47066669 --- /dev/null +++ b/dom/fetch/InternalHeaders.h @@ -0,0 +1,160 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_InternalHeaders_h +#define mozilla_dom_InternalHeaders_h + +// needed for HeadersGuardEnum. +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/UnionTypes.h" + +#include "nsClassHashtable.h" +#include "nsWrapperCache.h" + +namespace mozilla { + +class ErrorResult; + +namespace dom { + +template<typename T> class MozMap; +class HeadersEntry; + +class InternalHeaders final +{ + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders) + +public: + struct Entry + { + Entry(const nsACString& aName, const nsACString& aValue) + : mName(aName) + , mValue(aValue) + { } + + Entry() { } + + nsCString mName; + nsCString mValue; + }; + +private: + HeadersGuardEnum mGuard; + nsTArray<Entry> mList; + +public: + explicit InternalHeaders(HeadersGuardEnum aGuard = HeadersGuardEnum::None) + : mGuard(aGuard) + { + } + + explicit InternalHeaders(const InternalHeaders& aOther) + : mGuard(HeadersGuardEnum::None) + { + ErrorResult result; + Fill(aOther, result); + MOZ_ASSERT(!result.Failed()); + // Note that it's important to set the guard after Fill(), to make sure + // that Fill() doesn't fail if aOther is immutable. + mGuard = aOther.mGuard; + } + + explicit InternalHeaders(const nsTArray<Entry>&& aHeaders, + HeadersGuardEnum aGuard = HeadersGuardEnum::None); + + InternalHeaders(const nsTArray<HeadersEntry>& aHeadersEntryList, + HeadersGuardEnum aGuard); + + void ToIPC(nsTArray<HeadersEntry>& aIPCHeaders, + HeadersGuardEnum& aGuard); + + void Append(const nsACString& aName, const nsACString& aValue, + ErrorResult& aRv); + void Delete(const nsACString& aName, ErrorResult& aRv); + void Get(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const; + void GetFirst(const nsACString& aName, nsACString& aValue, ErrorResult& aRv) const; + bool Has(const nsACString& aName, ErrorResult& aRv) const; + void Set(const nsACString& aName, const nsACString& aValue, ErrorResult& aRv); + + uint32_t GetIterableLength() const + { + return mList.Length(); + } + const NS_ConvertASCIItoUTF16 GetKeyAtIndex(unsigned aIndex) const + { + MOZ_ASSERT(aIndex < mList.Length()); + return NS_ConvertASCIItoUTF16(mList[aIndex].mName); + } + const NS_ConvertASCIItoUTF16 GetValueAtIndex(unsigned aIndex) const + { + MOZ_ASSERT(aIndex < mList.Length()); + return NS_ConvertASCIItoUTF16(mList[aIndex].mValue); + } + + void Clear(); + + HeadersGuardEnum Guard() const { return mGuard; } + void SetGuard(HeadersGuardEnum aGuard, ErrorResult& aRv); + + void Fill(const InternalHeaders& aInit, ErrorResult& aRv); + void Fill(const Sequence<Sequence<nsCString>>& aInit, ErrorResult& aRv); + void Fill(const MozMap<nsCString>& aInit, ErrorResult& aRv); + + bool HasOnlySimpleHeaders() const; + + bool HasRevalidationHeaders() const; + + static already_AddRefed<InternalHeaders> + BasicHeaders(InternalHeaders* aHeaders); + + static already_AddRefed<InternalHeaders> + CORSHeaders(InternalHeaders* aHeaders); + + void + GetEntries(nsTArray<InternalHeaders::Entry>& aEntries) const; + + void + GetUnsafeHeaders(nsTArray<nsCString>& aNames) const; +private: + virtual ~InternalHeaders(); + + static bool IsInvalidName(const nsACString& aName, ErrorResult& aRv); + static bool IsInvalidValue(const nsACString& aValue, ErrorResult& aRv); + bool IsImmutable(ErrorResult& aRv) const; + bool IsForbiddenRequestHeader(const nsACString& aName) const; + bool IsForbiddenRequestNoCorsHeader(const nsACString& aName) const; + bool IsForbiddenRequestNoCorsHeader(const nsACString& aName, + const nsACString& aValue) const; + bool IsForbiddenResponseHeader(const nsACString& aName) const; + + bool IsInvalidMutableHeader(const nsACString& aName, + ErrorResult& aRv) const + { + return IsInvalidMutableHeader(aName, EmptyCString(), aRv); + } + + bool IsInvalidMutableHeader(const nsACString& aName, + const nsACString& aValue, + ErrorResult& aRv) const + { + return IsInvalidName(aName, aRv) || + IsInvalidValue(aValue, aRv) || + IsImmutable(aRv) || + IsForbiddenRequestHeader(aName) || + IsForbiddenRequestNoCorsHeader(aName, aValue) || + IsForbiddenResponseHeader(aName); + } + + static bool IsSimpleHeader(const nsACString& aName, + const nsACString& aValue); + + static bool IsRevalidationHeader(const nsACString& aName); +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_InternalHeaders_h diff --git a/dom/fetch/InternalRequest.cpp b/dom/fetch/InternalRequest.cpp new file mode 100644 index 000000000..85feabde3 --- /dev/null +++ b/dom/fetch/InternalRequest.cpp @@ -0,0 +1,495 @@ +/* -*- 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 "InternalRequest.h" + +#include "nsIContentPolicy.h" +#include "nsIDocument.h" +#include "nsStreamUtils.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FetchTypes.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/workers/Workers.h" + +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { +// The global is used to extract the principal. +already_AddRefed<InternalRequest> +InternalRequest::GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const +{ + MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), "Internal Request's urlList should not be empty when copied from constructor."); + RefPtr<InternalRequest> copy = new InternalRequest(mURLList.LastElement(), + mFragment); + copy->SetMethod(mMethod); + copy->mHeaders = new InternalHeaders(*mHeaders); + copy->SetUnsafeRequest(); + copy->mBodyStream = mBodyStream; + copy->mForceOriginHeader = true; + // The "client" is not stored in our implementation. Fetch API users should + // use the appropriate window/document/principal and other Gecko security + // mechanisms as appropriate. + copy->mSameOriginDataURL = true; + copy->mPreserveContentCodings = true; + copy->mReferrer = mReferrer; + copy->mReferrerPolicy = mReferrerPolicy; + copy->mEnvironmentReferrerPolicy = mEnvironmentReferrerPolicy; + copy->mIntegrity = mIntegrity; + + copy->mContentPolicyType = mContentPolicyTypeOverridden ? + mContentPolicyType : + nsIContentPolicy::TYPE_FETCH; + copy->mMode = mMode; + copy->mCredentialsMode = mCredentialsMode; + copy->mCacheMode = mCacheMode; + copy->mRedirectMode = mRedirectMode; + copy->mCreatedByFetchEvent = mCreatedByFetchEvent; + copy->mContentPolicyTypeOverridden = mContentPolicyTypeOverridden; + return copy.forget(); +} + +already_AddRefed<InternalRequest> +InternalRequest::Clone() +{ + RefPtr<InternalRequest> clone = new InternalRequest(*this); + + if (!mBodyStream) { + return clone.forget(); + } + + nsCOMPtr<nsIInputStream> clonedBody; + nsCOMPtr<nsIInputStream> replacementBody; + + nsresult rv = NS_CloneInputStream(mBodyStream, getter_AddRefs(clonedBody), + getter_AddRefs(replacementBody)); + if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } + + clone->mBodyStream.swap(clonedBody); + if (replacementBody) { + mBodyStream.swap(replacementBody); + } + return clone.forget(); +} +InternalRequest::InternalRequest(const nsACString& aURL, + const nsACString& aFragment) + : mMethod("GET") + , mHeaders(new InternalHeaders(HeadersGuardEnum::None)) + , mContentPolicyType(nsIContentPolicy::TYPE_FETCH) + , mReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR)) + , mReferrerPolicy(ReferrerPolicy::_empty) + , mEnvironmentReferrerPolicy(net::RP_Default) + , mMode(RequestMode::No_cors) + , mCredentialsMode(RequestCredentials::Omit) + , mResponseTainting(LoadTainting::Basic) + , mCacheMode(RequestCache::Default) + , mRedirectMode(RequestRedirect::Follow) + , mAuthenticationFlag(false) + , mForceOriginHeader(false) + , mPreserveContentCodings(false) + // FIXME(nsm): This should be false by default, but will lead to the + // algorithm never loading data: URLs right now. See Bug 1018872 about + // how certain contexts will override it to set it to true. Fetch + // specification does not handle this yet. + , mSameOriginDataURL(true) + , mSkipServiceWorker(false) + , mSynchronous(false) + , mUnsafeRequest(false) + , mUseURLCredentials(false) +{ + MOZ_ASSERT(!aURL.IsEmpty()); + AddURL(aURL, aFragment); +} +InternalRequest::InternalRequest(const nsACString& aURL, + const nsACString& aFragment, + const nsACString& aMethod, + already_AddRefed<InternalHeaders> aHeaders, + RequestCache aCacheMode, + RequestMode aMode, + RequestRedirect aRequestRedirect, + RequestCredentials aRequestCredentials, + const nsAString& aReferrer, + ReferrerPolicy aReferrerPolicy, + nsContentPolicyType aContentPolicyType, + const nsAString& aIntegrity) + : mMethod(aMethod) + , mHeaders(aHeaders) + , mContentPolicyType(aContentPolicyType) + , mReferrer(aReferrer) + , mReferrerPolicy(aReferrerPolicy) + , mEnvironmentReferrerPolicy(net::RP_Default) + , mMode(aMode) + , mCredentialsMode(aRequestCredentials) + , mResponseTainting(LoadTainting::Basic) + , mCacheMode(aCacheMode) + , mRedirectMode(aRequestRedirect) + , mIntegrity(aIntegrity) + , mAuthenticationFlag(false) + , mForceOriginHeader(false) + , mPreserveContentCodings(false) + // FIXME See the above comment in the default constructor. + , mSameOriginDataURL(true) + , mSkipServiceWorker(false) + , mSynchronous(false) + , mUnsafeRequest(false) + , mUseURLCredentials(false) +{ + MOZ_ASSERT(!aURL.IsEmpty()); + AddURL(aURL, aFragment); +} +InternalRequest::InternalRequest(const InternalRequest& aOther) + : mMethod(aOther.mMethod) + , mURLList(aOther.mURLList) + , mHeaders(new InternalHeaders(*aOther.mHeaders)) + , mContentPolicyType(aOther.mContentPolicyType) + , mReferrer(aOther.mReferrer) + , mReferrerPolicy(aOther.mReferrerPolicy) + , mEnvironmentReferrerPolicy(aOther.mEnvironmentReferrerPolicy) + , mMode(aOther.mMode) + , mCredentialsMode(aOther.mCredentialsMode) + , mResponseTainting(aOther.mResponseTainting) + , mCacheMode(aOther.mCacheMode) + , mRedirectMode(aOther.mRedirectMode) + , mIntegrity(aOther.mIntegrity) + , mFragment(aOther.mFragment) + , mAuthenticationFlag(aOther.mAuthenticationFlag) + , mForceOriginHeader(aOther.mForceOriginHeader) + , mPreserveContentCodings(aOther.mPreserveContentCodings) + , mSameOriginDataURL(aOther.mSameOriginDataURL) + , mSkipServiceWorker(aOther.mSkipServiceWorker) + , mSynchronous(aOther.mSynchronous) + , mUnsafeRequest(aOther.mUnsafeRequest) + , mUseURLCredentials(aOther.mUseURLCredentials) + , mCreatedByFetchEvent(aOther.mCreatedByFetchEvent) + , mContentPolicyTypeOverridden(aOther.mContentPolicyTypeOverridden) +{ + // NOTE: does not copy body stream... use the fallible Clone() for that +} + +InternalRequest::InternalRequest(const IPCInternalRequest& aIPCRequest) + : mMethod(aIPCRequest.method()) + , mURLList(aIPCRequest.urls()) + , mHeaders(new InternalHeaders(aIPCRequest.headers(), + aIPCRequest.headersGuard())) + , mContentPolicyType(aIPCRequest.contentPolicyType()) + , mReferrer(aIPCRequest.referrer()) + , mReferrerPolicy(aIPCRequest.referrerPolicy()) + , mMode(aIPCRequest.mode()) + , mCredentialsMode(aIPCRequest.credentials()) + , mCacheMode(aIPCRequest.requestCache()) + , mRedirectMode(aIPCRequest.requestRedirect()) +{ + MOZ_ASSERT(!mURLList.IsEmpty()); +} + +InternalRequest::~InternalRequest() +{ +} + +void +InternalRequest::ToIPC(IPCInternalRequest* aIPCRequest) +{ + MOZ_ASSERT(aIPCRequest); + MOZ_ASSERT(!mURLList.IsEmpty()); + aIPCRequest->urls() = mURLList; + aIPCRequest->method() = mMethod; + + mHeaders->ToIPC(aIPCRequest->headers(), aIPCRequest->headersGuard()); + + aIPCRequest->referrer() = mReferrer; + aIPCRequest->referrerPolicy() = mReferrerPolicy; + aIPCRequest->mode() = mMode; + aIPCRequest->credentials() = mCredentialsMode; + aIPCRequest->contentPolicyType() = mContentPolicyType; + aIPCRequest->requestCache() = mCacheMode; + aIPCRequest->requestRedirect() = mRedirectMode; +} + +void +InternalRequest::SetContentPolicyType(nsContentPolicyType aContentPolicyType) +{ + mContentPolicyType = aContentPolicyType; +} + +void +InternalRequest::OverrideContentPolicyType(nsContentPolicyType aContentPolicyType) +{ + SetContentPolicyType(aContentPolicyType); + mContentPolicyTypeOverridden = true; +} + +/* static */ +RequestContext +InternalRequest::MapContentPolicyTypeToRequestContext(nsContentPolicyType aContentPolicyType) +{ + RequestContext context = RequestContext::Internal; + switch (aContentPolicyType) { + case nsIContentPolicy::TYPE_OTHER: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + context = RequestContext::Script; + break; + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + context = RequestContext::Worker; + break; + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + context = RequestContext::Sharedworker; + break; + case nsIContentPolicy::TYPE_INTERNAL_IMAGE: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD: + case nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON: + context = RequestContext::Image; + break; + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: + case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: + context = RequestContext::Style; + break; + case nsIContentPolicy::TYPE_INTERNAL_OBJECT: + context = RequestContext::Object; + break; + case nsIContentPolicy::TYPE_INTERNAL_EMBED: + context = RequestContext::Embed; + break; + case nsIContentPolicy::TYPE_DOCUMENT: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_INTERNAL_IFRAME: + context = RequestContext::Iframe; + break; + case nsIContentPolicy::TYPE_INTERNAL_FRAME: + context = RequestContext::Frame; + break; + case nsIContentPolicy::TYPE_REFRESH: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_XBL: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_PING: + context = RequestContext::Ping; + break; + case nsIContentPolicy::TYPE_INTERNAL_XMLHTTPREQUEST: + context = RequestContext::Xmlhttprequest; + break; + case nsIContentPolicy::TYPE_INTERNAL_EVENTSOURCE: + context = RequestContext::Eventsource; + break; + case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST: + context = RequestContext::Plugin; + break; + case nsIContentPolicy::TYPE_DTD: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_FONT: + context = RequestContext::Font; + break; + case nsIContentPolicy::TYPE_INTERNAL_AUDIO: + context = RequestContext::Audio; + break; + case nsIContentPolicy::TYPE_INTERNAL_VIDEO: + context = RequestContext::Video; + break; + case nsIContentPolicy::TYPE_INTERNAL_TRACK: + context = RequestContext::Track; + break; + case nsIContentPolicy::TYPE_WEBSOCKET: + context = RequestContext::Internal; + break; + case nsIContentPolicy::TYPE_CSP_REPORT: + context = RequestContext::Cspreport; + break; + case nsIContentPolicy::TYPE_XSLT: + context = RequestContext::Xslt; + break; + case nsIContentPolicy::TYPE_BEACON: + context = RequestContext::Beacon; + break; + case nsIContentPolicy::TYPE_FETCH: + context = RequestContext::Fetch; + break; + case nsIContentPolicy::TYPE_IMAGESET: + context = RequestContext::Imageset; + break; + case nsIContentPolicy::TYPE_WEB_MANIFEST: + context = RequestContext::Manifest; + break; + default: + MOZ_ASSERT(false, "Unhandled nsContentPolicyType value"); + break; + } + return context; +} + +// static +bool +InternalRequest::IsNavigationContentPolicy(nsContentPolicyType aContentPolicyType) +{ + // https://fetch.spec.whatwg.org/#navigation-request-context + // + // A navigation request context is one of "form", "frame", "hyperlink", + // "iframe", "internal" (as long as context frame type is not "none"), + // "location", "metarefresh", and "prerender". + // + // Note, all of these request types are effectively initiated by nsDocShell. + // + // The TYPE_REFRESH is used in some code paths for metarefresh, but will not + // be seen during the actual load. Instead the new load gets a normal + // nsDocShell policy type. We include it here in case this utility method + // is called before the load starts. + return aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT || + aContentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT || + aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME || + aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + aContentPolicyType == nsIContentPolicy::TYPE_REFRESH; +} + +// static +bool +InternalRequest::IsWorkerContentPolicy(nsContentPolicyType aContentPolicyType) +{ + // https://fetch.spec.whatwg.org/#worker-request-context + // + // A worker request context is one of "serviceworker", "sharedworker", and + // "worker". + // + // Note, service workers are not included here because currently there is + // no way to generate a Request with a "serviceworker" RequestContext. + // ServiceWorker scripts cannot be intercepted. + return aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER; +} + +bool +InternalRequest::IsNavigationRequest() const +{ + return IsNavigationContentPolicy(mContentPolicyType); +} + +bool +InternalRequest::IsWorkerRequest() const +{ + return IsWorkerContentPolicy(mContentPolicyType); +} + +bool +InternalRequest::IsClientRequest() const +{ + return IsNavigationRequest() || IsWorkerRequest(); +} + +// static +RequestMode +InternalRequest::MapChannelToRequestMode(nsIChannel* aChannel) +{ + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsILoadInfo> loadInfo; + MOZ_ALWAYS_SUCCEEDS(aChannel->GetLoadInfo(getter_AddRefs(loadInfo))); + + nsContentPolicyType contentPolicy = loadInfo->InternalContentPolicyType(); + if (IsNavigationContentPolicy(contentPolicy)) { + return RequestMode::Navigate; + } + + // TODO: remove the worker override once securityMode is fully implemented (bug 1189945) + if (IsWorkerContentPolicy(contentPolicy)) { + return RequestMode::Same_origin; + } + + uint32_t securityMode = loadInfo->GetSecurityMode(); + + switch(securityMode) { + case nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS: + case nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED: + return RequestMode::Same_origin; + case nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS: + case nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL: + return RequestMode::No_cors; + case nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS: + // TODO: Check additional flag force-preflight after bug 1199693 (bug 1189945) + return RequestMode::Cors; + default: + // TODO: assert never reached after CorsMode flag removed (bug 1189945) + MOZ_ASSERT(securityMode == nsILoadInfo::SEC_NORMAL); + break; + } + + // TODO: remove following code once securityMode is fully implemented (bug 1189945) + + nsCOMPtr<nsIHttpChannelInternal> httpChannel = do_QueryInterface(aChannel); + + uint32_t corsMode; + MOZ_ALWAYS_SUCCEEDS(httpChannel->GetCorsMode(&corsMode)); + MOZ_ASSERT(corsMode != nsIHttpChannelInternal::CORS_MODE_NAVIGATE); + + // This cast is valid due to static asserts in ServiceWorkerManager.cpp. + return static_cast<RequestMode>(corsMode); +} + +// static +RequestCredentials +InternalRequest::MapChannelToRequestCredentials(nsIChannel* aChannel) +{ + MOZ_ASSERT(aChannel); + + nsCOMPtr<nsILoadInfo> loadInfo; + MOZ_ALWAYS_SUCCEEDS(aChannel->GetLoadInfo(getter_AddRefs(loadInfo))); + + + // TODO: Remove following code after stylesheet and image support cookie policy + if (loadInfo->GetSecurityMode() == nsILoadInfo::SEC_NORMAL) { + uint32_t loadFlags; + aChannel->GetLoadFlags(&loadFlags); + + if (loadFlags & nsIRequest::LOAD_ANONYMOUS) { + return RequestCredentials::Omit; + } else { + bool includeCrossOrigin; + nsCOMPtr<nsIHttpChannelInternal> internalChannel = do_QueryInterface(aChannel); + + internalChannel->GetCorsIncludeCredentials(&includeCrossOrigin); + if (includeCrossOrigin) { + return RequestCredentials::Include; + } + } + return RequestCredentials::Same_origin; + } + + uint32_t cookiePolicy = loadInfo->GetCookiePolicy(); + + if (cookiePolicy == nsILoadInfo::SEC_COOKIES_INCLUDE) { + return RequestCredentials::Include; + } else if (cookiePolicy == nsILoadInfo::SEC_COOKIES_OMIT) { + return RequestCredentials::Omit; + } else if (cookiePolicy == nsILoadInfo::SEC_COOKIES_SAME_ORIGIN) { + return RequestCredentials::Same_origin; + } + + MOZ_ASSERT_UNREACHABLE("Unexpected cookie policy!"); + return RequestCredentials::Same_origin; +} + +void +InternalRequest::MaybeSkipCacheIfPerformingRevalidation() +{ + if (mCacheMode == RequestCache::Default && + mHeaders->HasRevalidationHeaders()) { + mCacheMode = RequestCache::No_store; + } +} + +void +InternalRequest::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) +{ + mPrincipalInfo = Move(aPrincipalInfo); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/InternalRequest.h b/dom/fetch/InternalRequest.h new file mode 100644 index 000000000..84ee0bf69 --- /dev/null +++ b/dom/fetch/InternalRequest.h @@ -0,0 +1,536 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_InternalRequest_h +#define mozilla_dom_InternalRequest_h + +#include "mozilla/dom/HeadersBinding.h" +#include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/RequestBinding.h" +#include "mozilla/LoadTainting.h" +#include "mozilla/net/ReferrerPolicy.h" + +#include "nsIContentPolicy.h" +#include "nsIInputStream.h" +#include "nsISupportsImpl.h" +#ifdef DEBUG +#include "nsIURLParser.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#endif + +namespace mozilla { + +namespace ipc { +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +/* + * The mapping of RequestContext and nsContentPolicyType is currently as the + * following. Note that this mapping is not perfect yet (see the TODO comments + * below for examples). + * + * RequestContext | nsContentPolicyType + * ------------------+-------------------- + * audio | TYPE_INTERNAL_AUDIO + * beacon | TYPE_BEACON + * cspreport | TYPE_CSP_REPORT + * download | + * embed | TYPE_INTERNAL_EMBED + * eventsource | + * favicon | + * fetch | TYPE_FETCH + * font | TYPE_FONT + * form | + * frame | TYPE_INTERNAL_FRAME + * hyperlink | + * iframe | TYPE_INTERNAL_IFRAME + * image | TYPE_INTERNAL_IMAGE, TYPE_INTERNAL_IMAGE_PRELOAD, TYPE_INTERNAL_IMAGE_FAVICON + * imageset | TYPE_IMAGESET + * import | Not supported by Gecko + * internal | TYPE_DOCUMENT, TYPE_XBL, TYPE_OTHER + * location | + * manifest | TYPE_WEB_MANIFEST + * object | TYPE_INTERNAL_OBJECT + * ping | TYPE_PING + * plugin | TYPE_OBJECT_SUBREQUEST + * prefetch | + * script | TYPE_INTERNAL_SCRIPT, TYPE_INTERNAL_SCRIPT_PRELOAD + * sharedworker | TYPE_INTERNAL_SHARED_WORKER + * subresource | Not supported by Gecko + * style | TYPE_INTERNAL_STYLESHEET, TYPE_INTERNAL_STYLESHEET_PRELOAD + * track | TYPE_INTERNAL_TRACK + * video | TYPE_INTERNAL_VIDEO + * worker | TYPE_INTERNAL_WORKER + * xmlhttprequest | TYPE_INTERNAL_XMLHTTPREQUEST + * eventsource | TYPE_INTERNAL_EVENTSOURCE + * xslt | TYPE_XSLT + * + * TODO: Figure out if TYPE_REFRESH maps to anything useful + * TODO: Figure out if TYPE_DTD maps to anything useful + * TODO: Figure out if TYPE_WEBSOCKET maps to anything useful + * TODO: Add a content type for prefetch + * TODO: Use the content type for manifest when it becomes available + * TODO: Add a content type for location + * TODO: Add a content type for hyperlink + * TODO: Add a content type for form + * TODO: Add a content type for favicon + * TODO: Add a content type for download + */ + +class Request; +class IPCInternalRequest; + +#define kFETCH_CLIENT_REFERRER_STR "about:client" +class InternalRequest final +{ + friend class Request; +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalRequest) + InternalRequest(const nsACString& aURL, const nsACString& aFragment); + InternalRequest(const nsACString& aURL, + const nsACString& aFragment, + const nsACString& aMethod, + already_AddRefed<InternalHeaders> aHeaders, + RequestCache aCacheMode, + RequestMode aMode, + RequestRedirect aRequestRedirect, + RequestCredentials aRequestCredentials, + const nsAString& aReferrer, + ReferrerPolicy aReferrerPolicy, + nsContentPolicyType aContentPolicyType, + const nsAString& aIntegrity); + + explicit InternalRequest(const IPCInternalRequest& aIPCRequest); + + void ToIPC(IPCInternalRequest* aIPCRequest); + + already_AddRefed<InternalRequest> Clone(); + + void + GetMethod(nsCString& aMethod) const + { + aMethod.Assign(mMethod); + } + + void + SetMethod(const nsACString& aMethod) + { + mMethod.Assign(aMethod); + } + + bool + HasSimpleMethod() const + { + return mMethod.LowerCaseEqualsASCII("get") || + mMethod.LowerCaseEqualsASCII("post") || + mMethod.LowerCaseEqualsASCII("head"); + } + // GetURL should get the request's current url with fragment. A request has + // an associated current url. It is a pointer to the last fetch URL in + // request's url list. + void + GetURL(nsACString& aURL) const + { + aURL.Assign(GetURLWithoutFragment()); + if (GetFragment().IsEmpty()) { + return; + } + aURL.Append(NS_LITERAL_CSTRING("#")); + aURL.Append(GetFragment()); + } + + const nsCString& + GetURLWithoutFragment() const + { + MOZ_RELEASE_ASSERT(!mURLList.IsEmpty(), + "Internal Request's urlList should not be empty."); + + return mURLList.LastElement(); + } + // AddURL should append the url into url list. + // Normally we strip the fragment from the URL in Request::Constructor and + // pass the fragment as the second argument into it. + // If a fragment is present in the URL it must be stripped and passed in + // separately. + void + AddURL(const nsACString& aURL, const nsACString& aFragment) + { + MOZ_ASSERT(!aURL.IsEmpty()); + MOZ_ASSERT(!aURL.Contains('#')); + + mURLList.AppendElement(aURL); + + mFragment.Assign(aFragment); + } + // Get the URL list without their fragments. + void + GetURLListWithoutFragment(nsTArray<nsCString>& aURLList) + { + aURLList.Assign(mURLList); + } + void + GetReferrer(nsAString& aReferrer) const + { + aReferrer.Assign(mReferrer); + } + + void + SetReferrer(const nsAString& aReferrer) + { +#ifdef DEBUG + bool validReferrer = false; + if (aReferrer.IsEmpty() || + aReferrer.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) { + validReferrer = true; + } else { + nsCOMPtr<nsIURLParser> parser = do_GetService(NS_STDURLPARSER_CONTRACTID); + if (!parser) { + NS_WARNING("Could not get parser to validate URL!"); + } else { + uint32_t schemePos; + int32_t schemeLen; + uint32_t authorityPos; + int32_t authorityLen; + uint32_t pathPos; + int32_t pathLen; + + NS_ConvertUTF16toUTF8 ref(aReferrer); + nsresult rv = parser->ParseURL(ref.get(), ref.Length(), + &schemePos, &schemeLen, + &authorityPos, &authorityLen, + &pathPos, &pathLen); + if (NS_FAILED(rv)) { + NS_WARNING("Invalid referrer URL!"); + } else if (schemeLen < 0 || authorityLen < 0) { + NS_WARNING("Invalid referrer URL!"); + } else { + validReferrer = true; + } + } + } + + MOZ_ASSERT(validReferrer); +#endif + + mReferrer.Assign(aReferrer); + } + + ReferrerPolicy + ReferrerPolicy_() const + { + return mReferrerPolicy; + } + + void + SetReferrerPolicy(ReferrerPolicy aReferrerPolicy) + { + mReferrerPolicy = aReferrerPolicy; + } + + net::ReferrerPolicy + GetEnvironmentReferrerPolicy() const + { + return mEnvironmentReferrerPolicy; + } + + void + SetEnvironmentReferrerPolicy(net::ReferrerPolicy aReferrerPolicy) + { + mEnvironmentReferrerPolicy = aReferrerPolicy; + } + + bool + SkipServiceWorker() const + { + return mSkipServiceWorker; + } + + void + SetSkipServiceWorker() + { + mSkipServiceWorker = true; + } + + bool + IsSynchronous() const + { + return mSynchronous; + } + + RequestMode + Mode() const + { + return mMode; + } + + void + SetMode(RequestMode aMode) + { + mMode = aMode; + } + + RequestCredentials + GetCredentialsMode() const + { + return mCredentialsMode; + } + + void + SetCredentialsMode(RequestCredentials aCredentialsMode) + { + mCredentialsMode = aCredentialsMode; + } + + LoadTainting + GetResponseTainting() const + { + return mResponseTainting; + } + + void + MaybeIncreaseResponseTainting(LoadTainting aTainting) + { + if (aTainting > mResponseTainting) { + mResponseTainting = aTainting; + } + } + + RequestCache + GetCacheMode() const + { + return mCacheMode; + } + + void + SetCacheMode(RequestCache aCacheMode) + { + mCacheMode = aCacheMode; + } + + RequestRedirect + GetRedirectMode() const + { + return mRedirectMode; + } + + void + SetRedirectMode(RequestRedirect aRedirectMode) + { + mRedirectMode = aRedirectMode; + } + + const nsString& + GetIntegrity() const + { + return mIntegrity; + } + void + SetIntegrity(const nsAString& aIntegrity) + { + MOZ_ASSERT(mIntegrity.IsEmpty()); + mIntegrity.Assign(aIntegrity); + } + const nsCString& + GetFragment() const + { + return mFragment; + } + + nsContentPolicyType + ContentPolicyType() const + { + return mContentPolicyType; + } + void + SetContentPolicyType(nsContentPolicyType aContentPolicyType); + + void + OverrideContentPolicyType(nsContentPolicyType aContentPolicyType); + + RequestContext + Context() const + { + return MapContentPolicyTypeToRequestContext(mContentPolicyType); + } + + bool + UnsafeRequest() const + { + return mUnsafeRequest; + } + + void + SetUnsafeRequest() + { + mUnsafeRequest = true; + } + + InternalHeaders* + Headers() + { + return mHeaders; + } + + bool + ForceOriginHeader() + { + return mForceOriginHeader; + } + + bool + SameOriginDataURL() const + { + return mSameOriginDataURL; + } + + void + UnsetSameOriginDataURL() + { + mSameOriginDataURL = false; + } + + void + SetBody(nsIInputStream* aStream) + { + // A request's body may not be reset once set. + MOZ_ASSERT_IF(aStream, !mBodyStream); + mBodyStream = aStream; + } + + // Will return the original stream! + // Use a tee or copy if you don't want to erase the original. + void + GetBody(nsIInputStream** aStream) + { + nsCOMPtr<nsIInputStream> s = mBodyStream; + s.forget(aStream); + } + + // The global is used as the client for the new object. + already_AddRefed<InternalRequest> + GetRequestConstructorCopy(nsIGlobalObject* aGlobal, ErrorResult& aRv) const; + + bool + WasCreatedByFetchEvent() const + { + return mCreatedByFetchEvent; + } + + void + SetCreatedByFetchEvent() + { + mCreatedByFetchEvent = true; + } + + void + ClearCreatedByFetchEvent() + { + mCreatedByFetchEvent = false; + } + + bool + IsNavigationRequest() const; + + bool + IsWorkerRequest() const; + + bool + IsClientRequest() const; + + void + MaybeSkipCacheIfPerformingRevalidation(); + + bool + IsContentPolicyTypeOverridden() const + { + return mContentPolicyTypeOverridden; + } + + static RequestMode + MapChannelToRequestMode(nsIChannel* aChannel); + + static RequestCredentials + MapChannelToRequestCredentials(nsIChannel* aChannel); + + // Takes ownership of the principal info. + void + SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo); + + const UniquePtr<mozilla::ipc::PrincipalInfo>& + GetPrincipalInfo() const + { + return mPrincipalInfo; + } + +private: + // Does not copy mBodyStream. Use fallible Clone() for complete copy. + explicit InternalRequest(const InternalRequest& aOther); + + ~InternalRequest(); + + static RequestContext + MapContentPolicyTypeToRequestContext(nsContentPolicyType aContentPolicyType); + + static bool + IsNavigationContentPolicy(nsContentPolicyType aContentPolicyType); + + static bool + IsWorkerContentPolicy(nsContentPolicyType aContentPolicyType); + + nsCString mMethod; + // mURLList: a list of one or more fetch URLs + nsTArray<nsCString> mURLList; + RefPtr<InternalHeaders> mHeaders; + nsCOMPtr<nsIInputStream> mBodyStream; + + nsContentPolicyType mContentPolicyType; + + // Empty string: no-referrer + // "about:client": client (default) + // URL: an URL + nsString mReferrer; + ReferrerPolicy mReferrerPolicy; + + // This will be used for request created from Window or Worker contexts + // In case there's no Referrer Policy in Request, this will be passed to + // channel. + // The Environment Referrer Policy should be net::ReferrerPolicy so that it + // could be associated with nsIHttpChannel. + net::ReferrerPolicy mEnvironmentReferrerPolicy; + RequestMode mMode; + RequestCredentials mCredentialsMode; + MOZ_INIT_OUTSIDE_CTOR LoadTainting mResponseTainting; + RequestCache mCacheMode; + RequestRedirect mRedirectMode; + nsString mIntegrity; + nsCString mFragment; + MOZ_INIT_OUTSIDE_CTOR bool mAuthenticationFlag; + MOZ_INIT_OUTSIDE_CTOR bool mForceOriginHeader; + MOZ_INIT_OUTSIDE_CTOR bool mPreserveContentCodings; + MOZ_INIT_OUTSIDE_CTOR bool mSameOriginDataURL; + MOZ_INIT_OUTSIDE_CTOR bool mSkipServiceWorker; + MOZ_INIT_OUTSIDE_CTOR bool mSynchronous; + MOZ_INIT_OUTSIDE_CTOR bool mUnsafeRequest; + MOZ_INIT_OUTSIDE_CTOR bool mUseURLCredentials; + // This is only set when a Request object is created by a fetch event. We + // use it to check if Service Workers are simply fetching intercepted Request + // objects without modifying them. + bool mCreatedByFetchEvent = false; + // This is only set when Request.overrideContentPolicyType() has been set. + // It is illegal to pass such a Request object to a fetch() method unless + // if the caller has chrome privileges. + bool mContentPolicyTypeOverridden = false; + + UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_InternalRequest_h diff --git a/dom/fetch/InternalResponse.cpp b/dom/fetch/InternalResponse.cpp new file mode 100644 index 000000000..cb7d74597 --- /dev/null +++ b/dom/fetch/InternalResponse.cpp @@ -0,0 +1,261 @@ +/* -*- 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 "InternalResponse.h" + +#include "mozilla/Assertions.h" +#include "mozilla/dom/InternalHeaders.h" +#include "mozilla/dom/cache/CacheTypes.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "nsIURI.h" +#include "nsStreamUtils.h" + +namespace mozilla { +namespace dom { + +InternalResponse::InternalResponse(uint16_t aStatus, const nsACString& aStatusText) + : mType(ResponseType::Default) + , mStatus(aStatus) + , mStatusText(aStatusText) + , mHeaders(new InternalHeaders(HeadersGuardEnum::Response)) + , mBodySize(UNKNOWN_BODY_SIZE) +{ +} + +already_AddRefed<InternalResponse> +InternalResponse::FromIPC(const IPCInternalResponse& aIPCResponse) +{ + if (aIPCResponse.type() == ResponseType::Error) { + return InternalResponse::NetworkError(); + } + + RefPtr<InternalResponse> response = + new InternalResponse(aIPCResponse.status(), + aIPCResponse.statusText()); + + response->SetURLList(aIPCResponse.urlList()); + + response->mHeaders = new InternalHeaders(aIPCResponse.headers(), + aIPCResponse.headersGuard()); + + response->InitChannelInfo(aIPCResponse.channelInfo()); + if (aIPCResponse.principalInfo().type() == mozilla::ipc::OptionalPrincipalInfo::TPrincipalInfo) { + UniquePtr<mozilla::ipc::PrincipalInfo> info(new mozilla::ipc::PrincipalInfo(aIPCResponse.principalInfo().get_PrincipalInfo())); + response->SetPrincipalInfo(Move(info)); + } + + nsCOMPtr<nsIInputStream> stream = DeserializeIPCStream(aIPCResponse.body()); + response->SetBody(stream, aIPCResponse.bodySize()); + + switch (aIPCResponse.type()) + { + case ResponseType::Basic: + response = response->BasicResponse(); + break; + case ResponseType::Cors: + response = response->CORSResponse(); + break; + case ResponseType::Default: + break; + case ResponseType::Opaque: + response = response->OpaqueResponse(); + break; + case ResponseType::Opaqueredirect: + response = response->OpaqueRedirectResponse(); + break; + default: + MOZ_CRASH("Unexpected ResponseType!"); + } + MOZ_ASSERT(response); + + return response.forget(); +} + +InternalResponse::~InternalResponse() +{ +} + +template void +InternalResponse::ToIPC<PContentParent> + (IPCInternalResponse* aIPCResponse, + PContentParent* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<nsIContentChild> + (IPCInternalResponse* aIPCResponse, + nsIContentChild* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<mozilla::ipc::PBackgroundParent> + (IPCInternalResponse* aIPCResponse, + mozilla::ipc::PBackgroundParent* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); +template void +InternalResponse::ToIPC<mozilla::ipc::PBackgroundChild> + (IPCInternalResponse* aIPCResponse, + mozilla::ipc::PBackgroundChild* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); + +template<typename M> +void +InternalResponse::ToIPC(IPCInternalResponse* aIPCResponse, + M* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream) +{ + MOZ_ASSERT(aIPCResponse); + aIPCResponse->type() = mType; + aIPCResponse->urlList() = mURLList; + aIPCResponse->status() = GetUnfilteredStatus(); + aIPCResponse->statusText() = GetUnfilteredStatusText(); + + mHeaders->ToIPC(aIPCResponse->headers(), aIPCResponse->headersGuard()); + + aIPCResponse->channelInfo() = mChannelInfo.AsIPCChannelInfo(); + if (mPrincipalInfo) { + aIPCResponse->principalInfo() = *mPrincipalInfo; + } else { + aIPCResponse->principalInfo() = void_t(); + } + + nsCOMPtr<nsIInputStream> body; + int64_t bodySize; + GetUnfilteredBody(getter_AddRefs(body), &bodySize); + + if (body) { + aAutoStream.reset(new mozilla::ipc::AutoIPCStream(aIPCResponse->body())); + aAutoStream->Serialize(body, aManager); + } else { + aIPCResponse->body() = void_t(); + } + + aIPCResponse->bodySize() = bodySize; +} + +already_AddRefed<InternalResponse> +InternalResponse::Clone() +{ + RefPtr<InternalResponse> clone = CreateIncompleteCopy(); + + clone->mHeaders = new InternalHeaders(*mHeaders); + if (mWrappedResponse) { + clone->mWrappedResponse = mWrappedResponse->Clone(); + MOZ_ASSERT(!mBody); + return clone.forget(); + } + + if (!mBody) { + return clone.forget(); + } + + nsCOMPtr<nsIInputStream> clonedBody; + nsCOMPtr<nsIInputStream> replacementBody; + + nsresult rv = NS_CloneInputStream(mBody, getter_AddRefs(clonedBody), + getter_AddRefs(replacementBody)); + if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } + + clone->mBody.swap(clonedBody); + if (replacementBody) { + mBody.swap(replacementBody); + } + + return clone.forget(); +} + +already_AddRefed<InternalResponse> +InternalResponse::BasicResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't BasicResponse a already wrapped response"); + RefPtr<InternalResponse> basic = CreateIncompleteCopy(); + basic->mType = ResponseType::Basic; + basic->mHeaders = InternalHeaders::BasicHeaders(Headers()); + basic->mWrappedResponse = this; + return basic.forget(); +} + +already_AddRefed<InternalResponse> +InternalResponse::CORSResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't CORSResponse a already wrapped response"); + RefPtr<InternalResponse> cors = CreateIncompleteCopy(); + cors->mType = ResponseType::Cors; + cors->mHeaders = InternalHeaders::CORSHeaders(Headers()); + cors->mWrappedResponse = this; + return cors.forget(); +} + +void +InternalResponse::SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo) +{ + mPrincipalInfo = Move(aPrincipalInfo); +} + +LoadTainting +InternalResponse::GetTainting() const +{ + switch (mType) { + case ResponseType::Cors: + return LoadTainting::CORS; + case ResponseType::Opaque: + return LoadTainting::Opaque; + default: + return LoadTainting::Basic; + } +} + +already_AddRefed<InternalResponse> +InternalResponse::Unfiltered() +{ + RefPtr<InternalResponse> ref = mWrappedResponse; + if (!ref) { + ref = this; + } + return ref.forget(); +} + +already_AddRefed<InternalResponse> +InternalResponse::OpaqueResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueResponse a already wrapped response"); + RefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString()); + response->mType = ResponseType::Opaque; + response->mTerminationReason = mTerminationReason; + response->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + response->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo); + } + response->mWrappedResponse = this; + return response.forget(); +} + +already_AddRefed<InternalResponse> +InternalResponse::OpaqueRedirectResponse() +{ + MOZ_ASSERT(!mWrappedResponse, "Can't OpaqueRedirectResponse a already wrapped response"); + MOZ_ASSERT(!mURLList.IsEmpty(), "URLList should not be emtpy for internalResponse"); + RefPtr<InternalResponse> response = OpaqueResponse(); + response->mType = ResponseType::Opaqueredirect; + response->mURLList = mURLList; + return response.forget(); +} + +already_AddRefed<InternalResponse> +InternalResponse::CreateIncompleteCopy() +{ + RefPtr<InternalResponse> copy = new InternalResponse(mStatus, mStatusText); + copy->mType = mType; + copy->mTerminationReason = mTerminationReason; + copy->mURLList = mURLList; + copy->mChannelInfo = mChannelInfo; + if (mPrincipalInfo) { + copy->mPrincipalInfo = MakeUnique<mozilla::ipc::PrincipalInfo>(*mPrincipalInfo); + } + return copy.forget(); +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/InternalResponse.h b/dom/fetch/InternalResponse.h new file mode 100644 index 000000000..35467836a --- /dev/null +++ b/dom/fetch/InternalResponse.h @@ -0,0 +1,313 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_InternalResponse_h +#define mozilla_dom_InternalResponse_h + +#include "nsIInputStream.h" +#include "nsISupportsImpl.h" + +#include "mozilla/dom/ResponseBinding.h" +#include "mozilla/dom/ChannelInfo.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace ipc { +class PrincipalInfo; +class AutoIPCStream; +} // namespace ipc + +namespace dom { + +class InternalHeaders; +class IPCInternalResponse; + +class InternalResponse final +{ + friend class FetchDriver; + +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalResponse) + + InternalResponse(uint16_t aStatus, const nsACString& aStatusText); + + static already_AddRefed<InternalResponse> + FromIPC(const IPCInternalResponse& aIPCResponse); + + template<typename M> + void + ToIPC(IPCInternalResponse* aIPCResponse, + M* aManager, + UniquePtr<mozilla::ipc::AutoIPCStream>& aAutoStream); + + already_AddRefed<InternalResponse> Clone(); + + static already_AddRefed<InternalResponse> + NetworkError() + { + RefPtr<InternalResponse> response = new InternalResponse(0, EmptyCString()); + ErrorResult result; + response->Headers()->SetGuard(HeadersGuardEnum::Immutable, result); + MOZ_ASSERT(!result.Failed()); + response->mType = ResponseType::Error; + return response.forget(); + } + + already_AddRefed<InternalResponse> + OpaqueResponse(); + + already_AddRefed<InternalResponse> + OpaqueRedirectResponse(); + + already_AddRefed<InternalResponse> + BasicResponse(); + + already_AddRefed<InternalResponse> + CORSResponse(); + + ResponseType + Type() const + { + MOZ_ASSERT_IF(mType == ResponseType::Error, !mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Default, !mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Basic, mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Cors, mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Opaque, mWrappedResponse); + MOZ_ASSERT_IF(mType == ResponseType::Opaqueredirect, mWrappedResponse); + return mType; + } + + bool + IsError() const + { + return Type() == ResponseType::Error; + } + // GetUrl should return last fetch URL in response's url list and null if + // response's url list is the empty list. + const nsCString& + GetURL() const + { + // Empty urlList when response is a synthetic response. + if (mURLList.IsEmpty()) { + return EmptyCString(); + } + return mURLList.LastElement(); + } + void + GetURLList(nsTArray<nsCString>& aURLList) const + { + aURLList.Assign(mURLList); + } + const nsCString& + GetUnfilteredURL() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetURL(); + } + return GetURL(); + } + void + GetUnfilteredURLList(nsTArray<nsCString>& aURLList) const + { + if (mWrappedResponse) { + return mWrappedResponse->GetURLList(aURLList); + } + + return GetURLList(aURLList); + } + + void + SetURLList(const nsTArray<nsCString>& aURLList) + { + mURLList.Assign(aURLList); + +#ifdef DEBUG + for(uint32_t i = 0; i < mURLList.Length(); ++i) { + MOZ_ASSERT(mURLList[i].Find(NS_LITERAL_CSTRING("#")) == kNotFound); + } +#endif + } + + uint16_t + GetStatus() const + { + return mStatus; + } + + uint16_t + GetUnfilteredStatus() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatus(); + } + + return GetStatus(); + } + + const nsCString& + GetStatusText() const + { + return mStatusText; + } + + const nsCString& + GetUnfilteredStatusText() const + { + if (mWrappedResponse) { + return mWrappedResponse->GetStatusText(); + } + + return GetStatusText(); + } + + InternalHeaders* + Headers() + { + return mHeaders; + } + + InternalHeaders* + UnfilteredHeaders() + { + if (mWrappedResponse) { + return mWrappedResponse->Headers(); + }; + + return Headers(); + } + + void + GetUnfilteredBody(nsIInputStream** aStream, int64_t* aBodySize = nullptr) + { + if (mWrappedResponse) { + MOZ_ASSERT(!mBody); + return mWrappedResponse->GetBody(aStream, aBodySize); + } + nsCOMPtr<nsIInputStream> stream = mBody; + stream.forget(aStream); + if (aBodySize) { + *aBodySize = mBodySize; + } + } + + void + GetBody(nsIInputStream** aStream, int64_t* aBodySize = nullptr) + { + if (Type() == ResponseType::Opaque || + Type() == ResponseType::Opaqueredirect) { + *aStream = nullptr; + if (aBodySize) { + *aBodySize = UNKNOWN_BODY_SIZE; + } + return; + } + + return GetUnfilteredBody(aStream, aBodySize); + } + + void + SetBody(nsIInputStream* aBody, int64_t aBodySize) + { + if (mWrappedResponse) { + return mWrappedResponse->SetBody(aBody, aBodySize); + } + // A request's body may not be reset once set. + MOZ_ASSERT(!mBody); + MOZ_ASSERT(mBodySize == UNKNOWN_BODY_SIZE); + // Check arguments. + MOZ_ASSERT(aBodySize == UNKNOWN_BODY_SIZE || aBodySize >= 0); + // If body is not given, then size must be unknown. + MOZ_ASSERT_IF(!aBody, aBodySize == UNKNOWN_BODY_SIZE); + + mBody = aBody; + mBodySize = aBodySize; + } + + void + InitChannelInfo(nsIChannel* aChannel) + { + mChannelInfo.InitFromChannel(aChannel); + } + + void + InitChannelInfo(const mozilla::ipc::IPCChannelInfo& aChannelInfo) + { + mChannelInfo.InitFromIPCChannelInfo(aChannelInfo); + } + + void + InitChannelInfo(const ChannelInfo& aChannelInfo) + { + mChannelInfo = aChannelInfo; + } + + const ChannelInfo& + GetChannelInfo() const + { + return mChannelInfo; + } + + const UniquePtr<mozilla::ipc::PrincipalInfo>& + GetPrincipalInfo() const + { + return mPrincipalInfo; + } + + bool + IsRedirected() const + { + return mURLList.Length() > 1; + } + + // Takes ownership of the principal info. + void + SetPrincipalInfo(UniquePtr<mozilla::ipc::PrincipalInfo> aPrincipalInfo); + + LoadTainting + GetTainting() const; + + already_AddRefed<InternalResponse> + Unfiltered(); + +private: + ~InternalResponse(); + + explicit InternalResponse(const InternalResponse& aOther) = delete; + InternalResponse& operator=(const InternalResponse&) = delete; + + // Returns an instance of InternalResponse which is a copy of this + // InternalResponse, except headers, body and wrapped response (if any) which + // are left uninitialized. Used for cloning and filtering. + already_AddRefed<InternalResponse> CreateIncompleteCopy(); + + ResponseType mType; + nsCString mTerminationReason; + // A response has an associated url list (a list of zero or more fetch URLs). + // Unless stated otherwise, it is the empty list. The current url is the last + // element in mURLlist + nsTArray<nsCString> mURLList; + const uint16_t mStatus; + const nsCString mStatusText; + RefPtr<InternalHeaders> mHeaders; + nsCOMPtr<nsIInputStream> mBody; + int64_t mBodySize; +public: + static const int64_t UNKNOWN_BODY_SIZE = -1; +private: + ChannelInfo mChannelInfo; + UniquePtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo; + + // For filtered responses. + // Cache, and SW interception should always serialize/access the underlying + // unfiltered headers and when deserializing, create an InternalResponse + // with the unfiltered headers followed by wrapping it. + RefPtr<InternalResponse> mWrappedResponse; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_InternalResponse_h diff --git a/dom/fetch/Request.cpp b/dom/fetch/Request.cpp new file mode 100644 index 000000000..bc17afae3 --- /dev/null +++ b/dom/fetch/Request.cpp @@ -0,0 +1,627 @@ +/* -*- 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 "Request.h" + +#include "nsIURI.h" +#include "nsPIDOMWindow.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/Headers.h" +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FetchUtil.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/URL.h" +#include "mozilla/dom/WorkerPrivate.h" +#include "mozilla/Unused.h" + +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Request) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Request) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Request::Request(nsIGlobalObject* aOwner, InternalRequest* aRequest) + : FetchBody<Request>() + , mOwner(aOwner) + , mRequest(aRequest) +{ + MOZ_ASSERT(aRequest->Headers()->Guard() == HeadersGuardEnum::Immutable || + aRequest->Headers()->Guard() == HeadersGuardEnum::Request || + aRequest->Headers()->Guard() == HeadersGuardEnum::Request_no_cors); + SetMimeType(); +} + +Request::~Request() +{ +} + +// static +bool +Request::RequestContextEnabled(JSContext* aCx, JSObject* aObj) +{ + if (NS_IsMainThread()) { + return Preferences::GetBool("dom.requestcontext.enabled", false); + } + + using namespace workers; + + // Otherwise, check the pref via the WorkerPrivate + WorkerPrivate* workerPrivate = GetWorkerPrivateFromContext(aCx); + if (!workerPrivate) { + return false; + } + + return workerPrivate->RequestContextEnabled(); +} + +already_AddRefed<InternalRequest> +Request::GetInternalRequest() +{ + RefPtr<InternalRequest> r = mRequest; + return r.forget(); +} + +namespace { +already_AddRefed<nsIURI> +ParseURLFromDocument(nsIDocument* aDocument, const nsAString& aInput, + ErrorResult& aRv) +{ + MOZ_ASSERT(aDocument); + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr<nsIURI> baseURI = aDocument->GetBaseURI(); + nsCOMPtr<nsIURI> resolvedURI; + aRv = NS_NewURI(getter_AddRefs(resolvedURI), aInput, nullptr, baseURI); + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_URL>(aInput); + } + return resolvedURI.forget(); +} +void +GetRequestURLFromDocument(nsIDocument* aDocument, const nsAString& aInput, + nsAString& aRequestURL, nsACString& aURLfragment, + ErrorResult& aRv) +{ + nsCOMPtr<nsIURI> resolvedURI = ParseURLFromDocument(aDocument, aInput, aRv); + if (aRv.Failed()) { + return; + } + // This fails with URIs with weird protocols, even when they are valid, + // so we ignore the failure + nsAutoCString credentials; + Unused << resolvedURI->GetUserPass(credentials); + if (!credentials.IsEmpty()) { + aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput); + return; + } + + nsCOMPtr<nsIURI> resolvedURIClone; + // We use CloneIgnoringRef to strip away the fragment even if the original URI + // is immutable. + aRv = resolvedURI->CloneIgnoringRef(getter_AddRefs(resolvedURIClone)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + nsAutoCString spec; + aRv = resolvedURIClone->GetSpec(spec); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + CopyUTF8toUTF16(spec, aRequestURL); + + // Get the fragment from nsIURI. + aRv = resolvedURI->GetRef(aURLfragment); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} +already_AddRefed<nsIURI> +ParseURLFromChrome(const nsAString& aInput, ErrorResult& aRv) +{ + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr<nsIURI> uri; + aRv = NS_NewURI(getter_AddRefs(uri), aInput, nullptr, nullptr); + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_URL>(aInput); + } + return uri.forget(); +} +void +GetRequestURLFromChrome(const nsAString& aInput, nsAString& aRequestURL, + nsACString& aURLfragment, ErrorResult& aRv) +{ + nsCOMPtr<nsIURI> uri = ParseURLFromChrome(aInput, aRv); + if (aRv.Failed()) { + return; + } + // This fails with URIs with weird protocols, even when they are valid, + // so we ignore the failure + nsAutoCString credentials; + Unused << uri->GetUserPass(credentials); + if (!credentials.IsEmpty()) { + aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput); + return; + } + + nsCOMPtr<nsIURI> uriClone; + // We use CloneIgnoringRef to strip away the fragment even if the original URI + // is immutable. + aRv = uri->CloneIgnoringRef(getter_AddRefs(uriClone)); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + nsAutoCString spec; + aRv = uriClone->GetSpec(spec); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + CopyUTF8toUTF16(spec, aRequestURL); + + // Get the fragment from nsIURI. + aRv = uri->GetRef(aURLfragment); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} +already_AddRefed<URL> +ParseURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput, + ErrorResult& aRv) +{ + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + + NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref); + RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aInput, baseURL, aRv); + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_URL>(aInput); + } + return url.forget(); +} +void +GetRequestURLFromWorker(const GlobalObject& aGlobal, const nsAString& aInput, + nsAString& aRequestURL, nsACString& aURLfragment, + ErrorResult& aRv) +{ + RefPtr<URL> url = ParseURLFromWorker(aGlobal, aInput, aRv); + if (aRv.Failed()) { + return; + } + nsString username; + url->GetUsername(username, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + nsString password; + url->GetPassword(password, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + if (!username.IsEmpty() || !password.IsEmpty()) { + aRv.ThrowTypeError<MSG_URL_HAS_CREDENTIALS>(aInput); + return; + } + // Get the fragment from URL. + nsAutoString fragment; + url->GetHash(fragment, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + + // Note: URL::GetHash() includes the "#" and we want the fragment with out + // the hash symbol. + if (!fragment.IsEmpty()) { + CopyUTF16toUTF8(Substring(fragment, 1), aURLfragment); + } + + url->SetHash(EmptyString(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } + url->Stringify(aRequestURL, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return; + } +} + +class ReferrerSameOriginChecker final : public workers::WorkerMainThreadRunnable +{ +public: + ReferrerSameOriginChecker(workers::WorkerPrivate* aWorkerPrivate, + const nsAString& aReferrerURL, + nsresult& aResult) + : workers::WorkerMainThreadRunnable(aWorkerPrivate, + NS_LITERAL_CSTRING("Fetch :: Referrer same origin check")), + mReferrerURL(aReferrerURL), + mResult(aResult) + { + mWorkerPrivate->AssertIsOnWorkerThread(); + } + + bool + MainThreadRun() override + { + nsCOMPtr<nsIURI> uri; + if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(uri), mReferrerURL))) { + nsCOMPtr<nsIPrincipal> principal = mWorkerPrivate->GetPrincipal(); + if (principal) { + mResult = principal->CheckMayLoad(uri, /* report */ false, + /* allowIfInheritsPrincipal */ false); + } + } + return true; + } + +private: + const nsString mReferrerURL; + nsresult& mResult; +}; + +} // namespace + +/*static*/ already_AddRefed<Request> +Request::Constructor(const GlobalObject& aGlobal, + const RequestOrUSVString& aInput, + const RequestInit& aInit, ErrorResult& aRv) +{ + nsCOMPtr<nsIInputStream> temporaryBody; + RefPtr<InternalRequest> request; + + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + + if (aInput.IsRequest()) { + RefPtr<Request> inputReq = &aInput.GetAsRequest(); + nsCOMPtr<nsIInputStream> body; + inputReq->GetBody(getter_AddRefs(body)); + if (inputReq->BodyUsed()) { + aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); + return nullptr; + } + if (body) { + temporaryBody = body; + } + + request = inputReq->GetInternalRequest(); + } else { + // aInput is USVString. + // We need to get url before we create a InternalRequest. + nsAutoString input; + input.Assign(aInput.GetAsUSVString()); + nsAutoString requestURL; + nsCString fragment; + if (NS_IsMainThread()) { + nsIDocument* doc = GetEntryDocument(); + if (doc) { + GetRequestURLFromDocument(doc, input, requestURL, fragment, aRv); + } else { + // If we don't have a document, we must assume that this is a full URL. + GetRequestURLFromChrome(input, requestURL, fragment, aRv); + } + } else { + GetRequestURLFromWorker(aGlobal, input, requestURL, fragment, aRv); + } + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + request = new InternalRequest(NS_ConvertUTF16toUTF8(requestURL), fragment); + } + request = request->GetRequestConstructorCopy(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + RequestMode fallbackMode = RequestMode::EndGuard_; + RequestCredentials fallbackCredentials = RequestCredentials::EndGuard_; + RequestCache fallbackCache = RequestCache::EndGuard_; + if (aInput.IsUSVString()) { + fallbackMode = RequestMode::Cors; + fallbackCredentials = RequestCredentials::Omit; + fallbackCache = RequestCache::Default; + } + + RequestMode mode = aInit.mMode.WasPassed() ? aInit.mMode.Value() : fallbackMode; + RequestCredentials credentials = + aInit.mCredentials.WasPassed() ? aInit.mCredentials.Value() + : fallbackCredentials; + + if (mode == RequestMode::Navigate || + (aInit.IsAnyMemberPresent() && request->Mode() == RequestMode::Navigate)) { + aRv.ThrowTypeError<MSG_INVALID_REQUEST_MODE>(NS_LITERAL_STRING("navigate")); + return nullptr; + } + + if (aInit.IsAnyMemberPresent()) { + request->SetReferrer(NS_LITERAL_STRING(kFETCH_CLIENT_REFERRER_STR)); + request->SetReferrerPolicy(ReferrerPolicy::_empty); + } + if (aInit.mReferrer.WasPassed()) { + const nsString& referrer = aInit.mReferrer.Value(); + if (referrer.IsEmpty()) { + request->SetReferrer(NS_LITERAL_STRING("")); + } else { + nsAutoString referrerURL; + if (NS_IsMainThread()) { + nsIDocument* doc = GetEntryDocument(); + nsCOMPtr<nsIURI> uri; + if (doc) { + uri = ParseURLFromDocument(doc, referrer, aRv); + } else { + // If we don't have a document, we must assume that this is a full URL. + uri = ParseURLFromChrome(referrer, aRv); + } + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer); + return nullptr; + } + nsAutoCString spec; + uri->GetSpec(spec); + CopyUTF8toUTF16(spec, referrerURL); + if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) { + nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull(); + if (principal) { + nsresult rv = principal->CheckMayLoad(uri, /* report */ false, + /* allowIfInheritsPrincipal */ false); + if (NS_FAILED(rv)) { + nsAutoCString globalOrigin; + principal->GetOrigin(globalOrigin); + aRv.ThrowTypeError<MSG_CROSS_ORIGIN_REFERRER_URL>(referrer, + NS_ConvertUTF8toUTF16(globalOrigin)); + return nullptr; + } + } + } + } else { + RefPtr<URL> url = ParseURLFromWorker(aGlobal, referrer, aRv); + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer); + return nullptr; + } + url->Stringify(referrerURL, aRv); + if (NS_WARN_IF(aRv.Failed())) { + aRv.ThrowTypeError<MSG_INVALID_REFERRER_URL>(referrer); + return nullptr; + } + if (!referrerURL.EqualsLiteral(kFETCH_CLIENT_REFERRER_STR)) { + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + nsresult rv = NS_OK; + // ReferrerSameOriginChecker uses a sync loop to get the main thread + // to perform the same-origin check. Overall, on Workers this method + // can create 3 sync loops (two for constructing URLs and one here) so + // in the future we may want to optimize it all by off-loading all of + // this work in a single sync loop. + RefPtr<ReferrerSameOriginChecker> checker = + new ReferrerSameOriginChecker(worker, referrerURL, rv); + checker->Dispatch(aRv); + if (aRv.Failed() || NS_FAILED(rv)) { + aRv.ThrowTypeError<MSG_CROSS_ORIGIN_REFERRER_URL>(referrer, + worker->GetLocationInfo().mOrigin); + return nullptr; + } + } + } + request->SetReferrer(referrerURL); + } + } + + if (aInit.mReferrerPolicy.WasPassed()) { + request->SetReferrerPolicy(aInit.mReferrerPolicy.Value()); + } + + if (NS_IsMainThread()) { + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); + if (window) { + nsCOMPtr<nsIDocument> doc; + doc = window->GetExtantDoc(); + if (doc) { + request->SetEnvironmentReferrerPolicy(doc->GetReferrerPolicy()); + } + } + } else { + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + if (worker) { + worker->AssertIsOnWorkerThread(); + request->SetEnvironmentReferrerPolicy(worker->GetReferrerPolicy()); + } + } + + if (mode != RequestMode::EndGuard_) { + request->ClearCreatedByFetchEvent(); + request->SetMode(mode); + } + + if (credentials != RequestCredentials::EndGuard_) { + request->ClearCreatedByFetchEvent(); + request->SetCredentialsMode(credentials); + } + + RequestCache cache = aInit.mCache.WasPassed() ? + aInit.mCache.Value() : fallbackCache; + if (cache != RequestCache::EndGuard_) { + if (cache == RequestCache::Only_if_cached && + request->Mode() != RequestMode::Same_origin) { + uint32_t t = static_cast<uint32_t>(request->Mode()); + NS_ConvertASCIItoUTF16 modeString(RequestModeValues::strings[t].value, + RequestModeValues::strings[t].length); + aRv.ThrowTypeError<MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN>(modeString); + return nullptr; + } + request->ClearCreatedByFetchEvent(); + request->SetCacheMode(cache); + } + + if (aInit.mRedirect.WasPassed()) { + request->SetRedirectMode(aInit.mRedirect.Value()); + } + + if (aInit.mIntegrity.WasPassed()) { + request->SetIntegrity(aInit.mIntegrity.Value()); + } + + // Request constructor step 14. + if (aInit.mMethod.WasPassed()) { + nsAutoCString method(aInit.mMethod.Value()); + + // Step 14.1. Disallow forbidden methods, and anything that is not a HTTP + // token, since HTTP states that Method may be any of the defined values or + // a token (extension method). + nsAutoCString outMethod; + nsresult rv = FetchUtil::GetValidRequestMethod(method, outMethod); + if (NS_FAILED(rv)) { + NS_ConvertUTF8toUTF16 label(method); + aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label); + return nullptr; + } + + // Step 14.2 + request->ClearCreatedByFetchEvent(); + request->SetMethod(outMethod); + } + + RefPtr<InternalHeaders> requestHeaders = request->Headers(); + + RefPtr<InternalHeaders> headers; + if (aInit.mHeaders.WasPassed()) { + RefPtr<Headers> h = Headers::Constructor(aGlobal, aInit.mHeaders.Value(), aRv); + if (aRv.Failed()) { + return nullptr; + } + request->ClearCreatedByFetchEvent(); + headers = h->GetInternalHeaders(); + } else { + headers = new InternalHeaders(*requestHeaders); + } + + requestHeaders->Clear(); + // From "Let r be a new Request object associated with request and a new + // Headers object whose guard is "request"." + requestHeaders->SetGuard(HeadersGuardEnum::Request, aRv); + MOZ_ASSERT(!aRv.Failed()); + + if (request->Mode() == RequestMode::No_cors) { + if (!request->HasSimpleMethod()) { + nsAutoCString method; + request->GetMethod(method); + NS_ConvertUTF8toUTF16 label(method); + aRv.ThrowTypeError<MSG_INVALID_REQUEST_METHOD>(label); + return nullptr; + } + + if (!request->GetIntegrity().IsEmpty()) { + aRv.ThrowTypeError<MSG_REQUEST_INTEGRITY_METADATA_NOT_EMPTY>(); + return nullptr; + } + + requestHeaders->SetGuard(HeadersGuardEnum::Request_no_cors, aRv); + if (aRv.Failed()) { + return nullptr; + } + } + + requestHeaders->Fill(*headers, aRv); + if (aRv.Failed()) { + return nullptr; + } + + if ((aInit.mBody.WasPassed() && !aInit.mBody.Value().IsNull()) || + temporaryBody) { + // HEAD and GET are not allowed to have a body. + nsAutoCString method; + request->GetMethod(method); + // method is guaranteed to be uppercase due to step 14.2 above. + if (method.EqualsLiteral("HEAD") || method.EqualsLiteral("GET")) { + aRv.ThrowTypeError<MSG_NO_BODY_ALLOWED_FOR_GET_AND_HEAD>(); + return nullptr; + } + } + + if (aInit.mBody.WasPassed()) { + const Nullable<OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& bodyInitNullable = + aInit.mBody.Value(); + if (!bodyInitNullable.IsNull()) { + const OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams& bodyInit = + bodyInitNullable.Value(); + nsCOMPtr<nsIInputStream> stream; + nsAutoCString contentType; + uint64_t contentLengthUnused; + aRv = ExtractByteStreamFromBody(bodyInit, + getter_AddRefs(stream), + contentType, + contentLengthUnused); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + temporaryBody = stream; + + if (!contentType.IsVoid() && + !requestHeaders->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) { + requestHeaders->Append(NS_LITERAL_CSTRING("Content-Type"), + contentType, aRv); + } + + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + request->ClearCreatedByFetchEvent(); + request->SetBody(temporaryBody); + } + } + + RefPtr<Request> domRequest = new Request(global, request); + domRequest->SetMimeType(); + + if (aInput.IsRequest()) { + RefPtr<Request> inputReq = &aInput.GetAsRequest(); + nsCOMPtr<nsIInputStream> body; + inputReq->GetBody(getter_AddRefs(body)); + if (body) { + inputReq->SetBody(nullptr); + inputReq->SetBodyUsed(); + } + } + return domRequest.forget(); +} + +already_AddRefed<Request> +Request::Clone(ErrorResult& aRv) const +{ + if (BodyUsed()) { + aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); + return nullptr; + } + + RefPtr<InternalRequest> ir = mRequest->Clone(); + if (!ir) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Request> request = new Request(mOwner, ir); + return request.forget(); +} + +Headers* +Request::Headers_() +{ + if (!mHeaders) { + mHeaders = new Headers(mOwner, mRequest->Headers()); + } + + return mHeaders; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/Request.h b/dom/fetch/Request.h new file mode 100644 index 000000000..d33c74812 --- /dev/null +++ b/dom/fetch/Request.h @@ -0,0 +1,167 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_Request_h +#define mozilla_dom_Request_h + +#include "nsIContentPolicy.h" +#include "nsISupportsImpl.h" +#include "nsWrapperCache.h" + +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/InternalRequest.h" +// Required here due to certain WebIDL enums/classes being declared in both +// files. +#include "mozilla/dom/RequestBinding.h" + +namespace mozilla { +namespace dom { + +class Headers; +class InternalHeaders; +class RequestOrUSVString; + +class Request final : public nsISupports + , public FetchBody<Request> + , public nsWrapperCache +{ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request) + +public: + Request(nsIGlobalObject* aOwner, InternalRequest* aRequest); + + static bool + RequestContextEnabled(JSContext* aCx, JSObject* aObj); + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return RequestBinding::Wrap(aCx, this, aGivenProto); + } + + void + GetUrl(nsAString& aUrl) const + { + nsAutoCString url; + mRequest->GetURL(url); + CopyUTF8toUTF16(url, aUrl); + } + + void + GetMethod(nsCString& aMethod) const + { + aMethod = mRequest->mMethod; + } + + RequestMode + Mode() const + { + return mRequest->mMode; + } + + RequestCredentials + Credentials() const + { + return mRequest->mCredentialsMode; + } + + RequestCache + Cache() const + { + return mRequest->GetCacheMode(); + } + + RequestRedirect + Redirect() const + { + return mRequest->GetRedirectMode(); + } + + void + GetIntegrity(nsAString& aIntegrity) const + { + aIntegrity = mRequest->GetIntegrity(); + } + + RequestContext + Context() const + { + return mRequest->Context(); + } + + void + OverrideContentPolicyType(nsContentPolicyType aContentPolicyType) + { + mRequest->OverrideContentPolicyType(aContentPolicyType); + } + + bool + IsContentPolicyTypeOverridden() const + { + return mRequest->IsContentPolicyTypeOverridden(); + } + + void + GetReferrer(nsAString& aReferrer) const + { + mRequest->GetReferrer(aReferrer); + } + + ReferrerPolicy + ReferrerPolicy_() const + { + return mRequest->ReferrerPolicy_(); + } + + InternalHeaders* + GetInternalHeaders() const + { + return mRequest->Headers(); + } + + Headers* Headers_(); + + void + GetBody(nsIInputStream** aStream) { return mRequest->GetBody(aStream); } + + void + SetBody(nsIInputStream* aStream) { return mRequest->SetBody(aStream); } + + static already_AddRefed<Request> + Constructor(const GlobalObject& aGlobal, const RequestOrUSVString& aInput, + const RequestInit& aInit, ErrorResult& rv); + + nsIGlobalObject* GetParentObject() const + { + return mOwner; + } + + already_AddRefed<Request> + Clone(ErrorResult& aRv) const; + + already_AddRefed<InternalRequest> + GetInternalRequest(); + + const UniquePtr<mozilla::ipc::PrincipalInfo>& + GetPrincipalInfo() const + { + return mRequest->GetPrincipalInfo(); + } + +private: + ~Request(); + + nsCOMPtr<nsIGlobalObject> mOwner; + RefPtr<InternalRequest> mRequest; + // Lazily created. + RefPtr<Headers> mHeaders; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Request_h diff --git a/dom/fetch/Response.cpp b/dom/fetch/Response.cpp new file mode 100644 index 000000000..a76071bf8 --- /dev/null +++ b/dom/fetch/Response.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "Response.h" + +#include "nsISupportsImpl.h" +#include "nsIURI.h" +#include "nsPIDOMWindow.h" + +#include "mozilla/ErrorResult.h" +#include "mozilla/dom/FetchBinding.h" +#include "mozilla/dom/Headers.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/URL.h" + +#include "nsDOMString.h" + +#include "InternalResponse.h" +#include "WorkerPrivate.h" + +namespace mozilla { +namespace dom { + +NS_IMPL_CYCLE_COLLECTING_ADDREF(Response) +NS_IMPL_CYCLE_COLLECTING_RELEASE(Response) +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner, mHeaders) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +Response::Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse) + : FetchBody<Response>() + , mOwner(aGlobal) + , mInternalResponse(aInternalResponse) +{ + MOZ_ASSERT(aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Immutable || + aInternalResponse->Headers()->Guard() == HeadersGuardEnum::Response); + SetMimeType(); +} + +Response::~Response() +{ +} + +/* static */ already_AddRefed<Response> +Response::Error(const GlobalObject& aGlobal) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + RefPtr<InternalResponse> error = InternalResponse::NetworkError(); + RefPtr<Response> r = new Response(global, error); + return r.forget(); +} + +/* static */ already_AddRefed<Response> +Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, + uint16_t aStatus, ErrorResult& aRv) +{ + nsAutoString parsedURL; + + if (NS_IsMainThread()) { + nsCOMPtr<nsIURI> baseURI; + nsIDocument* doc = GetEntryDocument(); + if (doc) { + baseURI = doc->GetBaseURI(); + } + nsCOMPtr<nsIURI> resolvedURI; + aRv = NS_NewURI(getter_AddRefs(resolvedURI), aUrl, nullptr, baseURI); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + nsAutoCString spec; + aRv = resolvedURI->GetSpec(spec); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + CopyUTF8toUTF16(spec, parsedURL); + } else { + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + worker->AssertIsOnWorkerThread(); + + NS_ConvertUTF8toUTF16 baseURL(worker->GetLocationInfo().mHref); + RefPtr<URL> url = URL::WorkerConstructor(aGlobal, aUrl, baseURL, aRv); + if (aRv.Failed()) { + return nullptr; + } + + url->Stringify(parsedURL, aRv); + } + + if (aRv.Failed()) { + return nullptr; + } + + if (aStatus != 301 && aStatus != 302 && aStatus != 303 && aStatus != 307 && aStatus != 308) { + aRv.ThrowRangeError<MSG_INVALID_REDIRECT_STATUSCODE_ERROR>(); + return nullptr; + } + + Optional<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams> body; + ResponseInit init; + init.mStatus = aStatus; + RefPtr<Response> r = Response::Constructor(aGlobal, body, init, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + r->GetInternalHeaders()->Set(NS_LITERAL_CSTRING("Location"), + NS_ConvertUTF16toUTF8(parsedURL), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + r->GetInternalHeaders()->SetGuard(HeadersGuardEnum::Immutable, aRv); + MOZ_ASSERT(!aRv.Failed()); + + return r.forget(); +} + +/*static*/ already_AddRefed<Response> +Response::Constructor(const GlobalObject& aGlobal, + const Optional<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aBody, + const ResponseInit& aInit, ErrorResult& aRv) +{ + nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); + + if (aInit.mStatus < 200 || aInit.mStatus > 599) { + aRv.ThrowRangeError<MSG_INVALID_RESPONSE_STATUSCODE_ERROR>(); + return nullptr; + } + + // Check if the status text contains illegal characters + nsACString::const_iterator start, end; + aInit.mStatusText.BeginReading(start); + aInit.mStatusText.EndReading(end); + if (FindCharInReadable('\r', start, end)) { + aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>(); + return nullptr; + } + // Reset iterator since FindCharInReadable advances it. + aInit.mStatusText.BeginReading(start); + if (FindCharInReadable('\n', start, end)) { + aRv.ThrowTypeError<MSG_RESPONSE_INVALID_STATUSTEXT_ERROR>(); + return nullptr; + } + + RefPtr<InternalResponse> internalResponse = + new InternalResponse(aInit.mStatus, aInit.mStatusText); + + // Grab a valid channel info from the global so this response is 'valid' for + // interception. + if (NS_IsMainThread()) { + ChannelInfo info; + nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global); + if (window) { + nsIDocument* doc = window->GetExtantDoc(); + MOZ_ASSERT(doc); + info.InitFromDocument(doc); + } else { + info.InitFromChromeGlobal(global); + } + internalResponse->InitChannelInfo(info); + } else { + workers::WorkerPrivate* worker = workers::GetCurrentThreadWorkerPrivate(); + MOZ_ASSERT(worker); + internalResponse->InitChannelInfo(worker->GetChannelInfo()); + } + + RefPtr<Response> r = new Response(global, internalResponse); + + if (aInit.mHeaders.WasPassed()) { + internalResponse->Headers()->Clear(); + + // Instead of using Fill, create an object to allow the constructor to + // unwrap the HeadersInit. + RefPtr<Headers> headers = + Headers::Create(global, aInit.mHeaders.Value(), aRv); + if (aRv.Failed()) { + return nullptr; + } + + internalResponse->Headers()->Fill(*headers->GetInternalHeaders(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + if (aBody.WasPassed()) { + if (aInit.mStatus == 204 || aInit.mStatus == 205 || aInit.mStatus == 304) { + aRv.ThrowTypeError<MSG_RESPONSE_NULL_STATUS_WITH_BODY>(); + return nullptr; + } + + nsCOMPtr<nsIInputStream> bodyStream; + nsCString contentType; + uint64_t bodySize = 0; + aRv = ExtractByteStreamFromBody(aBody.Value(), + getter_AddRefs(bodyStream), + contentType, + bodySize); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + internalResponse->SetBody(bodyStream, bodySize); + + if (!contentType.IsVoid() && + !internalResponse->Headers()->Has(NS_LITERAL_CSTRING("Content-Type"), aRv)) { + // Ignore Append() failing here. + ErrorResult error; + internalResponse->Headers()->Append(NS_LITERAL_CSTRING("Content-Type"), contentType, error); + error.SuppressException(); + } + + if (aRv.Failed()) { + return nullptr; + } + } + + r->SetMimeType(); + return r.forget(); +} + +already_AddRefed<Response> +Response::Clone(ErrorResult& aRv) const +{ + if (BodyUsed()) { + aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); + return nullptr; + } + + RefPtr<InternalResponse> ir = mInternalResponse->Clone(); + RefPtr<Response> response = new Response(mOwner, ir); + return response.forget(); +} + +already_AddRefed<Response> +Response::CloneUnfiltered(ErrorResult& aRv) const +{ + if (BodyUsed()) { + aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>(); + return nullptr; + } + + RefPtr<InternalResponse> clone = mInternalResponse->Clone(); + RefPtr<InternalResponse> ir = clone->Unfiltered(); + RefPtr<Response> ref = new Response(mOwner, ir); + return ref.forget(); +} + +void +Response::SetBody(nsIInputStream* aBody, int64_t aBodySize) +{ + MOZ_ASSERT(!BodyUsed()); + mInternalResponse->SetBody(aBody, aBodySize); +} + +already_AddRefed<InternalResponse> +Response::GetInternalResponse() const +{ + RefPtr<InternalResponse> ref = mInternalResponse; + return ref.forget(); +} + +Headers* +Response::Headers_() +{ + if (!mHeaders) { + mHeaders = new Headers(mOwner, mInternalResponse->Headers()); + } + + return mHeaders; +} + +} // namespace dom +} // namespace mozilla diff --git a/dom/fetch/Response.h b/dom/fetch/Response.h new file mode 100644 index 000000000..64b3c5f45 --- /dev/null +++ b/dom/fetch/Response.h @@ -0,0 +1,149 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_Response_h +#define mozilla_dom_Response_h + +#include "nsWrapperCache.h" +#include "nsISupportsImpl.h" + +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/ResponseBinding.h" + +#include "InternalHeaders.h" +#include "InternalResponse.h" + +namespace mozilla { +namespace ipc { +class PrincipalInfo; +} // namespace ipc + +namespace dom { + +class Headers; + +class Response final : public nsISupports + , public FetchBody<Response> + , public nsWrapperCache +{ + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Response) + +public: + Response(nsIGlobalObject* aGlobal, InternalResponse* aInternalResponse); + + Response(const Response& aOther) = delete; + + JSObject* + WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override + { + return ResponseBinding::Wrap(aCx, this, aGivenProto); + } + + ResponseType + Type() const + { + return mInternalResponse->Type(); + } + void + GetUrl(nsAString& aUrl) const + { + CopyUTF8toUTF16(mInternalResponse->GetURL(), aUrl); + } + bool + Redirected() const + { + return mInternalResponse->IsRedirected(); + } + uint16_t + Status() const + { + return mInternalResponse->GetStatus(); + } + + bool + Ok() const + { + return mInternalResponse->GetStatus() >= 200 && + mInternalResponse->GetStatus() <= 299; + } + + void + GetStatusText(nsCString& aStatusText) const + { + aStatusText = mInternalResponse->GetStatusText(); + } + + InternalHeaders* + GetInternalHeaders() const + { + return mInternalResponse->Headers(); + } + + void + InitChannelInfo(nsIChannel* aChannel) + { + mInternalResponse->InitChannelInfo(aChannel); + } + + const ChannelInfo& + GetChannelInfo() const + { + return mInternalResponse->GetChannelInfo(); + } + + const UniquePtr<mozilla::ipc::PrincipalInfo>& + GetPrincipalInfo() const + { + return mInternalResponse->GetPrincipalInfo(); + } + + Headers* Headers_(); + + void + GetBody(nsIInputStream** aStream) { return mInternalResponse->GetBody(aStream); } + + static already_AddRefed<Response> + Error(const GlobalObject& aGlobal); + + static already_AddRefed<Response> + Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, uint16_t aStatus, ErrorResult& aRv); + + static already_AddRefed<Response> + Constructor(const GlobalObject& aGlobal, + const Optional<ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams>& aBody, + const ResponseInit& aInit, ErrorResult& rv); + + nsIGlobalObject* GetParentObject() const + { + return mOwner; + } + + already_AddRefed<Response> + Clone(ErrorResult& aRv) const; + + already_AddRefed<Response> + CloneUnfiltered(ErrorResult& aRv) const; + + void + SetBody(nsIInputStream* aBody, int64_t aBodySize); + + already_AddRefed<InternalResponse> + GetInternalResponse() const; + +private: + ~Response(); + + nsCOMPtr<nsIGlobalObject> mOwner; + RefPtr<InternalResponse> mInternalResponse; + // Lazily created + RefPtr<Headers> mHeaders; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Response_h diff --git a/dom/fetch/moz.build b/dom/fetch/moz.build new file mode 100644 index 000000000..fb2279fe7 --- /dev/null +++ b/dom/fetch/moz.build @@ -0,0 +1,52 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + 'ChannelInfo.h', + 'Fetch.h', + 'FetchDriver.h', + 'FetchIPCTypes.h', + 'FetchUtil.h', + 'Headers.h', + 'InternalHeaders.h', + 'InternalRequest.h', + 'InternalResponse.h', + 'Request.h', + 'Response.h', +] + +UNIFIED_SOURCES += [ + 'ChannelInfo.cpp', + 'Fetch.cpp', + 'FetchConsumer.cpp', + 'FetchDriver.cpp', + 'FetchUtil.cpp', + 'Headers.cpp', + 'InternalHeaders.cpp', + 'InternalRequest.cpp', + 'InternalResponse.cpp', + 'Request.cpp', + 'Response.cpp', +] + +IPDL_SOURCES += [ + 'ChannelInfo.ipdlh', + 'FetchTypes.ipdlh', +] + +LOCAL_INCLUDES += [ + '../workers', + # For HttpBaseChannel.h dependencies + '/netwerk/base', + # For nsDataHandler.h + '/netwerk/protocol/data', + # For HttpBaseChannel.h + '/netwerk/protocol/http', +] + +FINAL_LIBRARY = 'xul' + +include('/ipc/chromium/chromium-config.mozbuild') |