1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set expandtab ts=4 sw=2 sts=2 cin: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // HttpLog.h should generally be included first
12 #include "mozilla/dom/nsCSPContext.h"
13 #include "mozilla/ScopeExit.h"
14 #include "mozilla/Sprintf.h"
17 #include "nsHttpChannel.h"
18 #include "nsHttpChannelAuthProvider.h"
19 #include "nsHttpHandler.h"
20 #include "nsIApplicationCacheService.h"
21 #include "nsIApplicationCacheContainer.h"
22 #include "nsICacheStorageService.h"
23 #include "nsICacheStorage.h"
24 #include "nsICacheEntry.h"
25 #include "nsICaptivePortalService.h"
26 #include "nsICookieService.h"
27 #include "nsICryptoHash.h"
28 #include "nsINetworkInterceptController.h"
29 #include "nsINSSErrorsService.h"
30 #include "nsISecurityReporter.h"
31 #include "nsIStringBundle.h"
32 #include "nsIStreamListenerTee.h"
33 #include "nsISeekableStream.h"
34 #include "nsILoadGroupChild.h"
35 #include "nsIProtocolProxyService2.h"
36 #include "nsIURIClassifier.h"
37 #include "nsMimeTypes.h"
39 #include "nsNetUtil.h"
41 #include "nsIURIMutator.h"
42 #include "nsIStreamTransportService.h"
45 #include "nsStreamUtils.h"
46 #include "nsIOService.h"
47 #include "nsDNSPrefetch.h"
48 #include "nsChannelClassifier.h"
49 #include "nsIRedirectResultListener.h"
50 #include "mozIThirdPartyUtil.h"
51 #include "mozilla/dom/ContentVerifier.h"
52 #include "mozilla/TimeStamp.h"
54 #include "nsPrintfCString.h"
55 #include "nsAlgorithm.h"
56 #include "nsQueryObject.h"
57 #include "nsThreadUtils.h"
58 #include "GeckoProfiler.h"
59 #include "nsIConsoleService.h"
60 #include "mozilla/AntiTrackingCommon.h"
61 #include "mozilla/Attributes.h"
62 #include "mozilla/DebugOnly.h"
63 #include "mozilla/Preferences.h"
64 #include "mozilla/Services.h"
65 #include "nsISSLSocketControl.h"
67 #include "nsContentUtils.h"
68 #include "nsContentSecurityManager.h"
69 #include "nsIClassOfService.h"
70 #include "nsIPermissionManager.h"
71 #include "nsIPrincipal.h"
72 #include "nsIScriptError.h"
73 #include "nsIScriptSecurityManager.h"
74 #include "nsITransportSecurityInfo.h"
75 #include "nsIWebProgressListener.h"
76 #include "LoadContextInfo.h"
78 #include "nsHttpTransaction.h"
79 #include "nsICacheEntryDescriptor.h"
80 #include "nsICancelable.h"
81 #include "nsIHttpChannelAuthProvider.h"
82 #include "nsIHttpChannelInternal.h"
83 #include "nsIPrompt.h"
84 #include "nsInputStreamPump.h"
85 #include "nsIURIFixup.h"
86 #include "nsURLHelper.h"
87 #include "nsISocketTransport.h"
88 #include "nsIStreamConverterService.h"
89 #include "nsISiteSecurityService.h"
92 #include "CacheObserver.h"
93 #include "mozilla/dom/PerformanceStorage.h"
94 #include "mozilla/Telemetry.h"
95 #include "AlternateServices.h"
96 #include "InterceptedChannel.h"
97 #include "nsIHttpPushListener.h"
98 #include "nsIX509Cert.h"
99 #include "ScopedNSSTypes.h"
100 #include "nsIDeprecationWarner.h"
101 #include "mozilla/dom/Document.h"
102 #include "nsICompressConvStats.h"
103 #include "nsCORSListenerProxy.h"
104 #include "nsISocketProvider.h"
105 #include "mozilla/extensions/StreamFilterParent.h"
106 #include "mozilla/net/Predictor.h"
107 #include "mozilla/MathAlgorithms.h"
108 #include "mozilla/NullPrincipal.h"
109 #include "CacheControlParser.h"
110 #include "nsMixedContentBlocker.h"
111 #include "CacheStorageService.h"
112 #include "HttpChannelParent.h"
113 #include "HttpChannelParentListener.h"
114 #include "InterceptedHttpChannel.h"
115 #include "nsIBufferedStreams.h"
116 #include "nsIFileStreams.h"
117 #include "nsIMIMEInputStream.h"
118 #include "nsIMultiplexInputStream.h"
119 #include "../../cache2/CacheFileUtils.h"
120 #include "nsINetworkLinkService.h"
121 #include "mozilla/dom/PromiseNativeHandler.h"
122 #include "mozilla/dom/Promise.h"
123 #include "mozilla/dom/ServiceWorkerUtils.h"
124 #include "mozilla/net/AsyncUrlChannelClassifier.h"
125 #include "mozilla/net/UrlClassifierFeatureFactory.h"
127 #ifdef MOZ_TASK_TRACER
128 # include "GeckoTaskTracer.h"
131 #ifdef MOZ_GECKO_PROFILER
132 # include "ProfilerMarkerPayload.h"
143 static bool sRCWNEnabled = false;
144 static uint32_t sRCWNQueueSizeNormal = 50;
145 static uint32_t sRCWNQueueSizePriority = 10;
146 static uint32_t sRCWNSmallResourceSizeKB = 256;
147 static uint32_t sRCWNMinWaitMs = 0;
148 static uint32_t sRCWNMaxWaitMs = 500;
150 // True if the local cache should be bypassed when processing a request.
151 #define BYPASS_LOCAL_CACHE(loadFlags) \
152 (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
153 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
155 #define RECOVER_FROM_CACHE_FILE_ERROR(result) \
156 ((result) == NS_ERROR_FILE_NOT_FOUND || \
157 (result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)
159 #define WRONG_RACING_RESPONSE_SOURCE(req) \
160 (mRaceCacheWithNetwork && \
161 (((mFirstResponseSource == RESPONSE_FROM_CACHE) && (req != mCachePump)) || \
162 ((mFirstResponseSource == RESPONSE_FROM_NETWORK) && \
163 (req != mTransactionPump))))
165 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
167 void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss) {
168 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
171 // Computes and returns a SHA1 hash of the input buffer. The input buffer
172 // must be a null-terminated string.
173 nsresult Hash(const char *buf, nsACString &hash) {
176 nsCOMPtr<nsICryptoHash> hasher =
177 do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
178 NS_ENSURE_SUCCESS(rv, rv);
180 rv = hasher->Init(nsICryptoHash::SHA1);
181 NS_ENSURE_SUCCESS(rv, rv);
184 hasher->Update(reinterpret_cast<unsigned const char *>(buf), strlen(buf));
185 NS_ENSURE_SUCCESS(rv, rv);
187 rv = hasher->Finish(true, hash);
188 NS_ENSURE_SUCCESS(rv, rv);
193 bool IsInSubpathOfAppCacheManifest(nsIApplicationCache *cache,
194 nsACString const &uriSpec) {
199 nsCOMPtr<nsIURI> uri;
200 rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
205 nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
210 nsAutoCString directory;
211 rv = url->GetDirectory(directory);
216 nsCOMPtr<nsIURI> manifestURI;
217 rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
222 nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
227 nsAutoCString manifestDirectory;
228 rv = manifestURL->GetDirectory(manifestDirectory);
233 return StringBeginsWith(directory, manifestDirectory);
236 } // unnamed namespace
238 // We only treat 3xx responses as redirects if they have a Location header and
239 // the status code is in a whitelist.
240 bool nsHttpChannel::WillRedirect(nsHttpResponseHead *response) {
241 return IsRedirectStatus(response->Status()) &&
242 response->HasHeader(nsHttp::Location);
245 nsresult StoreAuthorizationMetaData(nsICacheEntry *entry,
246 nsHttpRequestHead *requestHead);
248 class AutoRedirectVetoNotifier {
250 explicit AutoRedirectVetoNotifier(nsHttpChannel *channel)
251 : mChannel(channel) {
252 if (mChannel->mHasAutoRedirectVetoNotifier) {
253 MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
258 mChannel->mHasAutoRedirectVetoNotifier = true;
260 ~AutoRedirectVetoNotifier() { ReportRedirectResult(false); }
261 void RedirectSucceeded() { ReportRedirectResult(true); }
264 nsHttpChannel *mChannel;
265 void ReportRedirectResult(bool succeeded);
268 void AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) {
269 if (!mChannel) return;
271 mChannel->mRedirectChannel = nullptr;
274 mChannel->RemoveAsNonTailRequest();
277 nsCOMPtr<nsIRedirectResultListener> vetoHook;
278 NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
279 getter_AddRefs(vetoHook));
281 nsHttpChannel *channel = mChannel;
284 if (vetoHook) vetoHook->OnRedirectResult(succeeded);
286 // Drop after the notification
287 channel->mHasAutoRedirectVetoNotifier = false;
290 //-----------------------------------------------------------------------------
291 // nsHttpChannel <public>
292 //-----------------------------------------------------------------------------
294 nsHttpChannel::nsHttpChannel()
295 : HttpAsyncAborter<nsHttpChannel>(this),
296 mCacheDisposition(kCacheUnresolved),
300 mOfflineCacheLastModifiedTime(0),
301 mSuspendTotalTime(0),
303 mCacheOpenWithPriority(false),
304 mCacheQueueSizeWhenOpen(0),
305 mCachedContentIsValid(false),
306 mCachedContentIsPartial(false),
307 mCacheOnlyMetadata(false),
308 mTransactionReplaced(false),
309 mAuthRetryPending(false),
310 mProxyAuthPending(false),
311 mCustomAuthHeader(false),
313 mInitedCacheEntry(false),
314 mFallbackChannel(false),
315 mCustomConditionalRequest(false),
317 mWaitingForRedirectCallback(false),
318 mRequestTimeInitialized(false),
319 mCacheEntryIsReadOnly(false),
320 mCacheEntryIsWriteOnly(false),
321 mCacheEntriesToWaitFor(0),
322 mConcurrentCacheAccess(0),
323 mIsPartialRequest(0),
324 mHasAutoRedirectVetoNotifier(0),
326 mIsCorsPreflightDone(0),
327 mStronglyFramed(false),
329 mAuthConnectionRestartable(0),
330 mChannelClassifierCancellationPending(0),
331 mAsyncResumePending(0),
332 mPushedStream(nullptr),
333 mLocalBlocklist(false),
334 mOnTailUnblock(nullptr),
335 mWarningReporter(nullptr),
336 mIsReadingFromCache(false),
337 mFirstResponseSource(RESPONSE_PENDING),
338 mRaceCacheWithNetwork(false),
340 mIgnoreCacheEntry(false),
341 mRCWNLock("nsHttpChannel.mRCWNLock"),
343 LOG(("Creating nsHttpChannel [this=%p]\n", this));
344 mChannelCreationTime = PR_Now();
345 mChannelCreationTimestamp = TimeStamp::Now();
348 nsHttpChannel::~nsHttpChannel() {
349 LOG(("Destroying nsHttpChannel [this=%p]\n", this));
352 DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
353 MOZ_ASSERT(NS_SUCCEEDED(rv));
356 ReleaseMainThreadOnlyReferences();
359 void nsHttpChannel::ReleaseMainThreadOnlyReferences() {
360 if (NS_IsMainThread()) {
361 // Already on main thread, let dtor to
362 // take care of releasing references
366 nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
367 arrayToRelease.AppendElement(mApplicationCacheForWrite.forget());
368 arrayToRelease.AppendElement(mAuthProvider.forget());
369 arrayToRelease.AppendElement(mRedirectURI.forget());
370 arrayToRelease.AppendElement(mRedirectChannel.forget());
371 arrayToRelease.AppendElement(mPreflightChannel.forget());
373 NS_DispatchToMainThread(new ProxyReleaseRunnable(std::move(arrayToRelease)));
376 nsresult nsHttpChannel::Init(nsIURI *uri, uint32_t caps, nsProxyInfo *proxyInfo,
377 uint32_t proxyResolveFlags, nsIURI *proxyURI,
379 nsContentPolicyType aContentPolicyType) {
380 nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags,
381 proxyURI, channelId, aContentPolicyType);
382 if (NS_FAILED(rv)) return rv;
384 LOG1(("nsHttpChannel::Init [this=%p]\n", this));
389 nsresult nsHttpChannel::AddSecurityMessage(const nsAString &aMessageTag,
390 const nsAString &aMessageCategory) {
391 if (mWarningReporter) {
392 return mWarningReporter->ReportSecurityMessage(aMessageTag,
395 return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
399 nsHttpChannel::LogBlockedCORSRequest(const nsAString &aMessage,
400 const nsACString &aCategory) {
401 if (mWarningReporter) {
402 return mWarningReporter->LogBlockedCORSRequest(aMessage, aCategory);
404 return NS_ERROR_UNEXPECTED;
408 nsHttpChannel::LogMimeTypeMismatch(const nsACString &aMessageName,
409 bool aWarning, const nsAString &aURL,
410 const nsAString &aContentType) {
411 if (mWarningReporter) {
412 return mWarningReporter->LogMimeTypeMismatch(aMessageName, aWarning, aURL,
415 return NS_ERROR_UNEXPECTED;
418 //-----------------------------------------------------------------------------
419 // nsHttpChannel <private>
420 //-----------------------------------------------------------------------------
422 nsresult nsHttpChannel::PrepareToConnect() {
423 LOG(("nsHttpChannel::PrepareToConnect [this=%p]\n", this));
425 AddCookiesToRequest();
427 // notify "http-on-modify-request" observers
428 CallOnModifyRequestObservers();
430 SetLoadGroupUserAgentOverride();
432 // Check if request was cancelled during on-modify-request or on-useragent.
438 // We abandon the connection here if there was one.
439 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
440 MOZ_ASSERT(!mCallOnResume);
441 mCallOnResume = [](nsHttpChannel *self) {
442 self->HandleOnBeforeConnect();
448 return OnBeforeConnect();
451 void nsHttpChannel::HandleContinueCancellingByChannelClassifier(
452 nsresult aErrorCode) {
454 UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
455 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
459 ("Waiting until resume HandleContinueCancellingByChannelClassifier "
462 mCallOnResume = [aErrorCode](nsHttpChannel *self) {
463 self->HandleContinueCancellingByChannelClassifier(aErrorCode);
469 LOG(("nsHttpChannel::HandleContinueCancellingByChannelClassifier [this=%p]\n",
471 ContinueCancellingByChannelClassifier(aErrorCode);
474 void nsHttpChannel::HandleOnBeforeConnect() {
475 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
479 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
480 mCallOnResume = [](nsHttpChannel *self) {
481 self->HandleOnBeforeConnect();
487 LOG(("nsHttpChannel::HandleOnBeforeConnect [this=%p]\n", this));
488 rv = OnBeforeConnect();
490 CloseCacheEntry(false);
491 Unused << AsyncAbort(rv);
495 nsresult nsHttpChannel::OnBeforeConnect() {
498 // Check if request was cancelled during suspend AFTER on-modify-request or
504 // Check to see if we should redirect this channel elsewhere by
505 // nsIHttpChannel.redirectTo API request
506 if (mAPIRedirectToURI) {
507 return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
510 // Note that we are only setting the "Upgrade-Insecure-Requests" request
511 // header for *all* navigational requests instead of all requests as
512 // defined in the spec, see:
513 // https://www.w3.org/TR/upgrade-insecure-requests/#preference
514 nsContentPolicyType type = mLoadInfo
515 ? mLoadInfo->GetExternalContentPolicyType()
516 : nsIContentPolicy::TYPE_OTHER;
518 if (type == nsIContentPolicy::TYPE_DOCUMENT ||
519 type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
520 rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
521 NS_LITERAL_CSTRING("1"), false);
522 NS_ENSURE_SUCCESS(rv, rv);
525 bool isHttps = false;
526 rv = mURI->SchemeIs("https", &isHttps);
527 NS_ENSURE_SUCCESS(rv, rv);
528 nsCOMPtr<nsIPrincipal> resultPrincipal;
529 if (!isHttps && mLoadInfo) {
530 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
531 this, getter_AddRefs(resultPrincipal));
533 OriginAttributes originAttributes;
534 if (!NS_GetOriginAttributes(this, originAttributes)) {
535 return NS_ERROR_FAILURE;
538 rv = mURI->SchemeIs("http", &isHttp);
539 NS_ENSURE_SUCCESS(rv, rv);
541 // At this point it is no longer possible to call
542 // HttpBaseChannel::UpgradeToSecure.
543 mUpgradableToSecure = false;
545 bool shouldUpgrade = mUpgradeToSecure;
546 if (!shouldUpgrade) {
547 rv = NS_ShouldSecureUpgrade(mURI, mLoadInfo, resultPrincipal,
548 mPrivateBrowsing, mAllowSTS, originAttributes,
550 NS_ENSURE_SUCCESS(rv, rv);
553 return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
557 // ensure that we are using a valid hostname
558 if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin())))
559 return NS_ERROR_UNKNOWN_HOST;
561 if (mUpgradeProtocolCallback) {
562 // Websockets can run over HTTP/2, but other upgrades can't.
563 if (mUpgradeProtocol.EqualsLiteral("websocket") &&
564 gHttpHandler->IsH2WebsocketsEnabled()) {
565 // Need to tell the conn manager that we're ok with http/2 even with
566 // the allow keepalive bit not set. That bit needs to stay off,
567 // though, in case we end up having to fallback to http/1.1 (where
568 // we absolutely do want to disable keepalive).
569 mCaps |= NS_HTTP_ALLOW_SPDY_WITHOUT_KEEPALIVE;
571 mCaps |= NS_HTTP_DISALLOW_SPDY;
576 mCaps |= NS_HTTP_LARGE_KEEPALIVE | NS_HTTP_DISABLE_TRR;
579 if (mLoadFlags & LOAD_DISABLE_TRR) {
580 mCaps |= NS_HTTP_DISABLE_TRR;
583 // Finalize ConnectionInfo flags before SpeculativeConnect
584 mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
585 mConnectionInfo->SetPrivate(mPrivateBrowsing);
586 mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
587 mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
589 mConnectionInfo->SetTlsFlags(mTlsFlags);
590 mConnectionInfo->SetTrrUsed(mTRR);
591 mConnectionInfo->SetTrrDisabled(mCaps & NS_HTTP_DISABLE_TRR);
592 mConnectionInfo->SetIPv4Disabled(mCaps & NS_HTTP_DISABLE_IPV4);
593 mConnectionInfo->SetIPv6Disabled(mCaps & NS_HTTP_DISABLE_IPV6);
595 // notify "http-on-before-connect" observers
596 gHttpHandler->OnBeforeConnect(this);
598 // Check if request was cancelled during http-on-before-connect.
604 // We abandon the connection here if there was one.
605 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
606 MOZ_ASSERT(!mCallOnResume);
607 mCallOnResume = [](nsHttpChannel *self) {
608 self->OnBeforeConnectContinue();
617 void nsHttpChannel::OnBeforeConnectContinue() {
618 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
622 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
623 mCallOnResume = [](nsHttpChannel *self) {
624 self->OnBeforeConnectContinue();
630 LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
633 CloseCacheEntry(false);
634 Unused << AsyncAbort(rv);
638 nsresult nsHttpChannel::Connect() {
639 LOG(("nsHttpChannel::Connect [this=%p]\n", this));
641 // Don't allow resuming when cache must be used
642 if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
643 LOG(("Resuming from cache is not supported yet"));
644 return NS_ERROR_DOCUMENT_NOT_CACHED;
647 if (ShouldIntercept()) {
648 return RedirectToInterceptedChannel();
651 bool isTrackingResource = mIsThirdPartyTrackingResource; // is atomic
652 LOG(("nsHttpChannel %p tracking resource=%d, cos=%u", this,
653 isTrackingResource, mClassOfService));
655 if (isTrackingResource) {
656 AddClassFlags(nsIClassOfService::Tail);
659 if (WaitingForTailUnblock()) {
660 MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
661 mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
665 return ConnectOnTailUnblock();
668 nsresult nsHttpChannel::ConnectOnTailUnblock() {
671 LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));
673 // Consider opening a TCP connection right away.
674 SpeculativeConnect();
676 // open a cache entry for this channel...
677 bool isHttps = false;
678 rv = mURI->SchemeIs("https", &isHttps);
679 NS_ENSURE_SUCCESS(rv, rv);
680 rv = OpenCacheEntry(isHttps);
682 // do not continue if asyncOpenCacheEntry is in progress
683 if (AwaitingCacheCallbacks()) {
684 LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
686 MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
688 if (mNetworkTriggered && mWaitingForProxy) {
689 // Someone has called TriggerNetwork(), meaning we are racing the
690 // network with the cache.
691 mWaitingForProxy = false;
692 return ContinueConnect();
699 LOG(("OpenCacheEntry failed [rv=%" PRIx32 "]\n",
700 static_cast<uint32_t>(rv)));
701 // if this channel is only allowed to pull from the cache, then
702 // we must fail if we were unable to open a cache entry.
703 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
704 // If we have a fallback URI (and we're not already
705 // falling back), process the fallback asynchronously.
706 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
707 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
709 return NS_ERROR_DOCUMENT_NOT_CACHED;
711 // otherwise, let's just proceed without using the cache.
714 if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
715 (mDidReval || mCachedContentIsPartial)) ||
716 mIgnoreCacheEntry)) {
717 // We won't send the conditional request because the unconditional
718 // request was already sent (see bug 1377223).
719 AccumulateCategorical(
720 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
723 // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
724 // returns, then we may not have started reading from the cache.
725 // If the content is valid, we should attempt to do so, as technically the
726 // cache has won the race.
727 if (mRaceCacheWithNetwork && mCachedContentIsValid) {
728 Unused << ReadFromCache(true);
731 return TriggerNetwork();
734 nsresult nsHttpChannel::ContinueConnect() {
735 // If we need to start a CORS preflight, do it now!
736 // Note that it is important to do this before the early returns below.
737 if (!mIsCorsPreflightDone && mRequireCORSPreflight) {
738 MOZ_ASSERT(!mPreflightChannel);
739 nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
740 this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
744 MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
745 "CORS preflight must have been finished by the time we "
746 "do the rest of ContinueConnect");
748 // we may or may not have a cache entry at this point
750 // read straight from the cache if possible...
751 if (mCachedContentIsValid) {
752 nsRunnableMethod<nsHttpChannel> *event = nullptr;
754 if (!mCachedContentIsPartial) {
755 rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
757 LOG((" AsyncCall failed (%08x)", static_cast<uint32_t>(rv)));
760 rv = ReadFromCache(true);
761 if (NS_FAILED(rv) && event) {
765 AccumulateCacheHitTelemetry(kCacheHit);
766 mCacheDisposition = kCacheHit;
770 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
771 // the cache contains the requested resource, but it must be
772 // validated before we can reuse it. since we are not allowed
773 // to hit the net, there's nothing more to do. the document
774 // is effectively not in the cache.
775 LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
776 return NS_ERROR_DOCUMENT_NOT_CACHED;
778 } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
779 // If we have a fallback URI (and we're not already
780 // falling back), process the fallback asynchronously.
781 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
782 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
784 LOG((" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
785 return NS_ERROR_DOCUMENT_NOT_CACHED;
788 if (mLoadFlags & LOAD_NO_NETWORK_IO) {
789 LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
790 return NS_ERROR_DOCUMENT_NOT_CACHED;
797 nsresult nsHttpChannel::DoConnect(nsHttpTransaction *aTransWithStickyConn) {
798 LOG(("nsHttpChannel::DoConnect [this=%p, aTransWithStickyConn=%p]\n", this,
799 aTransWithStickyConn));
801 nsresult rv = SetupTransaction();
806 if (aTransWithStickyConn) {
807 rv = gHttpHandler->InitiateTransactionWithStickyConn(
808 mTransaction, mPriority, aTransWithStickyConn);
810 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
817 rv = mTransactionPump->AsyncRead(this, nullptr);
822 uint32_t suspendCount = mSuspendCount;
823 if (mAsyncResumePending) {
825 (" Suspend()'ing transaction pump once because of async resume pending"
826 ", sc=%u, pump=%p, this=%p",
827 suspendCount, mTransactionPump.get(), this));
830 while (suspendCount--) {
831 mTransactionPump->Suspend();
837 void nsHttpChannel::SpeculativeConnect() {
838 // Before we take the latency hit of dealing with the cache, try and
839 // get the TCP (and SSL) handshakes going so they can overlap.
841 // don't speculate if we are on uses of the offline application cache,
842 // if we are offline, when doing http upgrade (i.e.
843 // websockets bootstrap), or if we can't do keep-alive (because then we
844 // couldn't reuse the speculative connection anyhow).
845 if (mApplicationCache || gIOService->IsOffline() ||
846 mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
849 // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
850 // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
851 // so skip preconnects for them.
852 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
853 LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
856 if (mAllowStaleCacheContent) {
860 nsCOMPtr<nsIInterfaceRequestor> callbacks;
861 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
862 getter_AddRefs(callbacks));
863 if (!callbacks) return;
865 Unused << gHttpHandler->SpeculativeConnect(
866 mConnectionInfo, callbacks,
867 mCaps & (NS_HTTP_DISALLOW_SPDY | NS_HTTP_DISABLE_TRR |
868 NS_HTTP_DISABLE_IPV4 | NS_HTTP_DISABLE_IPV6));
871 void nsHttpChannel::DoNotifyListenerCleanup() {
872 // We don't need this info anymore
873 CleanRedirectCacheChainIfNecessary();
876 void nsHttpChannel::ReleaseListeners() {
877 HttpBaseChannel::ReleaseListeners();
878 mChannelClassifier = nullptr;
879 mWarningReporter = nullptr;
882 void nsHttpChannel::DoAsyncAbort(nsresult aStatus) {
883 Unused << AsyncAbort(aStatus);
886 void nsHttpChannel::HandleAsyncRedirect() {
887 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
890 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
891 mCallOnResume = [](nsHttpChannel *self) {
892 self->HandleAsyncRedirect();
900 LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
902 // since this event is handled asynchronously, it is possible that this
903 // channel could have been canceled, in which case there would be no point
904 // in processing the redirect.
905 if (NS_SUCCEEDED(mStatus)) {
906 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
907 rv = AsyncProcessRedirection(mResponseHead->Status());
909 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
910 // TODO: if !DoNotRender3xxBody(), render redirect body instead.
911 // But first we need to cache 3xx bodies (bug 748510)
912 rv = ContinueHandleAsyncRedirect(rv);
913 MOZ_ASSERT(NS_SUCCEEDED(rv));
916 rv = ContinueHandleAsyncRedirect(mStatus);
917 MOZ_ASSERT(NS_SUCCEEDED(rv));
921 nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
923 // If AsyncProcessRedirection fails, then we have to send out the
924 // OnStart/OnStop notifications.
925 LOG(("ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32 "]\n",
926 static_cast<uint32_t>(rv)));
928 bool redirectsEnabled = !mLoadInfo || !mLoadInfo->GetDontFollowRedirects();
930 if (redirectsEnabled) {
931 // TODO: stop failing original channel if redirect vetoed?
936 // Blow away cache entry if we couldn't process the redirect
937 // for some reason (the cache entry might be corrupt).
939 mCacheEntry->AsyncDoom(nullptr);
946 CloseCacheEntry(true);
950 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
955 void nsHttpChannel::HandleAsyncNotModified() {
956 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
959 LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
960 mCallOnResume = [](nsHttpChannel *self) {
961 self->HandleAsyncNotModified();
967 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
971 CloseCacheEntry(false);
975 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
978 void nsHttpChannel::HandleAsyncFallback() {
979 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
982 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
983 mCallOnResume = [](nsHttpChannel *self) {
984 self->HandleAsyncFallback();
992 LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
994 // since this event is handled asynchronously, it is possible that this
995 // channel could have been canceled, in which case there would be no point
996 // in processing the fallback.
998 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
999 bool waitingForRedirectCallback;
1000 rv = ProcessFallback(&waitingForRedirectCallback);
1001 if (waitingForRedirectCallback) return;
1002 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
1005 rv = ContinueHandleAsyncFallback(rv);
1006 MOZ_ASSERT(NS_SUCCEEDED(rv));
1009 nsresult nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) {
1010 if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
1011 // If ProcessFallback fails, then we have to send out the
1012 // OnStart/OnStop notifications.
1013 LOG(("ProcessFallback failed [rv=%" PRIx32 ", %d]\n",
1014 static_cast<uint32_t>(rv), mFallingBack));
1015 mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
1021 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
1026 nsresult nsHttpChannel::SetupTransaction() {
1027 LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n", this,
1028 mClassOfService, mPriority));
1030 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
1034 mozilla::MutexAutoLock lock(mRCWNLock);
1036 // If we're racing cache with network, conditional or byte range header
1037 // could be added in OnCacheEntryCheck. We cannot send conditional request
1038 // without having the entry, so we need to remove the headers here and
1039 // ignore the cache entry in OnCacheEntryAvailable.
1040 if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
1042 LOG((" Removing conditional request headers"));
1043 UntieValidationRequest();
1045 mIgnoreCacheEntry = true;
1048 if (mCachedContentIsPartial) {
1049 LOG((" Removing byte range request headers"));
1050 UntieByteRangeRequest();
1051 mCachedContentIsPartial = false;
1052 mIgnoreCacheEntry = true;
1055 if (mIgnoreCacheEntry) {
1056 if (!mAvailableCachedAltDataType.IsEmpty()) {
1057 mAvailableCachedAltDataType.Truncate();
1060 mCacheInputStream.CloseAndRelease();
1067 mCaps |= NS_HTTP_DISALLOW_SPDY;
1069 if (mBeConservative) {
1070 mCaps |= NS_HTTP_BE_CONSERVATIVE;
1073 // Use the URI path if not proxying (transparent proxying such as proxy
1074 // CONNECT does not count here). Also figure out what HTTP version to use.
1075 nsAutoCString buf, path;
1076 nsCString *requestURI;
1078 // This is the normal e2e H1 path syntax "/index.html"
1079 rv = mURI->GetPathQueryRef(path);
1080 if (NS_FAILED(rv)) {
1084 // path may contain UTF-8 characters, so ensure that they're escaped.
1085 if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII | esc_Spaces,
1092 // trim off the #ref portion if any...
1093 int32_t ref1 = requestURI->FindChar('#');
1094 if (ref1 != kNotFound) {
1095 requestURI->SetLength(ref1);
1098 if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
1099 mRequestHead.SetVersion(gHttpHandler->HttpVersion());
1101 mRequestHead.SetPath(*requestURI);
1103 // RequestURI should be the absolute uri H1 proxy syntax
1104 // "http://foo/index.html" so we will overwrite the relative version in
1106 rv = mURI->GetUserPass(buf);
1107 if (NS_FAILED(rv)) return rv;
1108 if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
1109 strncmp(mSpec.get(), "https:", 6) == 0)) {
1110 nsCOMPtr<nsIURIFixup> urifixup = services::GetURIFixup();
1111 if (NS_WARN_IF(!urifixup)) {
1112 return NS_ERROR_FAILURE;
1115 nsCOMPtr<nsIURI> tempURI;
1116 nsresult rv = urifixup->CreateExposableURI(mURI, getter_AddRefs(tempURI));
1117 if (NS_WARN_IF(NS_FAILED(rv))) {
1121 rv = tempURI->GetAsciiSpec(path);
1122 if (NS_FAILED(rv)) return rv;
1125 requestURI = &mSpec;
1128 // trim off the #ref portion if any...
1129 int32_t ref2 = requestURI->FindChar('#');
1130 if (ref2 != kNotFound) {
1131 requestURI->SetLength(ref2);
1134 mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
1137 mRequestHead.SetRequestURI(*requestURI);
1139 // set the request time for cache expiration calculations
1140 mRequestTime = NowInSeconds();
1141 mRequestTimeInitialized = true;
1143 // if doing a reload, force end-to-end
1144 if (mLoadFlags & LOAD_BYPASS_CACHE) {
1145 // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
1146 // no proxy is configured since we might be talking with a transparent
1147 // proxy, i.e. one that operates at the network level. See bug #14772.
1148 rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
1149 MOZ_ASSERT(NS_SUCCEEDED(rv));
1150 // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
1152 if (mRequestHead.Version() >= HttpVersion::v1_1) {
1153 rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
1154 MOZ_ASSERT(NS_SUCCEEDED(rv));
1156 } else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
1157 // We need to send 'Cache-Control: max-age=0' to force each cache along
1158 // the path to the origin server to revalidate its own entry, if any,
1159 // with the next cache or server. See bug #84847.
1161 // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
1162 if (mRequestHead.Version() >= HttpVersion::v1_1)
1163 rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
1165 rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
1166 MOZ_ASSERT(NS_SUCCEEDED(rv));
1171 SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
1172 rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
1173 MOZ_ASSERT(NS_SUCCEEDED(rv));
1175 if (!mEntityID.IsEmpty()) {
1176 // Also, we want an error if this resource changed in the meantime
1177 // Format of the entity id is: escaped_etag/size/lastmod
1178 nsCString::const_iterator start, end, slash;
1179 mEntityID.BeginReading(start);
1180 mEntityID.EndReading(end);
1181 mEntityID.BeginReading(slash);
1183 if (FindCharInReadable('/', slash, end)) {
1184 nsAutoCString ifMatch;
1185 rv = mRequestHead.SetHeader(
1187 NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
1188 MOZ_ASSERT(NS_SUCCEEDED(rv));
1190 ++slash; // Incrementing, so that searching for '/' won't find
1191 // the same slash again
1194 if (FindCharInReadable('/', slash, end)) {
1195 rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
1196 Substring(++slash, end));
1197 MOZ_ASSERT(NS_SUCCEEDED(rv));
1202 // create wrapper for this channel's notification callbacks
1203 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1204 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
1205 getter_AddRefs(callbacks));
1207 // create the transaction object
1208 mTransaction = new nsHttpTransaction();
1209 LOG1(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
1210 mTransaction.get()));
1211 mTransaction->SetTransactionObserver(mTransactionObserver);
1212 mTransactionObserver = nullptr;
1214 // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
1215 if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
1217 if (mTimingEnabled) mCaps |= NS_HTTP_TIMING_ENABLED;
1219 if (mUpgradeProtocolCallback) {
1220 rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
1221 MOZ_ASSERT(NS_SUCCEEDED(rv));
1222 rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
1224 MOZ_ASSERT(NS_SUCCEEDED(rv));
1225 mCaps |= NS_HTTP_STICKY_CONNECTION;
1226 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
1229 if (mPushedStream) {
1230 mTransaction->SetPushedStream(mPushedStream);
1231 mPushedStream = nullptr;
1234 nsCOMPtr<nsIHttpPushListener> pushListener;
1235 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
1236 NS_GET_IID(nsIHttpPushListener),
1237 getter_AddRefs(pushListener));
1239 mCaps |= NS_HTTP_ONPUSH_LISTENER;
1242 EnsureTopLevelOuterContentWindowId();
1244 nsCOMPtr<nsIAsyncInputStream> responseStream;
1245 rv = mTransaction->Init(
1246 mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
1247 mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
1248 mTopLevelOuterContentWindowId, getter_AddRefs(responseStream));
1249 if (NS_FAILED(rv)) {
1250 mTransaction = nullptr;
1254 mTransaction->SetClassOfService(mClassOfService);
1255 if (EnsureRequestContext()) {
1256 mTransaction->SetRequestContext(mRequestContext);
1259 rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
1264 enum class Report { Error, Warning };
1266 // Helper Function to report messages to the console when the loaded
1267 // script had a wrong MIME type.
1268 void ReportMimeTypeMismatch(nsHttpChannel *aChannel, const char *aMessageName,
1269 nsIURI *aURI, const nsACString &aContentType,
1271 NS_ConvertUTF8toUTF16 spec(aURI->GetSpecOrDefault());
1272 NS_ConvertUTF8toUTF16 contentType(aContentType);
1274 aChannel->LogMimeTypeMismatch(nsCString(aMessageName),
1275 report == Report::Warning, spec, contentType);
1278 // Check and potentially enforce X-Content-Type-Options: nosniff
1279 nsresult ProcessXCTO(nsHttpChannel *aChannel, nsIURI *aURI,
1280 nsHttpResponseHead *aResponseHead,
1281 nsILoadInfo *aLoadInfo) {
1282 if (!aURI || !aResponseHead || !aLoadInfo) {
1283 // if there is no uri, no response head or no loadInfo, then there is
1288 // 1) Query the XCTO header and check if 'nosniff' is the first value.
1289 nsAutoCString contentTypeOptionsHeader;
1290 Unused << aResponseHead->GetHeader(nsHttp::X_Content_Type_Options,
1291 contentTypeOptionsHeader);
1292 if (contentTypeOptionsHeader.IsEmpty()) {
1293 // if there is no XCTO header, then there is nothing to do.
1296 // XCTO header might contain multiple values which are comma separated, so:
1297 // a) let's skip all subsequent values
1298 // e.g. " NoSniFF , foo " will be " NoSniFF "
1299 int32_t idx = contentTypeOptionsHeader.Find(",");
1301 contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
1303 // b) let's trim all surrounding whitespace
1304 // e.g. " NoSniFF " -> "NoSniFF"
1305 nsHttp::TrimHTTPWhitespace(contentTypeOptionsHeader,
1306 contentTypeOptionsHeader);
1307 // c) let's compare the header (ignoring case)
1308 // e.g. "NoSniFF" -> "nosniff"
1309 // if it's not 'nosniff' then there is nothing to do here
1310 if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
1311 // since we are getting here, the XCTO header was sent;
1312 // a non matching value most likely means a mistake happenend;
1313 // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
1314 NS_ConvertUTF8toUTF16 char16_header(contentTypeOptionsHeader);
1315 const char16_t *params[] = {char16_header.get()};
1316 RefPtr<Document> doc;
1317 aLoadInfo->GetLoadingDocument(getter_AddRefs(doc));
1318 nsContentUtils::ReportToConsole(
1319 nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XCTO"), doc,
1320 nsContentUtils::eSECURITY_PROPERTIES, "XCTOHeaderValueMissing", params,
1321 ArrayLength(params));
1325 // 2) Query the content type from the channel
1326 nsAutoCString contentType;
1327 aResponseHead->ContentType(contentType);
1329 // 3) Compare the expected MIME type with the actual type
1330 if (aLoadInfo->GetExternalContentPolicyType() ==
1331 nsIContentPolicy::TYPE_STYLESHEET) {
1332 if (contentType.EqualsLiteral(TEXT_CSS)) {
1335 ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
1337 return NS_ERROR_CORRUPTED_CONTENT;
1340 if (aLoadInfo->GetExternalContentPolicyType() ==
1341 nsIContentPolicy::TYPE_SCRIPT) {
1342 if (nsContentUtils::IsJavascriptMIMEType(
1343 NS_ConvertUTF8toUTF16(contentType))) {
1346 ReportMimeTypeMismatch(aChannel, "MimeTypeMismatch2", aURI, contentType,
1348 return NS_ERROR_CORRUPTED_CONTENT;
1353 // Ensure that a load of type script has correct MIME type
1354 nsresult EnsureMIMEOfScript(nsHttpChannel *aChannel, nsIURI *aURI,
1355 nsHttpResponseHead *aResponseHead,
1356 nsILoadInfo *aLoadInfo) {
1357 if (!aURI || !aResponseHead || !aLoadInfo) {
1358 // if there is no uri, no response head or no loadInfo, then there is
1363 if (aLoadInfo->GetExternalContentPolicyType() !=
1364 nsIContentPolicy::TYPE_SCRIPT) {
1365 // if this is not a script load, then there is nothing to do
1369 nsAutoCString contentType;
1370 aResponseHead->ContentType(contentType);
1371 NS_ConvertUTF8toUTF16 typeString(contentType);
1373 if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
1374 // script load has type script
1375 AccumulateCategorical(
1376 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::javaScript);
1380 switch (aLoadInfo->InternalContentPolicyType()) {
1381 case nsIContentPolicy::TYPE_SCRIPT:
1382 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT:
1383 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD:
1384 case nsIContentPolicy::TYPE_INTERNAL_MODULE:
1385 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD:
1386 AccumulateCategorical(
1387 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::script_load);
1389 case nsIContentPolicy::TYPE_INTERNAL_WORKER:
1390 case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
1391 AccumulateCategorical(
1392 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::worker_load);
1394 case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
1395 AccumulateCategorical(
1396 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::serviceworker_load);
1398 case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS:
1399 AccumulateCategorical(
1400 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::importScript_load);
1403 MOZ_ASSERT_UNREACHABLE("unexpected script type");
1407 nsCOMPtr<nsIURI> requestURI;
1408 aLoadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(requestURI));
1410 nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
1411 bool isPrivateWin = aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
1412 nsresult rv = ssm->CheckSameOriginURI(requestURI, aURI, false, isPrivateWin);
1413 if (NS_SUCCEEDED(rv)) {
1415 AccumulateCategorical(
1416 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::same_origin);
1419 nsAutoCString corsOrigin;
1420 rv = aResponseHead->GetHeader(
1421 nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin);
1422 if (NS_SUCCEEDED(rv)) {
1423 if (corsOrigin.Equals("*")) {
1426 nsCOMPtr<nsIURI> corsOriginURI;
1427 rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
1428 if (NS_SUCCEEDED(rv)) {
1430 aLoadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
1431 rv = ssm->CheckSameOriginURI(requestURI, corsOriginURI, false,
1433 if (NS_SUCCEEDED(rv)) {
1441 AccumulateCategorical(
1442 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::CORS_origin);
1445 AccumulateCategorical(
1446 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::cross_origin);
1451 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
1452 // script load has type image
1453 AccumulateCategorical(
1454 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::image);
1456 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
1457 // script load has type audio
1458 AccumulateCategorical(
1459 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::audio);
1461 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
1462 // script load has type video
1463 AccumulateCategorical(
1464 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::video);
1466 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
1467 // script load has type text/csv
1468 AccumulateCategorical(
1469 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_csv);
1474 // Instead of consulting Preferences::GetBool() all the time we
1475 // can cache the result to speed things up.
1476 static bool sCachedBlockScriptWithWrongMime = false;
1477 static bool sIsInited = false;
1480 Preferences::AddBoolVarCache(&sCachedBlockScriptWithWrongMime,
1481 "security.block_script_with_wrong_mime",
1485 // Do not block the load if the feature is not enabled.
1486 if (!sCachedBlockScriptWithWrongMime) {
1490 ReportMimeTypeMismatch(aChannel, "BlockScriptWithWrongMimeType2", aURI,
1491 contentType, Report::Error);
1492 return NS_ERROR_CORRUPTED_CONTENT;
1495 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
1496 // script load has type text/plain
1497 AccumulateCategorical(
1498 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_plain);
1499 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
1500 // script load has type text/xml
1501 AccumulateCategorical(
1502 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_xml);
1503 } else if (StringBeginsWith(contentType,
1504 NS_LITERAL_CSTRING("application/octet-stream"))) {
1505 // script load has type application/octet-stream
1506 AccumulateCategorical(
1507 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_octet_stream);
1508 } else if (StringBeginsWith(contentType,
1509 NS_LITERAL_CSTRING("application/xml"))) {
1510 // script load has type application/xml
1511 AccumulateCategorical(
1512 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_xml);
1513 } else if (StringBeginsWith(contentType,
1514 NS_LITERAL_CSTRING("application/json"))) {
1515 // script load has type application/json
1516 AccumulateCategorical(
1517 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::app_json);
1518 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/json"))) {
1519 // script load has type text/json
1520 AccumulateCategorical(
1521 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_json);
1522 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
1523 // script load has type text/html
1524 AccumulateCategorical(
1525 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::text_html);
1526 } else if (contentType.IsEmpty()) {
1527 // script load has no type
1528 AccumulateCategorical(
1529 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::empty);
1531 // script load has unknown type
1532 AccumulateCategorical(
1533 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_3::unknown);
1536 // We restrict importScripts() in worker code to JavaScript MIME types.
1537 nsContentPolicyType internalType = aLoadInfo->InternalContentPolicyType();
1538 if (internalType == nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS) {
1539 // Instead of consulting Preferences::GetBool() all the time we
1540 // can cache the result to speed things up.
1541 static bool sCachedBlockImportScriptsWithWrongMime = false;
1542 static bool sIsInited = false;
1545 Preferences::AddBoolVarCache(
1546 &sCachedBlockImportScriptsWithWrongMime,
1547 "security.block_importScripts_with_wrong_mime", true);
1550 // Do not block the load if the feature is not enabled.
1551 if (!sCachedBlockImportScriptsWithWrongMime) {
1555 ReportMimeTypeMismatch(aChannel, "BlockImportScriptsWithWrongMimeType",
1556 aURI, contentType, Report::Error);
1557 return NS_ERROR_CORRUPTED_CONTENT;
1560 // ES6 modules require a strict MIME type check.
1561 if (internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE ||
1562 internalType == nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD) {
1563 ReportMimeTypeMismatch(aChannel, "BlockModuleWithWrongMimeType", aURI,
1564 contentType, Report::Error);
1565 return NS_ERROR_CORRUPTED_CONTENT;
1571 // Warn when a load of type script uses a wrong MIME type and
1572 // wasn't blocked by EnsureMIMEOfScript or ProcessXCTO.
1573 void WarnWrongMIMEOfScript(nsHttpChannel *aChannel, nsIURI *aURI,
1574 nsHttpResponseHead *aResponseHead,
1575 nsILoadInfo *aLoadInfo) {
1576 if (!aURI || !aResponseHead || !aLoadInfo) {
1577 // If there is no uri, no response head or no loadInfo, then there is
1582 if (aLoadInfo->GetExternalContentPolicyType() !=
1583 nsIContentPolicy::TYPE_SCRIPT) {
1584 // If this is not a script load, then there is nothing to do.
1588 nsAutoCString contentType;
1589 aResponseHead->ContentType(contentType);
1590 NS_ConvertUTF8toUTF16 typeString(contentType);
1591 if (!nsContentUtils::IsJavascriptMIMEType(typeString)) {
1592 ReportMimeTypeMismatch(aChannel, "WarnScriptWithWrongMimeType", aURI,
1593 contentType, Report::Warning);
1597 nsresult nsHttpChannel::CallOnStartRequest() {
1598 LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
1600 MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
1601 "CORS preflight must have been finished by the time we "
1602 "call OnStartRequest");
1604 if (mOnStartRequestCalled) {
1605 // This can only happen when a range request loading rest of the data
1606 // after interrupted concurrent cache read asynchronously failed, e.g.
1607 // the response range bytes are not as expected or this channel has
1608 // been externally canceled.
1610 // It's legal to bypass CallOnStartRequest for that case since we've
1611 // already called OnStartRequest on our listener and also added all
1612 // content converters before.
1613 MOZ_ASSERT(mConcurrentCacheAccess);
1614 LOG(("CallOnStartRequest already invoked before"));
1618 mTracingEnabled = false;
1620 // Ensure mListener->OnStartRequest will be invoked before exiting
1622 auto onStartGuard = MakeScopeExit([&] {
1624 (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
1626 this, mListener.get()));
1627 MOZ_ASSERT(!mOnStartRequestCalled);
1630 nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
1631 deleteProtector->OnStartRequest(this, nullptr);
1634 mOnStartRequestCalled = true;
1637 nsresult rv = EnsureMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);
1638 NS_ENSURE_SUCCESS(rv, rv);
1640 rv = ProcessXCTO(this, mURI, mResponseHead, mLoadInfo);
1641 NS_ENSURE_SUCCESS(rv, rv);
1643 WarnWrongMIMEOfScript(this, mURI, mResponseHead, mLoadInfo);
1645 // Allow consumers to override our content type
1646 if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
1647 // NOTE: We can have both a txn pump and a cache pump when the cache
1648 // content is partial. In that case, we need to read from the cache,
1649 // because that's the one that has the initial contents. If that fails
1650 // then give the transaction pump a shot.
1652 nsIChannel *thisChannel = static_cast<nsIChannel *>(this);
1654 bool typeSniffersCalled = false;
1656 typeSniffersCalled =
1657 NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
1660 if (!typeSniffersCalled && mTransactionPump) {
1661 mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
1665 bool unknownDecoderStarted = false;
1666 if (mResponseHead && !mResponseHead->HasContentType()) {
1667 MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
1668 if (!mContentTypeHint.IsEmpty())
1669 mResponseHead->SetContentType(mContentTypeHint);
1670 else if (mResponseHead->Version() == HttpVersion::v0_9 &&
1671 mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
1672 mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
1674 // Uh-oh. We had better find out what type we are!
1675 nsCOMPtr<nsIStreamConverterService> serv;
1676 rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
1677 // If we failed, we just fall through to the "normal" case
1678 if (NS_SUCCEEDED(rv)) {
1679 nsCOMPtr<nsIStreamListener> converter;
1680 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
1681 nullptr, getter_AddRefs(converter));
1682 if (NS_SUCCEEDED(rv)) {
1683 mListener = converter;
1684 unknownDecoderStarted = true;
1690 if (mResponseHead && !mResponseHead->HasContentCharset())
1691 mResponseHead->SetContentCharset(mContentCharsetHint);
1693 LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
1696 // About to call OnStartRequest, dismiss the guard object.
1697 onStartGuard.release();
1700 MOZ_ASSERT(!mOnStartRequestCalled,
1701 "We should not call OsStartRequest twice");
1702 nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
1703 rv = deleteProtector->OnStartRequest(this, nullptr);
1704 mOnStartRequestCalled = true;
1705 if (NS_FAILED(rv)) return rv;
1707 NS_WARNING("OnStartRequest skipped because of null listener");
1708 mOnStartRequestCalled = true;
1711 // Install stream converter if required.
1712 // If we use unknownDecoder, stream converters will be installed later (in
1713 // nsUnknownDecoder) after OnStartRequest is called for the real listener.
1714 if (!unknownDecoderStarted) {
1715 nsCOMPtr<nsIStreamListener> listener;
1717 DoApplyContentConversions(mListener, getter_AddRefs(listener), nullptr);
1718 if (NS_FAILED(rv)) {
1722 mListener = listener;
1723 mCompressListener = listener;
1727 // if this channel is for a download, close off access to the cache.
1728 if (mCacheEntry && mChannelIsForDownload) {
1729 mCacheEntry->AsyncDoom(nullptr);
1731 // We must keep the cache entry in case of partial request.
1732 // Concurrent access is the same, we need the entry in
1734 // We also need the cache entry when racing cache with network to find
1735 // out what is the source of the data.
1736 if (!mCachedContentIsPartial && !mConcurrentCacheAccess &&
1737 !(mRaceCacheWithNetwork &&
1738 mFirstResponseSource == RESPONSE_FROM_CACHE)) {
1739 CloseCacheEntry(false);
1744 // create offline cache entry if offline caching was requested
1745 if (ShouldUpdateOfflineCacheEntry()) {
1746 LOG(("writing to the offline cache"));
1747 rv = InitOfflineCacheEntry();
1748 if (NS_FAILED(rv)) return rv;
1750 // InitOfflineCacheEntry may have closed mOfflineCacheEntry
1751 if (mOfflineCacheEntry) {
1752 rv = InstallOfflineCacheListener();
1753 if (NS_FAILED(rv)) return rv;
1755 } else if (mApplicationCacheForWrite) {
1756 LOG(("offline cache is up to date, not updating"));
1757 CloseOfflineCacheEntry();
1761 // Check for a Content-Signature header and inject mediator if the header is
1762 // requested and available.
1763 // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
1764 // present but not valid, fail this channel and return
1765 // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
1766 // fallback load in nsDocShell.
1767 // Note that OnStartRequest has already been called on the target stream
1768 // listener at this point. We have to add the listener here that late to
1769 // ensure that it's the last listener and can thus block the load in
1772 rv = ProcessContentSignatureHeader(mResponseHead);
1773 if (NS_FAILED(rv)) {
1774 LOG(("Content-signature verification failed.\n"));
1782 nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
1783 // Failure to set up a proxy tunnel via CONNECT means one of the following:
1784 // 1) Proxy wants authorization, or forbids.
1785 // 2) DNS at proxy couldn't resolve target URL.
1786 // 3) Proxy connection to target failed or timed out.
1787 // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
1789 // Our current architecture would parse the proxy's response content with
1790 // the permission of the target URL. Given #4, we must avoid rendering the
1791 // body of the reply, and instead give the user a (hopefully helpful)
1792 // boilerplate error page, based on just the HTTP status of the reply.
1794 MOZ_ASSERT(mConnectionInfo->UsingConnect(),
1795 "proxy connect failed but not using CONNECT?");
1797 switch (httpStatus) {
1804 // Bad redirect: not top-level, or it's a POST, bad/missing Location,
1805 // or ProcessRedirect() failed for some other reason. Legal
1806 // redirects that fail because site not available, etc., are handled
1807 // elsewhere, in the regular codepath.
1808 rv = NS_ERROR_CONNECTION_REFUSED;
1810 case 403: // HTTP/1.1: "Forbidden"
1811 case 407: // ProcessAuthentication() failed
1812 case 501: // HTTP/1.1: "Not Implemented"
1813 // user sees boilerplate Mozilla "Proxy Refused Connection" page.
1814 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
1816 // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
1817 case 404: // HTTP/1.1: "Not Found"
1818 // RFC 2616: "some deployed proxies are known to return 400 or 500 when
1819 // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
1820 // we have a conflict here).
1821 case 400: // HTTP/1.1 "Bad Request"
1822 case 500: // HTTP/1.1: "Internal Server Error"
1823 /* User sees: "Address Not Found: Firefox can't find the server at
1826 rv = NS_ERROR_UNKNOWN_HOST;
1828 case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
1829 // Squid returns 503 if target request fails for anything but DNS.
1830 case 503: // HTTP/1.1: "Service Unavailable"
1831 /* User sees: "Failed to Connect:
1832 * Firefox can't establish a connection to the server at
1833 * www.foo.com. Though the site seems valid, the browser
1834 * was unable to establish a connection."
1836 rv = NS_ERROR_CONNECTION_REFUSED;
1838 // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
1839 // do here: picking target timeout, as DNS covered by 400/404/500
1840 case 504: // HTTP/1.1: "Gateway Timeout"
1841 // user sees: "Network Timeout: The server at www.foo.com
1842 // is taking too long to respond."
1843 rv = NS_ERROR_NET_TIMEOUT;
1845 // Confused proxy server or malicious response
1847 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
1850 LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
1854 nsresult rv = CallOnStartRequest();
1855 if (NS_FAILED(rv)) {
1856 LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
1857 httpStatus, static_cast<uint32_t>(rv)));
1863 static void GetSTSConsoleErrorTag(uint32_t failureResult,
1864 nsAString &consoleErrorTag) {
1865 switch (failureResult) {
1866 case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
1867 consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
1869 case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
1870 consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
1872 case nsISiteSecurityService::ERROR_NO_MAX_AGE:
1873 consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
1875 case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
1876 consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
1878 case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
1879 consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
1881 case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
1882 consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
1884 case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
1885 consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
1887 case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
1888 consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
1891 consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
1896 static void GetPKPConsoleErrorTag(uint32_t failureResult,
1897 nsAString &consoleErrorTag) {
1898 switch (failureResult) {
1899 case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
1900 consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
1902 case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
1903 consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
1905 case nsISiteSecurityService::ERROR_NO_MAX_AGE:
1906 consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
1908 case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
1909 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
1911 case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
1912 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
1914 case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
1915 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
1917 case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
1918 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
1920 case nsISiteSecurityService::ERROR_INVALID_PIN:
1921 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
1923 case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
1924 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
1926 case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
1927 consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
1929 case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
1930 consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
1932 case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
1933 consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
1935 case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
1936 consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
1939 consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
1945 * Process a single security header. Only two types are supported: HSTS and
1948 nsresult nsHttpChannel::ProcessSingleSecurityHeader(
1949 uint32_t aType, nsITransportSecurityInfo *aSecInfo, uint32_t aFlags) {
1952 case nsISiteSecurityService::HEADER_HSTS:
1953 atom = nsHttp::ResolveAtom("Strict-Transport-Security");
1955 case nsISiteSecurityService::HEADER_HPKP:
1956 atom = nsHttp::ResolveAtom("Public-Key-Pins");
1959 MOZ_ASSERT_UNREACHABLE("Invalid security header type");
1960 return NS_ERROR_FAILURE;
1963 nsAutoCString securityHeader;
1964 nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
1965 if (NS_SUCCEEDED(rv)) {
1966 nsISiteSecurityService *sss = gHttpHandler->GetSSService();
1967 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
1968 // Process header will now discard the headers itself if the channel
1969 // wasn't secure (whereas before it had to be checked manually)
1970 OriginAttributes originAttributes;
1971 NS_GetOriginAttributes(this, originAttributes);
1972 uint32_t failureResult;
1973 uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
1974 rv = sss->ProcessHeader(aType, mURI, securityHeader, aSecInfo, aFlags,
1975 headerSource, originAttributes, nullptr, nullptr,
1977 if (NS_FAILED(rv)) {
1978 nsAutoString consoleErrorCategory;
1979 nsAutoString consoleErrorTag;
1981 case nsISiteSecurityService::HEADER_HSTS:
1982 GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
1983 consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
1985 case nsISiteSecurityService::HEADER_HPKP:
1986 GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
1987 consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
1990 return NS_ERROR_FAILURE;
1992 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
1993 LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
1997 if (rv != NS_ERROR_NOT_AVAILABLE) {
1998 // All other errors are fatal
1999 NS_ENSURE_SUCCESS(rv, rv);
2001 LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
2007 * Decide whether or not to remember Strict-Transport-Security, and whether
2008 * or not to enforce channel integrity.
2010 * @return NS_ERROR_FAILURE if there's security information missing even though
2011 * it's an HTTPS connection.
2013 nsresult nsHttpChannel::ProcessSecurityHeaders() {
2015 bool isHttps = false;
2016 rv = mURI->SchemeIs("https", &isHttps);
2017 NS_ENSURE_SUCCESS(rv, rv);
2019 // If this channel is not loading securely, STS or PKP doesn't do anything.
2020 // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
2021 // channel load process.
2022 if (!isHttps) return NS_OK;
2024 nsAutoCString asciiHost;
2025 rv = mURI->GetAsciiHost(asciiHost);
2026 NS_ENSURE_SUCCESS(rv, NS_OK);
2028 // If the channel is not a hostname, but rather an IP, do not process STS
2031 if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
2034 // mSecurityInfo may not always be present, and if it's not then it is okay
2035 // to just disregard any security headers since we know nothing about the
2036 // security of the connection.
2037 NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
2040 NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
2042 // Get the TransportSecurityInfo
2043 nsCOMPtr<nsITransportSecurityInfo> transSecInfo =
2044 do_QueryInterface(mSecurityInfo);
2045 NS_ENSURE_TRUE(transSecInfo, NS_ERROR_FAILURE);
2047 rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
2048 transSecInfo, flags);
2049 NS_ENSURE_SUCCESS(rv, rv);
2051 rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
2052 transSecInfo, flags);
2053 NS_ENSURE_SUCCESS(rv, rv);
2058 nsresult nsHttpChannel::ProcessContentSignatureHeader(
2059 nsHttpResponseHead *aResponseHead) {
2060 nsresult rv = NS_OK;
2062 // we only do this if we require it in loadInfo
2063 if (!mLoadInfo || !mLoadInfo->GetVerifySignedContent()) {
2067 NS_ENSURE_TRUE(aResponseHead, NS_ERROR_ABORT);
2068 nsAutoCString contentSignatureHeader;
2069 nsHttpAtom atom = nsHttp::ResolveAtom("Content-Signature");
2070 rv = aResponseHead->GetHeader(atom, contentSignatureHeader);
2071 if (NS_FAILED(rv)) {
2072 LOG(("Content-Signature header is missing but expected."));
2073 DoInvalidateCacheEntry(mURI);
2074 return NS_ERROR_INVALID_SIGNATURE;
2077 // if we require a signature but it is empty, fail
2078 if (contentSignatureHeader.IsEmpty()) {
2079 DoInvalidateCacheEntry(mURI);
2080 LOG(("An expected content-signature header is missing.\n"));
2081 return NS_ERROR_INVALID_SIGNATURE;
2084 // we ensure a content type here to avoid running into problems with
2085 // content sniffing, which might sniff parts of the content before we can
2086 // verify the signature
2087 if (!aResponseHead->HasContentType()) {
2089 "Empty content type can get us in trouble when verifying "
2090 "content signatures");
2091 return NS_ERROR_INVALID_SIGNATURE;
2093 // create a new listener that meadiates the content
2094 RefPtr<ContentVerifier> contentVerifyingMediator =
2095 new ContentVerifier(mListener, nullptr);
2096 rv = contentVerifyingMediator->Init(contentSignatureHeader, this, nullptr);
2097 NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
2098 mListener = contentVerifyingMediator;
2104 * Decide whether or not to send a security report and, if so, give the
2105 * SecurityReporter the information required to send such a report.
2107 void nsHttpChannel::ProcessSecurityReport(nsresult status) {
2108 uint32_t errorClass;
2109 nsCOMPtr<nsINSSErrorsService> errSvc =
2110 do_GetService("@mozilla.org/nss_errors_service;1");
2111 // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
2112 // not in the set of errors covered by the NSS errors service.
2113 nsresult rv = errSvc->GetErrorClass(status, &errorClass);
2114 if (!NS_SUCCEEDED(rv)) {
2118 // if the content was not loaded succesfully and we have security info,
2119 // send a TLS error report - we must do this early as other parts of
2120 // OnStopRequest can return early
2121 bool reportingEnabled =
2122 Preferences::GetBool("security.ssl.errorReporting.enabled");
2123 bool reportingAutomatic =
2124 Preferences::GetBool("security.ssl.errorReporting.automatic");
2125 if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
2129 nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(mSecurityInfo);
2130 nsCOMPtr<nsISecurityReporter> errorReporter =
2131 do_GetService("@mozilla.org/securityreporter;1");
2133 if (!secInfo || !mURI) {
2137 nsAutoCString hostStr;
2139 rv = mURI->GetHost(hostStr);
2140 if (!NS_SUCCEEDED(rv)) {
2144 rv = mURI->GetPort(&port);
2146 if (NS_SUCCEEDED(rv)) {
2147 errorReporter->ReportTLSError(secInfo, hostStr, port);
2151 bool nsHttpChannel::IsHTTPS() {
2153 if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps) return false;
2157 void nsHttpChannel::ProcessSSLInformation() {
2158 // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
2159 // can be whitelisted for TLS False Start in future sessions. We could
2160 // do the same for DH but its rarity doesn't justify the lookup.
2162 if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
2166 nsCOMPtr<nsITransportSecurityInfo> securityInfo =
2167 do_QueryInterface(mSecurityInfo);
2168 if (!securityInfo) return;
2171 if (securityInfo && NS_SUCCEEDED(securityInfo->GetSecurityState(&state)) &&
2172 (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
2173 // Send weak crypto warnings to the web console
2174 if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
2175 nsString consoleErrorTag = NS_LITERAL_STRING("WeakCipherSuiteWarning");
2176 nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
2177 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
2181 // Send (SHA-1) signature algorithm errors to the web console
2182 nsCOMPtr<nsIX509Cert> cert;
2183 securityInfo->GetServerCert(getter_AddRefs(cert));
2185 UniqueCERTCertificate nssCert(cert->GetCert());
2187 SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
2188 LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag,
2190 // Check to see if the signature is sha-1 based.
2191 // Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
2192 // from http://tools.ietf.org/html/rfc2437#section-8 since I
2193 // can't see reference to it outside this spec
2194 if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
2195 tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
2196 tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
2197 nsString consoleErrorTag = NS_LITERAL_STRING("SHA1Sig");
2198 nsString consoleErrorMessage = NS_LITERAL_STRING("SHA-1 Signature");
2199 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
2205 void nsHttpChannel::ProcessAltService() {
2206 // e.g. Alt-Svc: h2=":443"; ma=60
2207 // e.g. Alt-Svc: h2="otherhost:443"
2208 // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
2209 // alternative = protocol-id "=" alt-authority
2210 // protocol-id = token ; percent-encoded ALPN protocol identifier
2211 // alt-authority = quoted-string ; containing [ uri-host ] ":" port
2213 if (!mAllowAltSvc) { // per channel opt out
2217 if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
2221 nsAutoCString scheme;
2222 mURI->GetScheme(scheme);
2223 bool isHttp = scheme.EqualsLiteral("http");
2224 if (!isHttp && !scheme.EqualsLiteral("https")) {
2228 nsAutoCString altSvc;
2229 Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
2230 if (altSvc.IsEmpty()) {
2234 if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
2235 LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
2239 nsAutoCString originHost;
2240 int32_t originPort = 80;
2241 mURI->GetPort(&originPort);
2242 if (NS_FAILED(mURI->GetAsciiHost(originHost))) {
2246 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2247 nsCOMPtr<nsProxyInfo> proxyInfo;
2248 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
2249 getter_AddRefs(callbacks));
2251 proxyInfo = do_QueryInterface(mProxyInfo);
2254 OriginAttributes originAttributes;
2255 NS_GetOriginAttributes(this, originAttributes);
2257 AltSvcMapping::ProcessHeader(
2258 altSvc, scheme, originHost, originPort, mUsername, mPrivateBrowsing,
2259 callbacks, proxyInfo, mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
2262 nsresult nsHttpChannel::ProcessResponse() {
2263 uint32_t httpStatus = mResponseHead->Status();
2265 LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
2268 // Gather data on whether the transaction and page (if this is
2269 // the initial page load) is being loaded with SSL.
2270 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
2271 mConnectionInfo->EndToEndSSL());
2272 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
2273 Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
2274 mConnectionInfo->EndToEndSSL());
2277 if (Telemetry::CanRecordPrereleaseData()) {
2278 // how often do we see something like Alt-Svc: "443:quic,p=1"
2279 nsAutoCString alt_service;
2280 Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
2282 (!alt_service.IsEmpty() && PL_strstr(alt_service.get(), "quic"))
2285 Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);
2287 // Gather data on how many URLS get redirected
2288 switch (httpStatus) {
2290 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
2293 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
2296 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
2299 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
2302 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
2305 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
2308 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
2311 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
2314 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
2317 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
2320 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
2323 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
2328 // Let the predictor know whether this was a cacheable response or not so
2329 // that it knows whether or not to possibly prefetch this resource in the
2331 // We use GetReferringPage because mReferrer may not be set at all, or may
2332 // not be a full URI (HttpBaseChannel::SetReferrer has the gorey details).
2333 // If that's null, though, we'll fall back to mReferrer just in case (this
2334 // is especially useful in xpcshell tests, where we don't have an actual
2335 // pageload to get a referrer from).
2336 nsCOMPtr<nsIURI> referrer = GetReferringPage();
2338 referrer = mReferrer;
2342 nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
2343 mozilla::net::Predictor::UpdateCacheability(
2344 referrer, mURI, httpStatus, mRequestHead, mResponseHead, lci,
2345 mIsThirdPartyTrackingResource);
2348 // Only allow 407 (authentication required) to continue
2349 if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
2350 return ProcessFailedProxyConnect(httpStatus);
2353 MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
2354 "We should not be hitting the network if we have valid cached "
2355 "content unless we are racing the network and cache");
2357 ProcessSSLInformation();
2359 // notify "http-on-examine-response" observers
2360 gHttpHandler->OnExamineResponse(this);
2362 return ContinueProcessResponse1();
2365 void nsHttpChannel::AsyncContinueProcessResponse() {
2367 rv = ContinueProcessResponse1();
2368 if (NS_FAILED(rv)) {
2369 // A synchronous failure here would normally be passed as the return
2370 // value from OnStartRequest, which would in turn cancel the request.
2371 // If we're continuing asynchronously, we need to cancel the request
2373 Unused << Cancel(rv);
2377 nsresult nsHttpChannel::ContinueProcessResponse1() {
2378 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
2381 if (mSuspendCount) {
2382 LOG(("Waiting until resume to finish processing response [this=%p]\n",
2384 mCallOnResume = [](nsHttpChannel *self) {
2385 self->AsyncContinueProcessResponse();
2391 // Check if request was cancelled during http-on-examine-response.
2393 return CallOnStartRequest();
2396 uint32_t httpStatus = mResponseHead->Status();
2398 // STS, Cookies and Alt-Service should not be handled on proxy failure.
2399 // If proxy CONNECT response needs to complete, wait to process connection
2400 // for Strict-Transport-Security.
2401 if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
2402 (httpStatus != 407)) {
2403 nsAutoCString cookie;
2404 if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
2405 SetCookie(cookie.get());
2408 // Given a successful connection, process any STS or PKP data that's
2410 DebugOnly<nsresult> rv = ProcessSecurityHeaders();
2411 MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
2413 if ((httpStatus < 500) && (httpStatus != 421)) {
2414 ProcessAltService();
2418 if (mConcurrentCacheAccess && mCachedContentIsPartial && httpStatus != 206) {
2420 (" only expecting 206 when doing partial request during "
2421 "interrupted cache concurrent read"));
2422 return NS_ERROR_CORRUPTED_CONTENT;
2425 // handle unused username and password in url (see bug 232567)
2426 if (httpStatus != 401 && httpStatus != 407) {
2427 if (!mAuthRetryPending) {
2428 rv = mAuthProvider->CheckForSuperfluousAuth();
2429 if (NS_FAILED(rv)) {
2430 LOG((" CheckForSuperfluousAuth failed (%08x)",
2431 static_cast<uint32_t>(rv)));
2434 if (mCanceled) return CallOnStartRequest();
2436 // reset the authentication's current continuation state because ourvr
2437 // last authentication attempt has been completed successfully
2438 rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
2439 if (NS_FAILED(rv)) {
2440 LOG((" Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
2442 mAuthProvider = nullptr;
2443 LOG((" continuation state has been reset"));
2447 if (mRedirectTabPromise && !mCanceled) {
2448 MOZ_ASSERT(!mOnStartRequestCalled);
2450 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
2451 rv = StartCrossProcessRedirect();
2452 if (NS_SUCCEEDED(rv)) {
2455 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
2458 // No process switch needed, continue as normal.
2459 return ContinueProcessResponse2(rv);
2462 nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
2463 if (NS_FAILED(rv) && !mCanceled) {
2464 // The process switch failed, cancel this channel.
2466 return CallOnStartRequest();
2469 if (mAPIRedirectToURI && !mCanceled) {
2470 MOZ_ASSERT(!mOnStartRequestCalled);
2471 nsCOMPtr<nsIURI> redirectTo;
2472 mAPIRedirectToURI.swap(redirectTo);
2474 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
2475 rv = StartRedirectChannelToURI(redirectTo,
2476 nsIChannelEventSink::REDIRECT_TEMPORARY);
2477 if (NS_SUCCEEDED(rv)) {
2480 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
2483 // Hack: ContinueProcessResponse3 uses NS_OK to detect successful
2484 // redirects, so we distinguish this codepath (a non-redirect that's
2485 // processing normally) by passing in a bogus error code.
2486 return ContinueProcessResponse3(NS_BINDING_FAILED);
2489 nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
2490 LOG(("nsHttpChannel::ContinueProcessResponse3 [this=%p, rv=%" PRIx32 "]",
2491 this, static_cast<uint32_t>(rv)));
2493 if (NS_SUCCEEDED(rv)) {
2494 // redirectTo() has passed through, we don't want to go on with
2495 // this channel. It will now be canceled by the redirect handling
2496 // code that called this function.
2502 uint32_t httpStatus = mResponseHead->Status();
2504 // handle different server response categories. Note that we handle
2505 // caching or not caching of error pages in
2506 // nsHttpResponseHead::MustValidate; if you change this switch, update that
2508 switch (httpStatus) {
2511 // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
2512 // So if a server does that and sends 200 instead of 206 that we
2513 // expect, notify our caller.
2514 // However, if we wanted to start from the beginning, let it go through
2515 if (mResuming && mStartPos != 0) {
2516 LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
2517 Cancel(NS_ERROR_NOT_RESUMABLE);
2518 rv = CallOnStartRequest();
2521 // these can normally be cached
2522 rv = ProcessNormal();
2523 MaybeInvalidateCacheEntryForSubsequentGet();
2526 if (mCachedContentIsPartial) { // an internal byte range request...
2527 auto func = [](auto *self, nsresult aRv) {
2528 return self->ContinueProcessResponseAfterPartialContent(aRv);
2530 rv = ProcessPartialContent(func);
2531 // Directly call ContinueProcessResponseAfterPartialContent if channel
2532 // is not suspended or ProcessPartialContent throws.
2533 if (!mSuspendCount || NS_FAILED(rv)) {
2534 return ContinueProcessResponseAfterPartialContent(rv);
2538 mCacheInputStream.CloseAndRelease();
2539 rv = ProcessNormal();
2549 case 305: // disabled as a security measure (see bug 187996).
2551 // don't store the response body for redirects
2552 MaybeInvalidateCacheEntryForSubsequentGet();
2553 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
2554 rv = AsyncProcessRedirection(httpStatus);
2555 if (NS_FAILED(rv)) {
2556 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse4);
2557 LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
2558 static_cast<uint32_t>(rv)));
2559 // don't cache failed redirect responses.
2560 if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
2561 if (DoNotRender3xxBody(rv)) {
2565 rv = ContinueProcessResponse4(rv);
2570 if (!ShouldBypassProcessNotModified()) {
2571 auto func = [](auto *self, nsresult aRv) {
2572 return self->ContinueProcessResponseAfterNotModified(aRv);
2574 rv = ProcessNotModified(func);
2575 // Directly call ContinueProcessResponseAfterNotModified if channel
2576 // is not suspended or ProcessNotModified throws.
2577 if (!mSuspendCount || NS_FAILED(rv)) {
2578 return ContinueProcessResponseAfterNotModified(rv);
2583 // Don't cache uninformative 304
2584 if (mCustomConditionalRequest) {
2585 CloseCacheEntry(false);
2588 if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
2589 rv = ProcessNormal();
2594 if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
2595 // When a custom auth header fails, we don't want to try
2596 // any cached credentials, nor we want to ask the user.
2597 // It's up to the consumer to re-try w/o setting a custom
2598 // auth header if cached credentials should be attempted.
2599 rv = NS_ERROR_FAILURE;
2601 rv = mAuthProvider->ProcessAuthentication(
2602 httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
2603 mTransaction->ProxyConnectFailed());
2605 if (rv == NS_ERROR_IN_PROGRESS) {
2606 // authentication prompt has been invoked and result
2607 // is expected asynchronously
2608 mAuthRetryPending = true;
2609 if (httpStatus == 407 ||
2610 (mTransaction && mTransaction->ProxyConnectFailed()))
2611 mProxyAuthPending = true;
2613 // suspend the transaction pump to stop receiving the
2614 // unauthenticated content data. We will throw that data
2615 // away when user provides credentials or resume the pump
2616 // when user refuses to authenticate.
2618 ("Suspending the transaction, asynchronously prompting for "
2620 mTransactionPump->Suspend();
2622 } else if (NS_FAILED(rv)) {
2623 LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
2624 static_cast<uint32_t>(rv)));
2625 if (mTransaction && mTransaction->ProxyConnectFailed()) {
2626 return ProcessFailedProxyConnect(httpStatus);
2628 if (!mAuthRetryPending) {
2629 rv = mAuthProvider->CheckForSuperfluousAuth();
2630 if (NS_FAILED(rv)) {
2631 LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
2632 static_cast<uint32_t>(rv)));
2635 rv = ProcessNormal();
2637 mAuthRetryPending = true; // see DoAuthRetry
2642 // Do not cache 425.
2643 CloseCacheEntry(false);
2644 MOZ_FALLTHROUGH; // process normally
2646 rv = ProcessNormal();
2647 MaybeInvalidateCacheEntryForSubsequentGet();
2651 UpdateCacheDisposition(false, false);
2655 nsresult nsHttpChannel::ContinueProcessResponseAfterPartialContent(
2658 ("nsHttpChannel::ContinueProcessResponseAfterPartialContent "
2659 "[this=%p, rv=%" PRIx32 "]",
2660 this, static_cast<uint32_t>(aRv)));
2662 UpdateCacheDisposition(false, NS_SUCCEEDED(aRv));
2666 nsresult nsHttpChannel::ContinueProcessResponseAfterNotModified(nsresult aRv) {
2668 ("nsHttpChannel::ContinueProcessResponseAfterNotModified "
2669 "[this=%p, rv=%" PRIx32 "]",
2670 this, static_cast<uint32_t>(aRv)));
2672 if (NS_SUCCEEDED(aRv)) {
2673 mTransactionReplaced = true;
2674 UpdateCacheDisposition(true, false);
2678 LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
2679 static_cast<uint32_t>(aRv)));
2681 // We cannot read from the cache entry, it might be in an
2682 // incosistent state. Doom it and redirect the channel
2683 // to the same URI to reload from the network.
2684 mCacheInputStream.CloseAndRelease();
2686 mCacheEntry->AsyncDoom(nullptr);
2687 mCacheEntry = nullptr;
2691 StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
2692 if (NS_SUCCEEDED(rv)) {
2696 // Don't cache uninformative 304
2697 if (mCustomConditionalRequest) {
2698 CloseCacheEntry(false);
2701 if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
2702 rv = ProcessNormal();
2705 UpdateCacheDisposition(false, false);
2709 void nsHttpChannel::UpdateCacheDisposition(bool aSuccessfulReval,
2710 bool aPartialContentUsed) {
2711 if (mRaceDelay && !mRaceCacheWithNetwork &&
2712 (mCachedContentIsPartial || mDidReval)) {
2713 if (aSuccessfulReval || aPartialContentUsed) {
2714 AccumulateCategorical(
2715 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
2717 AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
2718 CachedContentNotUsed);
2722 if (Telemetry::CanRecordPrereleaseData()) {
2723 CacheDisposition cacheDisposition;
2725 cacheDisposition = kCacheMissed;
2726 } else if (aSuccessfulReval) {
2727 cacheDisposition = kCacheHitViaReval;
2729 cacheDisposition = kCacheMissedViaReval;
2731 AccumulateCacheHitTelemetry(cacheDisposition);
2732 mCacheDisposition = cacheDisposition;
2734 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
2735 static_cast<uint32_t>(mResponseHead->Version()));
2737 if (mResponseHead->Version() == HttpVersion::v0_9) {
2738 // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
2739 // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
2740 uint32_t v09Info = 0;
2741 if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
2744 if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
2747 Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
2752 nsresult nsHttpChannel::ContinueProcessResponse4(nsresult rv) {
2753 bool doNotRender = DoNotRender3xxBody(rv);
2755 if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
2756 bool isHTTP = false;
2757 if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP))) isHTTP = false;
2758 if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
2762 // This was a blocked attempt to redirect and subvert the system by
2763 // redirecting to another protocol (perhaps javascript:)
2764 // In that case we want to throw an error instead of displaying the
2765 // non-redirected response body.
2766 LOG(("ContinueProcessResponse4 detected rejected Non-HTTP Redirection"));
2768 rv = NS_ERROR_CORRUPTED_CONTENT;
2778 if (NS_SUCCEEDED(rv)) {
2779 UpdateInhibitPersistentCachingFlag();
2781 rv = InitCacheEntry();
2782 if (NS_FAILED(rv)) {
2784 ("ContinueProcessResponse4 "
2785 "failed to init cache entry [rv=%x]\n",
2786 static_cast<uint32_t>(rv)));
2788 CloseCacheEntry(false);
2790 if (mApplicationCacheForWrite) {
2791 // Store response in the offline cache
2792 Unused << InitOfflineCacheEntry();
2793 CloseOfflineCacheEntry();
2798 LOG(("ContinueProcessResponse4 got failure result [rv=%" PRIx32 "]\n",
2799 static_cast<uint32_t>(rv)));
2800 if (mTransaction && mTransaction->ProxyConnectFailed()) {
2801 return ProcessFailedProxyConnect(mRedirectType);
2803 return ProcessNormal();
2806 nsresult nsHttpChannel::ProcessNormal() {
2809 LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
2812 rv = GetRequestSucceeded(&succeeded);
2813 if (NS_SUCCEEDED(rv) && !succeeded) {
2814 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
2815 bool waitingForRedirectCallback;
2816 Unused << ProcessFallback(&waitingForRedirectCallback);
2817 if (waitingForRedirectCallback) {
2818 // The transaction has been suspended by ProcessFallback.
2821 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
2824 return ContinueProcessNormal(NS_OK);
2827 nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
2828 LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));
2830 if (NS_FAILED(rv)) {
2831 // Fill the failure status here, we have failed to fall back, thus we
2832 // have to report our status as failed.
2839 // Do not continue with normal processing, fallback is in
2844 // if we're here, then any byte-range requests failed to result in a partial
2845 // response. we must clear this flag to prevent BufferPartialContent from
2846 // being called inside our OnDataAvailable (see bug 136678).
2847 mCachedContentIsPartial = false;
2849 ClearBogusContentEncodingIfNeeded();
2851 UpdateInhibitPersistentCachingFlag();
2853 // this must be called before firing OnStartRequest, since http clients,
2854 // such as imagelib, expect our cache entry to already have the correct
2855 // expiration time (bug 87710).
2857 rv = InitCacheEntry();
2858 if (NS_FAILED(rv)) CloseCacheEntry(true);
2861 // Check that the server sent us what we were asking for
2863 // Create an entity id from the response
2865 rv = GetEntityID(id);
2866 if (NS_FAILED(rv)) {
2867 // If creating an entity id is not possible -> error
2868 Cancel(NS_ERROR_NOT_RESUMABLE);
2869 } else if (mResponseHead->Status() != 206 &&
2870 mResponseHead->Status() != 200) {
2871 // Probably 404 Not Found, 412 Precondition Failed or
2872 // 416 Invalid Range -> error
2873 LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
2875 Cancel(NS_ERROR_ENTITY_CHANGED);
2877 // If we were passed an entity id, verify it's equal to the server's
2878 else if (!mEntityID.IsEmpty()) {
2879 if (!mEntityID.Equals(id)) {
2880 LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
2881 mEntityID.get(), id.get(), this));
2882 Cancel(NS_ERROR_ENTITY_CHANGED);
2887 rv = CallOnStartRequest();
2888 if (NS_FAILED(rv)) return rv;
2890 // install cache listener if we still have a cache entry open
2891 if (mCacheEntry && !mCacheEntryIsReadOnly) {
2892 rv = InstallCacheListener();
2893 if (NS_FAILED(rv)) return rv;
2899 nsresult nsHttpChannel::PromptTempRedirect() {
2900 if (!gHttpHandler->PromptTempRedirect()) {
2904 nsCOMPtr<nsIStringBundleService> bundleService =
2905 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
2906 if (NS_FAILED(rv)) return rv;
2908 nsCOMPtr<nsIStringBundle> stringBundle;
2910 bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
2911 if (NS_FAILED(rv)) return rv;
2913 nsAutoString messageString;
2914 rv = stringBundle->GetStringFromName("RepostFormData", messageString);
2915 if (NS_SUCCEEDED(rv)) {
2916 bool repost = false;
2918 nsCOMPtr<nsIPrompt> prompt;
2919 GetCallback(prompt);
2920 if (!prompt) return NS_ERROR_NO_INTERFACE;
2922 prompt->Confirm(nullptr, messageString.get(), &repost);
2923 if (!repost) return NS_ERROR_FAILURE;
2929 nsresult nsHttpChannel::ProxyFailover() {
2930 LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
2934 nsCOMPtr<nsIProtocolProxyService> pps =
2935 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
2936 if (NS_FAILED(rv)) return rv;
2938 nsCOMPtr<nsIProxyInfo> pi;
2939 rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
2940 getter_AddRefs(pi));
2941 if (NS_FAILED(rv)) return rv;
2943 // XXXbz so where does this codepath remove us from the loadgroup,
2945 return AsyncDoReplaceWithProxy(pi);
2948 void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
2949 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
2951 if (mSuspendCount) {
2952 LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
2954 mCallOnResume = [](nsHttpChannel *self) {
2955 self->HandleAsyncRedirectChannelToHttps();
2961 nsresult rv = StartRedirectChannelToHttps();
2962 if (NS_FAILED(rv)) {
2963 rv = ContinueAsyncRedirectChannelToURI(rv);
2964 if (NS_FAILED(rv)) {
2965 LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
2966 static_cast<uint32_t>(rv), this));
2971 nsresult nsHttpChannel::StartRedirectChannelToHttps() {
2972 LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
2974 nsCOMPtr<nsIURI> upgradedURI;
2975 nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
2976 NS_ENSURE_SUCCESS(rv, rv);
2978 return StartRedirectChannelToURI(
2979 upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
2980 nsIChannelEventSink::REDIRECT_STS_UPGRADE);
2983 void nsHttpChannel::HandleAsyncAPIRedirect() {
2984 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
2985 MOZ_ASSERT(mAPIRedirectToURI, "How did that happen?");
2987 if (mSuspendCount) {
2988 LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
2989 mCallOnResume = [](nsHttpChannel *self) {
2990 self->HandleAsyncAPIRedirect();
2996 nsresult rv = StartRedirectChannelToURI(
2997 mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
2998 if (NS_FAILED(rv)) {
2999 rv = ContinueAsyncRedirectChannelToURI(rv);
3000 if (NS_FAILED(rv)) {
3001 LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
3002 static_cast<uint32_t>(rv), this));
3007 nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI,
3009 nsresult rv = NS_OK;
3010 LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
3012 nsCOMPtr<nsIChannel> newChannel;
3013 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
3014 CloneLoadInfoForRedirect(upgradedURI, flags);
3016 nsCOMPtr<nsIIOService> ioService;
3017 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
3018 NS_ENSURE_SUCCESS(rv, rv);
3020 rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
3022 nullptr, // PerformanceStorage
3023 nullptr, // aLoadGroup
3024 nullptr, // aCallbacks
3025 nsIRequest::LOAD_NORMAL, ioService);
3026 NS_ENSURE_SUCCESS(rv, rv);
3028 rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
3029 NS_ENSURE_SUCCESS(rv, rv);
3031 // Inform consumers about this fake redirect
3032 mRedirectChannel = newChannel;
3034 PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
3035 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
3037 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
3039 if (NS_FAILED(rv)) {
3040 AutoRedirectVetoNotifier notifier(this);
3042 /* Remove the async call to ContinueAsyncRedirectChannelToURI().
3043 * It is called directly by our callers upon return (to clean up
3044 * the failed redirect). */
3045 PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
3051 nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
3052 LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));
3054 // Since we handle mAPIRedirectToURI also after on-examine-response handler
3055 // rather drop it here to avoid any redirect loops, even just hypothetical.
3056 mAPIRedirectToURI = nullptr;
3058 if (NS_SUCCEEDED(rv)) {
3059 rv = OpenRedirectChannel(rv);
3062 if (NS_FAILED(rv)) {
3063 // Cancel the channel here, the update to https had been vetoed
3064 // but from the security reasons we have to discard the whole channel
3070 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
3073 if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
3074 // We have to manually notify the listener because there is not any pump
3075 // that would call our OnStart/StopRequest after resume from waiting for
3076 // the redirect callback.
3083 nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
3084 AutoRedirectVetoNotifier notifier(this);
3086 // Make sure to do this after we received redirect veto answer,
3087 // i.e. after all sinks had been notified
3088 mRedirectChannel->SetOriginalURI(mOriginalURI);
3091 rv = mRedirectChannel->AsyncOpen(mListener);
3093 NS_ENSURE_SUCCESS(rv, rv);
3095 mStatus = NS_BINDING_REDIRECTED;
3097 notifier.RedirectSucceeded();
3104 nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo *pi) {
3105 LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
3108 nsCOMPtr<nsIChannel> newChannel;
3109 rv = gHttpHandler->NewProxiedChannel2(mURI, pi, mProxyResolveFlags, mProxyURI,
3110 mLoadInfo, getter_AddRefs(newChannel));
3111 if (NS_FAILED(rv)) return rv;
3113 uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
3115 rv = SetupReplacementChannel(mURI, newChannel, true, flags);
3116 if (NS_FAILED(rv)) return rv;
3118 // Inform consumers about this fake redirect
3119 mRedirectChannel = newChannel;
3121 PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
3122 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
3124 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
3126 if (NS_FAILED(rv)) {
3127 AutoRedirectVetoNotifier notifier(this);
3128 PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
3134 nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
3135 AutoRedirectVetoNotifier notifier(this);
3137 if (NS_FAILED(rv)) return rv;
3139 MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
3141 // Make sure to do this after we received redirect veto answer,
3142 // i.e. after all sinks had been notified
3143 mRedirectChannel->SetOriginalURI(mOriginalURI);
3147 rv = mRedirectChannel->AsyncOpen(mListener);
3148 NS_ENSURE_SUCCESS(rv, rv);
3150 mStatus = NS_BINDING_REDIRECTED;
3152 notifier.RedirectSucceeded();
3159 nsresult nsHttpChannel::ResolveProxy() {
3160 LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
3164 nsCOMPtr<nsIProtocolProxyService> pps =
3165 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
3166 if (NS_FAILED(rv)) return rv;
3168 // using the nsIProtocolProxyService2 allows a minor performance
3169 // optimization, but if an add-on has only provided the original interface
3170 // then it is ok to use that version.
3171 nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
3173 rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
3174 getter_AddRefs(mProxyRequest));
3176 rv = pps->AsyncResolve(static_cast<nsIChannel *>(this), mProxyResolveFlags,
3177 this, nullptr, getter_AddRefs(mProxyRequest));
3183 bool nsHttpChannel::ResponseWouldVary(nsICacheEntry *entry) {
3185 nsAutoCString buf, metaKey;
3186 Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
3187 if (!buf.IsEmpty()) {
3188 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
3190 // enumerate the elements of the Vary header...
3191 char *val = buf.BeginWriting(); // going to munge buf
3192 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3195 ("nsHttpChannel::ResponseWouldVary [channel=%p] "
3199 // if "*", then assume response would vary. technically speaking,
3200 // "Vary: header, *" is not permitted, but we allow it anyways.
3202 // We hash values of cookie-headers for the following reasons:
3204 // 1- cookies can be very large in size
3206 // 2- cookies may contain sensitive information. (for parity with
3207 // out policy of not storing Set-cookie headers in the cache
3208 // meta data, we likewise do not want to store cookie headers
3212 return true; // if we encounter this, just get out of here
3214 // build cache meta data key...
3215 metaKey = prefix + nsDependentCString(token);
3217 // check the last value of the given request header to see if it has
3218 // since changed. if so, then indeed the cached response is invalid.
3220 entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
3222 ("nsHttpChannel::ResponseWouldVary [channel=%p] "
3223 "stored value = \"%s\"\n",
3224 this, lastVal.get()));
3226 // Look for value of "Cookie" in the request headers
3227 nsHttpAtom atom = nsHttp::ResolveAtom(token);
3228 nsAutoCString newVal;
3229 bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
3230 if (!lastVal.IsEmpty()) {
3231 // value for this header in cache, but no value in request
3233 return true; // yes - response would vary
3236 // If this is a cookie-header, stored metadata is not
3237 // the value itself but the hash. So we also hash the
3238 // outgoing value here in order to compare the hashes
3240 if (atom == nsHttp::Cookie) {
3241 rv = Hash(newVal.get(), hash);
3242 // If hash failed, be conservative (the cached hash
3243 // exists at this point) and claim response would vary
3244 if (NS_FAILED(rv)) return true;
3248 ("nsHttpChannel::ResponseWouldVary [this=%p] "
3249 "set-cookie value hashed to %s\n",
3250 this, newVal.get()));
3253 if (!newVal.Equals(lastVal)) {
3254 return true; // yes, response would vary
3257 } else if (hasHeader) { // old value is empty, but newVal is set
3262 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3268 // We need to have an implementation of this function just so that we can keep
3269 // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
3270 // to set a member function ptr to a base class function.
3271 void nsHttpChannel::HandleAsyncAbort() {
3272 HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
3275 //-----------------------------------------------------------------------------
3276 // nsHttpChannel <byte-range>
3277 //-----------------------------------------------------------------------------
3279 bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
3280 bool ignoreMissingPartialLen) const {
3281 bool hasContentEncoding =
3282 mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
3285 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
3287 !etag.IsEmpty() && StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));
3289 return (partialLen < contentLength) &&
3290 (partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
3291 !hasWeakEtag && mCachedResponseHead->IsResumable() &&
3292 !mCustomConditionalRequest && !mCachedResponseHead->NoStore();
3295 nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
3296 int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
3298 mIsPartialRequest = false;
3300 if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen))
3301 return NS_ERROR_NOT_RESUMABLE;
3303 // looks like a partial entry we can reuse; add If-Range
3304 // and Range headers.
3305 nsresult rv = SetupByteRangeRequest(partialLen);
3306 if (NS_FAILED(rv)) {
3307 // Make the request unconditional again.
3308 UntieByteRangeRequest();
3314 nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
3315 // cached content has been found to be partial, add necessary request
3316 // headers to complete cache entry.
3318 // use strongest validator available...
3320 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
3322 Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
3323 if (val.IsEmpty()) {
3324 // if we hit this code it means mCachedResponseHead->IsResumable() is
3325 // either broken or not being called.
3326 MOZ_ASSERT_UNREACHABLE("no cache validator");
3327 mIsPartialRequest = false;
3328 return NS_ERROR_FAILURE;
3332 SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
3334 DebugOnly<nsresult> rv;
3335 rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
3336 MOZ_ASSERT(NS_SUCCEEDED(rv));
3337 rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
3338 MOZ_ASSERT(NS_SUCCEEDED(rv));
3339 mIsPartialRequest = true;
3344 void nsHttpChannel::UntieByteRangeRequest() {
3345 DebugOnly<nsresult> rv;
3346 rv = mRequestHead.ClearHeader(nsHttp::Range);
3347 MOZ_ASSERT(NS_SUCCEEDED(rv));
3348 rv = mRequestHead.ClearHeader(nsHttp::If_Range);
3349 MOZ_ASSERT(NS_SUCCEEDED(rv));
3352 nsresult nsHttpChannel::ProcessPartialContent(
3353 const std::function<nsresult(nsHttpChannel *, nsresult)>
3354 &aContinueProcessResponseFunc) {
3355 // ok, we've just received a 206
3357 // we need to stream whatever data is in the cache out first, and then
3358 // pick up whatever data is on the wire, writing it into the cache.
3360 LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
3362 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
3363 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
3365 // Make sure to clear bogus content-encodings before looking at the header
3366 ClearBogusContentEncodingIfNeeded();
3368 // Check if the content-encoding we now got is different from the one we
3370 nsAutoCString contentEncoding, cachedContentEncoding;
3371 // It is possible that there is not such headers
3372 Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
3373 Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
3374 cachedContentEncoding);
3375 if (PL_strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) != 0) {
3376 Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
3377 return CallOnStartRequest();
3382 int64_t cachedContentLength = mCachedResponseHead->ContentLength();
3383 int64_t entitySize = mResponseHead->TotalEntitySize();
3385 nsAutoCString contentRange;
3386 Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
3388 ("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
3389 "original content-length %" PRId64 ", entity-size %" PRId64
3390 ", content-range %s\n",
3391 this, mTransaction.get(), cachedContentLength, entitySize,
3392 contentRange.get()));
3394 if ((entitySize >= 0) && (cachedContentLength >= 0) &&
3395 (entitySize != cachedContentLength)) {
3397 ("nsHttpChannel::ProcessPartialContent [this=%p] "
3398 "206 has different total entity size than the content length "
3399 "of the original partially cached entity.\n",
3402 mCacheEntry->AsyncDoom(nullptr);
3403 Cancel(NS_ERROR_CORRUPTED_CONTENT);
3404 return CallOnStartRequest();
3407 if (mConcurrentCacheAccess) {
3408 // We started to read cached data sooner than its write has been done.
3409 // But the concurrent write has not finished completely, so we had to
3410 // do a range request. Now let the content coming from the network
3411 // be presented to consumers and also stored to the cache entry.
3413 rv = InstallCacheListener(mLogicalOffset);
3414 if (NS_FAILED(rv)) return rv;
3416 if (mOfflineCacheEntry) {
3417 rv = InstallOfflineCacheListener(mLogicalOffset);
3418 if (NS_FAILED(rv)) return rv;
3421 // suspend the current transaction
3422 rv = mTransactionPump->Suspend();
3423 if (NS_FAILED(rv)) return rv;
3426 // merge any new headers with the cached response headers
3427 rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
3428 if (NS_FAILED(rv)) return rv;
3430 // update the cached response head
3432 mCachedResponseHead->Flatten(head, true);
3433 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
3434 if (NS_FAILED(rv)) return rv;
3436 // make the cached response be the current response
3437 mResponseHead = std::move(mCachedResponseHead);
3439 UpdateInhibitPersistentCachingFlag();
3441 rv = UpdateExpirationTime();
3442 if (NS_FAILED(rv)) return rv;
3444 // notify observers interested in looking at a response that has been
3445 // merged with any cached headers (http-on-examine-merged-response).
3446 gHttpHandler->OnExamineMergedResponse(this);
3448 if (mConcurrentCacheAccess) {
3449 mCachedContentIsPartial = false;
3450 // Leave the mConcurrentCacheAccess flag set, we want to use it
3451 // to prevent duplicate OnStartRequest call on the target listener
3452 // in case this channel is canceled before it gets its OnStartRequest
3453 // from the http transaction.
3457 // Now we continue reading the network response.
3458 // the cached content is valid, although incomplete.
3459 mCachedContentIsValid = true;
3460 return CallOrWaitForResume([aContinueProcessResponseFunc](auto *self) {
3461 nsresult rv = self->ReadFromCache(false);
3462 return aContinueProcessResponseFunc(self, rv);
3466 nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone) {
3469 LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
3471 // by default, assume we would have streamed all data or failed...
3474 // setup cache listener to append to cache entry
3476 rv = mCacheEntry->GetDataSize(&size);
3477 if (NS_FAILED(rv)) return rv;
3479 rv = InstallCacheListener(size);
3480 if (NS_FAILED(rv)) return rv;
3482 // Entry is valid, do it now, after the output stream has been opened,
3483 // otherwise when done earlier, pending readers would consider the cache
3484 // entry still as partial (CacheEntry::GetDataSize would return the partial
3485 // data size) and consumers would do the conditional request again.
3486 rv = mCacheEntry->SetValid();
3487 if (NS_FAILED(rv)) return rv;
3489 // need to track the logical offset of the data being sent to our listener
3490 mLogicalOffset = size;
3492 // we're now completing the cached content, so we can clear this flag.
3493 // this puts us in the state of a regular download.
3494 mCachedContentIsPartial = false;
3495 // The cache input stream pump is finished, we do not need it any more.
3496 // (see bug 1313923)
3497 mCachePump = nullptr;
3499 // resume the transaction if it exists, otherwise the pipe contained the
3500 // remaining part of the document and we've now streamed all of the data.
3501 if (mTransactionPump) {
3502 rv = mTransactionPump->Resume();
3503 if (NS_SUCCEEDED(rv)) *streamDone = false;
3505 MOZ_ASSERT_UNREACHABLE("no transaction");
3509 //-----------------------------------------------------------------------------
3510 // nsHttpChannel <cache>
3511 //-----------------------------------------------------------------------------
3513 bool nsHttpChannel::ShouldBypassProcessNotModified() {
3514 if (mCustomConditionalRequest) {
3515 LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
3521 ("Server returned a 304 response even though we did not send a "
3522 "conditional request"));
3529 nsresult nsHttpChannel::ProcessNotModified(
3530 const std::function<nsresult(nsHttpChannel *, nsresult)>
3531 &aContinueProcessResponseFunc) {
3534 LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
3536 // Assert ShouldBypassProcessNotModified() has been checked before call to
3537 // ProcessNotModified().
3538 MOZ_ASSERT(!ShouldBypassProcessNotModified());
3540 MOZ_ASSERT(mCachedResponseHead);
3541 MOZ_ASSERT(mCacheEntry);
3542 NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
3544 // If the 304 response contains a Last-Modified different than the
3545 // one in our cache that is pretty suspicious and is, in at least the
3546 // case of bug 716840, a sign of the server having previously corrupted
3547 // our cache with a bad response. Take the minor step here of just dooming
3548 // that cache entry so there is a fighting chance of getting things on the
3551 nsAutoCString lastModifiedCached;
3552 nsAutoCString lastModified304;
3555 mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
3556 if (NS_SUCCEEDED(rv)) {
3557 rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
3560 if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
3562 ("Cache Entry and 304 Last-Modified Headers Do Not Match "
3564 lastModifiedCached.get(), lastModified304.get()));
3566 mCacheEntry->AsyncDoom(nullptr);
3567 Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
3570 // merge any new headers with the cached response headers
3571 rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
3572 if (NS_FAILED(rv)) return rv;
3574 // update the cached response head
3576 mCachedResponseHead->Flatten(head, true);
3577 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
3578 if (NS_FAILED(rv)) return rv;
3580 // make the cached response be the current response
3581 mResponseHead = std::move(mCachedResponseHead);
3583 UpdateInhibitPersistentCachingFlag();
3585 rv = UpdateExpirationTime();
3586 if (NS_FAILED(rv)) return rv;
3588 rv = AddCacheEntryHeaders(mCacheEntry);
3589 if (NS_FAILED(rv)) return rv;
3591 // notify observers interested in looking at a reponse that has been
3592 // merged with any cached headers
3593 gHttpHandler->OnExamineMergedResponse(this);
3595 mCachedContentIsValid = true;
3597 // Tell other consumers the entry is OK to use
3598 rv = mCacheEntry->SetValid();
3599 if (NS_FAILED(rv)) return rv;
3601 return CallOrWaitForResume([aContinueProcessResponseFunc](auto *self) {
3602 nsresult rv = self->ReadFromCache(false);
3603 return aContinueProcessResponseFunc(self, rv);
3607 nsresult nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback) {
3608 LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
3611 *waitingForRedirectCallback = false;
3612 mFallingBack = false;
3614 // At this point a load has failed (either due to network problems
3615 // or an error returned on the server). Perform an application
3616 // cache fallback if we have a URI to fall back to.
3617 if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
3618 LOG((" choosing not to fallback [%p,%s,%d]", mApplicationCache.get(),
3619 mFallbackKey.get(), mFallbackChannel));
3623 // Make sure the fallback entry hasn't been marked as a foreign
3625 uint32_t fallbackEntryType;
3626 rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
3627 NS_ENSURE_SUCCESS(rv, rv);
3629 if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
3630 // This cache points to a fallback that refers to a different
3631 // manifest. Refuse to fall back.
3635 if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
3636 // Refuse to fallback if the fallback key is not contained in the same
3637 // path as the cache manifest.
3641 MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
3642 "Fallback entry not marked correctly!");
3644 // Kill any offline cache entry, and disable offline caching for the
3646 if (mOfflineCacheEntry) {
3647 mOfflineCacheEntry->AsyncDoom(nullptr);
3648 mOfflineCacheEntry = nullptr;
3651 mApplicationCacheForWrite = nullptr;
3652 mOfflineCacheEntry = nullptr;
3654 // Close the current cache entry.
3655 CloseCacheEntry(true);
3657 // Create a new channel to load the fallback entry.
3658 RefPtr<nsIChannel> newChannel;
3659 rv = gHttpHandler->NewChannel2(mURI, mLoadInfo, getter_AddRefs(newChannel));
3660 NS_ENSURE_SUCCESS(rv, rv);
3662 uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
3663 rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
3664 NS_ENSURE_SUCCESS(rv, rv);
3666 // Make sure the new channel loads from the fallback key.
3667 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
3668 do_QueryInterface(newChannel, &rv);
3669 NS_ENSURE_SUCCESS(rv, rv);
3671 rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
3672 NS_ENSURE_SUCCESS(rv, rv);
3674 // ... and fallbacks should only load from the cache.
3675 uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
3676 rv = newChannel->SetLoadFlags(newLoadFlags);
3678 // Inform consumers about this fake redirect
3679 mRedirectChannel = newChannel;
3681 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
3682 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
3684 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
3686 if (NS_FAILED(rv)) {
3687 AutoRedirectVetoNotifier notifier(this);
3688 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
3692 // Indicate we are now waiting for the asynchronous redirect callback
3694 *waitingForRedirectCallback = true;
3698 nsresult nsHttpChannel::ContinueProcessFallback(nsresult rv) {
3699 AutoRedirectVetoNotifier notifier(this);
3701 if (NS_FAILED(rv)) return rv;
3703 MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
3705 // Make sure to do this after we received redirect veto answer,
3706 // i.e. after all sinks had been notified
3707 mRedirectChannel->SetOriginalURI(mOriginalURI);
3709 rv = mRedirectChannel->AsyncOpen(mListener);
3710 NS_ENSURE_SUCCESS(rv, rv);
3712 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
3713 MaybeWarnAboutAppCache();
3716 // close down this channel
3717 Cancel(NS_BINDING_REDIRECTED);
3719 notifier.RedirectSucceeded();
3723 mFallingBack = true;
3728 // Determines if a request is a byte range request for a subrange,
3729 // i.e. is a byte range request, but not a 0- byte range request.
3730 static bool IsSubRangeRequest(nsHttpRequestHead &aRequestHead) {
3731 nsAutoCString byteRange;
3732 if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
3735 return !byteRange.EqualsLiteral("bytes=0-");
3738 nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
3739 // Drop this flag here
3740 mConcurrentCacheAccess = 0;
3742 mLoadedFromApplicationCache = false;
3744 LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
3746 // make sure we're not abusing this function
3747 MOZ_ASSERT(!mCacheEntry, "cache entry already open");
3749 if (mRequestHead.IsPost()) {
3750 // If the post id is already set then this is an attempt to replay
3751 // a post transaction via the cache. Otherwise, we need a unique
3752 // post id for this transaction.
3753 if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
3754 } else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
3755 // don't use the cache for other types of requests
3759 // Pick up an application cache from the notification
3760 // callbacks if available and if we are not an intercepted channel.
3761 if (!mApplicationCache && mInheritApplicationCache) {
3762 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
3763 GetCallback(appCacheContainer);
3765 if (appCacheContainer) {
3766 appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
3770 return OpenCacheEntryInternal(isHttps, mApplicationCache, true);
3773 nsresult nsHttpChannel::OpenCacheEntryInternal(
3774 bool isHttps, nsIApplicationCache *applicationCache,
3775 bool allowApplicationCache) {
3776 MOZ_ASSERT_IF(!allowApplicationCache, !applicationCache);
3781 // We don't support caching for requests initiated
3782 // via nsIResumableChannel.
3786 // Don't cache byte range requests which are subranges, only cache 0-
3787 // byte range requests.
3788 if (IsSubRangeRequest(mRequestHead)) {
3792 // Handle correctly mCacheEntriesToWaitFor
3793 AutoCacheWaitFlags waitFlags(this);
3795 nsAutoCString cacheKey;
3796 nsAutoCString extension;
3798 nsCOMPtr<nsICacheStorageService> cacheStorageService(
3799 services::GetCacheStorageService());
3800 if (!cacheStorageService) {
3801 return NS_ERROR_NOT_AVAILABLE;
3804 nsCOMPtr<nsICacheStorage> cacheStorage;
3805 nsCOMPtr<nsIURI> openURI;
3806 if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
3807 // This is a fallback channel, open fallback URI instead
3808 rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
3809 NS_ENSURE_SUCCESS(rv, rv);
3814 RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
3816 return NS_ERROR_FAILURE;
3819 uint32_t cacheEntryOpenFlags;
3820 bool offline = gIOService->IsOffline();
3822 bool maybeRCWN = false;
3824 nsAutoCString cacheControlRequestHeader;
3825 Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
3826 cacheControlRequestHeader);
3827 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
3828 if (cacheControlRequest.NoStore()) {
3829 goto bypassCacheEntryOpen;
3832 if (offline || (mLoadFlags & INHIBIT_CACHING)) {
3833 if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) {
3834 goto bypassCacheEntryOpen;
3836 cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
3837 mCacheEntryIsReadOnly = true;
3838 } else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !applicationCache) {
3839 cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
3841 cacheEntryOpenFlags =
3842 nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
3845 // Remember the request is a custom conditional request so that we can
3846 // process any 304 response correctly.
3847 mCustomConditionalRequest =
3848 mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
3849 mRequestHead.HasHeader(nsHttp::If_None_Match) ||
3850 mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
3851 mRequestHead.HasHeader(nsHttp::If_Match) ||
3852 mRequestHead.HasHeader(nsHttp::If_Range);
3854 if (!mPostID && applicationCache) {
3855 rv = cacheStorageService->AppCacheStorage(info, applicationCache,
3856 getter_AddRefs(cacheStorage));
3857 } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
3858 rv = cacheStorageService->MemoryCacheStorage(
3859 info, // ? choose app cache as well...
3860 getter_AddRefs(cacheStorage));
3861 } else if (mPinCacheContent) {
3862 rv = cacheStorageService->PinningCacheStorage(info,
3863 getter_AddRefs(cacheStorage));
3865 bool lookupAppCache =
3866 (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)) &&
3867 !mPostID && MOZ_LIKELY(allowApplicationCache);
3868 // Try to race only if we use disk cache storage and we don't lookup
3870 maybeRCWN = (!lookupAppCache) && mRequestHead.IsSafeMethod();
3871 rv = cacheStorageService->DiskCacheStorage(info, lookupAppCache,
3872 getter_AddRefs(cacheStorage));
3874 NS_ENSURE_SUCCESS(rv, rv);
3876 if ((mClassOfService & nsIClassOfService::Leader) ||
3877 (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
3878 cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
3880 // Only for backward compatibility with the old cache back end.
3881 // When removed, remove the flags and related code snippets.
3882 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
3883 cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
3886 extension.Append(nsPrintfCString("%d", mPostID));
3889 extension.Append("TRR");
3892 if (mIsThirdPartyTrackingResource &&
3893 !AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(this, mURI,
3895 nsCOMPtr<nsIURI> topWindowURI;
3896 rv = GetTopWindowURI(getter_AddRefs(topWindowURI));
3897 bool isDocument = false;
3898 if (NS_FAILED(rv) && NS_SUCCEEDED(GetIsMainDocumentChannel(&isDocument)) &&
3900 // For top-level documents, use the document channel's origin to compute
3901 // the unique storage space identifier instead of the top Window URI.
3902 rv = NS_GetFinalChannelURI(this, getter_AddRefs(topWindowURI));
3903 NS_ENSURE_SUCCESS(rv, rv);
3906 nsAutoString topWindowOrigin;
3907 rv = nsContentUtils::GetUTFOrigin(topWindowURI ? topWindowURI : mURI,
3909 NS_ENSURE_SUCCESS(rv, rv);
3911 extension.Append("-unique:");
3912 extension.Append(NS_ConvertUTF16toUTF8(topWindowOrigin));
3915 mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
3916 mCacheQueueSizeWhenOpen =
3917 CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
3919 if (sRCWNEnabled && maybeRCWN && !mApplicationCacheForWrite) {
3920 bool hasAltData = false;
3921 uint32_t sizeInKb = 0;
3922 rv = cacheStorage->GetCacheIndexEntryAttrs(openURI, extension, &hasAltData,
3925 // We will attempt to race the network vs the cache if we've found
3926 // this entry in the cache index, and it has appropriate attributes
3927 // (doesn't have alt-data, and has a small size)
3928 if (NS_SUCCEEDED(rv) && !hasAltData &&
3929 sizeInKb < sRCWNSmallResourceSizeKB) {
3930 MaybeRaceCacheWithNetwork();
3934 if (!mCacheOpenDelay) {
3935 MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
3936 if (mNetworkTriggered) {
3937 mRaceCacheWithNetwork = sRCWNEnabled;
3939 rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags,
3942 // We pass `this` explicitly as a parameter due to the raw pointer
3943 // to refcounted object in lambda analysis.
3944 mCacheOpenFunc = [openURI, extension, cacheEntryOpenFlags,
3945 cacheStorage](nsHttpChannel *self) -> void {
3946 MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
3947 if (self->mNetworkTriggered) {
3948 self->mRaceCacheWithNetwork = true;
3950 cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, self);
3953 // calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
3954 NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), this,
3955 mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
3957 NS_ENSURE_SUCCESS(rv, rv);
3959 waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
3961 bypassCacheEntryOpen:
3962 if (!mApplicationCacheForWrite || !allowApplicationCache) return NS_OK;
3964 // If there is an app cache to write to, open the entry right now in parallel.
3966 // make sure we're not abusing this function
3967 MOZ_ASSERT(!mOfflineCacheEntry, "cache entry already open");
3970 // only put things in the offline cache while online
3974 if (mLoadFlags & INHIBIT_CACHING) {
3975 // respect demand not to cache
3979 if (!mRequestHead.IsGet()) {
3980 // only cache complete documents offline
3984 rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
3985 getter_AddRefs(cacheStorage));
3986 NS_ENSURE_SUCCESS(rv, rv);
3988 rv = cacheStorage->AsyncOpenURI(mURI, EmptyCString(),
3989 nsICacheStorage::OPEN_TRUNCATE, this);
3990 NS_ENSURE_SUCCESS(rv, rv);
3992 waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
3997 nsresult nsHttpChannel::CheckPartial(nsICacheEntry *aEntry, int64_t *aSize,
3998 int64_t *aContentLength) {
3999 return nsHttp::CheckPartial(
4000 aEntry, aSize, aContentLength,
4001 mCachedResponseHead ? mCachedResponseHead : mResponseHead);
4004 void nsHttpChannel::UntieValidationRequest() {
4005 DebugOnly<nsresult> rv;
4006 // Make the request unconditional again.
4007 rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
4008 MOZ_ASSERT(NS_SUCCEEDED(rv));
4009 rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
4010 MOZ_ASSERT(NS_SUCCEEDED(rv));
4011 rv = mRequestHead.ClearHeader(nsHttp::ETag);
4012 MOZ_ASSERT(NS_SUCCEEDED(rv));
4016 nsHttpChannel::OnCacheEntryCheck(nsICacheEntry *entry,
4017 nsIApplicationCache *appCache,
4018 uint32_t *aResult) {
4019 nsresult rv = NS_OK;
4021 LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
4024 mozilla::MutexAutoLock lock(mRCWNLock);
4026 if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
4028 ("Not using cached response because we've already got one from the "
4030 *aResult = ENTRY_NOT_WANTED;
4032 // Net-win indicates that mOnStartRequestTimestamp is from net.
4034 (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
4035 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
4038 } else if (mRaceCacheWithNetwork &&
4039 mFirstResponseSource == RESPONSE_PENDING) {
4040 mOnCacheEntryCheckTimestamp = TimeStamp::Now();
4043 nsAutoCString cacheControlRequestHeader;
4044 Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
4045 cacheControlRequestHeader);
4046 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
4048 if (cacheControlRequest.NoStore()) {
4050 ("Not using cached response based on no-store request cache "
4052 *aResult = ENTRY_NOT_WANTED;
4056 // Be pessimistic: assume the cache entry has no useful data.
4057 *aResult = ENTRY_WANTED;
4058 mCachedContentIsValid = false;
4062 // Get the method that was used to generate the cached response
4063 rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
4064 NS_ENSURE_SUCCESS(rv, rv);
4066 bool methodWasHead = buf.EqualsLiteral("HEAD");
4067 bool methodWasGet = buf.EqualsLiteral("GET");
4069 if (methodWasHead) {
4070 // The cached response does not contain an entity. We can only reuse
4071 // the response if the current request is also HEAD.
4072 if (!mRequestHead.IsHead()) {
4078 // We'll need this value in later computations...
4079 uint32_t lastModifiedTime;
4080 rv = entry->GetLastModified(&lastModifiedTime);
4081 NS_ENSURE_SUCCESS(rv, rv);
4083 // Determine if this is the first time that this cache entry
4084 // has been accessed during this session.
4085 bool fromPreviousSession =
4086 (gHttpHandler->SessionStartTime() > lastModifiedTime);
4088 // Get the cached HTTP response headers
4089 mCachedResponseHead = new nsHttpResponseHead();
4091 rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, mCachedResponseHead);
4092 NS_ENSURE_SUCCESS(rv, rv);
4094 bool isCachedRedirect = WillRedirect(mCachedResponseHead);
4096 // Do not return 304 responses from the cache, and also do not return
4097 // any other non-redirect 3xx responses from the cache (see bug 759043).
4098 NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
4101 if (mCachedResponseHead->NoStore() && mCacheEntryIsReadOnly) {
4102 // This prevents loading no-store responses when navigating back
4103 // while the browser is set to work offline.
4104 LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
4105 mLoadFlags |= nsIRequest::INHIBIT_CACHING;
4108 // Don't bother to validate items that are read-only,
4109 // unless they are read-only because of INHIBIT_CACHING or because
4110 // we're updating the offline cache.
4111 // Don't bother to validate if this is a fallback entry.
4112 if (!mApplicationCacheForWrite &&
4113 (appCache || (mCacheEntryIsReadOnly &&
4114 !(mLoadFlags & nsIRequest::INHIBIT_CACHING)))) {
4116 int64_t size, contentLength;
4117 rv = CheckPartial(entry, &size, &contentLength);
4118 NS_ENSURE_SUCCESS(rv, rv);
4120 if (contentLength != int64_t(-1) && contentLength != size) {
4121 *aResult = ENTRY_NOT_WANTED;
4126 rv = OpenCacheInputStream(entry, true, !!appCache);
4127 if (NS_SUCCEEDED(rv)) {
4128 mCachedContentIsValid = true;
4129 entry->MaybeMarkValid();
4134 bool wantCompleteEntry = false;
4136 if (!methodWasHead && !isCachedRedirect) {
4137 // If the cached content-length is set and it does not match the data
4138 // size of the cached content, then the cached response is partial...
4139 // either we need to issue a byte range request or we need to refetch
4140 // the entire document.
4142 // We exclude redirects from this check because we (usually) strip the
4143 // entity when we store the cache entry, and even if we didn't, we
4144 // always ignore a cached redirect's entity anyway. See bug 759043.
4145 int64_t size, contentLength;
4146 rv = CheckPartial(entry, &size, &contentLength);
4147 NS_ENSURE_SUCCESS(rv, rv);
4149 if (size == int64_t(-1)) {
4150 LOG((" write is in progress"));
4151 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
4153 (" not interested in the entry, "
4154 "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
4156 *aResult = ENTRY_NOT_WANTED;
4160 // Ignore !(size > 0) from the resumability condition
4161 if (!IsResumable(size, contentLength, true)) {
4162 if (IsNavigation()) {
4164 (" bypassing wait for the entry, "
4165 "this is a navigational load"));
4166 *aResult = ENTRY_NOT_WANTED;
4171 (" wait for entry completion, "
4172 "response is not resumable"));
4174 wantCompleteEntry = true;
4176 mConcurrentCacheAccess = 1;
4178 } else if (contentLength != int64_t(-1) && contentLength != size) {
4180 ("Cached data size does not match the Content-Length header "
4181 "[content-length=%" PRId64 " size=%" PRId64 "]\n",
4182 contentLength, size));
4184 rv = MaybeSetupByteRangeRequest(size, contentLength);
4185 mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
4186 if (mCachedContentIsPartial) {
4187 rv = OpenCacheInputStream(entry, false, !!appCache);
4188 if (NS_FAILED(rv)) {
4189 UntieByteRangeRequest();
4193 *aResult = ENTRY_NEEDS_REVALIDATION;
4197 if (size == 0 && mCacheOnlyMetadata) {
4198 // Don't break cache entry load when the entry's data size
4199 // is 0 and mCacheOnlyMetadata flag is set. In that case we
4200 // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
4202 MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
4209 bool isHttps = false;
4210 rv = mURI->SchemeIs("https", &isHttps);
4211 NS_ENSURE_SUCCESS(rv, rv);
4213 bool doValidation = false;
4214 bool canAddImsHeader = true;
4216 bool isForcedValid = false;
4217 entry->GetIsForcedValid(&isForcedValid);
4219 bool weaklyFramed, isImmutable;
4220 nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead, isHttps,
4221 &weaklyFramed, &isImmutable);
4223 // Cached entry is not the entity we request (see bug #633743)
4224 if (ResponseWouldVary(entry)) {
4225 LOG(("Validating based on Vary headers returning TRUE\n"));
4226 canAddImsHeader = false;
4227 doValidation = true;
4229 doValidation = nsHttp::ValidationRequired(
4230 isForcedValid, mCachedResponseHead, mLoadFlags, mAllowStaleCacheContent,
4231 isImmutable, mCustomConditionalRequest, mRequestHead, entry,
4232 cacheControlRequest, fromPreviousSession);
4235 // If a content signature is expected to be valid in this load,
4236 // set doValidation to force a signature check.
4237 if (!doValidation && mLoadInfo && mLoadInfo->GetVerifySignedContent()) {
4238 doValidation = true;
4241 nsAutoCString requestedETag;
4242 if (!doValidation &&
4243 NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
4244 (methodWasGet || methodWasHead)) {
4245 nsAutoCString cachedETag;
4246 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
4247 if (!cachedETag.IsEmpty() &&
4248 (StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
4249 !requestedETag.Equals(cachedETag))) {
4250 // User has defined If-Match header, if the cached entry is not
4251 // matching the provided header value or the cached ETag is weak,
4252 // force validation.
4253 doValidation = true;
4257 // Previous error should not be propagated.
4260 if (!doValidation) {
4262 // Check the authorization headers used to generate the cache entry.
4263 // We must validate the cache entry if:
4265 // 1) the cache entry was generated prior to this session w/
4266 // credentials (see bug 103402).
4267 // 2) the cache entry was generated w/o credentials, but would now
4268 // require credentials (see bug 96705).
4270 // NOTE: this does not apply to proxy authentication.
4272 entry->GetMetaDataElement("auth", getter_Copies(buf));
4274 (fromPreviousSession && !buf.IsEmpty()) ||
4275 (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
4278 // Bug #561276: We maintain a chain of cache-keys which returns cached
4279 // 3xx-responses (redirects) in order to detect cycles. If a cycle is
4280 // found, ignore the cached response and hit the net. Otherwise, use
4281 // the cached response and add the cache-key to the chain. Note that
4282 // a limited number of redirects (cached or not) is allowed and is
4283 // enforced independently of this mechanism
4284 if (!doValidation && isCachedRedirect) {
4285 nsAutoCString cacheKey;
4286 rv = GenerateCacheKey(mPostID, cacheKey);
4287 MOZ_ASSERT(NS_SUCCEEDED(rv));
4289 if (!mRedirectedCachekeys)
4290 mRedirectedCachekeys = new nsTArray<nsCString>();
4291 else if (mRedirectedCachekeys->Contains(cacheKey))
4292 doValidation = true;
4294 LOG(("Redirection-chain %s key %s\n",
4295 doValidation ? "contains" : "does not contain", cacheKey.get()));
4297 // Append cacheKey if not in the chain already
4298 if (!doValidation) mRedirectedCachekeys->AppendElement(cacheKey);
4301 mCachedContentIsValid = !doValidation;
4305 // now, we are definitely going to issue a HTTP request to the server.
4306 // make it conditional if possible.
4308 // do not attempt to validate no-store content, since servers will not
4309 // expect it to be cached. (we only keep it in our cache for the
4310 // purposes of back/forward, etc.)
4312 // the request method MUST be either GET or HEAD (see bug 175641) and
4313 // the cached response code must be < 400
4315 // the cached content must not be weakly framed or marked immutable
4317 // do not override conditional headers when consumer has defined its own
4318 if (!mCachedResponseHead->NoStore() &&
4319 (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
4320 !mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
4321 (mCachedResponseHead->Status() < 400)) {
4322 if (mConcurrentCacheAccess) {
4323 // In case of concurrent read and also validation request we
4324 // must wait for the current writer to close the output stream
4325 // first. Otherwise, when the writer's job would have been interrupted
4326 // before all the data were downloaded, we'd have to do a range request
4327 // which would be a second request in line during this channel's
4328 // life-time. nsHttpChannel is not designed to do that, so rather
4329 // turn off concurrent read and wait for entry's completion.
4330 // Then only re-validation or range-re-validation request will go out.
4331 mConcurrentCacheAccess = 0;
4332 // This will cause that OnCacheEntryCheck is called again with the same
4333 // entry after the writer is done.
4334 wantCompleteEntry = true;
4337 // Add If-Modified-Since header if a Last-Modified was given
4338 // and we are allowed to do this (see bugs 510359 and 269303)
4339 if (canAddImsHeader) {
4340 Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
4341 if (!val.IsEmpty()) {
4342 rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
4343 MOZ_ASSERT(NS_SUCCEEDED(rv));
4346 // Add If-None-Match header if an ETag was given in the response
4347 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
4348 if (!val.IsEmpty()) {
4349 rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
4350 MOZ_ASSERT(NS_SUCCEEDED(rv));
4357 if (mCachedContentIsValid || mDidReval) {
4358 rv = OpenCacheInputStream(entry, mCachedContentIsValid, !!appCache);
4359 if (NS_FAILED(rv)) {
4360 // If we can't get the entity then we have to act as though we
4361 // don't have the cache entry.
4363 UntieValidationRequest();
4366 mCachedContentIsValid = false;
4371 *aResult = ENTRY_NEEDS_REVALIDATION;
4372 else if (wantCompleteEntry)
4373 *aResult = RECHECK_AFTER_WRITE_FINISHED;
4375 *aResult = ENTRY_WANTED;
4378 if (mCachedContentIsValid) {
4379 entry->MaybeMarkValid();
4383 ("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
4385 this, doValidation, *aResult));
4390 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew,
4391 nsIApplicationCache *aAppCache,
4393 MOZ_ASSERT(NS_IsMainThread());
4398 ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
4399 "new=%d appcache=%p status=%" PRIx32
4400 " mAppCache=%p mAppCacheForWrite=%p]\n",
4401 this, entry, aNew, aAppCache, static_cast<uint32_t>(status),
4402 mApplicationCache.get(), mApplicationCacheForWrite.get()));
4404 // if the channel's already fired onStopRequest, then we should ignore
4407 mCacheInputStream.CloseAndRelease();
4411 rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
4412 if (NS_FAILED(rv)) {
4413 CloseCacheEntry(false);
4414 if (mRaceCacheWithNetwork && mNetworkTriggered &&
4415 mFirstResponseSource != RESPONSE_FROM_CACHE) {
4416 // Ignore the error if we're racing cache with network and the cache
4417 // didn't win, The network part will handle cancelation or any other
4418 // error. Otherwise we could end up calling the listener twice, see
4421 (" not calling AsyncAbort() because we're racing cache with "
4424 Unused << AsyncAbort(rv);
4431 nsresult nsHttpChannel::OnCacheEntryAvailableInternal(
4432 nsICacheEntry *entry, bool aNew, nsIApplicationCache *aAppCache,
4437 LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
4438 static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
4442 if (mIgnoreCacheEntry) {
4443 if (!entry || aNew) {
4444 // We use this flag later to decide whether to report
4445 // LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
4446 // an usable entry, so drop the flag.
4447 mIgnoreCacheEntry = false;
4450 status = NS_ERROR_NOT_AVAILABLE;
4454 if (mApplicationCache == aAppCache && !mCacheEntry) {
4455 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
4456 } else if (mApplicationCacheForWrite == aAppCache && aNew &&
4457 !mOfflineCacheEntry) {
4458 rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
4460 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
4463 rv = OnNormalCacheEntryAvailable(entry, aNew, status);
4466 if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
4467 // If we have a fallback URI (and we're not already
4468 // falling back), process the fallback asynchronously.
4469 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
4470 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
4473 return NS_ERROR_DOCUMENT_NOT_CACHED;
4476 if (NS_FAILED(rv)) {
4480 // We may be waiting for more callbacks...
4481 if (AwaitingCacheCallbacks()) {
4485 if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
4486 (mDidReval || mCachedContentIsPartial)) ||
4487 mIgnoreCacheEntry)) {
4488 // We won't send the conditional request because the unconditional
4489 // request was already sent (see bug 1377223).
4490 AccumulateCategorical(
4491 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
4494 if (mRaceCacheWithNetwork && mCachedContentIsValid) {
4495 Unused << ReadFromCache(true);
4498 return TriggerNetwork();
4501 nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
4503 nsresult aEntryStatus) {
4504 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
4506 if (NS_FAILED(aEntryStatus) || aNew) {
4507 // Make sure this flag is dropped. It may happen the entry is doomed
4508 // between OnCacheEntryCheck and OnCacheEntryAvailable.
4509 mCachedContentIsValid = false;
4511 // From the same reason remove any conditional headers added
4512 // in OnCacheEntryCheck.
4514 LOG((" Removing conditional request headers"));
4515 UntieValidationRequest();
4519 if (mCachedContentIsPartial) {
4520 LOG((" Removing byte range request headers"));
4521 UntieByteRangeRequest();
4522 mCachedContentIsPartial = false;
4525 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
4526 // if this channel is only allowed to pull from the cache, then
4527 // we must fail if we were unable to open a cache entry for read.
4528 return NS_ERROR_DOCUMENT_NOT_CACHED;
4532 if (NS_SUCCEEDED(aEntryStatus)) {
4533 mCacheEntry = aEntry;
4534 mCacheEntryIsWriteOnly = aNew;
4536 if (!aNew && !mAsyncOpenTime.IsNull()) {
4537 // We use microseconds for IO operations. For consistency let's use
4538 // microseconds here too.
4539 uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
4540 bool isSlow = false;
4541 if ((mCacheOpenWithPriority &&
4542 mCacheQueueSizeWhenOpen >= sRCWNQueueSizePriority) ||
4543 (!mCacheOpenWithPriority &&
4544 mCacheQueueSizeWhenOpen >= sRCWNQueueSizeNormal)) {
4547 CacheFileUtils::CachePerfStats::AddValue(
4548 CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
4551 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
4552 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false);
4559 nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(
4560 nsICacheEntry *aEntry, bool aNew, nsIApplicationCache *aAppCache,
4561 nsresult aEntryStatus) {
4562 MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
4563 MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
4565 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
4569 if (NS_SUCCEEDED(aEntryStatus)) {
4570 if (!mApplicationCache) {
4571 mApplicationCache = aAppCache;
4574 // We successfully opened an offline cache session and the entry,
4575 // so indicate we will load from the offline cache.
4576 mLoadedFromApplicationCache = true;
4577 mCacheEntryIsReadOnly = true;
4578 mCacheEntry = aEntry;
4579 mCacheEntryIsWriteOnly = false;
4581 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
4582 MaybeWarnAboutAppCache();
4588 if (!mApplicationCacheForWrite && !mFallbackChannel) {
4589 if (!mApplicationCache) {
4590 mApplicationCache = aAppCache;
4593 // Check for namespace match.
4594 nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
4595 rv = mApplicationCache->GetMatchingNamespace(
4596 mSpec, getter_AddRefs(namespaceEntry));
4597 NS_ENSURE_SUCCESS(rv, rv);
4599 uint32_t namespaceType = 0;
4600 if (!namespaceEntry ||
4601 NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
4602 (namespaceType & (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
4603 nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) ==
4605 // When loading from an application cache, only items
4606 // on the whitelist or matching a
4607 // fallback namespace should hit the network...
4608 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
4610 // ... and if there were an application cache entry,
4611 // we would have found it earlier.
4612 return NS_ERROR_CACHE_KEY_NOT_FOUND;
4615 if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
4616 nsAutoCString namespaceSpec;
4617 rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
4618 NS_ENSURE_SUCCESS(rv, rv);
4620 // This prevents fallback attacks injected by an insecure subdirectory
4621 // for the whole origin (or a parent directory).
4622 if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
4626 rv = namespaceEntry->GetData(mFallbackKey);
4627 NS_ENSURE_SUCCESS(rv, rv);
4630 if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) {
4632 ("nsHttpChannel::OnOfflineCacheEntryAvailable this=%p, URL matches "
4634 " looking for a regular cache entry",
4637 bool isHttps = false;
4638 rv = mURI->SchemeIs("https", &isHttps);
4639 NS_ENSURE_SUCCESS(rv, rv);
4641 rv = OpenCacheEntryInternal(isHttps, nullptr,
4642 false /* don't allow appcache lookups */);
4643 if (NS_FAILED(rv)) {
4644 // Don't let this fail when cache entry can't be synchronously open.
4645 // We want to go forward even without a regular cache entry.
4654 nsresult nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(
4655 nsICacheEntry *aEntry, nsIApplicationCache *aAppCache,
4656 nsresult aEntryStatus) {
4657 MOZ_ASSERT(mApplicationCacheForWrite &&
4658 aAppCache == mApplicationCacheForWrite);
4660 mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
4662 if (NS_SUCCEEDED(aEntryStatus)) {
4663 mOfflineCacheEntry = aEntry;
4664 if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
4665 mOfflineCacheLastModifiedTime = 0;
4669 return aEntryStatus;
4672 // Generates the proper cache-key for this instance of nsHttpChannel
4673 nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
4674 nsACString &cacheKey) {
4675 AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(), postID,
4680 // Assembles a cache-key from the given pieces of information and |mLoadFlags|
4681 void nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID,
4682 nsACString &cacheKey) {
4683 cacheKey.Truncate();
4685 if (mLoadFlags & LOAD_ANONYMOUS) {
4686 cacheKey.AssignLiteral("anon&");
4691 SprintfLiteral(buf, "id=%x&", postID);
4692 cacheKey.Append(buf);
4695 if (!cacheKey.IsEmpty()) {
4696 cacheKey.AppendLiteral("uri=");
4699 // Strip any trailing #ref from the URL before using it as the key
4700 const char *p = strchr(spec, '#');
4702 cacheKey.Append(spec, p - spec);
4704 cacheKey.Append(spec);
4707 nsresult DoUpdateExpirationTime(nsHttpChannel *aSelf,
4708 nsICacheEntry *aCacheEntry,
4709 nsHttpResponseHead *aResponseHead,
4710 uint32_t &aExpirationTime) {
4711 MOZ_ASSERT(aExpirationTime == 0);
4712 NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
4716 if (!aResponseHead->MustValidate()) {
4717 uint32_t freshnessLifetime = 0;
4719 rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
4720 if (NS_FAILED(rv)) return rv;
4722 if (freshnessLifetime > 0) {
4723 uint32_t now = NowInSeconds(), currentAge = 0;
4725 rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
4727 if (NS_FAILED(rv)) return rv;
4729 LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
4732 if (freshnessLifetime > currentAge) {
4733 uint32_t timeRemaining = freshnessLifetime - currentAge;
4734 // be careful... now + timeRemaining may overflow
4735 if (now + timeRemaining < now)
4736 aExpirationTime = uint32_t(-1);
4738 aExpirationTime = now + timeRemaining;
4740 aExpirationTime = 0;
4744 rv = aCacheEntry->SetExpirationTime(aExpirationTime);
4745 NS_ENSURE_SUCCESS(rv, rv);
4750 // UpdateExpirationTime is called when a new response comes in from the server.
4751 // It updates the stored response-time and sets the expiration time on the
4754 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
4756 // timeRemaining = freshnessLifetime - currentAge
4757 // expirationTime = now + timeRemaining
4759 nsresult nsHttpChannel::UpdateExpirationTime() {
4760 uint32_t expirationTime = 0;
4762 DoUpdateExpirationTime(this, mCacheEntry, mResponseHead, expirationTime);
4763 NS_ENSURE_SUCCESS(rv, rv);
4765 if (mOfflineCacheEntry) {
4766 rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
4767 NS_ENSURE_SUCCESS(rv, rv);
4773 bool nsHttpChannel::ShouldUpdateOfflineCacheEntry() {
4774 if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
4778 // if we're updating the cache entry, update the offline cache entry too
4779 if (mCacheEntry && mCacheEntryIsWriteOnly) {
4783 // if there's nothing in the offline cache, add it
4784 if (mOfflineCacheEntry) {
4788 // if the document is newer than the offline entry, update it
4789 uint32_t docLastModifiedTime;
4790 nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
4791 if (NS_FAILED(rv)) {
4795 if (mOfflineCacheLastModifiedTime == 0) {
4799 if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
4806 nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry *cacheEntry,
4807 bool startBuffering,
4808 bool checkingAppCacheEntry) {
4811 bool isHttps = false;
4812 rv = mURI->SchemeIs("https", &isHttps);
4813 NS_ENSURE_SUCCESS(rv, rv);
4816 rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
4817 if (NS_FAILED(rv)) {
4818 LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
4820 NS_WARNING("failed to parse security-info");
4821 cacheEntry->AsyncDoom(nullptr);
4825 // XXX: We should not be skilling this check in the offline cache
4826 // case, but we have to do so now to work around bug 794507.
4827 bool mustHaveSecurityInfo =
4828 !mLoadedFromApplicationCache && !checkingAppCacheEntry;
4829 MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
4830 if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
4832 ("mCacheEntry->GetSecurityInfo returned success but did not "
4833 "return the security info [channel=%p, entry=%p]",
4835 cacheEntry->AsyncDoom(nullptr);
4836 return NS_ERROR_UNEXPECTED; // XXX error code
4840 // Keep the conditions below in sync with the conditions in ReadFromCache.
4844 if (WillRedirect(mCachedResponseHead)) {
4845 // Do not even try to read the entity for a redirect because we do not
4846 // return an entity to the application when we process redirects.
4847 LOG(("Will skip read of cached redirect entity\n"));
4851 if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
4852 !mCachedContentIsPartial) {
4853 // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
4855 if (!mApplicationCacheForWrite) {
4857 ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
4862 // If offline caching has been requested and the offline cache needs
4863 // updating, we must complete the call even if the main cache entry
4864 // is up to date. We don't know yet for sure whether the offline
4865 // cache needs updating because at this point we haven't opened it
4866 // for writing yet, so we have to start reading the cached entity now
4869 ("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
4873 // Open an input stream for the entity, so that the call to OpenInputStream
4874 // happens off the main thread.
4875 nsCOMPtr<nsIInputStream> stream;
4877 // If an alternate representation was requested, try to open the alt
4879 // If the entry has a "is-from-child" metadata, then only open the altdata
4880 // stream if the consumer is also from child.
4881 bool altDataFromChild = false;
4884 rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
4885 getter_Copies(value));
4886 altDataFromChild = !value.IsEmpty();
4889 nsAutoCString altDataType;
4890 Unused << cacheEntry->GetAltDataType(altDataType);
4892 nsAutoCString contentType;
4893 mCachedResponseHead->ContentType(contentType);
4895 bool foundAltData = false;
4896 if (!altDataType.IsEmpty() && !mPreferredCachedAltDataTypes.IsEmpty() &&
4897 altDataFromChild == mAltDataForChild) {
4898 for (auto &pref : mPreferredCachedAltDataTypes) {
4899 if (mozilla::Get<0>(pref) == altDataType &&
4900 (mozilla::Get<1>(pref).IsEmpty() ||
4901 mozilla::Get<1>(pref) == contentType)) {
4902 foundAltData = true;
4908 rv = cacheEntry->OpenAlternativeInputStream(altDataType,
4909 getter_AddRefs(stream));
4910 if (NS_SUCCEEDED(rv)) {
4911 LOG(("Opened alt-data input stream type=%s", altDataType.get()));
4912 // We have succeeded.
4913 mAvailableCachedAltDataType = altDataType;
4914 // Set the correct data size on the channel.
4915 int64_t altDataSize;
4916 if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
4917 mAltDataLength = altDataSize;
4923 rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
4926 if (NS_FAILED(rv)) {
4928 ("Failed to open cache input stream [channel=%p, "
4934 if (startBuffering) {
4936 rv = stream->IsNonBlocking(&nonBlocking);
4937 if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
4940 if (!startBuffering) {
4941 // Bypass wrapping the input stream for the new cache back-end since
4942 // nsIStreamTransportService expects a blocking stream. Preloading of
4943 // the data must be done on the level of the cache backend, internally.
4945 // We do not connect the stream to the stream transport service if we
4946 // have to validate the entry with the server. If we did, we would get
4947 // into a race condition between the stream transport service reading
4948 // the existing contents and the opening of the cache entry's output
4949 // stream to write the new contents in the case where we get a non-304
4952 ("Opened cache input stream without buffering [channel=%p, "
4953 "mCacheEntry=%p, stream=%p]",
4954 this, cacheEntry, stream.get()));
4955 mCacheInputStream.takeOver(stream);
4959 // Have the stream transport service start reading the entity on one of its
4960 // background threads.
4962 nsCOMPtr<nsITransport> transport;
4963 nsCOMPtr<nsIInputStream> wrapper;
4965 nsCOMPtr<nsIStreamTransportService> sts(
4966 services::GetStreamTransportService());
4967 rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
4968 if (NS_SUCCEEDED(rv)) {
4969 rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
4971 if (NS_SUCCEEDED(rv)) {
4972 rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
4974 if (NS_SUCCEEDED(rv)) {
4976 ("Opened cache input stream [channel=%p, wrapper=%p, "
4977 "transport=%p, stream=%p]",
4978 this, wrapper.get(), transport.get(), stream.get()));
4981 ("Failed to open cache input stream [channel=%p, "
4982 "wrapper=%p, transport=%p, stream=%p]",
4983 this, wrapper.get(), transport.get(), stream.get()));
4989 mCacheInputStream.takeOver(wrapper);
4994 // Actually process the cached response that we started to handle in CheckCache
4995 // and/or StartBufferingCachedEntity.
4996 nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
4997 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
4998 NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
4999 NS_ENSURE_TRUE(!mCachePump, NS_OK); // already opened
5002 ("nsHttpChannel::ReadFromCache [this=%p] "
5003 "Using cached copy of: %s\n",
5004 this, mSpec.get()));
5006 // When racing the cache with the network with a timer, and we get data from
5007 // the cache, we should prevent the timer from triggering a network request.
5008 if (mNetworkTriggerTimer) {
5009 mNetworkTriggerTimer->Cancel();
5010 mNetworkTriggerTimer = nullptr;
5013 if (mRaceCacheWithNetwork) {
5014 MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
5015 if (mFirstResponseSource == RESPONSE_PENDING) {
5016 LOG(("First response from cache\n"));
5017 mFirstResponseSource = RESPONSE_FROM_CACHE;
5019 // Cancel the transaction because we will serve the request from the cache
5020 CancelNetworkRequest(NS_BINDING_ABORTED);
5021 if (mTransactionPump && mSuspendCount) {
5022 uint32_t suspendCount = mSuspendCount;
5023 while (suspendCount--) {
5024 mTransactionPump->Resume();
5027 mTransaction = nullptr;
5028 mTransactionPump = nullptr;
5030 MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
5032 ("Skipping read from cache because first response was from "
5035 if (!mOnCacheEntryCheckTimestamp.IsNull()) {
5036 TimeStamp currentTime = TimeStamp::Now();
5038 (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
5039 Telemetry::Accumulate(
5040 Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
5043 (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
5044 Telemetry::Accumulate(
5045 Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
5052 if (mCachedResponseHead) mResponseHead = std::move(mCachedResponseHead);
5054 UpdateInhibitPersistentCachingFlag();
5056 // if we don't already have security info, try to get it from the cache
5057 // entry. there are two cases to consider here: 1) we are just reading
5058 // from the cache, or 2) this may be due to a 304 not modified response,
5059 // in which case we could have security info from a socket transport.
5060 if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;
5062 if (!alreadyMarkedValid && !mCachedContentIsPartial) {
5063 // We validated the entry, and we have write access to the cache, so
5064 // mark the cache entry as valid in order to allow others access to
5065 // this cache entry.
5067 // TODO: This should be done asynchronously so we don't take the cache
5068 // service lock on the main thread.
5069 mCacheEntry->MaybeMarkValid();
5074 // Keep the conditions below in sync with the conditions in
5075 // StartBufferingCachedEntity.
5077 if (WillRedirect(mResponseHead)) {
5078 // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
5079 // to avoid event dispatching latency.
5080 MOZ_ASSERT(!mCacheInputStream);
5081 LOG(("Skipping skip read of cached redirect entity\n"));
5082 return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
5085 if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
5086 if (!mApplicationCacheForWrite) {
5088 ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
5090 MOZ_ASSERT(!mCacheInputStream);
5091 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
5092 // here, to avoid event dispatching latency.
5093 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
5096 if (!ShouldUpdateOfflineCacheEntry()) {
5098 ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
5099 "load flag (mApplicationCacheForWrite not null case)\n"));
5100 mCacheInputStream.CloseAndRelease();
5101 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
5102 // here, to avoid event dispatching latency.
5103 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
5107 MOZ_ASSERT(mCacheInputStream);
5108 if (!mCacheInputStream) {
5110 "mCacheInputStream is null but we're expecting to "
5111 "be able to read from it.");
5112 return NS_ERROR_UNEXPECTED;
5115 nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
5117 rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
5119 if (NS_FAILED(rv)) {
5120 inputStream->Close();
5124 rv = mCachePump->AsyncRead(this, nullptr);
5125 if (NS_FAILED(rv)) return rv;
5127 if (mTimingEnabled) mCacheReadStart = TimeStamp::Now();
5129 uint32_t suspendCount = mSuspendCount;
5130 if (mAsyncResumePending) {
5132 (" Suspend()'ing cache pump once because of async resume pending"
5133 ", sc=%u, pump=%p, this=%p",
5134 suspendCount, mCachePump.get(), this));
5137 while (suspendCount--) {
5138 mCachePump->Suspend();
5144 void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
5145 mCacheInputStream.CloseAndRelease();
5147 if (!mCacheEntry) return;
5149 LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
5150 " mCacheEntryIsWriteOnly=%x",
5151 this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
5152 mCacheEntryIsWriteOnly));
5154 // If we have begun to create or replace a cache entry, and that cache
5155 // entry is not complete and not resumable, then it needs to be doomed.
5156 // Otherwise, CheckCache will make the mistake of thinking that the
5157 // partial cache entry is complete.
5160 if (mInitedCacheEntry) {
5161 MOZ_ASSERT(mResponseHead, "oops");
5162 if (NS_FAILED(mStatus) && doomOnFailure && mCacheEntryIsWriteOnly &&
5163 !mResponseHead->IsResumable())
5165 } else if (mCacheEntryIsWriteOnly)
5169 LOG((" dooming cache entry!!"));
5170 mCacheEntry->AsyncDoom(nullptr);
5172 // Store updated security info, makes cached EV status race less likely
5173 // (see bug 1040086)
5174 if (mSecurityInfo) mCacheEntry->SetSecurityInfo(mSecurityInfo);
5177 mCachedResponseHead = nullptr;
5179 mCachePump = nullptr;
5180 // This releases the entry for other consumers to use.
5181 // We call Dismiss() in case someone still keeps a reference
5182 // to this entry handle.
5183 mCacheEntry->Dismiss();
5184 mCacheEntry = nullptr;
5185 mCacheEntryIsWriteOnly = false;
5186 mInitedCacheEntry = false;
5189 void nsHttpChannel::CloseOfflineCacheEntry() {
5190 if (!mOfflineCacheEntry) return;
5192 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
5194 if (NS_FAILED(mStatus)) {
5195 mOfflineCacheEntry->AsyncDoom(nullptr);
5198 if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
5199 mOfflineCacheEntry->AsyncDoom(nullptr);
5202 mOfflineCacheEntry = nullptr;
5205 // Initialize the cache entry for writing.
5206 // - finalize storage policy
5207 // - store security info
5208 // - update expiration time
5209 // - store headers and other meta data
5210 nsresult nsHttpChannel::InitCacheEntry() {
5213 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
5214 // if only reading, nothing to be done here.
5215 if (mCacheEntryIsReadOnly) return NS_OK;
5217 // Don't cache the response again if already cached...
5218 if (mCachedContentIsValid) return NS_OK;
5220 LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
5221 mCacheEntry.get()));
5223 bool recreate = !mCacheEntryIsWriteOnly;
5224 bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
5226 if (!recreate && dontPersist) {
5227 // If the current entry is persistent but we inhibit peristence
5228 // then force recreation of the entry as memory/only.
5229 rv = mCacheEntry->GetPersistent(&recreate);
5230 if (NS_FAILED(rv)) return rv;
5235 (" we have a ready entry, but reading it again from the server -> "
5236 "recreating cache entry\n"));
5237 // clean the altData cache and reset this to avoid wrong content length
5238 mAvailableCachedAltDataType.Truncate();
5240 nsCOMPtr<nsICacheEntry> currentEntry;
5241 currentEntry.swap(mCacheEntry);
5242 rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
5243 if (NS_FAILED(rv)) {
5244 LOG((" recreation failed, the response will not be cached"));
5248 mCacheEntryIsWriteOnly = true;
5251 // Set the expiration time for this cache entry
5252 rv = UpdateExpirationTime();
5253 if (NS_FAILED(rv)) return rv;
5255 // mark this weakly framed until a response body is seen
5256 mCacheEntry->SetMetaDataElement("strongly-framed", "0");
5258 rv = AddCacheEntryHeaders(mCacheEntry);
5259 if (NS_FAILED(rv)) return rv;
5261 mInitedCacheEntry = true;
5263 // Don't perform the check when writing (doesn't make sense)
5264 mConcurrentCacheAccess = 0;
5269 void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
5270 // The no-store directive within the 'Cache-Control:' header indicates
5271 // that we must not store the response in a persistent cache.
5272 if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
5274 // Only cache SSL content on disk if the pref is set
5276 if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
5277 NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
5278 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
5282 nsresult nsHttpChannel::InitOfflineCacheEntry() {
5283 // This function can be called even when we fail to connect (bug 551990)
5285 if (!mOfflineCacheEntry) {
5289 if (!mResponseHead || mResponseHead->NoStore()) {
5290 if (mResponseHead && mResponseHead->NoStore()) {
5291 mOfflineCacheEntry->AsyncDoom(nullptr);
5294 CloseOfflineCacheEntry();
5296 if (mResponseHead && mResponseHead->NoStore()) {
5297 return NS_ERROR_NOT_AVAILABLE;
5303 // This entry's expiration time should match the main entry's expiration
5304 // time. UpdateExpirationTime() will keep it in sync once the offline
5305 // cache entry has been created.
5307 uint32_t expirationTime;
5308 nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
5309 NS_ENSURE_SUCCESS(rv, rv);
5311 mOfflineCacheEntry->SetExpirationTime(expirationTime);
5314 return AddCacheEntryHeaders(mOfflineCacheEntry);
5317 nsresult DoAddCacheEntryHeaders(nsHttpChannel *self, nsICacheEntry *entry,
5318 nsHttpRequestHead *requestHead,
5319 nsHttpResponseHead *responseHead,
5320 nsISupports *securityInfo) {
5323 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
5324 // Store secure data in memory only
5325 if (securityInfo) entry->SetSecurityInfo(securityInfo);
5327 // Store the HTTP request method with the cache entry so we can distinguish
5328 // for example GET and HEAD responses.
5329 nsAutoCString method;
5330 requestHead->Method(method);
5331 rv = entry->SetMetaDataElement("request-method", method.get());
5332 if (NS_FAILED(rv)) return rv;
5334 // Store the HTTP authorization scheme used if any...
5335 rv = StoreAuthorizationMetaData(entry, requestHead);
5336 if (NS_FAILED(rv)) return rv;
5338 // Iterate over the headers listed in the Vary response header, and
5339 // store the value of the corresponding request header so we can verify
5340 // that it has not varied when we try to re-use the cached response at
5341 // a later time. Take care to store "Cookie" headers only as hashes
5342 // due to security considerations and the fact that they can be pretty
5343 // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
5345 // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
5346 // in the cache. we could try to avoid needlessly storing the "accept"
5347 // header in this case, but it doesn't seem worth the extra code to perform
5350 nsAutoCString buf, metaKey;
5351 Unused << responseHead->GetHeader(nsHttp::Vary, buf);
5352 if (!buf.IsEmpty()) {
5353 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
5355 char *bufData = buf.BeginWriting(); // going to munge buf
5356 char *token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
5359 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5362 if (*token != '*') {
5363 nsHttpAtom atom = nsHttp::ResolveAtom(token);
5366 if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
5367 // If cookie-header, store a hash of the value
5368 if (atom == nsHttp::Cookie) {
5370 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5373 rv = Hash(val.get(), hash);
5374 // If hash failed, store a string not very likely
5375 // to be the result of subsequent hashes
5376 if (NS_FAILED(rv)) {
5377 val = NS_LITERAL_CSTRING("<hash failed>");
5382 LOG((" hashed to %s\n", val.get()));
5385 // build cache meta data key and set meta data element...
5386 metaKey = prefix + nsDependentCString(token);
5387 entry->SetMetaDataElement(metaKey.get(), val.get());
5390 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5391 "clearing metadata for %s",
5393 metaKey = prefix + nsDependentCString(token);
5394 entry->SetMetaDataElement(metaKey.get(), nullptr);
5397 token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
5402 // Store the received HTTP head with the cache entry as an element of
5405 responseHead->Flatten(head, true);
5406 rv = entry->SetMetaDataElement("response-head", head.get());
5407 if (NS_FAILED(rv)) return rv;
5409 responseHead->FlattenNetworkOriginalHeaders(head);
5410 rv = entry->SetMetaDataElement("original-response-headers", head.get());
5411 if (NS_FAILED(rv)) return rv;
5413 // Indicate we have successfully finished setting metadata on the cache entry.
5414 rv = entry->MetaDataReady();
5419 nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry) {
5420 return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead,
5424 inline void GetAuthType(const char *challenge, nsCString &authType) {
5427 // get the challenge type
5428 if ((p = strchr(challenge, ' ')) != nullptr)
5429 authType.Assign(challenge, p - challenge);
5431 authType.Assign(challenge);
5434 nsresult StoreAuthorizationMetaData(nsICacheEntry *entry,
5435 nsHttpRequestHead *requestHead) {
5436 // Not applicable to proxy authorization...
5438 if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
5442 // eg. [Basic realm="wally world"]
5444 GetAuthType(val.get(), buf);
5445 return entry->SetMetaDataElement("auth", buf.get());
5448 // Finalize the cache entry
5449 // - may need to rewrite response headers if any headers changed
5450 // - may need to recalculate the expiration time if any headers changed
5451 // - called only for freshly written cache entries
5452 nsresult nsHttpChannel::FinalizeCacheEntry() {
5453 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
5455 // Don't update this meta-data on 304
5456 if (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
5457 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
5459 mCacheEntry->SetMetaDataElement("strongly-framed", "1");
5462 if (mResponseHead && mResponseHeadersModified) {
5463 // Set the expiration time for this cache entry
5464 nsresult rv = UpdateExpirationTime();
5465 if (NS_FAILED(rv)) return rv;
5470 // Open an output stream to the cache entry and insert a listener tee into
5471 // the chain of response listeners.
5472 nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
5475 LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
5477 MOZ_ASSERT(mCacheEntry);
5478 MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial ||
5479 mRaceCacheWithNetwork);
5480 MOZ_ASSERT(mListener);
5482 nsAutoCString contentEncoding, contentType;
5483 Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
5484 mResponseHead->ContentType(contentType);
5485 // If the content is compressible and the server has not compressed it,
5486 // mark the cache entry for compression.
5487 if (contentEncoding.IsEmpty() &&
5488 (contentType.EqualsLiteral(TEXT_HTML) ||
5489 contentType.EqualsLiteral(TEXT_PLAIN) ||
5490 contentType.EqualsLiteral(TEXT_CSS) ||
5491 contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
5492 contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
5493 contentType.EqualsLiteral(TEXT_XML) ||
5494 contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
5495 contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
5496 contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
5497 contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
5498 rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
5499 if (NS_FAILED(rv)) {
5500 LOG(("unable to mark cache entry for compression"));
5504 LOG(("Trading cache input stream for output stream [channel=%p]", this));
5506 // We must close the input stream first because cache entries do not
5507 // correctly handle having an output stream and input streams open at
5509 mCacheInputStream.CloseAndRelease();
5511 int64_t predictedSize = mResponseHead->TotalEntitySize();
5512 if (predictedSize != -1) {
5513 predictedSize -= offset;
5516 nsCOMPtr<nsIOutputStream> out;
5518 mCacheEntry->OpenOutputStream(offset, predictedSize, getter_AddRefs(out));
5519 if (rv == NS_ERROR_NOT_AVAILABLE) {
5520 LOG((" entry doomed, not writing it [channel=%p]", this));
5521 // Entry is already doomed.
5522 // This may happen when expiration time is set to past and the entry
5523 // has been removed by the background eviction logic.
5526 if (rv == NS_ERROR_FILE_TOO_BIG) {
5527 LOG((" entry would exceed max allowed size, not writing it [channel=%p]",
5531 if (NS_FAILED(rv)) return rv;
5533 if (mCacheOnlyMetadata) {
5534 LOG(("Not storing content, cacheOnlyMetadata set"));
5535 // We must open and then close the output stream of the cache entry.
5536 // This way we indicate the content has been written (despite with zero
5537 // length) and the entry is now in the ready state with "having data".
5543 // XXX disk cache does not support overlapped i/o yet
5545 // Mark entry valid inorder to allow simultaneous reading...
5546 rv = mCacheEntry->MarkValid();
5547 if (NS_FAILED(rv)) return rv;
5550 nsCOMPtr<nsIStreamListenerTee> tee =
5551 do_CreateInstance(kStreamListenerTeeCID, &rv);
5552 if (NS_FAILED(rv)) return rv;
5554 LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
5555 static_cast<uint32_t>(rv)));
5556 rv = tee->Init(mListener, out, nullptr);
5557 if (NS_FAILED(rv)) return rv;
5563 nsresult nsHttpChannel::InstallOfflineCacheListener(int64_t offset) {
5566 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
5569 MOZ_ASSERT(mOfflineCacheEntry);
5570 MOZ_ASSERT(mListener);
5572 nsCOMPtr<nsIOutputStream> out;
5573 rv = mOfflineCacheEntry->OpenOutputStream(offset, -1, getter_AddRefs(out));
5574 if (NS_FAILED(rv)) return rv;
5576 nsCOMPtr<nsIStreamListenerTee> tee =
5577 do_CreateInstance(kStreamListenerTeeCID, &rv);
5578 if (NS_FAILED(rv)) return rv;
5580 rv = tee->Init(mListener, out, nullptr);
5581 if (NS_FAILED(rv)) return rv;
5588 void nsHttpChannel::ClearBogusContentEncodingIfNeeded() {
5589 // For .gz files, apache sends both a Content-Type: application/x-gzip
5590 // as well as Content-Encoding: gzip, which is completely wrong. In
5591 // this case, we choose to ignore the rogue Content-Encoding header. We
5592 // must do this early on so as to prevent it from being seen up stream.
5593 // The same problem exists for Content-Encoding: compress in default
5595 nsAutoCString contentType;
5596 mResponseHead->ContentType(contentType);
5597 if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") &&
5598 (contentType.EqualsLiteral(APPLICATION_GZIP) ||
5599 contentType.EqualsLiteral(APPLICATION_GZIP2) ||
5600 contentType.EqualsLiteral(APPLICATION_GZIP3))) {
5601 // clear the Content-Encoding header
5602 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
5603 } else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding,
5605 (contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
5606 contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
5607 // clear the Content-Encoding header
5608 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
5612 //-----------------------------------------------------------------------------
5613 // nsHttpChannel <redirect>
5614 //-----------------------------------------------------------------------------
5616 nsresult nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
5617 nsIChannel *newChannel,
5618 bool preserveMethod,
5619 uint32_t redirectFlags) {
5621 ("nsHttpChannel::SetupReplacementChannel "
5622 "[this=%p newChannel=%p preserveMethod=%d]",
5623 this, newChannel, preserveMethod));
5625 nsresult rv = HttpBaseChannel::SetupReplacementChannel(
5626 newURI, newChannel, preserveMethod, redirectFlags);
5627 if (NS_FAILED(rv)) return rv;
5629 rv = CheckRedirectLimit(redirectFlags);
5630 NS_ENSURE_SUCCESS(rv, rv);
5632 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
5633 if (!httpChannel) return NS_OK; // no other options to set
5635 // convey the mApplyConversion flag (bug 91862)
5636 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
5637 if (encodedChannel) encodedChannel->SetApplyConversion(mApplyConversion);
5639 // transfer the resume information
5641 nsCOMPtr<nsIResumableChannel> resumableChannel(
5642 do_QueryInterface(newChannel));
5643 if (!resumableChannel) {
5645 "Got asked to resume, but redirected to non-resumable channel!");
5646 return NS_ERROR_NOT_RESUMABLE;
5648 resumableChannel->ResumeAt(mStartPos, mEntityID);
5651 nsCOMPtr<nsIHttpChannelInternal> internalChannel =
5652 do_QueryInterface(newChannel, &rv);
5653 if (NS_SUCCEEDED(rv)) {
5654 TimeStamp timestamp;
5655 rv = GetNavigationStartTimeStamp(×tamp);
5656 if (NS_WARN_IF(NS_FAILED(rv))) {
5660 Unused << internalChannel->SetNavigationStartTimeStamp(timestamp);
5667 nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
5668 LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
5671 nsAutoCString location;
5673 // if a location header was not given, then we can't perform the redirect,
5674 // so just carry on as though this were a normal response.
5675 if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location)))
5676 return NS_ERROR_FAILURE;
5678 // If we were told to not follow redirects automatically, then again
5679 // carry on as though this were a normal response.
5680 if (mLoadInfo && mLoadInfo->GetDontFollowRedirects()) {
5681 return NS_ERROR_FAILURE;
5684 // make sure non-ASCII characters in the location header are escaped.
5685 nsAutoCString locationBuf;
5686 if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces,
5688 location = locationBuf;
5690 mRedirectType = redirectType;
5692 LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
5693 uint32_t(mRedirectionLimit)));
5695 nsresult rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
5697 if (NS_FAILED(rv)) {
5698 LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
5699 return NS_ERROR_CORRUPTED_CONTENT;
5702 if (mApplicationCache) {
5703 // if we are redirected to a different origin check if there is a fallback
5704 // cache entry to fall back to. we don't care about file strict
5705 // checking, at least mURI is not a file URI.
5706 if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
5707 PushRedirectAsyncFunc(
5708 &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
5709 bool waitingForRedirectCallback;
5710 Unused << ProcessFallback(&waitingForRedirectCallback);
5711 if (waitingForRedirectCallback) return NS_OK;
5712 PopRedirectAsyncFunc(
5713 &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
5717 return ContinueProcessRedirectionAfterFallback(NS_OK);
5720 nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
5721 if (NS_SUCCEEDED(rv) && mFallingBack) {
5722 // do not continue with redirect processing, fallback is in
5727 // Kill the current cache entry if we are redirecting
5729 bool redirectingBackToSameURI = false;
5730 if (mCacheEntry && mCacheEntryIsWriteOnly &&
5731 NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
5732 redirectingBackToSameURI)
5733 mCacheEntry->AsyncDoom(nullptr);
5735 // move the reference of the old location to the new one if the new
5737 PropagateReferenceIfNeeded(mURI, mRedirectURI);
5740 ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());
5742 // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
5743 if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
5744 rv = PromptTempRedirect();
5745 if (NS_FAILED(rv)) return rv;
5748 #ifdef MOZ_GECKO_PROFILER
5749 if (profiler_is_active()) {
5750 int32_t priority = PRIORITY_NORMAL;
5751 GetPriority(&priority);
5752 profiler_add_network_marker(
5753 mURI, priority, mChannelId, NetworkLoadType::LOAD_REDIRECT,
5754 mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
5755 mCacheDisposition, nullptr, mRedirectURI);
5759 nsCOMPtr<nsIIOService> ioService;
5760 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
5761 if (NS_FAILED(rv)) return rv;
5763 uint32_t redirectFlags;
5764 if (nsHttp::IsPermanentRedirect(mRedirectType))
5765 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
5767 redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
5769 nsCOMPtr<nsIChannel> newChannel;
5770 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
5771 CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
5772 rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
5774 nullptr, // PerformanceStorage
5775 nullptr, // aLoadGroup
5776 nullptr, // aCallbacks
5777 nsIRequest::LOAD_NORMAL, ioService);
5778 NS_ENSURE_SUCCESS(rv, rv);
5780 rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
5782 if (NS_FAILED(rv)) return rv;
5784 // verify that this is a legal redirect
5785 mRedirectChannel = newChannel;
5787 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
5788 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
5790 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
5792 if (NS_FAILED(rv)) {
5793 AutoRedirectVetoNotifier notifier(this);
5794 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
5800 nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
5801 AutoRedirectVetoNotifier notifier(this);
5803 LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
5804 static_cast<uint32_t>(rv), this));
5805 if (NS_FAILED(rv)) return rv;
5807 MOZ_ASSERT(mRedirectChannel, "No redirect channel?");
5809 // Make sure to do this after we received redirect veto answer,
5810 // i.e. after all sinks had been notified
5811 mRedirectChannel->SetOriginalURI(mOriginalURI);
5813 // XXX we used to talk directly with the script security manager, but that
5814 // should really be handled by the event sink implementation.
5816 // begin loading the new channel
5817 rv = mRedirectChannel->AsyncOpen(mListener);
5818 LOG((" new channel AsyncOpen returned %" PRIX32, static_cast<uint32_t>(rv)));
5819 NS_ENSURE_SUCCESS(rv, rv);
5821 // close down this channel
5822 Cancel(NS_BINDING_REDIRECTED);
5824 notifier.RedirectSucceeded();
5831 //-----------------------------------------------------------------------------
5832 // nsHttpChannel <auth>
5833 //-----------------------------------------------------------------------------
5835 NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
5836 LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
5838 // setting mAuthRetryPending flag and resuming the transaction
5839 // triggers process of throwing away the unauthenticated data already
5840 // coming from the network
5841 mAuthRetryPending = true;
5842 mProxyAuthPending = false;
5843 LOG(("Resuming the transaction, we got credentials from user"));
5844 if (mTransactionPump) {
5845 mTransactionPump->Resume();
5851 NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
5852 LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
5854 if (mTransactionPump) {
5855 // If the channel is trying to authenticate to a proxy and
5856 // that was canceled we cannot show the http response body
5857 // from the 40x as that might mislead the user into thinking
5858 // it was a end host response instead of a proxy reponse.
5859 // This must check explicitly whether a proxy auth was being done
5860 // because we do want to show the content if this is an error from
5861 // the origin server.
5862 if (mProxyAuthPending) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
5864 // ensure call of OnStartRequest of the current listener here,
5865 // it would not be called otherwise at all
5866 nsresult rv = CallOnStartRequest();
5868 // drop mAuthRetryPending flag and resume the transaction
5869 // this resumes load of the unauthenticated content data (which
5870 // may have been canceled if we don't want to show it)
5871 mAuthRetryPending = false;
5872 LOG(("Resuming the transaction, user cancelled the auth dialog"));
5873 mTransactionPump->Resume();
5875 if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
5878 mProxyAuthPending = false;
5882 NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
5883 LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
5885 // Require we are between OnStartRequest and OnStopRequest, because
5886 // what we do here takes effect in OnStopRequest (not reusing the
5887 // connection for next authentication round).
5889 LOG((" channel not pending"));
5891 "CloseStickyConnection not called before OnStopRequest, won't have any "
5893 return NS_ERROR_UNEXPECTED;
5896 MOZ_ASSERT(mTransaction);
5897 if (!mTransaction) {
5898 return NS_ERROR_UNEXPECTED;
5901 if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
5902 mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
5903 LOG((" not sticky"));
5907 mTransaction->DontReuseConnection();
5911 NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
5912 LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
5914 mAuthConnectionRestartable = aRestartable;
5918 //-----------------------------------------------------------------------------
5919 // nsHttpChannel::nsISupports
5920 //-----------------------------------------------------------------------------
5922 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
5923 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
5925 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
5926 NS_INTERFACE_MAP_ENTRY(nsIRequest)
5927 NS_INTERFACE_MAP_ENTRY(nsIChannel)
5928 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
5929 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
5930 NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
5931 NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
5932 NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
5933 NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
5934 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
5935 NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
5936 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
5937 NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
5938 NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
5939 NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
5940 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
5941 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
5942 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
5943 NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
5944 NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
5945 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
5946 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
5947 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
5948 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
5949 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
5950 NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
5951 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
5952 NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
5953 NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
5954 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
5955 NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
5956 NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
5957 NS_INTERFACE_MAP_ENTRY_CONCRETE(nsHttpChannel)
5958 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
5960 //-----------------------------------------------------------------------------
5961 // nsHttpChannel::nsIRequest
5962 //-----------------------------------------------------------------------------
5965 nsHttpChannel::Cancel(nsresult status) {
5966 MOZ_ASSERT(NS_IsMainThread());
5967 // We should never have a pump open while a CORS preflight is in progress.
5968 MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
5970 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
5971 MOZ_CRASH_UNSAFE_PRINTF(
5972 "Blocking classifier error %" PRIx32
5973 " need to be handled by CancelByChannelClassifier()",
5974 static_cast<uint32_t>(status));
5978 LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
5979 static_cast<uint32_t>(status)));
5981 LOG((" ignoring; already canceled\n"));
5985 if (mWaitingForRedirectCallback) {
5986 LOG(("channel canceled during wait for redirect callback"));
5989 return CancelInternal(status);
5993 nsHttpChannel::CancelByChannelClassifier(nsresult aErrorCode) {
5995 UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
5996 MOZ_ASSERT(NS_IsMainThread());
5997 // We should never have a pump open while a CORS preflight is in progress.
5998 MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
6000 LOG(("nsHttpChannel::CancelByChannelClassifier [this=%p]\n", this));
6003 LOG((" ignoring; already canceled\n"));
6007 // We are being canceled by the channel classifier because of tracking
6008 // protection, but we haven't yet had a chance to dispatch the
6009 // "http-on-modify-request" notifications yet (this would normally be
6010 // done in PrepareToConnect()). So do that now, before proceeding to
6013 // Note that running these observers can itself result in the channel
6014 // being canceled. In that case, we accept that cancelation code as
6015 // the cause of the cancelation, as if the classification of the channel
6016 // would have occurred past this point!
6018 // notify "http-on-modify-request" observers
6019 CallOnModifyRequestObservers();
6021 SetLoadGroupUserAgentOverride();
6023 // Check if request was cancelled during on-modify-request or on-useragent.
6028 if (mSuspendCount) {
6029 LOG(("Waiting until resume in Cancel [this=%p]\n", this));
6030 MOZ_ASSERT(!mCallOnResume);
6031 mChannelClassifierCancellationPending = 1;
6032 mCallOnResume = [aErrorCode](nsHttpChannel *self) {
6033 self->HandleContinueCancellingByChannelClassifier(aErrorCode);
6039 // Check to see if we should redirect this channel elsewhere by
6040 // nsIHttpChannel.redirectTo API request
6041 if (mAPIRedirectToURI) {
6042 mChannelClassifierCancellationPending = 1;
6043 return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
6046 return CancelInternal(aErrorCode);
6049 void nsHttpChannel::ContinueCancellingByChannelClassifier(nsresult aErrorCode) {
6051 UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
6052 MOZ_ASSERT(NS_IsMainThread());
6053 // We should never have a pump open while a CORS preflight is in progress.
6054 MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
6056 LOG(("nsHttpChannel::ContinueCancellingByChannelClassifier [this=%p]\n",
6059 LOG((" ignoring; already canceled\n"));
6063 // Check to see if we should redirect this channel elsewhere by
6064 // nsIHttpChannel.redirectTo API request
6065 if (mAPIRedirectToURI) {
6066 Unused << AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
6070 Unused << CancelInternal(aErrorCode);
6073 nsresult nsHttpChannel::CancelInternal(nsresult status) {
6074 bool channelClassifierCancellationPending =
6075 !!mChannelClassifierCancellationPending;
6076 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
6077 mChannelClassifierCancellationPending = 0;
6082 if (mProxyRequest) mProxyRequest->Cancel(status);
6083 CancelNetworkRequest(status);
6084 mCacheInputStream.CloseAndRelease();
6085 if (mCachePump) mCachePump->Cancel(status);
6086 if (mAuthProvider) mAuthProvider->Cancel(status);
6087 if (mPreflightChannel) mPreflightChannel->Cancel(status);
6088 if (mRequestContext && mOnTailUnblock) {
6089 mOnTailUnblock = nullptr;
6090 mRequestContext->CancelTailedRequest(this);
6091 CloseCacheEntry(false);
6092 Unused << AsyncAbort(status);
6093 } else if (channelClassifierCancellationPending) {
6094 // If we're coming from an asynchronous path when canceling a channel due
6095 // to safe-browsing protection, we need to AsyncAbort the channel now.
6096 Unused << AsyncAbort(status);
6101 void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
6103 nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
6104 if (NS_FAILED(rv)) {
6105 LOG(("failed to cancel the transaction\n"));
6108 if (mTransactionPump) mTransactionPump->Cancel(aStatus);
6112 nsHttpChannel::Suspend() {
6113 nsresult rv = SuspendInternal();
6115 nsresult rvParentChannel = NS_OK;
6116 if (mParentChannel) {
6117 rvParentChannel = mParentChannel->SuspendMessageDiversion();
6120 return NS_FAILED(rv) ? rv : rvParentChannel;
6124 nsHttpChannel::Resume() {
6125 nsresult rv = ResumeInternal();
6127 nsresult rvParentChannel = NS_OK;
6128 if (mParentChannel) {
6129 rvParentChannel = mParentChannel->ResumeMessageDiversion();
6132 return NS_FAILED(rv) ? rv : rvParentChannel;
6135 //-----------------------------------------------------------------------------
6136 // nsHttpChannel::nsIChannel
6137 //-----------------------------------------------------------------------------
6140 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo) {
6141 NS_ENSURE_ARG_POINTER(securityInfo);
6142 *securityInfo = mSecurityInfo;
6143 NS_IF_ADDREF(*securityInfo);
6147 // If any of the functions that AsyncOpen calls returns immediately an error
6148 // AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
6149 // To be sure that they are not call ReleaseListeners() is called.
6150 // If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
6153 nsHttpChannel::AsyncOpen(nsIStreamListener *aListener) {
6154 nsCOMPtr<nsIStreamListener> listener = aListener;
6156 nsContentSecurityManager::doContentSecurityCheck(this, listener);
6157 if (NS_WARN_IF(NS_FAILED(rv))) {
6162 !mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
6163 mLoadInfo->GetInitialSecurityCheckDone() ||
6164 (mLoadInfo->GetSecurityMode() ==
6165 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
6166 nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
6167 "security flags in loadInfo but doContentSecurityCheck() not called");
6169 LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
6171 #ifdef MOZ_TASK_TRACER
6172 if (tasktracer::IsStartLogging()) {
6173 uint64_t sourceEventId, parentTaskId;
6174 tasktracer::SourceEventType sourceEventType;
6175 GetCurTraceInfo(&sourceEventId, &parentTaskId, &sourceEventType);
6176 nsAutoCString urispec;
6177 mURI->GetSpec(urispec);
6178 tasktracer::AddLabel("nsHttpChannel::AsyncOpen %s", urispec.get());
6182 #ifdef MOZ_GECKO_PROFILER
6183 mLastStatusReported =
6184 TimeStamp::Now(); // in case we enable the profiler after AsyncOpen()
6185 if (profiler_is_active()) {
6186 profiler_add_network_marker(
6187 mURI, mPriority, mChannelId, NetworkLoadType::LOAD_START,
6188 mChannelCreationTimestamp, mLastStatusReported, 0, mCacheDisposition);
6192 NS_CompareLoadInfoAndLoadContext(this);
6195 AssertPrivateBrowsingId();
6198 NS_ENSURE_ARG_POINTER(listener);
6199 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
6200 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
6202 if (MaybeWaitForUploadStreamLength(listener, nullptr)) {
6206 MOZ_ASSERT(NS_IsMainThread());
6208 if (!gHttpHandler->Active()) {
6209 LOG((" after HTTP shutdown..."));
6211 return NS_ERROR_NOT_AVAILABLE;
6214 static bool sRCWNInited = false;
6217 Preferences::AddBoolVarCache(&sRCWNEnabled, "network.http.rcwn.enabled");
6218 Preferences::AddUintVarCache(
6219 &sRCWNQueueSizeNormal,
6220 "network.http.rcwn.cache_queue_normal_threshold");
6221 Preferences::AddUintVarCache(
6222 &sRCWNQueueSizePriority,
6223 "network.http.rcwn.cache_queue_priority_threshold");
6224 Preferences::AddUintVarCache(&sRCWNSmallResourceSizeKB,
6225 "network.http.rcwn.small_resource_size_kb");
6226 Preferences::AddUintVarCache(&sRCWNMinWaitMs,
6227 "network.http.rcwn.min_wait_before_racing_ms");
6228 Preferences::AddUintVarCache(&sRCWNMaxWaitMs,
6229 "network.http.rcwn.max_wait_before_racing_ms");
6232 rv = NS_CheckPortSafety(mURI);
6233 if (NS_FAILED(rv)) {
6238 if (!mLoadGroup && !mCallbacks) {
6239 // If no one called SetLoadGroup or SetNotificationCallbacks, the private
6240 // state has not been updated on PrivateBrowsingChannel (which we derive
6241 // from) Hence, we have to call UpdatePrivateBrowsing() here
6242 UpdatePrivateBrowsing();
6245 if (WaitingForTailUnblock()) {
6246 // This channel is marked as Tail and is part of a request context
6247 // that has positive number of non-tailed requestst, hence this channel
6248 // has been put to a queue.
6249 // When tail is unblocked, OnTailUnblock on this channel will be called
6250 // to continue AsyncOpen.
6251 mListener = listener;
6252 MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
6253 mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;
6255 LOG((" put on hold until tail is unblocked"));
6259 // Remember the cookie header that was set, if any
6260 nsAutoCString cookieHeader;
6261 if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
6262 mUserSetCookieHeader = cookieHeader;
6265 // Set user agent override, do so before OnOpeningRequest notification
6266 // since we want to allow consumers of that notification change or remove
6267 // the User-Agent request header.
6268 HttpBaseChannel::SetDocshellUserAgentOverride();
6270 // After we notify any observers (on-opening-request, loadGroup, etc) we
6271 // must return NS_OK and return any errors asynchronously via
6272 // OnStart/OnStopRequest. Observers may add a reference to the channel
6273 // and expect to get OnStopRequest so they know when to drop the reference,
6276 // notify "http-on-opening-request" observers, but not if this is a redirect
6277 if (!(mLoadFlags & LOAD_REPLACE)) {
6278 gHttpHandler->OnOpeningRequest(this);
6284 mListener = listener;
6286 // PauseTask/DelayHttpChannel queuing
6287 if (!DelayHttpChannelQueue::AttemptQueueChannel(this)) {
6288 // If fuzzyfox is disabled; or adding to the queue failed, the channel must
6290 AsyncOpenFinal(TimeStamp::Now());
6296 nsresult nsHttpChannel::AsyncOpenFinal(TimeStamp aTimeStamp) {
6297 // Added due to PauseTask/DelayHttpChannel
6300 if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
6302 // record asyncopen time unconditionally and clear it if we
6303 // don't want it after OnModifyRequest() weighs in. But waiting for
6304 // that to complete would mean we don't include proxy resolution in the
6306 if (!mAsyncOpenTimeOverriden) {
6307 mAsyncOpenTime = aTimeStamp;
6310 // Remember we have Authorization header set here. We need to check on it
6311 // just once and early, AsyncOpen is the best place.
6312 mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization);
6314 // The common case for HTTP channels is to begin proxy resolution and return
6315 // at this point. The only time we know mProxyInfo already is if we're
6316 // proxying a non-http protocol like ftp. We don't need to discover proxy
6317 // settings if we are never going to make a network connection.
6319 !(mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) &&
6320 NS_SUCCEEDED(ResolveProxy())) {
6324 rv = BeginConnect();
6325 if (NS_FAILED(rv)) {
6326 CloseCacheEntry(false);
6327 Unused << AsyncAbort(rv);
6333 nsresult nsHttpChannel::AsyncOpenOnTailUnblock() {
6334 return AsyncOpen(mListener);
6337 already_AddRefed<nsChannelClassifier>
6338 nsHttpChannel::GetOrCreateChannelClassifier() {
6339 if (!mChannelClassifier) {
6340 mChannelClassifier = new nsChannelClassifier(this);
6341 LOG(("nsHttpChannel [%p] created nsChannelClassifier [%p]\n", this,
6342 mChannelClassifier.get()));
6345 RefPtr<nsChannelClassifier> classifier = mChannelClassifier;
6346 return classifier.forget();
6349 // BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
6350 // functions that called BeginConnect if needed. Only AsyncOpen and
6351 // OnProxyAvailable ever call BeginConnect.
6352 nsresult nsHttpChannel::BeginConnect() {
6353 LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
6356 // Construct connection info object
6358 nsAutoCString scheme;
6360 bool isHttps = false;
6362 rv = mURI->GetScheme(scheme);
6363 if (NS_SUCCEEDED(rv)) rv = mURI->SchemeIs("https", &isHttps);
6364 if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
6365 if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
6366 if (NS_SUCCEEDED(rv)) mURI->GetUsername(mUsername);
6367 if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
6368 if (NS_FAILED(rv)) {
6372 // Reject the URL if it doesn't specify a host
6373 if (host.IsEmpty()) {
6374 rv = NS_ERROR_MALFORMED_URI;
6377 LOG(("host=%s port=%d\n", host.get(), port));
6378 LOG(("uri=%s\n", mSpec.get()));
6380 nsCOMPtr<nsProxyInfo> proxyInfo;
6381 if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
6383 if (mCaps & NS_HTTP_CONNECT_ONLY) {
6385 LOG(("return failure: no proxy for connect-only channel\n"));
6386 return NS_ERROR_FAILURE;
6389 if (!proxyInfo->IsHTTP() && !proxyInfo->IsHTTPS()) {
6390 LOG(("return failure: non-http proxy for connect-only channel\n"));
6391 return NS_ERROR_FAILURE;
6395 mRequestHead.SetHTTPS(isHttps);
6396 mRequestHead.SetOrigin(scheme, host, port);
6401 OriginAttributes originAttributes;
6402 NS_GetOriginAttributes(this, originAttributes);
6404 RefPtr<nsHttpConnectionInfo> connInfo =
6405 new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername, proxyInfo,
6406 originAttributes, isHttps);
6407 mAllowAltSvc = (mAllowAltSvc && !gHttpHandler->IsSpdyBlacklisted(connInfo));
6409 RefPtr<AltSvcMapping> mapping;
6410 if (!mConnectionInfo && mAllowAltSvc && // per channel
6411 !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
6412 AltSvcMapping::AcceptableProxy(proxyInfo) &&
6413 (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
6414 (mapping = gHttpHandler->GetAltServiceMapping(
6415 scheme, host, port, mPrivateBrowsing, originAttributes))) {
6416 LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n", this,
6417 scheme.get(), mapping->AlternateHost().get(), mapping->AlternatePort(),
6418 mapping->HashKey().get()));
6420 if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
6421 nsAutoCString altUsedLine(mapping->AlternateHost());
6423 mapping->AlternatePort() ==
6424 (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
6426 altUsedLine.AppendLiteral(":");
6427 altUsedLine.AppendInt(mapping->AlternatePort());
6429 rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
6430 MOZ_ASSERT(NS_SUCCEEDED(rv));
6433 nsCOMPtr<nsIConsoleService> consoleService =
6434 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
6435 if (consoleService) {
6436 nsAutoString message(
6437 NS_LITERAL_STRING("Alternate Service Mapping found: "));
6438 AppendASCIItoUTF16(scheme, message);
6439 message.AppendLiteral(u"://");
6440 AppendASCIItoUTF16(host, message);
6441 message.AppendLiteral(u":");
6442 message.AppendInt(port);
6443 message.AppendLiteral(u" to ");
6444 AppendASCIItoUTF16(scheme, message);
6445 message.AppendLiteral(u"://");
6446 AppendASCIItoUTF16(mapping->AlternateHost(), message);
6447 message.AppendLiteral(u":");
6448 message.AppendInt(mapping->AlternatePort());
6449 consoleService->LogStringMessage(message.get());
6452 LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
6453 mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
6455 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
6456 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
6457 } else if (mConnectionInfo) {
6458 LOG(("nsHttpChannel %p Using channel supplied connection info", this));
6459 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
6461 LOG(("nsHttpChannel %p Using default connection info", this));
6463 mConnectionInfo = connInfo;
6464 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
6467 // Need to re-ask the handler, since mConnectionInfo may not be the connInfo
6469 if (gHttpHandler->IsSpdyBlacklisted(mConnectionInfo)) {
6471 mCaps |= NS_HTTP_DISALLOW_SPDY;
6472 mConnectionInfo->SetNoSpdy(true);
6475 mAuthProvider = new nsHttpChannelAuthProvider();
6476 rv = mAuthProvider->Init(this);
6477 if (NS_FAILED(rv)) {
6481 // check to see if authorization headers should be included
6482 // mCustomAuthHeader is set in AsyncOpen if we find Authorization header
6483 rv = mAuthProvider->AddAuthorizationHeaders(mCustomAuthHeader);
6484 if (NS_FAILED(rv)) {
6485 LOG(("nsHttpChannel %p AddAuthorizationHeaders failed (%08x)", this,
6486 static_cast<uint32_t>(rv)));
6489 // If mTimingEnabled flag is not set after OnModifyRequest() then
6490 // clear the already recorded AsyncOpen value for consistency.
6491 if (!mTimingEnabled) mAsyncOpenTime = TimeStamp();
6493 // if this somehow fails we can go on without it
6494 Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
6496 if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
6497 mCaps |= NS_HTTP_REFRESH_DNS;
6499 // Adjust mCaps according to our request headers:
6500 // - If "Connection: close" is set as a request header, then do not bother
6501 // trying to establish a keep-alive connection.
6502 if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
6503 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
6505 if (gHttpHandler->CriticalRequestPrioritization()) {
6506 if (mClassOfService & nsIClassOfService::Leader) {
6507 mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
6509 if (mClassOfService & nsIClassOfService::Unblocked) {
6510 mCaps |= NS_HTTP_LOAD_UNBLOCKED;
6512 if (mClassOfService & nsIClassOfService::UrgentStart &&
6513 gHttpHandler->IsUrgentStartEnabled()) {
6514 mCaps |= NS_HTTP_URGENT_START;
6515 SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
6519 // Force-Reload should reset the persistent connection pool for this host
6520 if (mLoadFlags & LOAD_FRESH_CONNECTION) {
6521 // just the initial document resets the whole pool
6522 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
6523 gHttpHandler->ConnMgr()->ClearAltServiceMappings();
6524 rv = gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(
6526 if (NS_FAILED(rv)) {
6528 ("nsHttpChannel::BeginConnect "
6529 "DoShiftReloadConnectionCleanup failed: %08x [this=%p]",
6530 static_cast<uint32_t>(rv), this));
6535 // We may have been cancelled already, either by on-modify-request
6536 // listeners or load group observers; in that case, we should not send the
6537 // request to the server
6542 if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
6543 MaybeStartDNSPrefetch();
6544 return ContinueBeginConnectWithResult();
6547 // We are about to do an async lookup to check if the URI is a
6548 // tracker. If yes, this channel will be canceled by channel classifier.
6549 // Chances are the lookup is not needed so CheckIsTrackerWithLocalTable()
6550 // will return an error and then we can BeginConnectActual() right away.
6551 RefPtr<nsHttpChannel> self = this;
6552 bool willCallback = NS_SUCCEEDED(
6553 AsyncUrlChannelClassifier::CheckChannel(this, [self]() -> void {
6554 nsresult rv = self->BeginConnectActual();
6555 if (NS_FAILED(rv)) {
6556 // Since this error is thrown asynchronously so that the caller
6557 // of BeginConnect() will not do clean up for us. We have to do
6559 self->CloseCacheEntry(false);
6560 Unused << self->AsyncAbort(rv);
6564 if (!willCallback) {
6565 // We can do BeginConnectActual immediately if CheckIsTrackerWithLocalTable
6566 // is failed. Note that we don't need to handle the failure because
6567 // BeginConnect() will return synchronously and the caller will be
6568 // responsible for handling it.
6569 return BeginConnectActual();
6575 void nsHttpChannel::MaybeStartDNSPrefetch() {
6576 if (!mConnectionInfo->UsingHttpProxy() &&
6577 !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
6578 // Start a DNS lookup very early in case the real open is queued the DNS can
6579 // happen in parallel. Do not do so in the presence of an HTTP proxy as
6580 // all lookups other than for the proxy itself are done by the proxy.
6581 // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
6582 // LOAD_ONLY_FROM_CACHE flags are set.
6584 // We keep the DNS prefetch object around so that we can retrieve
6585 // timing information from it. There is no guarantee that we actually
6586 // use the DNS prefetch data for the real connection, but as we keep
6587 // this data around for 3 minutes by default, this should almost always
6588 // be correct, and even when it isn't, the timing still represents _a_
6589 // valid DNS lookup timing for the site, even if it is not _the_
6591 LOG(("nsHttpChannel::MaybeStartDNSPrefetch [this=%p] prefetching%s\n", this,
6592 mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
6593 OriginAttributes originAttributes;
6594 NS_GetOriginAttributes(this, originAttributes);
6596 new nsDNSPrefetch(mURI, originAttributes, this, mTimingEnabled);
6597 mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
6601 nsresult nsHttpChannel::BeginConnectActual() {
6606 AUTO_PROFILER_LABEL("nsHttpChannel::BeginConnectActual", NETWORK);
6608 if (mChannelClassifierCancellationPending) {
6610 ("Waiting for safe-browsing protection cancellation in "
6611 "BeginConnectActual [this=%p]\n",
6616 MaybeStartDNSPrefetch();
6618 nsresult rv = ContinueBeginConnectWithResult();
6619 if (NS_FAILED(rv)) {
6623 // Start nsChannelClassifier to catch phishing and malware URIs.
6624 RefPtr<nsChannelClassifier> channelClassifier =
6625 GetOrCreateChannelClassifier();
6626 LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
6627 channelClassifier.get(), this));
6628 channelClassifier->Start();
6634 nsHttpChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize) {
6635 if (mCacheEntry && !mCacheEntryIsWriteOnly) {
6636 int64_t dataSize = 0;
6637 mCacheEntry->GetDataSize(&dataSize);
6638 *aEncodedBodySize = dataSize;
6640 *aEncodedBodySize = mLogicalOffset;
6645 //-----------------------------------------------------------------------------
6646 // nsHttpChannel::nsIHttpChannelInternal
6647 //-----------------------------------------------------------------------------
6650 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey) {
6651 ENSURE_CALLED_BEFORE_CONNECT();
6653 LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]\n", this,
6655 mFallbackChannel = true;
6656 mFallbackKey = aFallbackKey;
6662 nsHttpChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
6663 if (aChannelIsForDownload) {
6664 AddClassFlags(nsIClassOfService::Throttleable);
6666 ClearClassFlags(nsIClassOfService::Throttleable);
6669 return HttpBaseChannel::SetChannelIsForDownload(aChannelIsForDownload);
6672 base::ProcessId nsHttpChannel::ProcessId() {
6673 nsCOMPtr<nsIParentChannel> parentChannel;
6674 NS_QueryNotificationCallbacks(this, parentChannel);
6675 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6677 return httpParent->OtherPid();
6679 return base::GetCurrentProcId();
6682 bool nsHttpChannel::AttachStreamFilter(
6683 mozilla::ipc::Endpoint<extensions::PStreamFilterParent> &&aEndpoint)
6686 nsCOMPtr<nsIParentChannel> parentChannel;
6687 NS_QueryNotificationCallbacks(this, parentChannel);
6688 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6690 return httpParent->SendAttachStreamFilter(std::move(aEndpoint));
6693 extensions::StreamFilterParent::Attach(this, std::move(aEndpoint));
6698 nsHttpChannel::GetNavigationStartTimeStamp(TimeStamp *aTimeStamp) {
6699 LOG(("nsHttpChannel::GetNavigationStartTimeStamp %p", this));
6700 MOZ_ASSERT(aTimeStamp);
6701 *aTimeStamp = mNavigationStartTimeStamp;
6706 nsHttpChannel::SetNavigationStartTimeStamp(TimeStamp aTimeStamp) {
6707 LOG(("nsHttpChannel::SetNavigationStartTimeStamp %p", this));
6708 mNavigationStartTimeStamp = aTimeStamp;
6712 //-----------------------------------------------------------------------------
6713 // nsHttpChannel::nsISupportsPriority
6714 //-----------------------------------------------------------------------------
6717 nsHttpChannel::SetPriority(int32_t value) {
6718 int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
6719 if (mPriority == newValue) return NS_OK;
6721 LOG(("nsHttpChannel::SetPriority %p p=%d", this, newValue));
6723 mPriority = newValue;
6725 nsresult rv = gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
6726 if (NS_FAILED(rv)) {
6728 ("nsHttpChannel::SetPriority [this=%p] "
6729 "RescheduleTransaction failed (%08x)",
6730 this, static_cast<uint32_t>(rv)));
6734 // If this channel is the real channel for an e10s channel, notify the
6735 // child side about the priority change as well.
6736 nsCOMPtr<nsIParentChannel> parentChannel;
6737 NS_QueryNotificationCallbacks(this, parentChannel);
6738 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6740 httpParent->DoSendSetPriority(newValue);
6746 nsresult nsHttpChannel::ContinueBeginConnectWithResult() {
6747 LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
6748 MOZ_ASSERT(!mCallOnResume, "How did that happen?");
6752 if (mSuspendCount) {
6753 LOG(("Waiting until resume to do async connect [this=%p]\n", this));
6754 mCallOnResume = [](nsHttpChannel *self) {
6755 self->ContinueBeginConnect();
6759 } else if (mCanceled) {
6760 // We may have been cancelled already, by nsChannelClassifier in that
6761 // case, we should not send the request to the server
6764 rv = PrepareToConnect();
6768 ("nsHttpChannel::ContinueBeginConnectWithResult result [this=%p "
6769 "rv=%" PRIx32 " mCanceled=%u]\n",
6770 this, static_cast<uint32_t>(rv), static_cast<bool>(mCanceled)));
6774 void nsHttpChannel::ContinueBeginConnect() {
6775 LOG(("nsHttpChannel::ContinueBeginConnect this=%p", this));
6777 nsresult rv = ContinueBeginConnectWithResult();
6778 if (NS_FAILED(rv)) {
6779 CloseCacheEntry(false);
6780 Unused << AsyncAbort(rv);
6784 //-----------------------------------------------------------------------------
6785 // HttpChannel::nsIClassOfService
6786 //-----------------------------------------------------------------------------
6788 void nsHttpChannel::OnClassOfServiceUpdated() {
6789 LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%u", this,
6793 gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
6796 if (EligibleForTailing()) {
6797 RemoveAsNonTailRequest();
6799 AddAsNonTailRequest();
6804 nsHttpChannel::SetClassFlags(uint32_t inFlags) {
6805 uint32_t previous = mClassOfService;
6806 mClassOfService = inFlags;
6807 if (previous != mClassOfService) {
6808 OnClassOfServiceUpdated();
6814 nsHttpChannel::AddClassFlags(uint32_t inFlags) {
6815 uint32_t previous = mClassOfService;
6816 mClassOfService |= inFlags;
6817 if (previous != mClassOfService) {
6818 OnClassOfServiceUpdated();
6824 nsHttpChannel::ClearClassFlags(uint32_t inFlags) {
6825 uint32_t previous = mClassOfService;
6826 mClassOfService &= ~inFlags;
6827 if (previous != mClassOfService) {
6828 OnClassOfServiceUpdated();
6833 //-----------------------------------------------------------------------------
6834 // nsHttpChannel::nsIProtocolProxyCallback
6835 //-----------------------------------------------------------------------------
6838 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
6839 nsIProxyInfo *pi, nsresult status) {
6840 LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
6841 " mStatus=%" PRIx32 "]\n",
6842 this, pi, static_cast<uint32_t>(status),
6843 static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
6844 mProxyRequest = nullptr;
6848 // If status is a failure code, then it means that we failed to resolve
6849 // proxy info. That is a non-fatal error assuming it wasn't because the
6850 // request was canceled. We just failover to DIRECT when proxy resolution
6851 // fails (failure can mean that the PAC URL could not be loaded).
6853 if (NS_SUCCEEDED(status)) mProxyInfo = pi;
6855 if (!gHttpHandler->Active()) {
6857 ("nsHttpChannel::OnProxyAvailable [this=%p] "
6858 "Handler no longer active.\n",
6860 rv = NS_ERROR_NOT_AVAILABLE;
6862 rv = BeginConnect();
6865 if (NS_FAILED(rv)) {
6866 CloseCacheEntry(false);
6867 Unused << AsyncAbort(rv);
6872 //-----------------------------------------------------------------------------
6873 // nsHttpChannel::nsIProxiedChannel
6874 //-----------------------------------------------------------------------------
6877 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result) {
6878 if (!mConnectionInfo)
6879 *result = mProxyInfo;
6881 *result = mConnectionInfo->ProxyInfo();
6882 NS_IF_ADDREF(*result);
6886 //-----------------------------------------------------------------------------
6887 // nsHttpChannel::nsITimedChannel
6888 //-----------------------------------------------------------------------------
6891 nsHttpChannel::GetDomainLookupStart(TimeStamp *_retval) {
6893 *_retval = mTransaction->GetDomainLookupStart();
6895 *_retval = mTransactionTimings.domainLookupStart;
6900 nsHttpChannel::GetDomainLookupEnd(TimeStamp *_retval) {
6902 *_retval = mTransaction->GetDomainLookupEnd();
6904 *_retval = mTransactionTimings.domainLookupEnd;
6909 nsHttpChannel::GetConnectStart(TimeStamp *_retval) {
6911 *_retval = mTransaction->GetConnectStart();
6913 *_retval = mTransactionTimings.connectStart;
6918 nsHttpChannel::GetTcpConnectEnd(TimeStamp *_retval) {
6920 *_retval = mTransaction->GetTcpConnectEnd();
6922 *_retval = mTransactionTimings.tcpConnectEnd;
6927 nsHttpChannel::GetSecureConnectionStart(TimeStamp *_retval) {
6929 *_retval = mTransaction->GetSecureConnectionStart();
6931 *_retval = mTransactionTimings.secureConnectionStart;
6936 nsHttpChannel::GetConnectEnd(TimeStamp *_retval) {
6938 *_retval = mTransaction->GetConnectEnd();
6940 *_retval = mTransactionTimings.connectEnd;
6945 nsHttpChannel::GetRequestStart(TimeStamp *_retval) {
6947 *_retval = mTransaction->GetRequestStart();
6949 *_retval = mTransactionTimings.requestStart;
6954 nsHttpChannel::GetResponseStart(TimeStamp *_retval) {
6956 *_retval = mTransaction->GetResponseStart();
6958 *_retval = mTransactionTimings.responseStart;
6963 nsHttpChannel::GetResponseEnd(TimeStamp *_retval) {
6965 *_retval = mTransaction->GetResponseEnd();
6967 *_retval = mTransactionTimings.responseEnd;
6971 //-----------------------------------------------------------------------------
6972 // nsHttpChannel::nsIHttpAuthenticableChannel
6973 //-----------------------------------------------------------------------------
6976 nsHttpChannel::GetIsSSL(bool *aIsSSL) {
6977 // this attribute is really misnamed - it wants to know if
6978 // https:// is being used. SSL might be used to cover http://
6979 // in some circumstances (proxies, http/2, etc..)
6980 return mURI->SchemeIs("https", aIsSSL);
6984 nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect) {
6985 *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
6990 nsHttpChannel::GetServerResponseHeader(nsACString &value) {
6991 if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
6992 return mResponseHead->GetHeader(nsHttp::Server, value);
6996 nsHttpChannel::GetProxyChallenges(nsACString &value) {
6997 if (!mResponseHead) return NS_ERROR_UNEXPECTED;
6998 return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
7002 nsHttpChannel::GetWWWChallenges(nsACString &value) {
7003 if (!mResponseHead) return NS_ERROR_UNEXPECTED;
7004 return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
7008 nsHttpChannel::SetProxyCredentials(const nsACString &value) {
7009 return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
7013 nsHttpChannel::SetWWWCredentials(const nsACString &value) {
7014 // This method is called when various browser initiated authorization
7015 // code sets the credentials. We need to flag this header as the
7016 // "browser default" so it does not show up in the ServiceWorker
7017 // FetchEvent. This may actually get called more than once, though,
7018 // so we clear the header first since "default" headers are not
7019 // allowed to overwrite normally.
7020 Unused << mRequestHead.ClearHeader(nsHttp::Authorization);
7021 return mRequestHead.SetHeader(nsHttp::Authorization, value, false,
7022 nsHttpHeaderArray::eVarietyRequestDefault);
7025 //-----------------------------------------------------------------------------
7026 // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
7027 // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
7031 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) {
7032 return HttpBaseChannel::GetLoadFlags(aLoadFlags);
7036 nsHttpChannel::GetURI(nsIURI **aURI) { return HttpBaseChannel::GetURI(aURI); }
7039 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) {
7040 return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
7044 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) {
7045 return HttpBaseChannel::GetLoadGroup(aLoadGroup);
7049 nsHttpChannel::GetRequestMethod(nsACString &aMethod) {
7050 return HttpBaseChannel::GetRequestMethod(aMethod);
7053 //-----------------------------------------------------------------------------
7054 // nsHttpChannel::nsIRequestObserver
7055 //-----------------------------------------------------------------------------
7057 // This class is used to convert from a DOM promise to a MozPromise.
7058 class DomPromiseListener final : dom::PromiseNativeHandler {
7061 static RefPtr<nsHttpChannel::TabPromise> Create(dom::Promise *aDOMPromise) {
7062 MOZ_ASSERT(aDOMPromise);
7063 RefPtr<DomPromiseListener> handler = new DomPromiseListener();
7064 RefPtr<nsHttpChannel::TabPromise> promise =
7065 handler->mPromiseHolder.Ensure(__func__);
7066 aDOMPromise->AppendNativeHandler(handler);
7070 virtual void ResolvedCallback(JSContext *aCx,
7071 JS::Handle<JS::Value> aValue) override {
7072 nsCOMPtr<nsITabParent> tabParent;
7073 JS::Rooted<JSObject *> obj(aCx, &aValue.toObject());
7074 nsresult rv = UnwrapArg<nsITabParent>(aCx, obj, getter_AddRefs(tabParent));
7075 if (NS_FAILED(rv)) {
7076 mPromiseHolder.Reject(rv, __func__);
7079 mPromiseHolder.Resolve(tabParent, __func__);
7082 virtual void RejectedCallback(JSContext *aCx,
7083 JS::Handle<JS::Value> aValue) override {
7084 if (!aValue.isInt32()) {
7085 mPromiseHolder.Reject(NS_ERROR_DOM_NOT_NUMBER_ERR, __func__);
7088 mPromiseHolder.Reject((nsresult)aValue.toInt32(), __func__);
7092 DomPromiseListener() = default;
7093 ~DomPromiseListener() = default;
7094 MozPromiseHolder<nsHttpChannel::TabPromise> mPromiseHolder;
7097 NS_IMPL_ISUPPORTS0(DomPromiseListener)
7099 NS_IMETHODIMP nsHttpChannel::SwitchProcessTo(dom::Promise *aTabPromise,
7100 uint64_t aIdentifier) {
7101 MOZ_ASSERT(NS_IsMainThread());
7102 NS_ENSURE_ARG(aTabPromise);
7104 LOG(("nsHttpChannel::SwitchProcessTo [this=%p]", this));
7105 LogCallingScriptLocation(this);
7107 // We cannot do this after OnStartRequest of the listener has been called.
7108 NS_ENSURE_FALSE(mOnStartRequestCalled, NS_ERROR_NOT_AVAILABLE);
7110 mRedirectTabPromise = DomPromiseListener::Create(aTabPromise);
7111 mCrossProcessRedirectIdentifier = aIdentifier;
7115 nsresult nsHttpChannel::StartCrossProcessRedirect() {
7118 rv = CheckRedirectLimit(nsIChannelEventSink::REDIRECT_INTERNAL);
7119 NS_ENSURE_SUCCESS(rv, rv);
7121 RefPtr<HttpChannelParentListener> listener = do_QueryObject(mCallbacks);
7122 MOZ_ASSERT(listener);
7124 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
7125 CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
7127 listener->TriggerCrossProcessRedirect(this, redirectLoadInfo,
7128 mCrossProcessRedirectIdentifier);
7130 // This will suspend the channel
7131 rv = WaitForRedirectCallback();
7137 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
7140 MOZ_ASSERT(mRequestObserversCalled);
7142 AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
7144 if (!(mCanceled || NS_FAILED(mStatus)) &&
7145 !WRONG_RACING_RESPONSE_SOURCE(request)) {
7146 // capture the request's status, so our consumers will know ASAP of any
7147 // connection failures, etc - bug 93581
7149 request->GetStatus(&status);
7153 LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
7155 this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
7157 Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_ONSTART_SUCCESS,
7158 NS_SUCCEEDED(mStatus));
7160 if (mRaceCacheWithNetwork) {
7162 (" racingNetAndCache - mFirstResponseSource:%d fromCache:%d "
7164 static_cast<int32_t>(mFirstResponseSource), request == mCachePump,
7165 request == mTransactionPump));
7166 if (mFirstResponseSource == RESPONSE_PENDING) {
7167 // When the cache wins mFirstResponseSource is set to RESPONSE_FROM_CACHE
7168 // earlier in ReadFromCache, so this must be a response from the network.
7169 MOZ_ASSERT(request == mTransactionPump);
7170 LOG((" First response from network\n"));
7172 // Race condition with OnCacheEntryCheck, which is not limited
7174 mozilla::MutexAutoLock lock(mRCWNLock);
7175 mFirstResponseSource = RESPONSE_FROM_NETWORK;
7176 mOnStartRequestTimestamp = TimeStamp::Now();
7178 // Conditional or byte range header could be added in
7179 // OnCacheEntryCheck. We need to remove them because the
7180 // request might be sent again due to auth retry and we must
7181 // not send these headers without having the entry.
7183 LOG((" Removing conditional request headers"));
7184 UntieValidationRequest();
7187 if (mCachedContentIsPartial) {
7188 LOG((" Removing byte range request headers"));
7189 UntieByteRangeRequest();
7190 mCachedContentIsPartial = false;
7193 mAvailableCachedAltDataType.Truncate();
7194 } else if (WRONG_RACING_RESPONSE_SOURCE(request)) {
7195 LOG((" Early return when racing. This response not needed."));
7200 // Make sure things are what we expect them to be...
7201 MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
7202 "Unexpected request");
7204 MOZ_ASSERT(mRaceCacheWithNetwork || !(mTransactionPump && mCachePump) ||
7205 mCachedContentIsPartial,
7206 "If we have both pumps, the cache content must be partial");
7208 mAfterOnStartRequestBegun = true;
7209 if (mOnStartRequestTimestamp.IsNull()) {
7210 mOnStartRequestTimestamp = TimeStamp::Now();
7213 Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
7216 if (!mSecurityInfo && !mCachePump && mTransaction) {
7217 // grab the security info from the connection object; the transaction
7218 // is guaranteed to own a reference to the connection.
7219 mSecurityInfo = mTransaction->SecurityInfo();
7222 // don't enter this block if we're reading from the cache...
7223 if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
7224 // mTransactionPump doesn't hit OnInputStreamReady and call this until
7225 // all of the response headers have been acquired, so we can take ownership
7226 // of them from the transaction.
7227 mResponseHead = mTransaction->TakeResponseHead();
7228 // the response head may be null if the transaction was cancelled. in
7229 // which case we just need to call OnStartRequest/OnStopRequest.
7230 if (mResponseHead) return ProcessResponse();
7232 NS_WARNING("No response head in OnStartRequest");
7235 // cache file could be deleted on our behalf, it could contain errors or
7236 // it failed to allocate memory, reload from network here.
7237 if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
7238 LOG((" cache file error, reloading from server"));
7239 mCacheEntry->AsyncDoom(nullptr);
7241 StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
7242 if (NS_SUCCEEDED(rv)) return NS_OK;
7245 // avoid crashing if mListener happens to be null...
7247 MOZ_ASSERT_UNREACHABLE("mListener is null");
7251 // before we check for redirects, check if the load should be shifted into a
7254 if (mRedirectTabPromise && !mCanceled) {
7255 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
7256 rv = StartCrossProcessRedirect();
7257 if (NS_SUCCEEDED(rv)) {
7260 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
7263 // No process change is needed, so continue on to ContinueOnStartRequest1.
7264 return ContinueOnStartRequest1(rv);
7267 nsresult nsHttpChannel::ContinueOnStartRequest1(nsresult result) {
7270 // if process selection failed, cancel this load.
7271 if (NS_FAILED(result) && !mCanceled) {
7273 return CallOnStartRequest();
7276 // before we start any content load, check for redirectTo being called
7277 // this code is executed mainly before we start load from the cache
7278 if (mAPIRedirectToURI && !mCanceled) {
7279 nsAutoCString redirectToSpec;
7280 mAPIRedirectToURI->GetAsciiSpec(redirectToSpec);
7281 LOG((" redirectTo called with uri=%s", redirectToSpec.BeginReading()));
7283 MOZ_ASSERT(!mOnStartRequestCalled);
7285 nsCOMPtr<nsIURI> redirectTo;
7286 mAPIRedirectToURI.swap(redirectTo);
7288 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
7289 rv = StartRedirectChannelToURI(redirectTo,
7290 nsIChannelEventSink::REDIRECT_TEMPORARY);
7291 if (NS_SUCCEEDED(rv)) {
7294 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
7297 // Hack: ContinueOnStartRequest2 uses NS_OK to detect successful redirects,
7298 // so we distinguish this codepath (a non-redirect that's processing
7299 // normally) by passing in a bogus error code.
7300 return ContinueOnStartRequest2(NS_BINDING_FAILED);
7303 nsresult nsHttpChannel::ContinueOnStartRequest2(nsresult result) {
7304 if (NS_SUCCEEDED(result)) {
7305 // Redirect has passed through, we don't want to go on with this
7306 // channel. It will now be canceled by the redirect handling code
7307 // that called this function.
7311 // on proxy errors, try to failover
7312 if (mConnectionInfo->ProxyInfo() &&
7313 (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
7314 mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
7315 mStatus == NS_ERROR_NET_TIMEOUT)) {
7316 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
7317 if (NS_SUCCEEDED(ProxyFailover())) return NS_OK;
7318 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
7321 // Hack: ContinueOnStartRequest3 uses NS_OK to detect successful redirects,
7322 // so we distinguish this codepath (a non-redirect that's processing
7323 // normally) by passing in a bogus error code.
7324 return ContinueOnStartRequest3(NS_BINDING_FAILED);
7327 nsresult nsHttpChannel::ContinueOnStartRequest3(nsresult result) {
7328 if (NS_SUCCEEDED(result)) {
7329 // Redirect has passed through, we don't want to go on with this
7330 // channel. It will now be canceled by the redirect handling code
7331 // that called this function.
7335 // on other request errors, try to fall back
7336 if (NS_FAILED(mStatus)) {
7337 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest4);
7338 bool waitingForRedirectCallback;
7339 Unused << ProcessFallback(&waitingForRedirectCallback);
7340 if (waitingForRedirectCallback) return NS_OK;
7341 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest4);
7344 return ContinueOnStartRequest4(NS_OK);
7347 nsresult nsHttpChannel::ContinueOnStartRequest4(nsresult result) {
7348 LOG(("nsHttpChannel::ContinueOnStartRequest4 [this=%p]", this));
7350 if (mFallingBack) return NS_OK;
7352 return CallOnStartRequest();
7356 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
7358 AUTO_PROFILER_LABEL("nsHttpChannel::OnStopRequest", NETWORK);
7360 LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%" PRIx32 "]\n",
7361 this, request, static_cast<uint32_t>(status)));
7363 LOG(("OnStopRequest %p requestFromCache: %d mFirstResponseSource: %d\n", this,
7364 request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
7366 MOZ_ASSERT(NS_IsMainThread(),
7367 "OnStopRequest should only be called from the main thread");
7369 if (WRONG_RACING_RESPONSE_SOURCE(request)) {
7373 if (NS_FAILED(status)) {
7374 ProcessSecurityReport(status);
7377 // If this load failed because of a security error, it may be because we
7378 // are in a captive portal - trigger an async check to make sure.
7379 int32_t nsprError = -1 * NS_ERROR_GET_CODE(status);
7380 if (mozilla::psm::IsNSSErrorCode(nsprError)) {
7381 gIOService->RecheckCaptivePortal();
7384 if (mTimingEnabled && request == mCachePump) {
7385 mCacheReadEnd = TimeStamp::Now();
7387 ReportNetVSCacheTelemetry();
7390 // allow content to be cached if it was loaded successfully (bug #482935)
7391 bool contentComplete = NS_SUCCEEDED(status);
7393 // honor the cancelation status even if the underlying transaction completed.
7394 if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
7396 if (mCachedContentIsPartial) {
7397 if (NS_SUCCEEDED(status)) {
7398 // mTransactionPump should be suspended
7399 MOZ_ASSERT(request != mTransactionPump,
7400 "byte-range transaction finished prematurely");
7402 if (request == mCachePump) {
7404 status = OnDoneReadingPartialCacheEntry(&streamDone);
7405 if (NS_SUCCEEDED(status) && !streamDone) return status;
7406 // otherwise, fall through and fire OnStopRequest...
7407 } else if (request == mTransactionPump) {
7408 MOZ_ASSERT(mConcurrentCacheAccess);
7410 MOZ_ASSERT_UNREACHABLE("unexpected request");
7412 // Do not to leave the transaction in a suspended state in error cases.
7413 if (NS_FAILED(status) && mTransaction) {
7414 nsresult rv = gHttpHandler->CancelTransaction(mTransaction, status);
7415 if (NS_FAILED(rv)) {
7416 LOG((" CancelTransaction failed (%08x)", static_cast<uint32_t>(rv)));
7421 nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
7423 conv->GetDecodedDataLength(&mDecodedBodySize);
7426 bool isFromNet = request == mTransactionPump;
7429 // determine if we should call DoAuthRetry
7430 bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
7431 mStronglyFramed = mTransaction->ResponseIsComplete();
7432 LOG(("nsHttpChannel %p has a strongly framed transaction: %d", this,
7435 // Save the reference of |mTransaction| to |transactionWithStickyConn|
7436 // when it has a sticky connection.
7437 // In the case we need to retry an authentication request, we need to
7438 // reuse the connection of |transactionWithStickyConn|.
7439 RefPtr<nsHttpTransaction> transactionWithStickyConn;
7440 if (mCaps & NS_HTTP_STICKY_CONNECTION ||
7441 mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION) {
7442 transactionWithStickyConn = mTransaction;
7443 LOG((" transaction %p has sticky connection",
7444 transactionWithStickyConn.get()));
7447 // this code relies on the code in nsHttpTransaction::Close, which
7448 // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
7449 // keep the connection around after the transaction is finished.
7451 LOG((" mAuthRetryPending=%d, status=%" PRIx32 ", sticky conn cap=%d",
7452 mAuthRetryPending, static_cast<uint32_t>(status),
7453 mCaps & NS_HTTP_STICKY_CONNECTION));
7454 // We must check caps for stickinness also on the transaction because it
7455 // might have been updated by the transaction itself during inspection of
7456 // the reposnse headers yet on the socket thread (found connection based
7459 if ((mAuthRetryPending || NS_FAILED(status)) && transactionWithStickyConn) {
7460 if (NS_FAILED(status)) {
7461 // Close (don't reuse) the sticky connection if it's in the middle
7462 // of an NTLM negotiation and this channel has been cancelled.
7463 // There are proxy servers known to get confused when we send
7464 // a new request over such a half-stated connection.
7465 if (!mAuthConnectionRestartable) {
7466 LOG((" not reusing a half-authenticated sticky connection"));
7467 transactionWithStickyConn->DontReuseConnection();
7472 if (mCaps & NS_HTTP_STICKY_CONNECTION) {
7473 mTransaction->SetH2WSConnRefTaken();
7476 mTransferSize = mTransaction->GetTransferSize();
7478 // If we are using the transaction to serve content, we also save the
7479 // time since async open in the cache entry so we can compare telemetry
7480 // between cache and net response.
7481 // Do not store the time of conditional requests because even if we
7482 // fetch the data from the server, the time includes loading of the old
7483 // cache entry which would skew the network load time.
7484 if (request == mTransactionPump && mCacheEntry && !mDidReval &&
7485 !mCustomConditionalRequest && !mAsyncOpenTime.IsNull() &&
7486 !mOnStartRequestTimestamp.IsNull()) {
7487 uint64_t onStartTime =
7488 (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
7489 uint64_t onStopTime =
7490 (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds();
7491 Unused << mCacheEntry->SetNetworkTimes(onStartTime, onStopTime);
7494 mResponseTrailers = mTransaction->TakeResponseTrailers();
7496 // at this point, we're done with the transaction
7497 mTransactionTimings = mTransaction->Timings();
7498 mTransaction = nullptr;
7499 mTransactionPump = nullptr;
7501 // We no longer need the dns prefetch object
7502 if (mDNSPrefetch && mDNSPrefetch->TimingsValid() &&
7503 !mTransactionTimings.requestStart.IsNull() &&
7504 !mTransactionTimings.connectStart.IsNull() &&
7505 mDNSPrefetch->EndTimestamp() <= mTransactionTimings.connectStart) {
7506 // We only need the domainLookup timestamps when not using a
7507 // persistent connection, meaning if the endTimestamp < connectStart
7508 mTransactionTimings.domainLookupStart = mDNSPrefetch->StartTimestamp();
7509 mTransactionTimings.domainLookupEnd = mDNSPrefetch->EndTimestamp();
7511 mDNSPrefetch = nullptr;
7513 // handle auth retry...
7515 mAuthRetryPending = false;
7516 auto continueOSR = [authRetry, isFromNet, contentComplete,
7517 transactionWithStickyConn](auto *self,
7519 return self->ContinueOnStopRequestAfterAuthRetry(
7520 aStatus, authRetry, isFromNet, contentComplete,
7521 transactionWithStickyConn);
7523 status = DoAuthRetry(transactionWithStickyConn, continueOSR);
7524 if (NS_SUCCEEDED(status)) {
7528 return ContinueOnStopRequestAfterAuthRetry(status, authRetry, isFromNet,
7530 transactionWithStickyConn);
7533 return ContinueOnStopRequest(status, isFromNet, contentComplete);
7536 nsresult nsHttpChannel::ContinueOnStopRequestAfterAuthRetry(
7537 nsresult aStatus, bool aAuthRetry, bool aIsFromNet, bool aContentComplete,
7538 nsHttpTransaction *aTransWithStickyConn) {
7540 ("nsHttpChannel::ContinueOnStopRequestAfterAuthRetry "
7541 "[this=%p, aStatus=%" PRIx32
7542 " aAuthRetry=%d, aIsFromNet=%d, aTransWithStickyConn=%p]\n",
7543 this, static_cast<uint32_t>(aStatus), aAuthRetry, aIsFromNet,
7544 aTransWithStickyConn));
7546 if (aAuthRetry && NS_SUCCEEDED(aStatus)) {
7550 // If DoAuthRetry failed, or if we have been cancelled since showing
7551 // the auth. dialog, then we need to send OnStartRequest now
7552 if (aAuthRetry || (mAuthRetryPending && NS_FAILED(aStatus))) {
7553 MOZ_ASSERT(NS_FAILED(aStatus), "should have a failure code here");
7554 // NOTE: since we have a failure status, we can ignore the return
7555 // value from onStartRequest.
7556 LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
7559 MOZ_ASSERT(!mOnStartRequestCalled,
7560 "We should not call OnStartRequest twice.");
7561 mListener->OnStartRequest(this, nullptr);
7562 mOnStartRequestCalled = true;
7564 NS_WARNING("OnStartRequest skipped because of null listener");
7568 // if this transaction has been replaced, then bail.
7569 if (mTransactionReplaced) {
7570 LOG(("Transaction replaced\n"));
7571 // This was just the network check for a 304 response.
7572 mFirstResponseSource = RESPONSE_PENDING;
7576 bool upgradeWebsocket = mUpgradeProtocolCallback && aTransWithStickyConn &&
7578 ((mResponseHead->Status() == 101 &&
7579 mResponseHead->Version() == HttpVersion::v1_1) ||
7580 (mResponseHead->Status() == 200 &&
7581 mResponseHead->Version() == HttpVersion::v2_0));
7583 bool upgradeConnect = mUpgradeProtocolCallback && aTransWithStickyConn &&
7584 (mCaps & NS_HTTP_CONNECT_ONLY) && mResponseHead &&
7585 mResponseHead->Status() == 200;
7587 if (upgradeWebsocket || upgradeConnect) {
7588 nsresult rv = gHttpHandler->ConnMgr()->CompleteUpgrade(
7589 aTransWithStickyConn, mUpgradeProtocolCallback);
7590 if (NS_FAILED(rv)) {
7591 LOG((" CompleteUpgrade failed with %08x", static_cast<uint32_t>(rv)));
7595 return ContinueOnStopRequest(aStatus, aIsFromNet, aContentComplete);
7598 nsresult nsHttpChannel::ContinueOnStopRequest(nsresult aStatus, bool aIsFromNet,
7599 bool aContentComplete) {
7601 ("nsHttpChannel::ContinueOnStopRequest "
7602 "[this=%p aStatus=%" PRIx32 ", aIsFromNet=%d]\n",
7603 this, static_cast<uint32_t>(aStatus), aIsFromNet));
7605 // HTTP_CHANNEL_DISPOSITION TELEMETRY
7606 enum ChannelDisposition {
7610 kHttpNetEarlyFail = 3,
7611 kHttpNetLateFail = 4,
7615 kHttpsNetEarlyFail = 11,
7616 kHttpsNetLateFail = 12
7617 } chanDisposition = kHttpCanceled;
7618 // HTTP_CHANNEL_DISPOSITION_UPGRADE TELEMETRY
7619 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE upgradeChanDisposition =
7620 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
7622 // HTTP 0.9 is more likely to be an error than really 0.9, so count it that
7625 chanDisposition = kHttpCanceled;
7626 upgradeChanDisposition =
7627 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::cancel;
7628 } else if (!mUsedNetwork || (mRaceCacheWithNetwork &&
7629 mFirstResponseSource == RESPONSE_FROM_CACHE)) {
7630 chanDisposition = kHttpDisk;
7631 upgradeChanDisposition =
7632 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::disk;
7633 } else if (NS_SUCCEEDED(aStatus) && mResponseHead &&
7634 mResponseHead->Version() != HttpVersion::v0_9) {
7635 chanDisposition = kHttpNetOK;
7636 upgradeChanDisposition =
7637 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netOk;
7638 } else if (!mTransferSize) {
7639 chanDisposition = kHttpNetEarlyFail;
7640 upgradeChanDisposition =
7641 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netEarlyFail;
7643 chanDisposition = kHttpNetLateFail;
7644 upgradeChanDisposition =
7645 Telemetry::LABELS_HTTP_CHANNEL_DISPOSITION_UPGRADE::netLateFail;
7647 // Browser upgrading only happens on HTTPS pages for mixed passive content
7648 // when upgrading is enabled.
7649 nsCString upgradeKey;
7651 // Browser upgrading is disabled and the content is already HTTPS
7652 upgradeKey = NS_LITERAL_CSTRING("disabledNoReason");
7653 // Checks "security.mixed_content.upgrade_display_content" is true
7654 if (nsMixedContentBlocker::ShouldUpgradeMixedDisplayContent()) {
7655 if (mLoadInfo && mLoadInfo->GetBrowserUpgradeInsecureRequests()) {
7656 // HTTP content the browser has upgraded to HTTPS
7657 upgradeKey = NS_LITERAL_CSTRING("enabledUpgrade");
7659 // Content wasn't upgraded but is already HTTPS
7660 upgradeKey = NS_LITERAL_CSTRING("enabledNoReason");
7663 // shift http to https disposition enums
7665 static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
7666 } else if (mLoadInfo && mLoadInfo->GetBrowserWouldUpgradeInsecureRequests()) {
7667 // HTTP content the browser would upgrade to HTTPS if upgrading was enabled
7668 upgradeKey = NS_LITERAL_CSTRING("disabledUpgrade");
7670 // HTTP content that wouldn't upgrade
7671 upgradeKey = nsMixedContentBlocker::ShouldUpgradeMixedDisplayContent()
7672 ? NS_LITERAL_CSTRING("enabledWont")
7673 : NS_LITERAL_CSTRING("disabledWont");
7675 Telemetry::AccumulateCategoricalKeyed(upgradeKey, upgradeChanDisposition);
7676 LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n",
7678 Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
7680 // if needed, check cache entry has all data we expect
7681 if (mCacheEntry && mCachePump && mConcurrentCacheAccess && aContentComplete) {
7682 int64_t size, contentLength;
7683 nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
7684 if (NS_SUCCEEDED(rv)) {
7685 if (size == int64_t(-1)) {
7686 // mayhemer TODO - we have to restart read from cache here at the size
7690 (" cache entry write is still in progress, but we just "
7691 "finished reading the cache entry"));
7692 } else if (contentLength != int64_t(-1) && contentLength != size) {
7693 LOG((" concurrent cache entry write has been interrupted"));
7694 mCachedResponseHead = std::move(mResponseHead);
7695 // Ignore zero partial length because we also want to resume when
7696 // no data at all has been read from the cache.
7697 rv = MaybeSetupByteRangeRequest(size, contentLength, true);
7698 if (NS_SUCCEEDED(rv) && mIsPartialRequest) {
7699 // Prevent read from cache again
7700 mCachedContentIsValid = 0;
7701 mCachedContentIsPartial = 1;
7703 // Perform the range request
7704 rv = ContinueConnect();
7705 if (NS_SUCCEEDED(rv)) {
7706 LOG((" performing range request"));
7707 mCachePump = nullptr;
7710 LOG((" but range request perform failed 0x%08" PRIx32,
7711 static_cast<uint32_t>(rv)));
7712 aStatus = NS_ERROR_NET_INTERRUPT;
7714 LOG((" but range request setup failed rv=0x%08" PRIx32
7716 static_cast<uint32_t>(rv)));
7725 // perform any final cache operations before we close the cache entry.
7726 if (mCacheEntry && mRequestTimeInitialized) {
7728 // New implementation just returns value of the !mCacheEntryIsReadOnly flag
7729 // passed in. Old implementation checks on nsICache::ACCESS_WRITE flag.
7730 mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
7732 nsresult rv = FinalizeCacheEntry();
7733 if (NS_FAILED(rv)) {
7734 LOG(("FinalizeCacheEntry failed (%08x)", static_cast<uint32_t>(rv)));
7739 ReportRcwnStats(aIsFromNet);
7741 // Register entry to the PerformanceStorage resource timing
7742 MaybeReportTimingData();
7744 #ifdef MOZ_GECKO_PROFILER
7745 if (profiler_is_active() && !mRedirectURI) {
7746 // Don't include this if we already redirected
7747 // These do allocations/frees/etc; avoid if not active
7748 nsCOMPtr<nsIURI> uri;
7749 GetURI(getter_AddRefs(uri));
7750 int32_t priority = PRIORITY_NORMAL;
7751 GetPriority(&priority);
7752 profiler_add_network_marker(
7753 uri, priority, mChannelId, NetworkLoadType::LOAD_STOP,
7754 mLastStatusReported, TimeStamp::Now(), mLogicalOffset,
7755 mCacheDisposition, &mTransactionTimings, nullptr);
7760 LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
7761 MOZ_ASSERT(mOnStartRequestCalled,
7762 "OnStartRequest should be called before OnStopRequest");
7763 MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");
7764 mListener->OnStopRequest(this, nullptr, aStatus);
7765 mOnStopRequestCalled = true;
7768 // notify "http-on-stop-connect" observers
7769 gHttpHandler->OnStopRequest(this);
7771 RemoveAsNonTailRequest();
7773 // If a preferred alt-data type was set, this signals the consumer is
7774 // interested in reading and/or writing the alt-data representation.
7775 // We need to hold a reference to the cache entry in case the listener calls
7776 // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
7777 if (!mPreferredCachedAltDataTypes.IsEmpty()) {
7778 mAltDataCacheEntry = mCacheEntry;
7781 CloseCacheEntry(!aContentComplete);
7783 if (mOfflineCacheEntry) CloseOfflineCacheEntry();
7786 mLoadGroup->RemoveRequest(this, nullptr, aStatus);
7789 // We don't need this info anymore
7790 CleanRedirectCacheChainIfNecessary();
7797 //-----------------------------------------------------------------------------
7798 // nsHttpChannel::nsIStreamListener
7799 //-----------------------------------------------------------------------------
7801 class OnTransportStatusAsyncEvent : public Runnable {
7803 OnTransportStatusAsyncEvent(nsITransportEventSink *aEventSink,
7804 nsresult aTransportStatus, int64_t aProgress,
7805 int64_t aProgressMax)
7806 : Runnable("net::OnTransportStatusAsyncEvent"),
7807 mEventSink(aEventSink),
7808 mTransportStatus(aTransportStatus),
7809 mProgress(aProgress),
7810 mProgressMax(aProgressMax) {
7811 MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
7814 NS_IMETHOD Run() override {
7815 MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
7817 mEventSink->OnTransportStatus(nullptr, mTransportStatus, mProgress,
7824 nsCOMPtr<nsITransportEventSink> mEventSink;
7825 nsresult mTransportStatus;
7827 int64_t mProgressMax;
7831 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
7832 nsIInputStream *input, uint64_t offset,
7835 AUTO_PROFILER_LABEL("nsHttpChannel::OnDataAvailable", NETWORK);
7837 LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
7838 " count=%" PRIu32 "]\n",
7839 this, request, offset, count));
7841 LOG((" requestFromCache: %d mFirstResponseSource: %d\n",
7842 request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
7844 // don't send out OnDataAvailable notifications if we've been canceled.
7845 if (mCanceled) return mStatus;
7847 if (mAuthRetryPending || WRONG_RACING_RESPONSE_SOURCE(request) ||
7848 (request == mTransactionPump && mTransactionReplaced)) {
7850 return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
7853 MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
7855 MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
7856 "transaction pump not suspended");
7858 mIsReadingFromCache = (request == mCachePump);
7862 // synthesize transport progress event. we do this here since we want
7863 // to delay OnProgress events until we start streaming data. this is
7864 // crucially important since it impacts the lock icon (see bug 240053).
7866 nsresult transportStatus;
7867 if (request == mCachePump)
7868 transportStatus = NS_NET_STATUS_READING;
7870 transportStatus = NS_NET_STATUS_RECEIVING_FROM;
7872 // mResponseHead may reference new or cached headers, but either way it
7873 // holds our best estimate of the total content length. Even in the case
7874 // of a byte range request, the content length stored in the cached
7875 // response headers is what we want to use here.
7877 int64_t progressMax = -1;
7878 rv = GetContentLength(&progressMax);
7879 if (NS_FAILED(rv)) {
7880 NS_WARNING("GetContentLength failed");
7882 int64_t progress = mLogicalOffset + count;
7884 if ((progress > progressMax) && (progressMax != -1)) {
7886 "unexpected progress values - "
7887 "is server exceeding content length?");
7890 // make sure params are in range for js
7891 if (!InScriptableRange(progressMax)) {
7895 if (!InScriptableRange(progress)) {
7899 if (NS_IsMainThread()) {
7900 OnTransportStatus(nullptr, transportStatus, progress, progressMax);
7902 rv = NS_DispatchToMainThread(new OnTransportStatusAsyncEvent(
7903 this, transportStatus, progress, progressMax));
7904 NS_ENSURE_SUCCESS(rv, rv);
7908 // we have to manually keep the logical offset of the stream up-to-date.
7909 // we cannot depend solely on the offset provided, since we may have
7910 // already streamed some data from another source (see, for example,
7911 // OnDoneReadingPartialCacheEntry).
7913 int64_t offsetBefore = 0;
7914 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
7915 if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
7920 mListener->OnDataAvailable(this, nullptr, input, mLogicalOffset, count);
7921 if (NS_SUCCEEDED(rv)) {
7922 // by contract mListener must read all of "count" bytes, but
7923 // nsInputStreamPump is tolerant to seekable streams that violate that
7924 // and it will redeliver incompletely read data. So we need to do
7925 // the same thing when updating the progress counter to stay in sync.
7926 int64_t offsetAfter, delta;
7927 if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
7928 delta = offsetAfter - offsetBefore;
7929 if (delta != count) {
7932 NS_WARNING("Listener OnDataAvailable contract violation");
7933 nsCOMPtr<nsIConsoleService> consoleService =
7934 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
7935 nsAutoString message(NS_LITERAL_STRING(
7936 "http channel Listener OnDataAvailable contract violation"));
7937 if (consoleService) {
7938 consoleService->LogStringMessage(message.get());
7942 mLogicalOffset += count;
7948 return NS_ERROR_ABORT;
7951 //-----------------------------------------------------------------------------
7952 // nsHttpChannel::nsIThreadRetargetableRequest
7953 //-----------------------------------------------------------------------------
7956 nsHttpChannel::RetargetDeliveryTo(nsIEventTarget *aNewTarget) {
7957 MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
7959 NS_ENSURE_ARG(aNewTarget);
7960 if (aNewTarget->IsOnCurrentThread()) {
7961 NS_WARNING("Retargeting delivery to same thread");
7964 if (!mTransactionPump && !mCachePump) {
7965 LOG(("nsHttpChannel::RetargetDeliveryTo %p %p no pump available\n", this,
7967 return NS_ERROR_NOT_AVAILABLE;
7970 nsresult rv = NS_OK;
7971 // If both cache pump and transaction pump exist, we're probably dealing
7972 // with partially cached content. So, we must be able to retarget both.
7973 nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
7974 nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
7976 retargetableCachePump = do_QueryObject(mCachePump);
7977 // nsInputStreamPump should implement this interface.
7978 MOZ_ASSERT(retargetableCachePump);
7979 rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
7981 if (NS_SUCCEEDED(rv) && mTransactionPump) {
7982 retargetableTransactionPump = do_QueryObject(mTransactionPump);
7983 // nsInputStreamPump should implement this interface.
7984 MOZ_ASSERT(retargetableTransactionPump);
7985 rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
7987 // If retarget fails for transaction pump, we must restore mCachePump.
7988 if (NS_FAILED(rv) && retargetableCachePump) {
7989 nsCOMPtr<nsIEventTarget> main = GetMainThreadEventTarget();
7990 NS_ENSURE_TRUE(main, NS_ERROR_UNEXPECTED);
7991 rv = retargetableCachePump->RetargetDeliveryTo(main);
7998 nsHttpChannel::GetDeliveryTarget(nsIEventTarget **aEventTarget) {
8000 return mCachePump->GetDeliveryTarget(aEventTarget);
8002 if (mTransactionPump) {
8003 return mTransactionPump->GetDeliveryTarget(aEventTarget);
8005 return NS_ERROR_NOT_AVAILABLE;
8008 //-----------------------------------------------------------------------------
8009 // nsHttpChannel::nsThreadRetargetableStreamListener
8010 //-----------------------------------------------------------------------------
8013 nsHttpChannel::CheckListenerChain() {
8014 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
8015 nsresult rv = NS_OK;
8016 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
8017 do_QueryInterface(mListener, &rv);
8018 if (retargetableListener) {
8019 rv = retargetableListener->CheckListenerChain();
8024 //-----------------------------------------------------------------------------
8025 // nsHttpChannel::nsITransportEventSink
8026 //-----------------------------------------------------------------------------
8029 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
8030 int64_t progress, int64_t progressMax) {
8031 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
8032 // cache the progress sink so we don't have to query for it each time.
8033 if (!mProgressSink) GetCallback(mProgressSink);
8035 if (status == NS_NET_STATUS_CONNECTED_TO ||
8036 status == NS_NET_STATUS_WAITING_FOR) {
8038 mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr);
8040 nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(trans);
8041 if (socketTransport) {
8042 socketTransport->GetSelfAddr(&mSelfAddr);
8043 socketTransport->GetPeerAddr(&mPeerAddr);
8048 // block socket status event after Cancel or OnStopRequest has been called.
8049 if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending) {
8050 LOG(("sending progress%s notification [this=%p status=%" PRIx32
8051 " progress=%" PRId64 "/%" PRId64 "]\n",
8052 (mLoadFlags & LOAD_BACKGROUND) ? "" : " and status", this,
8053 static_cast<uint32_t>(status), progress, progressMax));
8055 if (!(mLoadFlags & LOAD_BACKGROUND)) {
8057 mURI->GetHost(host);
8058 mProgressSink->OnStatus(this, nullptr, status,
8059 NS_ConvertUTF8toUTF16(host).get());
8063 if ((progress > progressMax) && (progressMax != -1)) {
8064 NS_WARNING("unexpected progress values");
8067 // Try to get mProgressSink if it was nulled out during OnStatus.
8068 if (!mProgressSink) {
8069 GetCallback(mProgressSink);
8071 if (mProgressSink) {
8072 mProgressSink->OnProgress(this, nullptr, progress, progressMax);
8080 //-----------------------------------------------------------------------------
8081 // nsHttpChannel::nsICacheInfoChannel
8082 //-----------------------------------------------------------------------------
8085 nsHttpChannel::IsFromCache(bool *value) {
8086 if (!mIsPending) return NS_ERROR_NOT_AVAILABLE;
8088 if (!mRaceCacheWithNetwork) {
8089 // return false if reading a partial cache entry; the data isn't
8090 // entirely from the cache!
8091 *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
8092 mCachedContentIsValid && !mCachedContentIsPartial;
8096 // If we are racing network and cache (or skipping the cache)
8097 // we just return the first response source.
8098 *value = mFirstResponseSource == RESPONSE_FROM_CACHE;
8104 nsHttpChannel::GetCacheEntryId(uint64_t *aCacheEntryId) {
8105 bool fromCache = false;
8106 if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || !mCacheEntry ||
8107 NS_FAILED(mCacheEntry->GetCacheEntryId(aCacheEntryId))) {
8108 return NS_ERROR_NOT_AVAILABLE;
8115 nsHttpChannel::GetCacheTokenFetchCount(int32_t *_retval) {
8116 NS_ENSURE_ARG_POINTER(_retval);
8117 nsCOMPtr<nsICacheEntry> cacheEntry =
8118 mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
8120 return NS_ERROR_NOT_AVAILABLE;
8123 return cacheEntry->GetFetchCount(_retval);
8127 nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval) {
8128 NS_ENSURE_ARG_POINTER(_retval);
8129 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
8131 return mCacheEntry->GetExpirationTime(_retval);
8135 nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval) {
8138 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
8140 nsCString cachedCharset;
8141 rv = mCacheEntry->GetMetaDataElement("charset", getter_Copies(cachedCharset));
8142 if (NS_SUCCEEDED(rv)) _retval = cachedCharset;
8148 nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset) {
8149 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
8151 return mCacheEntry->SetMetaDataElement("charset",
8152 PromiseFlatCString(aCharset).get());
8156 nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
8157 LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]", this,
8158 aAllowStaleCacheContent));
8159 mAllowStaleCacheContent = aAllowStaleCacheContent;
8163 nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent) {
8164 NS_ENSURE_ARG(aAllowStaleCacheContent);
8165 *aAllowStaleCacheContent = mAllowStaleCacheContent;
8170 nsHttpChannel::PreferAlternativeDataType(const nsACString &aType,
8171 const nsACString &aContentType) {
8172 ENSURE_CALLED_BEFORE_ASYNC_OPEN();
8173 mPreferredCachedAltDataTypes.AppendElement(
8174 MakePair(nsCString(aType), nsCString(aContentType)));
8178 const nsTArray<mozilla::Tuple<nsCString, nsCString>>
8179 &nsHttpChannel::PreferredAlternativeDataTypes() {
8180 return mPreferredCachedAltDataTypes;
8184 nsHttpChannel::GetAlternativeDataType(nsACString &aType) {
8185 // must be called during or after OnStartRequest
8186 if (!mAfterOnStartRequestBegun) {
8187 return NS_ERROR_NOT_AVAILABLE;
8189 aType = mAvailableCachedAltDataType;
8194 nsHttpChannel::OpenAlternativeOutputStream(const nsACString &type,
8195 int64_t predictedSize,
8196 nsIOutputStream **_retval) {
8197 // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
8198 // if the consumer called PreferAlternativeDataType()
8199 nsCOMPtr<nsICacheEntry> cacheEntry =
8200 mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
8202 return NS_ERROR_NOT_AVAILABLE;
8205 cacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval);
8206 if (NS_SUCCEEDED(rv)) {
8207 // Clear this metadata flag in case it exists.
8208 // The caller of this method may set it again.
8209 cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
8215 nsHttpChannel::GetOriginalInputStream(nsIInputStreamReceiver *aReceiver) {
8216 if (aReceiver == nullptr) {
8217 return NS_ERROR_INVALID_ARG;
8219 nsCOMPtr<nsIInputStream> inputStream;
8221 nsCOMPtr<nsICacheEntry> cacheEntry =
8222 mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
8224 cacheEntry->OpenInputStream(0, getter_AddRefs(inputStream));
8226 aReceiver->OnInputStreamReady(inputStream);
8230 //-----------------------------------------------------------------------------
8231 // nsHttpChannel::nsICachingChannel
8232 //-----------------------------------------------------------------------------
8235 nsHttpChannel::GetCacheToken(nsISupports **token) {
8236 NS_ENSURE_ARG_POINTER(token);
8237 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
8238 return CallQueryInterface(mCacheEntry, token);
8242 nsHttpChannel::SetCacheToken(nsISupports *token) {
8243 return NS_ERROR_NOT_IMPLEMENTED;
8247 nsHttpChannel::GetOfflineCacheToken(nsISupports **token) {
8248 NS_ENSURE_ARG_POINTER(token);
8249 if (!mOfflineCacheEntry) return NS_ERROR_NOT_AVAILABLE;
8250 return CallQueryInterface(mOfflineCacheEntry, token);
8254 nsHttpChannel::SetOfflineCacheToken(nsISupports *token) {
8255 return NS_ERROR_NOT_IMPLEMENTED;
8259 nsHttpChannel::GetCacheKey(uint32_t *key) {
8260 NS_ENSURE_ARG_POINTER(key);
8262 LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
8269 nsHttpChannel::SetCacheKey(uint32_t key) {
8270 LOG(("nsHttpChannel::SetCacheKey [this=%p key=%u]\n", this, key));
8272 ENSURE_CALLED_BEFORE_CONNECT();
8279 nsHttpChannel::GetCacheOnlyMetadata(bool *aOnlyMetadata) {
8280 NS_ENSURE_ARG(aOnlyMetadata);
8281 *aOnlyMetadata = mCacheOnlyMetadata;
8286 nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata) {
8287 LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n", this,
8290 ENSURE_CALLED_BEFORE_ASYNC_OPEN();
8292 mCacheOnlyMetadata = aOnlyMetadata;
8293 if (aOnlyMetadata) {
8294 mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
8301 nsHttpChannel::GetPin(bool *aPin) {
8302 NS_ENSURE_ARG(aPin);
8303 *aPin = mPinCacheContent;
8308 nsHttpChannel::SetPin(bool aPin) {
8309 LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n", this, aPin));
8311 ENSURE_CALLED_BEFORE_CONNECT();
8313 mPinCacheContent = aPin;
8318 nsHttpChannel::ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture) {
8321 ("nsHttpChannel::ForceCacheEntryValidFor found no cache entry "
8322 "for this channel [this=%p].",
8325 mCacheEntry->ForceValidFor(aSecondsToTheFuture);
8328 mCacheEntry->GetKey(key);
8331 ("nsHttpChannel::ForceCacheEntryValidFor successfully forced valid "
8332 "entry with key %s for %d seconds. [this=%p]",
8333 key.get(), aSecondsToTheFuture, this));
8339 //-----------------------------------------------------------------------------
8340 // nsHttpChannel::nsIResumableChannel
8341 //-----------------------------------------------------------------------------
8344 nsHttpChannel::ResumeAt(uint64_t aStartPos, const nsACString &aEntityID) {
8345 LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%" PRIu64 " id='%s']\n", this,
8346 aStartPos, PromiseFlatCString(aEntityID).get()));
8347 mEntityID = aEntityID;
8348 mStartPos = aStartPos;
8353 nsresult nsHttpChannel::DoAuthRetry(
8354 nsHttpTransaction *aTransWithStickyConn,
8355 const std::function<nsresult(nsHttpChannel *, nsresult)>
8356 &aContinueOnStopRequestFunc) {
8357 LOG(("nsHttpChannel::DoAuthRetry [this=%p, aTransWithStickyConn=%p]\n", this,
8358 aTransWithStickyConn));
8360 MOZ_ASSERT(!mTransaction, "should not have a transaction");
8362 // Note that we don't have to toggle |mIsPending| anymore. See the reasons
8364 // 1. We can't suspend the channel during "http-on-modify-request"
8365 // when |mIsPending| is false.
8366 // 2. We don't check |mIsPending| in SetRequestHeader now.
8368 // Reset mRequestObserversCalled because we've probably called the request
8369 // observers once already.
8370 mRequestObserversCalled = false;
8372 // fetch cookies, and add them to the request header.
8373 // the server response could have included cookies that must be sent with
8374 // this authentication attempt (bug 84794).
8375 // TODO: save cookies from auth response and send them here (bug 572151).
8376 AddCookiesToRequest();
8378 // notify "http-on-modify-request" observers
8379 CallOnModifyRequestObservers();
8381 RefPtr<nsHttpTransaction> trans(aTransWithStickyConn);
8382 return CallOrWaitForResume(
8383 [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto *self) {
8384 return self->ContinueDoAuthRetry(trans, aContinueOnStopRequestFunc);
8388 nsresult nsHttpChannel::ContinueDoAuthRetry(
8389 nsHttpTransaction *aTransWithStickyConn,
8390 const std::function<nsresult(nsHttpChannel *, nsresult)>
8391 &aContinueOnStopRequestFunc) {
8392 LOG(("nsHttpChannel::ContinueDoAuthRetry [this=%p]\n", this));
8396 // get rid of the old response headers
8397 mResponseHead = nullptr;
8399 // rewind the upload stream
8400 if (mUploadStream) {
8401 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
8403 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
8407 // always set sticky connection flag
8408 mCaps |= NS_HTTP_STICKY_CONNECTION;
8409 // and when needed, allow restart regardless the sticky flag
8410 if (mAuthConnectionRestartable) {
8411 LOG((" connection made restartable"));
8412 mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
8413 mAuthConnectionRestartable = false;
8415 LOG((" connection made non-restartable"));
8416 mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
8419 // notify "http-on-before-connect" observers
8420 gHttpHandler->OnBeforeConnect(this);
8422 RefPtr<nsHttpTransaction> trans(aTransWithStickyConn);
8423 return CallOrWaitForResume(
8424 [trans{std::move(trans)}, aContinueOnStopRequestFunc](auto *self) {
8425 nsresult rv = self->DoConnect(trans);
8426 return aContinueOnStopRequestFunc(self, rv);
8430 //-----------------------------------------------------------------------------
8431 // nsHttpChannel::nsIApplicationCacheChannel
8432 //-----------------------------------------------------------------------------
8435 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out) {
8436 NS_IF_ADDREF(*out = mApplicationCache);
8441 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache) {
8442 ENSURE_CALLED_BEFORE_CONNECT();
8444 mApplicationCache = appCache;
8449 nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out) {
8450 NS_IF_ADDREF(*out = mApplicationCacheForWrite);
8455 nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache) {
8456 ENSURE_CALLED_BEFORE_CONNECT();
8458 mApplicationCacheForWrite = appCache;
8463 nsHttpChannel::GetLoadedFromApplicationCache(
8464 bool *aLoadedFromApplicationCache) {
8465 *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
8470 nsHttpChannel::GetInheritApplicationCache(bool *aInherit) {
8471 *aInherit = mInheritApplicationCache;
8476 nsHttpChannel::SetInheritApplicationCache(bool aInherit) {
8477 ENSURE_CALLED_BEFORE_CONNECT();
8479 mInheritApplicationCache = aInherit;
8484 nsHttpChannel::GetChooseApplicationCache(bool *aChoose) {
8485 *aChoose = mChooseApplicationCache;
8490 nsHttpChannel::SetChooseApplicationCache(bool aChoose) {
8491 ENSURE_CALLED_BEFORE_CONNECT();
8493 mChooseApplicationCache = aChoose;
8497 nsHttpChannel::OfflineCacheEntryAsForeignMarker *
8498 nsHttpChannel::GetOfflineCacheEntryAsForeignMarker() {
8499 if (!mApplicationCache) return nullptr;
8501 return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
8504 nsresult nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign() {
8507 nsCOMPtr<nsIURI> noRefURI;
8508 rv = NS_GetURIWithoutRef(mCacheURI, getter_AddRefs(noRefURI));
8509 NS_ENSURE_SUCCESS(rv, rv);
8512 rv = noRefURI->GetAsciiSpec(spec);
8513 NS_ENSURE_SUCCESS(rv, rv);
8515 return mApplicationCache->MarkEntry(spec, nsIApplicationCache::ITEM_FOREIGN);
8519 nsHttpChannel::MarkOfflineCacheEntryAsForeign() {
8522 nsAutoPtr<OfflineCacheEntryAsForeignMarker> marker(
8523 GetOfflineCacheEntryAsForeignMarker());
8525 if (!marker) return NS_ERROR_NOT_AVAILABLE;
8527 rv = marker->MarkAsForeign();
8528 NS_ENSURE_SUCCESS(rv, rv);
8533 //-----------------------------------------------------------------------------
8534 // nsHttpChannel::nsIAsyncVerifyRedirectCallback
8535 //-----------------------------------------------------------------------------
8537 nsresult nsHttpChannel::WaitForRedirectCallback() {
8539 LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
8541 if (mTransactionPump) {
8542 rv = mTransactionPump->Suspend();
8543 NS_ENSURE_SUCCESS(rv, rv);
8546 rv = mCachePump->Suspend();
8547 if (NS_FAILED(rv) && mTransactionPump) {
8551 mTransactionPump->Resume();
8552 MOZ_ASSERT(NS_SUCCEEDED(resume), "Failed to resume transaction pump");
8554 NS_ENSURE_SUCCESS(rv, rv);
8557 mWaitingForRedirectCallback = true;
8562 nsHttpChannel::OnRedirectVerifyCallback(nsresult result) {
8564 ("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
8565 "result=%" PRIx32 " stack=%zu mWaitingForRedirectCallback=%u\n",
8566 this, static_cast<uint32_t>(result), mRedirectFuncStack.Length(),
8567 mWaitingForRedirectCallback));
8568 MOZ_ASSERT(mWaitingForRedirectCallback,
8569 "Someone forgot to call WaitForRedirectCallback() ?!");
8570 mWaitingForRedirectCallback = false;
8572 if (mCanceled && NS_SUCCEEDED(result)) result = NS_BINDING_ABORTED;
8574 for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
8576 // Pop the last function pushed to the stack
8577 nsContinueRedirectionFunc func = mRedirectFuncStack.PopLastElement();
8579 // Call it with the result we got from the callback or the deeper
8581 result = (this->*func)(result);
8583 // If a new function has been pushed to the stack and placed us in the
8584 // waiting state, we need to break the chain and wait for the callback
8586 if (mWaitingForRedirectCallback) break;
8589 if (NS_FAILED(result) && !mCanceled) {
8590 // First, cancel this channel if we are in failure state to set mStatus
8591 // and let it be propagated to pumps.
8595 if (!mWaitingForRedirectCallback) {
8596 // We are not waiting for the callback. At this moment we must release
8597 // reference to the redirect target channel, otherwise we may leak.
8598 mRedirectChannel = nullptr;
8601 // We always resume the pumps here. If all functions on stack have been
8602 // called we need OnStopRequest to be triggered, and if we broke out of the
8603 // loop above (and are thus waiting for a new callback) the suspension
8604 // count must be balanced in the pumps.
8605 if (mTransactionPump) mTransactionPump->Resume();
8606 if (mCachePump) mCachePump->Resume();
8611 void nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func) {
8612 mRedirectFuncStack.AppendElement(func);
8615 void nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func) {
8616 MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
8617 "Trying to pop wrong method from redirect async stack!");
8619 mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
8622 //-----------------------------------------------------------------------------
8623 // nsIDNSListener functions
8624 //-----------------------------------------------------------------------------
8627 nsHttpChannel::OnLookupComplete(nsICancelable *request, nsIDNSRecord *rec,
8629 MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
8632 ("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
8633 "%s status[0x%" PRIx32 "]\n",
8634 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
8635 NS_SUCCEEDED(status) ? "success" : "failure",
8636 static_cast<uint32_t>(status)));
8638 // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
8639 // validly null if OnStopRequest has already been called.
8640 // We only need the domainLookup timestamps when not loading from cache
8641 if (mDNSPrefetch && mDNSPrefetch->TimingsValid() && mTransaction) {
8642 TimeStamp connectStart = mTransaction->GetConnectStart();
8643 TimeStamp requestStart = mTransaction->GetRequestStart();
8644 // We only set the domainLookup timestamps if we're not using a
8645 // persistent connection.
8646 if (requestStart.IsNull() && connectStart.IsNull()) {
8647 mTransaction->SetDomainLookupStart(mDNSPrefetch->StartTimestamp());
8648 mTransaction->SetDomainLookupEnd(mDNSPrefetch->EndTimestamp());
8651 mDNSPrefetch = nullptr;
8653 // Unset DNS cache refresh if it was requested,
8654 if (mCaps & NS_HTTP_REFRESH_DNS) {
8655 mCaps &= ~NS_HTTP_REFRESH_DNS;
8657 mTransaction->SetDNSWasRefreshed();
8665 nsHttpChannel::OnLookupByTypeComplete(nsICancelable *aRequest,
8666 nsIDNSByTypeRecord *aRes,
8671 //-----------------------------------------------------------------------------
8672 // nsHttpChannel internal functions
8673 //-----------------------------------------------------------------------------
8675 // Creates an URI to the given location using current URI for base and charset
8676 nsresult nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI) {
8677 nsCOMPtr<nsIIOService> ioService;
8678 nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
8679 if (NS_FAILED(rv)) return rv;
8681 return ioService->NewURI(nsDependentCString(loc), nullptr, mURI, newURI);
8684 void nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet() {
8685 // See RFC 2616 section 5.1.1. These are considered valid
8686 // methods which DO NOT invalidate cache-entries for the
8687 // referred resource. POST, PUT and DELETE as well as any
8688 // other method not listed here will potentially invalidate
8689 // any cached copy of the resource
8690 if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
8691 mRequestHead.IsHead() || mRequestHead.IsTrace() ||
8692 mRequestHead.IsConnect()) {
8696 // Invalidate the request-uri.
8697 if (LOG_ENABLED()) {
8699 mURI->GetAsciiSpec(key);
8700 LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", this,
8704 DoInvalidateCacheEntry(mURI);
8706 // Invalidate Location-header if set
8707 nsAutoCString location;
8708 Unused << mResponseHead->GetHeader(nsHttp::Location, location);
8709 if (!location.IsEmpty()) {
8710 LOG((" Location-header=%s\n", location.get()));
8711 InvalidateCacheEntryForLocation(location.get());
8714 // Invalidate Content-Location-header if set
8715 Unused << mResponseHead->GetHeader(nsHttp::Content_Location, location);
8716 if (!location.IsEmpty()) {
8717 LOG((" Content-Location-header=%s\n", location.get()));
8718 InvalidateCacheEntryForLocation(location.get());
8722 void nsHttpChannel::InvalidateCacheEntryForLocation(const char *location) {
8723 nsAutoCString tmpCacheKey, tmpSpec;
8724 nsCOMPtr<nsIURI> resultingURI;
8725 nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
8726 if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
8727 DoInvalidateCacheEntry(resultingURI);
8729 LOG((" hosts not matching\n"));
8733 void nsHttpChannel::DoInvalidateCacheEntry(nsIURI *aURI) {
8735 // Following comments 24,32 and 33 in bug #327765, we only care about
8736 // the cache in the protocol-handler, not the application cache.
8737 // The logic below deviates from the original logic in OpenCacheEntry on
8738 // one point by using only READ_ONLY access-policy. I think this is safe.
8743 if (LOG_ENABLED()) {
8744 aURI->GetAsciiSpec(key);
8747 LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
8749 nsCOMPtr<nsICacheStorageService> cacheStorageService(
8750 services::GetCacheStorageService());
8751 rv = cacheStorageService ? NS_OK : NS_ERROR_FAILURE;
8753 nsCOMPtr<nsICacheStorage> cacheStorage;
8754 if (NS_SUCCEEDED(rv)) {
8755 RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
8756 rv = cacheStorageService->DiskCacheStorage(info, false,
8757 getter_AddRefs(cacheStorage));
8760 if (NS_SUCCEEDED(rv)) {
8761 rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr);
8764 LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(),
8768 void nsHttpChannel::AsyncOnExamineCachedResponse() {
8769 gHttpHandler->OnExamineCachedResponse(this);
8772 void nsHttpChannel::UpdateAggregateCallbacks() {
8773 if (!mTransaction) {
8776 nsCOMPtr<nsIInterfaceRequestor> callbacks;
8777 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
8778 GetCurrentThreadEventTarget(),
8779 getter_AddRefs(callbacks));
8780 mTransaction->SetSecurityCallbacks(callbacks);
8784 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) {
8785 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
8787 nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
8788 if (NS_SUCCEEDED(rv)) {
8789 UpdateAggregateCallbacks();
8795 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) {
8796 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
8798 nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
8799 if (NS_SUCCEEDED(rv)) {
8800 UpdateAggregateCallbacks();
8805 bool nsHttpChannel::AwaitingCacheCallbacks() {
8806 return mCacheEntriesToWaitFor != 0;
8809 void nsHttpChannel::SetPushedStream(Http2PushedStream *stream) {
8811 MOZ_ASSERT(!mPushedStream);
8812 mPushedStream = stream;
8815 nsresult nsHttpChannel::OnPush(const nsACString &url,
8816 Http2PushedStream *pushedStream) {
8817 MOZ_ASSERT(NS_IsMainThread());
8818 LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
8820 MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
8821 nsCOMPtr<nsIHttpPushListener> pushListener;
8822 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
8823 NS_GET_IID(nsIHttpPushListener),
8824 getter_AddRefs(pushListener));
8826 MOZ_ASSERT(pushListener);
8827 if (!pushListener) {
8829 ("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
8830 "implement nsIHttpPushListener\n",
8832 return NS_ERROR_UNEXPECTED;
8835 nsCOMPtr<nsIURI> pushResource;
8838 // Create a Channel for the Push Resource
8839 rv = NS_NewURI(getter_AddRefs(pushResource), url);
8840 if (NS_FAILED(rv)) {
8841 return NS_ERROR_FAILURE;
8844 nsCOMPtr<nsIIOService> ioService;
8845 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
8846 NS_ENSURE_SUCCESS(rv, rv);
8848 nsCOMPtr<nsIChannel> pushChannel;
8849 rv = NS_NewChannelInternal(getter_AddRefs(pushChannel), pushResource,
8851 nullptr, // PerformanceStorage
8852 nullptr, // aLoadGroup
8853 nullptr, // aCallbacks
8854 nsIRequest::LOAD_NORMAL, ioService);
8855 NS_ENSURE_SUCCESS(rv, rv);
8857 nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
8858 MOZ_ASSERT(pushHttpChannel);
8859 if (!pushHttpChannel) {
8860 return NS_ERROR_UNEXPECTED;
8863 RefPtr<nsHttpChannel> channel;
8864 CallQueryInterface(pushHttpChannel, channel.StartAssignment());
8865 MOZ_ASSERT(channel);
8867 return NS_ERROR_UNEXPECTED;
8870 // new channel needs mrqeuesthead and headers from pushedStream
8871 channel->mRequestHead.ParseHeaderSet(
8872 pushedStream->GetRequestString().BeginWriting());
8874 channel->mLoadGroup = mLoadGroup;
8875 channel->mLoadInfo = mLoadInfo;
8876 channel->mCallbacks = mCallbacks;
8878 // Link the pushed stream with the new channel and call listener
8879 channel->SetPushedStream(pushedStream);
8880 rv = pushListener->OnPush(this, pushHttpChannel);
8885 bool nsHttpChannel::IsRedirectStatus(uint32_t status) {
8886 // 305 disabled as a security measure (see bug 187996).
8887 return status == 300 || status == 301 || status == 302 || status == 303 ||
8888 status == 307 || status == 308;
8891 void nsHttpChannel::SetCouldBeSynthesized() {
8892 MOZ_ASSERT(!BypassServiceWorker());
8893 mResponseCouldBeSynthesized = true;
8896 void nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo *aCI) {
8897 mConnectionInfo = aCI ? aCI->Clone() : nullptr;
8901 nsHttpChannel::OnPreflightSucceeded() {
8902 MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
8903 mIsCorsPreflightDone = 1;
8904 mPreflightChannel = nullptr;
8906 return ContinueConnect();
8910 nsHttpChannel::OnPreflightFailed(nsresult aError) {
8911 MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
8912 mIsCorsPreflightDone = 1;
8913 mPreflightChannel = nullptr;
8915 CloseCacheEntry(false);
8916 Unused << AsyncAbort(aError);
8920 //-----------------------------------------------------------------------------
8921 // AChannelHasDivertableParentChannelAsListener internal functions
8922 //-----------------------------------------------------------------------------
8925 nsHttpChannel::MessageDiversionStarted(
8926 ADivertableParentChannel *aParentChannel) {
8927 LOG(("nsHttpChannel::MessageDiversionStarted [this=%p]", this));
8928 MOZ_ASSERT(!mParentChannel);
8929 mParentChannel = aParentChannel;
8930 // If the channel is suspended, propagate that info to the parent's mEventQ.
8931 uint32_t suspendCount = mSuspendCount;
8932 while (suspendCount--) {
8933 mParentChannel->SuspendMessageDiversion();
8939 nsHttpChannel::MessageDiversionStop() {
8940 LOG(("nsHttpChannel::MessageDiversionStop [this=%p]", this));
8941 MOZ_ASSERT(mParentChannel);
8942 mParentChannel = nullptr;
8947 nsHttpChannel::SuspendInternal() {
8948 NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
8950 LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
8954 if (mSuspendCount == 1) {
8955 mSuspendTimestamp = TimeStamp::NowLoRes();
8958 nsresult rvTransaction = NS_OK;
8959 if (mTransactionPump) {
8960 rvTransaction = mTransactionPump->Suspend();
8962 nsresult rvCache = NS_OK;
8964 rvCache = mCachePump->Suspend();
8967 return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
8970 nsresult nsHttpChannel::CallOrWaitForResume(
8971 const std::function<nsresult(nsHttpChannel *)> &aFunc) {
8973 MOZ_ASSERT(NS_FAILED(mStatus));
8977 if (mSuspendCount) {
8978 LOG(("Waiting until resume [this=%p]\n", this));
8979 MOZ_ASSERT(!mCallOnResume);
8980 mCallOnResume = aFunc;
8988 nsHttpChannel::ResumeInternal() {
8989 NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
8991 LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
8993 if (--mSuspendCount == 0) {
8994 mSuspendTotalTime +=
8995 (TimeStamp::NowLoRes() - mSuspendTimestamp).ToMilliseconds();
8997 if (mCallOnResume) {
8998 // Resume the interrupted procedure first, then resume
8999 // the pump to continue process the input stream.
9000 // Any newly created pump MUST be suspended to prevent calling
9001 // its OnStartRequest before OnStopRequest of any pre-existing
9002 // pump. mAsyncResumePending ensures that.
9003 MOZ_ASSERT(!mAsyncResumePending);
9004 mAsyncResumePending = 1;
9006 std::function<nsresult(nsHttpChannel *)> callOnResume = nullptr;
9007 std::swap(callOnResume, mCallOnResume);
9009 RefPtr<nsHttpChannel> self(this);
9010 RefPtr<nsInputStreamPump> transactionPump = mTransactionPump;
9011 RefPtr<nsInputStreamPump> cachePump = mCachePump;
9013 nsresult rv = NS_DispatchToCurrentThread(NS_NewRunnableFunction(
9014 "nsHttpChannel::CallOnResume",
9015 [callOnResume{std::move(callOnResume)}, self{std::move(self)},
9016 transactionPump{std::move(transactionPump)},
9017 cachePump{std::move(cachePump)}]() {
9018 MOZ_ASSERT(self->mAsyncResumePending);
9019 nsresult rv = self->CallOrWaitForResume(callOnResume);
9020 if (NS_FAILED(rv)) {
9021 self->CloseCacheEntry(false);
9022 Unused << self->AsyncAbort(rv);
9024 MOZ_ASSERT(self->mAsyncResumePending);
9026 self->mAsyncResumePending = 0;
9028 // And now actually resume the previously existing pumps.
9029 if (transactionPump) {
9031 ("nsHttpChannel::CallOnResume resuming previous transaction "
9033 transactionPump.get(), self.get()));
9034 transactionPump->Resume();
9038 ("nsHttpChannel::CallOnResume resuming previous cache pump "
9040 cachePump.get(), self.get()));
9041 cachePump->Resume();
9044 // Any newly created pumps were suspended once because of
9045 // mAsyncResumePending. Problem is that the stream listener
9046 // notification is already pending in the queue right now, because
9047 // AsyncRead doesn't (regardless if called after Suspend) respect
9048 // the suspend coutner and the right order would not be preserved.
9049 // Hence, we do another dispatch round to actually Resume after the
9050 // notification from the original pump.
9051 if (transactionPump != self->mTransactionPump &&
9052 self->mTransactionPump) {
9054 ("nsHttpChannel::CallOnResume async-resuming new transaction "
9056 self->mTransactionPump.get(), self.get()));
9058 RefPtr<nsInputStreamPump> pump = self->mTransactionPump;
9059 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
9060 "nsHttpChannel::CallOnResume new transaction",
9061 [pump{std::move(pump)}]() { pump->Resume(); }));
9063 if (cachePump != self->mCachePump && self->mCachePump) {
9065 ("nsHttpChannel::CallOnResume async-resuming new cache pump "
9067 self->mCachePump.get(), self.get()));
9069 RefPtr<nsInputStreamPump> pump = self->mCachePump;
9070 NS_DispatchToCurrentThread(NS_NewRunnableFunction(
9071 "nsHttpChannel::CallOnResume new pump",
9072 [pump{std::move(pump)}]() { pump->Resume(); }));
9075 NS_ENSURE_SUCCESS(rv, rv);
9080 nsresult rvTransaction = NS_OK;
9081 if (mTransactionPump) {
9082 rvTransaction = mTransactionPump->Resume();
9085 nsresult rvCache = NS_OK;
9087 rvCache = mCachePump->Resume();
9090 return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
9093 void nsHttpChannel::MaybeWarnAboutAppCache() {
9094 // First, accumulate a telemetry ping about appcache usage.
9095 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, true);
9097 // Then, issue a deprecation warning.
9098 nsCOMPtr<nsIDeprecationWarner> warner;
9099 GetCallback(warner);
9101 warner->IssueWarning(Document::eAppCache, false);
9102 // When the page is insecure and the API is still enabled
9103 // provide an additional warning for developers of removal
9105 Preferences::GetBool("browser.cache.offline.insecure.enable")) {
9106 warner->IssueWarning(Document::eAppCacheInsecure, true);
9111 void nsHttpChannel::SetLoadGroupUserAgentOverride() {
9112 nsCOMPtr<nsIURI> uri;
9113 GetURI(getter_AddRefs(uri));
9114 nsAutoCString uriScheme;
9116 uri->GetScheme(uriScheme);
9119 // We don't need a UA for file: protocols.
9120 if (uriScheme.EqualsLiteral("file")) {
9121 gHttpHandler->OnUserAgentRequest(this);
9125 nsIRequestContextService *rcsvc = gHttpHandler->GetRequestContextService();
9126 nsCOMPtr<nsIRequestContext> rc;
9128 rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(rc));
9132 if (nsContentUtils::IsNonSubresourceRequest(this)) {
9133 gHttpHandler->OnUserAgentRequest(this);
9135 GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
9136 rc->SetUserAgentOverride(ua);
9139 GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
9140 // Don't overwrite the UA if it is already set (eg by an XHR with explicit
9144 SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"),
9145 rc->GetUserAgentOverride(), false);
9147 gHttpHandler->OnUserAgentRequest(this);
9153 // Step 10 of HTTP-network-or-cache fetch
9154 void nsHttpChannel::SetOriginHeader() {
9155 if (mRequestHead.IsGet() || mRequestHead.IsHead()) {
9158 nsAutoCString existingHeader;
9159 Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
9160 if (!existingHeader.IsEmpty()) {
9161 LOG(("nsHttpChannel::SetOriginHeader Origin header already present"));
9165 DebugOnly<nsresult> rv;
9167 // Instead of consulting Preferences::GetInt() all the time we
9168 // can cache the result to speed things up.
9169 static int32_t sSendOriginHeader = 0;
9170 static bool sIsInited = false;
9173 Preferences::AddIntVarCache(&sSendOriginHeader,
9174 "network.http.sendOriginHeader");
9176 if (sSendOriginHeader == 0) {
9177 // Origin header suppressed by user setting
9181 nsCOMPtr<nsIURI> referrer;
9182 mLoadInfo->TriggeringPrincipal()->GetURI(getter_AddRefs(referrer));
9184 nsAutoCString origin("null");
9185 if (referrer && IsReferrerSchemeAllowed(referrer)) {
9186 nsContentUtils::GetASCIIOrigin(referrer, origin);
9189 // Restrict Origin to same-origin loads if requested by user or leaving from
9191 if (sSendOriginHeader == 1) {
9192 nsAutoCString currentOrigin;
9193 nsContentUtils::GetASCIIOrigin(mURI, currentOrigin);
9194 if (!origin.EqualsIgnoreCase(currentOrigin.get())) {
9195 // Origin header suppressed by user setting
9198 } else if (gHttpHandler->HideOnionReferrerSource()) {
9200 if (referrer && NS_SUCCEEDED(referrer->GetAsciiHost(host)) &&
9201 StringEndsWith(host, NS_LITERAL_CSTRING(".onion"))) {
9202 nsAutoCString currentOrigin;
9203 nsContentUtils::GetASCIIOrigin(mURI, currentOrigin);
9204 if (!origin.EqualsIgnoreCase(currentOrigin.get())) {
9205 // Origin header is suppressed by .onion
9211 rv = mRequestHead.SetHeader(nsHttp::Origin, origin, false /* merge */);
9212 MOZ_ASSERT(NS_SUCCEEDED(rv));
9215 void nsHttpChannel::SetDoNotTrack() {
9217 * 'DoNotTrack' header should be added if 'privacy.donottrackheader.enabled'
9218 * is true or tracking protection is enabled. See bug 1258033.
9220 nsCOMPtr<nsILoadContext> loadContext;
9221 NS_QueryNotificationCallbacks(this, loadContext);
9223 if ((loadContext && loadContext->UseTrackingProtection()) ||
9224 nsContentUtils::DoNotTrackEnabled()) {
9225 DebugOnly<nsresult> rv = mRequestHead.SetHeader(
9226 nsHttp::DoNotTrack, NS_LITERAL_CSTRING("1"), false);
9227 MOZ_ASSERT(NS_SUCCEEDED(rv));
9231 void nsHttpChannel::ReportRcwnStats(bool isFromNet) {
9232 if (!sRCWNEnabled) {
9237 if (mRaceCacheWithNetwork) {
9238 gIOService->IncrementNetWonRequestNumber();
9239 Telemetry::Accumulate(
9240 Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_NETWORK_WIN,
9243 AccumulateCategorical(
9244 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9245 NetworkDelayedRace);
9247 AccumulateCategorical(
9248 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9252 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
9254 AccumulateCategorical(
9255 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9259 if (mRaceCacheWithNetwork || mRaceDelay) {
9260 gIOService->IncrementCacheWonRequestNumber();
9261 Telemetry::Accumulate(
9262 Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_CACHE_WIN,
9265 AccumulateCategorical(
9266 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9269 AccumulateCategorical(
9270 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9274 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
9276 AccumulateCategorical(
9277 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
9282 gIOService->IncrementRequestNumber();
9285 static const size_t kPositiveBucketNumbers = 34;
9286 static const int64_t kPositiveBucketLevels[kPositiveBucketNumbers] = {
9287 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200,
9288 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000,
9289 6000, 7000, 8000, 9000, 10000, 20000, 30000, 40000, 50000, 60000};
9292 * For space efficiency, we collect finer resolution for small difference
9293 * between net and cache time, coarser for larger.
9294 * Bucket #40 for a tie.
9295 * #41 to #50 indicates cache wins by 1ms to 100ms, split equally.
9296 * #51 to #59 indicates cache wins by 101ms to 1000ms.
9297 * #60 to #68 indicates cache wins by 1s to 10s.
9298 * #69 to #73 indicates cache wins by 11s to 60s.
9299 * #74 indicates cache wins by more than 1 minute.
9301 * #39 to #30 indicates network wins by 1ms to 100ms, split equally.
9302 * #29 to #21 indicates network wins by 101ms to 1000ms.
9303 * #20 to #12 indicates network wins by 1s to 10s.
9304 * #11 to #7 indicates network wins by 11s to 60s.
9305 * #6 indicates network wins by more than 1 minute.
9307 * Other bucket numbers are reserved.
9309 inline int64_t nsHttpChannel::ComputeTelemetryBucketNumber(
9310 int64_t difftime_ms) {
9311 int64_t absBucketIndex =
9312 std::lower_bound(kPositiveBucketLevels,
9313 kPositiveBucketLevels + kPositiveBucketNumbers,
9314 static_cast<int64_t>(mozilla::Abs(difftime_ms))) -
9315 kPositiveBucketLevels;
9317 return difftime_ms >= 0 ? 40 + absBucketIndex : 40 - absBucketIndex;
9320 void nsHttpChannel::ReportNetVSCacheTelemetry() {
9326 // We only report telemetry if the entry is persistent (on disk)
9328 rv = mCacheEntry->GetPersistent(&persistent);
9329 if (NS_FAILED(rv) || !persistent) {
9333 uint64_t onStartNetTime = 0;
9334 if (NS_FAILED(mCacheEntry->GetOnStartTime(&onStartNetTime))) {
9338 uint64_t onStopNetTime = 0;
9339 if (NS_FAILED(mCacheEntry->GetOnStopTime(&onStopNetTime))) {
9343 uint64_t onStartCacheTime =
9344 (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
9345 int64_t onStartDiff = onStartNetTime - onStartCacheTime;
9346 onStartDiff = ComputeTelemetryBucketNumber(onStartDiff);
9348 uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
9349 int64_t onStopDiff = onStopNetTime - onStopCacheTime;
9350 onStopDiff = ComputeTelemetryBucketNumber(onStopDiff);
9353 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED_V2,
9355 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED_V2,
9358 Telemetry::Accumulate(
9359 Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED_V2, onStartDiff);
9360 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED_V2,
9365 // We don't report revalidated probes as the data would be skewed.
9369 if (mCacheOpenWithPriority) {
9370 if (mCacheQueueSizeWhenOpen < 5) {
9371 Telemetry::Accumulate(
9372 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI_V2, onStartDiff);
9373 Telemetry::Accumulate(
9374 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI_V2, onStopDiff);
9375 } else if (mCacheQueueSizeWhenOpen < 10) {
9376 Telemetry::Accumulate(
9377 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI_V2, onStartDiff);
9378 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI_V2,
9381 Telemetry::Accumulate(
9382 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI_V2, onStartDiff);
9383 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI_V2,
9386 } else { // The limits are higher for normal priority cache queues
9387 if (mCacheQueueSizeWhenOpen < 10) {
9388 Telemetry::Accumulate(
9389 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI_V2,
9391 Telemetry::Accumulate(
9392 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI_V2, onStopDiff);
9393 } else if (mCacheQueueSizeWhenOpen < 50) {
9394 Telemetry::Accumulate(
9395 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI_V2, onStartDiff);
9396 Telemetry::Accumulate(
9397 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI_V2, onStopDiff);
9399 Telemetry::Accumulate(
9400 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI_V2, onStartDiff);
9401 Telemetry::Accumulate(
9402 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI_V2, onStopDiff);
9406 uint32_t diskStorageSizeK = 0;
9407 rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
9408 if (NS_FAILED(rv)) {
9412 // No significant difference was observed between different sizes for
9414 if (diskStorageSizeK < 256) {
9415 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_V2,
9418 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_V2,
9424 nsHttpChannel::Test_delayCacheEntryOpeningBy(int32_t aTimeout) {
9425 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
9426 mCacheOpenDelay = aTimeout;
9431 nsHttpChannel::Test_triggerDelayedOpenCacheEntry() {
9432 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
9434 if (!mCacheOpenDelay) {
9435 // No delay was set.
9436 return NS_ERROR_NOT_AVAILABLE;
9438 if (!mCacheOpenFunc) {
9439 // There should be a runnable.
9440 return NS_ERROR_FAILURE;
9442 if (mCacheOpenTimer) {
9443 rv = mCacheOpenTimer->Cancel();
9444 if (NS_FAILED(rv)) {
9447 mCacheOpenTimer = nullptr;
9449 mCacheOpenDelay = 0;
9450 // Avoid re-entrancy issues by nulling our mCacheOpenFunc before calling it.
9451 std::function<void(nsHttpChannel *)> cacheOpenFunc = nullptr;
9452 std::swap(cacheOpenFunc, mCacheOpenFunc);
9453 cacheOpenFunc(this);
9458 nsresult nsHttpChannel::TriggerNetworkWithDelay(uint32_t aDelay) {
9459 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
9461 LOG(("nsHttpChannel::TriggerNetworkWithDelay [this=%p, delay=%u]\n", this,
9465 LOG((" channel was canceled.\n"));
9469 // If a network request has already gone out, there is no point in
9470 // doing this again.
9471 if (mNetworkTriggered) {
9472 LOG((" network already triggered. Returning.\n"));
9477 // We cannot call TriggerNetwork() directly here, because it would
9478 // cause performance regression in tp6 tests, see bug 1398847.
9479 return NS_DispatchToMainThread(
9480 NewRunnableMethod("net::nsHttpChannel::TriggerNetworkWithDelay", this,
9481 &nsHttpChannel::TriggerNetwork),
9482 NS_DISPATCH_NORMAL);
9485 if (!mNetworkTriggerTimer) {
9486 mNetworkTriggerTimer = NS_NewTimer();
9488 mNetworkTriggerTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
9492 nsresult nsHttpChannel::TriggerNetwork() {
9493 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
9495 LOG(("nsHttpChannel::TriggerNetwork [this=%p]\n", this));
9498 LOG((" channel was canceled.\n"));
9502 // If a network request has already gone out, there is no point in
9503 // doing this again.
9504 if (mNetworkTriggered) {
9505 LOG((" network already triggered. Returning.\n"));
9509 mNetworkTriggered = true;
9510 if (mNetworkTriggerTimer) {
9511 mNetworkTriggerTimer->Cancel();
9512 mNetworkTriggerTimer = nullptr;
9515 // If we are waiting for a proxy request, that means we can't trigger
9516 // the next step just yet. We need for mConnectionInfo to be non-null
9517 // before we call ContinueConnect. OnProxyAvailable will trigger
9518 // BeginConnect, and Connect will call ContinueConnect even if it's
9519 // for the cache callbacks.
9520 if (mProxyRequest) {
9521 LOG((" proxy request in progress. Delaying network trigger.\n"));
9522 mWaitingForProxy = true;
9526 if (AwaitingCacheCallbacks()) {
9527 mRaceCacheWithNetwork = sRCWNEnabled;
9530 LOG((" triggering network\n"));
9531 return ContinueConnect();
9534 nsresult nsHttpChannel::MaybeRaceCacheWithNetwork() {
9537 nsCOMPtr<nsINetworkLinkService> netLinkSvc =
9538 do_GetService(NS_NETWORK_LINK_SERVICE_CONTRACTID, &rv);
9539 NS_ENSURE_SUCCESS(rv, rv);
9542 rv = netLinkSvc->GetLinkType(&linkType);
9543 NS_ENSURE_SUCCESS(rv, rv);
9545 if (!(linkType == nsINetworkLinkService::LINK_TYPE_UNKNOWN ||
9546 linkType == nsINetworkLinkService::LINK_TYPE_ETHERNET ||
9547 linkType == nsINetworkLinkService::LINK_TYPE_USB ||
9548 linkType == nsINetworkLinkService::LINK_TYPE_WIFI)) {
9552 // Don't trigger the network if the load flags say so.
9553 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
9557 // We must not race if the channel has a failure status code.
9558 if (NS_FAILED(mStatus)) {
9562 // If a CORS Preflight is required we must not race.
9563 if (mRequireCORSPreflight && !mIsCorsPreflightDone) {
9567 if (CacheFileUtils::CachePerfStats::IsCacheSlow()) {
9568 // If the cache is slow, trigger the network request immediately.
9571 // Give cache a headstart of 3 times the average cache entry open time.
9572 mRaceDelay = CacheFileUtils::CachePerfStats::GetAverage(
9573 CacheFileUtils::CachePerfStats::ENTRY_OPEN, true) *
9575 // We use microseconds in CachePerfStats but we need milliseconds
9576 // for TriggerNetwork.
9580 mRaceDelay = clamped<uint32_t>(mRaceDelay, sRCWNMinWaitMs, sRCWNMaxWaitMs);
9582 MOZ_ASSERT(sRCWNEnabled, "The pref must be turned on.");
9583 LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this,
9586 return TriggerNetworkWithDelay(mRaceDelay);
9590 nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) {
9591 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
9592 return TriggerNetworkWithDelay(aTimeout);
9596 nsHttpChannel::Notify(nsITimer *aTimer) {
9597 RefPtr<nsHttpChannel> self(this);
9598 if (aTimer == mCacheOpenTimer) {
9599 return Test_triggerDelayedOpenCacheEntry();
9600 } else if (aTimer == mNetworkTriggerTimer) {
9601 return TriggerNetwork();
9603 MOZ_CRASH("Unknown timer");
9609 bool nsHttpChannel::EligibleForTailing() {
9610 if (!(mClassOfService & nsIClassOfService::Tail)) {
9614 if (mClassOfService &
9615 (nsIClassOfService::UrgentStart | nsIClassOfService::Leader |
9616 nsIClassOfService::TailForbidden)) {
9620 if (mClassOfService & nsIClassOfService::Unblocked &&
9621 !(mClassOfService & nsIClassOfService::TailAllowed)) {
9625 if (IsNavigation()) {
9632 bool nsHttpChannel::WaitingForTailUnblock() {
9635 if (!gHttpHandler->IsTailBlockingEnabled()) {
9636 LOG(("nsHttpChannel %p tail-blocking disabled", this));
9640 if (!EligibleForTailing()) {
9641 LOG(("nsHttpChannel %p not eligible for tail-blocking", this));
9642 AddAsNonTailRequest();
9646 if (!EnsureRequestContext()) {
9647 LOG(("nsHttpChannel %p no request context", this));
9651 LOG(("nsHttpChannel::WaitingForTailUnblock this=%p, rc=%p", this,
9652 mRequestContext.get()));
9655 rv = mRequestContext->IsContextTailBlocked(this, &blocked);
9656 if (NS_FAILED(rv)) {
9660 LOG((" blocked=%d", blocked));
9665 //-----------------------------------------------------------------------------
9666 // nsHttpChannel::nsIRequestTailUnblockCallback
9667 //-----------------------------------------------------------------------------
9669 // Must be implemented in the leaf class because we don't have
9670 // AsyncAbort in HttpBaseChannel.
9672 nsHttpChannel::OnTailUnblock(nsresult rv) {
9673 LOG(("nsHttpChannel::OnTailUnblock this=%p rv=%" PRIx32 " rc=%p", this,
9674 static_cast<uint32_t>(rv), mRequestContext.get()));
9676 MOZ_RELEASE_ASSERT(mOnTailUnblock);
9678 if (NS_FAILED(mStatus)) {
9682 if (NS_SUCCEEDED(rv)) {
9683 auto callback = mOnTailUnblock;
9684 mOnTailUnblock = nullptr;
9685 rv = (this->*callback)();
9688 if (NS_FAILED(rv)) {
9689 CloseCacheEntry(false);
9690 return AsyncAbort(rv);
9696 void nsHttpChannel::SetWarningReporter(
9697 HttpChannelSecurityWarningReporter *aReporter) {
9698 LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
9699 mWarningReporter = aReporter;
9702 HttpChannelSecurityWarningReporter *nsHttpChannel::GetWarningReporter() {
9703 LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this,
9704 mWarningReporter.get()));
9705 return mWarningReporter.get();
9710 class CopyNonDefaultHeaderVisitor final : public nsIHttpHeaderVisitor {
9711 nsCOMPtr<nsIHttpChannel> mTarget;
9713 ~CopyNonDefaultHeaderVisitor() = default;
9716 VisitHeader(const nsACString &aHeader, const nsACString &aValue) override {
9717 if (aValue.IsEmpty()) {
9718 return mTarget->SetEmptyRequestHeader(aHeader);
9720 return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */);
9725 explicit CopyNonDefaultHeaderVisitor(nsIHttpChannel *aTarget)
9726 : mTarget(aTarget) {
9727 MOZ_DIAGNOSTIC_ASSERT(mTarget);
9733 NS_IMPL_ISUPPORTS(CopyNonDefaultHeaderVisitor, nsIHttpHeaderVisitor)
9735 } // anonymous namespace
9737 nsresult nsHttpChannel::RedirectToInterceptedChannel() {
9738 nsCOMPtr<nsINetworkInterceptController> controller;
9739 GetCallback(controller);
9741 RefPtr<InterceptedHttpChannel> intercepted =
9742 InterceptedHttpChannel::CreateForInterception(
9743 mChannelCreationTime, mChannelCreationTimestamp, mAsyncOpenTime);
9745 nsContentPolicyType type = mLoadInfo
9746 ? mLoadInfo->GetExternalContentPolicyType()
9747 : nsIContentPolicy::TYPE_OTHER;
9749 nsresult rv = intercepted->Init(
9750 mURI, mCaps, static_cast<nsProxyInfo *>(mProxyInfo.get()),
9751 mProxyResolveFlags, mProxyURI, mChannelId, type);
9753 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
9754 CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
9755 intercepted->SetLoadInfo(redirectLoadInfo);
9757 rv = SetupReplacementChannel(mURI, intercepted, true,
9758 nsIChannelEventSink::REDIRECT_INTERNAL);
9759 NS_ENSURE_SUCCESS(rv, rv);
9761 // Some APIs, like fetch(), allow content to set non-standard headers.
9762 // Normally these APIs are responsible for copying these headers across
9763 // redirects. In the e10s parent-side intercept case, though, we currently
9764 // "hide" the internal redirect to the InterceptedHttpChannel. So the
9765 // fetch() API does not have the opportunity to move headers over.
9766 // Therefore, we do it automatically here.
9768 // Once child-side interception is removed and the internal redirect no
9769 // longer needs to be "hidden", then this header copying code can be
9771 if (ServiceWorkerParentInterceptEnabled()) {
9772 nsCOMPtr<nsIHttpHeaderVisitor> visitor =
9773 new CopyNonDefaultHeaderVisitor(intercepted);
9774 rv = VisitNonDefaultRequestHeaders(visitor);
9775 NS_ENSURE_SUCCESS(rv, rv);
9778 mRedirectChannel = intercepted;
9780 PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
9782 rv = gHttpHandler->AsyncOnChannelRedirect(
9783 this, intercepted, nsIChannelEventSink::REDIRECT_INTERNAL);
9785 if (NS_SUCCEEDED(rv)) {
9786 rv = WaitForRedirectCallback();
9789 if (NS_FAILED(rv)) {
9790 AutoRedirectVetoNotifier notifier(this);
9792 PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
9799 } // namespace mozilla