/* -*- 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 #include #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/Mutex.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 "nsGlobalWindowInner.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 nsHttpAtomLiteral 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()); } int operator()(nsHttpAtomLiteral 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 rv = mChannel->SetRequestHeader(aHeader, aValue, false); MOZ_ASSERT(NS_SUCCEEDED(rv)); } return NS_OK; } private: ~AddHeadersToChannelVisitor() = default; nsCOMPtr mChannel; }; NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor) static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() { uint32_t pref = StaticPrefs:: browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly(); if (NS_WARN_IF(pref > static_cast(OpaqueResponseFilterFetch::All))) { return OpaqueResponseFilterFetch::All; } return static_cast(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), mHasContentDecompressed(false) { StoreApplyConversion(true); StoreAllowSTS(true); StoreTracingEnabled(true); StoreReportTiming(true); StoreAllowSpdy(true); StoreAllowHttp3(true); StoreAllowAltSvc(true); StoreResponseTimeoutEnabled(true); StoreAllRedirectsSameOrigin(true); StoreAllRedirectsPassTimingAllowCheck(true); StoreUpgradableToSecure(true); StoreIsUserAgentHeaderModified(false); 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 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> 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 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(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 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 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(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 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 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 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 mDest; }; NS_IMPL_ISUPPORTS(MIMEHeaderCopyVisitor, nsIHttpHeaderVisitor) static void NormalizeCopyComplete(void* aClosure, nsresult aStatus) { #ifdef DEBUG // Called on the STS thread by NS_AsyncCopy nsCOMPtr 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 ready = already_AddRefed(static_cast(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 lazyStream = do_QueryInterface(aUploadStream)) { nsCOMPtr internal; if (NS_SUCCEEDED( lazyStream->TakeInternalStream(getter_AddRefs(internal)))) { nsCOMPtr 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 mime = do_QueryInterface(aUploadStream)) { nsCOMPtr data; nsresult rv = mime->GetData(getter_AddRefs(data)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr replacement; rv = NormalizeUploadStream(data, getter_AddRefs(replacement), aReadyPromise); NS_ENSURE_SUCCESS(rv, rv); if (replacement) { nsCOMPtr replacementMime( do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 buffered = do_QueryInterface(aUploadStream)) { nsCOMPtr data; if (NS_SUCCEEDED(buffered->GetData(getter_AddRefs(data)))) { nsCOMPtr 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 multiplex = do_QueryInterface(aUploadStream)) { uint32_t count = multiplex->GetCount(); nsTArray> streams(count); nsTArray> promises(count); bool replace = false; for (uint32_t i = 0; i < count; ++i) { nsCOMPtr inner; nsresult rv = multiplex->GetStream(i, getter_AddRefs(inner)); NS_ENSURE_SUCCESS(rv, rv); RefPtr promise; nsCOMPtr 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 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 ready = GenericPromise::AllSettled(GetCurrentSerialEventTarget(), promises) ->Then(GetCurrentSerialEventTarget(), __func__, [](GenericPromise::AllSettledPromiseType:: ResolveOrRejectValue&& aResults) -> RefPtr { 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 async = do_QueryInterface(aUploadStream); nsCOMPtr 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 storageStream; nsresult rv = NS_NewStorageStream(4096, UINT32_MAX, getter_AddRefs(storageStream)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr sink; rv = storageStream->GetOutputStream(0, getter_AddRefs(sink)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 source = aUploadStream; if (!NS_InputStreamIsBuffered(aUploadStream)) { nsCOMPtr 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 target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); RefPtr 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 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> 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 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 replacement; RefPtr 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 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 nsIThreadRetargetableStreamListener { virtual ~InterceptFailedOnStop() = default; nsCOMPtr mNext; HttpBaseChannel* mChannel; public: InterceptFailedOnStop(nsIStreamListener* arg, HttpBaseChannel* chan) : mNext(arg), mChannel(chan) {} NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER 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(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_ADDREF(InterceptFailedOnStop) NS_IMPL_RELEASE(InterceptFailedOnStop) NS_INTERFACE_MAP_BEGIN(InterceptFailedOnStop) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRequestObserver) NS_INTERFACE_MAP_END NS_IMETHODIMP InterceptFailedOnStop::CheckListenerChain() { nsCOMPtr listener = do_QueryInterface(mNext); if (!listener) { return NS_ERROR_NO_INTERFACE; } return listener->CheckListenerChain(); } NS_IMETHODIMP InterceptFailedOnStop::OnDataFinished(nsresult aStatus) { nsCOMPtr listener = do_QueryInterface(mNext); if (listener) { return listener->OnDataFinished(aStatus); } return NS_OK; } 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 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 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; } else if (from.EqualsLiteral("zstd")) { mode = 4; } 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 enumerator = new nsContentEncodings(this, encoding.get()); enumerator.forget(aEncodings); return NS_OK; } //----------------------------------------------------------------------------- // HttpBaseChannel::nsContentEncodings //----------------------------------------------------------------------------- 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; } } if (!haveType) { encoding.BeginReading(start); if (CaseInsensitiveFindInReadable("zstd"_ns, start, end)) { aNextEncoding.AssignLiteral(APPLICATION_ZSTD); 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 //----------------------------------------------------------------------------- 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 loadContext; GetCallback(loadContext); if (loadContext) { nsCOMPtr 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) { MutexAutoLock lock(mOnDataFinishedMutex); *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) { MutexAutoLock lock(mOnDataFinishedMutex); *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(aReferrerInfo)->Clone(); } dom::ReferrerInfo* referrerInfo = static_cast(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 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 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; } // Mark that the User-Agent header has been modified. if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) { StoreIsUserAgentHeaderModified(true); } 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 aURI; rv = NS_NewURI(getter_AddRefs(aURI), aUrl); NS_ENSURE_SUCCESS(rv, rv); // Create new ReferrerInfo and initialize it. nsCOMPtr 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; } // Mark that the User-Agent header has been modified. if (nsHttp::ResolveAtom(aHeader) == nsHttp::User_Agent) { StoreIsUserAgentHeaderModified(true); } 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::GetIsUserAgentHeaderModified(bool* value) { NS_ENSURE_ARG_POINTER(value); *value = LoadIsUserAgentHeaderModified(); return NS_OK; } NS_IMETHODIMP HttpBaseChannel::SetIsUserAgentHeaderModified(bool value) { StoreIsUserAgentHeaderModified(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(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; nsAutoCString version; // https://fetch.spec.whatwg.org : // Responses over an HTTP/2 connection will always have the empty byte // sequence as status message as HTTP/2 does not support them. if (NS_WARN_IF(NS_FAILED(GetProtocolVersion(version))) || !version.EqualsLiteral("h2")) { 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 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 uriBeingLoaded = AntiTrackingUtils::MaybeGetDocumentURIBeingLoaded(this); return GetTopWindowURI(uriBeingLoaded, aTopWindowURI); } nsresult HttpBaseChannel::GetTopWindowURI(nsIURI* aURIBeingLoaded, nsIURI** aTopWindowURI) { nsresult rv = NS_OK; nsCOMPtr 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 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(version) / 10; } if (minor) { *minor = static_cast(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(version) / 10; } if (minor) { *minor = static_cast(version) % 10; } return NS_OK; } void HttpBaseChannel::NotifySetCookie(const nsACString& aCookie) { nsCOMPtr obs = services::GetObserverService(); if (obs) { obs->NotifyObservers(static_cast(this), "http-on-response-set-cookie", NS_ConvertASCIItoUTF16(aCookie).get()); } } bool HttpBaseChannel::IsBrowsingContextDiscarded() const { // If there is no loadGroup attached to the current channel, we check the // global private browsing state for the private channel instead. For // non-private channel, we will always return false here. // // Note that we can only access the global private browsing state in the // parent process. So, we will fallback to just return false in the content // process. if (!mLoadGroup) { if (!XRE_IsParentProcess()) { return false; } return mLoadInfo->GetOriginAttributes().mPrivateBrowsingId != 0 && !dom::CanonicalBrowsingContext::IsPrivateBrowsingActive(); } return mLoadGroup->GetIsBrowsingContextDiscarded(); } // https://mikewest.github.io/corpp/#process-navigation-response nsresult HttpBaseChannel::ProcessCrossOriginEmbedderPolicyHeader() { nsresult rv; if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { return NS_OK; } // Only consider Cross-Origin-Embedder-Policy for document loads. if (mLoadInfo->GetExternalContentPolicyType() != ExtContentPolicy::TYPE_DOCUMENT && mLoadInfo->GetExternalContentPolicyType() != ExtContentPolicy::TYPE_SUBDOCUMENT) { return NS_OK; } nsILoadInfo::CrossOriginEmbedderPolicy resultPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL; bool isCoepCredentiallessEnabled; rv = mLoadInfo->GetIsOriginTrialCoepCredentiallessEnabledForTopLevel( &isCoepCredentiallessEnabled); NS_ENSURE_SUCCESS(rv, rv); rv = GetResponseEmbedderPolicy(isCoepCredentiallessEnabled, &resultPolicy); if (NS_FAILED(rv)) { return NS_OK; } // https://html.spec.whatwg.org/multipage/origin.html#coep if (mLoadInfo->GetExternalContentPolicyType() == ExtContentPolicy::TYPE_SUBDOCUMENT && !nsHttpChannel::IsRedirectStatus(mResponseHead->Status()) && mLoadInfo->GetLoadingEmbedderPolicy() != nsILoadInfo::EMBEDDER_POLICY_NULL && resultPolicy != nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP && resultPolicy != nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) { return NS_ERROR_DOM_COEP_FAILED; } return NS_OK; } // https://mikewest.github.io/corpp/#corp-check nsresult HttpBaseChannel::ProcessCrossOriginResourcePolicyHeader() { // Fetch 4.5.9 dom::RequestMode requestMode; MOZ_ALWAYS_SUCCEEDS(GetRequestMode(&requestMode)); // XXX this seems wrong per spec? What about navigate if (requestMode != RequestMode::No_cors) { return NS_OK; } // We only apply this for resources. auto extContentPolicyType = mLoadInfo->GetExternalContentPolicyType(); if (extContentPolicyType == ExtContentPolicy::TYPE_DOCUMENT || extContentPolicyType == ExtContentPolicy::TYPE_WEBSOCKET || extContentPolicyType == ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD) { return NS_OK; } if (extContentPolicyType == ExtContentPolicy::TYPE_SUBDOCUMENT) { // COEP pref off, skip CORP checking for subdocument. if (!StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { return NS_OK; } // COEP 3.2.1.2 when request targets a nested browsing context then embedder // policy value is "unsafe-none", then return allowed. if (mLoadInfo->GetLoadingEmbedderPolicy() == nsILoadInfo::EMBEDDER_POLICY_NULL) { return NS_OK; } } MOZ_ASSERT(mLoadInfo->GetLoadingPrincipal(), "Resources should always have a LoadingPrincipal"); if (!mResponseHead) { return NS_OK; } if (mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()) { return NS_OK; } nsAutoCString content; Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Resource_Policy, content); if (StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy()) { if (content.IsEmpty()) { if (mLoadInfo->GetLoadingEmbedderPolicy() == nsILoadInfo::EMBEDDER_POLICY_CREDENTIALLESS) { bool requestIncludesCredentials = false; nsresult rv = GetCorsIncludeCredentials(&requestIncludesCredentials); if (NS_FAILED(rv)) { return NS_OK; } // COEP: Set policy to `same-origin` if: response’s // request-includes-credentials is true, or forNavigation is true. if (requestIncludesCredentials || extContentPolicyType == ExtContentPolicyType::TYPE_SUBDOCUMENT) { content = "same-origin"_ns; } } else if (mLoadInfo->GetLoadingEmbedderPolicy() == nsILoadInfo::EMBEDDER_POLICY_REQUIRE_CORP) { // COEP 3.2.1.6 If policy is null, and embedder policy is // "require-corp", set policy to "same-origin". Note that we treat // invalid value as "cross-origin", which spec indicates. We might want // to make that stricter. content = "same-origin"_ns; } } } if (content.IsEmpty()) { return NS_OK; } nsCOMPtr 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 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 ctx; mLoadInfo->GetBrowsingContext(getter_AddRefs(ctx)); // In xpcshell-tests we don't always have a browsingContext if (!ctx) { return NS_OK; } nsCOMPtr 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 currentWindowGlobal = ctx->Canonical()->GetCurrentWindowGlobal(); if (!currentWindowGlobal) { return NS_OK; } // We use the top window principal as the documentOrigin nsCOMPtr 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 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 params; CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement()); RefPtr 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 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 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 or "); 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 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* cacheKeys) { auto RedirectedCachekeys = mRedirectedCachekeys.Lock(); auto& ref = RedirectedCachekeys.ref(); ref = WrapUnique(cacheKeys); return NS_OK; } NS_IMETHODIMP HttpBaseChannel::GetLocalAddress(nsACString& addr) { if (mSelfAddr.raw.family == PR_AF_UNSPEC) return NS_ERROR_NOT_AVAILABLE; addr.SetLength(kIPv6CStrBufSize); mSelfAddr.ToStringBuffer(addr.BeginWriting(), kIPv6CStrBufSize); addr.SetLength(strlen(addr.BeginReading())); return NS_OK; } NS_IMETHODIMP HttpBaseChannel::TakeAllSecurityMessages( nsCOMArray& aMessages) { MOZ_ASSERT(NS_IsMainThread()); aMessages.Clear(); for (const auto& pair : mSecurityConsoleMessages) { nsresult rv; nsCOMPtr 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 pair(aMessageTag, aMessageCategory); mSecurityConsoleMessages.AppendElement(std::move(pair)); nsCOMPtr console( do_GetService(NS_CONSOLESERVICE_CONTRACTID)); if (!console) { return NS_ERROR_FAILURE; } nsCOMPtr 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 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) { 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& 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& 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 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 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 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 newLoadInfo = static_cast(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 redirectPrincipal; nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( this, getter_AddRefs(redirectPrincipal)); nsCOMPtr 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 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 csp = newLoadInfo->GetCspToInherit(); if (csp) { bool upgradeInsecureRequests = false; csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests); if (upgradeInsecureRequests) { nsCOMPtr resultPrincipal = BasePrincipal::CreateContentPrincipal( aNewURI, newLoadInfo->GetOriginAttributes()); bool isConsideredSameOriginforUIR = nsContentSecurityUtils::IsConsideredSameOriginForUIR( newLoadInfo->TriggeringPrincipal(), resultPrincipal); static_cast(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 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 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); // notify "http-on-before-stop-request" observers gHttpHandler->OnBeforeStopRequest(this); if (mListener && !LoadOnStopRequestCalled()) { nsCOMPtr listener = mListener; StoreOnStopRequestCalled(true); listener->OnStopRequest(this, mStatus); } StoreOnStopRequestCalled(true); // notify "http-on-stop-request" 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 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(nsHttp::Cookie.val(), cookie, false); } /* static */ void HttpBaseChannel::PropagateReferenceIfNeeded( nsIURI* aURI, nsCOMPtr& 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); } // In case we are here because an upgrade happened through mixed content // upgrading, CSP upgrade-insecure-requests, HTTPS-Only or HTTPS-First, we // have to recalculate the referrer based on the original referrer to // account for the different scheme. This does NOT apply to HSTS. // See Bug 1857894 and order of https://fetch.spec.whatwg.org/#main-fetch. // Otherwise, if we have a new referrer policy, we want to recalculate the // referrer based on the old computed referrer (Bug 1678545). bool wasNonHSTSUpgrade = (aRedirectFlags & nsIChannelEventSink::REDIRECT_STS_UPGRADE) && (!mLoadInfo->GetHstsStatus()); if (wasNonHSTSUpgrade) { nsCOMPtr referrer = mReferrerInfo->GetOriginalReferrer(); config.referrerInfo = new dom::ReferrerInfo(referrer, mReferrerInfo->ReferrerPolicy(), mReferrerInfo->GetSendReferrer()); } else if (referrerPolicy != dom::ReferrerPolicy::_empty) { nsCOMPtr referrer = mReferrerInfo->GetComputedReferrer(); config.referrerInfo = new dom::ReferrerInfo( referrer, referrerPolicy, mReferrerInfo->GetSendReferrer()); } else { config.referrerInfo = mReferrerInfo; } } } nsCOMPtr oldTimedChannel( do_QueryInterface(static_cast(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 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 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 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 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 newPBChannel = do_QueryInterface(newChannel); if (newPBChannel) { newPBChannel->SetPrivate(*config.privateBrowsing); } } // Transfer the timing data (if we are dealing with an nsITimedChannel). nsCOMPtr 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( config.timedChannelInfo->internalRedirectCount()))); } else { int32_t newCount = config.timedChannelInfo->redirectCount() + 1; newTimedChannel->SetRedirectCount(std::max( newCount, static_cast(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 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 httpChannel = do_QueryInterface(newChannel); if (!httpChannel) { return; // no other options to set } if (config.uploadStream) { nsCOMPtr uploadChannel = do_QueryInterface(httpChannel); nsCOMPtr 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 success{}; success = httpChannel->SetReferrerInfo(config.referrerInfo); MOZ_ASSERT(NS_SUCCEEDED(success)); } if (config.method) { DebugOnly 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 newLoadInfo = newChannel->LoadInfo(); nsCOMPtr 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 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 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 httpInternal = do_QueryInterface(newChannel); if (httpInternal) { httpInternal->SetLastRedirectFlags(redirectFlags); if (LoadRequireCORSPreflight()) { httpInternal->SetCorsPreflightParameters(mUnsafeHeaders, false, 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)); } } // convery the IsUserAgentHeaderModified value. if (httpInternal) { rv = httpInternal->SetIsUserAgentHeaderModified( LoadIsUserAgentHeaderModified()); 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 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 realChannel; CallQueryInterface(newChannel, realChannel.StartAssignment()); if (realChannel) { realChannel->SetTopWindowURI(mTopWindowURI); realChannel->StoreTaintedOriginFlag( ShouldTaintReplacementChannelOrigin(newChannel, redirectFlags)); } // update the DocumentURI indicator since we are being redirected. // if this was a top-level document channel, then the new channel // should have its mDocumentURI point to newURI; otherwise, we // just need to pass along our mDocumentURI to the new channel. if (newURI && (mURI == mDocumentURI)) { rv = httpInternal->SetDocumentURI(newURI); } else { rv = httpInternal->SetDocumentURI(mDocumentURI); } MOZ_ASSERT(NS_SUCCEEDED(rv)); // if there is a chain of keys for redirect-responses we transfer it to // the new channel (see bug #561276) { auto redirectedCachekeys = mRedirectedCachekeys.Lock(); auto& ref = redirectedCachekeys.ref(); if (ref) { LOG( ("HttpBaseChannel::SetupReplacementChannel " "[this=%p] transferring chain of redirect cache-keys", this)); rv = httpInternal->SetCacheKeysRedirectChain(ref.release()); MOZ_ASSERT(NS_SUCCEEDED(rv)); } } // Preserve Request mode. rv = httpInternal->SetRequestMode(mRequestMode); MOZ_ASSERT(NS_SUCCEEDED(rv)); // Preserve Redirect mode flag. rv = httpInternal->SetRedirectMode(mRedirectMode); MOZ_ASSERT(NS_SUCCEEDED(rv)); // Preserve Integrity metadata. rv = httpInternal->SetIntegrityMetadata(mIntegrityMetadata); MOZ_ASSERT(NS_SUCCEEDED(rv)); httpInternal->SetAltDataForChild(LoadAltDataForChild()); if (LoadDisableAltDataCache()) { httpInternal->DisableAltDataCache(); } } // transfer any properties nsCOMPtr 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 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 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(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 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& aLists) { aLists = mMatchedTrackingLists.Clone(); return NS_OK; } NS_IMETHODIMP HttpBaseChannel::GetMatchedTrackingFullHashes( nsTArray& aFullHashes) { aFullHashes = mMatchedTrackingFullHashes.Clone(); return NS_OK; } NS_IMETHODIMP HttpBaseChannel::SetMatchedTrackingInfo( const nsTArray& aLists, const nsTArray& 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 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() && !serializedOrigin.EqualsLiteral("null"))) { *_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 = mTransactionTimings.transactionPending; 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 performanceTimingData( dom::PerformanceTimingData::Create(this, this, 0, initiatorType, entryName)); if (!performanceTimingData) { return; } 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 pDomWindow = GetInnerDOMWindow(); if (!pDomWindow) { return nullptr; } return pDomWindow->GetDocumentURI(); } nsPIDOMWindowInner* HttpBaseChannel::GetInnerDOMWindow() { nsCOMPtr loadContext; NS_QueryNotificationCallbacks(this, loadContext); if (!loadContext) { return nullptr; } nsCOMPtr domWindow; loadContext->GetAssociatedWindow(getter_AddRefs(domWindow)); if (!domWindow) { return nullptr; } auto* pDomWindow = nsPIDOMWindowOuter::From(domWindow); if (!pDomWindow) { return nullptr; } nsCOMPtr 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 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 childLoadGroup = do_QueryInterface(mLoadGroup); if (!childLoadGroup) { return false; } nsCOMPtr 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(mRequestContext); } void HttpBaseChannel::EnsureBrowserId() { if (mBrowserId) { return; } RefPtr bc; MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc))); if (bc) { mBrowserId = bc->GetBrowserId(); } } void HttpBaseChannel::SetCorsPreflightParameters( const nsTArray& aUnsafeHeaders, bool aShouldStripRequestBodyHeader, bool aShouldStripAuthHeader) { MOZ_RELEASE_ASSERT(!LoadRequestObserversCalled()); StoreRequireCORSPreflight(true); mUnsafeHeaders = aUnsafeHeaders.Clone(); if (aShouldStripRequestBodyHeader || aShouldStripAuthHeader) { mUnsafeHeaders.RemoveElementsBy([&](const nsCString& aHeader) { return (aShouldStripRequestBodyHeader && (aHeader.LowerCaseEqualsASCII("content-type") || aHeader.LowerCaseEqualsASCII("content-encoding") || aHeader.LowerCaseEqualsASCII("content-language") || aHeader.LowerCaseEqualsASCII("content-location"))) || (aShouldStripAuthHeader && aHeader.LowerCaseEqualsASCII("authorization")); }); } } 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) { // for internal redirect due to auth retry we do not have any limit // as we might restrict the number of times a user might retry // authentication if (aRedirectFlags & nsIChannelEventSink::REDIRECT_AUTH_RETRY) { return NS_OK; } // 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(aClosure); const char* snifferType = [chan]() { if (RefPtr 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 static void ParseServerTimingHeader( const UniquePtr& aHeader, nsTArray>& aOutput) { if (!aHeader) { return; } nsAutoCString serverTimingHeader; Unused << aHeader->GetHeader(nsHttp::Server_Timing, serverTimingHeader); if (serverTimingHeader.IsEmpty()) { return; } ServerTimingParser parser(serverTimingHeader); parser.Parse(); nsTArray> array = parser.TakeServerTimingHeaders(); aOutput.AppendElements(array); } NS_IMETHODIMP HttpBaseChannel::GetServerTiming(nsIArray** aServerTiming) { nsresult rv; NS_ENSURE_ARG_POINTER(aServerTiming); nsCOMPtr array = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); nsTArray> 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>& aServerTiming) { aServerTiming.Clear(); if (nsContentUtils::ComputeIsSecureContext(this)) { ParseServerTimingHeader(mResponseHead, aServerTiming); ParseServerTimingHeader(mResponseTrailers, aServerTiming); } return NS_OK; } NS_IMETHODIMP HttpBaseChannel::CancelByURLClassifier(nsresult aErrorCode) { MOZ_ASSERT( UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode)); return Cancel(aErrorCode); } NS_IMETHODIMP HttpBaseChannel::SetIPv4Disabled() { mCaps |= NS_HTTP_DISABLE_IPV4; return NS_OK; } NS_IMETHODIMP HttpBaseChannel::SetIPv6Disabled() { mCaps |= NS_HTTP_DISABLE_IPV6; return NS_OK; } NS_IMETHODIMP HttpBaseChannel::GetResponseEmbedderPolicy( bool aIsOriginTrialCoepCredentiallessEnabled, nsILoadInfo::CrossOriginEmbedderPolicy* aOutPolicy) { *aOutPolicy = nsILoadInfo::EMBEDDER_POLICY_NULL; if (!mResponseHead) { return NS_ERROR_NOT_AVAILABLE; } if (!nsContentUtils::ComputeIsSecureContext(this)) { // Feature is only available for secure contexts. return NS_OK; } nsAutoCString content; Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Embedder_Policy, content); *aOutPolicy = NS_GetCrossOriginEmbedderPolicyFromHeader( content, aIsOriginTrialCoepCredentiallessEnabled); return NS_OK; } // Obtain a cross-origin opener-policy from a response response and a // cross-origin opener policy initiator. // https://gist.github.com/annevk/6f2dd8c79c77123f39797f6bdac43f3e NS_IMETHODIMP HttpBaseChannel::ComputeCrossOriginOpenerPolicy( nsILoadInfo::CrossOriginOpenerPolicy aInitiatorPolicy, nsILoadInfo::CrossOriginOpenerPolicy* aOutPolicy) { MOZ_ASSERT(aOutPolicy); *aOutPolicy = nsILoadInfo::OPENER_POLICY_UNSAFE_NONE; if (!mResponseHead) { return NS_ERROR_NOT_AVAILABLE; } // COOP headers are ignored for insecure-context loads. if (!nsContentUtils::ComputeIsSecureContext(this)) { return NS_OK; } nsAutoCString openerPolicy; Unused << mResponseHead->GetHeader(nsHttp::Cross_Origin_Opener_Policy, openerPolicy); // Cross-Origin-Opener-Policy = %s"same-origin" / // %s"same-origin-allow-popups" / // %s"unsafe-none"; case-sensitive nsCOMPtr sfv = GetSFVService(); nsCOMPtr item; nsresult rv = sfv->ParseItem(openerPolicy, getter_AddRefs(item)); if (NS_FAILED(rv)) { return rv; } nsCOMPtr value; rv = item->GetValue(getter_AddRefs(value)); if (NS_FAILED(rv)) { return rv; } nsCOMPtr 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 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 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(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(); } void HttpBaseChannel::SetEarlyHints( nsTArray&& aEarlyHints) { mEarlyHints = std::move(aEarlyHints); } nsTArray&& 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(mProxyInfo.get())->IsDirect()) { StoreIsProxyUsed(true); } } *aIsProxyUsed = LoadIsProxyUsed(); return NS_OK; } static void CollectORBBlockTelemetry( const OpaqueResponseBlockedTelemetryReason aTelemetryReason, ExtContentPolicy aPolicy) { Telemetry::LABELS_ORB_BLOCK_REASON label{ static_cast(aTelemetryReason)}; Telemetry::AccumulateCategorical(label); switch (aPolicy) { case ExtContentPolicy::TYPE_INVALID: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::INVALID); break; case ExtContentPolicy::TYPE_OTHER: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER); break; case ExtContentPolicy::TYPE_FETCH: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::BLOCKED_FETCH); break; case ExtContentPolicy::TYPE_SCRIPT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::SCRIPT); break; case ExtContentPolicy::TYPE_IMAGE: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGE); break; case ExtContentPolicy::TYPE_STYLESHEET: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::STYLESHEET); break; case ExtContentPolicy::TYPE_XMLHTTPREQUEST: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::XMLHTTPREQUEST); break; case ExtContentPolicy::TYPE_DTD: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::DTD); break; case ExtContentPolicy::TYPE_FONT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::FONT); break; case ExtContentPolicy::TYPE_MEDIA: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::MEDIA); break; case ExtContentPolicy::TYPE_CSP_REPORT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::CSP_REPORT); break; case ExtContentPolicy::TYPE_XSLT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::XSLT); break; case ExtContentPolicy::TYPE_IMAGESET: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::IMAGESET); break; case ExtContentPolicy::TYPE_WEB_MANIFEST: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_MANIFEST); break; case ExtContentPolicy::TYPE_SPECULATIVE: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::SPECULATIVE); break; case ExtContentPolicy::TYPE_UA_FONT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::UA_FONT); break; case ExtContentPolicy::TYPE_PROXIED_WEBRTC_MEDIA: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::PROXIED_WEBRTC_MEDIA); break; case ExtContentPolicy::TYPE_PING: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::PING); break; case ExtContentPolicy::TYPE_BEACON: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::BEACON); break; case ExtContentPolicy::TYPE_WEB_TRANSPORT: Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::WEB_TRANSPORT); break; case ExtContentPolicy::TYPE_WEB_IDENTITY: // Don't bother extending the telemetry for this. Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::OTHER); break; case ExtContentPolicy::TYPE_DOCUMENT: case ExtContentPolicy::TYPE_SUBDOCUMENT: case ExtContentPolicy::TYPE_OBJECT: case ExtContentPolicy::TYPE_OBJECT_SUBREQUEST: case ExtContentPolicy::TYPE_WEBSOCKET: case ExtContentPolicy::TYPE_SAVEAS_DOWNLOAD: MOZ_ASSERT_UNREACHABLE("Shouldn't block this type"); // DOCUMENT, SUBDOCUMENT, OBJECT, OBJECT_SUBREQUEST, // WEBSOCKET and SAVEAS_DOWNLOAD are excluded from ORB Telemetry::AccumulateCategorical( Telemetry::LABELS_ORB_BLOCK_INITIATOR::EXCLUDED); break; // Do not add default: so that compilers can catch the missing case. } } void HttpBaseChannel::LogORBError( const nsAString& aReason, const OpaqueResponseBlockedTelemetryReason aTelemetryReason) { auto policy = mLoadInfo->GetExternalContentPolicyType(); CollectORBBlockTelemetry(aTelemetryReason, policy); // Blocking `ExtContentPolicy::TYPE_BEACON` isn't web observable, so keep // quiet in the console about blocking it. if (policy == ExtContentPolicy::TYPE_BEACON) { return; } RefPtr 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 params; params.AppendElement(NS_ConvertUTF8toUTF16(uri)); params.AppendElement(aReason); nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "ORB"_ns, doc, nsContentUtils::eNECKO_PROPERTIES, "ResourceBlockedORB", params); } NS_IMETHODIMP HttpBaseChannel::SetEarlyHintLinkType( uint32_t aEarlyHintLinkType) { mEarlyHintLinkType = aEarlyHintLinkType; return NS_OK; } NS_IMETHODIMP HttpBaseChannel::GetEarlyHintLinkType( uint32_t* aEarlyHintLinkType) { *aEarlyHintLinkType = mEarlyHintLinkType; return NS_OK; } NS_IMETHODIMP HttpBaseChannel::SetHasContentDecompressed(bool aValue) { LOG(("HttpBaseChannel::SetHasContentDecompressed [this=%p value=%d]\n", this, aValue)); mHasContentDecompressed = aValue; return NS_OK; } NS_IMETHODIMP HttpBaseChannel::GetHasContentDecompressed(bool* value) { *value = mHasContentDecompressed; return NS_OK; } } // namespace net } // namespace mozilla