/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set expandtab ts=4 sw=2 sts=2 cin: */
/* 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 "HttpLog.h"
#include <inttypes.h>
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/glean/AntitrackingMetrics.h"
#include "mozilla/glean/NetwerkMetrics.h"
#include "mozilla/glean/NetwerkProtocolHttpMetrics.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "nsCOMPtr.h"
#include "nsContentSecurityUtils.h"
#include "nsHttp.h"
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.h"
#include "nsIStreamConverter.h"
#include "nsString.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "nsICacheEntry.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsINetworkInterceptController.h"
#include "nsIStringBundle.h"
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsIProtocolProxyService2.h"
#include "nsIURLQueryStringStripper.h"
#include "nsIWebTransport.h"
#include "nsCRT.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIStreamTransportService.h"
#include "prnetdb.h"
#include "nsEscape.h"
#include "nsComponentManagerUtils.h"
#include "nsStreamUtils.h"
#include "nsIOService.h"
#include "nsDNSPrefetch.h"
#include "nsChannelClassifier.h"
#include "nsIRedirectResultListener.h"
#include "mozilla/TimeStamp.h"
#include "nsError.h"
#include "nsPrintfCString.h"
#include "nsQueryObject.h"
#include "nsThreadUtils.h"
#include "nsIConsoleService.h"
#include "nsINetworkErrorLogging.h"
#include "mozilla/AntiTrackingRedirectHeuristic.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/PerfStats.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/Components.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_privacy.h"
#include "mozilla/StaticPrefs_security.h"
#include "sslt.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsContentUtils.h"
#include "nsContentSecurityManager.h"
#include "nsIClassOfService.h"
#include "CookieService.h"
#include "nsIPrincipal.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsITransportSecurityInfo.h"
#include "nsIWebProgressListener.h"
#include "LoadContextInfo.h"
#include "netCore.h"
#include "nsHttpTransaction.h"
#include "nsICancelable.h"
#include "nsIHttpChannelInternal.h"
#include "nsIPrompt.h"
#include "nsInputStreamPump.h"
#include "nsURLHelper.h"
#include "nsISocketTransport.h"
#include "nsIStreamConverterService.h"
#include "nsISiteSecurityService.h"
#include "nsString.h"
#include "nsStringStream.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/dom/ReferrerInfo.h"
#include "mozilla/Telemetry.h"
#include "AlternateServices.h"
#include "NetworkMarker.h"
#include "nsIHttpPushListener.h"
#include "nsIDNSRecord.h"
#include "mozilla/dom/Document.h"
#include "nsICompressConvStats.h"
#include "nsCORSListenerProxy.h"
#include "nsISocketProvider.h"
#include "mozilla/extensions/StreamFilterParent.h"
#include "mozilla/net/Predictor.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/NullPrincipal.h"
#include "CacheControlParser.h"
#include "nsMixedContentBlocker.h"
#include "CacheStorageService.h"
#include "HttpChannelParent.h"
#include "HttpTransactionParent.h"
#include "ThirdPartyUtil.h"
#include "InterceptedHttpChannel.h"
#include "../../cache2/CacheFileUtils.h"
#include "nsINetworkLinkService.h"
#include "mozilla/ContentBlockingAllowList.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/nsHTTPSOnlyStreamListener.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/net/AsyncUrlChannelClassifier.h"
#include "mozilla/net/CookieJarSettings.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/OpaqueResponseUtils.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "HttpTrafficAnalyzer.h"
#include "mozilla/net/SocketProcessParent.h"
#include "mozilla/dom/SecFetch.h"
#include "mozilla/net/TRRService.h"
#include "nsUnknownDecoder.h"
#ifdef XP_WIN
# include
"HttpWinUtils.h"
#endif
#ifdef XP_MACOSX
# include
"MicrosoftEntraSSOUtils.h"
#endif
#ifdef FUZZING
# include
"mozilla/StaticPrefs_fuzzing.h"
#endif
namespace mozilla {
using namespace dom;
namespace net {
namespace {
// True if the local cache should be bypassed when processing a request.
#define BYPASS_LOCAL_CACHE(loadFlags, isPreferCacheLoadOverBypass) \
((loadFlags) & (nsIRequest::LOAD_BYPASS_CACHE | \
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE) && \
!(((loadFlags) & nsIRequest::LOAD_FROM_CACHE) && \
(isPreferCacheLoadOverBypass)))
#define RECOVER_FROM_CACHE_FILE_ERROR(result) \
((result) == NS_ERROR_FILE_NOT_FOUND || \
(result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)
#define WRONG_RACING_RESPONSE_SOURCE(req) \
(mRaceCacheWithNetwork && \
(((mFirstResponseSource == RESPONSE_FROM_CACHE) && \
((req) != mCachePump)) || \
((mFirstResponseSource == RESPONSE_FROM_NETWORK) && \
((req) != mTransactionPump))))
static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
enum ChannelDisposition {
kHttpCanceled = 0,
kHttpDisk = 1,
kHttpNetOK = 2,
kHttpNetEarlyFail = 3,
kHttpNetLateFail = 4,
kHttpsCanceled = 8,
kHttpsDisk = 9,
kHttpsNetOK = 10,
kHttpsNetEarlyFail = 11,
kHttpsNetLateFail = 12
};
void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss,
nsIChannel* aChannel) {
nsCString key(
"UNKNOWN");
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
nsAutoCString contentType;
if (NS_SUCCEEDED(aChannel->GetContentType(contentType))) {
if (nsContentUtils::IsJavascriptMIMEType(
NS_ConvertUTF8toUTF16(contentType))) {
key.AssignLiteral(
"JAVASCRIPT");
}
else if (StringBeginsWith(contentType,
"text/css"_ns) ||
(loadInfo && loadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_STYLESHEET)) {
key.AssignLiteral(
"STYLESHEET");
}
else if (StringBeginsWith(contentType,
"application/wasm"_ns)) {
key.AssignLiteral(
"WASM");
}
else if (StringBeginsWith(contentType,
"image/"_ns)) {
key.AssignLiteral(
"IMAGE");
}
else if (StringBeginsWith(contentType,
"video/"_ns)) {
key.AssignLiteral(
"MEDIA");
}
else if (StringBeginsWith(contentType,
"audio/"_ns)) {
key.AssignLiteral(
"MEDIA");
}
else if (!StringBeginsWith(contentType,
nsLiteralCString(UNKNOWN_CONTENT_TYPE))) {
key.AssignLiteral(
"OTHER");
}
}
Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3 label =
Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
switch (hitOrMiss) {
case kCacheUnresolved:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unresolved;
break;
case kCacheHit:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Hit;
break;
case kCacheHitViaReval:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::HitViaReval;
break;
case kCacheMissedViaReval:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::MissedViaReval;
break;
case kCacheMissed:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Missed;
break;
case kCacheUnknown:
label = Telemetry::LABELS_HTTP_CACHE_DISPOSITION_3::Unknown;
break;
}
Telemetry::AccumulateCategoricalKeyed(key, label);
Telemetry::AccumulateCategoricalKeyed(
"ALL"_ns, label);
}
// Computes and returns a SHA1 hash of the input buffer. The input buffer
// must be a null-terminated string.
nsresult Hash(
const char* buf, nsACString& hash) {
nsresult rv;
nsCOMPtr<nsICryptoHash> hasher =
do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Init(nsICryptoHash::SHA1);
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Update(
reinterpret_cast<
unsigned const char*>(buf), strlen(buf));
NS_ENSURE_SUCCESS(rv, rv);
rv = hasher->Finish(
true, hash);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
class CookieVisitor final {
public:
explicit CookieVisitor(nsHttpResponseHead* aResponseHead) {
nsAutoCString cookieHeader;
if (NS_SUCCEEDED(
aResponseHead->GetHeader(nsHttp::Set_Cookie, cookieHeader))) {
for (
const auto& cookie : cookieHeader.Split(
'\n')) {
mCookieHeaders.AppendElement(cookie);
}
}
}
~CookieVisitor() =
default;
const nsTArray<nsCString>& CookieHeaders()
const {
return mCookieHeaders; }
private:
nsTArray<nsCString> mCookieHeaders;
};
}
// unnamed namespace
// We only treat 3xx responses as redirects if they have a Location header and
// the status code is in a whitelist.
bool nsHttpChannel::WillRedirect(
const nsHttpResponseHead& response) {
return IsRedirectStatus(response.Status()) &&
response.HasHeader(nsHttp::Location);
}
nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
nsHttpRequestHead* requestHead);
class MOZ_STACK_CLASS AutoRedirectVetoNotifier {
public:
explicit AutoRedirectVetoNotifier(nsHttpChannel* channel, nsresult& aRv)
: mChannel(channel), mRv(aRv) {
if (mChannel->LoadHasAutoRedirectVetoNotifier()) {
MOZ_CRASH(
"Nested AutoRedirectVetoNotifier on the stack");
mChannel = nullptr;
return;
}
mChannel->StoreHasAutoRedirectVetoNotifier(
true);
}
~AutoRedirectVetoNotifier() { ReportRedirectResult(mRv); }
void RedirectSucceeded() { ReportRedirectResult(NS_OK); }
private:
nsHttpChannel* mChannel;
bool mCalledReport =
false;
nsresult& mRv;
void ReportRedirectResult(nsresult aRv);
};
void AutoRedirectVetoNotifier::ReportRedirectResult(nsresult aRv) {
if (!mChannel)
return;
if (mCalledReport) {
return;
}
mCalledReport =
true;
mChannel->mRedirectChannel = nullptr;
if (NS_SUCCEEDED(aRv)) {
mChannel->RemoveAsNonTailRequest();
}
nsCOMPtr<nsIRedirectResultListener> vetoHook;
NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
getter_AddRefs(vetoHook));
nsHttpChannel* channel = mChannel;
mChannel = nullptr;
if (vetoHook) vetoHook->OnRedirectResult(aRv);
// Drop after the notification
channel->StoreHasAutoRedirectVetoNotifier(
false);
}
//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------
nsHttpChannel::nsHttpChannel() : HttpAsyncAborter<nsHttpChannel>(
this) {
LOG((
"Creating nsHttpChannel [this=%p, nsIChannel=%p]\n",
this,
static_cast<nsIChannel*>(
this)));
mChannelCreationTime = PR_Now();
mChannelCreationTimestamp = TimeStamp::Now();
}
nsHttpChannel::~nsHttpChannel() {
LOG((
"Destroying nsHttpChannel [this=%p, nsIChannel=%p]\n",
this,
static_cast<nsIChannel*>(
this)));
if (LOG_ENABLED()) {
nsCString webExtension;
this->GetPropertyAsACString(u
"cancelledByExtension"_ns, webExtension);
if (!webExtension.IsEmpty()) {
LOG((
"channel [%p] cancelled by extension [id=%s]",
this,
webExtension.get()));
}
}
if (mAuthProvider) {
DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
ReleaseMainThreadOnlyReferences();
if (gHttpHandler) {
gHttpHandler->RemoveHttpChannel(mChannelId);
}
}
void nsHttpChannel::ReleaseMainThreadOnlyReferences() {
if (NS_IsMainThread()) {
// Already on main thread, let dtor to
// take care of releasing references
return;
}
nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
arrayToRelease.AppendElement(mAuthProvider.forget());
arrayToRelease.AppendElement(mRedirectChannel.forget());
arrayToRelease.AppendElement(mPreflightChannel.forget());
arrayToRelease.AppendElement(mDNSPrefetch.forget());
MOZ_DIAGNOSTIC_ASSERT(
!mEarlyHintObserver,
"Early hint observer should have been released in ReleaseListeners()");
arrayToRelease.AppendElement(mEarlyHintObserver.forget());
MOZ_DIAGNOSTIC_ASSERT(
!mChannelClassifier,
"Channel classifier should have been released in ReleaseListeners()");
arrayToRelease.AppendElement(
mChannelClassifier.forget().downcast<nsIURIClassifierCallback>());
MOZ_DIAGNOSTIC_ASSERT(
!mWarningReporter,
"Warning reporter should have been released in ReleaseListeners()");
arrayToRelease.AppendElement(mWarningReporter.forget());
NS_DispatchToMainThread(
new ProxyReleaseRunnable(std::move(arrayToRelease)));
}
nsresult nsHttpChannel::Init(nsIURI* uri, uint32_t caps, nsProxyInfo* proxyInfo,
uint32_t proxyResolveFlags, nsIURI* proxyURI,
uint64_t channelId,
ExtContentPolicyType aContentPolicyType,
nsILoadInfo* aLoadInfo) {
nsresult rv =
HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags, proxyURI,
channelId, aContentPolicyType, aLoadInfo);
if (NS_FAILED(rv))
return rv;
LOG1((
"nsHttpChannel::Init [this=%p]\n",
this));
return rv;
}
nsresult nsHttpChannel::AddSecurityMessage(
const nsAString& aMessageTag,
const nsAString& aMessageCategory) {
if (mWarningReporter) {
return mWarningReporter->ReportSecurityMessage(aMessageTag,
aMessageCategory);
}
return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
}
NS_IMETHODIMP
nsHttpChannel::LogBlockedCORSRequest(
const nsAString& aMessage,
const nsACString& aCategory,
bool aIsWarning) {
if (mWarningReporter) {
return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory,
aIsWarning);
}
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsHttpChannel::LogMimeTypeMismatch(
const nsACString& aMessageName,
bool aWarning,
const nsAString& aURL,
const nsAString& aContentType) {
if (mWarningReporter) {
return mWarningReporter->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
aContentType);
}
return NS_ERROR_UNEXPECTED;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <private>
//-----------------------------------------------------------------------------
nsresult nsHttpChannel::PrepareToConnect() {
LOG((
"nsHttpChannel::PrepareToConnect [this=%p]\n",
this));
// notify "http-on-modify-request-before-cookies" observers
gHttpHandler->OnModifyRequestBeforeCookies(
this);
AddCookiesToRequest();
#if defined(XP_WIN) ||
defined(XP_MACOSX)
auto prefEnabledForCurrentContainer = [&]() {
uint32_t containerId = mLoadInfo->GetOriginAttributes().mUserContextId;
// Make sure that the default container ID is 0
static_assert(nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID == 0);
nsAutoCString prefName;
# ifdef XP_WIN
prefName = nsPrintfCString(
"network.http.windows-sso.container-enabled.%u",
containerId);
# endif
# ifdef XP_MACOSX
prefName = nsPrintfCString(
"network.http.microsoft-entra-sso.container-enabled.%u", containerId);
# endif
bool enabled =
false;
Preferences::GetBool(prefName.get(), &enabled);
LOG((
"Pref for %s is %d\n", prefName.get(), enabled));
return enabled;
};
#endif // defined(XP_WIN) || defined(XP_MACOSX)
#ifdef XP_WIN
// If Windows 10 SSO is enabled, we potentially add auth
// information to secure top level loads (DOCUMENTs) and iframes
// (SUBDOCUMENTs) that aren't anonymous or private browsing.
if (StaticPrefs::network_http_windows_sso_enabled() &&
mURI->SchemeIs(
"https") && !(mLoadFlags & LOAD_ANONYMOUS) &&
!mPrivateBrowsing) {
ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
if ((type == ExtContentPolicy::TYPE_DOCUMENT ||
type == ExtContentPolicy::TYPE_SUBDOCUMENT) &&
prefEnabledForCurrentContainer()) {
AddWindowsSSO(
this);
}
}
#endif
#ifdef XP_MACOSX
auto isUriMSAuthority = [&]() {
nsAutoCString endPoint;
nsresult rv = mURI->GetHost(endPoint);
if (!NS_SUCCEEDED(rv)) {
return false;
}
LOG((
"endPoint is %s\n", endPoint.get()));
return gHttpHandler->IsHostMSAuthority(endPoint);
};
// If macOS SSO is enabled, we potentially add auth
// information to secure top level loads (DOCUMENTs) and iframes
// (SUBDOCUMENTs) that aren't anonymous or private browsing.
if (StaticPrefs::network_http_microsoft_entra_sso_enabled() &&
mURI->SchemeIs(
"https") && !(mLoadFlags & LOAD_ANONYMOUS) &&
!mPrivateBrowsing) {
ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
if ((type == ExtContentPolicy::TYPE_DOCUMENT ||
type == ExtContentPolicy::TYPE_SUBDOCUMENT) &&
prefEnabledForCurrentContainer() && isUriMSAuthority()) {
nsMainThreadPtrHandle<nsHttpChannel> self(
new nsMainThreadPtrHolder<nsHttpChannel>(
"nsHttpChannel::PrepareToConnect::self",
this));
auto resultCallback = [self(self)]() {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = self->ContinuePrepareToConnect();
if (NS_FAILED(rv)) {
self->CloseCacheEntry(
false);
Unused << self->AsyncAbort(rv);
}
};
nsresult rv = AddMicrosoftEntraSSO(
this, std::move(resultCallback));
// Returns NS_OK if performRequests is called in MicrosoftEntraSSOUtils
// This temporarily stops the channel setup for the delegate.
if (NS_SUCCEEDED(rv)) {
return rv;
}
}
}
#endif
return ContinuePrepareToConnect();
}
nsresult nsHttpChannel::ContinuePrepareToConnect() {
// notify "http-on-modify-request" observers
CallOnModifyRequestObservers();
return CallOrWaitForResume(
[](
auto* self) {
return self->OnBeforeConnect(); });
}
void nsHttpChannel::HandleContinueCancellingByURLClassifier(
nsresult aErrorCode) {
MOZ_ASSERT(
UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
MOZ_ASSERT(!mCallOnResume,
"How did that happen?");
if (mSuspendCount) {
LOG(
(
"Waiting until resume HandleContinueCancellingByURLClassifier "
"[this=%p]\n",
this));
mCallOnResume = [aErrorCode](nsHttpChannel* self) {
self->HandleContinueCancellingByURLClassifier(aErrorCode);
return NS_OK;
};
return;
}
LOG((
"nsHttpChannel::HandleContinueCancellingByURLClassifier [this=%p]\n",
this));
ContinueCancellingByURLClassifier(aErrorCode);
}
void nsHttpChannel::SetPriorityHeader() {
nsAutoCString userSetPriority;
Unused << GetRequestHeader(
"Priority"_ns, userSetPriority);
if (!userSetPriority.IsEmpty()) {
// If the Priority header is set by the user, do not override it.
return;
}
uint8_t urgency =
nsHttpHandler::UrgencyFromCoSFlags(mClassOfService.Flags(), mPriority);
bool incremental = mClassOfService.Incremental();
nsPrintfCString value(
"%s", urgency != 3 ? nsPrintfCString(
"u=%d", urgency).get() :
"");
if (incremental) {
if (!value.IsEmpty()) {
value.Append(
", ");
}
value.Append(
"i");
}
if (!value.IsEmpty()) {
SetRequestHeader(
"Priority"_ns, value,
false);
}
}
nsresult nsHttpChannel::OnBeforeConnect() {
nsresult rv = NS_OK;
// Check if request was cancelled during suspend AFTER on-modify-request
if (mCanceled) {
return mStatus;
}
// Check to see if we should redirect this channel elsewhere by
// nsIHttpChannel.redirectTo API request
if (mAPIRedirectTo) {
return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
}
// Note that we are only setting the "Upgrade-Insecure-Requests" request
// header for *all* navigational requests instead of all requests as
// defined in the spec, see:
// https://www.w3.org/TR/upgrade-insecure-requests/#preference
ExtContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
if (type == ExtContentPolicy::TYPE_DOCUMENT ||
type == ExtContentPolicy::TYPE_SUBDOCUMENT) {
rv = SetRequestHeader(
"Upgrade-Insecure-Requests"_ns,
"1"_ns,
false);
NS_ENSURE_SUCCESS(rv, rv);
}
if (LoadAuthRedirectedChannel()) {
// This channel is a result of a redirect due to auth retry
// We have already checked for HSTS upgarde in the redirecting channel.
// We can safely skip those checks
return ContinueOnBeforeConnect(
false, rv);
}
SecFetch::AddSecFetchHeader(
this);
// Check to see if we should redirect this channel to the unstripped URI. To
// revert the query stripping if the loading channel is in the content
// blocking allow list.
if (ContentBlockingAllowList::Check(
this)) {
nsCOMPtr<nsIURI> unstrippedURI;
mLoadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI));
if (unstrippedURI) {
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectToUnstrippedURI);
}
}
nsCOMPtr<nsIPrincipal> resultPrincipal;
if (!mURI->SchemeIs(
"https")) {
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
this, getter_AddRefs(resultPrincipal));
}
// Check if we already know about the HSTS status of the host
nsISiteSecurityService* sss = gHttpHandler->GetSSService();
NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
bool isSecureURI;
OriginAttributes originAttributes;
if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(
this,
originAttributes)) {
return NS_ERROR_FAILURE;
}
rv = sss->IsSecureURI(mURI, originAttributes, &isSecureURI);
NS_ENSURE_SUCCESS(rv, rv);
// Save that on the loadInfo so it can later be consumed by
// SecurityInfo.sys.mjs
mLoadInfo->SetHstsStatus(isSecureURI);
RefPtr<mozilla::dom::BrowsingContext> bc;
mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
// If bypassing the cache and we're forced offline
// we can just return the error here.
if (bc && bc->Top()->GetForceOffline() &&
BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass())) {
return NS_ERROR_OFFLINE;
}
// At this point it is no longer possible to call
// HttpBaseChannel::UpgradeToSecure.
StoreUpgradableToSecure(
false);
bool shouldUpgrade = LoadUpgradeToSecure();
if (mURI->SchemeIs(
"http")) {
OriginAttributes originAttributes;
if (!StoragePrincipalHelper::GetOriginAttributesForHSTS(
this,
originAttributes)) {
return NS_ERROR_FAILURE;
}
if (!shouldUpgrade) {
// Make sure http channel is released on main thread.
// See bug 1539148 for details.
nsMainThreadPtrHandle<nsHttpChannel> self(
new nsMainThreadPtrHolder<nsHttpChannel>(
"nsHttpChannel::OnBeforeConnect::self",
this));
auto resultCallback = [self(self)](
bool aResult, nsresult aStatus) {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = self->MaybeUseHTTPSRRForUpgrade(aResult, aStatus);
if (NS_FAILED(rv)) {
self->CloseCacheEntry(
false);
Unused << self->AsyncAbort(rv);
}
};
bool willCallback =
false;
rv = NS_ShouldSecureUpgrade(
mURI, mLoadInfo, resultPrincipal, LoadAllowSTS(), originAttributes,
shouldUpgrade, std::move(resultCallback), willCallback);
// If the request gets upgraded because of the HTTPS-Only mode, but no
// event listener has been registered so far, we want to do that here.
uint32_t httpOnlyStatus = mLoadInfo->GetHttpsOnlyStatus();
if (httpOnlyStatus &
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED) {
RefPtr<nsHTTPSOnlyStreamListener> httpsOnlyListener =
new nsHTTPSOnlyStreamListener(mListener, mLoadInfo);
mListener = httpsOnlyListener;
httpOnlyStatus ^=
nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
httpOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_REGISTERED;
mLoadInfo->SetHttpsOnlyStatus(httpOnlyStatus);
}
LOG(
(
"nsHttpChannel::OnBeforeConnect "
"[this=%p willCallback=%d rv=%" PRIx32
"]\n",
this, willCallback,
static_cast<uint32_t>(rv)));
if (NS_FAILED(rv) || MOZ_UNLIKELY(willCallback)) {
return rv;
}
}
}
return MaybeUseHTTPSRRForUpgrade(shouldUpgrade, NS_OK);
}
// Returns true if the network connectivity checker indicated
// that HTTPS records can be resolved on this network - false otherwise.
// When TRR is enabled, we always return true, as resolving HTTPS
// records don't depend on the network.
static bool canUseHTTPSRRonNetwork(
bool& aTRREnabled) {
// Respect the pref.
if (StaticPrefs::network_dns_force_use_https_rr()) {
aTRREnabled =
true;
return true;
}
aTRREnabled =
false;
if (nsCOMPtr<nsIDNSService> dns = mozilla::components::DNS::Service()) {
nsIDNSService::ResolverMode mode;
// If the browser is currently using TRR/DoH, then it can
// definitely resolve HTTPS records.
if (NS_SUCCEEDED(dns->GetCurrentTrrMode(&mode))) {
if (mode == nsIDNSService::MODE_TRRFIRST) {
RefPtr<TRRService> trr = TRRService::Get();
if (trr && trr->IsConfirmed()) {
aTRREnabled =
true;
}
}
else if (mode == nsIDNSService::MODE_TRRONLY) {
aTRREnabled =
true;
}
if (aTRREnabled) {
return true;
}
}
}
if (RefPtr<NetworkConnectivityService> ncs =
NetworkConnectivityService::GetSingleton()) {
nsINetworkConnectivityService::ConnectivityState state;
if (NS_SUCCEEDED(ncs->GetDNS_HTTPS(&state)) &&
state == nsINetworkConnectivityService::NOT_AVAILABLE) {
return false;
}
}
return true;
}
nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(
bool aShouldUpgrade,
nsresult aStatus) {
if (NS_FAILED(aStatus)) {
return aStatus;
}
RefPtr<mozilla::dom::BrowsingContext> bc;
mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
bool forceOffline = bc && bc->Top()->GetForceOffline();
if (mURI->SchemeIs(
"https") || aShouldUpgrade || !LoadUseHTTPSSVC() ||
forceOffline) {
return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
}
auto shouldSkipUpgradeWithHTTPSRR = [&]() ->
bool {
if (mCaps & NS_HTTP_DISALLOW_HTTPS_RR) {
return true;
}
// Skip using HTTPS RR to upgrade when this is not a top-level load and the
// loading principal is http.
if ((mLoadInfo->GetExternalContentPolicyType() !=
ExtContentPolicy::TYPE_DOCUMENT) &&
(mLoadInfo->GetLoadingPrincipal() &&
mLoadInfo->GetLoadingPrincipal()->SchemeIs(
"http"))) {
return true;
}
// If the network connectivity checker indicates the network is
// blocking HTTPS requests, then we should skip them so we don't
// needlessly wait for a timeout.
bool trrEnabled =
false;
if (!canUseHTTPSRRonNetwork(trrEnabled)) {
return true;
}
// Don't block the channel when TRR is not used.
if (!trrEnabled) {
return true;
}
auto dnsStrategy = GetProxyDNSStrategy();
if (dnsStrategy != ProxyDNSStrategy::ORIGIN) {
return true;
}
nsAutoCString uriHost;
mURI->GetAsciiHost(uriHost);
return gHttpHandler->IsHostExcludedForHTTPSRR(uriHost);
};
if (shouldSkipUpgradeWithHTTPSRR()) {
StoreUseHTTPSSVC(
false);
// If the website does not want to use HTTPS RR, we should set
// NS_HTTP_DISALLOW_HTTPS_RR. This is for avoiding HTTPS RR being used by
// the transaction.
DisallowHTTPSRR(mCaps);
return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
}
if (mHTTPSSVCRecord.isSome()) {
LOG((
"nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] mHTTPSSVCRecord is some",
this));
StoreWaitHTTPSSVCRecord(
false);
bool hasHTTPSRR = (mHTTPSSVCRecord.ref() != nullptr);
return ContinueOnBeforeConnect(hasHTTPSRR, aStatus, hasHTTPSRR);
}
LOG((
"nsHttpChannel::MaybeUseHTTPSRRForUpgrade [%p] wait for HTTPS RR",
this));
OriginAttributes originAttributes;
StoragePrincipalHelper::GetOriginAttributesForHTTPSRR(
this, originAttributes);
RefPtr<nsDNSPrefetch> resolver =
new nsDNSPrefetch(mURI, originAttributes, nsIRequest::GetTRRMode());
nsWeakPtr weakPtrThis(
do_GetWeakReference(
static_cast<nsIHttpChannel*>(
this)));
nsresult rv = resolver->FetchHTTPSSVC(
mCaps & NS_HTTP_REFRESH_DNS, !LoadUseHTTPSSVC(),
[weakPtrThis](nsIDNSHTTPSSVCRecord* aRecord) {
nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis);
RefPtr<nsHttpChannel> httpChannelImpl = do_QueryObject(channel);
if (httpChannelImpl) {
httpChannelImpl->OnHTTPSRRAvailable(aRecord);
}
});
if (NS_FAILED(rv)) {
LOG((
" FetchHTTPSSVC failed with 0x%08" PRIx32,
static_cast<uint32_t>(rv)));
return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
}
StoreWaitHTTPSSVCRecord(
true);
return NS_OK;
}
nsresult nsHttpChannel::ContinueOnBeforeConnect(
bool aShouldUpgrade,
nsresult aStatus,
bool aUpgradeWithHTTPSRR) {
LOG(
(
"nsHttpChannel::ContinueOnBeforeConnect "
"[this=%p aShouldUpgrade=%d rv=%" PRIx32
"]\n",
this, aShouldUpgrade,
static_cast<uint32_t>(aStatus)));
MOZ_ASSERT(!LoadWaitHTTPSSVCRecord());
if (NS_FAILED(aStatus)) {
return aStatus;
}
if (aShouldUpgrade && !mURI->SchemeIs(
"https")) {
// only set HTTPS_RR to be responsbile for the upgrade in the loadinfo
// if it actually was responsible, otherwise the correct flag is
// already present in the loadinfo.
if (aUpgradeWithHTTPSRR) {
mLoadInfo->SetHttpsUpgradeTelemetry(nsILoadInfo::HTTPS_RR);
}
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
}
// ensure that we are using a valid hostname
if (!net_IsValidDNSHost(nsDependentCString(mConnectionInfo->Origin()))) {
return NS_ERROR_UNKNOWN_HOST;
}
if (mUpgradeProtocolCallback) {
// Websockets can run over HTTP/2, but other upgrades can't.
if (mUpgradeProtocol.EqualsLiteral(
"websocket") &&
StaticPrefs::network_http_http2_websockets()) {
// Need to tell the conn manager that we're ok with http/2 even with
// the allow keepalive bit not set. That bit needs to stay off,
// though, in case we end up having to fallback to http/1.1 (where
// we absolutely do want to disable keepalive).
mCaps |= NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE;
}
else {
mCaps |= NS_HTTP_DISALLOW_SPDY;
}
// Upgrades cannot use HTTP/3.
mCaps |= NS_HTTP_DISALLOW_HTTP3;
// Because NS_HTTP_STICKY_CONNECTION breaks HTTPS RR fallabck mecnahism, we
// can not use HTTPS RR for upgrade requests.
DisallowHTTPSRR(mCaps);
}
if (LoadIsTRRServiceChannel()) {
mCaps |= NS_HTTP_LARGE_KEEPALIVE;
DisallowHTTPSRR(mCaps);
}
if (mTransactionSticky) {
MOZ_ASSERT(LoadAuthRedirectedChannel());
// this means this is a redirected channel channel due to auth retry and a
// connection based auth scheme was used
// we have a reference to the old-transaction with sticky connection which
// we need to use
mCaps |= NS_HTTP_STICKY_CONNECTION;
}
mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
// Finalize ConnectionInfo flags before SpeculativeConnect
mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
mConnectionInfo->SetPrivate(mPrivateBrowsing);
mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
LoadBeConservative());
mConnectionInfo->SetTlsFlags(mTlsFlags);
mConnectionInfo->SetIsTrrServiceChannel(LoadIsTRRServiceChannel());
mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
mConnectionInfo->SetAnonymousAllowClientCert(
(mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) != 0);
if (mWebTransportSessionEventListener) {
nsTArray<RefPtr<nsIWebTransportHash>> aServerCertHashes;
nsresult rv;
nsCOMPtr<WebTransportConnectionSettings> wtconSettings =
do_QueryInterface(mWebTransportSessionEventListener, &rv);
NS_ENSURE_SUCCESS(rv, rv);
wtconSettings->GetServerCertificateHashes(aServerCertHashes);
gHttpHandler->ConnMgr()->StoreServerCertHashes(
mConnectionInfo, gHttpHandler->IsHttp2Excluded(mConnectionInfo),
!Http3Allowed(), std::move(aServerCertHashes));
}
if (ShouldIntercept()) {
return RedirectToInterceptedChannel();
}
// notify "http-on-before-connect" observers
gHttpHandler->OnBeforeConnect(
this);
return CallOrWaitForResume([](
auto* self) {
return self->Connect(); });
}
class MOZ_STACK_CLASS AddResponseHeadersToResponseHead final
:
public nsIHttpHeaderVisitor {
public:
explicit AddResponseHeadersToResponseHead(nsHttpResponseHead* aResponseHead)
: mResponseHead(aResponseHead) {}
NS_IMETHOD VisitHeader(
const nsACString& aHeader,
const nsACString& aValue) override {
nsAutoCString headerLine = aHeader +
": "_ns + aValue;
DebugOnly<nsresult> rv = mResponseHead->ParseHeaderLine(headerLine);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return NS_OK;
}
NS_IMETHOD QueryInterface(REFNSIID aIID,
void** aInstancePtr) override;
// Stub AddRef/Release since this is a stack class.
NS_IMETHOD_(MozExternalRefCountType) AddRef(
void) override {
return ++mRefCnt;
}
NS_IMETHOD_(MozExternalRefCountType) Release(
void) override {
return --mRefCnt;
}
virtual ~AddResponseHeadersToResponseHead() {
MOZ_DIAGNOSTIC_ASSERT(mRefCnt == 0);
}
private:
nsHttpResponseHead* mResponseHead;
nsrefcnt mRefCnt = 0;
};
NS_IMPL_QUERY_INTERFACE(AddResponseHeadersToResponseHead, nsIHttpHeaderVisitor)
nsresult nsHttpChannel::HandleOverrideResponse() {
// Start building a response with the data from mOverrideResponse.
mResponseHead = MakeUnique<nsHttpResponseHead>();
// Apply override response status code and status text.
uint32_t statusCode;
nsresult rv = mOverrideResponse->GetResponseStatus(&statusCode);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString statusText;
rv = mOverrideResponse->GetResponseStatusText(statusText);
NS_ENSURE_SUCCESS(rv, rv);
// Hardcoding protocol HTTP/1.1
nsPrintfCString line(
"HTTP/1.1 %u %s", statusCode, statusText.get());
rv = mResponseHead->ParseStatusLine(line);
NS_ENSURE_SUCCESS(rv, rv);
// Apply override response headers.
AddResponseHeadersToResponseHead visitor(mResponseHead.get());
rv = mOverrideResponse->VisitResponseHeaders(&visitor);
NS_ENSURE_SUCCESS(rv, rv);
if (WillRedirect(*mResponseHead)) {
// TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
// to avoid event dispatching latency.
LOG((
"Skipping read of overridden response redirect entity\n"));
return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
}
// Handle Set-Cookie headers as if the response was from networking.
CookieVisitor cookieVisitor(mResponseHead.get());
SetCookieHeaders(cookieVisitor.CookieHeaders());
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(
this, parentChannel);
if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
httpParent->SetCookieHeaders(cookieVisitor.CookieHeaders());
}
rv = ProcessSecurityHeaders();
if (NS_FAILED(rv)) {
NS_WARNING(
"ProcessSecurityHeaders failed, continuing load.");
}
if ((statusCode < 500) && (statusCode != 421)) {
ProcessAltService();
}
nsAutoCString body;
rv = mOverrideResponse->GetResponseBody(body);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIInputStream> stringStream;
rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), body);
NS_ENSURE_SUCCESS(rv, rv);
rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), stringStream, 0, 0,
true);
if (NS_FAILED(rv)) {
stringStream->Close();
return rv;
}
rv = mCachePump->AsyncRead(
this);
if (NS_FAILED(rv))
return rv;
return NS_OK;
}
nsresult nsHttpChannel::Connect() {
LOG((
"nsHttpChannel::Connect [this=%p]\n",
this));
if (mAPIRedirectTo) {
LOG((
"nsHttpChannel::Connect [transparent=%d]\n",
mAPIRedirectTo->second()));
nsresult rv = StartRedirectChannelToURI(
mAPIRedirectTo->first(),
mAPIRedirectTo->second() ? nsIChannelEventSink::REDIRECT_PERMANENT |
nsIChannelEventSink::REDIRECT_TRANSPARENT
: nsIChannelEventSink::REDIRECT_PERMANENT);
mAPIRedirectTo = Nothing();
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
return NS_ERROR_FAILURE;
}
// If mOverrideResponse is set, bypass the rest of the connection and reply
// immediately with a response built using the data from mOverrideResponse.
if (mOverrideResponse) {
return HandleOverrideResponse();
}
// Don't allow resuming when cache must be used
if (LoadResuming() && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
LOG((
"Resuming from cache is not supported yet"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// Step 8.18 of HTTP-network-or-cache fetch
// https://fetch.spec.whatwg.org/#http-network-or-cache-fetch
nsAutoCString rangeVal;
if (NS_SUCCEEDED(GetRequestHeader(
"Range"_ns, rangeVal))) {
SetRequestHeader(
"Accept-Encoding"_ns,
"identity"_ns,
true);
}
if (mRequestHead.IsPost() || mRequestHead.IsPatch()) {
// If the post id is already set then this is an attempt to replay
// a post/patch transaction via the cache. Otherwise, we need a unique
// post id for this transaction.
if (mPostID == 0) {
mPostID = gHttpHandler->GenerateUniqueID();
}
if (StaticPrefs::network_http_idempotencyKey_enabled() &&
!mRequestHead.HasHeader(nsHttp::Idempotency_Key)) {
// check if we need to add
// idempotency-key header
// See Bug 1830022 for more details
nsAutoCString key;
gHttpHandler->GenerateIdempotencyKeyForPost(mPostID, mLoadInfo, key);
MOZ_ALWAYS_SUCCEEDS(
mRequestHead.SetHeader(nsHttp::Idempotency_Key, key,
false));
}
}
#ifdef MOZ_WIDGET_ANDROID
bool val =
false;
if (nsIOService::ShouldAddAdditionalSearchHeaders(mURI, &val)) {
SetRequestHeader(
"X-Search-Subdivision"_ns, val ?
"1"_ns :
"0"_ns,
false);
}
#endif
bool isTrackingResource = IsThirdPartyTrackingResource();
LOG((
"nsHttpChannel %p tracking resource=%d, cos=%lu, inc=%d",
this,
isTrackingResource, mClassOfService.Flags(),
mClassOfService.Incremental()));
if (isTrackingResource) {
AddClassFlags(nsIClassOfService::Tail);
}
if (WaitingForTailUnblock()) {
MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
return NS_OK;
}
return ConnectOnTailUnblock();
}
nsresult nsHttpChannel::ConnectOnTailUnblock() {
nsresult rv;
LOG((
"nsHttpChannel::ConnectOnTailUnblock [this=%p]\n",
this));
// Consider opening a TCP connection right away.
SpeculativeConnect();
// open a cache entry for this channel...
rv = OpenCacheEntry(mURI->SchemeIs(
"https"));
// do not continue if asyncOpenCacheEntry is in progress
if (AwaitingCacheCallbacks()) {
LOG((
"nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
this));
MOZ_ASSERT(NS_SUCCEEDED(rv),
"Unexpected state");
if (mNetworkTriggered && mWaitingForProxy) {
// Someone has called TriggerNetwork(), meaning we are racing the
// network with the cache.
mWaitingForProxy =
false;
return ContinueConnect();
}
return NS_OK;
}
if (NS_FAILED(rv)) {
LOG((
"OpenCacheEntry failed [rv=%" PRIx32
"]\n",
static_cast<uint32_t>(rv)));
// if this channel is only allowed to pull from the cache, then
// we must fail if we were unable to open a cache entry.
if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// otherwise, let's just proceed without using the cache.
}
if (mRaceCacheWithNetwork && ((mCacheEntry && !CachedContentIsValid() &&
(mDidReval || LoadCachedContentIsPartial())) ||
mIgnoreCacheEntry)) {
// We won't send the conditional request because the unconditional
// request was already sent (see bug 1377223).
AccumulateCategorical(
Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
}
// When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
// returns, then we may not have started reading from the cache.
// If the content is valid, we should attempt to do so, as technically the
// cache has won the race.
if (mRaceCacheWithNetwork && CachedContentIsValid()) {
Unused << ReadFromCache();
}
return TriggerNetwork();
}
nsresult nsHttpChannel::ContinueConnect() {
// If we need to start a CORS preflight, do it now!
// Note that it is important to do this before the early returns below.
if (!LoadIsCorsPreflightDone() && LoadRequireCORSPreflight()) {
MOZ_ASSERT(!mPreflightChannel);
nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
this,
this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
return rv;
}
MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
"CORS preflight must have been finished by the time we "
"do the rest of ContinueConnect");
RefPtr<mozilla::dom::BrowsingContext> bc;
mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
// we may or may not have a cache entry at this point
if (mCacheEntry) {
// read straight from the cache if possible...
if (CachedContentIsValid()) {
// If we're forced offline, and set to bypass the cache, return offline.
if (bc && bc->Top()->GetForceOffline() &&
BYPASS_LOCAL_CACHE(mLoadFlags, LoadPreferCacheLoadOverBypass())) {
return NS_ERROR_OFFLINE;
}
nsRunnableMethod<nsHttpChannel>* event = nullptr;
nsresult rv;
if (!LoadCachedContentIsPartial()) {
rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
if (NS_FAILED(rv)) {
LOG((
" AsyncCall failed (%08x)",
static_cast<uint32_t>(rv)));
}
}
rv = ReadFromCache();
if (NS_FAILED(rv) && event) {
event->Revoke();
}
AccumulateCacheHitTelemetry(kCacheHit,
this);
mCacheDisposition = kCacheHit;
return rv;
}
if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
// the cache contains the requested resource, but it must be
// validated before we can reuse it. since we are not allowed
// to hit the net, there's nothing more to do. the document
// is effectively not in the cache.
LOG((
" !CachedContentIsValid() && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
}
else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
LOG((
" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
if (mLoadFlags & LOAD_NO_NETWORK_IO) {
LOG((
" mLoadFlags & LOAD_NO_NETWORK_IO"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// We're about to hit the network. Don't if we're forced offline.
if (bc && bc->Top()->GetForceOffline()) {
return NS_ERROR_OFFLINE;
}
// hit the net...
nsresult rv = DoConnect(mTransactionSticky);
mTransactionSticky = nullptr;
return rv;
}
nsresult nsHttpChannel::DoConnect(HttpTransactionShell* aTransWithStickyConn) {
LOG((
"nsHttpChannel::DoConnect [this=%p]\n",
this));
if (!mDNSBlockingPromise.IsEmpty()) {
LOG((
" waiting for DNS prefetch"));
// Transaction is passed only from auth retry for which we will definitely
// not block on DNS to alter the origin server name for IP; it has already
// been done.
MOZ_ASSERT(!aTransWithStickyConn);
MOZ_ASSERT(mDNSBlockingThenable);
nsCOMPtr<nsISerialEventTarget> target(do_GetMainThread());
RefPtr<nsHttpChannel> self(
this);
mDNSBlockingThenable->Then(
target, __func__,
[self](
const nsCOMPtr<nsIDNSRecord>& aRec) {
nsresult rv = self->DoConnectActual(nullptr);
if (NS_FAILED(rv)) {
self->CloseCacheEntry(
false);
Unused << self->AsyncAbort(rv);
}
},
[self](nsresult err) {
self->CloseCacheEntry(
false);
Unused << self->AsyncAbort(err);
});
// The connection will continue when the promise is resolved in
// OnLookupComplete.
return NS_OK;
}
return DoConnectActual(aTransWithStickyConn);
}
nsresult nsHttpChannel::DoConnectActual(
HttpTransactionShell* aTransWithStickyConn) {
LOG((
"nsHttpChannel::DoConnectActual [this=%p, aTransWithStickyConn=%p]\n",
this, aTransWithStickyConn));
nsresult rv = SetupChannelForTransaction();
if (NS_FAILED(rv)) {
return rv;
}
return DispatchTransaction(aTransWithStickyConn);
}
nsresult nsHttpChannel::DispatchTransaction(
HttpTransactionShell* aTransWithStickyConn) {
LOG((
"nsHttpChannel::DispatchTransaction [this=%p, aTransWithStickyConn=%p]",
this, aTransWithStickyConn));
nsresult rv = InitTransaction();
if (NS_FAILED(rv)) {
return rv;
}
if (aTransWithStickyConn) {
rv = gHttpHandler->InitiateTransactionWithStickyConn(
mTransaction, mPriority, aTransWithStickyConn);
}
else {
rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
}
if (NS_FAILED(rv)) {
return rv;
}
rv = mTransaction->AsyncRead(
this, getter_AddRefs(mTransactionPump));
if (NS_FAILED(rv)) {
return rv;
}
uint32_t suspendCount = mSuspendCount;
if (LoadAsyncResumePending()) {
LOG(
(
" Suspend()'ing transaction pump once because of async resume pending"
", sc=%u, pump=%p, this=%p",
suspendCount, mTransactionPump.get(),
this));
++suspendCount;
}
while (suspendCount--) {
mTransactionPump->Suspend();
}
return NS_OK;
}
void nsHttpChannel::SpeculativeConnect() {
// Before we take the latency hit of dealing with the cache, try and
// get the TCP (and SSL) handshakes going so they can overlap.
// don't speculate if we are offline, when doing http upgrade (i.e.
// websockets bootstrap), or if we can't do keep-alive (because then we
// couldn't reuse the speculative connection anyhow).
RefPtr<mozilla::dom::BrowsingContext> bc;
mLoadInfo->GetBrowsingContext(getter_AddRefs(bc));
if (gIOService->IsOffline() || mUpgradeProtocolCallback ||
!(mCaps & NS_HTTP_ALLOW_KEEPALIVE) ||
(bc && bc->Top()->GetForceOffline())) {
return;
}
// LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
// LOAD_FROM_CACHE is unlikely to hit network, so skip preconnects for it.
if (mLoadFlags &
(LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
return;
}
if (LoadAllowStaleCacheContent()) {
return;
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (!callbacks)
return;
bool httpsRRAllowed = !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR);
Unused << gHttpHandler->MaybeSpeculativeConnectWithHTTPSRR(
mConnectionInfo, callbacks,
mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_TRR_MODE_MASK |
NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6 |
NS_HTTP_DISALLOW_HTTP3 | NS_HTTP_REFRESH_DNS),
gHttpHandler->EchConfigEnabled() && httpsRRAllowed);
}
void nsHttpChannel::DoNotifyListenerCleanup() {
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
}
void nsHttpChannel::ReleaseListeners() {
HttpBaseChannel::ReleaseListeners();
mChannelClassifier = nullptr;
mWarningReporter = nullptr;
mEarlyHintObserver = nullptr;
mWebTransportSessionEventListener = nullptr;
for (StreamFilterRequest& request : mStreamFilterRequests) {
request.mPromise->Reject(
false, __func__);
}
mStreamFilterRequests.Clear();
}
void nsHttpChannel::DoAsyncAbort(nsresult aStatus) {
Unused << AsyncAbort(aStatus);
}
void nsHttpChannel::HandleAsyncRedirect() {
MOZ_ASSERT(!mCallOnResume,
"How did that happen?");
if (mSuspendCount) {
LOG((
"Waiting until resume to do async redirect [this=%p]\n",
this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleAsyncRedirect();
return NS_OK;
};
return;
}
nsresult rv = NS_OK;
LOG((
"nsHttpChannel::HandleAsyncRedirect [this=%p]\n",
this));
// since this event is handled asynchronously, it is possible that this
// channel could have been canceled, in which case there would be no point
// in processing the redirect.
if (NS_SUCCEEDED(mStatus)) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
rv = AsyncProcessRedirection(mResponseHead->Status());
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
// TODO: if !DoNotRender3xxBody(), render redirect body instead.
// But first we need to cache 3xx bodies (bug 748510)
rv = ContinueHandleAsyncRedirect(rv);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
else {
rv = ContinueHandleAsyncRedirect(mStatus);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
if (NS_FAILED(rv)) {
// If AsyncProcessRedirection fails, then we have to send out the
// OnStart/OnStop notifications.
LOG((
"ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32
"]\n",
static_cast<uint32_t>(rv)));
bool redirectsEnabled = !mLoadInfo->GetDontFollowRedirects();
if (redirectsEnabled) {
// TODO: stop failing original channel if redirect vetoed?
mStatus = rv;
DoNotifyListener();
// Blow away cache entry if we couldn't process the redirect
// for some reason (the cache entry might be corrupt).
if (mCacheEntry) {
mCacheEntry->AsyncDoom(nullptr);
}
}
else {
DoNotifyListener();
}
}
CloseCacheEntry(
true);
StoreIsPending(
false);
if (mLoadGroup) mLoadGroup->RemoveRequest(
this, nullptr, mStatus);
return NS_OK;
}
void nsHttpChannel::HandleAsyncNotModified() {
MOZ_ASSERT(!mCallOnResume,
"How did that happen?");
if (mSuspendCount) {
LOG((
"Waiting until resume to do async not-modified [this=%p]\n",
this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleAsyncNotModified();
return NS_OK;
};
return;
}
LOG((
"nsHttpChannel::HandleAsyncNotModified [this=%p]\n",
this));
DoNotifyListener();
CloseCacheEntry(
false);
StoreIsPending(
false);
if (mLoadGroup) mLoadGroup->RemoveRequest(
this, nullptr, mStatus);
}
nsresult nsHttpChannel::SetupChannelForTransaction() {
LOG((
"nsHttpChannel::SetupChannelForTransaction [this=%p, cos=%lu, inc=%d "
"prio=%d]\n",
this, mClassOfService.Flags(), mClassOfService.Incremental(), mPriority));
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
mozilla::MutexAutoLock lock(mRCWNLock);
if (StaticPrefs::network_http_priority_header_enabled()) {
SetPriorityHeader();
}
// If we're racing cache with network, conditional or byte range header
// could be added in OnCacheEntryCheck. We cannot send conditional request
// without having the entry, so we need to remove the headers here and
// ignore the cache entry in OnCacheEntryAvailable.
if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
if (mDidReval) {
LOG((
" Removing conditional request headers"));
UntieValidationRequest();
mDidReval =
false;
mIgnoreCacheEntry =
true;
}
if (LoadCachedContentIsPartial()) {
LOG((
" Removing byte range request headers"));
UntieByteRangeRequest();
StoreCachedContentIsPartial(
false);
mIgnoreCacheEntry =
true;
}
if (mIgnoreCacheEntry) {
mAvailableCachedAltDataType.Truncate();
StoreDeliveringAltData(
false);
mAltDataLength = -1;
mCacheInputStream.CloseAndRelease();
}
}
StoreUsedNetwork(1);
if (!LoadAllowSpdy()) {
mCaps |= NS_HTTP_DISALLOW_SPDY;
}
if (!LoadAllowHttp3()) {
mCaps |= NS_HTTP_DISALLOW_HTTP3;
}
if (LoadBeConservative()) {
mCaps |= NS_HTTP_BE_CONSERVATIVE;
}
if (mLoadFlags & LOAD_ANONYMOUS_ALLOW_CLIENT_CERT) {
mCaps |= NS_HTTP_LOAD_ANONYMOUS_CONNECT_ALLOW_CLIENT_CERT;
}
if (nsContentUtils::ShouldResistFingerprinting(
this,
RFPTarget::HttpUserAgent)) {
mCaps |= NS_HTTP_USE_RFP;
}
// Use the URI path if not proxying (transparent proxying such as proxy
// CONNECT does not count here). Also figure out what HTTP version to use.
nsAutoCString buf, path;
nsCString* requestURI;
// This is the normal e2e H1 path syntax "/index.html"
rv = mURI->GetPathQueryRef(path);
if (NS_FAILED(rv)) {
return rv;
}
// path may contain UTF-8 characters, so ensure that they're escaped.
if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
buf)) {
requestURI = &buf;
}
else {
requestURI = &path;
}
// trim off the #ref portion if any...
int32_t ref1 = requestURI->FindChar(
'#');
if (ref1 != kNotFound) {
requestURI->SetLength(ref1);
}
if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
mRequestHead.SetVersion(gHttpHandler->HttpVersion());
}
else {
mRequestHead.SetPath(*requestURI);
// RequestURI should be the absolute uri H1 proxy syntax
// "http://foo/index.html" so we will overwrite the relative version in
// requestURI
rv = mURI->GetUserPass(buf);
if (NS_FAILED(rv))
return rv;
if (!buf.IsEmpty() && ((strncmp(mSpec.get(),
"http:", 5) == 0) ||
strncmp(mSpec.get(),
"https:", 6) == 0)) {
nsCOMPtr<nsIURI> tempURI = nsIOService::CreateExposableURI(mURI);
rv = tempURI->GetAsciiSpec(path);
if (NS_FAILED(rv))
return rv;
requestURI = &path;
}
else {
requestURI = &mSpec;
}
// trim off the #ref portion if any...
int32_t ref2 = requestURI->FindChar(
'#');
if (ref2 != kNotFound) {
requestURI->SetLength(ref2);
}
mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
}
mRequestHead.SetRequestURI(*requestURI);
// set the request time for cache expiration calculations
mRequestTime = NowInSeconds();
StoreRequestTimeInitialized(
true);
// if doing a reload, force end-to-end
if (mLoadFlags & LOAD_BYPASS_CACHE) {
// We need to send 'Pragma:no-cache' to inhibit proxy caching even if
// no proxy is configured since we might be talking with a transparent
// proxy, i.e. one that operates at the network level. See bug #14772.
// But we should not touch Pragma if Cache-Control is already set
// (https://fetch.spec.whatwg.org/#ref-for-concept-request-cache-mode%E2%91%A3)
if (!mRequestHead.HasHeader(nsHttp::Pragma)) {
rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma,
"no-cache",
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
// If we're configured to speak HTTP/1.1 then also send 'Cache-control:
// no-cache'. But likewise don't touch Cache-Control if it's already set.
if (mRequestHead.Version() >= HttpVersion::v1_1 &&
!mRequestHead.HasHeader(nsHttp::Cache_Control)) {
rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control,
"no-cache",
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
else if (mLoadFlags & VALIDATE_ALWAYS) {
// We need to send 'Cache-Control: max-age=0' to force each cache along
// the path to the origin server to revalidate its own entry, if any,
// with the next cache or server. See bug #84847.
//
// If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
//
// But don't send the headers if they're already set:
// https://fetch.spec.whatwg.org/#ref-for-concept-request-cache-mode%E2%91%A2
if (mRequestHead.Version() >= HttpVersion::v1_1) {
if (!mRequestHead.HasHeader(nsHttp::Cache_Control)) {
rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control,
"max-age=0",
true);
}
}
else {
if (!mRequestHead.HasHeader(nsHttp::Pragma)) {
rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma,
"no-cache",
true);
}
}
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
if (LoadResuming()) {
char byteRange[32];
SprintfLiteral(byteRange,
"bytes=%" PRIu64
"-", mStartPos);
rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (!mEntityID.IsEmpty()) {
// Also, we want an error if this resource changed in the meantime
// Format of the entity id is: escaped_etag/size/lastmod
nsCString::const_iterator start, end, slash;
mEntityID.BeginReading(start);
mEntityID.EndReading(end);
mEntityID.BeginReading(slash);
if (FindCharInReadable(
'/', slash, end)) {
nsAutoCString ifMatch;
rv = mRequestHead.SetHeader(
nsHttp::If_Match,
NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
MOZ_ASSERT(NS_SUCCEEDED(rv));
++slash;
// Incrementing, so that searching for '/' won't find
// the same slash again
}
if (FindCharInReadable(
'/', slash, end)) {
rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
Substring(++slash, end));
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
}
// See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
if (mUpgradeProtocolCallback) {
rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol,
false);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mCaps |= NS_HTTP_STICKY_CONNECTION;
mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
}
if (mWebTransportSessionEventListener) {
mCaps |= NS_HTTP_STICKY_CONNECTION;
}
return NS_OK;
}
nsresult nsHttpChannel::InitTransaction() {
nsresult rv;
// create wrapper for this channel's notification callbacks
nsCOMPtr<nsIInterfaceRequestor> callbacks;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
// create the transaction object
if (nsIOService::UseSocketProcess()) {
if (NS_WARN_IF(!gIOService->SocketProcessReady())) {
return NS_ERROR_NOT_AVAILABLE;
}
RefPtr<SocketProcessParent> socketProcess =
SocketProcessParent::GetSingleton();
if (!socketProcess->CanSend()) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(
this, parentChannel);
RefPtr<DocumentLoadListener> documentChannelParent =
do_QueryObject(parentChannel);
// See HttpTransactionChild::CanSendODAToContentProcessDirectly() and
// nsHttpChannel::CallOnStartRequest() for the reason why we need to know if
// this is a document load. We only send ODA directly to child process for
// non document loads.
RefPtr<HttpTransactionParent> transParent =
new HttpTransactionParent(!!documentChannelParent);
LOG1((
"nsHttpChannel %p created HttpTransactionParent %p\n",
this,
transParent.get()));
// Since OnStopRequest could be sent to child process from socket process
// directly, we need to store these two values in HttpTransactionChild and
// forward to child process until HttpTransactionChild::OnStopRequest is
// called.
transParent->SetRedirectTimestamp(mRedirectStartTimeStamp,
mRedirectEndTimeStamp);
if (socketProcess) {
MOZ_ALWAYS_TRUE(
socketProcess->SendPHttpTransactionConstructor(transParent));
}
mTransaction = transParent;
}
else {
mTransaction =
new nsHttpTransaction();
LOG1((
"nsHttpChannel %p created nsHttpTransaction %p\n",
this,
mTransaction.get()));
}
// Save the mapping of channel id and the channel. We need this mapping for
// nsIHttpActivityObserver.
gHttpHandler->AddHttpChannel(mChannelId, ToSupports(
this));
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsIHttpPushListener),
getter_AddRefs(pushListener));
HttpTransactionShell::OnPushCallback pushCallback = nullptr;
if (pushListener) {
mCaps |= NS_HTTP_ONPUSH_LISTENER;
nsWeakPtr weakPtrThis(
do_GetWeakReference(
static_cast<nsIHttpChannel*>(
this)));
pushCallback = [weakPtrThis](uint32_t aPushedStreamId,
const nsACString& aUrl,
const nsACString& aRequestString,
HttpTransactionShell* aTransaction) {
if (nsCOMPtr<nsIHttpChannel> channel = do_QueryReferent(weakPtrThis)) {
return static_cast<nsHttpChannel*>(channel.get())
->OnPush(aPushedStreamId, aUrl, aRequestString, aTransaction);
}
return NS_ERROR_NOT_AVAILABLE;
};
}
EnsureBrowserId();
EnsureRequestContext();
HttpTrafficCategory category = CreateTrafficCategory();
std::function<
void(TransactionObserverResult&&)> observer;
if (mTransactionObserver) {
observer = [transactionObserver{std::move(mTransactionObserver)}](
TransactionObserverResult&& aResult) {
transactionObserver->Complete(aResult.versionOk(), aResult.authOk(),
aResult.closeReason());
};
}
mTransaction->SetIsForWebTransport(!!mWebTransportSessionEventListener);
rv = mTransaction->Init(
mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
LoadUploadStreamHasHeaders(), GetCurrentSerialEventTarget(), callbacks,
this, mBrowserId, category, mRequestContext, mClassOfService,
mInitialRwin, LoadResponseTimeoutEnabled(), mChannelId,
std::move(observer), std::move(pushCallback), mTransWithPushedStream,
mPushedStreamId);
if (NS_FAILED(rv)) {
mTransaction = nullptr;
return rv;
}
return rv;
}
HttpTrafficCategory nsHttpChannel::CreateTrafficCategory() {
MOZ_ASSERT(!mFirstPartyClassificationFlags ||
!mThirdPartyClassificationFlags);
if (!StaticPrefs::network_traffic_analyzer_enabled()) {
return HttpTrafficCategory::eInvalid;
}
HttpTrafficAnalyzer::ClassOfService cos;
{
if ((mClassOfService.Flags() & nsIClassOfService::Leader) &&
mLoadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_SCRIPT) {
cos = HttpTrafficAnalyzer::ClassOfService::eLeader;
}
else if (mLoadFlags & nsIRequest::LOAD_BACKGROUND) {
cos = HttpTrafficAnalyzer::ClassOfService::eBackground;
}
else {
cos = HttpTrafficAnalyzer::ClassOfService::eOther;
}
}
bool isThirdParty = AntiTrackingUtils::IsThirdPartyChannel(
this);
HttpTrafficAnalyzer::TrackingClassification tc;
{
uint32_t flags = isThirdParty ? mThirdPartyClassificationFlags
: mFirstPartyClassificationFlags;
using CF = nsIClassifiedChannel::ClassificationFlags;
using TC = HttpTrafficAnalyzer::TrackingClassification;
if (flags & CF::CLASSIFIED_TRACKING_CONTENT) {
tc = TC::eContent;
}
else if (flags & CF::CLASSIFIED_FINGERPRINTING_CONTENT) {
tc = TC::eFingerprinting;
}
else if (flags & CF::CLASSIFIED_ANY_BASIC_TRACKING) {
tc = TC::eBasic;
}
else {
tc = TC::eNone;
}
}
bool isSystemPrincipal =
mLoadInfo->GetLoadingPrincipal() &&
mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal();
return HttpTrafficAnalyzer::CreateTrafficCategory(
NS_UsePrivateBrowsing(
this), isSystemPrincipal, isThirdParty, cos, tc);
}
--> --------------------
--> maximum size reached
--> --------------------