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 "nsISecurityReporter.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/AntiTrackingUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ContentBlocking.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.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 "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 "nsCRT.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/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
#ifdef MOZ_GECKO_PROFILER
# include "ProfilerMarkerPayload.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() ==
nsIContentPolicy::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->mHasAutoRedirectVetoNotifier) {
MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
mChannel = nullptr;
return;
}
mChannel->mHasAutoRedirectVetoNotifier = 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->mHasAutoRedirectVetoNotifier = 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),
mAuthRetryPending(false),
mCachedContentIsPartial(false),
mCacheOnlyMetadata(false),
mTransactionReplaced(false),
mProxyAuthPending(false),
mCustomAuthHeader(false),
mResuming(false),
mInitedCacheEntry(false),
mFallbackChannel(false),
mCustomConditionalRequest(false),
mFallingBack(false),
mWaitingForRedirectCallback(false),
mRequestTimeInitialized(false),
mCacheEntryIsReadOnly(false),
mCacheEntryIsWriteOnly(false),
mCacheEntriesToWaitFor(0),
mConcurrentCacheAccess(0),
mIsPartialRequest(0),
mHasAutoRedirectVetoNotifier(0),
mPinCacheContent(0),
mIsCorsPreflightDone(0),
mStronglyFramed(false),
mUsedNetwork(0),
mAuthConnectionRestartable(0),
mChannelClassifierCancellationPending(0),
mAsyncResumePending(0),
mHasBeenIsolatedChecked(0),
mIsIsolated(0),
mTopWindowOriginComputed(0),
mDataSentToChildProcess(0),
mUseHTTPSSVC(0),
mWaitHTTPSSVCRecord(0),
mHTTPSSVCTelemetryReported(0),
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,
nsContentPolicyType 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();
if (mCanceled) {
return mStatus;
}
if (mSuspendCount) {
// We abandon the connection here if there was one.
LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
MOZ_ASSERT(!mCallOnResume);
mCallOnResume = [](nsHttpChannel* self) {
self->HandleOnBeforeConnect();
return NS_OK;
};
return NS_OK;
}
return 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::HandleOnBeforeConnect() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
nsresult rv;
if (mSuspendCount) {
LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleOnBeforeConnect();
return NS_OK;
};
return;
}
LOG(("nsHttpChannel::HandleOnBeforeConnect [this=%p]\n", this));
rv = OnBeforeConnect();
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
Unused << AsyncAbort(rv);
}
}
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:
nsContentPolicyType type = mLoadInfo->GetExternalContentPolicyType();
if (type == nsIContentPolicy::TYPE_DOCUMENT ||
type == nsIContentPolicy::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.
mUpgradableToSecure = false;
bool shouldUpgrade = mUpgradeToSecure;
if (mURI->SchemeIs("http")) {
OriginAttributes originAttributes;
if (!StoragePrincipalHelper::GetOriginAttributesForNetworkState(
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());
// We need to wait for HTTPSSVC record if there is no AltSvc or HSTS
// upgrade for this request.
if (!aResult && NS_SUCCEEDED(aStatus) && self->mUseHTTPSSVC) {
LOG(("nsHttpChannel Wait for HTTPSSVC record [this=%p]\n",
self.get()));
self->mWaitHTTPSSVCRecord = true;
return;
}
nsresult rv = self->ContinueOnBeforeConnect(aResult, aStatus);
if (NS_FAILED(rv)) {
self->CloseCacheEntry(false);
Unused << self->AsyncAbort(rv);
}
};
bool willCallback = false;
rv = NS_ShouldSecureUpgrade(mURI, mLoadInfo, resultPrincipal,
mPrivateBrowsing, mAllowSTS, 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);
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 ContinueOnBeforeConnect(shouldUpgrade, NS_OK);
}
nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
nsresult aStatus) {
LOG(
("nsHttpChannel::ContinueOnBeforeConnect "
"[this=%p aShouldUpgrade=%d rv=%" PRIx32 "]\n",
this, aShouldUpgrade, static_cast<uint32_t>(aStatus)));
if (NS_FAILED(aStatus)) {
return aStatus;
}
if (aShouldUpgrade) {
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 (mIsTRRServiceChannel) {
mCaps |= NS_HTTP_LARGE_KEEPALIVE;
}
mCaps |= NS_HTTP_TRR_FLAGS_FROM_MODE(nsIRequest::GetTRRMode());
// Finalize ConnectionInfo flags before SpeculativeConnect
mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
mConnectionInfo->SetPrivate(mPrivateBrowsing);
mConnectionInfo->SetIsolated(IsIsolated());
mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
mBeConservative);
mConnectionInfo->SetTlsFlags(mTlsFlags);
mConnectionInfo->SetIsTrrServiceChannel(mIsTRRServiceChannel);
mConnectionInfo->SetTRRMode(nsIRequest::GetTRRMode());
mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
if (mHTTPSSVCRecord) {
MOZ_ASSERT(mURI->SchemeIs("https"));
LOG((" Using connection info with HTTPSSVC record"));
nsCOMPtr<nsIDNSHTTPSSVCRecord> rec;
mHTTPSSVCRecord.swap(rec);
bool http3Allowed = !mUpgradeProtocolCallback && !mProxyInfo &&
!(mCaps & NS_HTTP_BE_CONSERVATIVE) && !mBeConservative;
nsCOMPtr<nsISVCBRecord> record;
if (NS_SUCCEEDED(rec->GetServiceModeRecord(mCaps & NS_HTTP_DISALLOW_SPDY,
!http3Allowed,
getter_AddRefs(record)))) {
MOZ_ASSERT(record);
RefPtr<nsHttpConnectionInfo> newConnInfo =
mConnectionInfo->CloneAndAdoptHTTPSSVCRecord(record);
mConnectionInfo = std::move(newConnInfo);
}
}
// notify "http-on-before-connect" observers
gHttpHandler->OnBeforeConnect(this);
// Check if request was cancelled during http-on-before-connect.
if (mCanceled) {
return mStatus;
}
if (mSuspendCount) {
// We abandon the connection here if there was one.
LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
MOZ_ASSERT(!mCallOnResume);
mCallOnResume = [](nsHttpChannel* self) {
self->OnBeforeConnectContinue();
return NS_OK;
};
return NS_OK;
}
return Connect();
}
void nsHttpChannel::OnBeforeConnectContinue() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
nsresult rv;
if (mSuspendCount) {
LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
mCallOnResume = [](nsHttpChannel* self) {
self->OnBeforeConnectContinue();
return NS_OK;
};
return;
}
LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
rv = Connect();
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
Unused << AsyncAbort(rv);
}
}
nsresult nsHttpChannel::Connect() {
LOG(("nsHttpChannel::Connect [this=%p]\n", this));
// Don't allow resuming when cache must be used
if (mResuming && (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 (!mFallbackChannel && !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 || mCachedContentIsPartial)) ||
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 (!mIsCorsPreflightDone && mRequireCORSPreflight) {
MOZ_ASSERT(!mPreflightChannel);
nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
return rv;
}
MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
"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 (!mCachedContentIsPartial) {
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 (!mFallbackChannel && !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 (mAsyncResumePending) {
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 (mAllowStaleCacheContent) {
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));
}
void nsHttpChannel::DoNotifyListenerCleanup() {
// We don't need this info anymore
CleanRedirectCacheChainIfNecessary();
}
void nsHttpChannel::ReleaseListeners() {
HttpBaseChannel::ReleaseListeners();
mChannelClassifier = nullptr;
mWarningReporter = nullptr;
}
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);
mIsPending = 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);
mIsPending = 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) || !mFallingBack)) {
// 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), mFallingBack));
mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
DoNotifyListener();
}
mIsPending = 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 (mCachedContentIsPartial) {
LOG((" Removing byte range request headers"));
UntieByteRangeRequest();
mCachedContentIsPartial = false;
mIgnoreCacheEntry = true;
}
if (mIgnoreCacheEntry) {
mAvailableCachedAltDataType.Truncate();
mDeliveringAltData = false;
mAltDataLength = -1;
mCacheInputStream.CloseAndRelease();
}
}
mUsedNetwork = 1;
if (!mAllowSpdy) {
mCaps |= NS_HTTP_DISALLOW_SPDY;
}
if (mBeConservative) {
mCaps |= NS_HTTP_BE_CONSERVATIVE;
}
// 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();
mRequestTimeInitialized = 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) && !mCacheEntryIsWriteOnly) {
// 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 (mResuming) {
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 (mTimingEnabled) 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;
};
}
EnsureTopLevelOuterContentWindowId();
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,
mUploadStreamHasHeaders, GetCurrentEventTarget(), callbacks, this,
mTopLevelOuterContentWindowId, category, mRequestContext, mClassOfService,
mInitialRwin, mResponseTimeoutEnabled, 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() ==
nsIContentPolicy::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);
}
enum class Report { Error, Warning };
// Helper Function to report messages to the console when the loaded
// script had a wrong MIME type.
void ReportMimeTypeMismatch(nsHttpChannel* aChannel, const char* aMessageName,
nsIURI* aURI, const nsACString& aContentType,
Report report) {
NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 contentType(aContentType);
aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
report == Report::Warning, spec, contentType);
}
// Check and potentially enforce X-Content-Type-Options: nosniff
nsresult ProcessXCTO(nsHttpChannel* aChannel, nsIURI* aURI,
nsHttpResponseHead* aResponseHead,
nsILoadInfo* aLoadInfo) {
if (!aURI || !aResponseHead || !aLoadInfo) {
// if there is no uri, no response head or no loadInfo, then there is
// nothing to do
return NS_OK;
}
// 1) Query the XCTO header and check if 'nosniff' is the first value.
nsAutoCString contentTypeOptionsHeader;
if (!aResponseHead->GetContentTypeOptionsHeader(contentTypeOptionsHeader)) {
// if failed to get XCTO header, then there is nothing to do.
return NS_OK;
}
// let's compare the header (ignoring case)
// e.g. "NoSniFF" -> "nosniff"
// if it's not 'nosniff' then there is nothing to do here
if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
// since we are getting here, the XCTO header was sent;
// a non matching value most likely means a mistake happenend;
// e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
AutoTArray<nsString, 1> params;
CopyUTF8toUTF16(contentTypeOptionsHeader, *params.AppendElement());
RefPtr<Document> doc;
aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "XCTO"_ns, doc,
nsContentUtils::eSECURITY_PROPERTIES,
"XCTOHeaderValueMissing", params);
return NS_OK;
}
// 2) Query the content type from the channel
nsAutoCString contentType;
aResponseHead->ContentType(contentType);
// 3) Compare the expected MIME type with the actual type
if (aLoadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_STYLESHEET) {
if (contentType.EqualsLiteral(TEXT_CSS)) {
return NS_OK;
}
ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
if (aLoadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_SCRIPT) {
if (nsContentUtils::IsJavascriptMIMEType(
NS_ConvertUTF8toUTF16(contentType))) {
return NS_OK;
}
ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
auto policyType = aLoadInfo->GetExternalContentPolicyType();
if (policyType == nsIContentPolicy::TYPE_DOCUMENT ||
policyType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
// If the header XCTO nosniff is set for any browsing context, then
// we set the skipContentSniffing flag on the Loadinfo. Within
// GetMIMETypeFromContent we then bail early and do not do any sniffing.
aLoadInfo->SetSkipContentSniffing(true);
return NS_OK;
}
return NS_OK;
}
// Ensure that a load of type script has correct MIME type
nsresult EnsureMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI,
nsHttpResponseHead* aResponseHead,
nsILoadInfo* aLoadInfo) {
if (!aURI || !aResponseHead || !aLoadInfo) {
// if there is no uri, no response head or no loadInfo, then there is
// nothing to do
return NS_OK;
}
if (aLoadInfo->GetExternalContentPolicyType() !=
nsIContentPolicy::TYPE_SCRIPT) {
// if this is not a script load, then there is nothing to do
return NS_OK;
}
nsAutoCString contentType;
aResponseHead->ContentType(contentType);
NS_ConvertUTF8toUTF16 typeString(contentType);
if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
// script load has type script
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
return NS_OK;
}
switch (aLoadInfo->InternalContentPolicyType()) {
case nsIContentPolicy::TYPE_SCRIPT:
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
case nsIContentPolicy::TYPE_INTERNAL_MODULE:
case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT:
case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT:
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
break;
case nsIContentPolicy::TYPE_INTERNAL_WORKER:
case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
break;
case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
break;
case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
break;
case nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET:
case nsIContentPolicy::TYPE_INTERNAL_PAINTWORKLET:
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worklet_load);
break;
default:
MOZ_ASSERT_UNREACHABLE("unexpected script type");
break;
}
bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
bool isSameOrigin = false;
aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(aURI, isPrivateWin,
&isSameOrigin);
if (isSameOrigin) {
// same origin
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
} else {
bool cors = false;
nsAutoCString corsOrigin;
nsresult rv = aResponseHead->GetHeader(
nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin);
if (NS_SUCCEEDED(rv)) {
if (corsOrigin.Equals("*")) {
cors = true;
} else {
nsCOMPtr<nsIURI> corsOriginURI;
rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
if (NS_SUCCEEDED(rv)) {
bool isPrivateWin =
aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
bool isSameOrigin = false;
aLoadInfo->GetLoadingPrincipal()->IsSameOrigin(
corsOriginURI, isPrivateWin, &isSameOrigin);
if (isSameOrigin) {
cors = true;
}
}
}
}
if (cors) {
// cors origin
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
} else {
// cross origin
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
}
}
bool block = false;
if (StringBeginsWith(contentType, "image/"_ns)) {
// script load has type image
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
block = true;
} else if (StringBeginsWith(contentType, "audio/"_ns)) {
// script load has type audio
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
block = true;
} else if (StringBeginsWith(contentType, "video/"_ns)) {
// script load has type video
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
block = true;
} else if (StringBeginsWith(contentType, "text/csv"_ns)) {
// script load has type text/csv
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
block = true;
}
if (block) {
ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
contentType, Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
if (StringBeginsWith(contentType, "text/plain"_ns)) {
// script load has type text/plain
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
} else if (StringBeginsWith(contentType, "text/xml"_ns)) {
// script load has type text/xml
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
} else if (StringBeginsWith(contentType, "application/octet-stream"_ns)) {
// script load has type application/octet-stream
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
} else if (StringBeginsWith(contentType, "application/xml"_ns)) {
// script load has type application/xml
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
} else if (StringBeginsWith(contentType, "application/json"_ns)) {
// script load has type application/json
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
} else if (StringBeginsWith(contentType, "text/json"_ns)) {
// script load has type text/json
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
} else if (StringBeginsWith(contentType, "text/html"_ns)) {
// script load has type text/html
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
} else if (contentType.IsEmpty()) {
// script load has no type
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
} else {
// script load has unknown type
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
}
// We restrict importScripts() in worker code to JavaScript MIME types.
nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS) {
ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
aURI, contentType, Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
internalType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER) {
// Do not block the load if the feature is not enabled.
if (!StaticPrefs::security_block_Worker_with_wrong_mime()) {
return NS_OK;
}
ReportMimeTypeMismatch(aChannel, "BlockWorkerWithWrongMimeType", aURI,
contentType, Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
// ES6 modules require a strict MIME type check.
if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
contentType, Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
return NS_OK;
}
// Warn when a load of type script uses a wrong MIME type and
// wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
void WarnWrongMIMEOfScript(nsHttpChannel* aChannel, nsIURI* aURI,
nsHttpResponseHead* aResponseHead,
nsILoadInfo* aLoadInfo) {
if (!aURI || !aResponseHead || !aLoadInfo) {
// If there is no uri, no response head or no loadInfo, then there is
// nothing to do.
return;
}
if (aLoadInfo->GetExternalContentPolicyType() !=
nsIContentPolicy::TYPE_SCRIPT) {
// If this is not a script load, then there is nothing to do.
return;
}
bool succeeded;
MOZ_ALWAYS_SUCCEEDS(aChannel->GetRequestSucceeded(&succeeded));
if (!succeeded) {
// Do not warn for failed loads: HTTP error pages are usually in HTML.
return;
}
nsAutoCString contentType;
aResponseHead->ContentType(contentType);
NS_ConvertUTF8toUTF16 typeString(contentType);
if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
contentType, Report::Warning);
}
}
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() ==
nsIContentPolicy::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));