summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/http/HttpBaseChannel.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /netwerk/protocol/http/HttpBaseChannel.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--netwerk/protocol/http/HttpBaseChannel.cpp6307
1 files changed, 6307 insertions, 0 deletions
diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp
new file mode 100644
index 0000000000..f432220113
--- /dev/null
+++ b/netwerk/protocol/http/HttpBaseChannel.cpp
@@ -0,0 +1,6307 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et 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/. */
+
+// HttpLog.h should generally be included first
+#include "mozilla/net/HttpBaseChannel.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "HttpBaseChannel.h"
+#include "HttpLog.h"
+#include "LoadInfo.h"
+#include "ReferrerInfo.h"
+#include "mozIRemoteLazyInputStream.h"
+#include "mozIThirdPartyUtil.h"
+#include "mozilla/LoadInfo.h"
+#include "mozilla/AntiTrackingUtils.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ConsoleReportCollector.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/InputStreamLengthHelper.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/PermissionManager.h"
+#include "mozilla/Components.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_network.h"
+#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Tokenizer.h"
+#include "mozilla/browser/NimbusFeatures.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/nsHTTPSOnlyUtils.h"
+#include "mozilla/dom/nsMixedContentBlocker.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceStorage.h"
+#include "mozilla/dom/ProcessIsolation.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/WindowGlobalParent.h"
+#include "mozilla/net/OpaqueResponseUtils.h"
+#include "mozilla/net/UrlClassifierCommon.h"
+#include "mozilla/net/UrlClassifierFeatureFactory.h"
+#include "nsBufferedStreams.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsContentSecurityManager.h"
+#include "nsContentSecurityUtils.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsEscape.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHttpChannel.h"
+#include "nsHTTPCompressConv.h"
+#include "nsHttpHandler.h"
+#include "nsICacheInfoChannel.h"
+#include "nsICachingChannel.h"
+#include "nsIChannelEventSink.h"
+#include "nsIConsoleService.h"
+#include "nsIContentPolicy.h"
+#include "nsICookieService.h"
+#include "nsIDOMWindowUtils.h"
+#include "nsIDocShell.h"
+#include "nsIDNSService.h"
+#include "nsIEncodedChannel.h"
+#include "nsIHttpHeaderVisitor.h"
+#include "nsILoadGroupChild.h"
+#include "nsIMIMEInputStream.h"
+#include "nsIMultiplexInputStream.h"
+#include "nsIMutableArray.h"
+#include "nsINetworkInterceptController.h"
+#include "nsIObserverService.h"
+#include "nsIPrincipal.h"
+#include "nsIProtocolProxyService.h"
+#include "nsIScriptError.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsISecurityConsoleMessage.h"
+#include "nsISeekableStream.h"
+#include "nsIStorageStream.h"
+#include "nsIStreamConverterService.h"
+#include "nsITimedChannel.h"
+#include "nsITransportSecurityInfo.h"
+#include "nsIURIMutator.h"
+#include "nsMimeTypes.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsProxyRelease.h"
+#include "nsReadableUtils.h"
+#include "nsRedirectHistoryEntry.h"
+#include "nsServerTiming.h"
+#include "nsStreamListenerWrapper.h"
+#include "nsStreamUtils.h"
+#include "nsString.h"
+#include "nsThreadUtils.h"
+#include "nsURLHelper.h"
+#include "mozilla/RemoteLazyInputStreamChild.h"
+#include "mozilla/net/SFVService.h"
+#include "mozilla/dom/ContentChild.h"
+#include "nsQueryObject.h"
+
+using mozilla::dom::RequestMode;
+
+#define LOGORB(msg, ...) \
+ MOZ_LOG(GetORBLog(), LogLevel::Debug, \
+ ("%s: %p " msg, __func__, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace net {
+
+static bool IsHeaderBlacklistedForRedirectCopy(nsHttpAtom const& aHeader) {
+ // IMPORTANT: keep this list ASCII-code sorted
+ static nsHttpAtom const* blackList[] = {&nsHttp::Accept,
+ &nsHttp::Accept_Encoding,
+ &nsHttp::Accept_Language,
+ &nsHttp::Alternate_Service_Used,
+ &nsHttp::Authentication,
+ &nsHttp::Authorization,
+ &nsHttp::Connection,
+ &nsHttp::Content_Length,
+ &nsHttp::Cookie,
+ &nsHttp::Host,
+ &nsHttp::If,
+ &nsHttp::If_Match,
+ &nsHttp::If_Modified_Since,
+ &nsHttp::If_None_Match,
+ &nsHttp::If_None_Match_Any,
+ &nsHttp::If_Range,
+ &nsHttp::If_Unmodified_Since,
+ &nsHttp::Proxy_Authenticate,
+ &nsHttp::Proxy_Authorization,
+ &nsHttp::Range,
+ &nsHttp::TE,
+ &nsHttp::Transfer_Encoding,
+ &nsHttp::Upgrade,
+ &nsHttp::User_Agent,
+ &nsHttp::WWW_Authenticate};
+
+ class HttpAtomComparator {
+ nsHttpAtom const& mTarget;
+
+ public:
+ explicit HttpAtomComparator(nsHttpAtom const& aTarget) : mTarget(aTarget) {}
+ int operator()(nsHttpAtom const* aVal) const {
+ if (mTarget == *aVal) {
+ return 0;
+ }
+ return strcmp(mTarget.get(), aVal->get());
+ }
+ };
+
+ size_t unused;
+ return BinarySearchIf(blackList, 0, ArrayLength(blackList),
+ HttpAtomComparator(aHeader), &unused);
+}
+
+class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit AddHeadersToChannelVisitor(nsIHttpChannel* aChannel)
+ : mChannel(aChannel) {}
+
+ NS_IMETHOD VisitHeader(const nsACString& aHeader,
+ const nsACString& aValue) override {
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!IsHeaderBlacklistedForRedirectCopy(atom)) {
+ DebugOnly<nsresult> rv =
+ mChannel->SetRequestHeader(aHeader, aValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ return NS_OK;
+ }
+
+ private:
+ ~AddHeadersToChannelVisitor() = default;
+
+ nsCOMPtr<nsIHttpChannel> mChannel;
+};
+
+NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
+
+static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
+ uint32_t pref = StaticPrefs::
+ browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
+ if (NS_WARN_IF(pref >
+ static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
+ return OpaqueResponseFilterFetch::All;
+ }
+
+ return static_cast<OpaqueResponseFilterFetch>(pref);
+}
+
+HttpBaseChannel::HttpBaseChannel()
+ : mReportCollector(new ConsoleReportCollector()),
+ mHttpHandler(gHttpHandler),
+ mChannelCreationTime(0),
+ mComputedCrossOriginOpenerPolicy(nsILoadInfo::OPENER_POLICY_UNSAFE_NONE),
+ mStartPos(UINT64_MAX),
+ mTransferSize(0),
+ mRequestSize(0),
+ mDecodedBodySize(0),
+ mSupportsHTTP3(false),
+ mEncodedBodySize(0),
+ mRequestContextID(0),
+ mContentWindowId(0),
+ mBrowserId(0),
+ mAltDataLength(-1),
+ mChannelId(0),
+ mReqContentLength(0U),
+ mStatus(NS_OK),
+ mCanceled(false),
+ mFirstPartyClassificationFlags(0),
+ mThirdPartyClassificationFlags(0),
+ mLoadFlags(LOAD_NORMAL),
+ mCaps(0),
+ mClassOfService(0, false),
+ mTlsFlags(0),
+ mSuspendCount(0),
+ mInitialRwin(0),
+ mProxyResolveFlags(0),
+ mContentDispositionHint(UINT32_MAX),
+ mRequestMode(RequestMode::No_cors),
+ mRedirectMode(nsIHttpChannelInternal::REDIRECT_MODE_FOLLOW),
+ mLastRedirectFlags(0),
+ mPriority(PRIORITY_NORMAL),
+ mRedirectionLimit(gHttpHandler->RedirectionLimit()),
+ mRedirectCount(0),
+ mInternalRedirectCount(0),
+ mCachedOpaqueResponseBlockingPref(
+ StaticPrefs::browser_opaqueResponseBlocking()),
+ mChannelBlockedByOpaqueResponse(false),
+ mDummyChannelForImageCache(false) {
+ StoreApplyConversion(true);
+ StoreAllowSTS(true);
+ StoreTracingEnabled(true);
+ StoreReportTiming(true);
+ StoreAllowSpdy(true);
+ StoreAllowHttp3(true);
+ StoreAllowAltSvc(true);
+ StoreResponseTimeoutEnabled(true);
+ StoreAllRedirectsSameOrigin(true);
+ StoreAllRedirectsPassTimingAllowCheck(true);
+ StoreUpgradableToSecure(true);
+
+ this->mSelfAddr.inet = {};
+ this->mPeerAddr.inet = {};
+ LOG(("Creating HttpBaseChannel @%p\n", this));
+
+ // Subfields of unions cannot be targeted in an initializer list.
+#ifdef MOZ_VALGRIND
+ // Zero the entire unions so that Valgrind doesn't complain when we send them
+ // to another process.
+ memset(&mSelfAddr, 0, sizeof(NetAddr));
+ memset(&mPeerAddr, 0, sizeof(NetAddr));
+#endif
+ mSelfAddr.raw.family = PR_AF_UNSPEC;
+ mPeerAddr.raw.family = PR_AF_UNSPEC;
+}
+
+HttpBaseChannel::~HttpBaseChannel() {
+ LOG(("Destroying HttpBaseChannel @%p\n", this));
+
+ // Make sure we don't leak
+ CleanRedirectCacheChainIfNecessary();
+
+ ReleaseMainThreadOnlyReferences();
+}
+
+namespace { // anon
+
+class NonTailRemover : public nsISupports {
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ explicit NonTailRemover(nsIRequestContext* rc) : mRequestContext(rc) {}
+
+ private:
+ virtual ~NonTailRemover() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mRequestContext->RemoveNonTailRequest();
+ }
+
+ nsCOMPtr<nsIRequestContext> mRequestContext;
+};
+
+NS_IMPL_ISUPPORTS0(NonTailRemover)
+
+} // namespace
+
+void HttpBaseChannel::ReleaseMainThreadOnlyReferences() {
+ if (NS_IsMainThread()) {
+ // Already on main thread, let dtor to
+ // take care of releasing references
+ RemoveAsNonTailRequest();
+ return;
+ }
+
+ nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
+ arrayToRelease.AppendElement(mLoadGroup.forget());
+ arrayToRelease.AppendElement(mLoadInfo.forget());
+ arrayToRelease.AppendElement(mCallbacks.forget());
+ arrayToRelease.AppendElement(mProgressSink.forget());
+ arrayToRelease.AppendElement(mPrincipal.forget());
+ arrayToRelease.AppendElement(mListener.forget());
+ arrayToRelease.AppendElement(mCompressListener.forget());
+ arrayToRelease.AppendElement(mORB.forget());
+
+ if (LoadAddedAsNonTailRequest()) {
+ // RemoveNonTailRequest() on our request context must be called on the main
+ // thread
+ MOZ_RELEASE_ASSERT(mRequestContext,
+ "Someone released rc or set flags w/o having it?");
+
+ nsCOMPtr<nsISupports> nonTailRemover(new NonTailRemover(mRequestContext));
+ arrayToRelease.AppendElement(nonTailRemover.forget());
+ }
+
+ NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
+}
+
+void HttpBaseChannel::AddClassificationFlags(uint32_t aClassificationFlags,
+ bool aIsThirdParty) {
+ LOG(
+ ("HttpBaseChannel::AddClassificationFlags classificationFlags=%d "
+ "thirdparty=%d %p",
+ aClassificationFlags, static_cast<int>(aIsThirdParty), this));
+
+ if (aIsThirdParty) {
+ mThirdPartyClassificationFlags |= aClassificationFlags;
+ } else {
+ mFirstPartyClassificationFlags |= aClassificationFlags;
+ }
+}
+
+static bool isSecureOrTrustworthyURL(nsIURI* aURI) {
+ return aURI->SchemeIs("https") ||
+ (StaticPrefs::network_http_encoding_trustworthy_is_https() &&
+ nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI));
+}
+
+nsresult HttpBaseChannel::Init(nsIURI* aURI, uint32_t aCaps,
+ nsProxyInfo* aProxyInfo,
+ uint32_t aProxyResolveFlags, nsIURI* aProxyURI,
+ uint64_t aChannelId,
+ ExtContentPolicyType aContentPolicyType,
+ nsILoadInfo* aLoadInfo) {
+ LOG1(("HttpBaseChannel::Init [this=%p]\n", this));
+
+ MOZ_ASSERT(aURI, "null uri");
+
+ mURI = aURI;
+ mOriginalURI = aURI;
+ mDocumentURI = nullptr;
+ mCaps = aCaps;
+ mProxyResolveFlags = aProxyResolveFlags;
+ mProxyURI = aProxyURI;
+ mChannelId = aChannelId;
+ mLoadInfo = aLoadInfo;
+
+ // Construct connection info object
+ nsAutoCString host;
+ int32_t port = -1;
+ bool isHTTPS = isSecureOrTrustworthyURL(mURI);
+
+ nsresult rv = mURI->GetAsciiHost(host);
+ if (NS_FAILED(rv)) return rv;
+
+ // Reject the URL if it doesn't specify a host
+ if (host.IsEmpty()) return NS_ERROR_MALFORMED_URI;
+
+ rv = mURI->GetPort(&port);
+ if (NS_FAILED(rv)) return rv;
+
+ LOG1(("host=%s port=%d\n", host.get(), port));
+
+ rv = mURI->GetAsciiSpec(mSpec);
+ if (NS_FAILED(rv)) return rv;
+ LOG1(("uri=%s\n", mSpec.get()));
+
+ // Assert default request method
+ MOZ_ASSERT(mRequestHead.EqualsMethod(nsHttpRequestHead::kMethod_Get));
+
+ // Set request headers
+ nsAutoCString hostLine;
+ rv = nsHttpHandler::GenerateHostPort(host, port, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = gHttpHandler->AddStandardRequestHeaders(
+ &mRequestHead, isHTTPS, aContentPolicyType,
+ nsContentUtils::ShouldResistFingerprinting(this));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString type;
+ if (aProxyInfo && NS_SUCCEEDED(aProxyInfo->GetType(type)) &&
+ !type.EqualsLiteral("unknown")) {
+ mProxyInfo = aProxyInfo;
+ }
+
+ mCurrentThread = GetCurrentSerialEventTarget();
+ return rv;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ADDREF(HttpBaseChannel)
+NS_IMPL_RELEASE(HttpBaseChannel)
+
+NS_INTERFACE_MAP_BEGIN(HttpBaseChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIRequest)
+ NS_INTERFACE_MAP_ENTRY(nsIChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIIdentChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
+ NS_INTERFACE_MAP_ENTRY(nsIForcePendingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
+ NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIPrivateBrowsingChannel)
+ NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIConsoleReportCollector)
+ NS_INTERFACE_MAP_ENTRY(nsIThrottledInputChannel)
+ NS_INTERFACE_MAP_ENTRY(nsIClassifiedChannel)
+ NS_INTERFACE_MAP_ENTRY_CONCRETE(HttpBaseChannel)
+NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIRequest
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetName(nsACString& aName) {
+ aName = mSpec;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPending(bool* aIsPending) {
+ NS_ENSURE_ARG_POINTER(aIsPending);
+ *aIsPending = LoadIsPending() || LoadForcePending();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetStatus(nsresult* aStatus) {
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = mStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadGroup(nsILoadGroup** aLoadGroup) {
+ NS_ENSURE_ARG_POINTER(aLoadGroup);
+ *aLoadGroup = do_AddRef(mLoadGroup).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetLoadGroup(aLoadGroup)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoadGroup = aLoadGroup;
+ mProgressSink = nullptr;
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadFlags(nsLoadFlags* aLoadFlags) {
+ NS_ENSURE_ARG_POINTER(aLoadFlags);
+ *aLoadFlags = mLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags) {
+ mLoadFlags = aLoadFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTRRMode(nsIRequest::TRRMode* aTRRMode) {
+ if (!LoadIsOCSP()) {
+ return GetTRRModeImpl(aTRRMode);
+ }
+
+ nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
+ nsIDNSService::ResolverMode trrMode = nsIDNSService::MODE_NATIVEONLY;
+ // If this is an OCSP channel, and the global TRR mode is TRR_ONLY (3)
+ // then we set the mode for this channel as TRR_DISABLED_MODE.
+ // We do this to prevent a TRR service channel's OCSP validation from
+ // blocking DNS resolution completely.
+ if (dns && NS_SUCCEEDED(dns->GetCurrentTrrMode(&trrMode)) &&
+ trrMode == nsIDNSService::MODE_TRRONLY) {
+ *aTRRMode = nsIRequest::TRR_DISABLED_MODE;
+ return NS_OK;
+ }
+
+ return GetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTRRMode(nsIRequest::TRRMode aTRRMode) {
+ return SetTRRModeImpl(aTRRMode);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocshellUserAgentOverride() {
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+ if (!bc) {
+ return NS_OK;
+ }
+
+ nsAutoString customUserAgent;
+ bc->GetCustomUserAgent(customUserAgent);
+ if (customUserAgent.IsEmpty() || customUserAgent.IsVoid()) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8CustomUserAgent(customUserAgent);
+ nsresult rv = SetRequestHeader("User-Agent"_ns, utf8CustomUserAgent, false);
+ if (NS_FAILED(rv)) return rv;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalURI(nsIURI** aOriginalURI) {
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ *aOriginalURI = do_AddRef(mOriginalURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOriginalURI(nsIURI* aOriginalURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ NS_ENSURE_ARG_POINTER(aOriginalURI);
+ mOriginalURI = aOriginalURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetURI(nsIURI** aURI) {
+ NS_ENSURE_ARG_POINTER(aURI);
+ *aURI = do_AddRef(mURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOwner(nsISupports** aOwner) {
+ NS_ENSURE_ARG_POINTER(aOwner);
+ *aOwner = do_AddRef(mOwner).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetOwner(nsISupports* aOwner) {
+ mOwner = aOwner;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLoadInfo(nsILoadInfo* aLoadInfo) {
+ MOZ_RELEASE_ASSERT(aLoadInfo, "loadinfo can't be null");
+ mLoadInfo = aLoadInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLoadInfo(nsILoadInfo** aLoadInfo) {
+ *aLoadInfo = do_AddRef(mLoadInfo).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsDocument(bool* aIsDocument) {
+ return NS_GetIsDocumentChannel(this, aIsDocument);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) {
+ *aCallbacks = do_AddRef(mCallbacks).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) {
+ MOZ_ASSERT(NS_IsMainThread(), "Should only be called on the main thread.");
+
+ if (!CanSetCallbacks(aCallbacks)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCallbacks = aCallbacks;
+ mProgressSink = nullptr;
+
+ UpdatePrivateBrowsing();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentType(nsACString& aContentType) {
+ if (!mResponseHead) {
+ aContentType.Truncate();
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ mResponseHead->ContentType(aContentType);
+ if (!aContentType.IsEmpty()) {
+ return NS_OK;
+ }
+
+ aContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentType(const nsACString& aContentType) {
+ if (mListener || LoadWasOpened() || mDummyChannelForImageCache) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsAutoCString contentTypeBuf, charsetBuf;
+ bool hadCharset;
+ net_ParseContentType(aContentType, contentTypeBuf, charsetBuf, &hadCharset);
+
+ mResponseHead->SetContentType(contentTypeBuf);
+
+ // take care not to stomp on an existing charset
+ if (hadCharset) mResponseHead->SetContentCharset(charsetBuf);
+
+ } else {
+ // We are being given a content-type hint.
+ bool dummy;
+ net_ParseContentType(aContentType, mContentTypeHint, mContentCharsetHint,
+ &dummy);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentCharset(nsACString& aContentCharset) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->ContentCharset(aContentCharset);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentCharset(const nsACString& aContentCharset) {
+ if (mListener) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ mResponseHead->SetContentCharset(aContentCharset);
+ } else {
+ // Charset hint
+ mContentCharsetHint = aContentCharset;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDisposition(uint32_t* aContentDisposition) {
+ // See bug 1658877. If mContentDispositionHint is already
+ // DISPOSITION_ATTACHMENT, it means this channel is created from a
+ // download attribute. In this case, we should prefer the value from the
+ // download attribute rather than the value in content disposition header.
+ // DISPOSITION_FORCE_INLINE is used to explicitly set inline, used by
+ // the pdf reader when loading a attachment pdf without having to
+ // download it.
+ if (mContentDispositionHint == nsIChannel::DISPOSITION_ATTACHMENT ||
+ mContentDispositionHint == nsIChannel::DISPOSITION_FORCE_INLINE) {
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_FAILED(rv)) {
+ if (mContentDispositionHint == UINT32_MAX) return rv;
+
+ *aContentDisposition = mContentDispositionHint;
+ return NS_OK;
+ }
+
+ *aContentDisposition = NS_GetContentDispositionFromHeader(header, this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDisposition(uint32_t aContentDisposition) {
+ mContentDispositionHint = aContentDisposition;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionFilename(
+ nsAString& aContentDispositionFilename) {
+ aContentDispositionFilename.Truncate();
+ nsresult rv;
+ nsCString header;
+
+ rv = GetContentDispositionHeader(header);
+ if (NS_SUCCEEDED(rv)) {
+ rv = NS_GetFilenameFromDisposition(aContentDispositionFilename, header);
+ }
+
+ // If we failed to get the filename from header, we should use
+ // mContentDispositionFilename, since mContentDispositionFilename is set from
+ // the download attribute.
+ if (NS_FAILED(rv)) {
+ if (!mContentDispositionFilename) {
+ return rv;
+ }
+
+ aContentDispositionFilename = *mContentDispositionFilename;
+ return NS_OK;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentDispositionFilename(
+ const nsAString& aContentDispositionFilename) {
+ mContentDispositionFilename =
+ MakeUnique<nsString>(aContentDispositionFilename);
+
+ // For safety reasons ensure the filename doesn't contain null characters and
+ // replace them with underscores. We may later pass the extension to system
+ // MIME APIs that expect null terminated strings.
+ mContentDispositionFilename->ReplaceChar(char16_t(0), '_');
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentDispositionHeader(
+ nsACString& aContentDispositionHeader) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv = mResponseHead->GetHeader(nsHttp::Content_Disposition,
+ aContentDispositionHeader);
+ if (NS_FAILED(rv) || aContentDispositionHeader.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentLength(int64_t* aContentLength) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ *aContentLength = mAltDataLength;
+ return NS_OK;
+ }
+
+ *aContentLength = mResponseHead->ContentLength();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetContentLength(int64_t value) {
+ if (!mDummyChannelForImageCache) {
+ MOZ_ASSERT_UNREACHABLE("HttpBaseChannel::SetContentLength");
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+ MOZ_ASSERT(mResponseHead);
+ mResponseHead->SetContentLength(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::Open(nsIInputStream** aStream) {
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ nsresult rv =
+ nsContentSecurityManager::doContentSecurityCheck(this, listener);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ NS_ENSURE_TRUE(!LoadWasOpened(), NS_ERROR_IN_PROGRESS);
+
+ if (!gHttpHandler->Active()) {
+ LOG(("HttpBaseChannel::Open after HTTP shutdown..."));
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_ImplementChannelOpen(this, aStream);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStream(nsIInputStream** stream) {
+ NS_ENSURE_ARG_POINTER(stream);
+ *stream = do_AddRef(mUploadStream).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetUploadStream(nsIInputStream* stream,
+ const nsACString& contentTypeArg,
+ int64_t contentLength) {
+ // NOTE: for backwards compatibility and for compatibility with old style
+ // plugins, |stream| may include headers, specifically Content-Type and
+ // Content-Length headers. in this case, |contentType| and |contentLength|
+ // would be unspecified. this is traditionally the case of a POST request,
+ // and so we select POST as the request method if contentType and
+ // contentLength are unspecified.
+
+ if (stream) {
+ nsAutoCString method;
+ bool hasHeaders = false;
+
+ // This method and ExplicitSetUploadStream mean different things by "empty
+ // content type string". This method means "no header", but
+ // ExplicitSetUploadStream means "header with empty value". So we have to
+ // massage the contentType argument into the form ExplicitSetUploadStream
+ // expects.
+ nsCOMPtr<nsIMIMEInputStream> mimeStream;
+ nsCString contentType(contentTypeArg);
+ if (contentType.IsEmpty()) {
+ contentType.SetIsVoid(true);
+ method = "POST"_ns;
+
+ // MIME streams are a special case, and include headers which need to be
+ // copied to the channel.
+ mimeStream = do_QueryInterface(stream);
+ if (mimeStream) {
+ // Copy non-origin related headers to the channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(this);
+ mimeStream->VisitHeaders(visitor);
+
+ return ExplicitSetUploadStream(stream, contentType, contentLength,
+ method, hasHeaders);
+ }
+
+ hasHeaders = true;
+ } else {
+ method = "PUT"_ns;
+
+ MOZ_ASSERT(
+ NS_FAILED(CallQueryInterface(stream, getter_AddRefs(mimeStream))),
+ "nsIMIMEInputStream should not be set with an explicit content type");
+ }
+ return ExplicitSetUploadStream(stream, contentType, contentLength, method,
+ hasHeaders);
+ }
+
+ // if stream is null, ExplicitSetUploadStream returns error.
+ // So we need special case for GET method.
+ StoreUploadStreamHasHeaders(false);
+ mRequestHead.SetMethod("GET"_ns); // revert to GET request
+ mUploadStream = nullptr;
+ return NS_OK;
+}
+
+namespace {
+
+class MIMEHeaderCopyVisitor final : public nsIHttpHeaderVisitor {
+ public:
+ explicit MIMEHeaderCopyVisitor(nsIMIMEInputStream* aDest) : mDest(aDest) {}
+
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD VisitHeader(const nsACString& aName,
+ const nsACString& aValue) override {
+ return mDest->AddHeader(PromiseFlatCString(aName).get(),
+ PromiseFlatCString(aValue).get());
+ }
+
+ private:
+ ~MIMEHeaderCopyVisitor() = default;
+
+ nsCOMPtr<nsIMIMEInputStream> mDest;
+};
+
+NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor)
+
+static void NormalizeCopyComplete(void* aClosure, nsresult aStatus) {
+#ifdef DEBUG
+ // Called on the STS thread by NS_AsyncCopy
+ nsCOMPtr<nsIEventTarget> sts =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ bool result = false;
+ sts->IsOnCurrentThread(&result);
+ MOZ_ASSERT(result, "Should only be called on the STS thread.");
+#endif
+
+ RefPtr<GenericPromise::Private> ready =
+ already_AddRefed(static_cast<GenericPromise::Private*>(aClosure));
+ if (NS_SUCCEEDED(aStatus)) {
+ ready->Resolve(true, __func__);
+ } else {
+ ready->Reject(aStatus, __func__);
+ }
+}
+
+// Normalize the upload stream for a HTTP channel, so that is one of the
+// expected and compatible types. Components like WebExtensions and DevTools
+// expect that upload streams in the parent process are cloneable, seekable, and
+// synchronous to read, which this function helps guarantee somewhat efficiently
+// and without loss of information.
+//
+// If the replacement stream outparameter is not initialized to `nullptr`, the
+// returned stream should be used instead of `aUploadStream` as the upload
+// stream for the HTTP channel, and the previous stream should not be touched
+// again.
+//
+// If aReadyPromise is non-nullptr after the function is called, it is a promise
+// which should be awaited before continuing to `AsyncOpen` the HTTP channel,
+// as the replacement stream will not be ready until it is resolved.
+static nsresult NormalizeUploadStream(nsIInputStream* aUploadStream,
+ nsIInputStream** aReplacementStream,
+ GenericPromise** aReadyPromise) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ *aReplacementStream = nullptr;
+ *aReadyPromise = nullptr;
+
+ // Unwrap RemoteLazyInputStream and normalize the contents as we're in the
+ // parent process.
+ if (nsCOMPtr<mozIRemoteLazyInputStream> lazyStream =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> internal;
+ if (NS_SUCCEEDED(
+ lazyStream->TakeInternalStream(getter_AddRefs(internal)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(internal, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ replacement.forget(aReplacementStream);
+ } else {
+ internal.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve MIME information on the stream when normalizing.
+ if (nsCOMPtr<nsIMIMEInputStream> mime = do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ nsresult rv = mime->GetData(getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacement;
+ rv =
+ NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (replacement) {
+ nsCOMPtr<nsIMIMEInputStream> replacementMime(
+ do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new MIMEHeaderCopyVisitor(replacementMime);
+ rv = mime->VisitHeaders(visitor);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = replacementMime->SetData(replacement);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ replacementMime.forget(aReplacementStream);
+ }
+ return NS_OK;
+ }
+
+ // Preserve "real" buffered input streams which wrap data (i.e. are backed by
+ // nsBufferedInputStream), but normalize the wrapped stream.
+ if (nsCOMPtr<nsIBufferedInputStream> buffered =
+ do_QueryInterface(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> data;
+ if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) {
+ nsCOMPtr<nsIInputStream> replacement;
+ nsresult rv = NormalizeUploadStream(data, getter_AddRefs(replacement),
+ aReadyPromise);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (replacement) {
+ // This buffer size should be kept in sync with HTMLFormSubmission.
+ rv = NS_NewBufferedInputStream(aReplacementStream, replacement.forget(),
+ 8192);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Preserve multiplex input streams, normalizing each individual inner stream
+ // to avoid unnecessary copying.
+ if (nsCOMPtr<nsIMultiplexInputStream> multiplex =
+ do_QueryInterface(aUploadStream)) {
+ uint32_t count = multiplex->GetCount();
+ nsTArray<nsCOMPtr<nsIInputStream>> streams(count);
+ nsTArray<RefPtr<GenericPromise>> promises(count);
+ bool replace = false;
+ for (uint32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIInputStream> inner;
+ nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<GenericPromise> promise;
+ nsCOMPtr<nsIInputStream> replacement;
+ rv = NormalizeUploadStream(inner, getter_AddRefs(replacement),
+ getter_AddRefs(promise));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (promise) {
+ promises.AppendElement(promise);
+ }
+ if (replacement) {
+ streams.AppendElement(replacement);
+ replace = true;
+ } else {
+ streams.AppendElement(inner);
+ }
+ }
+
+ // If any of the inner streams needed to be replaced, replace the entire
+ // nsIMultiplexInputStream.
+ if (replace) {
+ nsresult rv;
+ nsCOMPtr<nsIMultiplexInputStream> replacement =
+ do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto& stream : streams) {
+ rv = replacement->AppendStream(stream);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ MOZ_ALWAYS_SUCCEEDS(CallQueryInterface(replacement, aReplacementStream));
+ }
+
+ // Wait for all inner promises to settle before resolving the final promise.
+ if (!promises.IsEmpty()) {
+ RefPtr<GenericPromise> ready =
+ GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises)
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [](GenericPromise::AllSettledPromiseType::
+ ResolveOrRejectValue&& aResults)
+ -> RefPtr<GenericPromise> {
+ MOZ_ASSERT(aResults.IsResolve(),
+ "AllSettled never rejects");
+ for (auto& result : aResults.ResolveValue()) {
+ if (result.IsReject()) {
+ return GenericPromise::CreateAndReject(
+ result.RejectValue(), __func__);
+ }
+ }
+ return GenericPromise::CreateAndResolve(true, __func__);
+ });
+ ready.forget(aReadyPromise);
+ }
+ return NS_OK;
+ }
+
+ // If the stream is cloneable, seekable and non-async, we can allow it. Async
+ // input streams can cause issues, as various consumers of input streams
+ // expect the payload to be synchronous and `Available()` to be the length of
+ // the stream, which is not true for asynchronous streams.
+ nsCOMPtr<nsIAsyncInputStream> async = do_QueryInterface(aUploadStream);
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (NS_InputStreamIsCloneable(aUploadStream) && seekable && !async) {
+ return NS_OK;
+ }
+
+ // Asynchronously copy our non-normalized stream into a StorageStream so that
+ // it is seekable, cloneable, and synchronous once the copy completes.
+
+ NS_WARNING("Upload Stream is being copied into StorageStream");
+
+ nsCOMPtr<nsIStorageStream> storageStream;
+ nsresult rv =
+ NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> sink;
+ rv = storageStream->GetOutputStream(0, getter_AddRefs(sink));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> replacementStream;
+ rv = storageStream->NewInputStream(0, getter_AddRefs(replacementStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Ensure the source stream is buffered before starting the copy so we can use
+ // ReadSegments, as nsStorageStream doesn't implement WriteSegments.
+ nsCOMPtr<nsIInputStream> source = aUploadStream;
+ if (!NS_InputStreamIsBuffered(aUploadStream)) {
+ nsCOMPtr<nsIInputStream> bufferedSource;
+ rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedSource),
+ source.forget(), 4096);
+ NS_ENSURE_SUCCESS(rv, rv);
+ source = bufferedSource.forget();
+ }
+
+ // Perform an AsyncCopy into the input stream on the STS.
+ nsCOMPtr<nsIEventTarget> target =
+ do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ RefPtr<GenericPromise::Private> ready = new GenericPromise::Private(__func__);
+ rv = NS_AsyncCopy(source, sink, target, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096,
+ NormalizeCopyComplete, do_AddRef(ready).take());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ ready.get()->Release();
+ return rv;
+ }
+
+ replacementStream.forget(aReplacementStream);
+ ready.forget(aReadyPromise);
+ return NS_OK;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::CloneUploadStream(int64_t* aContentLength,
+ nsIInputStream** aClonedStream) {
+ NS_ENSURE_ARG_POINTER(aContentLength);
+ NS_ENSURE_ARG_POINTER(aClonedStream);
+ *aClonedStream = nullptr;
+
+ if (!XRE_IsParentProcess()) {
+ NS_WARNING("CloneUploadStream is only supported in the parent process");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!mUploadStream) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIInputStream> clonedStream;
+ nsresult rv =
+ NS_CloneInputStream(mUploadStream, getter_AddRefs(clonedStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ clonedStream.forget(aClonedStream);
+
+ *aContentLength = mReqContentLength;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIUploadChannel2
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream* aStream,
+ const nsACString& aContentType,
+ int64_t aContentLength,
+ const nsACString& aMethod,
+ bool aStreamHasHeaders) {
+ // Ensure stream is set and method is valid
+ NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE);
+
+ {
+ DebugOnly<nsCOMPtr<nsIMIMEInputStream>> mimeStream;
+ MOZ_ASSERT(
+ !aStreamHasHeaders || NS_FAILED(CallQueryInterface(
+ aStream, getter_AddRefs(mimeStream.value))),
+ "nsIMIMEInputStream should not include headers");
+ }
+
+ nsresult rv = SetRequestMethod(aMethod);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!aStreamHasHeaders && !aContentType.IsVoid()) {
+ if (aContentType.IsEmpty()) {
+ SetEmptyRequestHeader("Content-Type"_ns);
+ } else {
+ SetRequestHeader("Content-Type"_ns, aContentType, false);
+ }
+ }
+
+ StoreUploadStreamHasHeaders(aStreamHasHeaders);
+
+ return InternalSetUploadStream(aStream, aContentLength, !aStreamHasHeaders);
+}
+
+nsresult HttpBaseChannel::InternalSetUploadStream(
+ nsIInputStream* aUploadStream, int64_t aContentLength,
+ bool aSetContentLengthHeader) {
+ // If we're not on the main thread, such as for TRR, the content length must
+ // be provided, as we can't normalize our upload stream.
+ if (!NS_IsMainThread()) {
+ if (aContentLength < 0) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload content length must be explicit off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aUploadStream);
+ if (!NS_InputStreamIsCloneable(aUploadStream) || !seekable) {
+ MOZ_ASSERT_UNREACHABLE(
+ "Upload stream must be cloneable & seekable off-main-thread");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ mUploadStream = aUploadStream;
+ ExplicitSetUploadStreamLength(aContentLength, aSetContentLengthHeader);
+ return NS_OK;
+ }
+
+ // Normalize the upload stream we're provided to ensure that it is cloneable,
+ // seekable, and synchronous when in the parent process.
+ //
+ // This might be an async operation, in which case ready will be returned and
+ // resolved when the operation is complete.
+ nsCOMPtr<nsIInputStream> replacement;
+ RefPtr<GenericPromise> ready;
+ if (XRE_IsParentProcess()) {
+ nsresult rv = NormalizeUploadStream(
+ aUploadStream, getter_AddRefs(replacement), getter_AddRefs(ready));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ mUploadStream = replacement ? replacement.get() : aUploadStream;
+
+ // Once the upload stream is ready, fetch its length before proceeding with
+ // AsyncOpen.
+ auto onReady = [self = RefPtr{this}, aContentLength, aSetContentLengthHeader,
+ stream = mUploadStream]() {
+ auto setLengthAndResume = [self, aSetContentLengthHeader](int64_t aLength) {
+ self->StorePendingUploadStreamNormalization(false);
+ self->ExplicitSetUploadStreamLength(aLength >= 0 ? aLength : 0,
+ aSetContentLengthHeader);
+ self->MaybeResumeAsyncOpen();
+ };
+
+ if (aContentLength >= 0) {
+ setLengthAndResume(aContentLength);
+ return;
+ }
+
+ int64_t length;
+ if (InputStreamLengthHelper::GetSyncLength(stream, &length)) {
+ setLengthAndResume(length);
+ return;
+ }
+
+ InputStreamLengthHelper::GetAsyncLength(stream, setLengthAndResume);
+ };
+ StorePendingUploadStreamNormalization(true);
+
+ // Resolve onReady synchronously unless a promise is returned.
+ if (ready) {
+ ready->Then(GetCurrentSerialEventTarget(), __func__,
+ [onReady = std::move(onReady)](
+ GenericPromise::ResolveOrRejectValue&&) { onReady(); });
+ } else {
+ onReady();
+ }
+ return NS_OK;
+}
+
+void HttpBaseChannel::ExplicitSetUploadStreamLength(
+ uint64_t aContentLength, bool aSetContentLengthHeader) {
+ // We already have the content length. We don't need to determinate it.
+ mReqContentLength = aContentLength;
+
+ if (!aSetContentLengthHeader) {
+ return;
+ }
+
+ nsAutoCString header;
+ header.AssignLiteral("Content-Length");
+
+ // Maybe the content-length header has been already set.
+ nsAutoCString value;
+ nsresult rv = GetRequestHeader(header, value);
+ if (NS_SUCCEEDED(rv) && !value.IsEmpty()) {
+ return;
+ }
+
+ // SetRequestHeader propagates headers to chrome if HttpChannelChild
+ MOZ_ASSERT(!LoadWasOpened());
+ nsAutoCString contentLengthStr;
+ contentLengthStr.AppendInt(aContentLength);
+ SetRequestHeader(header, contentLengthStr, false);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetUploadStreamHasHeaders(bool* hasHeaders) {
+ NS_ENSURE_ARG(hasHeaders);
+
+ *hasHeaders = LoadUploadStreamHasHeaders();
+ return NS_OK;
+}
+
+bool HttpBaseChannel::MaybeWaitForUploadStreamNormalization(
+ nsIStreamListener* aListener, nsISupports* aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadAsyncOpenWaitingForStreamNormalization(),
+ "AsyncOpen() called twice?");
+
+ if (!LoadPendingUploadStreamNormalization()) {
+ return false;
+ }
+
+ mListener = aListener;
+ StoreAsyncOpenWaitingForStreamNormalization(true);
+ return true;
+}
+
+void HttpBaseChannel::MaybeResumeAsyncOpen() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!LoadPendingUploadStreamNormalization());
+
+ if (!LoadAsyncOpenWaitingForStreamNormalization()) {
+ return;
+ }
+
+ nsCOMPtr<nsIStreamListener> listener;
+ listener.swap(mListener);
+
+ StoreAsyncOpenWaitingForStreamNormalization(false);
+
+ nsresult rv = AsyncOpen(listener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ DoAsyncAbort(rv);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIEncodedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApplyConversion(bool* value) {
+ *value = LoadApplyConversion();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetApplyConversion(bool value) {
+ LOG(("HttpBaseChannel::SetApplyConversion [this=%p value=%d]\n", this,
+ value));
+ StoreApplyConversion(value);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::DoApplyContentConversions(
+ nsIStreamListener* aNextListener, nsIStreamListener** aNewNextListener) {
+ return DoApplyContentConversions(aNextListener, aNewNextListener, nullptr);
+}
+
+// create a listener chain that looks like this
+// http-channel -> decompressor (n times) -> InterceptFailedOnSTop ->
+// channel-creator-listener
+//
+// we need to do this because not every decompressor has fully streamed output
+// so may need a call to OnStopRequest to identify its completion state.. and if
+// it creates an error there the channel status code needs to be updated before
+// calling the terminal listener. Having the decompress do it via cancel() means
+// channels cannot effectively be used in two contexts (specifically this one
+// and a peek context for sniffing)
+//
+class InterceptFailedOnStop : public nsIStreamListener {
+ virtual ~InterceptFailedOnStop() = default;
+ nsCOMPtr<nsIStreamListener> mNext;
+ HttpBaseChannel* mChannel;
+
+ public:
+ InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan)
+ : mNext(arg), mChannel(chan) {}
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD OnStartRequest(nsIRequest* aRequest) override {
+ return mNext->OnStartRequest(aRequest);
+ }
+
+ NS_IMETHOD OnStopRequest(nsIRequest* aRequest,
+ nsresult aStatusCode) override {
+ if (NS_FAILED(aStatusCode) && NS_SUCCEEDED(mChannel->mStatus)) {
+ LOG(("HttpBaseChannel::InterceptFailedOnStop %p seting status %" PRIx32,
+ mChannel, static_cast<uint32_t>(aStatusCode)));
+ mChannel->mStatus = aStatusCode;
+ }
+ return mNext->OnStopRequest(aRequest, aStatusCode);
+ }
+
+ NS_IMETHOD OnDataAvailable(nsIRequest* aRequest, nsIInputStream* aInputStream,
+ uint64_t aOffset, uint32_t aCount) override {
+ return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount);
+ }
+};
+
+NS_IMPL_ISUPPORTS(InterceptFailedOnStop, nsIStreamListener, nsIRequestObserver)
+
+NS_IMETHODIMP
+HttpBaseChannel::DoApplyContentConversions(nsIStreamListener* aNextListener,
+ nsIStreamListener** aNewNextListener,
+ nsISupports* aCtxt) {
+ *aNewNextListener = nullptr;
+ if (!mResponseHead || !aNextListener) {
+ return NS_OK;
+ }
+
+ LOG(("HttpBaseChannel::DoApplyContentConversions [this=%p]\n", this));
+
+ if (!LoadApplyConversion()) {
+ LOG(("not applying conversion per ApplyConversion\n"));
+ return NS_OK;
+ }
+
+ if (LoadHasAppliedConversion()) {
+ LOG(("not applying conversion because HasAppliedConversion is true\n"));
+ return NS_OK;
+ }
+
+ if (LoadDeliveringAltData()) {
+ MOZ_ASSERT(!mAvailableCachedAltDataType.IsEmpty());
+ LOG(("not applying conversion because delivering alt-data\n"));
+ return NS_OK;
+ }
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+ if (NS_FAILED(rv) || contentEncoding.IsEmpty()) return NS_OK;
+
+ nsCOMPtr<nsIStreamListener> nextListener =
+ new InterceptFailedOnStop(aNextListener, this);
+
+ // The encodings are listed in the order they were applied
+ // (see rfc 2616 section 14.11), so they need to removed in reverse
+ // order. This is accomplished because the converter chain ends up
+ // being a stack with the last converter created being the first one
+ // to accept the raw network data.
+
+ char* cePtr = contentEncoding.BeginWriting();
+ uint32_t count = 0;
+ while (char* val = nsCRT::strtok(cePtr, HTTP_LWS ",", &cePtr)) {
+ if (++count > 16) {
+ // That's ridiculous. We only understand 2 different ones :)
+ // but for compatibility with old code, we will just carry on without
+ // removing the encodings
+ LOG(("Too many Content-Encodings. Ignoring remainder.\n"));
+ break;
+ }
+
+ if (gHttpHandler->IsAcceptableEncoding(val,
+ isSecureOrTrustworthyURL(mURI))) {
+ RefPtr<nsHTTPCompressConv> converter = new nsHTTPCompressConv();
+ nsAutoCString from(val);
+ ToLowerCase(from);
+ rv = converter->AsyncConvertData(from.get(), "uncompressed", nextListener,
+ aCtxt);
+ if (NS_FAILED(rv)) {
+ LOG(("Unexpected failure of AsyncConvertData %s\n", val));
+ return rv;
+ }
+
+ LOG(("converter removed '%s' content-encoding\n", val));
+ if (Telemetry::CanRecordPrereleaseData()) {
+ int mode = 0;
+ if (from.EqualsLiteral("gzip") || from.EqualsLiteral("x-gzip")) {
+ mode = 1;
+ } else if (from.EqualsLiteral("deflate") ||
+ from.EqualsLiteral("x-deflate")) {
+ mode = 2;
+ } else if (from.EqualsLiteral("br")) {
+ mode = 3;
+ }
+ Telemetry::Accumulate(Telemetry::HTTP_CONTENT_ENCODING, mode);
+ }
+ nextListener = converter;
+ } else {
+ if (val) LOG(("Unknown content encoding '%s', ignoring\n", val));
+ }
+ }
+ *aNewNextListener = do_AddRef(nextListener).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings) {
+ if (!mResponseHead) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+
+ nsAutoCString encoding;
+ Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, encoding);
+ if (encoding.IsEmpty()) {
+ *aEncodings = nullptr;
+ return NS_OK;
+ }
+ RefPtr<nsContentEncodings> enumerator =
+ new nsContentEncodings(this, encoding.get());
+ enumerator.forget(aEncodings);
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <public>
+//-----------------------------------------------------------------------------
+
+HttpBaseChannel::nsContentEncodings::nsContentEncodings(
+ nsIHttpChannel* aChannel, const char* aEncodingHeader)
+ : mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(false) {
+ mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
+ mCurStart = mCurEnd;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISimpleEnumerator
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::HasMore(bool* aMoreEncodings) {
+ if (mReady) {
+ *aMoreEncodings = true;
+ return NS_OK;
+ }
+
+ nsresult rv = PrepareForNext();
+ *aMoreEncodings = NS_SUCCEEDED(rv);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding) {
+ aNextEncoding.Truncate();
+ if (!mReady) {
+ nsresult rv = PrepareForNext();
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ const nsACString& encoding = Substring(mCurStart, mCurEnd);
+
+ nsACString::const_iterator start, end;
+ encoding.BeginReading(start);
+ encoding.EndReading(end);
+
+ bool haveType = false;
+ if (CaseInsensitiveFindInReadable("gzip"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_GZIP);
+ haveType = true;
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("compress"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("deflate"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_ZIP);
+ haveType = true;
+ }
+ }
+
+ if (!haveType) {
+ encoding.BeginReading(start);
+ if (CaseInsensitiveFindInReadable("br"_ns, start, end)) {
+ aNextEncoding.AssignLiteral(APPLICATION_BROTLI);
+ haveType = true;
+ }
+ }
+
+ // Prepare to fetch the next encoding
+ mCurEnd = mCurStart;
+ mReady = false;
+
+ if (haveType) return NS_OK;
+
+ NS_WARNING("Unknown encoding type");
+ return NS_ERROR_FAILURE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings::nsISupports
+//-----------------------------------------------------------------------------
+
+NS_IMPL_ISUPPORTS(HttpBaseChannel::nsContentEncodings, nsIUTF8StringEnumerator,
+ nsIStringEnumerator)
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsContentEncodings <private>
+//-----------------------------------------------------------------------------
+
+nsresult HttpBaseChannel::nsContentEncodings::PrepareForNext(void) {
+ MOZ_ASSERT(mCurStart == mCurEnd, "Indeterminate state");
+
+ // At this point both mCurStart and mCurEnd point to somewhere
+ // past the end of the next thing we want to return
+
+ while (mCurEnd != mEncodingHeader) {
+ --mCurEnd;
+ if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd)) break;
+ }
+ if (mCurEnd == mEncodingHeader) {
+ return NS_ERROR_NOT_AVAILABLE; // no more encodings
+ }
+ ++mCurEnd;
+
+ // At this point mCurEnd points to the first char _after_ the
+ // header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
+
+ mCurStart = mCurEnd - 1;
+ while (mCurStart != mEncodingHeader && *mCurStart != ',' &&
+ !nsCRT::IsAsciiSpace(*mCurStart)) {
+ --mCurStart;
+ }
+ if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart)) {
+ ++mCurStart; // we stopped because of a weird char, so move up one
+ }
+
+ // At this point mCurStart and mCurEnd bracket the encoding string
+ // we want. Check that it's not "identity"
+ if (Substring(mCurStart, mCurEnd)
+ .Equals("identity", nsCaseInsensitiveCStringComparator)) {
+ mCurEnd = mCurStart;
+ return PrepareForNext();
+ }
+
+ mReady = true;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelId(uint64_t* aChannelId) {
+ NS_ENSURE_ARG_POINTER(aChannelId);
+ *aChannelId = mChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelId(uint64_t aChannelId) {
+ mChannelId = aChannelId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetTopLevelContentWindowId(uint64_t* aWindowId) {
+ if (!mContentWindowId) {
+ nsCOMPtr<nsILoadContext> loadContext;
+ GetCallback(loadContext);
+ if (loadContext) {
+ nsCOMPtr<mozIDOMWindowProxy> topWindow;
+ loadContext->GetTopWindow(getter_AddRefs(topWindow));
+ if (topWindow) {
+ if (nsPIDOMWindowInner* inner =
+ nsPIDOMWindowOuter::From(topWindow)->GetCurrentInnerWindow()) {
+ mContentWindowId = inner->WindowID();
+ }
+ }
+ }
+ }
+ *aWindowId = mContentWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetBrowserId(uint64_t aId) {
+ mBrowserId = aId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetBrowserId(uint64_t* aId) {
+ EnsureBrowserId();
+ *aId = mBrowserId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetTopLevelContentWindowId(uint64_t aWindowId) {
+ mContentWindowId = aWindowId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartyTrackingResource(bool* aIsTrackingResource) {
+ MOZ_ASSERT(
+ !(mFirstPartyClassificationFlags && mThirdPartyClassificationFlags));
+ *aIsTrackingResource = UrlClassifierCommon::IsTrackingClassificationFlag(
+ mThirdPartyClassificationFlags,
+ mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsThirdPartySocialTrackingResource(
+ bool* aIsThirdPartySocialTrackingResource) {
+ MOZ_ASSERT(!mFirstPartyClassificationFlags ||
+ !mThirdPartyClassificationFlags);
+ *aIsThirdPartySocialTrackingResource =
+ UrlClassifierCommon::IsSocialTrackingClassificationFlag(
+ mThirdPartyClassificationFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetClassificationFlags(uint32_t* aFlags) {
+ if (mThirdPartyClassificationFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ } else {
+ *aFlags = mFirstPartyClassificationFlags;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFirstPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mFirstPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyClassificationFlags(uint32_t* aFlags) {
+ *aFlags = mThirdPartyClassificationFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransferSize(uint64_t* aTransferSize) {
+ *aTransferSize = mTransferSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSize(uint64_t* aRequestSize) {
+ *aRequestSize = mRequestSize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDecodedBodySize(uint64_t* aDecodedBodySize) {
+ *aDecodedBodySize = mDecodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEncodedBodySize(uint64_t* aEncodedBodySize) {
+ *aEncodedBodySize = mEncodedBodySize;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSupportsHTTP3(bool* aSupportsHTTP3) {
+ *aSupportsHTTP3 = mSupportsHTTP3;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHasHTTPSRR(bool* aHasHTTPSRR) {
+ *aHasHTTPSRR = LoadHasHTTPSRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMethod(nsACString& aMethod) {
+ mRequestHead.Method(aMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMethod(const nsACString& aMethod) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ const nsCString& flatMethod = PromiseFlatCString(aMethod);
+
+ // Method names are restricted to valid HTTP tokens.
+ if (!nsHttp::IsValidToken(flatMethod)) return NS_ERROR_INVALID_ARG;
+
+ mRequestHead.SetMethod(flatMethod);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReferrerInfo(nsIReferrerInfo** aReferrerInfo) {
+ NS_ENSURE_ARG_POINTER(aReferrerInfo);
+ *aReferrerInfo = do_AddRef(mReferrerInfo).take();
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::SetReferrerInfoInternal(
+ nsIReferrerInfo* aReferrerInfo, bool aClone, bool aCompute,
+ bool aRespectBeforeConnect) {
+ LOG(
+ ("HttpBaseChannel::SetReferrerInfoInternal [this=%p aClone(%d) "
+ "aCompute(%d)]\n",
+ this, aClone, aCompute));
+ if (aRespectBeforeConnect) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ }
+
+ mReferrerInfo = aReferrerInfo;
+
+ // clear existing referrer, if any
+ nsresult rv = ClearReferrerHeader();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (!mReferrerInfo) {
+ return NS_OK;
+ }
+
+ if (aClone) {
+ mReferrerInfo = static_cast<dom::ReferrerInfo*>(aReferrerInfo)->Clone();
+ }
+
+ dom::ReferrerInfo* referrerInfo =
+ static_cast<dom::ReferrerInfo*>(mReferrerInfo.get());
+
+ // Don't set referrerInfo if it has not been initialized.
+ if (!referrerInfo->IsInitialized()) {
+ mReferrerInfo = nullptr;
+ return NS_ERROR_NOT_INITIALIZED;
+ }
+
+ if (aClone) {
+ // Record the telemetry once we set the referrer info to the channel
+ // successfully.
+ referrerInfo->RecordTelemetry(this);
+ }
+
+ if (aCompute) {
+ rv = referrerInfo->ComputeReferrer(this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+ }
+
+ nsCOMPtr<nsIURI> computedReferrer = mReferrerInfo->GetComputedReferrer();
+ if (!computedReferrer) {
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = computedReferrer->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ return SetReferrerHeader(spec, aRespectBeforeConnect);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfo(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, true, true, true);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReferrerInfoWithoutClone(nsIReferrerInfo* aReferrerInfo) {
+ return SetReferrerInfoInternal(aReferrerInfo, false, true, true);
+}
+
+// Return the channel's proxy URI, or if it doesn't exist, the
+// channel's main URI.
+NS_IMETHODIMP
+HttpBaseChannel::GetProxyURI(nsIURI** aOut) {
+ NS_ENSURE_ARG_POINTER(aOut);
+ nsCOMPtr<nsIURI> result(mProxyURI);
+ result.forget(aOut);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestHeader(const nsACString& aHeader,
+ nsACString& aValue) {
+ aValue.Truncate();
+
+ // XXX might be better to search the header list directly instead of
+ // hitting the http atom hash table.
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mRequestHead.GetHeader(atom, aValue);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestHeader(const nsACString& aHeader,
+ const nsACString& aValue, bool aMerge) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+ const nsCString& flatValue = PromiseFlatCString(aValue);
+
+ LOG(
+ ("HttpBaseChannel::SetRequestHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, flatHeader.get(), flatValue.get(), aMerge));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader) ||
+ !nsHttp::IsReasonableHeaderValue(flatValue)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mRequestHead.SetHeader(aHeader, flatValue, aMerge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewReferrerInfo(const nsACString& aUrl,
+ nsIReferrerInfo::ReferrerPolicyIDL aPolicy,
+ bool aSendReferrer) {
+ nsresult rv;
+ // Create URI from string
+ nsCOMPtr<nsIURI> aURI;
+ rv = NS_NewURI(getter_AddRefs(aURI), aUrl);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Create new ReferrerInfo and initialize it.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo = new mozilla::dom::ReferrerInfo();
+ rv = referrerInfo->Init(aPolicy, aSendReferrer, aURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ // Set ReferrerInfo
+ return SetReferrerInfo(referrerInfo);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEmptyRequestHeader(const nsACString& aHeader) {
+ const nsCString& flatHeader = PromiseFlatCString(aHeader);
+
+ LOG(("HttpBaseChannel::SetEmptyRequestHeader [this=%p header=\"%s\"]\n", this,
+ flatHeader.get()));
+
+ // Verify header names are valid HTTP tokens and header values are reasonably
+ // close to whats allowed in RFC 2616.
+ if (!nsHttp::IsValidToken(flatHeader)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return mRequestHead.SetEmptyHeader(aHeader);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitNonDefaultRequestHeaders(nsIHttpHeaderVisitor* visitor) {
+ return mRequestHead.VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterSkipDefault);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseHeader(const nsACString& header,
+ nsACString& value) {
+ value.Truncate();
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ return mResponseHead->GetHeader(atom, value);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseHeader(const nsACString& header,
+ const nsACString& value, bool merge) {
+ LOG(
+ ("HttpBaseChannel::SetResponseHeader [this=%p header=\"%s\" value=\"%s\" "
+ "merge=%u]\n",
+ this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(),
+ merge));
+
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(header);
+ if (!atom) return NS_ERROR_NOT_AVAILABLE;
+
+ // these response headers must not be changed
+ if (atom == nsHttp::Content_Type || atom == nsHttp::Content_Length ||
+ atom == nsHttp::Content_Encoding || atom == nsHttp::Trailer ||
+ atom == nsHttp::Transfer_Encoding) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+
+ StoreResponseHeadersModified(true);
+
+ return mResponseHead->SetHeader(header, value, merge);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitResponseHeaders(nsIHttpHeaderVisitor* visitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ return mResponseHead->VisitHeaders(visitor,
+ nsHttpHeaderArray::eFilterResponse);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOriginalResponseHeader(const nsACString& aHeader,
+ nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsHttpAtom atom = nsHttp::ResolveAtom(aHeader);
+ if (!atom) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->GetOriginalHeader(atom, aVisitor);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::VisitOriginalResponseHeaders(nsIHttpHeaderVisitor* aVisitor) {
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return mResponseHead->VisitHeaders(
+ aVisitor, nsHttpHeaderArray::eFilterResponseOriginal);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSTS(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadAllowSTS();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSTS(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreAllowSTS(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsOCSP(bool* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = LoadIsOCSP();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsOCSP(bool value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ StoreIsOCSP(value);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectionLimit(uint32_t* value) {
+ NS_ENSURE_ARG_POINTER(value);
+ *value = mRedirectionLimit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectionLimit(uint32_t value) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mRedirectionLimit = std::min<uint32_t>(value, 0xff);
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::OverrideSecurityInfo(
+ nsITransportSecurityInfo* aSecurityInfo) {
+ MOZ_ASSERT(!mSecurityInfo,
+ "This can only be called when we don't have a security info "
+ "object already");
+ MOZ_RELEASE_ASSERT(
+ aSecurityInfo,
+ "This can only be called with a valid security info object");
+ MOZ_ASSERT(!BypassServiceWorker(),
+ "This can only be called on channels that are not bypassing "
+ "interception");
+ MOZ_ASSERT(LoadResponseCouldBeSynthesized(),
+ "This can only be called on channels that can be intercepted");
+ if (mSecurityInfo) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo mSecurityInfo is null! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+ if (!LoadResponseCouldBeSynthesized()) {
+ LOG(
+ ("HttpBaseChannel::OverrideSecurityInfo channel cannot be intercepted! "
+ "[this=%p]\n",
+ this));
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ mSecurityInfo = aSecurityInfo;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoStoreResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoStore();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsNoCacheResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->NoCache();
+ if (!*value) *value = mResponseHead->ExpiresInPast();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::IsPrivateResponse(bool* value) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *value = mResponseHead->Private();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatus(uint32_t* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ *aValue = mResponseHead->Status();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStatusText(nsACString& aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ mResponseHead->StatusText(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestSucceeded(bool* aValue) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t status = mResponseHead->Status();
+ *aValue = (status / 100 == 2);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::RedirectTo(nsIURI* targetURI) {
+ NS_ENSURE_ARG(targetURI);
+
+ nsAutoCString spec;
+ targetURI->GetAsciiSpec(spec);
+ LOG(("HttpBaseChannel::RedirectTo [this=%p, uri=%s]", this, spec.get()));
+ LogCallingScriptLocation(this);
+
+ // We cannot redirect after OnStartRequest of the listener
+ // has been called, since to redirect we have to switch channels
+ // and the dance with OnStartRequest et al has to start over.
+ // This would break the nsIStreamListener contract.
+ NS_ENSURE_FALSE(LoadOnStartRequestCalled(), NS_ERROR_NOT_AVAILABLE);
+
+ mAPIRedirectToURI = targetURI;
+ // Only Web Extensions are allowed to redirect a channel to a data:
+ // URI. To avoid any bypasses after the channel was flagged by
+ // the WebRequst API, we are dropping the flag here.
+ mLoadInfo->SetAllowInsecureRedirectToDataURI(false);
+
+ // We may want to rewrite origin allowance, hence we need an
+ // artificial response head.
+ if (!mResponseHead) {
+ mResponseHead.reset(new nsHttpResponseHead());
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::UpgradeToSecure() {
+ // Upgrades are handled internally between http-on-modify-request and
+ // http-on-before-connect, which means upgrades are only possible during
+ // on-modify, or WebRequest.onBeforeRequest in Web Extensions. Once we are
+ // past the code path where upgrades are handled, attempting an upgrade
+ // will throw an error.
+ NS_ENSURE_TRUE(LoadUpgradableToSecure(), NS_ERROR_NOT_AVAILABLE);
+
+ StoreUpgradeToSecure(true);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestContextID(uint64_t* aRCID) {
+ NS_ENSURE_ARG_POINTER(aRCID);
+ *aRCID = mRequestContextID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestContextID(uint64_t aRCID) {
+ mRequestContextID = aRCID;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsMainDocumentChannel(bool* aValue) {
+ NS_ENSURE_ARG_POINTER(aValue);
+ *aValue = IsNavigation();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsMainDocumentChannel(bool aValue) {
+ StoreForceMainDocumentChannel(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetProtocolVersion(nsACString& aProtocolVersion) {
+ // Try to use ALPN if available and if it is not for a proxy, i.e if an
+ // https proxy was not used or if https proxy was used but the connection to
+ // the origin server is also https. In the case, an https proxy was used and
+ // the connection to the origin server was http, mSecurityInfo will be from
+ // the proxy.
+ if (!mConnectionInfo || !mConnectionInfo->UsingHttpsProxy() ||
+ mConnectionInfo->EndToEndSSL()) {
+ nsAutoCString protocol;
+ if (mSecurityInfo &&
+ NS_SUCCEEDED(mSecurityInfo->GetNegotiatedNPN(protocol)) &&
+ !protocol.IsEmpty()) {
+ // The negotiated protocol was not empty so we can use it.
+ aProtocolVersion = protocol;
+ return NS_OK;
+ }
+ }
+
+ if (mResponseHead) {
+ HttpVersion version = mResponseHead->Version();
+ aProtocolVersion.Assign(nsHttp::GetProtocolVersion(version));
+ return NS_OK;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIHttpChannelInternal
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTopWindowURIIfUnknown(nsIURI* aTopWindowURI) {
+ if (!aTopWindowURI) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (mTopWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "mTopWindowURI is already set.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIURI> topWindowURI;
+ Unused << GetTopWindowURI(getter_AddRefs(topWindowURI));
+
+ // Don't modify |mTopWindowURI| if we can get one from GetTopWindowURI().
+ if (topWindowURI) {
+ LOG(
+ ("HttpChannelBase::SetTopWindowURIIfUnknown [this=%p] "
+ "Return an error since we got a top window uri.\n",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ mTopWindowURI = aTopWindowURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTopWindowURI(nsIURI** aTopWindowURI) {
+ nsCOMPtr<nsIURI> uriBeingLoaded =
+ AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(this);
+ return GetTopWindowURI(uriBeingLoaded, aTopWindowURI);
+}
+
+nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded,
+ nsIURI** aTopWindowURI) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<mozIThirdPartyUtil> util;
+ // Only compute the top window URI once. In e10s, this must be computed in the
+ // child. The parent gets the top window URI through HttpChannelOpenArgs.
+ if (!mTopWindowURI) {
+ util = components::ThirdPartyUtil::Service();
+ if (!util) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> win;
+ rv = util->GetTopWindowForChannel(this, aURIBeingLoaded,
+ getter_AddRefs(win));
+ if (NS_SUCCEEDED(rv)) {
+ rv = util->GetURIFromWindow(win, getter_AddRefs(mTopWindowURI));
+#if DEBUG
+ if (mTopWindowURI) {
+ nsCString spec;
+ if (NS_SUCCEEDED(mTopWindowURI->GetSpec(spec))) {
+ LOG(("HttpChannelBase::Setting topwindow URI spec %s [this=%p]\n",
+ spec.get(), this));
+ }
+ }
+#endif
+ }
+ }
+ *aTopWindowURI = do_AddRef(mTopWindowURI).take();
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDocumentURI(nsIURI** aDocumentURI) {
+ NS_ENSURE_ARG_POINTER(aDocumentURI);
+ *aDocumentURI = do_AddRef(mDocumentURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDocumentURI(nsIURI* aDocumentURI) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ mDocumentURI = aDocumentURI;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestVersion(uint32_t* major, uint32_t* minor) {
+ HttpVersion version = mRequestHead.Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseVersion(uint32_t* major, uint32_t* minor) {
+ if (!mResponseHead) {
+ *major = *minor = 0; // we should at least be kind about it
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ HttpVersion version = mResponseHead->Version();
+
+ if (major) {
+ *major = static_cast<uint32_t>(version) / 10;
+ }
+ if (minor) {
+ *minor = static_cast<uint32_t>(version) % 10;
+ }
+
+ return NS_OK;
+}
+
+void HttpBaseChannel::NotifySetCookie(const nsACString& aCookie) {
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ obs->NotifyObservers(static_cast<nsIChannel*>(this),
+ "http-on-response-set-cookie",
+ NS_ConvertASCIItoUTF16(aCookie).get());
+ }
+}
+
+bool HttpBaseChannel::IsBrowsingContextDiscarded() const {
+ // If there is no loadGroup attached to the current channel, we check the
+ // global private browsing state for the private channel instead. For
+ // non-private channel, we will always return false here.
+ //
+ // Note that we can only access the global private browsing state in the
+ // parent process. So, we will fallback to just return false in the content
+ // process.
+ if (!mLoadGroup) {
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+
+ return mLoadInfo->GetOriginAttributes().mPrivateBrowsingId != 0 &&
+ !dom::CanonicalBrowsingContext::IsPrivateBrowsingActive();
+ }
+
+ return mLoadGroup->GetIsBrowsingContextDiscarded();
+}
+
+// https://mikewest.github.io/corpp/#process-navigation-response
+nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() {
+ nsresult rv;
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Embedder-Policy for document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT &&
+ mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ return NS_OK;
+ }
+
+ nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ // https://html.spec.whatwg.org/multipage/origin.html#coep
+ if (mLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SUBDOCUMENT &&
+ !nsHttpChannel::IsRedirectStatus(mResponseHead->Status()) &&
+ mLoadInfo->GetLoadingEmbedderPolicy() !=
+ nsILoadInfo::EMBEDDER_POLICY_NULL &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP &&
+ resultPolicy != nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ return NS_ERROR_DOM_COEP_FAILED;
+ }
+
+ return NS_OK;
+}
+
+// https://mikewest.github.io/corpp/#corp-check
+nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() {
+ // Fetch 4.5.9
+ dom::RequestMode requestMode;
+ MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode));
+ // XXX this seems wrong per spec? What about navigate
+ if (requestMode != RequestMode::No_cors) {
+ return NS_OK;
+ }
+
+ // We only apply this for resources.
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ return NS_OK;
+ }
+
+ if (extContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // COEP pref off, skip CORP checking for subdocument.
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ return NS_OK;
+ }
+ // COEP 3.2.1.2 when request targets a nested browsing context then embedder
+ // policy value is "unsafe-none", then return allowed.
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_NULL) {
+ return NS_OK;
+ }
+ }
+
+ MOZ_ASSERT(mLoadInfo->GetLoadingPrincipal(),
+ "Resources should always have a LoadingPrincipal");
+ if (!mResponseHead) {
+ return NS_OK;
+ }
+
+ if (mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()) {
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy,
+ content);
+
+ if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) {
+ if (content.IsEmpty()) {
+ if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) {
+ bool requestIncludesCredentials = false;
+ nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials);
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+ // COEP: Set policy to `same-origin` if: response’s
+ // request-includes-credentials is true, or forNavigation is true.
+ if (requestIncludesCredentials ||
+ extContentPolicyType == ExtContentPolicyType::TYPE_SUBDOCUMENT) {
+ content = "same-origin"_ns;
+ }
+ } else if (mLoadInfo->GetLoadingEmbedderPolicy() ==
+ nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) {
+ // COEP 3.2.1.6 If policy is null, and embedder policy is
+ // "require-corp", set policy to "same-origin". Note that we treat
+ // invalid value as "cross-origin", which spec indicates. We might want
+ // to make that stricter.
+ content = "same-origin"_ns;
+ }
+ }
+ }
+
+ if (content.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> channelOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(channelOrigin));
+
+ // Cross-Origin-Resource-Policy = %s"same-origin" / %s"same-site" /
+ // %s"cross-origin"
+ if (content.EqualsLiteral("same-origin")) {
+ if (!channelOrigin->Equals(mLoadInfo->GetLoadingPrincipal())) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+ return NS_OK;
+ }
+ if (content.EqualsLiteral("same-site")) {
+ nsAutoCString documentBaseDomain;
+ nsAutoCString resourceBaseDomain;
+ mLoadInfo->GetLoadingPrincipal()->GetBaseDomain(documentBaseDomain);
+ channelOrigin->GetBaseDomain(resourceBaseDomain);
+ if (documentBaseDomain != resourceBaseDomain) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ nsCOMPtr<nsIURI> resourceURI = channelOrigin->GetURI();
+ if (!mLoadInfo->GetLoadingPrincipal()->SchemeIs("https") &&
+ resourceURI->SchemeIs("https")) {
+ return NS_ERROR_DOM_CORP_FAILED;
+ }
+
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+// This method runs steps 1-4 of the algorithm to compare
+// cross-origin-opener policies
+static bool CompareCrossOriginOpenerPolicies(
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy,
+ nsIPrincipal* documentOrigin,
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy,
+ nsIPrincipal* resultOrigin) {
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return true;
+ }
+
+ if (documentPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE ||
+ resultPolicy == nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ return false;
+ }
+
+ if (documentPolicy == resultPolicy && documentOrigin->Equals(resultOrigin)) {
+ return true;
+ }
+
+ return false;
+}
+
+// This runs steps 1-5 of the algorithm when navigating a top level document.
+// See https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+nsresult HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ StoreHasCrossOriginOpenerPolicyMismatch(false);
+ if (!StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy()) {
+ return NS_OK;
+ }
+
+ // Only consider Cross-Origin-Opener-Policy for toplevel document loads.
+ if (mLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ return NS_OK;
+ }
+
+ // Maybe the channel failed and we have no response head?
+ if (!mResponseHead) {
+ // Not having a response head is not a hard failure at the point where
+ // this method is called.
+ return NS_OK;
+ }
+
+ RefPtr<mozilla::dom::BrowsingContext> ctx;
+ mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx));
+
+ // In xpcshell-tests we don't always have a browsingContext
+ if (!ctx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> resultOrigin;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultOrigin));
+
+ // Get the policy of the active document, and the policy for the result.
+ nsILoadInfo::CrossOriginOpenerPolicy documentPolicy = ctx->GetOpenerPolicy();
+ nsILoadInfo::CrossOriginOpenerPolicy resultPolicy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+ Unused << ComputeCrossOriginOpenerPolicy(documentPolicy, &resultPolicy);
+ mComputedCrossOriginOpenerPolicy = resultPolicy;
+
+ // Add a permission to mark this site as high-value into the permission DB.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ mozilla::dom::AddHighValuePermission(
+ resultOrigin, mozilla::dom::kHighValueCOOPPermission);
+ }
+
+ // If bc's popup sandboxing flag set is not empty and potentialCOOP is
+ // non-null, then navigate bc to a network error and abort these steps.
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE &&
+ mLoadInfo->GetSandboxFlags()) {
+ LOG((
+ "HttpBaseChannel::ComputeCrossOriginOpenerPolicyMismatch network error "
+ "for non empty sandboxing and non null COOP"));
+ return NS_ERROR_DOM_COOP_FAILED;
+ }
+
+ // In xpcshell-tests we don't always have a current window global
+ RefPtr<mozilla::dom::WindowGlobalParent> currentWindowGlobal =
+ ctx->Canonical()->GetCurrentWindowGlobal();
+ if (!currentWindowGlobal) {
+ return NS_OK;
+ }
+
+ // We use the top window principal as the documentOrigin
+ nsCOMPtr<nsIPrincipal> documentOrigin =
+ currentWindowGlobal->DocumentPrincipal();
+
+ bool compareResult = CompareCrossOriginOpenerPolicies(
+ documentPolicy, documentOrigin, resultPolicy, resultOrigin);
+
+ if (LOG_ENABLED()) {
+ LOG(
+ ("HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch - "
+ "doc:%d result:%d - compare:%d\n",
+ documentPolicy, resultPolicy, compareResult));
+ nsAutoCString docOrigin("(null)");
+ nsCOMPtr<nsIURI> uri = documentOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(docOrigin);
+ }
+ nsAutoCString resOrigin("(null)");
+ uri = resultOrigin->GetURI();
+ if (uri) {
+ uri->GetSpec(resOrigin);
+ }
+ LOG(("doc origin:%s - res origin: %s\n", docOrigin.get(), resOrigin.get()));
+ }
+
+ if (compareResult) {
+ return NS_OK;
+ }
+
+ // If one of the following is false:
+ // - document's policy is same-origin-allow-popups
+ // - resultPolicy is null
+ // - doc is the initial about:blank document
+ // then we have a mismatch.
+
+ if (documentPolicy != nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (resultPolicy != nsILoadInfo::OPENER_POLICY_UNSAFE_NONE) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ if (!currentWindowGlobal->IsInitialDocument()) {
+ StoreHasCrossOriginOpenerPolicyMismatch(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+nsresult HttpBaseChannel::ProcessCrossOriginSecurityHeaders() {
+ StoreProcessCrossOriginSecurityHeadersCalled(true);
+ nsresult rv = ProcessCrossOriginEmbedderPolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ rv = ProcessCrossOriginResourcePolicyHeader();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ return ComputeCrossOriginOpenerPolicyMismatch();
+}
+
+enum class Report { Error, Warning };
+
+// Helper Function to report messages to the console when the loaded
+// script had a wrong MIME type.
+void ReportMimeTypeMismatch(HttpBaseChannel* aChannel, const char* aMessageName,
+ nsIURI* aURI, const nsACString& aContentType,
+ Report report) {
+ NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
+ NS_ConvertUTF8toUTF16 contentType(aContentType);
+
+ aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
+ report == Report::Warning, spec, contentType);
+}
+
+// Check and potentially enforce X-Content-Type-Options: nosniff
+nsresult ProcessXCTO(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ // 1) Query the XCTO header and check if 'nosniff' is the first value.
+ nsAutoCString contentTypeOptionsHeader;
+ if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
+ // if failed to get XCTO header, then there is nothing to do.
+ return NS_OK;
+ }
+
+ // let's compare the header (ignoring case)
+ // e.g. "NoSniFF" -> "nosniff"
+ // if it's not 'nosniff' then there is nothing to do here
+ if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
+ // since we are getting here, the XCTO header was sent;
+ // a non matching value most likely means a mistake happenend;
+ // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
+ AutoTArray<nsString, 1> params;
+ CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
+ RefPtr<dom::Document> doc;
+ aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc,
+ nsContentUtils::eSECURITY_PROPERTIES,
+ "XCTOHeaderValueMissing", params);
+ return NS_OK;
+ }
+
+ // 2) Query the content type from the channel
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+
+ // 3) Compare the expected MIME type with the actual type
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_STYLESHEET) {
+ if (contentType.EqualsLiteral(TEXT_CSS)) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_SCRIPT) {
+ if (nsContentUtils::IsJavascriptMIMEType(
+ NS_ConvertUTF8toUTF16(contentType))) {
+ return NS_OK;
+ }
+ ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
+ Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ auto policyType = aLoadInfo->GetExternalContentPolicyType();
+ if (policyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ policyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // If the header XCTO nosniff is set for any browsing context, then
+ // we set the skipContentSniffing flag on the Loadinfo. Within
+ // GetMIMETypeFromContent we then bail early and do not do any sniffing.
+ aLoadInfo->SetSkipContentSniffing(true);
+ return NS_OK;
+ }
+
+ return NS_OK;
+}
+
+// Ensure that a load of type script has correct MIME type
+nsresult EnsureMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ return NS_OK;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // if this is not a script load, then there is nothing to do
+ return NS_OK;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+
+ if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ // script load has type script
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
+ return NS_OK;
+ }
+
+ switch (aLoadInfo->InternalContentPolicyType()) {
+ case nsIContentPolicy::TYPE_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
+ case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
+ case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER:
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE:
+ case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
+ break;
+ case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
+ case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected script type");
+ break;
+ }
+
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI)) {
+ // same origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
+ } else {
+ bool cors = false;
+ nsAutoCString corsOrigin;
+ nsresult rv = aResponseHead->GetHeader(
+ nsHttp::ResolveAtom("Access-Control-Allow-Origin"_ns), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (corsOrigin.Equals("*")) {
+ cors = true;
+ } else {
+ nsCOMPtr<nsIURI> corsOriginURI;
+ rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
+ if (NS_SUCCEEDED(rv)) {
+ if (aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(corsOriginURI)) {
+ cors = true;
+ }
+ }
+ }
+ }
+ if (cors) {
+ // cors origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
+ } else {
+ // cross origin
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
+ }
+ }
+
+ bool block = false;
+ if (StringBeginsWith(contentType, "image/"_ns)) {
+ // script load has type image
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
+ block = true;
+ } else if (StringBeginsWith(contentType, "audio/"_ns)) {
+ // script load has type audio
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
+ block = true;
+ } else if (StringBeginsWith(contentType, "video/"_ns)) {
+ // script load has type video
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
+ block = true;
+ } else if (StringBeginsWith(contentType, "text/csv"_ns)) {
+ // script load has type text/csv
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
+ block = true;
+ }
+
+ if (block) {
+ ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (StringBeginsWith(contentType, "text/plain"_ns)) {
+ // script load has type text/plain
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
+ } else if (StringBeginsWith(contentType, "text/xml"_ns)) {
+ // script load has type text/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
+ } else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) {
+ // script load has type application/octet-stream
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
+ } else if (StringBeginsWith(contentType, "application/xml"_ns)) {
+ // script load has type application/xml
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
+ } else if (StringBeginsWith(contentType, "application/json"_ns)) {
+ // script load has type application/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
+ } else if (StringBeginsWith(contentType, "text/json"_ns)) {
+ // script load has type text/json
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
+ } else if (StringBeginsWith(contentType, "text/html"_ns)) {
+ // script load has type text/html
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
+ } else if (contentType.IsEmpty()) {
+ // script load has no type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
+ } else {
+ // script load has unknown type
+ AccumulateCategorical(
+ Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
+ }
+
+ // We restrict importScripts() in worker code to JavaScript MIME types.
+ nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_STATIC_MODULE) {
+ ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
+ aURI, contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ // Do not block the load if the feature is not enabled.
+ if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
+ return NS_OK;
+ }
+
+ ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ // ES6 modules require a strict MIME type check.
+ if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
+ internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
+ ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
+ contentType, Report::Error);
+ return NS_ERROR_CORRUPTED_CONTENT;
+ }
+
+ return NS_OK;
+}
+
+// Warn when a load of type script uses a wrong MIME type and
+// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
+void WarnWrongMIMEOfScript(HttpBaseChannel* aChannel, nsIURI* aURI,
+ nsHttpResponseHead* aResponseHead,
+ nsILoadInfo* aLoadInfo) {
+ if (!aURI || !aResponseHead || !aLoadInfo) {
+ // If there is no uri, no response head or no loadInfo, then there is
+ // nothing to do.
+ return;
+ }
+
+ if (aLoadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_SCRIPT) {
+ // If this is not a script load, then there is nothing to do.
+ return;
+ }
+
+ bool succeeded;
+ MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded));
+ if (!succeeded) {
+ // Do not warn for failed loads: HTTP error pages are usually in HTML.
+ return;
+ }
+
+ nsAutoCString contentType;
+ aResponseHead->ContentType(contentType);
+ NS_ConvertUTF8toUTF16 typeString(contentType);
+ if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
+ ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
+ contentType, Report::Warning);
+ }
+}
+
+nsresult HttpBaseChannel::ValidateMIMEType() {
+ nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = ProcessXCTO(this, mURI, mResponseHead.get(), mLoadInfo);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ WarnWrongMIMEOfScript(this, mURI, mResponseHead.get(), mLoadInfo);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::ShouldFilterOpaqueResponse(
+ OpaqueResponseFilterFetch aFilterType) const {
+ MOZ_DIAGNOSTIC_ASSERT(ShouldBlockOpaqueResponse());
+
+ if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
+ return false;
+ }
+
+ // We should filter a response in the parent if it is opaque and is the result
+ // of a fetch() function from the Fetch specification.
+ return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
+}
+
+bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
+ if (!mURI || !mResponseHead || !mLoadInfo) {
+ // if there is no uri, no response head or no loadInfo, then there is
+ // nothing to do
+ LOGORB("No block: no mURI, mResponseHead, or mLoadInfo");
+ return false;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal = mLoadInfo->GetLoadingPrincipal();
+ if (!principal || principal->IsSystemPrincipal()) {
+ // If it's a top-level load or a system principal, then there is nothing to
+ // do.
+ LOGORB("No block: top-level load or system principal");
+ return false;
+ }
+
+ // Check if the response is a opaque response, which means requestMode should
+ // be RequestMode::No_cors and responseType should be ResponseType::Opaque.
+ nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();
+
+ // Skip the RequestMode would be RequestMode::Navigate
+ if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_FRAME ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_IFRAME ||
+ // Skip the RequestMode would be RequestMode::Same_origin
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+ contentPolicy == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
+ return false;
+ }
+
+ uint32_t securityMode = mLoadInfo->GetSecurityMode();
+ // Skip when RequestMode would not be RequestMode::no_cors
+ if (securityMode !=
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT &&
+ securityMode != nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL) {
+ LOGORB("No block: not no_cors requests");
+ return false;
+ }
+
+ // Only continue when ResponseType would be ResponseType::Opaque
+ if (mLoadInfo->GetTainting() != mozilla::LoadTainting::Opaque) {
+ LOGORB("No block: not opaque response");
+ return false;
+ }
+
+ auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType();
+ if (extContentPolicyType == ExtContentPolicy::TYPE_OBJECT ||
+ extContentPolicyType == ExtContentPolicy::TYPE_OBJECT_SUBREQUEST ||
+ extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET ||
+ extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) {
+ LOGORB("No block: object || websocket request || save as download");
+ return false;
+ }
+
+ // Ignore the request from object or embed elements
+ if (mLoadInfo->GetIsFromObjectOrEmbed()) {
+ LOGORB("No block: Request From <object> or <embed>");
+ return false;
+ }
+
+ // Exclude no_cors System XHR
+ if (extContentPolicyType == ExtContentPolicy::TYPE_XMLHTTPREQUEST) {
+ if (securityMode ==
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT) {
+ LOGORB("No block: System XHR");
+ return false;
+ }
+ }
+
+ uint32_t httpsOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
+ if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_BYPASS_ORB) {
+ LOGORB("No block: HTTPS_ONLY_BYPASS_ORB");
+ return false;
+ }
+
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ LOGORB("No block: Request created by devtools");
+ return false;
+ }
+
+ return true;
+}
+
+OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
+ OpaqueResponseBlocker* aORB, const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason,
+ const char* aFormat, ...) {
+ NimbusFeatures::RecordExposureEvent("opaqueResponseBlocking"_ns, true);
+
+ const bool shouldFilter =
+ ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);
+
+ if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
+ va_list ap;
+ va_start(ap, aFormat);
+ nsVprintfCString logString(aFormat, ap);
+ va_end(ap);
+
+ LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
+ }
+
+ if (shouldFilter) {
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::FILTERED_FETCH);
+ // The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
+ // called before or after sniffing has completed.
+ // Another requirement is that `OpaqueResponseFilter` must come after
+ // `OpaqueResponseBlocker`, which is why in the case of having an
+ // `OpaqueResponseBlocker` we let it handle creating an
+ // `OpaqueResponseFilter`.
+ if (aORB) {
+ MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
+ aORB->FilterResponse();
+ } else {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+ return OpaqueResponse::Allow;
+ }
+
+ LogORBError(aReason, aTelemetryReason);
+ return OpaqueResponse::Block;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse
+HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
+ if (!ShouldBlockOpaqueResponse()) {
+ return OpaqueResponse::Allow;
+ }
+
+ // Regardless of if ORB is enabled or not, we check if we should filter the
+ // response in the parent. This way data won't reach a content process that
+ // will create a filtered `Response` object. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::All`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
+ mListener = new OpaqueResponseFilter(mListener);
+
+ // If we're filtering a response in the parent, there will be no data to
+ // determine if it should be blocked or not so the only option we have is to
+ // allow it.
+ return OpaqueResponse::Allow;
+ }
+
+ if (!mCachedOpaqueResponseBlockingPref) {
+ return OpaqueResponse::Allow;
+ }
+
+ // If ORB is enabled, we check if we should filter the response in the parent.
+ // This way data won't reach a content process that will create a filtered
+ // `Response` object. We allow ORB to determine if the response should be
+ // blocked or filtered, but regardless no data should reach the content
+ // process. This is enabled when
+ // 'browser.opaqueResponseBlocking.filterFetchResponse' is
+ // `OpaqueResponseFilterFetch::AllowedByORB`.
+ // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
+ if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
+ mListener = new OpaqueResponseFilter(mListener);
+ }
+
+ Telemetry::ScalarAdd(
+ Telemetry::ScalarID::
+ OPAQUE_RESPONSE_BLOCKING_CROSS_ORIGIN_OPAQUE_RESPONSE_COUNT,
+ 1);
+
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "Before sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ // Step 1
+ nsAutoCString contentType;
+ mResponseHead->ContentType(contentType);
+
+ // Step 2
+ nsAutoCString contentTypeOptionsHeader;
+ bool nosniff =
+ mResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader) &&
+ contentTypeOptionsHeader.EqualsIgnoreCase("nosniff");
+
+ // Step 3
+ switch (GetOpaqueResponseBlockedReason(contentType, mResponseHead->Status(),
+ nosniff)) {
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED:
+ // Step 3.1
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING:
+ LOGORB("Allowed %s in a spec breaking way", contentType.get());
+ return OpaqueResponse::Allow;
+ case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::MIME_NEVER_SNIFFED,
+ "BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
+ case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
+ // Step 3.3
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "BLOCKED_206_AND_BLOCKEDLISTED");
+ case OpaqueResponseBlockedReason::
+ BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
+ // Step 3.4
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
+ OpaqueResponseBlockedTelemetryReason::NOSNIFF_BLC_OR_TEXTP,
+ "BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
+ default:
+ break;
+ }
+
+ // Step 4
+ // If it's a media subsequent request, we assume that it will only be made
+ // after a successful initial request.
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ bool isMediaInitialRequest;
+ mLoadInfo->GetIsMediaInitialRequest(&isMediaInitialRequest);
+ if (!isMediaInitialRequest) {
+ return OpaqueResponse::Allow;
+ }
+ }
+
+ // Step 5
+ if (mResponseHead->Status() == 206 &&
+ !IsFirstPartialResponse(*mResponseHead)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"response status is 206 and not first partial response"_ns,
+ OpaqueResponseBlockedTelemetryReason::RESP_206_BLCLISTED,
+ "Is not a valid partial response given 0");
+ }
+
+ // Setup for steps 6, 7, 8 and 10.
+ // Steps 6 and 7 are handled by the sniffer framework.
+ // Steps 8 and 10 by are handled by
+ // `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+ if (mLoadFlags & nsIChannel::LOAD_CALL_CONTENT_SNIFFERS) {
+ mSnifferCategoryType = SnifferCategoryType::All;
+ } else {
+ mSnifferCategoryType = SnifferCategoryType::OpaqueResponseBlocking;
+ }
+
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+
+ // Install an input stream listener that performs ORB checks that depend on
+ // inspecting the incoming data. It is crucial that `OnStartRequest` is called
+ // on this listener either after sniffing is completed or that we skip
+ // sniffing, otherwise `OpaqueResponseBlocker` will allow responses that it
+ // shouldn't.
+ mORB = new OpaqueResponseBlocker(mListener, this, contentType, nosniff);
+ mListener = mORB;
+
+ nsAutoCString contentEncoding;
+ nsresult rv =
+ mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
+
+ if (NS_SUCCEEDED(rv) && !contentEncoding.IsEmpty()) {
+ return OpaqueResponse::SniffCompressed;
+ }
+ mLoadFlags |= (nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
+ nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE);
+ return OpaqueResponse::Sniff;
+}
+
+// The specification for ORB is currently being written:
+// https://whatpr.org/fetch/1442.html#orb-algorithm
+// The `opaque-response-safelist check` is implemented in:
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff`
+// * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck`
+// * `HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff`
+// * `OpaqueResponseBlocker::ValidateJavaScript`
+OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
+ const nsACString& aContentType, bool aNoSniff) {
+ PROFILER_MARKER_TEXT("ORB safelist check", NETWORK, {}, "After sniff"_ns);
+
+ // https://whatpr.org/fetch/1442.html#orb-algorithm
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(mCachedOpaqueResponseBlockingPref);
+
+ // Step 9
+ bool isMediaRequest;
+ mLoadInfo->GetIsMediaRequest(&isMediaRequest);
+ if (isMediaRequest) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: media request"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_MEDIA,
+ "media request");
+ }
+
+ // Step 11
+ if (aNoSniff) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: nosniff is true"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_NOSNIFF, "nosniff");
+ }
+
+ // Step 12
+ if (mResponseHead &&
+ (mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB, u"after sniff: status code is not in allowed range"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_STA_CODE,
+ "status code (%d) is not allowed", mResponseHead->Status());
+ }
+
+ // Step 13
+ if (!mResponseHead || aContentType.IsEmpty()) {
+ LOGORB("Allowed: mimeType is failure");
+ return OpaqueResponse::Allow;
+ }
+
+ // Step 14
+ if (StringBeginsWith(aContentType, "image/"_ns) ||
+ StringBeginsWith(aContentType, "video/"_ns) ||
+ StringBeginsWith(aContentType, "audio/"_ns)) {
+ return BlockOrFilterOpaqueResponse(
+ mORB,
+ u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
+ OpaqueResponseBlockedTelemetryReason::AFTER_SNIFF_CT_FAIL,
+ "ContentType is image/video/audio");
+ }
+
+ return OpaqueResponse::Sniff;
+}
+
+bool HttpBaseChannel::NeedOpaqueResponseAllowedCheckAfterSniff() const {
+ return mORB ? mORB->IsSniffing() : false;
+}
+
+void HttpBaseChannel::BlockOpaqueResponseAfterSniff(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ LogORBError(aReason, aTelemetryReason);
+ mORB->BlockResponse(this, NS_ERROR_FAILURE);
+}
+
+void HttpBaseChannel::AllowOpaqueResponseAfterSniff() {
+ MOZ_DIAGNOSTIC_ASSERT(mORB);
+ mORB->AllowResponse();
+}
+
+void HttpBaseChannel::SetChannelBlockedByOpaqueResponse() {
+ mChannelBlockedByOpaqueResponse = true;
+
+ RefPtr<dom::BrowsingContext> browsingContext =
+ dom::BrowsingContext::GetCurrentTopByBrowserId(mBrowserId);
+ if (!browsingContext) {
+ return;
+ }
+
+ dom::WindowContext* windowContext = browsingContext->GetTopWindowContext();
+ if (windowContext) {
+ windowContext->Canonical()->SetShouldReportHasBlockedOpaqueResponse(
+ mLoadInfo->InternalContentPolicyType());
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCookie(const nsACString& aCookieHeader) {
+ if (mLoadFlags & LOAD_ANONYMOUS) return NS_OK;
+
+ if (IsBrowsingContextDiscarded()) {
+ return NS_OK;
+ }
+
+ // empty header isn't an error
+ if (aCookieHeader.IsEmpty()) {
+ return NS_OK;
+ }
+
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
+
+ nsresult rv = cs->SetCookieStringFromHttp(mURI, aCookieHeader, this);
+ if (NS_SUCCEEDED(rv)) {
+ NotifySetCookie(aCookieHeader);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThirdPartyFlags(uint32_t* aFlags) {
+ *aFlags = LoadThirdPartyFlags();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThirdPartyFlags(uint32_t aFlags) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ StoreThirdPartyFlags(aFlags);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetForceAllowThirdPartyCookie(bool* aForce) {
+ *aForce = !!(LoadThirdPartyFlags() &
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetForceAllowThirdPartyCookie(bool aForce) {
+ ENSURE_CALLED_BEFORE_ASYNC_OPEN();
+
+ if (aForce) {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() |
+ nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ } else {
+ StoreThirdPartyFlags(LoadThirdPartyFlags() &
+ ~nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCanceled(bool* aCanceled) {
+ *aCanceled = mCanceled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelIsForDownload(bool* aChannelIsForDownload) {
+ *aChannelIsForDownload = LoadChannelIsForDownload();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
+ StoreChannelIsForDownload(aChannelIsForDownload);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCacheKeysRedirectChain(nsTArray<nsCString>* cacheKeys) {
+ auto RedirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = RedirectedCachekeys.ref();
+ ref = WrapUnique(cacheKeys);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalAddress(nsACString& addr) {
+ if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mSelfAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::TakeAllSecurityMessages(
+ nsCOMArray<nsISecurityConsoleMessage>& aMessages) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ aMessages.Clear();
+ for (const auto& pair : mSecurityConsoleMessages) {
+ nsresult rv;
+ nsCOMPtr<nsISecurityConsoleMessage> message =
+ do_CreateInstance(NS_SECURITY_CONSOLE_MESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ message->SetTag(pair.first);
+ message->SetCategory(pair.second);
+ aMessages.AppendElement(message);
+ }
+
+ MOZ_ASSERT(mSecurityConsoleMessages.Length() == aMessages.Length());
+ mSecurityConsoleMessages.Clear();
+
+ return NS_OK;
+}
+
+/* Please use this method with care. This can cause the message
+ * queue to grow large and cause the channel to take up a lot
+ * of memory. Use only static string messages and do not add
+ * server side data to the queue, as that can be large.
+ * Add only a limited number of messages to the queue to keep
+ * the channel size down and do so only in rare erroneous situations.
+ * More information can be found here:
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=846918
+ */
+nsresult HttpBaseChannel::AddSecurityMessage(
+ const nsAString& aMessageTag, const nsAString& aMessageCategory) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsresult rv;
+
+ // nsSecurityConsoleMessage is not thread-safe refcounted.
+ // Delay the object construction until requested.
+ // See TakeAllSecurityMessages()
+ std::pair<nsString, nsString> pair(aMessageTag, aMessageCategory);
+ mSecurityConsoleMessages.AppendElement(std::move(pair));
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID));
+ if (!console) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+
+ auto innerWindowID = loadInfo->GetInnerWindowID();
+
+ nsAutoString errorText;
+ rv = nsContentUtils::GetLocalizedString(
+ nsContentUtils::eSECURITY_PROPERTIES,
+ NS_ConvertUTF16toUTF8(aMessageTag).get(), errorText);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIScriptError> error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
+ error->InitWithSourceURI(
+ errorText, mURI, u""_ns, 0, 0, nsIScriptError::warningFlag,
+ NS_ConvertUTF16toUTF8(aMessageCategory), innerWindowID);
+
+ console->LogMessage(error);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLocalPort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mSelfAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mSelfAddr.inet.port);
+ } else if (mSelfAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mSelfAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemoteAddress(nsACString& addr) {
+ if (mPeerAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE;
+
+ addr.SetLength(kIPv6CStrBufSize);
+ mPeerAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize);
+ addr.SetLength(strlen(addr.BeginReading()));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRemotePort(int32_t* port) {
+ NS_ENSURE_ARG_POINTER(port);
+
+ if (mPeerAddr.raw.family == PR_AF_INET) {
+ *port = (int32_t)ntohs(mPeerAddr.inet.port);
+ } else if (mPeerAddr.raw.family == PR_AF_INET6) {
+ *port = (int32_t)ntohs(mPeerAddr.inet6.port);
+ } else {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HTTPUpgrade(const nsACString& aProtocolName,
+ nsIHttpUpgradeListener* aListener) {
+ NS_ENSURE_ARG(!aProtocolName.IsEmpty());
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ mUpgradeProtocol = aProtocolName;
+ mUpgradeProtocolCallback = aListener;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetOnlyConnect(bool* aOnlyConnect) {
+ NS_ENSURE_ARG_POINTER(aOnlyConnect);
+
+ *aOnlyConnect = mCaps & NS_HTTP_CONNECT_ONLY;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetConnectOnly() {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ if (!mUpgradeProtocolCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mCaps |= NS_HTTP_CONNECT_ONLY;
+ mProxyResolveFlags = nsIProtocolProxyService::RESOLVE_PREFER_HTTPS_PROXY |
+ nsIProtocolProxyService::RESOLVE_ALWAYS_TUNNEL;
+ return SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_ANONYMOUS |
+ nsIRequest::LOAD_BYPASS_CACHE |
+ nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowSpdy(bool* aAllowSpdy) {
+ NS_ENSURE_ARG_POINTER(aAllowSpdy);
+
+ *aAllowSpdy = LoadAllowSpdy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowSpdy(bool aAllowSpdy) {
+ StoreAllowSpdy(aAllowSpdy);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowHttp3(bool* aAllowHttp3) {
+ NS_ENSURE_ARG_POINTER(aAllowHttp3);
+
+ *aAllowHttp3 = LoadAllowHttp3();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowHttp3(bool aAllowHttp3) {
+ StoreAllowHttp3(aAllowHttp3);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllowAltSvc(bool* aAllowAltSvc) {
+ NS_ENSURE_ARG_POINTER(aAllowAltSvc);
+
+ *aAllowAltSvc = LoadAllowAltSvc();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllowAltSvc(bool aAllowAltSvc) {
+ StoreAllowAltSvc(aAllowAltSvc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBeConservative(bool* aBeConservative) {
+ NS_ENSURE_ARG_POINTER(aBeConservative);
+
+ *aBeConservative = LoadBeConservative();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBeConservative(bool aBeConservative) {
+ StoreBeConservative(aBeConservative);
+ return NS_OK;
+}
+
+bool HttpBaseChannel::BypassProxy() {
+ return StaticPrefs::network_proxy_allow_bypass() && LoadBypassProxy();
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBypassProxy(bool* aBypassProxy) {
+ NS_ENSURE_ARG_POINTER(aBypassProxy);
+
+ *aBypassProxy = BypassProxy();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBypassProxy(bool aBypassProxy) {
+ if (StaticPrefs::network_proxy_allow_bypass()) {
+ StoreBypassProxy(aBypassProxy);
+ } else {
+ NS_WARNING("bypassProxy set but network.proxy.allow_bypass is disabled");
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsTRRServiceChannel(bool* aIsTRRServiceChannel) {
+ NS_ENSURE_ARG_POINTER(aIsTRRServiceChannel);
+
+ *aIsTRRServiceChannel = LoadIsTRRServiceChannel();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIsTRRServiceChannel(bool aIsTRRServiceChannel) {
+ StoreIsTRRServiceChannel(aIsTRRServiceChannel);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsResolvedByTRR(bool* aResolvedByTRR) {
+ NS_ENSURE_ARG_POINTER(aResolvedByTRR);
+ *aResolvedByTRR = LoadResolvedByTRR();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEffectiveTRRMode(nsIRequest::TRRMode* aEffectiveTRRMode) {
+ *aEffectiveTRRMode = mEffectiveTRRMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTrrSkipReason(nsITRRSkipReason::value* aTrrSkipReason) {
+ *aTrrSkipReason = mTRRSkipReason;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsLoadedBySocketProcess(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = LoadLoadedBySocketProcess();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTlsFlags(uint32_t* aTlsFlags) {
+ NS_ENSURE_ARG_POINTER(aTlsFlags);
+
+ *aTlsFlags = mTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTlsFlags(uint32_t aTlsFlags) {
+ mTlsFlags = aTlsFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetApiRedirectToURI(nsIURI** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = do_AddRef(mAPIRedirectToURI).take();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseTimeoutEnabled(bool* aEnable) {
+ if (NS_WARN_IF(!aEnable)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aEnable = LoadResponseTimeoutEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetResponseTimeoutEnabled(bool aEnable) {
+ StoreResponseTimeoutEnabled(aEnable);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitialRwin(uint32_t* aRwin) {
+ if (NS_WARN_IF(!aRwin)) {
+ return NS_ERROR_NULL_POINTER;
+ }
+ *aRwin = mInitialRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitialRwin(uint32_t aRwin) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+ mInitialRwin = aRwin;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ForcePending(bool aForcePending) {
+ StoreForcePending(aForcePending);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastModifiedTime(PRTime* lastModifiedTime) {
+ if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
+ uint32_t lastMod;
+ nsresult rv = mResponseHead->GetLastModifiedValue(&lastMod);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *lastModifiedTime = lastMod;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCorsIncludeCredentials(bool* aInclude) {
+ *aInclude = LoadCorsIncludeCredentials();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetCorsIncludeCredentials(bool aInclude) {
+ StoreCorsIncludeCredentials(aInclude);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestMode(RequestMode* aMode) {
+ *aMode = mRequestMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRequestMode(RequestMode aMode) {
+ MOZ_ASSERT(aMode != RequestMode::EndGuard_);
+ mRequestMode = aMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectMode(uint32_t* aMode) {
+ *aMode = mRedirectMode;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectMode(uint32_t aMode) {
+ mRedirectMode = aMode;
+ return NS_OK;
+}
+
+namespace {
+
+bool ContainsAllFlags(uint32_t aLoadFlags, uint32_t aMask) {
+ return (aLoadFlags & aMask) == aMask;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::GetFetchCacheMode(uint32_t* aFetchCacheMode) {
+ NS_ENSURE_ARG_POINTER(aFetchCacheMode);
+
+ // Otherwise try to guess an appropriate cache mode from the load flags.
+ if (ContainsAllFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE;
+ } else if (ContainsAllFlags(mLoadFlags, LOAD_BYPASS_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_ALWAYS) ||
+ LoadForceValidateCacheContent()) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE;
+ } else if (ContainsAllFlags(
+ mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED;
+ } else if (ContainsAllFlags(mLoadFlags, VALIDATE_NEVER)) {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE;
+ } else {
+ *aFetchCacheMode = nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT;
+ }
+
+ return NS_OK;
+}
+
+namespace {
+
+void SetCacheFlags(uint32_t& aLoadFlags, uint32_t aFlags) {
+ // First, clear any possible cache related flags.
+ uint32_t allPossibleFlags =
+ nsIRequest::INHIBIT_CACHING | nsIRequest::LOAD_BYPASS_CACHE |
+ nsIRequest::VALIDATE_ALWAYS | nsIRequest::LOAD_FROM_CACHE |
+ nsICachingChannel::LOAD_ONLY_FROM_CACHE;
+ aLoadFlags &= ~allPossibleFlags;
+
+ // Then set the new flags.
+ aLoadFlags |= aFlags;
+}
+
+} // anonymous namespace
+
+NS_IMETHODIMP
+HttpBaseChannel::SetFetchCacheMode(uint32_t aFetchCacheMode) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ // Now, set the load flags that implement each cache mode.
+ switch (aFetchCacheMode) {
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_DEFAULT:
+ // The "default" mode means to use the http cache normally and
+ // respect any http cache-control headers. We effectively want
+ // to clear our cache related load flags.
+ SetCacheFlags(mLoadFlags, 0);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_STORE:
+ // no-store means don't consult the cache on the way to the network, and
+ // don't store the response in the cache even if it's cacheable.
+ SetCacheFlags(mLoadFlags, INHIBIT_CACHING | LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_RELOAD:
+ // reload means don't consult the cache on the way to the network, but
+ // do store the response in the cache if possible.
+ SetCacheFlags(mLoadFlags, LOAD_BYPASS_CACHE);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_NO_CACHE:
+ // no-cache means always validate what's in the cache.
+ SetCacheFlags(mLoadFlags, VALIDATE_ALWAYS);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_FORCE_CACHE:
+ // force-cache means don't validate unless if the response would vary.
+ SetCacheFlags(mLoadFlags, VALIDATE_NEVER);
+ break;
+ case nsIHttpChannelInternal::FETCH_CACHE_MODE_ONLY_IF_CACHED:
+ // only-if-cached means only from cache, no network, no validation,
+ // generate a network error if the document was't in the cache. The
+ // privacy implications of these flags (making it fast/easy to check if
+ // the user has things in their cache without any network traffic side
+ // effects) are addressed in the Request constructor which
+ // enforces/requires same-origin request mode.
+ SetCacheFlags(mLoadFlags,
+ VALIDATE_NEVER | nsICachingChannel::LOAD_ONLY_FROM_CACHE);
+ break;
+ }
+
+#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
+ uint32_t finalMode = 0;
+ MOZ_ALWAYS_SUCCEEDS(GetFetchCacheMode(&finalMode));
+ MOZ_DIAGNOSTIC_ASSERT(finalMode == aFetchCacheMode);
+#endif // MOZ_DIAGNOSTIC_ASSERT_ENABLED
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetIntegrityMetadata(const nsAString& aIntegrityMetadata) {
+ mIntegrityMetadata = aIntegrityMetadata;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIntegrityMetadata(nsAString& aIntegrityMetadata) {
+ aIntegrityMetadata = mIntegrityMetadata;
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsISupportsPriority
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetPriority(int32_t* value) {
+ *value = mPriority;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::AdjustPriority(int32_t delta) {
+ return SetPriority(mPriority + delta);
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIResumableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEntityID(nsACString& aEntityID) {
+ // Don't return an entity ID for Non-GET requests which require
+ // additional data
+ if (!mRequestHead.IsGet()) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ uint64_t size = UINT64_MAX;
+ nsAutoCString etag, lastmod;
+ if (mResponseHead) {
+ // Don't return an entity if the server sent the following header:
+ // Accept-Ranges: none
+ // Not sending the Accept-Ranges header means we can still try
+ // sending range requests.
+ nsAutoCString acceptRanges;
+ Unused << mResponseHead->GetHeader(nsHttp::Accept_Ranges, acceptRanges);
+ if (!acceptRanges.IsEmpty() &&
+ !nsHttp::FindToken(acceptRanges.get(), "bytes",
+ HTTP_HEADER_VALUE_SEPS)) {
+ return NS_ERROR_NOT_RESUMABLE;
+ }
+
+ size = mResponseHead->TotalEntitySize();
+ Unused << mResponseHead->GetHeader(nsHttp::Last_Modified, lastmod);
+ Unused << mResponseHead->GetHeader(nsHttp::ETag, etag);
+ }
+ nsCString entityID;
+ NS_EscapeURL(etag.BeginReading(), etag.Length(),
+ esc_AlwaysCopy | esc_FileBaseName | esc_Forced, entityID);
+ entityID.Append('/');
+ entityID.AppendInt(int64_t(size));
+ entityID.Append('/');
+ entityID.Append(lastmod);
+ // NOTE: Appending lastmod as the last part avoids having to escape it
+
+ aEntityID = entityID;
+
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIConsoleReportCollector
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::AddConsoleReport(
+ uint32_t aErrorFlags, const nsACString& aCategory,
+ nsContentUtils::PropertiesFile aPropertiesFile,
+ const nsACString& aSourceFileURI, uint32_t aLineNumber,
+ uint32_t aColumnNumber, const nsACString& aMessageName,
+ const nsTArray<nsString>& aStringParams) {
+ mReportCollector->AddConsoleReport(aErrorFlags, aCategory, aPropertiesFile,
+ aSourceFileURI, aLineNumber, aColumnNumber,
+ aMessageName, aStringParams);
+
+ // If this channel is already part of a loadGroup, we can flush this console
+ // report immediately.
+ HttpBaseChannel::MaybeFlushConsoleReports();
+}
+
+void HttpBaseChannel::FlushReportsToConsole(uint64_t aInnerWindowID,
+ ReportAction aAction) {
+ mReportCollector->FlushReportsToConsole(aInnerWindowID, aAction);
+}
+
+void HttpBaseChannel::FlushReportsToConsoleForServiceWorkerScope(
+ const nsACString& aScope, ReportAction aAction) {
+ mReportCollector->FlushReportsToConsoleForServiceWorkerScope(aScope, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(dom::Document* aDocument,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aDocument, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(nsILoadGroup* aLoadGroup,
+ ReportAction aAction) {
+ mReportCollector->FlushConsoleReports(aLoadGroup, aAction);
+}
+
+void HttpBaseChannel::FlushConsoleReports(
+ nsIConsoleReportCollector* aCollector) {
+ mReportCollector->FlushConsoleReports(aCollector);
+}
+
+void HttpBaseChannel::StealConsoleReports(
+ nsTArray<net::ConsoleReportCollected>& aReports) {
+ mReportCollector->StealConsoleReports(aReports);
+}
+
+void HttpBaseChannel::ClearConsoleReports() {
+ mReportCollector->ClearConsoleReports();
+}
+
+bool HttpBaseChannel::IsNavigation() {
+ return LoadForceMainDocumentChannel() || (mLoadFlags & LOAD_DOCUMENT_URI);
+}
+
+bool HttpBaseChannel::BypassServiceWorker() const {
+ return mLoadFlags & LOAD_BYPASS_SERVICE_WORKER;
+}
+
+bool HttpBaseChannel::ShouldIntercept(nsIURI* aURI) {
+ nsCOMPtr<nsINetworkInterceptController> controller;
+ GetCallback(controller);
+ bool shouldIntercept = false;
+
+ if (!StaticPrefs::dom_serviceWorkers_enabled()) {
+ return false;
+ }
+
+ // We should never intercept internal redirects. The ServiceWorker code
+ // can trigger interntal redirects as the result of a FetchEvent. If
+ // we re-intercept then an infinite loop can occur.
+ //
+ // Its also important that we do not set the LOAD_BYPASS_SERVICE_WORKER
+ // flag because an internal redirect occurs. Its possible that another
+ // interception should occur after the internal redirect. For example,
+ // if the ServiceWorker chooses not to call respondWith() the channel
+ // will be reset with an internal redirect. If the request is a navigation
+ // and the network then triggers a redirect its possible the new URL
+ // should be intercepted again.
+ //
+ // Note, HSTS upgrade redirects are often treated the same as internal
+ // redirects. In this case, however, we intentionally allow interception
+ // of HSTS upgrade redirects. This matches the expected spec behavior and
+ // does not run the risk of infinite loops as described above.
+ bool internalRedirect =
+ mLastRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL;
+
+ if (controller && mLoadInfo && !BypassServiceWorker() && !internalRedirect) {
+ nsresult rv = controller->ShouldPrepareForIntercept(
+ aURI ? aURI : mURI.get(), this, &shouldIntercept);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+ return shouldIntercept;
+}
+
+void HttpBaseChannel::AddAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (EnsureRequestContext()) {
+ LOG((
+ "HttpBaseChannel::AddAsNonTailRequest this=%p, rc=%p, already added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (!LoadAddedAsNonTailRequest()) {
+ mRequestContext->AddNonTailRequest();
+ StoreAddedAsNonTailRequest(true);
+ }
+ }
+}
+
+void HttpBaseChannel::RemoveAsNonTailRequest() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mRequestContext) {
+ LOG(
+ ("HttpBaseChannel::RemoveAsNonTailRequest this=%p, rc=%p, already "
+ "added=%d",
+ this, mRequestContext.get(), (bool)LoadAddedAsNonTailRequest()));
+
+ if (LoadAddedAsNonTailRequest()) {
+ mRequestContext->RemoveNonTailRequest();
+ StoreAddedAsNonTailRequest(false);
+ }
+ }
+}
+
+#ifdef DEBUG
+void HttpBaseChannel::AssertPrivateBrowsingId() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+
+ if (!loadContext) {
+ return;
+ }
+
+ // We skip testing of favicon loading here since it could be triggered by XUL
+ // image which uses SystemPrincipal. The SystemPrincpal doesn't have
+ // mPrivateBrowsingId.
+ if (mLoadInfo->GetLoadingPrincipal() &&
+ mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal() &&
+ mLoadInfo->InternalContentPolicyType() ==
+ nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
+ return;
+ }
+
+ OriginAttributes docShellAttrs;
+ loadContext->GetOriginAttributes(docShellAttrs);
+ MOZ_ASSERT(mLoadInfo->GetOriginAttributes().mPrivateBrowsingId ==
+ docShellAttrs.mPrivateBrowsingId,
+ "PrivateBrowsingId values are not the same between LoadInfo and "
+ "LoadContext.");
+}
+#endif
+
+already_AddRefed<nsILoadInfo> HttpBaseChannel::CloneLoadInfoForRedirect(
+ nsIURI* aNewURI, uint32_t aRedirectFlags) {
+ // make a copy of the loadinfo, append to the redirectchain
+ // this will be set on the newly created channel for the redirect target.
+ nsCOMPtr<nsILoadInfo> newLoadInfo =
+ static_cast<mozilla::net::LoadInfo*>(mLoadInfo.get())->Clone();
+
+ ExtContentPolicyType contentPolicyType =
+ mLoadInfo->GetExternalContentPolicyType();
+ if (contentPolicyType == ExtContentPolicy::TYPE_DOCUMENT ||
+ contentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) {
+ // Reset PrincipalToInherit to a null principal. We'll credit the the
+ // redirecting resource's result principal as the new principal's precursor.
+ // This means that a data: URI will end up loading in a process based on the
+ // redirected-from URI.
+ nsCOMPtr<nsIPrincipal> redirectPrincipal;
+ nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(redirectPrincipal));
+ nsCOMPtr<nsIPrincipal> nullPrincipalToInherit =
+ NullPrincipal::CreateWithInheritedAttributes(redirectPrincipal);
+ newLoadInfo->SetPrincipalToInherit(nullPrincipalToInherit);
+ }
+
+ bool isTopLevelDoc = newLoadInfo->GetExternalContentPolicyType() ==
+ ExtContentPolicy::TYPE_DOCUMENT;
+
+ if (isTopLevelDoc) {
+ // re-compute the origin attributes of the loadInfo if it's top-level load.
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ OriginAttributes docShellAttrs;
+ if (loadContext) {
+ loadContext->GetOriginAttributes(docShellAttrs);
+ }
+
+ OriginAttributes attrs = newLoadInfo->GetOriginAttributes();
+
+ MOZ_ASSERT(
+ docShellAttrs.mUserContextId == attrs.mUserContextId,
+ "docshell and necko should have the same userContextId attribute.");
+ MOZ_ASSERT(
+ docShellAttrs.mPrivateBrowsingId == attrs.mPrivateBrowsingId,
+ "docshell and necko should have the same privateBrowsingId attribute.");
+ MOZ_ASSERT(docShellAttrs.mGeckoViewSessionContextId ==
+ attrs.mGeckoViewSessionContextId,
+ "docshell and necko should have the same "
+ "geckoViewSessionContextId attribute");
+
+ attrs = docShellAttrs;
+ attrs.SetFirstPartyDomain(true, aNewURI);
+ newLoadInfo->SetOriginAttributes(attrs);
+
+ // re-compute the upgrade insecure requests bit for document navigations
+ // since it should only apply to same-origin navigations (redirects).
+ // we only do this if the CSP of the triggering element (the cspToInherit)
+ // uses 'upgrade-insecure-requests', otherwise UIR does not apply.
+ nsCOMPtr<nsIContentSecurityPolicy> csp = newLoadInfo->GetCspToInherit();
+ if (csp) {
+ bool upgradeInsecureRequests = false;
+ csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests);
+ if (upgradeInsecureRequests) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal =
+ BasePrincipal::CreateContentPrincipal(
+ aNewURI, newLoadInfo->GetOriginAttributes());
+ bool isConsideredSameOriginforUIR =
+ nsContentSecurityUtils::IsConsideredSameOriginForUIR(
+ newLoadInfo->TriggeringPrincipal(), resultPrincipal);
+ static_cast<mozilla::net::LoadInfo*>(newLoadInfo.get())
+ ->SetUpgradeInsecureRequests(isConsideredSameOriginforUIR);
+ }
+ }
+ }
+
+ // Leave empty, we want a 'clean ground' when creating the new channel.
+ // This will be ensured to be either set by the protocol handler or set
+ // to the redirect target URI properly after the channel creation.
+ newLoadInfo->SetResultPrincipalURI(nullptr);
+
+ bool isInternalRedirect =
+ (aRedirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ // Reset our sandboxed null principal ID when cloning loadInfo for an
+ // externally visible redirect.
+ if (!isInternalRedirect) {
+ // If we've redirected from http to something that isn't, clear
+ // the "external" flag, as loads that now go to other apps should be
+ // allowed to go ahead and not trip infinite-loop protection
+ // (see bug 1717314 for context).
+ if (!aNewURI->SchemeIs("http") && !aNewURI->SchemeIs("https")) {
+ newLoadInfo->SetLoadTriggeredFromExternal(false);
+ }
+ newLoadInfo->ResetSandboxedNullPrincipalID();
+ }
+
+ newLoadInfo->AppendRedirectHistoryEntry(this, isInternalRedirect);
+
+ return newLoadInfo.forget();
+}
+
+//-----------------------------------------------------------------------------
+// nsHttpChannel::nsITraceableChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNewListener(nsIStreamListener* aListener,
+ bool aMustApplyContentConversion,
+ nsIStreamListener** _retval) {
+ LOG((
+ "HttpBaseChannel::SetNewListener [this=%p, mListener=%p, newListener=%p]",
+ this, mListener.get(), aListener));
+
+ if (!LoadTracingEnabled()) return NS_ERROR_FAILURE;
+
+ NS_ENSURE_STATE(mListener);
+ NS_ENSURE_ARG_POINTER(aListener);
+
+ nsCOMPtr<nsIStreamListener> wrapper = new nsStreamListenerWrapper(mListener);
+
+ wrapper.forget(_retval);
+ mListener = aListener;
+ if (aMustApplyContentConversion) {
+ StoreListenerRequiresContentConversion(true);
+ }
+ return NS_OK;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel helpers
+//-----------------------------------------------------------------------------
+
+void HttpBaseChannel::ReleaseListeners() {
+ MOZ_ASSERT(mCurrentThread->IsOnCurrentThread(),
+ "Should only be called on the current thread");
+
+ mListener = nullptr;
+ mCallbacks = nullptr;
+ mProgressSink = nullptr;
+ mCompressListener = nullptr;
+ mORB = nullptr;
+}
+
+void HttpBaseChannel::DoNotifyListener() {
+ LOG(("HttpBaseChannel::DoNotifyListener this=%p", this));
+
+ // In case nsHttpChannel::OnStartRequest wasn't called (e.g. due to flag
+ // LOAD_ONLY_IF_MODIFIED) we want to set AfterOnStartRequestBegun to true
+ // before notifying listener.
+ if (!LoadAfterOnStartRequestBegun()) {
+ StoreAfterOnStartRequestBegun(true);
+ }
+
+ if (mListener && !LoadOnStartRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStartRequestCalled(true);
+ listener->OnStartRequest(this);
+ }
+ StoreOnStartRequestCalled(true);
+
+ // Make sure IsPending is set to false. At this moment we are done from
+ // the point of view of our consumer and we have to report our self
+ // as not-pending.
+ StoreIsPending(false);
+
+ if (mListener && !LoadOnStopRequestCalled()) {
+ nsCOMPtr<nsIStreamListener> listener = mListener;
+ StoreOnStopRequestCalled(true);
+ listener->OnStopRequest(this, mStatus);
+ }
+ StoreOnStopRequestCalled(true);
+
+ // notify "http-on-stop-connect" observers
+ gHttpHandler->OnStopRequest(this);
+
+ // This channel has finished its job, potentially release any tail-blocked
+ // requests with this.
+ RemoveAsNonTailRequest();
+
+ // We have to make sure to drop the references to listeners and callbacks
+ // no longer needed.
+ ReleaseListeners();
+
+ DoNotifyListenerCleanup();
+
+ // If this is a navigation, then we must let the docshell flush the reports
+ // to the console later. The LoadDocument() is pointing at the detached
+ // document that started the navigation. We want to show the reports on the
+ // new document. Otherwise the console is wiped and the user never sees
+ // the information.
+ if (!IsNavigation()) {
+ if (mLoadGroup) {
+ FlushConsoleReports(mLoadGroup);
+ } else {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+ FlushConsoleReports(doc);
+ }
+ }
+}
+
+void HttpBaseChannel::AddCookiesToRequest() {
+ if (mLoadFlags & LOAD_ANONYMOUS) {
+ return;
+ }
+
+ bool useCookieService = (XRE_IsParentProcess());
+ nsAutoCString cookie;
+ if (useCookieService) {
+ nsICookieService* cs = gHttpHandler->GetCookieService();
+ if (cs) {
+ cs->GetCookieStringFromHttp(mURI, this, cookie);
+ }
+
+ if (cookie.IsEmpty()) {
+ cookie = mUserSetCookieHeader;
+ } else if (!mUserSetCookieHeader.IsEmpty()) {
+ cookie.AppendLiteral("; ");
+ cookie.Append(mUserSetCookieHeader);
+ }
+ } else {
+ cookie = mUserSetCookieHeader;
+ }
+
+ // If we are in the child process, we want the parent seeing any
+ // cookie headers that might have been set by SetRequestHeader()
+ SetRequestHeader(nsDependentCString(nsHttp::Cookie), cookie, false);
+}
+
+/* static */
+void HttpBaseChannel::PropagateReferenceIfNeeded(
+ nsIURI* aURI, nsCOMPtr<nsIURI>& aRedirectURI) {
+ bool hasRef = false;
+ nsresult rv = aRedirectURI->GetHasRef(&hasRef);
+ if (NS_SUCCEEDED(rv) && !hasRef) {
+ nsAutoCString ref;
+ aURI->GetRef(ref);
+ if (!ref.IsEmpty()) {
+ // NOTE: SetRef will fail if mRedirectURI is immutable
+ // (e.g. an about: URI)... Oh well.
+ Unused << NS_MutateURI(aRedirectURI).SetRef(ref).Finalize(aRedirectURI);
+ }
+ }
+}
+
+bool HttpBaseChannel::ShouldRewriteRedirectToGET(
+ uint32_t httpStatus, nsHttpRequestHead::ParsedMethodType method) {
+ // for 301 and 302, only rewrite POST
+ if (httpStatus == 301 || httpStatus == 302) {
+ return method == nsHttpRequestHead::kMethod_Post;
+ }
+
+ // rewrite for 303 unless it was HEAD
+ if (httpStatus == 303) return method != nsHttpRequestHead::kMethod_Head;
+
+ // otherwise, such as for 307, do not rewrite
+ return false;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::ShouldStripRequestBodyHeader(const nsACString& aMethod,
+ bool* aResult) {
+ *aResult = false;
+ uint32_t httpStatus = 0;
+ if (NS_FAILED(GetResponseStatus(&httpStatus))) {
+ return NS_OK;
+ }
+
+ nsAutoCString method(aMethod);
+ nsHttpRequestHead::ParsedMethodType parsedMethod;
+ nsHttpRequestHead::ParseMethod(method, parsedMethod);
+ // Fetch 4.4.11, which is slightly different than the perserved method
+ // algrorithm: strip request-body-header for GET->GET redirection for 303.
+ *aResult =
+ ShouldRewriteRedirectToGET(httpStatus, parsedMethod) &&
+ !(httpStatus == 303 && parsedMethod == nsHttpRequestHead::kMethod_Get);
+
+ return NS_OK;
+}
+
+HttpBaseChannel::ReplacementChannelConfig
+HttpBaseChannel::CloneReplacementChannelConfig(bool aPreserveMethod,
+ uint32_t aRedirectFlags,
+ ReplacementReason aReason) {
+ ReplacementChannelConfig config;
+ config.redirectFlags = aRedirectFlags;
+ config.classOfService = mClassOfService;
+
+ if (mPrivateBrowsingOverriden) {
+ config.privateBrowsing = Some(mPrivateBrowsing);
+ }
+
+ if (mReferrerInfo) {
+ // When cloning for a document channel replacement (parent process
+ // copying values for a new content process channel), this happens after
+ // OnStartRequest so we have the headers for the response available.
+ // We don't want to apply them to the referrer for the channel though,
+ // since that is the referrer for the current document, and the header
+ // should only apply to navigations from the current document.
+ if (aReason == ReplacementReason::DocumentChannel) {
+ config.referrerInfo = mReferrerInfo;
+ } else {
+ dom::ReferrerPolicy referrerPolicy = dom::ReferrerPolicy::_empty;
+ nsAutoCString tRPHeaderCValue;
+ Unused << GetResponseHeader("referrer-policy"_ns, tRPHeaderCValue);
+ NS_ConvertUTF8toUTF16 tRPHeaderValue(tRPHeaderCValue);
+
+ if (!tRPHeaderValue.IsEmpty()) {
+ referrerPolicy =
+ dom::ReferrerInfo::ReferrerPolicyFromHeaderString(tRPHeaderValue);
+ }
+
+ if (referrerPolicy != dom::ReferrerPolicy::_empty) {
+ // We may reuse computed referrer in redirect, so if referrerPolicy
+ // changes, we must not use the old computed value, and have to compute
+ // again.
+ nsCOMPtr<nsIReferrerInfo> referrerInfo =
+ dom::ReferrerInfo::CreateFromOtherAndPolicyOverride(mReferrerInfo,
+ referrerPolicy);
+ config.referrerInfo = referrerInfo;
+ } else {
+ config.referrerInfo = mReferrerInfo;
+ }
+ }
+ }
+
+ nsCOMPtr<nsITimedChannel> oldTimedChannel(
+ do_QueryInterface(static_cast<nsIHttpChannel*>(this)));
+ if (oldTimedChannel) {
+ config.timedChannelInfo = Some(dom::TimedChannelInfo());
+ config.timedChannelInfo->timingEnabled() = LoadTimingEnabled();
+ config.timedChannelInfo->redirectCount() = mRedirectCount;
+ config.timedChannelInfo->internalRedirectCount() = mInternalRedirectCount;
+ config.timedChannelInfo->asyncOpen() = mAsyncOpenTime;
+ config.timedChannelInfo->channelCreation() = mChannelCreationTimestamp;
+ config.timedChannelInfo->redirectStart() = mRedirectStartTimeStamp;
+ config.timedChannelInfo->redirectEnd() = mRedirectEndTimeStamp;
+ config.timedChannelInfo->initiatorType() = mInitiatorType;
+ config.timedChannelInfo->allRedirectsSameOrigin() =
+ LoadAllRedirectsSameOrigin();
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ // Execute the timing allow check to determine whether
+ // to report the redirect timing info
+ nsCOMPtr<nsILoadInfo> loadInfo = LoadInfo();
+ // TYPE_DOCUMENT loads don't have a loadingPrincipal, so we can't set
+ // AllRedirectsPassTimingAllowCheck on them.
+ if (loadInfo->GetExternalContentPolicyType() !=
+ ExtContentPolicy::TYPE_DOCUMENT) {
+ nsCOMPtr<nsIPrincipal> principal = loadInfo->GetLoadingPrincipal();
+ config.timedChannelInfo->timingAllowCheckForPrincipal() =
+ Some(oldTimedChannel->TimingAllowCheck(principal));
+ }
+
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() =
+ LoadAllRedirectsPassTimingAllowCheck();
+ config.timedChannelInfo->launchServiceWorkerStart() =
+ mLaunchServiceWorkerStart;
+ config.timedChannelInfo->launchServiceWorkerEnd() = mLaunchServiceWorkerEnd;
+ config.timedChannelInfo->dispatchFetchEventStart() =
+ mDispatchFetchEventStart;
+ config.timedChannelInfo->dispatchFetchEventEnd() = mDispatchFetchEventEnd;
+ config.timedChannelInfo->handleFetchEventStart() = mHandleFetchEventStart;
+ config.timedChannelInfo->handleFetchEventEnd() = mHandleFetchEventEnd;
+ config.timedChannelInfo->responseStart() =
+ mTransactionTimings.responseStart;
+ config.timedChannelInfo->responseEnd() = mTransactionTimings.responseEnd;
+ }
+
+ if (aPreserveMethod) {
+ // since preserveMethod is true, we need to ensure that the appropriate
+ // request method gets set on the channel, regardless of whether or not
+ // we set the upload stream above. This means SetRequestMethod() will
+ // be called twice if ExplicitSetUploadStream() gets called above.
+
+ nsAutoCString method;
+ mRequestHead.Method(method);
+ config.method = Some(method);
+
+ if (mUploadStream) {
+ // rewind upload stream
+ nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
+ if (seekable) {
+ seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
+ }
+ config.uploadStream = mUploadStream;
+ }
+ config.uploadStreamLength = mReqContentLength;
+ config.uploadStreamHasHeaders = LoadUploadStreamHasHeaders();
+
+ nsAutoCString contentType;
+ nsresult rv = mRequestHead.GetHeader(nsHttp::Content_Type, contentType);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentType = Some(contentType);
+ }
+
+ nsAutoCString contentLength;
+ rv = mRequestHead.GetHeader(nsHttp::Content_Length, contentLength);
+ if (NS_SUCCEEDED(rv)) {
+ config.contentLength = Some(contentLength);
+ }
+ }
+
+ return config;
+}
+
+/* static */ void HttpBaseChannel::ConfigureReplacementChannel(
+ nsIChannel* newChannel, const ReplacementChannelConfig& config,
+ ReplacementReason aReason) {
+ nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(newChannel));
+ if (cos) {
+ cos->SetClassOfService(config.classOfService);
+ }
+
+ // Try to preserve the privacy bit if it has been overridden
+ if (config.privateBrowsing) {
+ nsCOMPtr<nsIPrivateBrowsingChannel> newPBChannel =
+ do_QueryInterface(newChannel);
+ if (newPBChannel) {
+ newPBChannel->SetPrivate(*config.privateBrowsing);
+ }
+ }
+
+ // Transfer the timing data (if we are dealing with an nsITimedChannel).
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetTimingEnabled(config.timedChannelInfo->timingEnabled());
+
+ // If we're an internal redirect, or a document channel replacement,
+ // then we shouldn't record any new timing for this and just copy
+ // over the existing values.
+ bool shouldHideTiming = aReason != ReplacementReason::Redirect;
+ if (shouldHideTiming) {
+ newTimedChannel->SetRedirectCount(
+ config.timedChannelInfo->redirectCount());
+ int32_t newCount = config.timedChannelInfo->internalRedirectCount() + 1;
+ newTimedChannel->SetInternalRedirectCount(std::max(
+ newCount, static_cast<int32_t>(
+ config.timedChannelInfo->internalRedirectCount())));
+ } else {
+ int32_t newCount = config.timedChannelInfo->redirectCount() + 1;
+ newTimedChannel->SetRedirectCount(std::max(
+ newCount,
+ static_cast<int32_t>(config.timedChannelInfo->redirectCount())));
+ newTimedChannel->SetInternalRedirectCount(
+ config.timedChannelInfo->internalRedirectCount());
+ }
+
+ if (shouldHideTiming) {
+ if (!config.timedChannelInfo->channelCreation().IsNull()) {
+ newTimedChannel->SetChannelCreation(
+ config.timedChannelInfo->channelCreation());
+ }
+
+ if (!config.timedChannelInfo->asyncOpen().IsNull()) {
+ newTimedChannel->SetAsyncOpen(config.timedChannelInfo->asyncOpen());
+ }
+ }
+
+ // If the RedirectStart is null, we will use the AsyncOpen value of the
+ // previous channel (this is the first redirect in the redirects chain).
+ if (config.timedChannelInfo->redirectStart().IsNull()) {
+ // Only do this for real redirects. Internal redirects should be hidden.
+ if (!shouldHideTiming) {
+ newTimedChannel->SetRedirectStart(config.timedChannelInfo->asyncOpen());
+ }
+ } else {
+ newTimedChannel->SetRedirectStart(
+ config.timedChannelInfo->redirectStart());
+ }
+
+ // For internal redirects just propagate the last redirect end time
+ // forward. Otherwise the new redirect end time is the last response
+ // end time.
+ TimeStamp newRedirectEnd;
+ if (shouldHideTiming) {
+ newRedirectEnd = config.timedChannelInfo->redirectEnd();
+ } else if (!config.timedChannelInfo->responseEnd().IsNull()) {
+ newRedirectEnd = config.timedChannelInfo->responseEnd();
+ } else {
+ newRedirectEnd = TimeStamp::Now();
+ }
+ newTimedChannel->SetRedirectEnd(newRedirectEnd);
+
+ newTimedChannel->SetInitiatorType(config.timedChannelInfo->initiatorType());
+
+ nsCOMPtr<nsILoadInfo> loadInfo = newChannel->LoadInfo();
+ MOZ_ASSERT(loadInfo);
+
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin());
+
+ if (config.timedChannelInfo->timingAllowCheckForPrincipal()) {
+ newTimedChannel->SetAllRedirectsPassTimingAllowCheck(
+ config.timedChannelInfo->allRedirectsPassTimingAllowCheck() &&
+ *config.timedChannelInfo->timingAllowCheckForPrincipal());
+ }
+
+ // Propagate service worker measurements across redirects. The
+ // PeformanceResourceTiming.workerStart API expects to see the
+ // worker start time after a redirect.
+ newTimedChannel->SetLaunchServiceWorkerStart(
+ config.timedChannelInfo->launchServiceWorkerStart());
+ newTimedChannel->SetLaunchServiceWorkerEnd(
+ config.timedChannelInfo->launchServiceWorkerEnd());
+ newTimedChannel->SetDispatchFetchEventStart(
+ config.timedChannelInfo->dispatchFetchEventStart());
+ newTimedChannel->SetDispatchFetchEventEnd(
+ config.timedChannelInfo->dispatchFetchEventEnd());
+ newTimedChannel->SetHandleFetchEventStart(
+ config.timedChannelInfo->handleFetchEventStart());
+ newTimedChannel->SetHandleFetchEventEnd(
+ config.timedChannelInfo->handleFetchEventEnd());
+ }
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+ if (!httpChannel) {
+ return; // no other options to set
+ }
+
+ if (config.uploadStream) {
+ nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
+ nsCOMPtr<nsIUploadChannel2> uploadChannel2 = do_QueryInterface(httpChannel);
+ if (uploadChannel2 || uploadChannel) {
+ // replicate original call to SetUploadStream...
+ if (uploadChannel2) {
+ const nsACString& ctype =
+ config.contentType ? *config.contentType : VoidCString();
+ // If header is not present mRequestHead.HasHeaderValue will truncated
+ // it. But we want to end up with a void string, not an empty string,
+ // because ExplicitSetUploadStream treats the former as "no header" and
+ // the latter as "header with empty string value".
+
+ const nsACString& method =
+ config.method ? *config.method : VoidCString();
+
+ uploadChannel2->ExplicitSetUploadStream(
+ config.uploadStream, ctype, config.uploadStreamLength, method,
+ config.uploadStreamHasHeaders);
+ } else {
+ if (config.uploadStreamHasHeaders) {
+ uploadChannel->SetUploadStream(config.uploadStream, ""_ns,
+ config.uploadStreamLength);
+ } else {
+ nsAutoCString ctype;
+ if (config.contentType) {
+ ctype = *config.contentType;
+ } else {
+ ctype = "application/octet-stream"_ns;
+ }
+ if (config.contentLength && !config.contentLength->IsEmpty()) {
+ uploadChannel->SetUploadStream(
+ config.uploadStream, ctype,
+ nsCRT::atoll(config.contentLength->get()));
+ }
+ }
+ }
+ }
+ }
+
+ if (config.referrerInfo) {
+ DebugOnly<nsresult> success{};
+ success = httpChannel->SetReferrerInfo(config.referrerInfo);
+ MOZ_ASSERT(NS_SUCCEEDED(success));
+ }
+
+ if (config.method) {
+ DebugOnly<nsresult> rv = httpChannel->SetRequestMethod(*config.method);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+}
+
+HttpBaseChannel::ReplacementChannelConfig::ReplacementChannelConfig(
+ const dom::ReplacementChannelConfigInit& aInit) {
+ redirectFlags = aInit.redirectFlags();
+ classOfService = aInit.classOfService();
+ privateBrowsing = aInit.privateBrowsing();
+ method = aInit.method();
+ referrerInfo = aInit.referrerInfo();
+ timedChannelInfo = aInit.timedChannelInfo();
+ uploadStream = aInit.uploadStream();
+ uploadStreamLength = aInit.uploadStreamLength();
+ uploadStreamHasHeaders = aInit.uploadStreamHasHeaders();
+ contentType = aInit.contentType();
+ contentLength = aInit.contentLength();
+}
+
+dom::ReplacementChannelConfigInit
+HttpBaseChannel::ReplacementChannelConfig::Serialize(
+ dom::ContentParent* aParent) {
+ dom::ReplacementChannelConfigInit config;
+ config.redirectFlags() = redirectFlags;
+ config.classOfService() = classOfService;
+ config.privateBrowsing() = privateBrowsing;
+ config.method() = method;
+ config.referrerInfo() = referrerInfo;
+ config.timedChannelInfo() = timedChannelInfo;
+ config.uploadStream() =
+ uploadStream ? RemoteLazyInputStream::WrapStream(uploadStream) : nullptr;
+ config.uploadStreamLength() = uploadStreamLength;
+ config.uploadStreamHasHeaders() = uploadStreamHasHeaders;
+ config.contentType() = contentType;
+ config.contentLength() = contentLength;
+
+ return config;
+}
+
+nsresult HttpBaseChannel::SetupReplacementChannel(nsIURI* newURI,
+ nsIChannel* newChannel,
+ bool preserveMethod,
+ uint32_t redirectFlags) {
+ nsresult rv;
+
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p newChannel=%p preserveMethod=%d]",
+ this, newChannel, preserveMethod));
+
+ // Ensure the channel's loadInfo's result principal URI so that it's
+ // either non-null or updated to the redirect target URI.
+ // We must do this because in case the loadInfo's result principal URI
+ // is null, it would be taken from OriginalURI of the channel. But we
+ // overwrite it with the whole redirect chain first URI before opening
+ // the target channel, hence the information would be lost.
+ // If the protocol handler that created the channel wants to use
+ // the originalURI of the channel as the principal URI, this fulfills
+ // that request - newURI is the original URI of the channel.
+ nsCOMPtr<nsILoadInfo> newLoadInfo = newChannel->LoadInfo();
+ nsCOMPtr<nsIURI> resultPrincipalURI;
+ rv = newLoadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!resultPrincipalURI) {
+ rv = newLoadInfo->SetResultPrincipalURI(newURI);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsLoadFlags loadFlags = mLoadFlags;
+ loadFlags |= LOAD_REPLACE;
+
+ // if the original channel was using SSL and this channel is not using
+ // SSL, then no need to inhibit persistent caching. however, if the
+ // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
+ // set, then allow the flag to apply to the redirected channel as well.
+ // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
+ // we only need to check if the original channel was using SSL.
+ if (mURI->SchemeIs("https")) {
+ loadFlags &= ~INHIBIT_PERSISTENT_CACHING;
+ }
+
+ newChannel->SetLoadFlags(loadFlags);
+
+ nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
+
+ ReplacementReason redirectType =
+ (redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)
+ ? ReplacementReason::InternalRedirect
+ : ReplacementReason::Redirect;
+ ReplacementChannelConfig config = CloneReplacementChannelConfig(
+ preserveMethod, redirectFlags, redirectType);
+ ConfigureReplacementChannel(newChannel, config, redirectType);
+
+ // Check whether or not this was a cross-domain redirect.
+ nsCOMPtr<nsITimedChannel> newTimedChannel(do_QueryInterface(newChannel));
+ bool sameOriginWithOriginalUri = SameOriginWithOriginalUri(newURI);
+ if (config.timedChannelInfo && newTimedChannel) {
+ newTimedChannel->SetAllRedirectsSameOrigin(
+ config.timedChannelInfo->allRedirectsSameOrigin() &&
+ sameOriginWithOriginalUri);
+ }
+
+ newChannel->SetLoadGroup(mLoadGroup);
+ newChannel->SetNotificationCallbacks(mCallbacks);
+ // TODO: create tests for cross-origin redirect in bug 1662896.
+ if (sameOriginWithOriginalUri) {
+ newChannel->SetContentDisposition(mContentDispositionHint);
+ if (mContentDispositionFilename) {
+ newChannel->SetContentDispositionFilename(*mContentDispositionFilename);
+ }
+ }
+
+ if (!httpChannel) return NS_OK; // no other options to set
+
+ // Preserve the CORS preflight information.
+ nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
+ if (httpInternal) {
+ httpInternal->SetLastRedirectFlags(redirectFlags);
+
+ if (LoadRequireCORSPreflight()) {
+ httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false);
+ }
+ }
+
+ // convey the LoadAllowSTS() flags
+ rv = httpChannel->SetAllowSTS(LoadAllowSTS());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // convey the Accept header value
+ {
+ nsAutoCString oldAcceptValue;
+ nsresult hasHeader = mRequestHead.GetHeader(nsHttp::Accept, oldAcceptValue);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("Accept"_ns, oldAcceptValue, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // convey the User-Agent header value
+ // since we might be setting custom user agent from DevTools.
+ if (httpInternal && mRequestMode == RequestMode::No_cors &&
+ redirectType == ReplacementReason::Redirect) {
+ nsAutoCString oldUserAgent;
+ nsresult hasHeader =
+ mRequestHead.GetHeader(nsHttp::User_Agent, oldUserAgent);
+ if (NS_SUCCEEDED(hasHeader)) {
+ rv = httpChannel->SetRequestHeader("User-Agent"_ns, oldUserAgent, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // share the request context - see bug 1236650
+ rv = httpChannel->SetRequestContextID(mRequestContextID);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // When on the parent process, the channel can't attempt to get it itself.
+ // When on the child process, it would be waste to query it again.
+ rv = httpChannel->SetBrowserId(mBrowserId);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Not setting this flag would break carrying permissions down to the child
+ // process when the channel is artificially forced to be a main document load.
+ rv = httpChannel->SetIsMainDocumentChannel(LoadForceMainDocumentChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve the loading order
+ nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(newChannel);
+ if (p) {
+ p->SetPriority(mPriority);
+ }
+
+ if (httpInternal) {
+ // Convey third party cookie, conservative, and spdy flags.
+ rv = httpInternal->SetThirdPartyFlags(LoadThirdPartyFlags());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowSpdy(LoadAllowSpdy());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowHttp3(LoadAllowHttp3());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetAllowAltSvc(LoadAllowAltSvc());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetBeConservative(LoadBeConservative());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetIsTRRServiceChannel(LoadIsTRRServiceChannel());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = httpInternal->SetTlsFlags(mTlsFlags);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Ensure the type of realChannel involves all types it may redirect to.
+ // Such as nsHttpChannel and InterceptedChannel.
+ // Even thought InterceptedChannel itself doesn't require these information,
+ // it may still be necessary for the following redirections.
+ // E.g. nsHttpChannel -> InterceptedChannel -> nsHttpChannel
+ RefPtr<HttpBaseChannel> realChannel;
+ CallQueryInterface(newChannel, realChannel.StartAssignment());
+ if (realChannel) {
+ realChannel->SetTopWindowURI(mTopWindowURI);
+
+ realChannel->StoreTaintedOriginFlag(
+ ShouldTaintReplacementChannelOrigin(newChannel, redirectFlags));
+ }
+
+ // update the DocumentURI indicator since we are being redirected.
+ // if this was a top-level document channel, then the new channel
+ // should have its mDocumentURI point to newURI; otherwise, we
+ // just need to pass along our mDocumentURI to the new channel.
+ if (newURI && (mURI == mDocumentURI)) {
+ rv = httpInternal->SetDocumentURI(newURI);
+ } else {
+ rv = httpInternal->SetDocumentURI(mDocumentURI);
+ }
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // if there is a chain of keys for redirect-responses we transfer it to
+ // the new channel (see bug #561276)
+ {
+ auto redirectedCachekeys = mRedirectedCachekeys.Lock();
+ auto& ref = redirectedCachekeys.ref();
+ if (ref) {
+ LOG(
+ ("HttpBaseChannel::SetupReplacementChannel "
+ "[this=%p] transferring chain of redirect cache-keys",
+ this));
+ rv = httpInternal->SetCacheKeysRedirectChain(ref.release());
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ // Preserve Request mode.
+ rv = httpInternal->SetRequestMode(mRequestMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Redirect mode flag.
+ rv = httpInternal->SetRedirectMode(mRedirectMode);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ // Preserve Integrity metadata.
+ rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ httpInternal->SetAltDataForChild(LoadAltDataForChild());
+ if (LoadDisableAltDataCache()) {
+ httpInternal->DisableAltDataCache();
+ }
+ }
+
+ // transfer any properties
+ nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
+ if (bag) {
+ for (const auto& entry : mPropertyHash) {
+ bag->SetProperty(entry.GetKey(), entry.GetWeak());
+ }
+ }
+
+ // Pass the preferred alt-data type on to the new channel.
+ nsCOMPtr<nsICacheInfoChannel> cacheInfoChan(do_QueryInterface(newChannel));
+ if (cacheInfoChan) {
+ for (auto& data : mPreferredCachedAltDataTypes) {
+ cacheInfoChan->PreferAlternativeDataType(data.type(), data.contentType(),
+ data.deliverAltData());
+ }
+
+ if (LoadForceValidateCacheContent()) {
+ Unused << cacheInfoChan->SetForceValidateCacheContent(true);
+ }
+ }
+
+ if (redirectFlags & (nsIChannelEventSink::REDIRECT_INTERNAL |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE)) {
+ // Copy non-origin related headers to the new channel.
+ nsCOMPtr<nsIHttpHeaderVisitor> visitor =
+ new AddHeadersToChannelVisitor(httpChannel);
+ rv = mRequestHead.VisitHeaders(visitor);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ // we need to strip Authentication headers for cross-origin requests
+ // Ref: https://fetch.spec.whatwg.org/#http-redirect-fetch
+ nsAutoCString authHeader;
+ if (StaticPrefs::network_http_redirect_stripAuthHeader() &&
+ NS_SUCCEEDED(
+ httpChannel->GetRequestHeader("Authorization"_ns, authHeader))) {
+ if (NS_ShouldRemoveAuthHeaderOnRedirect(static_cast<nsIChannel*>(this),
+ newChannel, redirectFlags)) {
+ rv = httpChannel->SetRequestHeader("Authorization"_ns, ""_ns, false);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+ }
+
+ return NS_OK;
+}
+
+// check whether the new channel is of same origin as the current channel
+bool HttpBaseChannel::IsNewChannelSameOrigin(nsIChannel* aNewChannel) {
+ bool isSameOrigin = false;
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+
+ if (!ssm) {
+ return false;
+ }
+
+ nsCOMPtr<nsIURI> newURI;
+ NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
+
+ nsresult rv = ssm->CheckSameOriginURI(newURI, mURI, false, false);
+ if (NS_SUCCEEDED(rv)) {
+ isSameOrigin = true;
+ }
+
+ return isSameOrigin;
+}
+
+bool HttpBaseChannel::ShouldTaintReplacementChannelOrigin(
+ nsIChannel* aNewChannel, uint32_t aRedirectFlags) {
+ if (LoadTaintedOriginFlag()) {
+ return true;
+ }
+
+ if (NS_IsInternalSameURIRedirect(this, aNewChannel, aRedirectFlags) ||
+ NS_IsHSTSUpgradeRedirect(this, aNewChannel, aRedirectFlags)) {
+ return false;
+ }
+
+ // If new channel is not of same origin we need to taint unless
+ // mURI <-> mOriginalURI/LoadingPrincipal are same origin.
+ if (IsNewChannelSameOrigin(aNewChannel)) {
+ return false;
+ }
+
+ nsresult rv;
+
+ if (mLoadInfo->GetLoadingPrincipal()) {
+ bool sameOrigin = false;
+ rv = mLoadInfo->GetLoadingPrincipal()->IsSameOrigin(mURI, &sameOrigin);
+ if (NS_FAILED(rv)) {
+ return true;
+ }
+ return !sameOrigin;
+ }
+ if (!mOriginalURI) {
+ return true;
+ }
+
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ if (!ssm) {
+ return true;
+ }
+
+ rv = ssm->CheckSameOriginURI(mOriginalURI, mURI, false, false);
+ return NS_FAILED(rv);
+}
+
+// Redirect Tracking
+bool HttpBaseChannel::SameOriginWithOriginalUri(nsIURI* aURI) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ bool isPrivateWin = mLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ nsresult rv =
+ ssm->CheckSameOriginURI(aURI, mOriginalURI, false, isPrivateWin);
+ return (NS_SUCCEEDED(rv));
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIClassifiedChannel
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedList(nsACString& aList) {
+ aList = mMatchedList;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedProvider(nsACString& aProvider) {
+ aProvider = mMatchedProvider;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedFullHash(nsACString& aFullHash) {
+ aFullHash = mMatchedFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedInfo(const nsACString& aList,
+ const nsACString& aProvider,
+ const nsACString& aFullHash) {
+ NS_ENSURE_ARG(!aList.IsEmpty());
+
+ mMatchedList = aList;
+ mMatchedProvider = aProvider;
+ mMatchedFullHash = aFullHash;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingLists(nsTArray<nsCString>& aLists) {
+ aLists = mMatchedTrackingLists.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetMatchedTrackingFullHashes(
+ nsTArray<nsCString>& aFullHashes) {
+ aFullHashes = mMatchedTrackingFullHashes.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetMatchedTrackingInfo(
+ const nsTArray<nsCString>& aLists, const nsTArray<nsCString>& aFullHashes) {
+ NS_ENSURE_ARG(!aLists.IsEmpty());
+ // aFullHashes can be empty for non hash-matching algorithm, for example,
+ // host based test entries in preference.
+
+ mMatchedTrackingLists = aLists.Clone();
+ mMatchedTrackingFullHashes = aFullHashes.Clone();
+ return NS_OK;
+}
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsITimedChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetTimingEnabled(bool enabled) {
+ StoreTimingEnabled(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTimingEnabled(bool* _retval) {
+ *_retval = LoadTimingEnabled();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetChannelCreation(TimeStamp* _retval) {
+ *_retval = mChannelCreationTimestamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetChannelCreation(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ TimeDuration adjust = aValue - mChannelCreationTimestamp;
+ mChannelCreationTimestamp = aValue;
+ mChannelCreationTime += (PRTime)adjust.ToMicroseconds();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAsyncOpen(TimeStamp* _retval) {
+ *_retval = mAsyncOpenTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAsyncOpen(TimeStamp aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(!aValue.IsNull());
+ mAsyncOpenTime = aValue;
+ StoreAsyncOpenTimeOverriden(true);
+ return NS_OK;
+}
+
+/**
+ * @return the number of redirects. There is no check for cross-domain
+ * redirects. This check must be done by the consumers.
+ */
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectCount(uint8_t aRedirectCount) {
+ mRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInternalRedirectCount(uint8_t* aRedirectCount) {
+ *aRedirectCount = mInternalRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInternalRedirectCount(uint8_t aRedirectCount) {
+ mInternalRedirectCount = aRedirectCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectStart(TimeStamp* _retval) {
+ *_retval = mRedirectStartTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectStart(TimeStamp aRedirectStart) {
+ mRedirectStartTimeStamp = aRedirectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRedirectEnd(TimeStamp* _retval) {
+ *_retval = mRedirectEndTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetRedirectEnd(TimeStamp aRedirectEnd) {
+ mRedirectEndTimeStamp = aRedirectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsSameOrigin(bool* aAllRedirectsSameOrigin) {
+ *aAllRedirectsSameOrigin = LoadAllRedirectsSameOrigin();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsSameOrigin(bool aAllRedirectsSameOrigin) {
+ StoreAllRedirectsSameOrigin(aAllRedirectsSameOrigin);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetAllRedirectsPassTimingAllowCheck(bool* aPassesCheck) {
+ *aPassesCheck = LoadAllRedirectsPassTimingAllowCheck();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetAllRedirectsPassTimingAllowCheck(bool aPassesCheck) {
+ StoreAllRedirectsPassTimingAllowCheck(aPassesCheck);
+ return NS_OK;
+}
+
+// https://fetch.spec.whatwg.org/#tao-check
+NS_IMETHODIMP
+HttpBaseChannel::TimingAllowCheck(nsIPrincipal* aOrigin, bool* _retval) {
+ nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
+ nsCOMPtr<nsIPrincipal> resourcePrincipal;
+ nsresult rv =
+ ssm->GetChannelURIPrincipal(this, getter_AddRefs(resourcePrincipal));
+ if (NS_FAILED(rv) || !resourcePrincipal || !aOrigin) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool sameOrigin = false;
+ rv = resourcePrincipal->Equals(aOrigin, &sameOrigin);
+
+ nsAutoCString serializedOrigin;
+ nsContentSecurityManager::GetSerializedOrigin(aOrigin, resourcePrincipal,
+ serializedOrigin, mLoadInfo);
+
+ // All redirects are same origin
+ if (sameOrigin && !serializedOrigin.IsEmpty()) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ nsAutoCString headerValue;
+ rv = GetResponseHeader("Timing-Allow-Origin"_ns, headerValue);
+ if (NS_FAILED(rv)) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ Tokenizer p(headerValue);
+ Tokenizer::Token t;
+
+ p.Record();
+ nsAutoCString headerItem;
+ while (p.Next(t)) {
+ if (t.Type() == Tokenizer::TOKEN_EOF ||
+ t.Equals(Tokenizer::Token::Char(','))) {
+ p.Claim(headerItem);
+ nsHttp::TrimHTTPWhitespace(headerItem, headerItem);
+ // If the list item contains a case-sensitive match for the value of the
+ // origin, or a wildcard, return pass
+ if (headerItem == serializedOrigin || headerItem == "*") {
+ *_retval = true;
+ return NS_OK;
+ }
+ // We start recording again for the following items in the list
+ p.Record();
+ }
+ }
+
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLaunchServiceWorkerEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mLaunchServiceWorkerEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) {
+ mLaunchServiceWorkerEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventStart(TimeStamp aTimeStamp) {
+ mDispatchFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDispatchFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mDispatchFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetDispatchFetchEventEnd(TimeStamp aTimeStamp) {
+ mDispatchFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventStart(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventStart(TimeStamp aTimeStamp) {
+ mHandleFetchEventStart = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetHandleFetchEventEnd(TimeStamp* _retval) {
+ MOZ_ASSERT(_retval);
+ *_retval = mHandleFetchEventEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetHandleFetchEventEnd(TimeStamp aTimeStamp) {
+ mHandleFetchEventEnd = aTimeStamp;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetDomainLookupEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.domainLookupEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTcpConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.tcpConnectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetSecureConnectionStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.secureConnectionStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.connectEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetRequestStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.requestStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseStart(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetResponseEnd(TimeStamp* _retval) {
+ *_retval = mTransactionTimings.responseEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadStart(TimeStamp* _retval) {
+ *_retval = mCacheReadStart;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCacheReadEnd(TimeStamp* _retval) {
+ *_retval = mCacheReadEnd;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetTransactionPending(TimeStamp* _retval) {
+ *_retval = mTransactionPendingTime;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetInitiatorType(nsAString& aInitiatorType) {
+ aInitiatorType = mInitiatorType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetInitiatorType(const nsAString& aInitiatorType) {
+ mInitiatorType = aInitiatorType;
+ return NS_OK;
+}
+
+#define IMPL_TIMING_ATTR(name) \
+ NS_IMETHODIMP \
+ HttpBaseChannel::Get##name##Time(PRTime* _retval) { \
+ TimeStamp stamp; \
+ Get##name(&stamp); \
+ if (stamp.IsNull()) { \
+ *_retval = 0; \
+ return NS_OK; \
+ } \
+ *_retval = \
+ mChannelCreationTime + \
+ (PRTime)((stamp - mChannelCreationTimestamp).ToSeconds() * 1e6); \
+ return NS_OK; \
+ }
+
+IMPL_TIMING_ATTR(ChannelCreation)
+IMPL_TIMING_ATTR(AsyncOpen)
+IMPL_TIMING_ATTR(LaunchServiceWorkerStart)
+IMPL_TIMING_ATTR(LaunchServiceWorkerEnd)
+IMPL_TIMING_ATTR(DispatchFetchEventStart)
+IMPL_TIMING_ATTR(DispatchFetchEventEnd)
+IMPL_TIMING_ATTR(HandleFetchEventStart)
+IMPL_TIMING_ATTR(HandleFetchEventEnd)
+IMPL_TIMING_ATTR(DomainLookupStart)
+IMPL_TIMING_ATTR(DomainLookupEnd)
+IMPL_TIMING_ATTR(ConnectStart)
+IMPL_TIMING_ATTR(TcpConnectEnd)
+IMPL_TIMING_ATTR(SecureConnectionStart)
+IMPL_TIMING_ATTR(ConnectEnd)
+IMPL_TIMING_ATTR(RequestStart)
+IMPL_TIMING_ATTR(ResponseStart)
+IMPL_TIMING_ATTR(ResponseEnd)
+IMPL_TIMING_ATTR(CacheReadStart)
+IMPL_TIMING_ATTR(CacheReadEnd)
+IMPL_TIMING_ATTR(RedirectStart)
+IMPL_TIMING_ATTR(RedirectEnd)
+IMPL_TIMING_ATTR(TransactionPending)
+
+#undef IMPL_TIMING_ATTR
+
+void HttpBaseChannel::MaybeReportTimingData() {
+ // If performance timing is disabled, there is no need for the Performance
+ // object anymore.
+ if (!LoadTimingEnabled()) {
+ return;
+ }
+
+ // There is no point in continuing, since the performance object in the parent
+ // isn't the same as the one in the child which will be reporting resource
+ // performance.
+ if (XRE_IsE10sParentProcess()) {
+ return;
+ }
+
+ // Devtools can create fetch requests on behalf the content document.
+ // If we don't exclude these requests, they'd also be reported
+ // to the content document.
+ bool isInDevToolsContext;
+ mLoadInfo->GetIsInDevToolsContext(&isInDevToolsContext);
+ if (isInDevToolsContext) {
+ return;
+ }
+
+ mozilla::dom::PerformanceStorage* documentPerformance =
+ mLoadInfo->GetPerformanceStorage();
+ if (documentPerformance) {
+ documentPerformance->AddEntry(this, this);
+ return;
+ }
+
+ if (!nsGlobalWindowInner::GetInnerWindowWithId(
+ mLoadInfo->GetInnerWindowID())) {
+ // The inner window is in a different process.
+ dom::ContentChild* child = dom::ContentChild::GetSingleton();
+
+ if (!child) {
+ return;
+ }
+ nsAutoString initiatorType;
+ nsAutoString entryName;
+
+ UniquePtr<dom::PerformanceTimingData> performanceTimingData(
+ dom::PerformanceTimingData::Create(this, this, 0, initiatorType,
+ entryName));
+ if (!performanceTimingData) {
+ return;
+ }
+
+ Maybe<LoadInfoArgs> loadInfoArgs;
+ mozilla::ipc::LoadInfoToLoadInfoArgs(mLoadInfo, &loadInfoArgs);
+ child->SendReportFrameTimingData(loadInfoArgs, entryName, initiatorType,
+ std::move(performanceTimingData));
+ }
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetReportResourceTiming(bool enabled) {
+ StoreReportTiming(enabled);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetReportResourceTiming(bool* _retval) {
+ *_retval = LoadReportTiming();
+ return NS_OK;
+}
+
+nsIURI* HttpBaseChannel::GetReferringPage() {
+ nsCOMPtr<nsPIDOMWindowInner> pDomWindow = GetInnerDOMWindow();
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ return pDomWindow->GetDocumentURI();
+}
+
+nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() {
+ nsCOMPtr<nsILoadContext> loadContext;
+ NS_QueryNotificationCallbacks(this, loadContext);
+ if (!loadContext) {
+ return nullptr;
+ }
+ nsCOMPtr<mozIDOMWindowProxy> domWindow;
+ loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
+ if (!domWindow) {
+ return nullptr;
+ }
+ auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow);
+ if (!pDomWindow) {
+ return nullptr;
+ }
+ nsCOMPtr<nsPIDOMWindowInner> innerWindow =
+ pDomWindow->GetCurrentInnerWindow();
+ if (!innerWindow) {
+ return nullptr;
+ }
+
+ return innerWindow;
+}
+
+//-----------------------------------------------------------------------------
+// HttpBaseChannel::nsIThrottledInputChannel
+//-----------------------------------------------------------------------------
+
+NS_IMETHODIMP
+HttpBaseChannel::SetThrottleQueue(nsIInputChannelThrottleQueue* aQueue) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mThrottleQueue = aQueue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetThrottleQueue(nsIInputChannelThrottleQueue** aQueue) {
+ NS_ENSURE_ARG_POINTER(aQueue);
+ nsCOMPtr<nsIInputChannelThrottleQueue> queue = mThrottleQueue;
+ queue.forget(aQueue);
+ return NS_OK;
+}
+
+//------------------------------------------------------------------------------
+
+bool HttpBaseChannel::EnsureRequestContextID() {
+ if (mRequestContextID) {
+ // Already have a request context ID, no need to do the rest of this work
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+ return true;
+ }
+
+ // Find the loadgroup at the end of the chain in order
+ // to make sure all channels derived from the load group
+ // use the same connection scope.
+ nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
+ if (!childLoadGroup) {
+ return false;
+ }
+
+ nsCOMPtr<nsILoadGroup> rootLoadGroup;
+ childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
+ if (!rootLoadGroup) {
+ return false;
+ }
+
+ // Set the load group connection scope on this channel and its transaction
+ rootLoadGroup->GetRequestContextID(&mRequestContextID);
+
+ LOG(("HttpBaseChannel::EnsureRequestContextID this=%p id=%" PRIx64, this,
+ mRequestContextID));
+
+ return true;
+}
+
+bool HttpBaseChannel::EnsureRequestContext() {
+ if (mRequestContext) {
+ // Already have a request context, no need to do the rest of this work
+ return true;
+ }
+
+ if (!EnsureRequestContextID()) {
+ return false;
+ }
+
+ nsIRequestContextService* rcsvc = gHttpHandler->GetRequestContextService();
+ if (!rcsvc) {
+ return false;
+ }
+
+ rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(mRequestContext));
+ return static_cast<bool>(mRequestContext);
+}
+
+void HttpBaseChannel::EnsureBrowserId() {
+ if (mBrowserId) {
+ return;
+ }
+
+ RefPtr<dom::BrowsingContext> bc;
+ MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
+
+ if (bc) {
+ mBrowserId = bc->GetBrowserId();
+ }
+}
+
+void HttpBaseChannel::SetCorsPreflightParameters(
+ const nsTArray<nsCString>& aUnsafeHeaders,
+ bool aShouldStripRequestBodyHeader) {
+ MOZ_RELEASE_ASSERT(!LoadRequestObserversCalled());
+
+ StoreRequireCORSPreflight(true);
+ mUnsafeHeaders = aUnsafeHeaders.Clone();
+ if (aShouldStripRequestBodyHeader) {
+ mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) {
+ return aHeader.LowerCaseEqualsASCII("content-type") ||
+ aHeader.LowerCaseEqualsASCII("content-encoding") ||
+ aHeader.LowerCaseEqualsASCII("content-language") ||
+ aHeader.LowerCaseEqualsASCII("content-location");
+ });
+ }
+}
+
+void HttpBaseChannel::SetAltDataForChild(bool aIsForChild) {
+ StoreAltDataForChild(aIsForChild);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetBlockAuthPrompt(bool* aValue) {
+ if (!aValue) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aValue = LoadBlockAuthPrompt();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetBlockAuthPrompt(bool aValue) {
+ ENSURE_CALLED_BEFORE_CONNECT();
+
+ StoreBlockAuthPrompt(aValue);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetConnectionInfoHashKey(nsACString& aConnectionInfoHashKey) {
+ if (!mConnectionInfo) {
+ return NS_ERROR_FAILURE;
+ }
+ aConnectionInfoHashKey.Assign(mConnectionInfo->HashKey());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetLastRedirectFlags(uint32_t* aValue) {
+ NS_ENSURE_ARG(aValue);
+ *aValue = mLastRedirectFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetLastRedirectFlags(uint32_t aValue) {
+ mLastRedirectFlags = aValue;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNavigationStartTimeStamp(TimeStamp* aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult HttpBaseChannel::CheckRedirectLimit(uint32_t aRedirectFlags) const {
+ if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) {
+ // Some platform features, like Service Workers, depend on internal
+ // redirects. We should allow some number of internal redirects above
+ // and beyond the normal redirect limit so these features continue
+ // to work.
+ static const int8_t kMinInternalRedirects = 5;
+
+ if (mInternalRedirectCount >= (mRedirectionLimit + kMinInternalRedirects)) {
+ LOG(("internal redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(aRedirectFlags & (nsIChannelEventSink::REDIRECT_TEMPORARY |
+ nsIChannelEventSink::REDIRECT_PERMANENT |
+ nsIChannelEventSink::REDIRECT_STS_UPGRADE));
+
+ if (mRedirectCount >= mRedirectionLimit) {
+ LOG(("redirection limit reached!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ // in case https-only mode is enabled which upgrades top-level requests to
+ // https and the page answers with a redirect (meta, 302, win.location, ...)
+ // then this method can break the cycle which causes the https-only exception
+ // page to appear. Note that https-first mode breaks upgrade downgrade endless
+ // loops within ShouldUpgradeHTTPSFirstRequest because https-first does not
+ // display an exception page but needs a soft fallback/downgrade.
+ if (nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
+ mURI, mLoadInfo,
+ {nsHTTPSOnlyUtils::UpgradeDowngradeEndlessLoopOptions::
+ EnforceForHTTPSOnlyMode})) {
+ LOG(("upgrade downgrade redirect loop!\n"));
+ return NS_ERROR_REDIRECT_LOOP;
+ }
+
+ return NS_OK;
+}
+
+// NOTE: This function duplicates code from nsBaseChannel. This will go away
+// once HTTP uses nsBaseChannel (part of bug 312760)
+/* static */
+void HttpBaseChannel::CallTypeSniffers(void* aClosure, const uint8_t* aData,
+ uint32_t aCount) {
+ nsIChannel* chan = static_cast<nsIChannel*>(aClosure);
+ const char* snifferType = [chan]() {
+ if (RefPtr<nsHttpChannel> httpChannel = do_QueryObject(chan)) {
+ switch (httpChannel->GetSnifferCategoryType()) {
+ case SnifferCategoryType::NetContent:
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ case SnifferCategoryType::OpaqueResponseBlocking:
+ return NS_ORB_SNIFFER_CATEGORY;
+ case SnifferCategoryType::All:
+ return NS_CONTENT_AND_ORB_SNIFFER_CATEGORY;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected SnifferCategoryType!");
+ }
+ }
+
+ return NS_CONTENT_SNIFFER_CATEGORY;
+ }();
+
+ nsAutoCString newType;
+ NS_SniffContent(snifferType, chan, aData, aCount, newType);
+ if (!newType.IsEmpty()) {
+ chan->SetContentType(newType);
+ }
+}
+
+template <class T>
+static void ParseServerTimingHeader(
+ const UniquePtr<T>& aHeader, nsTArray<nsCOMPtr<nsIServerTiming>>& aOutput) {
+ if (!aHeader) {
+ return;
+ }
+
+ nsAutoCString serverTimingHeader;
+ Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader);
+ if (serverTimingHeader.IsEmpty()) {
+ return;
+ }
+
+ ServerTimingParser parser(serverTimingHeader);
+ parser.Parse();
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> array = parser.TakeServerTimingHeaders();
+ aOutput.AppendElements(array);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) {
+ nsresult rv;
+ NS_ENSURE_ARG_POINTER(aServerTiming);
+
+ nsCOMPtr<nsIMutableArray> array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCOMPtr<nsIServerTiming>> data;
+ rv = GetNativeServerTiming(data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (const auto& entry : data) {
+ array->AppendElement(entry);
+ }
+
+ array.forget(aServerTiming);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetNativeServerTiming(
+ nsTArray<nsCOMPtr<nsIServerTiming>>& aServerTiming) {
+ aServerTiming.Clear();
+
+ if (nsContentUtils::ComputeIsSecureContext(this)) {
+ ParseServerTimingHeader(mResponseHead, aServerTiming);
+ ParseServerTimingHeader(mResponseTrailers, aServerTiming);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) {
+ MOZ_ASSERT(
+ UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
+ return Cancel(aErrorCode);
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv4Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV4;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetIPv6Disabled() {
+ mCaps |= NS_HTTP_DISABLE_IPV6;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy(
+ bool aIsOriginTrialCoepCredentiallessEnabled,
+ nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) {
+ *aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL;
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ // Feature is only available for secure contexts.
+ return NS_OK;
+ }
+
+ nsAutoCString content;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy,
+ content);
+ *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader(
+ content, aIsOriginTrialCoepCredentiallessEnabled);
+ return NS_OK;
+}
+
+// Obtain a cross-origin opener-policy from a response response and a
+// cross-origin opener policy initiator.
+// https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e
+NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy,
+ nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) {
+ MOZ_ASSERT(aOutPolicy);
+ *aOutPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (!mResponseHead) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // COOP headers are ignored for insecure-context loads.
+ if (!nsContentUtils::ComputeIsSecureContext(this)) {
+ return NS_OK;
+ }
+
+ nsAutoCString openerPolicy;
+ Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy,
+ openerPolicy);
+
+ // Cross-Origin-Opener-Policy = %s"same-origin" /
+ // %s"same-origin-allow-popups" /
+ // %s"unsafe-none"; case-sensitive
+
+ nsCOMPtr<nsISFVService> sfv = GetSFVService();
+
+ nsCOMPtr<nsISFVItem> item;
+ nsresult rv = sfv->ParseItem(openerPolicy, getter_AddRefs(item));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVBareItem> value;
+ rv = item->GetValue(getter_AddRefs(value));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsCOMPtr<nsISFVToken> token = do_QueryInterface(value);
+ if (!token) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ rv = token->GetValue(openerPolicy);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ nsILoadInfo::CrossOriginOpenerPolicy policy =
+ nsILoadInfo::OPENER_POLICY_UNSAFE_NONE;
+
+ if (openerPolicy.EqualsLiteral("same-origin")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN;
+ } else if (openerPolicy.EqualsLiteral("same-origin-allow-popups")) {
+ policy = nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_ALLOW_POPUPS;
+ }
+ if (policy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN) {
+ nsILoadInfo::CrossOriginEmbedderPolicy coep =
+ nsILoadInfo::EMBEDDER_POLICY_NULL;
+ bool isCoepCredentiallessEnabled;
+ rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel(
+ &isCoepCredentiallessEnabled);
+ if (!isCoepCredentiallessEnabled) {
+ nsAutoCString originTrialToken;
+ Unused << mResponseHead->GetHeader(nsHttp::OriginTrial, originTrialToken);
+ if (!originTrialToken.IsEmpty()) {
+ nsCOMPtr<nsIPrincipal> resultPrincipal;
+ rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
+ this, getter_AddRefs(resultPrincipal));
+ if (!NS_WARN_IF(NS_FAILED(rv))) {
+ OriginTrials trials;
+ trials.UpdateFromToken(NS_ConvertASCIItoUTF16(originTrialToken),
+ resultPrincipal);
+ if (trials.IsEnabled(OriginTrial::CoepCredentialless)) {
+ isCoepCredentiallessEnabled = true;
+ }
+ }
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (NS_SUCCEEDED(
+ GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &coep)) &&
+ (coep == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP ||
+ coep == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS)) {
+ policy =
+ nsILoadInfo::OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP;
+ }
+ }
+
+ *aOutPolicy = policy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetCrossOriginOpenerPolicy(
+ nsILoadInfo::CrossOriginOpenerPolicy* aPolicy) {
+ MOZ_ASSERT(aPolicy);
+ if (!aPolicy) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ // If this method is called before OnStartRequest (ie. before we call
+ // ComputeCrossOriginOpenerPolicy) or if we were unable to compute the
+ // policy we'll throw an error.
+ if (!LoadOnStartRequestCalled()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ *aPolicy = mComputedCrossOriginOpenerPolicy;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::HasCrossOriginOpenerPolicyMismatch(bool* aIsMismatch) {
+ // This should only be called in parent process.
+ MOZ_ASSERT(XRE_IsParentProcess());
+ *aIsMismatch = LoadHasCrossOriginOpenerPolicyMismatch();
+ return NS_OK;
+}
+
+void HttpBaseChannel::MaybeFlushConsoleReports() {
+ // Flush if we have a known window ID.
+ if (mLoadInfo->GetInnerWindowID() > 0) {
+ FlushReportsToConsole(mLoadInfo->GetInnerWindowID());
+ return;
+ }
+
+ // If this channel is part of a loadGroup, we can flush the console reports
+ // immediately.
+ nsCOMPtr<nsILoadGroup> loadGroup;
+ nsresult rv = GetLoadGroup(getter_AddRefs(loadGroup));
+ if (NS_SUCCEEDED(rv) && loadGroup) {
+ FlushConsoleReports(loadGroup);
+ }
+}
+
+void HttpBaseChannel::DoDiagnosticAssertWhenOnStopNotCalledOnDestroy() {}
+
+NS_IMETHODIMP HttpBaseChannel::SetWaitForHTTPSSVCRecord() {
+ mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR;
+ return NS_OK;
+}
+
+bool HttpBaseChannel::Http3Allowed() const {
+ bool isDirectOrNoProxy =
+ mProxyInfo ? static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()
+ : true;
+ return !mUpgradeProtocolCallback && isDirectOrNoProxy &&
+ !(mCaps & NS_HTTP_BE_CONSERVATIVE) && !LoadBeConservative() &&
+ LoadAllowHttp3();
+}
+
+void HttpBaseChannel::SetDummyChannelForImageCache() {
+ mDummyChannelForImageCache = true;
+ MOZ_ASSERT(!mResponseHead,
+ "SetDummyChannelForImageCache should only be called once");
+ mResponseHead = MakeUnique<nsHttpResponseHead>();
+}
+
+void HttpBaseChannel::SetEarlyHints(
+ nsTArray<EarlyHintConnectArgs>&& aEarlyHints) {
+ mEarlyHints = std::move(aEarlyHints);
+}
+
+nsTArray<EarlyHintConnectArgs>&& HttpBaseChannel::TakeEarlyHints() {
+ return std::move(mEarlyHints);
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetEarlyHintPreloaderId(uint64_t aEarlyHintPreloaderId) {
+ mEarlyHintPreloaderId = aEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetEarlyHintPreloaderId(uint64_t* aEarlyHintPreloaderId) {
+ NS_ENSURE_ARG_POINTER(aEarlyHintPreloaderId);
+ *aEarlyHintPreloaderId = mEarlyHintPreloaderId;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::SetClassicScriptHintCharset(
+ const nsAString& aClassicScriptHintCharset) {
+ mClassicScriptHintCharset = aClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetClassicScriptHintCharset(
+ nsAString& aClassicScriptHintCharset) {
+ aClassicScriptHintCharset = mClassicScriptHintCharset;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetDocumentCharacterSet(
+ const nsAString& aDocumentCharacterSet) {
+ mDocumentCharacterSet = aDocumentCharacterSet;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetDocumentCharacterSet(
+ nsAString& aDocumentCharacterSet) {
+ aDocumentCharacterSet = mDocumentCharacterSet;
+ return NS_OK;
+}
+
+void HttpBaseChannel::SetConnectionInfo(nsHttpConnectionInfo* aCI) {
+ mConnectionInfo = aCI ? aCI->Clone() : nullptr;
+}
+
+NS_IMETHODIMP
+HttpBaseChannel::GetIsProxyUsed(bool* aIsProxyUsed) {
+ if (mProxyInfo) {
+ if (!static_cast<nsProxyInfo*>(mProxyInfo.get())->IsDirect()) {
+ StoreIsProxyUsed(true);
+ }
+ }
+ *aIsProxyUsed = LoadIsProxyUsed();
+ return NS_OK;
+}
+
+void HttpBaseChannel::LogORBError(
+ const nsAString& aReason,
+ const OpaqueResponseBlockedTelemetryReason aTelemetryReason) {
+ RefPtr<dom::Document> doc;
+ mLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
+
+ nsAutoCString uri;
+ nsresult rv = nsContentUtils::AnonymizeURI(mURI, uri);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return;
+ }
+
+ uint64_t contentWindowId;
+ GetTopLevelContentWindowId(&contentWindowId);
+ if (contentWindowId) {
+ nsContentUtils::ReportToConsoleByWindowID(
+ u"A resource is blocked by OpaqueResponseBlocking, please check browser console for details."_ns,
+ nsIScriptError::warningFlag, "ORB"_ns, contentWindowId, mURI);
+ }
+
+ AutoTArray<nsString, 2> params;
+ params.AppendElement(NS_ConvertUTF8toUTF16(uri));
+ params.AppendElement(aReason);
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "ORB"_ns, doc,
+ nsContentUtils::eNECKO_PROPERTIES,
+ "ResourceBlockedORB", params);
+
+ Telemetry::LABELS_ORB_BLOCK_REASON label{
+ static_cast<uint32_t>(aTelemetryReason)};
+ Telemetry::AccumulateCategorical(label);
+
+ switch (mLoadInfo->GetExternalContentPolicyType()) {
+ case ExtContentPolicy::TYPE_FETCH:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::BLOCKED_FETCH);
+ break;
+ case ExtContentPolicy::TYPE_IMAGE:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGE);
+ break;
+ case ExtContentPolicy::TYPE_SCRIPT:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::SCRIPT);
+ break;
+ case ExtContentPolicy::TYPE_MEDIA:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::MEDIA);
+ break;
+ default:
+ Telemetry::AccumulateCategorical(
+ Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER);
+ break;
+ }
+}
+
+NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType(
+ uint32_t aEarlyHintLinkType) {
+ mEarlyHintLinkType = aEarlyHintLinkType;
+ return NS_OK;
+}
+
+NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType(
+ uint32_t* aEarlyHintLinkType) {
+ *aEarlyHintLinkType = mEarlyHintLinkType;
+ return NS_OK;
+}
+
+} // namespace net
+} // namespace mozilla