Source code

Revision control

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
* 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 "DocumentChannelParent.h"
#include "mozilla/MozPromiseInlines.h" // For MozPromise::FromDomPromise
#include "mozilla/ScopeExit.h"
#include "mozilla/Sprintf.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPService.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "nsHttp.h"
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.h"
#include "nsString.h"
#include "nsIApplicationCacheContainer.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "nsICacheEntry.h"
#include "nsICryptoHash.h"
#include "nsIEffectiveTLDService.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsINetworkInterceptController.h"
#include "nsINSSErrorsService.h"
#include "nsIStringBundle.h"
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsIProtocolProxyService2.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIURL.h"
#include "nsIStreamTransportService.h"
#include "prnetdb.h"
#include "nsEscape.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 "GeckoProfiler.h"
#include "nsIConsoleService.h"
#include "mozilla/AntiTrackingRedirectHeuristic.h"
#include "mozilla/AntiTrackingUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ContentBlocking.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Preferences.h"
#include "mozilla/Components.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_fission.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 "CacheObserver.h"
#include "mozilla/dom/PerformanceStorage.h"
#include "mozilla/Telemetry.h"
#include "AlternateServices.h"
#include "InterceptedChannel.h"
#include "nsIHttpPushListener.h"
#include "nsIX509Cert.h"
#include "ScopedNSSTypes.h"
#include "nsIDeprecationWarner.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 "ParentChannelListener.h"
#include "InterceptedHttpChannel.h"
#include "../../cache2/CacheFileUtils.h"
#include "nsIMultiplexInputStream.h"
#include "nsINetworkLinkService.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/dom/nsHTTPSOnlyStreamListener.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 "js/Conversions.h"
#include "mozilla/dom/SecFetch.h"
#include "mozilla/net/TRRService.h"
#ifdef MOZ_TASK_TRACER
# include "GeckoTaskTracer.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);
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;
}
bool IsInSubpathOfAppCacheManifest(nsIApplicationCache* cache,
nsACString const& uriSpec) {
MOZ_ASSERT(cache);
nsresult rv;
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
if (NS_FAILED(rv)) {
return false;
}
nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
if (NS_FAILED(rv)) {
return false;
}
nsAutoCString directory;
rv = url->GetDirectory(directory);
if (NS_FAILED(rv)) {
return false;
}
nsCOMPtr<nsIURI> manifestURI;
rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
if (NS_FAILED(rv)) {
return false;
}
nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
if (NS_FAILED(rv)) {
return false;
}
nsAutoCString manifestDirectory;
rv = manifestURL->GetDirectory(manifestDirectory);
if (NS_FAILED(rv)) {
return false;
}
return StringBeginsWith(directory, manifestDirectory);
}
} // 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 AutoRedirectVetoNotifier {
public:
explicit AutoRedirectVetoNotifier(nsHttpChannel* channel)
: mChannel(channel) {
if (mChannel->LoadHasAutoRedirectVetoNotifier()) {
MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
mChannel = nullptr;
return;
}
mChannel->StoreHasAutoRedirectVetoNotifier(true);
}
~AutoRedirectVetoNotifier() { ReportRedirectResult(false); }
void RedirectSucceeded() { ReportRedirectResult(true); }
private:
nsHttpChannel* mChannel;
void ReportRedirectResult(bool succeeded);
};
void AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) {
if (!mChannel) return;
mChannel->mRedirectChannel = nullptr;
if (succeeded) {
mChannel->RemoveAsNonTailRequest();
}
nsCOMPtr<nsIRedirectResultListener> vetoHook;
NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
getter_AddRefs(vetoHook));
nsHttpChannel* channel = mChannel;
mChannel = nullptr;
if (vetoHook) vetoHook->OnRedirectResult(succeeded);
// Drop after the notification
channel->StoreHasAutoRedirectVetoNotifier(false);
}
//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------
nsHttpChannel::nsHttpChannel()
: HttpAsyncAborter<nsHttpChannel>(this),
mCacheDisposition(kCacheUnresolved),
mLogicalOffset(0),
mPostID(0),
mRequestTime(0),
mOfflineCacheLastModifiedTime(0),
mSuspendTotalTime(0),
mRedirectType(0),
mCacheOpenWithPriority(false),
mCacheQueueSizeWhenOpen(0),
mCachedContentIsValid(false),
mIsAuthChannel(false),
mAuthRetryPending(false),
mPushedStreamId(0),
mLocalBlocklist(false),
mOnTailUnblock(nullptr),
mWarningReporter(nullptr),
mIsReadingFromCache(false),
mFirstResponseSource(RESPONSE_PENDING),
mRaceCacheWithNetwork(false),
mRaceDelay(0),
mIgnoreCacheEntry(false),
mRCWNLock("nsHttpChannel.mRCWNLock"),
mProxyConnectResponseCode(0),
mDidReval(false) {
LOG(("Creating nsHttpChannel [this=%p]\n", this));
mChannelCreationTime = PR_Now();
mChannelCreationTimestamp = TimeStamp::Now();
}
nsHttpChannel::~nsHttpChannel() {
LOG(("Destroying nsHttpChannel [this=%p]\n", this));
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(mApplicationCacheForWrite.forget());
arrayToRelease.AppendElement(mAuthProvider.forget());
arrayToRelease.AppendElement(mRedirectChannel.forget());
arrayToRelease.AppendElement(mPreflightChannel.forget());
arrayToRelease.AppendElement(mDNSPrefetch.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) {
nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags,
proxyURI, channelId, aContentPolicyType);
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) {
if (mWarningReporter) {
return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory);
}
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));
AddCookiesToRequest();
// 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);
}
nsresult nsHttpChannel::OnBeforeConnect() {
nsresult rv;
// 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 (mAPIRedirectToURI) {
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);
}
SecFetch::AddSecFetchHeader(this);
nsCOMPtr<nsIPrincipal> resultPrincipal;
if (!mURI->SchemeIs("https")) {
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
this, getter_AddRefs(resultPrincipal));
}
// 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,
mPrivateBrowsing, 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);
}
nsresult nsHttpChannel::MaybeUseHTTPSRRForUpgrade(bool aShouldUpgrade,
nsresult aStatus) {
if (NS_FAILED(aStatus)) {
return aStatus;
}
if (mURI->SchemeIs("https") || aShouldUpgrade || !LoadUseHTTPSSVC()) {
return ContinueOnBeforeConnect(aShouldUpgrade, aStatus);
}
auto shouldSkipUpgradeWithHTTPSRR = [&]() -> bool {
nsAutoCString uriHost;
mURI->GetAsciiHost(uriHost);
if (gHttpHandler->IsHostExcludedForHTTPSRR(uriHost)) {
return true;
}
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
mLoadInfo->TriggeringPrincipal();
// If the security context that triggered the load is not https, then it's
// not a downgrade scenario.
if (!triggeringPrincipal->SchemeIs("https")) {
return false;
}
nsAutoCString triggeringHost;
triggeringPrincipal->GetAsciiHost(triggeringHost);
// If the initial request's host is not the same, we should upgrade this
// request.
if (!triggeringHost.Equals(uriHost)) {
return false;
}
// Add the host to a excluded list because:
// 1. We don't need to do the same check again.
// 2. Other subresources in the same host will be also excluded.
gHttpHandler->ExcludeHTTPSRRHost(uriHost);
return true;
};
if (shouldSkipUpgradeWithHTTPSRR()) {
StoreUseHTTPSSVC(false);
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));
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) {
Telemetry::Accumulate(Telemetry::HTTPS_UPGRADE_WITH_HTTPS_RR,
aUpgradeWithHTTPSRR);
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
}
// ensure that we are using a valid hostname
if (!net_IsValidHostName(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") &&
gHttpHandler->IsH2WebsocketsEnabled()) {
// 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;
}
if (LoadIsTRRServiceChannel()) {
mCaps |= NS_HTTP_LARGE_KEEPALIVE;
mCaps |= NS_HTTP_DISALLOW_HTTPS_RR;
}
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);
// notify "http-on-before-connect" observers
gHttpHandler->OnBeforeConnect(this);
return CallOrWaitForResume(
[](auto* self) {
return self->Connect();
});
}
nsresult nsHttpChannel::Connect() {
LOG(("nsHttpChannel::Connect [this=%p]\n", this));
// 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;
}
if (ShouldIntercept()) {
return RedirectToInterceptedChannel();
}
bool isTrackingResource = IsThirdPartyTrackingResource();
LOG(("nsHttpChannel %p tracking resource=%d, cos=%u", this,
isTrackingResource, mClassOfService));
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) {
// If we have a fallback URI (and we're not already
// falling back), process the fallback asynchronously.
if (!LoadFallbackChannel() && !mFallbackKey.IsEmpty()) {
return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
}
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// otherwise, let's just proceed without using the cache.
}
if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
(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 && mCachedContentIsValid) {
Unused << ReadFromCache(true);
}
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");
// we may or may not have a cache entry at this point
if (mCacheEntry) {
// read straight from the cache if possible...
if (mCachedContentIsValid) {
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(true);
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((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
} else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
// If we have a fallback URI (and we're not already
// falling back), process the fallback asynchronously.
if (!LoadFallbackChannel() && !mFallbackKey.IsEmpty()) {
return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
}
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;
}
// hit the net...
return DoConnect();
}
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 = SetupTransaction();
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 on uses of the offline application cache,
// 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).
if (mApplicationCache || gIOService->IsOffline() ||
mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
return;
// LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
// LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
// so skip preconnects for them.
if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
return;
if (LoadAllowStaleCacheContent()) {
return;
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (!callbacks) return;
Unused << gHttpHandler->SpeculativeConnect(
mConnectionInfo, callbacks,
mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_TRR_MODE_MASK |
NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6 |
NS_HTTP_DISALLOW_HTTP3),
gHttpHandler->UseHTTPSRRForSpeculativeConnection());
}
void nsHttpChannel::DoNotifyListenerCleanup() {
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
}
void nsHttpChannel::ReleaseListeners() {
HttpBaseChannel::ReleaseListeners();
mChannelClassifier = nullptr;
mWarningReporter = 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);
}
void nsHttpChannel::HandleAsyncFallback() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
if (mSuspendCount) {
LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleAsyncFallback();
return NS_OK;
};
return;
}
nsresult rv = NS_OK;
LOG(("nsHttpChannel::HandleAsyncFallback [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 fallback.
if (!mCanceled) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
bool waitingForRedirectCallback;
rv = ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback) return;
PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
}
rv = ContinueHandleAsyncFallback(rv);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsresult nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) {
if (!mCanceled && (NS_FAILED(rv) || !LoadFallingBack())) {
// If ProcessFallback fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ProcessFallback failed [rv=%" PRIx32 ", %d]\n",
static_cast<uint32_t>(rv), LoadFallingBack()));
mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
DoNotifyListener();
}
StoreIsPending(false);
if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
return rv;
}
nsresult nsHttpChannel::SetupTransaction() {
LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n", this,
mClassOfService, mPriority));
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
mozilla::MutexAutoLock lock(mRCWNLock);
// 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;
}
// 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.
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'
if (mRequestHead.Version() >= HttpVersion::v1_1) {
rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
} else if ((mLoadFlags & VALIDATE_ALWAYS) && !LoadCacheEntryIsWriteOnly()) {
// 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'
if (mRequestHead.Version() >= HttpVersion::v1_1)
rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
else
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));
}
}
}
// 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()) {
MOZ_ASSERT(gIOService->SocketProcessReady(),
"Socket process should be ready.");
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);
SocketProcessParent* socketProcess = SocketProcessParent::GetSingleton();
if (socketProcess) {
Unused << 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));
// See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
mCaps |= NS_HTTP_CALL_CONTENT_SNIFFER;
}
if (LoadTimingEnabled()) mCaps |= NS_HTTP_TIMING_ENABLED;
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;
}
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;
};
}
EnsureTopBrowsingContextId();
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());
};
}
rv = mTransaction->Init(
mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
LoadUploadStreamHasHeaders(), GetCurrentEventTarget(), callbacks, this,
mTopBrowsingContextId, 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 & 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 =
nsContentUtils::IsThirdPartyWindowOrChannel(nullptr, this, mURI);
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);
}
void nsHttpChannel::SetCachedContentType() {
if (!mResponseHead) {
return;
}
nsAutoCString contentTypeStr;
mResponseHead->ContentType(contentTypeStr);
uint8_t contentType = nsICacheEntry::CONTENT_TYPE_OTHER;
if (nsContentUtils::IsJavascriptMIMEType(
NS_ConvertUTF8toUTF16(contentTypeStr))) {
contentType = nsICacheEntry::CONTENT_TYPE_JAVASCRIPT;
} else if (StringBeginsWith(contentTypeStr, "text/css"_ns) ||
(mLoadInfo->GetExternalContentPolicyType() ==
ExtContentPolicy::TYPE_STYLESHEET)) {
contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
} else if (StringBeginsWith(contentTypeStr, "application/wasm"_ns)) {
contentType = nsICacheEntry::CONTENT_TYPE_WASM;
} else if (StringBeginsWith(contentTypeStr, "image/"_ns)) {
contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
} else if (StringBeginsWith(contentTypeStr, "video/"_ns)) {
contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
} else if (StringBeginsWith(contentTypeStr, "audio/"_ns)) {
contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
}
mCacheEntry->SetContentType(contentType);
}
nsresult nsHttpChannel::CallOnStartRequest() {
LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
MOZ_RELEASE_ASSERT(!LoadRequireCORSPreflight() || LoadIsCorsPreflightDone(),
"CORS preflight must have been finished by the time we "
"call OnStartRequest");
if (LoadOnStartRequestCalled()) {
// This can only happen when a range request loading rest of the data
// after interrupted concurrent cache read asynchronously failed, e.g.
// the response range bytes are not as expected or this channel has
// been externally canceled.
//
// It's legal to bypass CallOnStartRequest for that case since we've
// already called OnStartRequest on our listener and also added all
// content converters before.
MOZ_ASSERT(LoadConcurrentCacheAccess());
LOG(("CallOnStartRequest already invoked before"));
return mStatus;
}
// Ensure mListener->OnStartRequest will be invoked before exiting
// this function.
auto onStartGuard = MakeScopeExit([&] {
LOG(
(" calling mListener->OnStartRequest by ScopeExit [this=%p, "
"listener=%p]\n",
this, mListener.get()));
MOZ_ASSERT(!LoadOnStartRequestCalled());
if (mListener) {
nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
StoreOnStartRequestCalled(true);
deleteProtector->OnStartRequest(this);
}
StoreOnStartRequestCalled(true);
});
nsresult rv = ValidateMIMEType();
// Since ODA and OnStopRequest could be sent from socket process directly, we
// need to update the channel status before calling mListener->OnStartRequest.
// This is the only way to let child process discard the already received ODA
// messages.
if (NS_FAILED(rv)) {
mStatus = rv;
return mStatus;
}
// EnsureOpaqueResponseIsAllowed and EnsureOpauqeResponseIsAllowedAfterSniff
// are the checks for Opaque Response Blocking to ensure that we block as many
// cross-origin responses with CORS headers as possible that are not either
// Javascript or media to avoid leaking their contents through side channels.
if (!EnsureOpaqueResponseIsAllowed()) {
// XXXtt: Return an error code or make the response body null.
// We silence the error result now because we only want to get how many
// response will get allowed or blocked by ORB.
}
// Allow consumers to override our content type
if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
// NOTE: We can have both a txn pump and a cache pump when the cache
// content is partial. In that case, we need to read from the cache,
// because that's the one that has the initial contents. If that fails
// then give the transaction pump a shot.
nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
bool typeSniffersCalled = false;
if (mCachePump) {
typeSniffersCalled =
NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
}
if (!typeSniffersCalled && mTransactionPump) {
RefPtr<nsInputStreamPump> pump = do_QueryObject(mTransactionPump);
if (pump) {
pump->PeekStream(CallTypeSniffers, thisChannel);
} else {
MOZ_ASSERT(nsIOService::UseSocketProcess());
RefPtr<HttpTransactionParent> trans = do_QueryObject(mTransactionPump);
MOZ_ASSERT(trans);
trans->SetSniffedTypeToChannel(CallTypeSniffers, thisChannel);
}
}
}
auto isAllowedOrErr = EnsureOpaqueResponseIsAllowedAfterSniff();
if (isAllowedOrErr.isErr() || !isAllowedOrErr.inspect()) {
// XXXtt: Return an error code or make the response body null.
// We silence the error result now because we only want to get how many
// response will get allowed or blocked by ORB.
}
// Note that the code below should be synced with the code in
// HttpTransactionChild::CanSendODAToContentProcessDirectly(). We MUST make
// sure HttpTransactionChild::CanSendODAToContentProcessDirectly() returns
// false when a stream converter is applied.
bool unknownDecoderStarted = false;
if (mResponseHead && !mResponseHead->HasContentType()) {
MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
if (!mContentTypeHint.IsEmpty())
mResponseHead->SetContentType(mContentTypeHint);
else if (mResponseHead->Version() == HttpVersion::v0_9 &&
mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
mResponseHead->SetContentType(nsLiteralCString(TEXT_PLAIN));
else {
// Uh-oh. We had better find out what type we are!
nsCOMPtr<nsIStreamConverterService> serv;
rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
// If we failed, we just fall through to the "normal" case
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIStreamListener> converter;
rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
nullptr, getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
mListener = converter;
unknownDecoderStarted = true;
}
}
}
}
// If the content is multipart/x-mixed-replace, we'll insert a MIME decoder
// in the pipeline to handle the content and pass it along to our
// original listener. nsUnknownDecoder doesn't support detecting this type,
// so we only need to insert this using the response header's mime type.
//
// We only do this for unwrapped document loads, since we might want to send
// parts to the external protocol handler without leaving the parent process.
bool mustRunStreamFilterInParent = false;
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(this, parentChannel);
RefPtr<DocumentLoadListener> docListener = do_QueryObject(parentChannel);
if (mResponseHead &&a