/* -*- 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 "nsHttp.h"
#include "nsHttpChannel.h"
#include "nsHttpChannelAuthProvider.h"
#include "nsHttpHandler.h"
#include "nsString.h"
#include "nsIApplicationCacheService.h"
#include "nsIApplicationCacheContainer.h"
#include "nsICacheStorageService.h"
#include "nsICacheStorage.h"
#include "nsICacheEntry.h"
#include "nsICaptivePortalService.h"
#include "nsICookieService.h"
#include "nsICryptoHash.h"
#include "nsINetworkInterceptController.h"
#include "nsINSSErrorsService.h"
#include "nsISecurityReporter.h"
#include "nsIStringBundle.h"
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsILoadGroupChild.h"
#include "nsIProtocolProxyService2.h"
#include "nsIURIClassifier.h"
#include "nsMimeTypes.h"
#include "nsNetCID.h"
#include "nsNetUtil.h"
#include "nsIURL.h"
#include "nsIURIMutator.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 "mozIThirdPartyUtil.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/AntiTrackingCommon.h"
#include "mozilla/Attributes.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 "nsISSLSocketControl.h"
#include "sslt.h"
#include "nsContentUtils.h"
#include "nsContentSecurityManager.h"
#include "nsIClassOfService.h"
#include "nsIPermissionManager.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 "nsICacheEntryDescriptor.h"
#include "nsICancelable.h"
#include "nsIHttpChannelAuthProvider.h"
#include "nsIHttpChannelInternal.h"
#include "nsIPrompt.h"
#include "nsInputStreamPump.h"
#include "nsIURIFixup.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 "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 "ParentChannelListener.h"
#include "InterceptedHttpChannel.h"
#include "nsIBufferedStreams.h"
#include "nsIFileStreams.h"
#include "nsIMIMEInputStream.h"
#include "nsIMultiplexInputStream.h"
#include "../../cache2/CacheFileUtils.h"
#include "../../cache2/CacheHashUtils.h"
#include "nsINetworkLinkService.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/ServiceWorkerUtils.h"
#include "mozilla/net/AsyncUrlChannelClassifier.h"
#include "mozilla/net/CookieSettings.h"
#include "mozilla/net/NeckoChannelParams.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "nsIWebNavigation.h"
#include "HttpTrafficAnalyzer.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "js/Conversions.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 {
static bool sRCWNEnabled = false;
static uint32_t sRCWNQueueSizeNormal = 50;
static uint32_t sRCWNQueueSizePriority = 10;
static uint32_t sRCWNSmallResourceSizeKB = 256;
static uint32_t sRCWNMinWaitMs = 0;
static uint32_t sRCWNMaxWaitMs = 500;
// 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) {
Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
}
// 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(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),
mCachedContentIsPartial(false),
mCacheOnlyMetadata(false),
mTransactionReplaced(false),
mAuthRetryPending(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),
mPushedStream(nullptr),
mLocalBlocklist(false),
mOnTailUnblock(nullptr),
mWarningReporter(nullptr),
mIsReadingFromCache(false),
mFirstResponseSource(RESPONSE_PENDING),
mRaceCacheWithNetwork(false),
mRaceDelay(0),
mIgnoreCacheEntry(false),
mRCWNLock("nsHttpChannel.mRCWNLock"),
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();
}
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(mRedirectURI.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:
// https://www.w3.org/TR/upgrade-insecure-requests/#preference
nsContentPolicyType type = mLoadInfo
? mLoadInfo->GetExternalContentPolicyType()
: nsIContentPolicy::TYPE_OTHER;
if (type == nsIContentPolicy::TYPE_DOCUMENT ||
type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
NS_LITERAL_CSTRING("1"), false);
NS_ENSURE_SUCCESS(rv, rv);
}
nsCOMPtr<nsIPrincipal> resultPrincipal;
if (!mURI->SchemeIs("https") && mLoadInfo) {
nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
this, getter_AddRefs(resultPrincipal));
}
OriginAttributes originAttributes;
if (!NS_GetOriginAttributes(this, originAttributes)) {
return NS_ERROR_FAILURE;
}
// At this point it is no longer possible to call
// HttpBaseChannel::UpgradeToSecure.
mUpgradableToSecure = false;
bool shouldUpgrade = mUpgradeToSecure;
if (mURI->SchemeIs("http")) {
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->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);
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;
}
}
if (mIsTRRServiceChannel) {
mCaps |= NS_HTTP_LARGE_KEEPALIVE | NS_HTTP_DISABLE_TRR;
}
if (mLoadFlags & LOAD_DISABLE_TRR) {
mCaps |= NS_HTTP_DISABLE_TRR;
}
// 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->SetTrrDisabled(mCaps & NS_HTTP_DISABLE_TRR);
mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
// 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);
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, 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 = mTransactionPump->AsyncRead(this, nullptr);
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_DISABLE_TRR |
NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6));
}
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 || !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<nsIURIFixup> urifixup = services::GetURIFixup();
if (NS_WARN_IF(!urifixup)) {
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIURI> tempURI;
nsresult rv = urifixup->CreateExposableURI(mURI, getter_AddRefs(tempURI));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
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
mTransaction = new nsHttpTransaction();
LOG1(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
mTransaction.get()));
mTransaction->SetTransactionObserver(mTransactionObserver);
mTransactionObserver = nullptr;
// See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
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;
}
if (mPushedStream) {
mTransaction->SetPushedStream(mPushedStream);
mPushedStream = nullptr;
}
nsCOMPtr<nsIHttpPushListener> pushListener;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsIHttpPushListener),
getter_AddRefs(pushListener));
if (pushListener) {
mCaps |= NS_HTTP_ONPUSH_LISTENER;
}
EnsureTopLevelOuterContentWindowId();
HttpTrafficCategory category = CreateTrafficCategory();
nsCOMPtr<nsIAsyncInputStream> responseStream;
rv = mTransaction->Init(
mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
mTopLevelOuterContentWindowId, category, getter_AddRefs(responseStream));
if (NS_FAILED(rv)) {
mTransaction = nullptr;
return rv;
}
mTransaction->SetClassOfService(mClassOfService);
if (EnsureRequestContext()) {
mTransaction->SetRequestContext(mRequestContext);
}
rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
responseStream);
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->LoadingPrincipal() &&
mLoadInfo->LoadingPrincipal()->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;
Unused << aResponseHead->GetHeader(nsHttp::X_Content_Type_Options,
contentTypeOptionsHeader);
if (contentTypeOptionsHeader.IsEmpty()) {
// if there is no XCTO header, then there is nothing to do.
return NS_OK;
}
// XCTO header might contain multiple values which are comma separated, so:
// a) let's skip all subsequent values
// e.g. " NoSniFF , foo " will be " NoSniFF "
int32_t idx = contentTypeOptionsHeader.Find(",");
if (idx > 0) {
contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
}
// b) let's trim all surrounding whitespace
// e.g. " NoSniFF " -> "NoSniFF"
nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
contentTypeOptionsHeader);
// c) 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, NS_LITERAL_CSTRING("XCTO"), 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) &&
gHttpHandler->IsDocumentNosniffEnabled()) {
// 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:
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;
default:
MOZ_ASSERT_UNREACHABLE("unexpected script type");
break;
}
nsCOMPtr<nsIURI> requestURI;
aLoadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(requestURI));
nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
nsresult rv = ssm->CheckSameOriginURI(requestURI, aURI, false, isPrivateWin);
if (NS_SUCCEEDED(rv)) {
// same origin
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
} else {
bool cors = false;
nsAutoCString corsOrigin;
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;
rv = ssm->CheckSameOriginURI(requestURI, corsOriginURI, false,
isPrivateWin);
if (NS_SUCCEEDED(rv)) {
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, NS_LITERAL_CSTRING("image/"))) {
// script load has type image
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
block = true;
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
// script load has type audio
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
block = true;
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
// script load has type video
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
block = true;
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
// script load has type text/csv
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
block = true;
}
if (block) {
// Do not block the load if the feature is not enabled.
if (!StaticPrefs::security_block_script_with_wrong_mime()) {
return NS_OK;
}
ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
contentType, Report::Error);
return NS_ERROR_CORRUPTED_CONTENT;
}
if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
// script load has type text/plain
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
// script load has type text/xml
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
} else if (StringBeginsWith(contentType,
NS_LITERAL_CSTRING("application/octet-stream"))) {
// script load has type application/octet-stream
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
} else if (StringBeginsWith(contentType,
NS_LITERAL_CSTRING("application/xml"))) {
// script load has type application/xml
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
} else if (StringBeginsWith(contentType,
NS_LITERAL_CSTRING("application/json"))) {
// script load has type application/json
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/json"))) {
// script load has type text/json
AccumulateCategorical(
Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
} else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
// 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) {
// Do not block the load if the feature is not enabled.
if (!StaticPrefs::security_block_importScripts_with_wrong_mime()) {
return NS_OK;
}
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;
}
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, NS_LITERAL_CSTRING("text/css")) ||
mLoadInfo->GetExternalContentPolicyType() ==
nsIContentPolicy::TYPE_STYLESHEET) {
contentType = nsICacheEntry::CONTENT_TYPE_STYLESHEET;
} else if (StringBeginsWith(contentTypeStr,
NS_LITERAL_CSTRING("application/wasm"))) {
contentType = nsICacheEntry::CONTENT_TYPE_WASM;
} else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("image/"))) {
contentType = nsICacheEntry::CONTENT_TYPE_IMAGE;
} else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("video/"))) {
contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
} else if (StringBeginsWith(contentTypeStr, NS_LITERAL_CSTRING("audio/"))) {
contentType = nsICacheEntry::CONTENT_TYPE_MEDIA;
}
mCacheEntry->SetContentType(contentType);
}
void nsHttpChannel::StoreSiteAccessToCacheEntry() {
nsresult rv;
nsCOMPtr<nsIURI> topWindowURI;
rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
if (NS_FAILED(rv)) {
return;
}
nsCOMPtr<nsIEffectiveTLDService> eTLDService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
return;
}
nsAutoCString baseDomain;
rv = eTLDService->GetBaseDomain(topWindowURI, 0, baseDomain);
if (NS_FAILED(rv)) {
return;
}
RefPtr<CacheHash> hash = new CacheHash();
hash->Update(baseDomain.get(), baseDomain.Length());
Unused << mCacheEntry->AddBaseDomainAccess(hash->GetHash());
}
nsresult nsHttpChannel::CallOnStartRequest() {
LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
"CORS preflight must have been finished by the time we "
"call OnStartRequest");
if (mOnStartRequestCalled) {
// 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(mConcurrentCacheAccess);
LOG(("CallOnStartRequest already invoked before"));
return mStatus;
}
ReportContentTypeTelemetryForCrossOriginStylesheets();
mTracingEnabled = false;
// 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(!mOnStartRequestCalled);
if (mListener) {
nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
mOnStartRequestCalled = true;
deleteProtector->OnStartRequest(this);
}
mOnStartRequestCalled = true;
});
nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);
NS_ENSURE_SUCCESS(rv, rv);
rv = ProcessXCTO(this, mURI, mResponseHead, mLoadInfo);
NS_ENSURE_SUCCESS(rv, rv);
WarnWrongMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);
// 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) {
mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
}
}
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(NS_LITERAL_CSTRING(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 (mResponseHead && !mResponseHead->HasContentCharset())
mResponseHead->SetContentCharset(mContentCharsetHint);
if (mCacheEntry && mCacheEntryIsWriteOnly) {
SetCachedContentType();
}
if (mCacheEntry) {
StoreSiteAccessToCacheEntry();
}
LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
mListener.get()));
// About to call OnStartRequest, dismiss the guard object.
onStartGuard.release();
if (mListener) {
MOZ_ASSERT(!mOnStartRequestCalled,
"We should not call OsStartRequest twice");
nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
mOnStartRequestCalled = true;
rv = deleteProtector->OnStartRequest(this);
if (NS_FAILED(rv)) return rv;
} else {
NS_WARNING("OnStartRequest skipped because of null listener");
mOnStartRequestCalled = true;
}
// Install stream converter if required.
// If we use unknownDecoder, stream converters will be installed later (in
// nsUnknownDecoder) after OnStartRequest is called for the real listener.
if (!unknownDecoderStarted) {
nsCOMPtr<nsIStreamListener> listener;
rv =
DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
if (NS_FAILED(rv)) {
return rv;
}
if (listener) {
mListener = listener;
mCompressListener = listener;
}
}
// if this channel is for a download, close off access to the cache.
if (mCacheEntry && mChannelIsForDownload) {
mCacheEntry->AsyncDoom(nullptr);
// We must keep the cache entry in case of partial request.
// Concurrent access is the same, we need the entry in
// OnStopRequest.
// We also need the cache entry when racing cache with network to find
// out what is the source of the data.
if (!mCachedContentIsPartial && !mConcurrentCacheAccess &&
!(mRaceCacheWithNetwork &&
mFirstResponseSource == RESPONSE_FROM_CACHE)) {
CloseCacheEntry(false);
}
}
if (!mCanceled) {
// create offline cache entry if offline caching was requested
if (ShouldUpdateOfflineCacheEntry()) {
LOG(("writing to the offline cache"));
rv = InitOfflineCacheEntry();
if (NS_FAILED(rv)) return rv;
// InitOfflineCacheEntry may have closed mOfflineCacheEntry
if (mOfflineCacheEntry) {
rv = InstallOfflineCacheListener();
if (NS_FAILED(rv)) return rv;
}
} else if (mApplicationCacheForWrite) {
LOG(("offline cache is up to date, not updating"));
CloseOfflineCacheEntry();
}
}
return NS_OK;
}
nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
// Failure to set up a proxy tunnel via CONNECT means one of the following:
// 1) Proxy wants authorization, or forbids.
// 2) DNS at proxy couldn't resolve target URL.
// 3) Proxy connection to target failed or timed out.
// 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
//
// Our current architecture would parse the proxy's response content with
// the permission of the target URL. Given #4, we must avoid rendering the
// body of the reply, and instead give the user a (hopefully helpful)
// boilerplate error page, based on just the HTTP status of the reply.
MOZ_ASSERT(mConnectionInfo->UsingConnect(),
"proxy connect failed but not using CONNECT?");
nsresult rv;
switch (httpStatus) {
case 300:
case 301:
case 302:
case 303:
case 307:
case 308:
// Bad redirect: not top-level, or it's a POST, bad/missing Location,
// or ProcessRedirect() failed for some other reason. Legal
// redirects that fail because site not available, etc., are handled
// elsewhere, in the regular codepath.
rv = NS_ERROR_CONNECTION_REFUSED;
break;
case 403: // HTTP/1.1: "Forbidden"
case 501: // HTTP/1.1: "Not Implemented"
// user sees boilerplate Mozilla "Proxy Refused Connection" page.
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
break;
case 407: // ProcessAuthentication() failed (e.g. no header)
rv = NS_ERROR_PROXY_AUTHENTICATION_FAILED;
break;
case 429:
rv = NS_ERROR_TOO_MANY_REQUESTS;
break;
// Squid sends 404 if DNS fails (regular 404 from target is tunneled)
case 404: // HTTP/1.1: "Not Found"
// RFC 2616: "some deployed proxies are known to return 400 or
// 500 when DNS lookups time out." (Squid uses 500 if it runs
// out of sockets: so we have a conflict here).
case 400: // HTTP/1.1 "Bad Request"
case 500: // HTTP/1.1: "Internal Server Error"
/* User sees: "Address Not Found: Firefox can't find the server at
* www.foo.com."
*/
rv = NS_ERROR_UNKNOWN_HOST;
break;
case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
rv = NS_ERROR_PROXY_BAD_GATEWAY;
break;
case 503: // HTTP/1.1: "Service Unavailable"
// Squid returns 503 if target request fails for anything but DNS.
/* User sees: "Failed to Connect:
* Firefox can't establish a connection to the server at
* www.foo.com. Though the site seems valid, the browser
* was unable to establish a connection."
*/
rv = NS_ERROR_CONNECTION_REFUSED;
break;
// RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
// do here: picking target timeout, as DNS covered by 400/404/500
case 504: // HTTP/1.1: "Gateway Timeout"
// user sees: "Network Timeout: The server at www.foo.com
// is taking too long to respond."
rv = NS_ERROR_PROXY_GATEWAY_TIMEOUT;
break;
// Confused proxy server or malicious response
default:
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
break;
}
LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
httpStatus));
// Make sure the connection is thrown away as it can be in a bad state
// and the proxy may just hang on the next request.
MOZ_ASSERT(mTransaction);
mTransaction->DontReuseConnection();
Cancel(rv);
{
nsresult rv = CallOnStartRequest();
if (NS_FAILED(rv)) {
LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
httpStatus, static_cast<uint32_t>(rv)));
}
}
return rv;
}
static void GetSTSConsoleErrorTag(uint32_t failureResult,
nsAString& consoleErrorTag) {
switch (failureResult) {
case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
break;
case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
break;
case nsISiteSecurityService::ERROR_NO_MAX_AGE:
consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
break;
case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
break;
case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
break;
case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
break;
case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
break;
case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
break;
default:
consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
break;
}
}
static void GetPKPConsoleErrorTag(uint32_t failureResult,
nsAString& consoleErrorTag) {
switch (failureResult) {
case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
break;
case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
break;
case nsISiteSecurityService::ERROR_NO_MAX_AGE:
consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
break;
case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
break;
case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
break;
case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
break;
case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
break;
case nsISiteSecurityService::ERROR_INVALID_PIN:
consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
break;
case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
break;
case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
break;
case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
break;
case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
break;
case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
break;
default:
consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
break;
}
}
/**
* Process a single security header. Only two types are supported: HSTS and
* HPKP.
*/
nsresult nsHttpChannel::ProcessSingleSecurityHeader(
uint32_t aType, nsITransportSecurityInfo* aSecInfo, uint32_t aFlags) {
nsHttpAtom atom;
switch (aType) {
case nsISiteSecurityService::HEADER_HSTS:
atom = nsHttp::ResolveAtom("Strict-Transport-Security");
break;
case nsISiteSecurityService::HEADER_HPKP:
atom = nsHttp::ResolveAtom("Public-Key-Pins");
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid security header type");
return NS_ERROR_FAILURE;
}
nsAutoCString securityHeader;
nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
if (NS_SUCCEEDED(rv)) {
nsISiteSecurityService* sss = gHttpHandler->GetSSService();
NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
// Process header will now discard the headers itself if the channel
// wasn't secure (whereas before it had to be checked manually)
OriginAttributes originAttributes;
NS_GetOriginAttributes(this, originAttributes);
uint32_t failureResult;
uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
rv = sss->ProcessHeader(aType, mURI, securityHeader, aSecInfo, aFlags,
headerSource, originAttributes, nullptr, nullptr,
&failureResult);
if (NS_FAILED(rv)) {
nsAutoString consoleErrorCategory;
nsAutoString consoleErrorTag;
switch (aType) {
case nsISiteSecurityService::HEADER_HSTS:
GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
break;
case nsISiteSecurityService::HEADER_HPKP:
GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
break;
default:
return NS_ERROR_FAILURE;
}
Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
atom.get()));
}
} else {
if (rv != NS_ERROR_NOT_AVAILABLE) {
// All other errors are fatal
NS_ENSURE_SUCCESS(rv, rv);
}
LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
}
return NS_OK;
}
/**
* Decide whether or not to remember Strict-Transport-Security, and whether
* or not to enforce channel integrity.
*
* @return NS_ERROR_FAILURE if there's security information missing even though
* it's an HTTPS connection.
*/
nsresult nsHttpChannel::ProcessSecurityHeaders() {
// If this channel is not loading securely, STS or PKP doesn't do anything.
// In the case of HSTS, the upgrade to HTTPS takes place earlier in the
// channel load process.
if (!mURI->SchemeIs("https")) {
return NS_OK;
}
nsAutoCString asciiHost;
nsresult rv = mURI->GetAsciiHost(asciiHost);
NS_ENSURE_SUCCESS(rv, NS_OK);
// If the channel is not a hostname, but rather an IP, do not process STS
// or PKP headers
PRNetAddr hostAddr;
if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
return NS_OK;
// mSecurityInfo may not always be present, and if it's not then it is okay
// to just disregard any security headers since we know nothing about the
// security of the connection.
NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
uint32_t flags =
NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
// Get the TransportSecurityInfo
nsCOMPtr<nsITransportSecurityInfo> transSecInfo =
do_QueryInterface(mSecurityInfo);
NS_ENSURE_TRUE(transSecInfo, NS_ERROR_FAILURE);
rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
transSecInfo, flags);
NS_ENSURE_SUCCESS(rv, rv);
rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
transSecInfo, flags);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
/**
* Decide whether or not to send a security report and, if so, give the
* SecurityReporter the information required to send such a report.
*/
void nsHttpChannel::ProcessSecurityReport(nsresult status) {
uint32_t errorClass;
nsCOMPtr<nsINSSErrorsService> errSvc =
do_GetService("@mozilla.org/nss_errors_service;1");
// getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
// not in the set of errors covered by the NSS errors service.
nsresult rv = errSvc->GetErrorClass(status, &errorClass);
if (!NS_SUCCEEDED(rv)) {
return;
}
// if the content was not loaded succesfully and we have security info,
// send a TLS error report - we must do this early as other parts of
// OnStopRequest can return early
bool reportingEnabled =
Preferences::GetBool("security.ssl.errorReporting.enabled");
bool reportingAutomatic =
Preferences::GetBool("security.ssl.errorReporting.automatic");
if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
return;
}
nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(mSecurityInfo);
nsCOMPtr<nsISecurityReporter> errorReporter =
do_GetService("@mozilla.org/securityreporter;1");
if (!secInfo || !mURI) {
return;
}
nsAutoCString hostStr;
int32_t port;
rv = mURI->GetHost(hostStr);
if (!NS_SUCCEEDED(rv)) {
return;
}
rv = mURI->GetPort(&port);
if (NS_SUCCEEDED(rv)) {
errorReporter->ReportTLSError(secInfo, hostStr, port);
}
}
bool nsHttpChannel::IsHTTPS() { return mURI->SchemeIs("https"); }
void nsHttpChannel::ProcessSSLInformation() {
// If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
// can be whitelisted for TLS False Start in future sessions. We could
// do the same for DH but its rarity doesn't justify the lookup.
if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
mPrivateBrowsing)
return;
nsCOMPtr<nsITransportSecurityInfo> securityInfo =
do_QueryInterface(mSecurityInfo);
if (!securityInfo) return;
uint32_t state;
if (securityInfo && NS_SUCCEEDED(securityInfo->GetSecurityState(&state)) &&
(state & nsIWebProgressListener::STATE_IS_BROKEN)) {
// Send weak crypto warnings to the web console
if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
nsString consoleErrorTag = NS_LITERAL_STRING("WeakCipherSuiteWarning");
nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
}
}
// Send (SHA-1) signature algorithm errors to the web console
nsCOMPtr<nsIX509Cert> cert;
securityInfo->GetServerCert(getter_AddRefs(cert));
if (cert) {
UniqueCERTCertificate nssCert(cert->GetCert());
if (nssCert) {
SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag,
this));
// Check to see if the signature is sha-1 based.
// Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
// from http://tools.ietf.org/html/rfc2437#section-8 since I
// can't see reference to it outside this spec
if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
nsString consoleErrorTag = NS_LITERAL_STRING("SHA1Sig");
nsString consoleErrorMessage = NS_LITERAL_STRING("SHA-1 Signature");
Unused << AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
}
}
}
uint16_t tlsVersion;
nsresult rv = securityInfo->GetProtocolVersion(&tlsVersion);
if (NS_SUCCEEDED(rv) &&
tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_2 &&
tlsVersion != nsITransportSecurityInfo::TLS_VERSION_1_3) {
nsString consoleErrorTag = NS_LITERAL_STRING("DeprecatedTLSVersion");
nsString consoleErrorCategory = NS_LITERAL_STRING("TLS");
Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
}
}
void nsHttpChannel::ProcessAltService() {
// e.g. Alt-Svc: h2=":443"; ma=60
// e.g. Alt-Svc: h2="otherhost:443"
// Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
// alternative = protocol-id "=" alt-authority
// protocol-id = token ; percent-encoded ALPN protocol identifier
// alt-authority = quoted-string ; containing [ uri-host ] ":" port
if (!mAllowAltSvc) { // per channel opt out
return;
}
if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
return;
}
nsAutoCString scheme;
mURI->GetScheme(scheme);
bool isHttp = scheme.EqualsLiteral("http");
if (!isHttp && !scheme.EqualsLiteral("https")) {
return;
}
nsAutoCString altSvc;
Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
if (altSvc.IsEmpty()) {
return;
}
if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
return;
}
nsAutoCString originHost;
int32_t originPort = 80;
mURI->GetPort(&originPort);
if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
return;
}
nsCOMPtr<nsIInterfaceRequestor> callbacks;
nsCOMPtr<nsProxyInfo> proxyInfo;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (mProxyInfo) {
proxyInfo = do_QueryInterface(mProxyInfo);
}
OriginAttributes originAttributes;
NS_GetOriginAttributes(this, originAttributes);
AltSvcMapping::ProcessHeader(
altSvc, scheme, originHost, originPort, mUsername, GetTopWindowOrigin(),
mPrivateBrowsing, IsIsolated(), callbacks, proxyInfo,
mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
}
nsresult nsHttpChannel::ProcessResponse() {
uint32_t httpStatus = mResponseHead->Status();
LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
httpStatus));
// Gather data on whether the transaction and page (if this is
// the initial page load) is being loaded with SSL.
Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
mConnectionInfo->EndToEndSSL());
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
mConnectionInfo->EndToEndSSL());
}
if (Telemetry::CanRecordPrereleaseData()) {
// how often do we see something like Alt-Svc: "443:quic,p=1"
nsAutoCString alt_service;
Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
bool saw_quic =
(!alt_service.IsEmpty() && PL_strstr(alt_service.get(), "quic"))
? true
: false;
Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);
// Gather data on how many URLS get redirected
switch (httpStatus) {
case 200:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
break;
case 301:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
break;
case 302:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
break;
case 304:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
break;
case 307:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
break;
case 308:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
break;
case 400:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
break;
case 401:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
break;
case 403:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
break;
case 404:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
break;
case 500:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
break;
default:
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
break;
}
}
// Let the predictor know whether this was a cacheable response or not so
// that it knows whether or not to possibly prefetch this resource in the
// future.
// We use GetReferringPage because mReferrerInfo may not be set at all(this is
// especially useful in xpcshell tests, where we don't have an actual pageload
// to get a referrer from).
nsCOMPtr<nsIURI> referrer = GetReferringPage();
if (!referrer && mReferrerInfo) {
referrer = mReferrerInfo->GetOriginalReferrer();
}
if (referrer) {
nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
mozilla::net::Predictor::UpdateCacheability(
referrer, mURI, httpStatus, mRequestHead, mResponseHead, lci,
IsThirdPartyTrackingResource());
}
// Only allow 407 (authentication required) to continue
if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
return ProcessFailedProxyConnect(httpStatus);
}
MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
"We should not be hitting the network if we have valid cached "
"content unless we are racing the network and cache");
ProcessSSLInformation();
// notify "http-on-examine-response" observers
gHttpHandler->OnExamineResponse(this);
return ContinueProcessResponse1();
}
void nsHttpChannel::AsyncContinueProcessResponse() {
nsresult rv;
rv = ContinueProcessResponse1();
if (NS_FAILED(rv)) {
// A synchronous failure here would normally be passed as the return
// value from OnStartRequest, which would in turn cancel the request.
// If we're continuing asynchronously, we need to cancel the request
// ourselves.
Unused << Cancel(rv);
}
}
nsresult nsHttpChannel::ContinueProcessResponse1() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
nsresult rv;
if (mSuspendCount) {
LOG(("Waiting until resume to finish processing response [this=%p]\n",
this));
mCallOnResume = [](nsHttpChannel* self) {
self->AsyncContinueProcessResponse();
return NS_OK;
};
return NS_OK;
}
// Check if request was cancelled during http-on-examine-response.
if (mCanceled) {
return CallOnStartRequest();
}
uint32_t httpStatus = mResponseHead->Status();
// STS, Cookies and Alt-Service should not be handled on proxy failure.
// If proxy CONNECT response needs to complete, wait to process connection
// for Strict-Transport-Security.
if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
(httpStatus != 407)) {
nsAutoCString cookie;
if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
SetCookie(cookie);
}
// Given a successful connection, process any STS or PKP data that's
// relevant.
DebugOnly<nsresult> rv = ProcessSecurityHeaders();
MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
if ((httpStatus < 500) && (httpStatus != 421)) {
ProcessAltService();
}
}
if (mConcurrentCacheAccess && mCachedContentIsPartial && httpStatus != 206) {
LOG(
(" only expecting 206 when doing partial request during "
"interrupted cache concurrent read"));
return NS_ERROR_CORRUPTED_CONTENT;
}
// handle unused username and password in url (see bug 232567)
if (httpStatus != 401 && httpStatus != 407) {
if (!mAuthRetryPending) {
rv = mAuthProvider->CheckForSuperfluousAuth();
if (NS_FAILED(rv)) {
LOG((" CheckForSuperfluousAuth failed (%08x)",
static_cast<uint32_t>(rv)));
}
}
if (mCanceled) return CallOnStartRequest();
// reset the authentication's current continuation state because ourvr
// last authentication attempt has been completed successfully
rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
if (NS_FAILED(rv)) {
LOG((" Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
}
mAuthProvider = nullptr;
LOG((" continuation state has been reset"));
}
rv = ProcessCrossOriginEmbedderPolicyHeader();
if (NS_FAILED(rv)) {
mStatus = NS_ERROR_BLOCKED_BY_POLICY;
HandleAsyncAbort();
return NS_OK;
}
rv = ProcessCrossOriginResourcePolicyHeader();
if (NS_FAILED(rv)) {
mStatus = NS_ERROR_DOM_CORP_FAILED;
HandleAsyncAbort();
return NS_OK;
}
rv = NS_OK;
if (!mCanceled) {
rv = ComputeCrossOriginOpenerPolicyMismatch();
if (rv == NS_ERROR_BLOCKED_BY_POLICY) {
// this navigates the doc's browsing context to a network error.
mStatus = NS_ERROR_BLOCKED_BY_POLICY;
HandleAsyncAbort();
return NS_OK;
}
AssertNotDocumentChannel();
}
// No process switch needed, continue as normal.
return ContinueProcessResponse2(rv);
}
void nsHttpChannel::AssertNotDocumentChannel() {
if (!mLoadInfo || !IsDocument()) {
return;
}
#ifndef DEBUG
if (!StaticPrefs::fission_autostart()) {
// This assertion is firing in the wild (Bug 1593545) and its not clear
// why. Disable the assertion in non-fission non-debug configurations to
// avoid crashing user's browsers until we're done dogfooding fission.
return;
}
#endif
nsCOMPtr<nsIParentChannel> parentChannel;
NS_QueryNotificationCallbacks(this, parentChannel);
RefPtr<DocumentChannelParent> documentChannelParent =
do_QueryObject(parentChannel);
if (documentChannelParent) {
// The load is using document channel.
return;
}
RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
if (!httpParent) {
// The load was initiated in the parent and doesn't need document
// channel.
return;
}
nsContentPolicyType contentPolicy;
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetExternalContentPolicyType(&contentPolicy));
RefPtr<BrowsingContext> bc;
if (contentPolicy == CSPService::TYPE_DOCUMENT) {
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetBrowsingContext(getter_AddRefs(bc)));
} else {
MOZ_ALWAYS_SUCCEEDS(mLoadInfo->GetFrameBrowsingContext(getter_AddRefs(bc)));
}
if (!bc) {
return;
}
if (mLoadInfo->LoadingPrincipal() &&
mLoadInfo->LoadingPrincipal()->IsSystemPrincipal()) {
// Loads with the system principal can skip document channel
return;
}
// The load was supposed to use document channel but didn't.
MOZ_DIAGNOSTIC_ASSERT(
!StaticPrefs::browser_tabs_documentchannel(),
"DocumentChannel is enabled but this load was done without it");
}
nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
if (NS_FAILED(rv) && !mCanceled) {
// The process switch failed, cancel this channel.
Cancel(rv);
return CallOnStartRequest();
}
if (mAPIRedirectToURI && !mCanceled) {
MOZ_ASSERT(!mOnStartRequestCalled);
nsCOMPtr<nsIURI> redirectTo;
mAPIRedirectToURI.swap(redirectTo);
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
rv = StartRedirectChannelToURI(redirectTo,
nsIChannelEventSink::REDIRECT_TEMPORARY);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
}
// Hack: ContinueProcessResponse3 uses NS_OK to detect successful
// redirects, so we distinguish this codepath (a non-redirect that's
// processing normally) by passing in a bogus error code.
return ContinueProcessResponse3(NS_BINDING_FAILED);
}
nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
LOG(("nsHttpChannel::ContinueProcessResponse3 [this=%p, rv=%" PRIx32 "]",
this, static_cast<uint32_t>(rv)));
if (NS_SUCCEEDED(rv)) {
// redirectTo() has passed through, we don't want to go on with
// this channel. It will now be canceled by the redirect handling
// code that called this function.
return NS_OK;
}
rv = NS_OK;
uint32_t httpStatus = mResponseHead->Status();
// handle different server response categories. Note that we handle
// caching or not caching of error pages in
// nsHttpResponseHead::MustValidate; if you change this switch, update that
// one
switch (httpStatus) {
case 200:
case 203:
// Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
// So if a server does that and sends 200 instead of 206 that we
// expect, notify our caller.
// However, if we wanted to start from the beginning, let it go through
if (mResuming && mStartPos != 0) {
LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
Cancel(NS_ERROR_NOT_RESUMABLE);
rv = CallOnStartRequest();
break;
}
// these can normally be cached
rv = ProcessNormal();
MaybeInvalidateCacheEntryForSubsequentGet();
break;
case 206:
if (mCachedContentIsPartial) { // an internal byte range request...
auto func = [](auto* self, nsresult aRv) {
return self->ContinueProcessResponseAfterPartialContent(aRv);
};
rv = ProcessPartialContent(func);
// Directly call ContinueProcessResponseAfterPartialContent if channel
// is not suspended or ProcessPartialContent throws.
if (!mSuspendCount || NS_FAILED(rv)) {
return ContinueProcessResponseAfterPartialContent(rv);
}
return NS_OK;
} else {
mCacheInputStream.CloseAndRelease();
rv = ProcessNormal();
}
break;
case 300:
case 301:
case 302:
case 307:
case 308:
case 303:
#if 0
case 305: // disabled as a security measure (see bug 187996).
#endif
// don't store the response body for redirects
MaybeInvalidateCacheEntryForSubsequentGet();
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
rv = AsyncProcessRedirection(httpStatus);
if (NS_FAILED(rv)) {
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
static_cast<uint32_t>(rv)));
// don't cache failed redirect responses.
if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
if (DoNotRender3xxBody(rv)) {
mStatus = rv;
DoNotifyListener();
} else {
rv = ContinueProcessResponse4(rv);
}
}
break;
case 304:
if (!ShouldBypassProcessNotModified()) {
auto func = [](auto* self, nsresult aRv) {
return self->ContinueProcessResponseAfterNotModified(aRv);
};
rv = ProcessNotModified(func);
// Directly call ContinueProcessResponseAfterNotModified if channel
// is not suspended or ProcessNotModified throws.
if (!mSuspendCount || NS_FAILED(rv)) {
return ContinueProcessResponseAfterNotModified(rv);
}
return NS_OK;
}
// Don't cache uninformative 304
if (mCustomConditionalRequest) {
CloseCacheEntry(false);
}
if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
rv = ProcessNormal();
}
break;
case 401:
case 407:
if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
// When a custom auth header fails, we don't want to try
// any cached credentials, nor we want to ask the user.
// It's up to the consumer to re-try w/o setting a custom
// auth header if cached credentials should be attempted.
rv = NS_ERROR_FAILURE;
} else {
rv = mAuthProvider->ProcessAuthentication(
httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
mTransaction->ProxyConnectFailed());
}
if (rv == NS_ERROR_IN_PROGRESS) {
// authentication prompt has been invoked and result
// is expected asynchronously
mAuthRetryPending = true;
if (httpStatus == 407 ||
(mTransaction && mTransaction->ProxyConnectFailed()))
mProxyAuthPending = true;
// suspend the transaction pump to stop receiving the
// unauthenticated content data. We will throw that data
// away when user provides credentials or resume the pump
// when user refuses to authenticate.
LOG(
("Suspending the transaction, asynchronously prompting for "
"credentials"));
mTransactionPump->Suspend();
rv = NS_OK;
} else if (NS_FAILED(rv)) {
LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
static_cast<uint32_t>(rv)));
if (mTransaction && mTransaction->ProxyConnectFailed()) {
return ProcessFailedProxyConnect(httpStatus);
}
if (!mAuthRetryPending) {
rv = mAuthProvider->CheckForSuperfluousAuth();
if (NS_FAILED(rv)) {
LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
static_cast<uint32_t>(rv)));
}
}
rv = ProcessNormal();
} else {
mAuthRetryPending = true; // see DoAuthRetry
}
break;
case 425:
case 429:
// Do not cache 425 and 429.
CloseCacheEntry(false);
MOZ_FALLTHROUGH; // process normally
default:
rv = ProcessNormal();
MaybeInvalidateCacheEntryForSubsequentGet();
break;
}
UpdateCacheDisposition(false, false);
return rv;
}
nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
nsresult aRv) {
LOG(
("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
"[this=%p, rv=%" PRIx32 "]",
this, static_cast<uint32_t>(aRv)));
UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
return aRv;
}
nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
LOG(
("nsHttpChannel::ContinueProcessResponseAfterNotModified "
"[this=%p, rv=%" PRIx32 "]",
this, static_cast<uint32_t>(aRv)));
if (NS_SUCCEEDED(aRv)) {
mTransactionReplaced = true;
UpdateCacheDisposition(true, false);
return NS_OK;
}
LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
static_cast<uint32_t>(aRv)));
// We cannot read from the cache entry, it might be in an
// incosistent state. Doom it and redirect the channel
// to the same URI to reload from the network.
mCacheInputStream.CloseAndRelease();
if (mCacheEntry) {
mCacheEntry->AsyncDoom(nullptr);
mCacheEntry = nullptr;
}
nsresult rv =
StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
if (NS_SUCCEEDED(rv)) {
return NS_OK;
}
// Don't cache uninformative 304
if (mCustomConditionalRequest) {
CloseCacheEntry(false);
}
if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
rv = ProcessNormal();
}
UpdateCacheDisposition(false, false);
return rv;
}
void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
bool aPartialContentUsed) {
if (mRaceDelay && !mRaceCacheWithNetwork &&
(mCachedContentIsPartial || mDidReval)) {
if (aSuccessfulReval || aPartialContentUsed) {
AccumulateCategorical(
Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
} else {
AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
CachedContentNotUsed);
}
}
if (Telemetry::CanRecordPrereleaseData()) {
CacheDisposition cacheDisposition;
if (!mDidReval) {
cacheDisposition = kCacheMissed;
} else if (aSuccessfulReval) {
cacheDisposition = kCacheHitViaReval;
} else {
cacheDisposition = kCacheMissedViaReval;
}
AccumulateCacheHitTelemetry(cacheDisposition);
mCacheDisposition = cacheDisposition;
Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
static_cast<uint32_t>(mResponseHead->Version()));
if (mResponseHead->Version() == HttpVersion::v0_9) {
// DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
// NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
uint32_t v09Info = 0;
if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
v09Info += 1;
}
if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
v09Info += 2;
}
Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
}
}
}
nsresult nsHttpChannel::ContinueProcessResponse4(nsresult rv) {
bool doNotRender = DoNotRender3xxBody(rv);
if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
bool isHTTP =
mRedirectURI->SchemeIs("http") || mRedirectURI->SchemeIs("https");
if (!isHTTP) {
// This was a blocked attempt to redirect and subvert the system by
// redirecting to another protocol (perhaps javascript:)
// In that case we want to throw an error instead of displaying the
// non-redirected response body.
LOG(("ContinueProcessResponse4 detected rejected Non-HTTP Redirection"));
doNotRender = true;
rv = NS_ERROR_CORRUPTED_CONTENT;
}
}
if (doNotRender) {
Cancel(rv);
DoNotifyListener();
return rv;
}
if (NS_SUCCEEDED(rv)) {
UpdateInhibitPersistentCachingFlag();
rv = InitCacheEntry();
if (NS_FAILED(rv)) {
LOG(
("ContinueProcessResponse4 "
"failed to init cache entry [rv=%x]\n",
static_cast<uint32_t>(rv)));
}
CloseCacheEntry(false);
if (mApplicationCacheForWrite) {
// Store response in the offline cache
Unused << InitOfflineCacheEntry();
CloseOfflineCacheEntry();
}
return NS_OK;
}
LOG(("ContinueProcessResponse4 got failure result [rv=%" PRIx32 "]\n",
static_cast<uint32_t>(rv)));
if (mTransaction && mTransaction->ProxyConnectFailed()) {
return ProcessFailedProxyConnect(mRedirectType);
}
return ProcessNormal();
}
nsresult nsHttpChannel::ProcessNormal() {
nsresult rv;
LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
bool succeeded;
rv = GetRequestSucceeded(&succeeded);
if (NS_SUCCEEDED(rv) && !succeeded) {
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
bool waitingForRedirectCallback;
Unused << ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback) {
// The transaction has been suspended by ProcessFallback.
return NS_OK;
}
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
}
return ContinueProcessNormal(NS_OK);
}
nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));
if (NS_FAILED(rv)) {
// Fill the failure status here, we have failed to fall back, thus we
// have to report our status as failed.
mStatus = rv;
DoNotifyListener();
return rv;
}
if (mFallingBack) {
// Do not continue with normal processing, fallback is in
// progress now.
return NS_OK;
}
// if we're here, then any byte-range requests failed to result in a partial
// response. we must clear this flag to prevent BufferPartialContent from
// being called inside our OnDataAvailable (see bug 136678).
mCachedContentIsPartial = false;
ClearBogusContentEncodingIfNeeded();
UpdateInhibitPersistentCachingFlag();
// this must be called before firing OnStartRequest, since http clients,
// such as imagelib, expect our cache entry to already have the correct
// expiration time (bug 87710).
if (mCacheEntry) {
rv = InitCacheEntry();
if (NS_FAILED(rv)) CloseCacheEntry(true);
}
// Check that the server sent us what we were asking for
if (mResuming) {
// Create an entity id from the response
nsAutoCString id;
rv = GetEntityID(id);
if (NS_FAILED(rv)) {
// If creating an entity id is not possible -> error
Cancel(NS_ERROR_NOT_RESUMABLE);
} else if (mResponseHead->Status() != 206 &&
mResponseHead->Status() != 200) {
// Probably 404 Not Found, 412 Precondition Failed or
// 416 Invalid Range -> error
LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
this));
Cancel(NS_ERROR_ENTITY_CHANGED);
}
// If we were passed an entity id, verify it's equal to the server's
else if (!mEntityID.IsEmpty()) {
if (!mEntityID.Equals(id)) {
LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
mEntityID.get(), id.get(), this));
Cancel(NS_ERROR_ENTITY_CHANGED);
}
}
}
rv = CallOnStartRequest();
if (NS_FAILED(rv)) return rv;
// install cache listener if we still have a cache entry open
if (mCacheEntry && !mCacheEntryIsReadOnly) {
rv = InstallCacheListener();
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
nsresult nsHttpChannel::PromptTempRedirect() {
if (!gHttpHandler->PromptTempRedirect()) {
return NS_OK;
}
nsresult rv;
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStringBundle> stringBundle;
rv =
bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
if (NS_FAILED(rv)) return rv;
nsAutoString messageString;
rv = stringBundle->GetStringFromName("RepostFormData", messageString);
if (NS_SUCCEEDED(rv)) {
bool repost = false;
nsCOMPtr<nsIPrompt> prompt;
GetCallback(prompt);
if (!prompt) return NS_ERROR_NO_INTERFACE;
prompt->Confirm(nullptr, messageString.get(), &repost);
if (!repost) return NS_ERROR_FAILURE;
}
return rv;
}
nsresult nsHttpChannel::ProxyFailover() {
LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIProxyInfo> pi;
rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
getter_AddRefs(pi));
if (NS_FAILED(rv)) return rv;
// XXXbz so where does this codepath remove us from the loadgroup,
// exactly?
return AsyncDoReplaceWithProxy(pi);
}
void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
if (mSuspendCount) {
LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleAsyncRedirectChannelToHttps();
return NS_OK;
};
return;
}
nsresult rv = StartRedirectChannelToHttps();
if (NS_FAILED(rv)) {
rv = ContinueAsyncRedirectChannelToURI(rv);
if (NS_FAILED(rv)) {
LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
static_cast<uint32_t>(rv), this));
}
}
}
nsresult nsHttpChannel::StartRedirectChannelToHttps() {
LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
nsCOMPtr<nsIURI> upgradedURI;
nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
NS_ENSURE_SUCCESS(rv, rv);
return StartRedirectChannelToURI(
upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
nsIChannelEventSink::REDIRECT_STS_UPGRADE);
}
void nsHttpChannel::HandleAsyncAPIRedirect() {
MOZ_ASSERT(!mCallOnResume, "How did that happen?");
MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");
if (mSuspendCount) {
LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
mCallOnResume = [](nsHttpChannel* self) {
self->HandleAsyncAPIRedirect();
return NS_OK;
};
return;
}
nsresult rv = StartRedirectChannelToURI(
mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
if (NS_FAILED(rv)) {
rv = ContinueAsyncRedirectChannelToURI(rv);
if (NS_FAILED(rv)) {
LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
static_cast<uint32_t>(rv), this));
}
}
}
nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI* upgradedURI,
uint32_t flags) {
nsresult rv = NS_OK;
LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
nsCOMPtr<nsIChannel> newChannel;
nsCOMPtr<nsILoadInfo> redirectLoadInfo =
CloneLoadInfoForRedirect(upgradedURI, flags);
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
NS_ENSURE_SUCCESS(rv, rv);
rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
redirectLoadInfo,
nullptr, // PerformanceStorage
nullptr, // aLoadGroup
nullptr, // aCallbacks
nsIRequest::LOAD_NORMAL, ioService);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
NS_ENSURE_SUCCESS(rv, rv);
// Inform consumers about this fake redirect
mRedirectChannel = newChannel;
PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
AutoRedirectVetoNotifier notifier(this);
/* Remove the async call to ContinueAsyncRedirectChannelToURI().
* It is called directly by our callers upon return (to clean up
* the failed redirect). */
PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
}
return rv;
}
nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));
// Since we handle mAPIRedirectToURI also after on-examine-response handler
// rather drop it here to avoid any redirect loops, even just hypothetical.
mAPIRedirectToURI = nullptr;
if (NS_SUCCEEDED(rv)) {
rv = OpenRedirectChannel(rv);
}
if (NS_FAILED(rv)) {
// Cancel the channel here, the update to https had been vetoed
// but from the security reasons we have to discard the whole channel
// load.
Cancel(rv);
}
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}
if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
// We have to manually notify the listener because there is not any pump
// that would call our OnStart/StopRequest after resume from waiting for
// the redirect callback.
DoNotifyListener();
}
return rv;
}
nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
AutoRedirectVetoNotifier notifier(this);
// Make sure to do this after we received redirect veto answer,
// i.e. after all sinks had been notified
mRedirectChannel->SetOriginalURI(mOriginalURI);
// open new channel
rv = mRedirectChannel->AsyncOpen(mListener);
NS_ENSURE_SUCCESS(rv, rv);
mStatus = NS_BINDING_REDIRECTED;
notifier.RedirectSucceeded();
ReleaseListeners();
return NS_OK;
}
nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi) {
LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags, mProxyURI,
mLoadInfo, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) return rv;
uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = SetupReplacementChannel(mURI, newChannel, true, flags);
if (NS_FAILED(rv)) return rv;
// Inform consumers about this fake redirect
mRedirectChannel = newChannel;
PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
AutoRedirectVetoNotifier notifier(this);
PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
}
return rv;
}
nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
AutoRedirectVetoNotifier notifier(this);
if (NS_FAILED(rv)) return rv;
MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
// Make sure to do this after we received redirect veto answer,
// i.e. after all sinks had been notified
mRedirectChannel->SetOriginalURI(mOriginalURI);
// open new channel
rv = mRedirectChannel->AsyncOpen(mListener);
NS_ENSURE_SUCCESS(rv, rv);
mStatus = NS_BINDING_REDIRECTED;
notifier.RedirectSucceeded();
ReleaseListeners();
return rv;
}
nsresult nsHttpChannel::ResolveProxy() {
LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
// using the nsIProtocolProxyService2 allows a minor performance
// optimization, but if an add-on has only provided the original interface
// then it is ok to use that version.
nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
if (pps2) {
rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
getter_AddRefs(mProxyRequest));
} else {
rv = pps->AsyncResolve(static_cast<nsIChannel*>(this), mProxyResolveFlags,
this, nullptr, getter_AddRefs(mProxyRequest));
}
return rv;
}
bool nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) {
nsresult rv;
nsAutoCString buf, metaKey;
Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
if (!buf.IsEmpty()) {
NS_NAMED_LITERAL_CSTRING(prefix, "request-");
// enumerate the elements of the Vary header...
char* val = buf.BeginWriting(); // going to munge buf
char* token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
while (token) {
LOG(
("nsHttpChannel::ResponseWouldVary [channel=%p] "
"processing %s\n",
this, token));
//
// if "*", then assume response would vary. technically speaking,
// "Vary: header, *" is not permitted, but we allow it anyways.
//
// We hash values of cookie-headers for the following reasons:
//
// 1- cookies can be very large in size
//
// 2- cookies may contain sensitive information. (for parity with
// out policy of not storing Set-cookie headers in the cache
// meta data, we likewise do not want to store cookie headers
// here.)
//
if (*token == '*')
return true; // if we encounter this, just get out of here
// build cache meta data key...
metaKey = prefix + nsDependentCString(token);
// check the last value of the given request header to see if it has
// since changed. if so, then indeed the cached response is invalid.
nsCString lastVal;
entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
LOG(
("nsHttpChannel::ResponseWouldVary [channel=%p] "
"stored value = \"%s\"\n",
this, lastVal.get()));
// Look for value of "Cookie" in the request headers
nsHttpAtom atom = nsHttp::ResolveAtom(token);
nsAutoCString newVal;
bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
if (!lastVal.IsEmpty()) {
// value for this header in cache, but no value in request
if (!hasHeader) {
return true; // yes - response would vary
}
// If this is a cookie-header, stored metadata is not
// the value itself but the hash. So we also hash the
// outgoing value here in order to compare the hashes
nsAutoCString hash;
if (atom == nsHttp::Cookie) {
rv = Hash(newVal.get(), hash);
// If hash failed, be conservative (the cached hash
// exists at this point) and claim response would vary
if (NS_FAILED(rv)) return true;
newVal = hash;
LOG(
("nsHttpChannel::ResponseWouldVary [this=%p] "
"set-cookie value hashed to %s\n",
this, newVal.get()));
}
if (!newVal.Equals(lastVal)) {
return true; // yes, response would vary
}
} else if (hasHeader) { // old value is empty, but newVal is set
return true;
}
// next token...
token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
}
}
return false;
}
// We need to have an implementation of this function just so that we can keep
// all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
// to set a member function ptr to a base class function.
void nsHttpChannel::HandleAsyncAbort() {
HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
}
//-----------------------------------------------------------------------------
// nsHttpChannel <byte-range>
//-----------------------------------------------------------------------------
bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
bool ignoreMissingPartialLen) const {
bool hasContentEncoding =
mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
nsAutoCString etag;
Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
bool hasWeakEtag =
!etag.IsEmpty() && StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));
return (partialLen < contentLength) &&
(partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
!hasWeakEtag && mCachedResponseHead->IsResumable() &&
!mCustomConditionalRequest && !mCachedResponseHead->NoStore();
}
nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
// Be pesimistic
mIsPartialRequest = false;
if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen))
return NS_ERROR_NOT_RESUMABLE;
// looks like a partial entry we can reuse; add If-Range
// and Range headers.
nsresult rv = SetupByteRangeRequest(partialLen);
if (NS_FAILED(rv)) {
// Make the request unconditional again.
UntieByteRangeRequest();
}
return rv;
}
nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
// cached content has been found to be partial, add necessary request
// headers to complete cache entry.
// use strongest validator available...
nsAutoCString val;
Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
if (val.IsEmpty())
Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
if (val.IsEmpty()) {
// if we hit this code it means mCachedResponseHead->IsResumable() is
// either broken or not being called.
MOZ_ASSERT_UNREACHABLE("no cache validator");
mIsPartialRequest = false;
return NS_ERROR_FAILURE;
}
char buf[64];
SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
DebugOnly<nsresult> rv;
rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
MOZ_ASSERT(NS_SUCCEEDED(rv));
mIsPartialRequest = true;
return NS_OK;
}
void nsHttpChannel::UntieByteRangeRequest() {
DebugOnly<nsresult> rv;
rv = mRequestHead.ClearHeader(nsHttp::Range);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.ClearHeader(nsHttp::If_Range);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
nsresult nsHttpChannel::ProcessPartialContent(
const std::function<nsresult(nsHttpChannel*, nsresult)>&
aContinueProcessResponseFunc) {
// ok, we've just received a 206
//
// we need to stream whatever data is in the cache out first, and then
// pick up whatever data is on the wire, writing it into the cache.
LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
// Make sure to clear bogus content-encodings before looking at the header
ClearBogusContentEncodingIfNeeded();
// Check if the content-encoding we now got is different from the one we
// got before
nsAutoCString contentEncoding, cachedContentEncoding;
// It is possible that there is not such headers
Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
cachedContentEncoding);
if (PL_strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) != 0) {
Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
return CallOnStartRequest();
}
nsresult rv;
int64_t cachedContentLength = mCachedResponseHead->ContentLength();
int64_t entitySize = mResponseHead->TotalEntitySize();
nsAutoCString contentRange;
Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
LOG(
("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
"original content-length %" PRId64 ", entity-size %" PRId64
", content-range %s\n",
this, mTransaction.get(), cachedContentLength, entitySize,
contentRange.get()));
if ((entitySize >= 0) && (cachedContentLength >= 0) &&
(entitySize != cachedContentLength)) {
LOG(
("nsHttpChannel::ProcessPartialContent [this=%p] "
"206 has different total entity size than the content length "
"of the original partially cached entity.\n",
this));
mCacheEntry->AsyncDoom(nullptr);
Cancel(NS_ERROR_CORRUPTED_CONTENT);
return CallOnStartRequest();
}
if (mConcurrentCacheAccess) {
// We started to read cached data sooner than its write has been done.
// But the concurrent write has not finished completely, so we had to
// do a range request. Now let the content coming from the network
// be presented to consumers and also stored to the cache entry.
rv = InstallCacheListener(mLogicalOffset);
if (NS_FAILED(rv)) return rv;
if (mOfflineCacheEntry) {
rv = InstallOfflineCacheListener(mLogicalOffset);
if (NS_FAILED(rv)) return rv;
}
} else {
// suspend the current transaction
rv = mTransactionPump->Suspend();
if (NS_FAILED(rv)) return rv;
}
// merge any new headers with the cached response headers
rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
if (NS_FAILED(rv)) return rv;
// update the cached response head
nsAutoCString head;
mCachedResponseHead->Flatten(head, true);
rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
// make the cached response be the current response
mResponseHead = std::move(mCachedResponseHead);
UpdateInhibitPersistentCachingFlag();
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
// notify observers interested in looking at a response that has been
// merged with any cached headers (http-on-examine-merged-response).
gHttpHandler->OnExamineMergedResponse(this);
if (mConcurrentCacheAccess) {
mCachedContentIsPartial = false;
// Leave the mConcurrentCacheAccess flag set, we want to use it
// to prevent duplicate OnStartRequest call on the target listener
// in case this channel is canceled before it gets its OnStartRequest
// from the http transaction.
return rv;
}
// Now we continue reading the network response.
// the cached content is valid, although incomplete.
mCachedContentIsValid = true;
return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
nsresult rv = self->ReadFromCache(false);
return aContinueProcessResponseFunc(self, rv);
});
}
nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool* streamDone) {
nsresult rv;
LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
// by default, assume we would have streamed all data or failed...
*streamDone = true;
// setup cache listener to append to cache entry
int64_t size;
rv = mCacheEntry->GetDataSize(&size);
if (NS_FAILED(rv)) return rv;
rv = InstallCacheListener(size);
if (NS_FAILED(rv)) return rv;
// Entry is valid, do it now, after the output stream has been opened,
// otherwise when done earlier, pending readers would consider the cache
// entry still as partial (CacheEntry::GetDataSize would return the partial
// data size) and consumers would do the conditional request again.
rv = mCacheEntry->SetValid();
if (NS_FAILED(rv)) return rv;
// need to track the logical offset of the data being sent to our listener
mLogicalOffset = size;
// we're now completing the cached content, so we can clear this flag.
// this puts us in the state of a regular download.
mCachedContentIsPartial = false;
// The cache input stream pump is finished, we do not need it any more.
// (see bug 1313923)
mCachePump = nullptr;
// resume the transaction if it exists, otherwise the pipe contained the
// remaining part of the document and we've now streamed all of the data.
if (mTransactionPump) {
rv = mTransactionPump->Resume();
if (NS_SUCCEEDED(rv)) *streamDone = false;
} else
MOZ_ASSERT_UNREACHABLE("no transaction");
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <cache>
//-----------------------------------------------------------------------------
bool nsHttpChannel::ShouldBypassProcessNotModified() {
if (mCustomConditionalRequest) {
LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
return true;
}
if (!mDidReval) {
LOG(
("Server returned a 304 response even though we did not send a "
"conditional request"));
return true;
}
return false;
}
nsresult nsHttpChannel::ProcessNotModified(
const std::function<nsresult(nsHttpChannel*, nsresult)>&
aContinueProcessResponseFunc) {
nsresult rv;
LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
// Assert ShouldBypassProcessNotModified() has been checked before call to
// ProcessNotModified().
MOZ_ASSERT(!ShouldBypassProcessNotModified());
MOZ_ASSERT(mCachedResponseHead);
MOZ_ASSERT(mCacheEntry);
NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
// If the 304 response contains a Last-Modified different than the
// one in our cache that is pretty suspicious and is, in at least the
// case of bug 716840, a sign of the server having previously corrupted
// our cache with a bad response. Take the minor step here of just dooming
// that cache entry so there is a fighting chance of getting things on the
// right track.
nsAutoCString lastModifiedCached;
nsAutoCString lastModified304;
rv =
mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
if (NS_SUCCEEDED(rv)) {
rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
}
if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
LOG(
("Cache Entry and 304 Last-Modified Headers Do Not Match "
"[%s] and [%s]\n",
lastModifiedCached.get(), lastModified304.get()));
mCacheEntry->AsyncDoom(nullptr);
Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
}
// merge any new headers with the cached response headers
rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
if (NS_FAILED(rv)) return rv;
// update the cached response head
nsAutoCString head;
mCachedResponseHead->Flatten(head, true);
rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
// make the cached response be the current response
mResponseHead = std::move(mCachedResponseHead);
UpdateInhibitPersistentCachingFlag();
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
rv = AddCacheEntryHeaders(mCacheEntry);
if (NS_FAILED(rv)) return rv;
// notify observers interested in looking at a reponse that has been
// merged with any cached headers
gHttpHandler->OnExamineMergedResponse(this);
mCachedContentIsValid = true;
// Tell other consumers the entry is OK to use
rv = mCacheEntry->SetValid();
if (NS_FAILED(rv)) return rv;
return CallOrWaitForResume([aContinueProcessResponseFunc](auto* self) {
nsresult rv = self->ReadFromCache(false);
return aContinueProcessResponseFunc(self, rv);
});
}
nsresult nsHttpChannel::ProcessFallback(bool* waitingForRedirectCallback) {
LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
nsresult rv;
*waitingForRedirectCallback = false;
mFallingBack = false;
// At this point a load has failed (either due to network problems
// or an error returned on the server). Perform an application
// cache fallback if we have a URI to fall back to.
if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
LOG((" choosing not to fallback [%p,%s,%d]", mApplicationCache.get(),
mFallbackKey.get(), mFallbackChannel));
return NS_OK;
}
// Make sure the fallback entry hasn't been marked as a foreign
// entry.
uint32_t fallbackEntryType;
rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
NS_ENSURE_SUCCESS(rv, rv);
if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
// This cache points to a fallback that refers to a different
// manifest. Refuse to fall back.
return NS_OK;
}
if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
// Refuse to fallback if the fallback key is not contained in the same
// path as the cache manifest.
return NS_OK;
}
MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
"Fallback entry not marked correctly!");
// Kill any offline cache entry, and disable offline caching for the
// fallback.
if (mOfflineCacheEntry) {
mOfflineCacheEntry->AsyncDoom(nullptr);
mOfflineCacheEntry = nullptr;
}
mApplicationCacheForWrite = nullptr;
mOfflineCacheEntry = nullptr;
// Close the current cache entry.
CloseCacheEntry(true);
// Create a new channel to load the fallback entry.
RefPtr<nsIChannel> newChannel;
rv = gHttpHandler->NewChannel(mURI, mLoadInfo, getter_AddRefs(newChannel));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
NS_ENSURE_SUCCESS(rv, rv);
// Make sure the new channel loads from the fallback key.
nsCOMPtr<nsIHttpChannelInternal> httpInternal =
do_QueryInterface(newChannel, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
NS_ENSURE_SUCCESS(rv, rv);
// ... and fallbacks should only load from the cache.
uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
rv = newChannel->SetLoadFlags(newLoadFlags);
// Inform consumers about this fake redirect
mRedirectChannel = newChannel;
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
AutoRedirectVetoNotifier notifier(this);
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
return rv;
}
// Indicate we are now waiting for the asynchronous redirect callback
// if all went OK.
*waitingForRedirectCallback = true;
return NS_OK;
}
nsresult nsHttpChannel::ContinueProcessFallback(nsresult rv) {
AutoRedirectVetoNotifier notifier(this);
if (NS_FAILED(rv)) return rv;
MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
// Make sure to do this after we received redirect veto answer,
// i.e. after all sinks had been notified
mRedirectChannel->SetOriginalURI(mOriginalURI);
rv = mRedirectChannel->AsyncOpen(mListener);
NS_ENSURE_SUCCESS(rv, rv);
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
MaybeWarnAboutAppCache();
}
// close down this channel
Cancel(NS_BINDING_REDIRECTED);
notifier.RedirectSucceeded();
ReleaseListeners();
mFallingBack = true;
return NS_OK;
}
// Determines if a request is a byte range request for a subrange,
// i.e. is a byte range request, but not a 0- byte range request.
static bool IsSubRangeRequest(nsHttpRequestHead& aRequestHead) {
nsAutoCString byteRange;
if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
return false;
}
return !byteRange.EqualsLiteral("bytes=0-");
}
nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
// Drop this flag here
mConcurrentCacheAccess = 0;
mLoadedFromApplicationCache = false;
LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
// make sure we're not abusing this function
MOZ_ASSERT(!mCacheEntry, "cache entry already open");
if (mRequestHead.IsPost()) {
// If the post id is already set then this is an attempt to replay
// a post transaction via the cache. Otherwise, we need a unique
// post id for this transaction.
if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
} else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
// don't use the cache for other types of requests
return NS_OK;
}
// Pick up an application cache from the notification
// callbacks if available and if we are not an intercepted channel.
if (!mApplicationCache && mInheritApplicationCache) {
nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
GetCallback(appCacheContainer);
if (appCacheContainer) {
appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
}
}
return OpenCacheEntryInternal(isHttps, mApplicationCache, true);
}
bool nsHttpChannel::IsIsolated() {
if (mHasBeenIsolatedChecked) {
return mIsIsolated;
}
mIsIsolated = StaticPrefs::browser_cache_cache_isolation() ||
(IsThirdPartyTrackingResource() &&
!AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(
this, mURI, nullptr));
mHasBeenIsolatedChecked = true;
return mIsIsolated;
}
const nsCString& nsHttpChannel::GetTopWindowOrigin() {
if (mTopWindowOriginComputed) {
return mTopWindowOrigin;
}
nsCOMPtr<nsIURI> topWindowURI;
nsresult rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
bool isDocument = false;
if (NS_FAILED(rv) && NS_SUCCEEDED(GetIsMainDocumentChannel(&isDocument)) &&
isDocument) {
// For top-level documents, use the document channel's origin to compute
// the unique storage space identifier instead of the top Window URI.
rv = NS_GetFinalChannelURI(this, getter_AddRefs(topWindowURI));
NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
}
rv = nsContentUtils::GetASCIIOrigin(topWindowURI ? topWindowURI : mURI,
mTopWindowOrigin);
NS_ENSURE_SUCCESS(rv, mTopWindowOrigin);
mTopWindowOriginComputed = true;
return mTopWindowOrigin;
}
nsresult nsHttpChannel::OpenCacheEntryInternal(
bool isHttps, nsIApplicationCache* applicationCache,
bool allowApplicationCache) {
MOZ_ASSERT_IF(!allowApplicationCache, !applicationCache);
nsresult rv;
if (mResuming) {
// We don't support caching for requests initiated
// via nsIResumableChannel.
return NS_OK;
}
// Don't cache byte range requests which are subranges, only cache 0-
// byte range requests.
if (IsSubRangeRequest(mRequestHead)) {
return NS_OK;
}
// Handle correctly mCacheEntriesToWaitFor
AutoCacheWaitFlags waitFlags(this);
nsAutoCString cacheKey;
nsAutoCString extension;
nsCOMPtr<nsICacheStorageService> cacheStorageService(
services::GetCacheStorageService());
if (!cacheStorageService) {
return NS_ERROR_NOT_AVAILABLE;
}
nsCOMPtr<nsICacheStorage> cacheStorage;
nsCOMPtr<nsIURI> openURI;
if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
// This is a fallback channel, open fallback URI instead
rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
NS_ENSURE_SUCCESS(rv, rv);
} else {
openURI = mURI;
}
RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
if (!info) {
return NS_ERROR_FAILURE;
}
uint32_t cacheEntryOpenFlags;
bool offline = gIOService->IsOffline();
bool maybeRCWN = false;
nsAutoCString cacheControlRequestHeader;
Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
cacheControlRequestHeader);
CacheControlParser cacheControlRequest(cacheControlRequestHeader);
if (cacheControlRequest.NoStore()) {
goto bypassCacheEntryOpen;
}
if (offline || (mLoadFlags & INHIBIT_CACHING)) {
if (BYPASS_LOCAL_CACHE(mLoadFlags, mPreferCacheLoadOverBypass) &&
!offline) {
goto bypassCacheEntryOpen;
}
cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
mCacheEntryIsReadOnly = true;
} else if (BYPASS_LOCAL_CACHE(mLoadFlags, mPreferCacheLoadOverBypass) &&
!applicationCache) {
cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
} else {
cacheEntryOpenFlags =
nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
}
// Remember the request is a custom conditional request so that we can
// process any 304 response correctly.
mCustomConditionalRequest =
mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
mRequestHead.HasHeader(nsHttp::If_None_Match) ||
mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
mRequestHead.HasHeader(nsHttp::If_Match) ||
mRequestHead.HasHeader(nsHttp::If_Range);
if (!mPostID && applicationCache) {
rv = cacheStorageService->AppCacheStorage(info, applicationCache,
getter_AddRefs(cacheStorage));
} else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
rv = cacheStorageService->MemoryCacheStorage(
info, // ? choose app cache as well...
getter_AddRefs(cacheStorage));
} else if (mPinCacheContent) {
rv = cacheStorageService->PinningCacheStorage(info,
getter_AddRefs(cacheStorage));
} else {
bool lookupAppCache =
(mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)) &&
!mPostID && MOZ_LIKELY(allowApplicationCache);
// Try to race only if we use disk cache storage and we don't lookup
// app cache first
maybeRCWN = (!lookupAppCache) && mRequestHead.IsSafeMethod();
rv = cacheStorageService->DiskCacheStorage(info, lookupAppCache,
getter_AddRefs(cacheStorage));
}
NS_ENSURE_SUCCESS(rv, rv);
if ((mClassOfService & nsIClassOfService::Leader) ||
(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
// Only for backward compatibility with the old cache back end.
// When removed, remove the flags and related code snippets.
if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
if (mPostID) {
extension.Append(nsPrintfCString("%d", mPostID));
}
if (mIsTRRServiceChannel) {
extension.Append("TRR");
}
if (mRequestHead.IsHead()) {
extension.Append("HEAD");
}
if (IsIsolated()) {
auto& topWindowOrigin = GetTopWindowOrigin();
if (topWindowOrigin.IsEmpty()) {
return NS_ERROR_FAILURE;
}
extension.Append("-unique:");
extension.Append(topWindowOrigin);
}
mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
mCacheQueueSizeWhenOpen =
CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
if (sRCWNEnabled && maybeRCWN && !mApplicationCacheForWrite) {
bool hasAltData = false;
uint32_t sizeInKb = 0;
rv = cacheStorage->GetCacheIndexEntryAttrs(openURI, extension, &hasAltData,
&sizeInKb);
// We will attempt to race the network vs the cache if we've found
// this entry in the cache index, and it has appropriate attributes
// (doesn't have alt-data, and has a small size)
if (NS_SUCCEEDED(rv) && !hasAltData &&
sizeInKb < sRCWNSmallResourceSizeKB) {
MaybeRaceCacheWithNetwork();
}
}
if (!mCacheOpenDelay) {
MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
if (mNetworkTriggered) {
mRaceCacheWithNetwork = sRCWNEnabled;
}
rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags,
this);
} else {
// We pass `this` explicitly as a parameter due to the raw pointer
// to refcounted object in lambda analysis.
mCacheOpenFunc = [openURI, extension, cacheEntryOpenFlags,
cacheStorage](nsHttpChannel* self) -> void {
MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, self);
};
// calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), this,
mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
}
NS_ENSURE_SUCCESS(rv, rv);
waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
bypassCacheEntryOpen:
if (!mApplicationCacheForWrite || !allowApplicationCache) return NS_OK;
// If there is an app cache to write to, open the entry right now in parallel.
// make sure we're not abusing this function
MOZ_ASSERT(!mOfflineCacheEntry, "cache entry already open");
if (offline) {
// only put things in the offline cache while online
return NS_OK;
}
if (mLoadFlags & INHIBIT_CACHING) {
// respect demand not to cache
return NS_OK;
}
if (!mRequestHead.IsGet()) {
// only cache complete documents offline
return NS_OK;
}
rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
getter_AddRefs(cacheStorage));
NS_ENSURE_SUCCESS(rv, rv);
rv = cacheStorage->AsyncOpenURI(mURI, EmptyCString(),
nsICacheStorage::OPEN_TRUNCATE, this);
NS_ENSURE_SUCCESS(rv, rv);
waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
return NS_OK;
}
nsresult nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t* aSize,
int64_t* aContentLength) {
return nsHttp::CheckPartial(
aEntry, aSize, aContentLength,
mCachedResponseHead ? mCachedResponseHead : mResponseHead);
}
void nsHttpChannel::UntieValidationRequest() {
DebugOnly<nsresult> rv;
// Make the request unconditional again.
rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
MOZ_ASSERT(NS_SUCCEEDED(rv));
rv = mRequestHead.ClearHeader(nsHttp::ETag);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
NS_IMETHODIMP
nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry,
nsIApplicationCache* appCache,
uint32_t* aResult) {
nsresult rv = NS_OK;
LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
entry));
mozilla::MutexAutoLock lock(mRCWNLock);
if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
LOG(
("Not using cached response because we've already got one from the "
"network\n"));
*aResult = ENTRY_NOT_WANTED;
// Net-win indicates that mOnStartRequestTimestamp is from net.
int64_t savedTime =
(TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
savedTime);
return NS_OK;
} else if (mRaceCacheWithNetwork &&
mFirstResponseSource == RESPONSE_PENDING) {
mOnCacheEntryCheckTimestamp = TimeStamp::Now();
}
nsAutoCString cacheControlRequestHeader;
Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
cacheControlRequestHeader);
CacheControlParser cacheControlRequest(cacheControlRequestHeader);
if (cacheControlRequest.NoStore()) {
LOG(
("Not using cached response based on no-store request cache "
"directive\n"));
*aResult = ENTRY_NOT_WANTED;
return NS_OK;
}
// Be pessimistic: assume the cache entry has no useful data.
*aResult = ENTRY_WANTED;
mCachedContentIsValid = false;
nsCString buf;
// Get the method that was used to generate the cached response
rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
NS_ENSURE_SUCCESS(rv, rv);
bool methodWasHead = buf.EqualsLiteral("HEAD");
bool methodWasGet = buf.EqualsLiteral("GET");
if (methodWasHead) {
// The cached response does not contain an entity. We can only reuse
// the response if the current request is also HEAD.
if (!mRequestHead.IsHead()) {
*aResult = ENTRY_NOT_WANTED;
return NS_OK;
}
}
buf.Adopt(nullptr);
// We'll need this value in later computations...
uint32_t lastModifiedTime;
rv = entry->GetLastModified(&lastModifiedTime);
NS_ENSURE_SUCCESS(rv, rv);
// Determine if this is the first time that this cache entry
// has been accessed during this session.
bool fromPreviousSession =
(gHttpHandler->SessionStartTime() > lastModifiedTime);
// Get the cached HTTP response headers
mCachedResponseHead = new nsHttpResponseHead();
rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, mCachedResponseHead);
NS_ENSURE_SUCCESS(rv, rv);
bool isCachedRedirect = WillRedirect(mCachedResponseHead);
// Do not return 304 responses from the cache, and also do not return
// any other non-redirect 3xx responses from the cache (see bug 759043).
NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
NS_ERROR_ABORT);
if (mCachedResponseHead->NoStore() && mCacheEntryIsReadOnly) {
// This prevents loading no-store responses when navigating back
// while the browser is set to work offline.
LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
mLoadFlags |= nsIRequest::INHIBIT_CACHING;
}
// Don't bother to validate items that are read-only,
// unless they are read-only because of INHIBIT_CACHING or because
// we're updating the offline cache.
// Don't bother to validate if this is a fallback entry.
if (!mApplicationCacheForWrite &&
(appCache || (mCacheEntryIsReadOnly &&
!(mLoadFlags & nsIRequest::INHIBIT_CACHING)))) {
if (!appCache) {
int64_t size, contentLength;
rv = CheckPartial(entry, &size, &contentLength);
NS_ENSURE_SUCCESS(rv, rv);
if (contentLength != int64_t(-1) && contentLength != size) {
*aResult = ENTRY_NOT_WANTED;
return NS_OK;
}
}
rv = OpenCacheInputStream(entry, true, !!appCache);
if (NS_SUCCEEDED(rv)) {
mCachedContentIsValid = true;
entry->MaybeMarkValid();
}
return rv;
}
bool wantCompleteEntry = false;
if (!methodWasHead && !isCachedRedirect) {
// If the cached content-length is set and it does not match the data
// size of the cached content, then the cached response is partial...
// either we need to issue a byte range request or we need to refetch
// the entire document.
//
// We exclude redirects from this check because we (usually) strip the
// entity when we store the cache entry, and even if we didn't, we
// always ignore a cached redirect's entity anyway. See bug 759043.
int64_t size, contentLength;
rv = CheckPartial(entry, &size, &contentLength);
NS_ENSURE_SUCCESS(rv, rv);
if (size == int64_t(-1)) {
LOG((" write is in progress"));
if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
LOG(
(" not interested in the entry, "
"LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
*aResult = ENTRY_NOT_WANTED;
return NS_OK;
}
// Ignore !(size > 0) from the resumability condition
if (!IsResumable(size, contentLength, true)) {
if (IsNavigation()) {
LOG(
(" bypassing wait for the entry, "
"this is a navigational load"));
*aResult = ENTRY_NOT_WANTED;
return NS_OK;
}
LOG(
(" wait for entry completion, "
"response is not resumable"));
wantCompleteEntry = true;
} else {
mConcurrentCacheAccess = 1;
}
} else if (contentLength != int64_t(-1) && contentLength != size) {
LOG(
("Cached data size does not match the Content-Length header "
"[content-length=%" PRId64 " size=%" PRId64 "]\n",
contentLength, size));
rv = MaybeSetupByteRangeRequest(size, contentLength);
mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
if (mCachedContentIsPartial) {
rv = OpenCacheInputStream(entry, false, !!appCache);
if (NS_FAILED(rv)) {
UntieByteRangeRequest();
return rv;
}
*aResult = ENTRY_NEEDS_REVALIDATION;
return NS_OK;
}
if (size == 0 && mCacheOnlyMetadata) {
// Don't break cache entry load when the entry's data size
// is 0 and mCacheOnlyMetadata flag is set. In that case we
// want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
// also set.
MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
} else {
return rv;
}
}
}
bool isHttps = mURI->SchemeIs("https");
bool doValidation = false;
bool doBackgroundValidation = false;
bool canAddImsHeader = true;
bool isForcedValid = false;
entry->GetIsForcedValid(&isForcedValid);
bool weaklyFramed, isImmutable;
nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead, isHttps,
&weaklyFramed, &isImmutable);
// Cached entry is not the entity we request (see bug #633743)
if (ResponseWouldVary(entry)) {
LOG(("Validating based on Vary headers returning TRUE\n"));
canAddImsHeader = false;
doValidation = true;
} else {
doValidation = nsHttp::ValidationRequired(
isForcedValid, mCachedResponseHead, mLoadFlags, mAllowStaleCacheContent,
isImmutable, mCustomConditionalRequest, mRequestHead, entry,
cacheControlRequest, fromPreviousSession, &doBackgroundValidation);
}
nsAutoCString requestedETag;
if (!doValidation &&
NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
(methodWasGet || methodWasHead)) {
nsAutoCString cachedETag;
Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
if (!cachedETag.IsEmpty() &&
(StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
!requestedETag.Equals(cachedETag))) {
// User has defined If-Match header, if the cached entry is not
// matching the provided header value or the cached ETag is weak,
// force validation.
doValidation = true;
}
}
// Previous error should not be propagated.
rv = NS_OK;
if (!doValidation) {
//
// Check the authorization headers used to generate the cache entry.
// We must validate the cache entry if:
//
// 1) the cache entry was generated prior to this session w/
// credentials (see bug 103402).
// 2) the cache entry was generated w/o credentials, but would now
// require credentials (see bug 96705).
//
// NOTE: this does not apply to proxy authentication.
//
entry->GetMetaDataElement("auth", getter_Copies(buf));
doValidation =
(fromPreviousSession && !buf.IsEmpty()) ||
(buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
}
// Bug #561276: We maintain a chain of cache-keys which returns cached
// 3xx-responses (redirects) in order to detect cycles. If a cycle is
// found, ignore the cached response and hit the net. Otherwise, use
// the cached response and add the cache-key to the chain. Note that
// a limited number of redirects (cached or not) is allowed and is
// enforced independently of this mechanism
if (!doValidation && isCachedRedirect) {
nsAutoCString cacheKey;
rv = GenerateCacheKey(mPostID, cacheKey);
MOZ_ASSERT(NS_SUCCEEDED(rv));
if (!mRedirectedCachekeys)
mRedirectedCachekeys = new nsTArray<nsCString>();
else if (mRedirectedCachekeys->Contains(cacheKey))
doValidation = true;
LOG(("Redirection-chain %s key %s\n",
doValidation ? "contains" : "does not contain", cacheKey.get()));
// Append cacheKey if not in the chain already
if (!doValidation) mRedirectedCachekeys->AppendElement(cacheKey);
}
mCachedContentIsValid = !doValidation;
if (doValidation) {
//
// now, we are definitely going to issue a HTTP request to the server.
// make it conditional if possible.
//
// do not attempt to validate no-store content, since servers will not
// expect it to be cached. (we only keep it in our cache for the
// purposes of back/forward, etc.)
//
// the request method MUST be either GET or HEAD (see bug 175641) and
// the cached response code must be < 400
//
// the cached content must not be weakly framed or marked immutable
//
// do not override conditional headers when consumer has defined its own
if (!mCachedResponseHead->NoStore() &&
(mRequestHead.IsGet() || mRequestHead.IsHead()) &&
!mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
(mCachedResponseHead->Status() < 400)) {
if (mConcurrentCacheAccess) {
// In case of concurrent read and also validation request we
// must wait for the current writer to close the output stream
// first. Otherwise, when the writer's job would have been interrupted
// before all the data were downloaded, we'd have to do a range request
// which would be a second request in line during this channel's
// life-time. nsHttpChannel is not designed to do that, so rather
// turn off concurrent read and wait for entry's completion.
// Then only re-validation or range-re-validation request will go out.
mConcurrentCacheAccess = 0;
// This will cause that OnCacheEntryCheck is called again with the same
// entry after the writer is done.
wantCompleteEntry = true;
} else {
nsAutoCString val;
// Add If-Modified-Since header if a Last-Modified was given
// and we are allowed to do this (see bugs 510359 and 269303)
if (canAddImsHeader) {
Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
if (!val.IsEmpty()) {
rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
}
// Add If-None-Match header if an ETag was given in the response
Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
if (!val.IsEmpty()) {
rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
MOZ_ASSERT(NS_SUCCEEDED(rv));
}
mDidReval = true;
}
}
}
if (mCachedContentIsValid || mDidReval) {
rv = OpenCacheInputStream(entry, mCachedContentIsValid, !!appCache);
if (NS_FAILED(rv)) {
// If we can't get the entity then we have to act as though we
// don't have the cache entry.
if (mDidReval) {
UntieValidationRequest();
mDidReval = false;
}
mCachedContentIsValid = false;
}
}
if (mDidReval)
*aResult = ENTRY_NEEDS_REVALIDATION;
else if (wantCompleteEntry)
*aResult = RECHECK_AFTER_WRITE_FINISHED;
else {
*aResult = ENTRY_WANTED;
if (doBackgroundValidation) {
PerformBackgroundCacheRevalidation();
}
}
if (mCachedContentIsValid) {
entry->MaybeMarkValid();
}
LOG(
("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
"result=%d]\n",
this, doValidation, *aResult));
return rv;
}
NS_IMETHODIMP
nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry* entry, bool aNew,
nsIApplicationCache* aAppCache,
nsresult status) {
MOZ_ASSERT(NS_IsMainThread());
nsresult rv;
LOG(
("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
"new=%d appcache=%p status=%" PRIx32
" mAppCache=%p mAppCacheForWrite=%p]\n",
this, entry, aNew, aAppCache, static_cast<uint32_t>(status),
mApplicationCache.get(), mApplicationCacheForWrite.get()));
// if the channel's already fired onStopRequest, then we should ignore
// this event.
if (!mIsPending) {
mCacheInputStream.CloseAndRelease();
return NS_OK;
}
rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
if (NS_FAILED(rv)) {
CloseCacheEntry(false);
if (mRaceCacheWithNetwork && mNetworkTriggered &&
mFirstResponseSource != RESPONSE_FROM_CACHE) {
// Ignore the error if we're racing cache with network and the cache
// didn't win, The network part will handle cancelation or any other
// error. Otherwise we could end up calling the listener twice, see
// bug 1397593.
LOG(
(" not calling AsyncAbort() because we're racing cache with "
"network"));
} else {
Unused << AsyncAbort(rv);
}
}
return NS_OK;
}
nsresult nsHttpChannel::OnCacheEntryAvailableInternal(
nsICacheEntry* entry, bool aNew, nsIApplicationCache* aAppCache,
nsresult status) {
nsresult rv;
if (mCanceled) {
LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
return mStatus;
}
if (mIgnoreCacheEntry) {
if (!entry || aNew) {
// We use this flag later to decide whether to report
// LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
// an usable entry, so drop the flag.
mIgnoreCacheEntry = false;
}
entry = nullptr;
status = NS_ERROR_NOT_AVAILABLE;
}
if (aAppCache) {
if (mApplicationCache == aAppCache && !mCacheEntry) {
rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
} else if (mApplicationCacheForWrite == aAppCache && aNew &&
!mOfflineCacheEntry) {
rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
} else {
rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
}
} else {
rv = OnNormalCacheEntryAvailable(entry, aNew, status);
}
if (NS_FAILED(rv) && (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;
}
if (NS_FAILED(rv)) {
return rv;
}
// We may be waiting for more callbacks...
if (AwaitingCacheCallbacks()) {
return NS_OK;
}
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);
}
if (mRaceCacheWithNetwork && mCachedContentIsValid) {
Unused << ReadFromCache(true);
}
return TriggerNetwork();
}
nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry* aEntry,
bool aNew,
nsresult aEntryStatus) {
mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
if (NS_FAILED(aEntryStatus) || aNew) {
// Make sure this flag is dropped. It may happen the entry is doomed
// between OnCacheEntryCheck and OnCacheEntryAvailable.
mCachedContentIsValid = false;
// From the same reason remove any conditional headers added
// in OnCacheEntryCheck.
if (mDidReval) {
LOG((" Removing conditional request headers"));
UntieValidationRequest();
mDidReval = false;
}
if (mCachedContentIsPartial) {
LOG((" Removing byte range request headers"));
UntieByteRangeRequest();
mCachedContentIsPartial = false;
}
if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
// if this channel is only allowed to pull from the cache, then
// we must fail if we were unable to open a cache entry for read.
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
}
if (NS_SUCCEEDED(aEntryStatus)) {
mCacheEntry = aEntry;
mCacheEntryIsWriteOnly = aNew;
if (!aNew && !mAsyncOpenTime.IsNull()) {
// We use microseconds for IO operations. For consistency let's use
// microseconds here too.
uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
bool isSlow = false;
if ((mCacheOpenWithPriority &&
mCacheQueueSizeWhenOpen >= sRCWNQueueSizePriority) ||
(!mCacheOpenWithPriority &&
mCacheQueueSizeWhenOpen >= sRCWNQueueSizeNormal)) {
isSlow = true;
}
CacheFileUtils::CachePerfStats::AddValue(
CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
}
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false);
}
}
return NS_OK;
}
nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(
nsICacheEntry* aEntry, bool aNew, nsIApplicationCache* aAppCache,
nsresult aEntryStatus) {
MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
nsresult rv;
if (NS_SUCCEEDED(aEntryStatus)) {
if (!mApplicationCache) {
mApplicationCache = aAppCache;
}
// We successfully opened an offline cache session and the entry,
// so indicate we will load from the offline cache.
mLoadedFromApplicationCache = true;
mCacheEntryIsReadOnly = true;
mCacheEntry = aEntry;
mCacheEntryIsWriteOnly = false;
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
MaybeWarnAboutAppCache();
}
return NS_OK;
}
if (!mApplicationCacheForWrite && !mFallbackChannel) {
if (!mApplicationCache) {
mApplicationCache = aAppCache;
}
// Check for namespace match.
nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
rv = mApplicationCache->GetMatchingNamespace(
mSpec, getter_AddRefs(namespaceEntry));
NS_ENSURE_SUCCESS(rv, rv);
uint32_t namespaceType = 0;
if (!namespaceEntry ||
NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
(namespaceType & (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) ==
0) {
// When loading from an application cache, only items
// on the whitelist or matching a
// fallback namespace should hit the network...
mLoadFlags |= LOAD_ONLY_FROM_CACHE;
// ... and if there were an application cache entry,
// we would have found it earlier.
return NS_ERROR_CACHE_KEY_NOT_FOUND;
}
if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
nsAutoCString namespaceSpec;
rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
NS_ENSURE_SUCCESS(rv, rv);
// This prevents fallback attacks injected by an insecure subdirectory
// for the whole origin (or a parent directory).
if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
return NS_OK;
}
rv = namespaceEntry->GetData(mFallbackKey);
NS_ENSURE_SUCCESS(rv, rv);
}
if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) {
LOG(
("nsHttpChannel::OnOfflineCacheEntryAvailable this=%p, URL matches "
"NETWORK,"
" looking for a regular cache entry",
this));
bool isHttps = mURI->SchemeIs("https");
rv = OpenCacheEntryInternal(isHttps, nullptr,
false /* don't allow appcache lookups */);
if (NS_FAILED(rv)) {
// Don't let this fail when cache entry can't be synchronously open.
// We want to go forward even without a regular cache entry.
return NS_OK;
}
}
}
return NS_OK;
}
nsresult nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(
nsICacheEntry* aEntry, nsIApplicationCache* aAppCache,
nsresult aEntryStatus) {
MOZ_ASSERT(mApplicationCacheForWrite &&
aAppCache == mApplicationCacheForWrite);
mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
if (NS_SUCCEEDED(aEntryStatus)) {
mOfflineCacheEntry = aEntry;
if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
mOfflineCacheLastModifiedTime = 0;
}
}
return aEntryStatus;
}
// Generates the proper cache-key for this instance of nsHttpChannel
nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
nsACString& cacheKey) {
AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(), postID,
cacheKey);
return NS_OK;
}
// Assembles a cache-key from the given pieces of information and |mLoadFlags|
void nsHttpChannel::AssembleCacheKey(const char* spec, uint32_t postID,
nsACString& cacheKey) {
cacheKey.Truncate();
if (mLoadFlags & LOAD_ANONYMOUS) {
cacheKey.AssignLiteral("anon&");
}
if (postID) {
char buf[32];
SprintfLiteral(buf, "id=%x&", postID);
cacheKey.Append(buf);
}
if (!cacheKey.IsEmpty()) {
cacheKey.AppendLiteral("uri=");
}
// Strip any trailing #ref from the URL before using it as the key
const char* p = strchr(spec, '#');
if (p)
cacheKey.Append(spec, p - spec);
else
cacheKey.Append(spec);
}
nsresult DoUpdateExpirationTime(nsHttpChannel* aSelf,
nsICacheEntry* aCacheEntry,
nsHttpResponseHead* aResponseHead,
uint32_t& aExpirationTime) {
MOZ_ASSERT(aExpirationTime == 0);
NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
nsresult rv;
if (!aResponseHead->MustValidate()) {
// For stale-while-revalidate we use expiration time as the absolute base
// for calculation of the stale window absolute end time. Hence, when the
// entry may be served w/o revalidation, we need a non-zero value for the
// expiration time. Let's set it to |now|, which basicly means "expired",
// same as when set to 0.
uint32_t now = NowInSeconds();
aExpirationTime = now;
uint32_t freshnessLifetime = 0;
rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
if (NS_FAILED(rv)) return rv;
if (freshnessLifetime > 0) {
uint32_t currentAge = 0;
rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
¤tAge);
if (NS_FAILED(rv)) return rv;
LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
currentAge));
if (freshnessLifetime > currentAge) {
uint32_t timeRemaining = freshnessLifetime - currentAge;
// be careful... now + timeRemaining may overflow
if (now + timeRemaining < now) {
aExpirationTime = uint32_t(-1);
} else {
aExpirationTime = now + timeRemaining;
}
}
}
}
rv = aCacheEntry->SetExpirationTime(aExpirationTime);
NS_ENSURE_SUCCESS(rv, rv);
return rv;
}
// UpdateExpirationTime is called when a new response comes in from the server.
// It updates the stored response-time and sets the expiration time on the
// cache entry.
//
// From section 13.2.4 of RFC2616, we compute expiration time as follows:
//
// timeRemaining = freshnessLifetime - currentAge
// expirationTime = now + timeRemaining
//
nsresult nsHttpChannel::UpdateExpirationTime() {
uint32_t expirationTime = 0;
nsresult rv =
DoUpdateExpirationTime(this, mCacheEntry, mResponseHead, expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
if (mOfflineCacheEntry) {
rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
bool nsHttpChannel::ShouldUpdateOfflineCacheEntry() {
if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
return false;
}
// if we're updating the cache entry, update the offline cache entry too
if (mCacheEntry && mCacheEntryIsWriteOnly) {
return true;
}
// if there's nothing in the offline cache, add it
if (mOfflineCacheEntry) {
return true;
}
// if the document is newer than the offline entry, update it
uint32_t docLastModifiedTime;
nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
if (NS_FAILED(rv)) {
return true;
}
if (mOfflineCacheLastModifiedTime == 0) {
return false;
}
if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
return true;
}
return false;
}
nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry,
bool startBuffering,
bool checkingAppCacheEntry) {
nsresult rv;
if (mURI->SchemeIs("https")) {
rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
if (NS_FAILED(rv)) {
LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
cacheEntry));
NS_WARNING("failed to parse security-info");
cacheEntry->AsyncDoom(nullptr);
return rv;
}
// XXX: We should not be skilling this check in the offline cache
// case, but we have to do so now to work around bug 794507.
bool mustHaveSecurityInfo =
!mLoadedFromApplicationCache && !checkingAppCacheEntry;
MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
LOG(
("mCacheEntry->GetSecurityInfo returned success but did not "
"return the security info [channel=%p, entry=%p]",
this, cacheEntry));
cacheEntry->AsyncDoom(nullptr);
return NS_ERROR_UNEXPECTED; // XXX error code
}
}
// Keep the conditions below in sync with the conditions in ReadFromCache.
rv = NS_OK;
if (WillRedirect(mCachedResponseHead)) {
// Do not even try to read the entity for a redirect because we do not
// return an entity to the application when we process redirects.
LOG(("Will skip read of cached redirect entity\n"));
return NS_OK;
}
if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
!mCachedContentIsPartial) {
// For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
// cached entity.
if (!mApplicationCacheForWrite) {
LOG(
("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
"load flag\n"));
return NS_OK;
}
// If offline caching has been requested and the offline cache needs
// updating, we must complete the call even if the main cache entry
// is up to date. We don't know yet for sure whether the offline
// cache needs updating because at this point we haven't opened it
// for writing yet, so we have to start reading the cached entity now
// just in case.
LOG(
("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
"load flag\n"));
}
// Open an input stream for the entity, so that the call to OpenInputStream
// happens off the main thread.
nsCOMPtr<nsIInputStream> stream;
// If an alternate representation was requested, try to open the alt
// input stream.
// If the entry has a "is-from-child" metadata, then only open the altdata
// stream if the consumer is also from child.
bool altDataFromChild = false;
{
nsCString value;
rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
getter_Copies(value));
altDataFromChild = !value.IsEmpty();
}
nsAutoCString altDataType;
Unused << cacheEntry->GetAltDataType(altDataType);
nsAutoCString contentType;
mCachedResponseHead->ContentType(contentType);
bool foundAltData = false;
bool deliverAltData = true;
if (!altDataType.IsEmpty() && !mPreferredCachedAltDataTypes.IsEmpty() &&
altDataFromChild == mAltDataForChild) {
for (auto& pref : mPreferredCachedAltDataTypes) {
if (pref.type() == altDataType &&
(pref.contentType().IsEmpty() || pref.contentType() == contentType)) {
foundAltData = true;
deliverAltData = pref.deliverAltData();
break;
}
}
}
nsCOMPtr<nsIInputStream> altData;
int64_t altDataSize;
if (foundAltData) {
rv = cacheEntry->OpenAlternativeInputStream(altDataType,
getter_AddRefs(altData));
if (NS_SUCCEEDED(rv)) {
LOG(("Opened alt-data input stream type=%s", altDataType.get()));
// We have succeeded.
mAvailableCachedAltDataType = altDataType;
mDeliveringAltData = deliverAltData;
// Set the correct data size on the channel.
Unused << cacheEntry->GetAltDataSize(&altDataSize);
mAltDataLength = altDataSize;
if (deliverAltData) {
stream = altData;
}
}
}
if (!stream) {
rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
}
if (NS_FAILED(rv)) {
LOG(
("Failed to open cache input stream [channel=%p, "
"mCacheEntry=%p]",
this, cacheEntry));
return rv;
}
if (startBuffering) {
bool nonBlocking;
rv = stream->IsNonBlocking(&nonBlocking);
if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
}
if (!startBuffering) {
// Bypass wrapping the input stream for the new cache back-end since
// nsIStreamTransportService expects a blocking stream. Preloading of
// the data must be done on the level of the cache backend, internally.
//
// We do not connect the stream to the stream transport service if we
// have to validate the entry with the server. If we did, we would get
// into a race condition between the stream transport service reading
// the existing contents and the opening of the cache entry's output
// stream to write the new contents in the case where we get a non-304
// response.
LOG(
("Opened cache input stream without buffering [channel=%p, "
"mCacheEntry=%p, stream=%p]",
this, cacheEntry, stream.get()));
mCacheInputStream.takeOver(stream);
return rv;
}
// Have the stream transport service start reading the entity on one of its
// background threads.
nsCOMPtr<nsITransport> transport;
nsCOMPtr<nsIInputStream> wrapper;
nsCOMPtr<nsIStreamTransportService> sts(
services::GetStreamTransportService());
rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
if (NS_SUCCEEDED(rv)) {
rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
}
if (NS_SUCCEEDED(rv)) {
rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
}
if (NS_SUCCEEDED(rv)) {
LOG(
("Opened cache input stream [channel=%p, wrapper=%p, "
"transport=%p, stream=%p]",
this, wrapper.get(), transport.get(), stream.get()));
} else {
LOG(
("Failed to open cache input stream [channel=%p, "
"wrapper=%p, transport=%p, stream=%p]",
this, wrapper.get(), transport.get(), stream.get()));
stream->Close();
return rv;
}
mCacheInputStream.takeOver(wrapper);
return NS_OK;
}
// Actually process the cached response that we started to handle in CheckCache
// and/or StartBufferingCachedEntity.
nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(!mCachePump, NS_OK); // already opened
LOG(
("nsHttpChannel::ReadFromCache [this=%p] "
"Using cached copy of: %s\n",
this, mSpec.get()));
// When racing the cache with the network with a timer, and we get data from
// the cache, we should prevent the timer from triggering a network request.
if (mNetworkTriggerTimer) {
mNetworkTriggerTimer->Cancel();
mNetworkTriggerTimer = nullptr;
}
if (mRaceCacheWithNetwork) {
MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
if (mFirstResponseSource == RESPONSE_PENDING) {
LOG(("First response from cache\n"));
mFirstResponseSource = RESPONSE_FROM_CACHE;
// Cancel the transaction because we will serve the request from the cache
CancelNetworkRequest(NS_BINDING_ABORTED);
if (mTransactionPump && mSuspendCount) {
uint32_t suspendCount = mSuspendCount;
while (suspendCount--) {
mTransactionPump->Resume();
}
}
mTransaction = nullptr;
mTransactionPump = nullptr;
} else {
MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
LOG(
("Skipping read from cache because first response was from "
"network\n"));
if (!mOnCacheEntryCheckTimestamp.IsNull()) {
TimeStamp currentTime = TimeStamp::Now();
int64_t savedTime =
(currentTime - mOnStartRequestTimestamp).ToMilliseconds();
Telemetry::Accumulate(
Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
int64_t diffTime =
(currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
Telemetry::Accumulate(
Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
diffTime);
}
return NS_OK;
}
}
if (mCachedResponseHead) mResponseHead = std::move(mCachedResponseHead);
UpdateInhibitPersistentCachingFlag();
// if we don't already have security info, try to get it from the cache
// entry. there are two cases to consider here: 1) we are just reading
// from the cache, or 2) this may be due to a 304 not modified response,
// in which case we could have security info from a socket transport.
if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;
if (!alreadyMarkedValid && !mCachedContentIsPartial) {
// We validated the entry, and we have write access to the cache, so
// mark the cache entry as valid in order to allow others access to
// this cache entry.
//
// TODO: This should be done asynchronously so we don't take the cache
// service lock on the main thread.
mCacheEntry->MaybeMarkValid();
}
nsresult rv;
// Keep the conditions below in sync with the conditions in
// StartBufferingCachedEntity.
if (WillRedirect(mResponseHead)) {
// TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
// to avoid event dispatching latency.
MOZ_ASSERT(!mCacheInputStream);
LOG(("Skipping skip read of cached redirect entity\n"));
return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
}
if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
if (!mApplicationCacheForWrite) {
LOG(
("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
"load flag\n"));
MOZ_ASSERT(!mCacheInputStream);
// TODO: Bug 759040 - We should call HandleAsyncNotModified directly
// here, to avoid event dispatching latency.
return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
}
if (!ShouldUpdateOfflineCacheEntry()) {
LOG(
("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
"load flag (mApplicationCacheForWrite not null case)\n"));
mCacheInputStream.CloseAndRelease();
// TODO: Bug 759040 - We should call HandleAsyncNotModified directly
// here, to avoid event dispatching latency.
return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
}
}
MOZ_ASSERT(mCacheInputStream);
if (!mCacheInputStream) {
NS_ERROR(
"mCacheInputStream is null but we're expecting to "
"be able to read from it.");
return NS_ERROR_UNEXPECTED;
}
nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
true);
if (NS_FAILED(rv)) {
inputStream->Close();
return rv;
}
rv = mCachePump->AsyncRead(this, nullptr);
if (NS_FAILED(rv)) return rv;
if (mTimingEnabled) mCacheReadStart = TimeStamp::Now();
uint32_t suspendCount = mSuspendCount;
if (mAsyncResumePending) {
LOG(
(" Suspend()'ing cache pump once because of async resume pending"
", sc=%u, pump=%p, this=%p",
suspendCount, mCachePump.get(), this));
++suspendCount;
}
while (suspendCount--) {
mCachePump->Suspend();
}
return NS_OK;
}
void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
mCacheInputStream.CloseAndRelease();
if (!mCacheEntry) return;
LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
" mCacheEntryIsWriteOnly=%x",
this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
mCacheEntryIsWriteOnly));
// If we have begun to create or replace a cache entry, and that cache
// entry is not complete and not resumable, then it needs to be doomed.
// Otherwise, CheckCache will make the mistake of thinking that the
// partial cache entry is complete.
bool doom = false;
if (mInitedCacheEntry) {
MOZ_ASSERT(mResponseHead, "oops");
if (NS_FAILED(mStatus) && doomOnFailure && mCacheEntryIsWriteOnly &&
!mResponseHead->IsResumable())
doom = true;
} else if (mCacheEntryIsWriteOnly)
doom = true;
if (doom) {
LOG((" dooming cache entry!!"));
mCacheEntry->AsyncDoom(nullptr);
} else {
// Store updated security info, makes cached EV status race less likely
// (see bug 1040086)
if (mSecurityInfo) mCacheEntry->SetSecurityInfo(mSecurityInfo);
}
mCachedResponseHead = nullptr;
mCachePump = nullptr;
// This releases the entry for other consumers to use.
// We call Dismiss() in case someone still keeps a reference
// to this entry handle.
mCacheEntry->Dismiss();
mCacheEntry = nullptr;
mCacheEntryIsWriteOnly = false;
mInitedCacheEntry = false;
}
void nsHttpChannel::CloseOfflineCacheEntry() {
if (!mOfflineCacheEntry) return;
LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
if (NS_FAILED(mStatus)) {
mOfflineCacheEntry->AsyncDoom(nullptr);
} else {
bool succeeded;
if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
mOfflineCacheEntry->AsyncDoom(nullptr);
}
mOfflineCacheEntry = nullptr;
}
// Initialize the cache entry for writing.
// - finalize storage policy
// - store security info
// - update expiration time
// - store headers and other meta data
nsresult nsHttpChannel::InitCacheEntry() {
nsresult rv;
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
// if only reading, nothing to be done here.
if (mCacheEntryIsReadOnly) return NS_OK;
// Don't cache the response again if already cached...
if (mCachedContentIsValid) return NS_OK;
LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
mCacheEntry.get()));
bool recreate = !mCacheEntryIsWriteOnly;
bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
if (!recreate && dontPersist) {
// If the current entry is persistent but we inhibit peristence
// then force recreation of the entry as memory/only.
rv = mCacheEntry->GetPersistent(&recreate);
if (NS_FAILED(rv)) return rv;
}
if (recreate) {
LOG(
(" we have a ready entry, but reading it again from the server -> "
"recreating cache entry\n"));
// clean the altData cache and reset this to avoid wrong content length
mAvailableCachedAltDataType.Truncate();
mDeliveringAltData = false;
nsCOMPtr<nsICacheEntry> currentEntry;
currentEntry.swap(mCacheEntry);
rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
if (NS_FAILED(rv)) {
LOG((" recreation failed, the response will not be cached"));
return NS_OK;
}
mCacheEntryIsWriteOnly = true;
}
// Set the expiration time for this cache entry
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
// mark this weakly framed until a response body is seen
mCacheEntry->SetMetaDataElement("strongly-framed", "0");
rv = AddCacheEntryHeaders(mCacheEntry);
if (NS_FAILED(rv)) return rv;
mInitedCacheEntry = true;
// Don't perform the check when writing (doesn't make sense)
mConcurrentCacheAccess = 0;
return NS_OK;
}
void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
// The no-store directive within the 'Cache-Control:' header indicates
// that we must not store the response in a persistent cache.
if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
// Only cache SSL content on disk if the pref is set
if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
mURI->SchemeIs("https")) {
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
}
}
nsresult nsHttpChannel::InitOfflineCacheEntry() {
// This function can be called even when we fail to connect (bug 551990)
if (!mOfflineCacheEntry) {
return NS_OK;
}
if (!mResponseHead || mResponseHead->NoStore()) {
if (mResponseHead && mResponseHead->NoStore()) {
mOfflineCacheEntry->AsyncDoom(nullptr);
}
CloseOfflineCacheEntry();
if (mResponseHead && mResponseHead->NoStore()) {
return NS_ERROR_NOT_AVAILABLE;
}
return NS_OK;
}
// This entry's expiration time should match the main entry's expiration
// time. UpdateExpirationTime() will keep it in sync once the offline
// cache entry has been created.
if (mCacheEntry) {
uint32_t expirationTime;
nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
NS_ENSURE_SUCCESS(rv, rv);
mOfflineCacheEntry->SetExpirationTime(expirationTime);
}
return AddCacheEntryHeaders(mOfflineCacheEntry);
}
nsresult DoAddCacheEntryHeaders(nsHttpChannel* self, nsICacheEntry* entry,
nsHttpRequestHead* requestHead,
nsHttpResponseHead* responseHead,
nsISupports* securityInfo) {
nsresult rv;
LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
// Store secure data in memory only
if (securityInfo) entry->SetSecurityInfo(securityInfo);
// Store the HTTP request method with the cache entry so we can distinguish
// for example GET and HEAD responses.
nsAutoCString method;
requestHead->Method(method);
rv = entry->SetMetaDataElement("request-method", method.get());
if (NS_FAILED(rv)) return rv;
// Store the HTTP authorization scheme used if any...
rv = StoreAuthorizationMetaData(entry, requestHead);
if (NS_FAILED(rv)) return rv;
// Iterate over the headers listed in the Vary response header, and
// store the value of the corresponding request header so we can verify
// that it has not varied when we try to re-use the cached response at
// a later time. Take care to store "Cookie" headers only as hashes
// due to security considerations and the fact that they can be pretty
// large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
//
// NOTE: if "Vary: accept, cookie", then we will store the "accept" header
// in the cache. we could try to avoid needlessly storing the "accept"
// header in this case, but it doesn't seem worth the extra code to perform
// the check.
{
nsAutoCString buf, metaKey;
Unused << responseHead->GetHeader(nsHttp::Vary, buf);
if (!buf.IsEmpty()) {
NS_NAMED_LITERAL_CSTRING(prefix, "request-");
char* bufData = buf.BeginWriting(); // going to munge buf
char* token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
while (token) {
LOG(
("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
"processing %s",
self, token));
if (*token != '*') {
nsHttpAtom atom = nsHttp::ResolveAtom(token);
nsAutoCString val;
nsAutoCString hash;
if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
// If cookie-header, store a hash of the value
if (atom == nsHttp::Cookie) {
LOG(
("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
"cookie-value %s",
self, val.get()));
rv = Hash(val.get(), hash);
// If hash failed, store a string not very likely
// to be the result of subsequent hashes
if (NS_FAILED(rv)) {
val = NS_LITERAL_CSTRING("<hash failed>");
} else {
val = hash;
}
LOG((" hashed to %s\n", val.get()));
}
// build cache meta data key and set meta data element...
metaKey = prefix + nsDependentCString(token);
entry->SetMetaDataElement(metaKey.get(), val.get());
} else {
LOG(
("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
"clearing metadata for %s",
self, token));
metaKey = prefix + nsDependentCString(token);
entry->SetMetaDataElement(metaKey.get(), nullptr);
}
}
token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
}
}
}
// Store the received HTTP head with the cache entry as an element of
// the meta data.
nsAutoCString head;
responseHead->Flatten(head, true);
rv = entry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
head.Truncate();
responseHead->FlattenNetworkOriginalHeaders(head);
rv = entry->SetMetaDataElement("original-response-headers", head.get());
if (NS_FAILED(rv)) return rv;
// Indicate we have successfully finished setting metadata on the cache entry.
rv = entry->MetaDataReady();
return rv;
}
nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry* entry) {
return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead,
mSecurityInfo);
}
inline void GetAuthType(const char* challenge, nsCString& authType) {
const char* p;
// get the challenge type
if ((p = strchr(challenge, ' ')) != nullptr)
authType.Assign(challenge, p - challenge);
else
authType.Assign(challenge);
}
nsresult StoreAuthorizationMetaData(nsICacheEntry* entry,
nsHttpRequestHead* requestHead) {
// Not applicable to proxy authorization...
nsAutoCString val;
if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
return NS_OK;
}
// eg. [Basic realm="wally world"]
nsAutoCString buf;
GetAuthType(val.get(), buf);
return entry->SetMetaDataElement("auth", buf.get());
}
// Finalize the cache entry
// - may need to rewrite response headers if any headers changed
// - may need to recalculate the expiration time if any headers changed
// - called only for freshly written cache entries
nsresult nsHttpChannel::FinalizeCacheEntry() {
LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
// Don't update this meta-data on 304
if (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
this));
mCacheEntry->SetMetaDataElement("strongly-framed", "1");
}
if (mResponseHead && mResponseHeadersModified) {
// Set the expiration time for this cache entry
nsresult rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
// Open an output stream to the cache entry and insert a listener tee into
// the chain of response listeners.
nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
nsresult rv;
LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
MOZ_ASSERT(mCacheEntry);
MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial ||
mRaceCacheWithNetwork);
MOZ_ASSERT(mListener);
nsAutoCString contentEncoding, contentType;
Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
mResponseHead->ContentType(contentType);
// If the content is compressible and the server has not compressed it,
// mark the cache entry for compression.
if (contentEncoding.IsEmpty() &&
(contentType.EqualsLiteral(TEXT_HTML) ||
contentType.EqualsLiteral(TEXT_PLAIN) ||
contentType.EqualsLiteral(TEXT_CSS) ||
contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
contentType.EqualsLiteral(TEXT_XML) ||
contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
if (NS_FAILED(rv)) {
LOG(("unable to mark cache entry for compression"));
}
}
LOG(("Trading cache input stream for output stream [channel=%p]", this));
// We must close the input stream first because cache entries do not
// correctly handle having an output stream and input streams open at
// the same time.
mCacheInputStream.CloseAndRelease();
int64_t predictedSize = mResponseHead->TotalEntitySize();
if (predictedSize != -1) {
predictedSize -= offset;
}
nsCOMPtr<nsIOutputStream> out;
rv =
mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
if (rv == NS_ERROR_NOT_AVAILABLE) {
LOG((" entry doomed, not writing it [channel=%p]", this));
// Entry is already doomed.
// This may happen when expiration time is set to past and the entry
// has been removed by the background eviction logic.
return NS_OK;
}
if (rv == NS_ERROR_FILE_TOO_BIG) {
LOG((" entry would exceed max allowed size, not writing it [channel=%p]",
this));
mCacheEntry->AsyncDoom(nullptr);
return NS_OK;
}
if (NS_FAILED(rv)) return rv;
if (mCacheOnlyMetadata) {
LOG(("Not storing content, cacheOnlyMetadata set"));
// We must open and then close the output stream of the cache entry.
// This way we indicate the content has been written (despite with zero
// length) and the entry is now in the ready state with "having data".
out->Close();
return NS_OK;
}
// XXX disk cache does not support overlapped i/o yet
#if 0
// Mark entry valid inorder to allow simultaneous reading...
rv = mCacheEntry->MarkValid();
if (NS_FAILED(rv)) return rv;
#endif
nsCOMPtr<nsIStreamListenerTee> tee =
do_CreateInstance(kStreamListenerTeeCID, &rv);
if (NS_FAILED(rv)) return rv;
LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
static_cast<uint32_t>(rv)));
rv = tee->Init(mListener, out, nullptr);
if (NS_FAILED(rv)) return rv;
mListener = tee;
return NS_OK;
}
nsresult nsHttpChannel::InstallOfflineCacheListener(int64_t offset) {
nsresult rv;
LOG(("Preparing to write data into the offline cache [uri=%s]\n",
mSpec.get()));
MOZ_ASSERT(mOfflineCacheEntry);
MOZ_ASSERT(mListener);
nsCOMPtr<nsIOutputStream> out;
rv = mOfflineCacheEntry->OpenOutputStream(offset, -1, getter_AddRefs(out));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStreamListenerTee> tee =
do_CreateInstance(kStreamListenerTeeCID, &rv);
if (NS_FAILED(rv)) return rv;
rv = tee->Init(mListener, out, nullptr);
if (NS_FAILED(rv)) return rv;
mListener = tee;
return NS_OK;
}
void nsHttpChannel::ClearBogusContentEncodingIfNeeded() {
// For .gz files, apache sends both a Content-Type: application/x-gzip
// as well as Content-Encoding: gzip, which is completely wrong. In
// this case, we choose to ignore the rogue Content-Encoding header. We
// must do this early on so as to prevent it from being seen up stream.
// The same problem exists for Content-Encoding: compress in default
// Apache installs.
nsAutoCString contentType;
mResponseHead->ContentType(contentType);
if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") &&
(contentType.EqualsLiteral(APPLICATION_GZIP) ||
contentType.EqualsLiteral(APPLICATION_GZIP2) ||
contentType.EqualsLiteral(APPLICATION_GZIP3))) {
// clear the Content-Encoding header
mResponseHead->ClearHeader(nsHttp::Content_Encoding);
} else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding,
"compress") &&
(contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
// clear the Content-Encoding header
mResponseHead->ClearHeader(nsHttp::Content_Encoding);
}
}
//-----------------------------------------------------------------------------
// nsHttpChannel <redirect>
//-----------------------------------------------------------------------------
nsresult nsHttpChannel::SetupReplacementChannel(nsIURI* newURI,
nsIChannel* newChannel,
bool preserveMethod,
uint32_t redirectFlags) {
LOG(
("nsHttpChannel::SetupReplacementChannel "
"[this=%p newChannel=%p preserveMethod=%d]",
this, newChannel, preserveMethod));
nsresult rv = HttpBaseChannel::SetupReplacementChannel(
newURI, newChannel, preserveMethod, redirectFlags);
if (NS_FAILED(rv)) return rv;
rv = CheckRedirectLimit(redirectFlags);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
if (!httpChannel) return NS_OK; // no other options to set
// convey the mApplyConversion flag (bug 91862)
nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
if (encodedChannel) encodedChannel->SetApplyConversion(mApplyConversion);
// transfer the resume information
if (mResuming) {
nsCOMPtr<nsIResumableChannel> resumableChannel(
do_QueryInterface(newChannel));
if (!resumableChannel) {
NS_WARNING(
"Got asked to resume, but redirected to non-resumable channel!");
return NS_ERROR_NOT_RESUMABLE;
}
resumableChannel->ResumeAt(mStartPos, mEntityID);
}
nsCOMPtr<nsIHttpChannelInternal> internalChannel =
do_QueryInterface(newChannel, &rv);
if (NS_SUCCEEDED(rv)) {
TimeStamp timestamp;
rv = GetNavigationStartTimeStamp(×tamp);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (timestamp) {
Unused << internalChannel->SetNavigationStartTimeStamp(timestamp);
}
}
return NS_OK;
}
nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
redirectType));
nsAutoCString location;
// if a location header was not given, then we can't perform the redirect,
// so just carry on as though this were a normal response.
if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location)))
return NS_ERROR_FAILURE;
// If we were told to not follow redirects automatically, then again
// carry on as though this were a normal response.
if (mLoadInfo && mLoadInfo->GetDontFollowRedirects()) {
return NS_ERROR_FAILURE;
}
// make sure non-ASCII characters in the location header are escaped.
nsAutoCString locationBuf;
if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
locationBuf))
location = locationBuf;
mRedirectType = redirectType;
LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
uint32_t(mRedirectionLimit)));
nsresult rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
if (NS_FAILED(rv)) {
LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
return NS_ERROR_CORRUPTED_CONTENT;
}
if (mApplicationCache) {
// if we are redirected to a different origin check if there is a fallback
// cache entry to fall back to. we don't care about file strict
// checking, at least mURI is not a file URI.
if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
PushRedirectAsyncFunc(
&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
bool waitingForRedirectCallback;
Unused << ProcessFallback(&waitingForRedirectCallback);
if (waitingForRedirectCallback) return NS_OK;
PopRedirectAsyncFunc(
&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
}
}
return ContinueProcessRedirectionAfterFallback(NS_OK);
}
nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
if (NS_SUCCEEDED(rv) && mFallingBack) {
// do not continue with redirect processing, fallback is in
// progress now.
return NS_OK;
}
// Kill the current cache entry if we are redirecting
// back to ourself.
bool redirectingBackToSameURI = false;
if (mCacheEntry && mCacheEntryIsWriteOnly &&
NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
redirectingBackToSameURI)
mCacheEntry->AsyncDoom(nullptr);
// move the reference of the old location to the new one if the new
// one has none.
PropagateReferenceIfNeeded(mURI, mRedirectURI);
bool rewriteToGET =
ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());
// prompt if the method is not safe (such as POST, PUT, DELETE, ...)
if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
rv = PromptTempRedirect();
if (NS_FAILED(rv)) return rv;
}
#ifdef MOZ_GECKO_PROFILER
if (profiler_can_accept_markers()) {
int32_t priority = PRIORITY_NORMAL;
GetPriority(&priority);
TimingStruct timings;
if (mTransaction) {
timings = mTransaction->Timings();
}
profiler_add_network_marker(
mURI, priority, mChannelId, NetworkLoadType::LOAD_REDIRECT,
mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
mCacheDisposition, &timings, mRedirectURI, std::move(mSource));
}
#endif
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) return rv;
uint32_t redirectFlags;
if (nsHttp::IsPermanentRedirect(mRedirectType))
redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
else
redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
nsCOMPtr<nsIChannel> newChannel;
nsCOMPtr<nsILoadInfo> redirectLoadInfo =
CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
redirectLoadInfo,
nullptr, // PerformanceStorage
nullptr, // aLoadGroup
nullptr, // aCallbacks
nsIRequest::LOAD_NORMAL, ioService);
NS_ENSURE_SUCCESS(rv, rv);
rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
redirectFlags);
if (NS_FAILED(rv)) return rv;
// verify that this is a legal redirect
mRedirectChannel = newChannel;
PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
if (NS_FAILED(rv)) {
AutoRedirectVetoNotifier notifier(this);
PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
}
return rv;
}
nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
AutoRedirectVetoNotifier notifier(this);
LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
static_cast<uint32_t>(rv), this));
if (NS_FAILED(rv)) return rv;
MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
// Make sure to do this after we received redirect veto answer,
// i.e. after all sinks had been notified
mRedirectChannel->SetOriginalURI(mOriginalURI);
// XXX we used to talk directly with the script security manager, but that
// should really be handled by the event sink implementation.
// begin loading the new channel
rv = mRedirectChannel->AsyncOpen(mListener);
LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
NS_ENSURE_SUCCESS(rv, rv);
// close down this channel
Cancel(NS_BINDING_REDIRECTED);
notifier.RedirectSucceeded();
ReleaseListeners();
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <auth>
//-----------------------------------------------------------------------------
NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
// setting mAuthRetryPending flag and resuming the transaction
// triggers process of throwing away the unauthenticated data already
// coming from the network
mAuthRetryPending = true;
mProxyAuthPending = false;
LOG(("Resuming the transaction, we got credentials from user"));
if (mTransactionPump) {
mTransactionPump->Resume();
}
return NS_OK;
}
NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
if (mTransactionPump) {
// If the channel is trying to authenticate to a proxy and
// that was canceled we cannot show the http response body
// from the 40x as that might mislead the user into thinking
// it was a end host response instead of a proxy reponse.
// This must check explicitly whether a proxy auth was being done
// because we do want to show the content if this is an error from
// the origin server.
if (mProxyAuthPending) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
// ensure call of OnStartRequest of the current listener here,
// it would not be called otherwise at all
nsresult rv = CallOnStartRequest();
// drop mAuthRetryPending flag and resume the transaction
// this resumes load of the unauthenticated content data (which
// may have been canceled if we don't want to show it)
mAuthRetryPending = false;
LOG(("Resuming the transaction, user cancelled the auth dialog"));
mTransactionPump->Resume();
if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
}
mProxyAuthPending = false;
return NS_OK;
}
NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
// Require we are between OnStartRequest and OnStopRequest, because
// what we do here takes effect in OnStopRequest (not reusing the
// connection for next authentication round).
if (!mIsPending) {
LOG((" channel not pending"));
NS_ERROR(
"CloseStickyConnection not called before OnStopRequest, won't have any "
"effect");
return NS_ERROR_UNEXPECTED;
}
MOZ_ASSERT(mTransaction);
if (!mTransaction) {
return NS_ERROR_UNEXPECTED;
}
if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
mTransaction->HasStickyConnection())) {
LOG((" not sticky"));
return NS_OK;
}
mTransaction->DontReuseConnection();
return NS_OK;
}
NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
aRestartable));
mAuthConnectionRestartable = aRestartable;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
NS_INTERFACE_MAP_ENTRY(nsIProcessSwitchRequestor)
NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::Cancel(nsresult status) {
MOZ_ASSERT(NS_IsMainThread());
// We should never have a pump open while a CORS preflight is in progress.
MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
#ifdef DEBUG
if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
MOZ_CRASH_UNSAFE_PRINTF("Blocking classifier error %" PRIx32
" need to be handled by CancelByURLClassifier()",
static_cast<uint32_t>(status));
}
#endif
LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
static_cast<uint32_t>(status)));
if (mCanceled) {
LOG((" ignoring; already canceled\n"));
return NS_OK;
}
if (mWaitingForRedirectCallback) {
LOG(("channel canceled during wait for redirect callback"));
}
return CancelInternal(status);
}
NS_IMETHODIMP
nsHttpChannel::CancelByURLClassifier(nsresult aErrorCode) {
MOZ_ASSERT(
UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
MOZ_ASSERT(NS_IsMainThread());
// We should never have a pump open while a CORS preflight is in progress.
MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
LOG(("nsHttpChannel::CancelByURLClassifier [this=%p]\n", this));
if (mCanceled) {
LOG((" ignoring; already canceled\n"));
return NS_OK;
}
// We are being canceled by the channel classifier because of tracking
// protection, but we haven't yet had a chance to dispatch the
// "http-on-modify-request" notifications yet (this would normally be
// done in PrepareToConnect()). So do that now, before proceeding to
// cancel.
//
// Note that running these observers can itself result in the channel
// being canceled. In that case, we accept that cancelation code as
// the cause of the cancelation, as if the classification of the channel
// would have occurred past this point!
// notify "http-on-modify-request" observers
CallOnModifyRequestObservers();
// Check if request was cancelled during on-modify-request
if (mCanceled) {
return mStatus;
}
if (mSuspendCount) {
LOG(("Waiting until resume in Cancel [this=%p]\n", this));
MOZ_ASSERT(!mCallOnResume);
mChannelClassifierCancellationPending = 1;
mCallOnResume = [aErrorCode](nsHttpChannel* self) {
self->HandleContinueCancellingByURLClassifier(aErrorCode);
return NS_OK;
};
return NS_OK;
}
// Check to see if we should redirect this channel elsewhere by
// nsIHttpChannel.redirectTo API request
if (mAPIRedirectToURI) {
mChannelClassifierCancellationPending = 1;
return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
}
return CancelInternal(aErrorCode);
}
void nsHttpChannel::ContinueCancellingByURLClassifier(nsresult aErrorCode) {
MOZ_ASSERT(
UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
MOZ_ASSERT(NS_IsMainThread());
// We should never have a pump open while a CORS preflight is in progress.
MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
LOG(("nsHttpChannel::ContinueCancellingByURLClassifier [this=%p]\n", this));
if (mCanceled) {
LOG((" ignoring; already canceled\n"));
return;
}
// Check to see if we should redirect this channel elsewhere by
// nsIHttpChannel.redirectTo API request
if (mAPIRedirectToURI) {
Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
return;
}
Unused << CancelInternal(aErrorCode);
}
nsresult nsHttpChannel::CancelInternal(nsresult status) {
bool channelClassifierCancellationPending =
!!mChannelClassifierCancellationPending;
if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
mChannelClassifierCancellationPending = 0;
}
mCanceled = true;
mStatus = status;
if (mProxyRequest) mProxyRequest->Cancel(status);
CancelNetworkRequest(status);
mCacheInputStream.CloseAndRelease();
if (mCachePump) mCachePump->Cancel(status);
if (mAuthProvider) mAuthProvider->Cancel(status);
if (mPreflightChannel) mPreflightChannel->Cancel(status);
if (mRequestContext && mOnTailUnblock) {
mOnTailUnblock = nullptr;
mRequestContext->CancelTailedRequest(this);
CloseCacheEntry(false);
Unused << AsyncAbort(status);
} else if (channelClassifierCancellationPending) {
// If we're coming from an asynchronous path when canceling a channel due
// to safe-browsing protection, we need to AsyncAbort the channel now.
Unused << AsyncAbort(status);
}
return NS_OK;
}
void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
if (mTransaction) {
nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
if (NS_FAILED(rv)) {
LOG(("failed to cancel the transaction\n"));
}
}
if (mTransactionPump) mTransactionPump->Cancel(aStatus);
}
NS_IMETHODIMP
nsHttpChannel::Suspend() {
nsresult rv = SuspendInternal();
nsresult rvParentChannel = NS_OK;
if (mParentChannel) {
rvParentChannel = mParentChannel->SuspendMessageDiversion();
}
return NS_FAILED(rv) ? rv : rvParentChannel;
}
NS_IMETHODIMP
nsHttpChannel::Resume() {
nsresult rv = ResumeInternal();
nsresult rvParentChannel = NS_OK;
if (mParentChannel) {
rvParentChannel = mParentChannel->ResumeMessageDiversion();
}