forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			6279 lines
		
	
	
	
		
			201 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			6279 lines
		
	
	
	
		
			201 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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,
 | ||
|                                                  RFPTarget::HttpUserAgent));
 | ||
|   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 {
 | ||
|   return mLoadGroup && 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 &&
 | ||
|       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) {
 | ||
|   mRedirectedCachekeys = 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::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)
 | ||
|     if (mRedirectedCachekeys) {
 | ||
|       LOG(
 | ||
|           ("HttpBaseChannel::SetupReplacementChannel "
 | ||
|            "[this=%p] transferring chain of redirect cache-keys",
 | ||
|            this));
 | ||
|       rv = httpInternal->SetCacheKeysRedirectChain(
 | ||
|           mRedirectedCachekeys.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);
 | ||
| }
 | ||
| 
 | ||
| void HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; }
 | ||
| 
 | ||
| void HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; }
 | ||
| 
 | ||
| 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
 | 
