Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
// 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/GleanMetrics.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 "nsAlgorithm.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 "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;
}
} // 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();
nsAutoCString query;
nsresult rv = mURI->GetQuery(query);
if ((type == ExtContentPolicy::TYPE_DOCUMENT ||
type == ExtContentPolicy::TYPE_SUBDOCUMENT) &&
NS_SUCCEEDED(rv) && !query.IsEmpty() &&
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);
}
};
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:
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.
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)) {
// 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.
if (nsAutoCString cookie;
NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
SetCookie(cookie);
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(this, parentChannel);
if (RefP