summaryrefslogtreecommitdiffstats
path: root/dom/fetch/FetchDriver.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/fetch/FetchDriver.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/fetch/FetchDriver.cpp')
-rw-r--r--dom/fetch/FetchDriver.cpp938
1 files changed, 938 insertions, 0 deletions
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