summaryrefslogtreecommitdiffstats
path: root/dom/fetch
diff options
context:
space:
mode:
Diffstat (limited to 'dom/fetch')
-rw-r--r--dom/fetch/ChannelInfo.cpp124
-rw-r--r--dom/fetch/ChannelInfo.h93
-rw-r--r--dom/fetch/ChannelInfo.ipdlh14
-rw-r--r--dom/fetch/Fetch.cpp759
-rw-r--r--dom/fetch/Fetch.h205
-rw-r--r--dom/fetch/FetchConsumer.cpp705
-rw-r--r--dom/fetch/FetchConsumer.h128
-rw-r--r--dom/fetch/FetchDriver.cpp938
-rw-r--r--dom/fetch/FetchDriver.h136
-rw-r--r--dom/fetch/FetchIPCTypes.h57
-rw-r--r--dom/fetch/FetchTypes.ipdlh61
-rw-r--r--dom/fetch/FetchUtil.cpp114
-rw-r--r--dom/fetch/FetchUtil.h42
-rw-r--r--dom/fetch/Headers.cpp97
-rw-r--r--dom/fetch/Headers.h144
-rw-r--r--dom/fetch/InternalHeaders.cpp423
-rw-r--r--dom/fetch/InternalHeaders.h160
-rw-r--r--dom/fetch/InternalRequest.cpp495
-rw-r--r--dom/fetch/InternalRequest.h536
-rw-r--r--dom/fetch/InternalResponse.cpp261
-rw-r--r--dom/fetch/InternalResponse.h313
-rw-r--r--dom/fetch/Request.cpp627
-rw-r--r--dom/fetch/Request.h167
-rw-r--r--dom/fetch/Response.cpp281
-rw-r--r--dom/fetch/Response.h149
-rw-r--r--dom/fetch/moz.build52
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')