1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set expandtab ts=4 sw=4 sts=4 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/. */
6
7 // HttpLog.h should generally be included first
8 #include "HttpLog.h"
9
10 #include <inttypes.h>
11
12 #include "mozilla/dom/nsCSPContext.h"
13 #include "mozilla/ScopeExit.h"
14 #include "mozilla/Sprintf.h"
15
16 #include "nsHttp.h"
17 #include "nsHttpChannel.h"
18 #include "nsHttpHandler.h"
19 #include "nsIApplicationCacheService.h"
20 #include "nsIApplicationCacheContainer.h"
21 #include "nsICacheStorageService.h"
22 #include "nsICacheStorage.h"
23 #include "nsICacheEntry.h"
24 #include "nsICaptivePortalService.h"
25 #include "nsICryptoHash.h"
26 #include "nsINetworkInterceptController.h"
27 #include "nsINSSErrorsService.h"
28 #include "nsISecurityReporter.h"
29 #include "nsIStringBundle.h"
30 #include "nsIStreamListenerTee.h"
31 #include "nsISeekableStream.h"
32 #include "nsILoadGroupChild.h"
33 #include "nsIProtocolProxyService2.h"
34 #include "nsIURIClassifier.h"
35 #include "nsMimeTypes.h"
36 #include "nsNetCID.h"
37 #include "nsNetUtil.h"
38 #include "nsIURL.h"
39 #include "nsIURIMutator.h"
40 #include "nsIStreamTransportService.h"
41 #include "prnetdb.h"
42 #include "nsEscape.h"
43 #include "nsStreamUtils.h"
44 #include "nsIOService.h"
45 #include "nsDNSPrefetch.h"
46 #include "nsChannelClassifier.h"
47 #include "nsIRedirectResultListener.h"
48 #include "mozilla/dom/ContentVerifier.h"
49 #include "mozilla/TimeStamp.h"
50 #include "nsError.h"
51 #include "nsPrintfCString.h"
52 #include "nsAlgorithm.h"
53 #include "nsQueryObject.h"
54 #include "nsThreadUtils.h"
55 #include "GeckoProfiler.h"
56 #include "nsIConsoleService.h"
57 #include "mozilla/Attributes.h"
58 #include "mozilla/DebugOnly.h"
59 #include "mozilla/Preferences.h"
60 #include "nsISSLSocketControl.h"
61 #include "sslt.h"
62 #include "nsContentUtils.h"
63 #include "nsContentSecurityManager.h"
64 #include "nsIClassOfService.h"
65 #include "nsIPermissionManager.h"
66 #include "nsIPrincipal.h"
67 #include "nsIScriptError.h"
68 #include "nsIScriptSecurityManager.h"
69 #include "nsISSLStatus.h"
70 #include "nsISSLStatusProvider.h"
71 #include "nsITransportSecurityInfo.h"
72 #include "nsIWebProgressListener.h"
73 #include "LoadContextInfo.h"
74 #include "netCore.h"
75 #include "nsHttpTransaction.h"
76 #include "nsICacheEntryDescriptor.h"
77 #include "nsICancelable.h"
78 #include "nsIHttpChannelAuthProvider.h"
79 #include "nsIHttpChannelInternal.h"
80 #include "nsIPrompt.h"
81 #include "nsInputStreamPump.h"
82 #include "nsURLHelper.h"
83 #include "nsISocketTransport.h"
84 #include "nsIStreamConverterService.h"
85 #include "nsISiteSecurityService.h"
86 #include "nsString.h"
87 #include "nsCRT.h"
88 #include "CacheObserver.h"
89 #include "mozilla/dom/PerformanceStorage.h"
90 #include "mozilla/Telemetry.h"
91 #include "AlternateServices.h"
92 #include "InterceptedChannel.h"
93 #include "nsIHttpPushListener.h"
94 #include "nsIX509Cert.h"
95 #include "ScopedNSSTypes.h"
96 #include "NullPrincipal.h"
97 #include "nsIDeprecationWarner.h"
98 #include "nsIDocument.h"
99 #include "nsIDOMDocument.h"
100 #include "nsICompressConvStats.h"
101 #include "nsCORSListenerProxy.h"
102 #include "nsISocketProvider.h"
103 #include "mozilla/extensions/StreamFilterParent.h"
104 #include "mozilla/net/Predictor.h"
105 #include "mozilla/MathAlgorithms.h"
106 #include "CacheControlParser.h"
107 #include "nsMixedContentBlocker.h"
108 #include "CacheStorageService.h"
109 #include "HttpChannelParent.h"
110 #include "InterceptedHttpChannel.h"
111 #include "nsIBufferedStreams.h"
112 #include "nsIFileStreams.h"
113 #include "nsIMIMEInputStream.h"
114 #include "nsIMultiplexInputStream.h"
115 #include "../../cache2/CacheFileUtils.h"
116
117 #ifdef MOZ_TASK_TRACER
118 #include "GeckoTaskTracer.h"
119 #endif
120
121 namespace mozilla {
122 namespace net {
123
124 namespace {
125
126 static bool sRCWNEnabled = false;
127 static uint32_t sRCWNQueueSizeNormal = 50;
128 static uint32_t sRCWNQueueSizePriority = 10;
129 static uint32_t sRCWNSmallResourceSizeKB = 256;
130 static uint32_t sRCWNMinWaitMs = 0;
131 static uint32_t sRCWNMaxWaitMs = 500;
132
133 // True if the local cache should be bypassed when processing a request.
134 #define BYPASS_LOCAL_CACHE(loadFlags) \
135 (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
136 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
137
138 #define RECOVER_FROM_CACHE_FILE_ERROR(result) \
139 ((result) == NS_ERROR_FILE_NOT_FOUND || \
140 (result) == NS_ERROR_FILE_CORRUPTED || (result) == NS_ERROR_OUT_OF_MEMORY)
141
142 #define WRONG_RACING_RESPONSE_SOURCE(req) \
143 (mRaceCacheWithNetwork && \
144 (((mFirstResponseSource == RESPONSE_FROM_CACHE) && (req != mCachePump)) || \
145 ((mFirstResponseSource == RESPONSE_FROM_NETWORK) && \
146 (req != mTransactionPump))))
147
148 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
149
150 enum CacheDisposition {
151 kCacheHit = 1,
152 kCacheHitViaReval = 2,
153 kCacheMissedViaReval = 3,
154 kCacheMissed = 4
155 };
156
AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss)157 void AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss) {
158 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
159 }
160
161 // Computes and returns a SHA1 hash of the input buffer. The input buffer
162 // must be a null-terminated string.
Hash(const char * buf,nsACString & hash)163 nsresult Hash(const char *buf, nsACString &hash) {
164 nsresult rv;
165
166 nsCOMPtr<nsICryptoHash> hasher =
167 do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
168 NS_ENSURE_SUCCESS(rv, rv);
169
170 rv = hasher->Init(nsICryptoHash::SHA1);
171 NS_ENSURE_SUCCESS(rv, rv);
172
173 rv =
174 hasher->Update(reinterpret_cast<unsigned const char *>(buf), strlen(buf));
175 NS_ENSURE_SUCCESS(rv, rv);
176
177 rv = hasher->Finish(true, hash);
178 NS_ENSURE_SUCCESS(rv, rv);
179
180 return NS_OK;
181 }
182
IsInSubpathOfAppCacheManifest(nsIApplicationCache * cache,nsACString const & uriSpec)183 bool IsInSubpathOfAppCacheManifest(nsIApplicationCache *cache,
184 nsACString const &uriSpec) {
185 MOZ_ASSERT(cache);
186
187 nsresult rv;
188
189 nsCOMPtr<nsIURI> uri;
190 rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
191 if (NS_FAILED(rv)) {
192 return false;
193 }
194
195 nsCOMPtr<nsIURL> url(do_QueryInterface(uri, &rv));
196 if (NS_FAILED(rv)) {
197 return false;
198 }
199
200 nsAutoCString directory;
201 rv = url->GetDirectory(directory);
202 if (NS_FAILED(rv)) {
203 return false;
204 }
205
206 nsCOMPtr<nsIURI> manifestURI;
207 rv = cache->GetManifestURI(getter_AddRefs(manifestURI));
208 if (NS_FAILED(rv)) {
209 return false;
210 }
211
212 nsCOMPtr<nsIURL> manifestURL(do_QueryInterface(manifestURI, &rv));
213 if (NS_FAILED(rv)) {
214 return false;
215 }
216
217 nsAutoCString manifestDirectory;
218 rv = manifestURL->GetDirectory(manifestDirectory);
219 if (NS_FAILED(rv)) {
220 return false;
221 }
222
223 return StringBeginsWith(directory, manifestDirectory);
224 }
225
226 } // unnamed namespace
227
228 // We only treat 3xx responses as redirects if they have a Location header and
229 // the status code is in a whitelist.
WillRedirect(nsHttpResponseHead * response)230 bool nsHttpChannel::WillRedirect(nsHttpResponseHead *response) {
231 return IsRedirectStatus(response->Status()) &&
232 response->HasHeader(nsHttp::Location);
233 }
234
235 nsresult StoreAuthorizationMetaData(nsICacheEntry *entry,
236 nsHttpRequestHead *requestHead);
237
238 class AutoRedirectVetoNotifier {
239 public:
AutoRedirectVetoNotifier(nsHttpChannel * channel)240 explicit AutoRedirectVetoNotifier(nsHttpChannel *channel)
241 : mChannel(channel) {
242 if (mChannel->mHasAutoRedirectVetoNotifier) {
243 MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
244 mChannel = nullptr;
245 return;
246 }
247
248 mChannel->mHasAutoRedirectVetoNotifier = true;
249 }
~AutoRedirectVetoNotifier()250 ~AutoRedirectVetoNotifier() { ReportRedirectResult(false); }
RedirectSucceeded()251 void RedirectSucceeded() { ReportRedirectResult(true); }
252
253 private:
254 nsHttpChannel *mChannel;
255 void ReportRedirectResult(bool succeeded);
256 };
257
ReportRedirectResult(bool succeeded)258 void AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded) {
259 if (!mChannel) return;
260
261 mChannel->mRedirectChannel = nullptr;
262
263 if (succeeded) {
264 mChannel->RemoveAsNonTailRequest();
265 }
266
267 nsCOMPtr<nsIRedirectResultListener> vetoHook;
268 NS_QueryNotificationCallbacks(mChannel, NS_GET_IID(nsIRedirectResultListener),
269 getter_AddRefs(vetoHook));
270
271 nsHttpChannel *channel = mChannel;
272 mChannel = nullptr;
273
274 if (vetoHook) vetoHook->OnRedirectResult(succeeded);
275
276 // Drop after the notification
277 channel->mHasAutoRedirectVetoNotifier = false;
278 }
279
280 //-----------------------------------------------------------------------------
281 // nsHttpChannel <public>
282 //-----------------------------------------------------------------------------
283
nsHttpChannel()284 nsHttpChannel::nsHttpChannel()
285 : HttpAsyncAborter<nsHttpChannel>(this),
286 mLogicalOffset(0),
287 mPostID(0),
288 mRequestTime(0),
289 mOfflineCacheLastModifiedTime(0),
290 mSuspendTotalTime(0),
291 mCacheOpenWithPriority(false),
292 mCacheQueueSizeWhenOpen(0),
293 mCachedContentIsValid(false),
294 mCachedContentIsPartial(false),
295 mCacheOnlyMetadata(false),
296 mTransactionReplaced(false),
297 mAuthRetryPending(false),
298 mProxyAuthPending(false),
299 mCustomAuthHeader(false),
300 mResuming(false),
301 mInitedCacheEntry(false),
302 mFallbackChannel(false),
303 mCustomConditionalRequest(false),
304 mFallingBack(false),
305 mWaitingForRedirectCallback(false),
306 mRequestTimeInitialized(false),
307 mCacheEntryIsReadOnly(false),
308 mCacheEntryIsWriteOnly(false),
309 mCacheEntriesToWaitFor(0),
310 mHasQueryString(0),
311 mConcurrentCacheAccess(0),
312 mIsPartialRequest(0),
313 mHasAutoRedirectVetoNotifier(0),
314 mPinCacheContent(0),
315 mIsCorsPreflightDone(0),
316 mStronglyFramed(false),
317 mUsedNetwork(0),
318 mAuthConnectionRestartable(0),
319 mPushedStream(nullptr),
320 mOnTailUnblock(nullptr),
321 mWarningReporter(nullptr),
322 mIsReadingFromCache(false),
323 mFirstResponseSource(RESPONSE_PENDING),
324 mRaceCacheWithNetwork(false),
325 mRaceDelay(0),
326 mIgnoreCacheEntry(false),
327 mRCWNLock("nsHttpChannel.mRCWNLock"),
328 mDidReval(false) {
329 LOG(("Creating nsHttpChannel [this=%p]\n", this));
330 mChannelCreationTime = PR_Now();
331 mChannelCreationTimestamp = TimeStamp::Now();
332 }
333
~nsHttpChannel()334 nsHttpChannel::~nsHttpChannel() {
335 LOG(("Destroying nsHttpChannel [this=%p]\n", this));
336
337 if (mAuthProvider) {
338 DebugOnly<nsresult> rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
339 MOZ_ASSERT(NS_SUCCEEDED(rv));
340 }
341
342 ReleaseMainThreadOnlyReferences();
343 }
344
ReleaseMainThreadOnlyReferences()345 void nsHttpChannel::ReleaseMainThreadOnlyReferences() {
346 if (NS_IsMainThread()) {
347 // Already on main thread, let dtor to
348 // take care of releasing references
349 return;
350 }
351
352 nsTArray<nsCOMPtr<nsISupports>> arrayToRelease;
353 arrayToRelease.AppendElement(mApplicationCacheForWrite.forget());
354 arrayToRelease.AppendElement(mAuthProvider.forget());
355 arrayToRelease.AppendElement(mRedirectURI.forget());
356 arrayToRelease.AppendElement(mRedirectChannel.forget());
357 arrayToRelease.AppendElement(mPreflightChannel.forget());
358
359 NS_DispatchToMainThread(new ProxyReleaseRunnable(Move(arrayToRelease)));
360 }
361
Init(nsIURI * uri,uint32_t caps,nsProxyInfo * proxyInfo,uint32_t proxyResolveFlags,nsIURI * proxyURI,uint64_t channelId)362 nsresult nsHttpChannel::Init(nsIURI *uri, uint32_t caps, nsProxyInfo *proxyInfo,
363 uint32_t proxyResolveFlags, nsIURI *proxyURI,
364 uint64_t channelId) {
365 nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo, proxyResolveFlags,
366 proxyURI, channelId);
367 if (NS_FAILED(rv)) return rv;
368
369 LOG(("nsHttpChannel::Init [this=%p]\n", this));
370
371 return rv;
372 }
373
AddSecurityMessage(const nsAString & aMessageTag,const nsAString & aMessageCategory)374 nsresult nsHttpChannel::AddSecurityMessage(const nsAString &aMessageTag,
375 const nsAString &aMessageCategory) {
376 if (mWarningReporter) {
377 return mWarningReporter->ReportSecurityMessage(aMessageTag,
378 aMessageCategory);
379 }
380 return HttpBaseChannel::AddSecurityMessage(aMessageTag, aMessageCategory);
381 }
382
383 NS_IMETHODIMP
LogBlockedCORSRequest(const nsAString & aMessage)384 nsHttpChannel::LogBlockedCORSRequest(const nsAString &aMessage) {
385 if (mWarningReporter) {
386 return mWarningReporter->LogBlockedCORSRequest(aMessage);
387 }
388 return NS_ERROR_UNEXPECTED;
389 }
390
391 //-----------------------------------------------------------------------------
392 // nsHttpChannel <private>
393 //-----------------------------------------------------------------------------
394
OnBeforeConnect()395 nsresult nsHttpChannel::OnBeforeConnect() {
396 nsresult rv;
397
398 // Note that we are only setting the "Upgrade-Insecure-Requests" request
399 // header for *all* navigational requests instead of all requests as
400 // defined in the spec, see:
401 // https://www.w3.org/TR/upgrade-insecure-requests/#preference
402 nsContentPolicyType type = mLoadInfo
403 ? mLoadInfo->GetExternalContentPolicyType()
404 : nsIContentPolicy::TYPE_OTHER;
405
406 if (type == nsIContentPolicy::TYPE_DOCUMENT ||
407 type == nsIContentPolicy::TYPE_SUBDOCUMENT) {
408 rv = SetRequestHeader(NS_LITERAL_CSTRING("Upgrade-Insecure-Requests"),
409 NS_LITERAL_CSTRING("1"), false);
410 NS_ENSURE_SUCCESS(rv, rv);
411 }
412
413 bool isHttps = false;
414 rv = mURI->SchemeIs("https", &isHttps);
415 NS_ENSURE_SUCCESS(rv, rv);
416 nsCOMPtr<nsIPrincipal> resultPrincipal;
417 if (!isHttps && mLoadInfo) {
418 nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal(
419 this, getter_AddRefs(resultPrincipal));
420 }
421 OriginAttributes originAttributes;
422 if (!NS_GetOriginAttributes(this, originAttributes)) {
423 return NS_ERROR_FAILURE;
424 }
425 bool isHttp = false;
426 rv = mURI->SchemeIs("http", &isHttp);
427 NS_ENSURE_SUCCESS(rv, rv);
428
429 // At this point it is no longer possible to call
430 // HttpBaseChannel::UpgradeToSecure.
431 mUpgradableToSecure = false;
432 if (isHttp) {
433 bool shouldUpgrade = mUpgradeToSecure;
434 if (!shouldUpgrade) {
435 rv = NS_ShouldSecureUpgrade(mURI, mLoadInfo, resultPrincipal,
436 mPrivateBrowsing, mAllowSTS, originAttributes,
437 shouldUpgrade);
438 NS_ENSURE_SUCCESS(rv, rv);
439 }
440 if (shouldUpgrade) {
441 return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
442 }
443 }
444
445 // ensure that we are using a valid hostname
446 if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Origin())))
447 return NS_ERROR_UNKNOWN_HOST;
448
449 if (mUpgradeProtocolCallback) {
450 mCaps |= NS_HTTP_DISALLOW_SPDY;
451 }
452
453 // Finalize ConnectionInfo flags before SpeculativeConnect
454 mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
455 mConnectionInfo->SetPrivate(mPrivateBrowsing);
456 mConnectionInfo->SetNoSpdy(mCaps & NS_HTTP_DISALLOW_SPDY);
457 mConnectionInfo->SetBeConservative((mCaps & NS_HTTP_BE_CONSERVATIVE) ||
458 mBeConservative);
459 mConnectionInfo->SetTlsFlags(mTlsFlags);
460
461 // notify "http-on-before-connect" observers
462 gHttpHandler->OnBeforeConnect(this);
463
464 // Check if request was cancelled during http-on-before-connect.
465 if (mCanceled) {
466 return mStatus;
467 }
468
469 if (mSuspendCount) {
470 // We abandon the connection here if there was one.
471 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
472 MOZ_ASSERT(!mCallOnResume);
473 mCallOnResume = &nsHttpChannel::OnBeforeConnectContinue;
474 return NS_OK;
475 }
476
477 return Connect();
478 }
479
OnBeforeConnectContinue()480 void nsHttpChannel::OnBeforeConnectContinue() {
481 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
482 nsresult rv;
483
484 if (mSuspendCount) {
485 LOG(("Waiting until resume OnBeforeConnect [this=%p]\n", this));
486 mCallOnResume = &nsHttpChannel::OnBeforeConnectContinue;
487 return;
488 }
489
490 LOG(("nsHttpChannel::OnBeforeConnectContinue [this=%p]\n", this));
491 rv = Connect();
492 if (NS_FAILED(rv)) {
493 CloseCacheEntry(false);
494 Unused << AsyncAbort(rv);
495 }
496 }
497
Connect()498 nsresult nsHttpChannel::Connect() {
499 LOG(("nsHttpChannel::Connect [this=%p]\n", this));
500
501 // Don't allow resuming when cache must be used
502 if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
503 LOG(("Resuming from cache is not supported yet"));
504 return NS_ERROR_DOCUMENT_NOT_CACHED;
505 }
506
507 if (ShouldIntercept()) {
508 return RedirectToInterceptedChannel();
509 }
510
511 bool isTrackingResource = mIsTrackingResource; // is atomic
512 LOG(("nsHttpChannel %p tracking resource=%d, cos=%u", this,
513 isTrackingResource, mClassOfService));
514
515 if (isTrackingResource) {
516 AddClassFlags(nsIClassOfService::Tail);
517 }
518
519 if (WaitingForTailUnblock()) {
520 MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
521 mOnTailUnblock = &nsHttpChannel::ConnectOnTailUnblock;
522 return NS_OK;
523 }
524
525 return ConnectOnTailUnblock();
526 }
527
ConnectOnTailUnblock()528 nsresult nsHttpChannel::ConnectOnTailUnblock() {
529 nsresult rv;
530
531 LOG(("nsHttpChannel::ConnectOnTailUnblock [this=%p]\n", this));
532
533 // Consider opening a TCP connection right away.
534 SpeculativeConnect();
535
536 // open a cache entry for this channel...
537 bool isHttps = false;
538 rv = mURI->SchemeIs("https", &isHttps);
539 NS_ENSURE_SUCCESS(rv, rv);
540 rv = OpenCacheEntry(isHttps);
541
542 // do not continue if asyncOpenCacheEntry is in progress
543 if (AwaitingCacheCallbacks()) {
544 LOG(("nsHttpChannel::Connect %p AwaitingCacheCallbacks forces async\n",
545 this));
546 MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
547
548 if (mNetworkTriggered && mWaitingForProxy) {
549 // Someone has called TriggerNetwork(), meaning we are racing the
550 // network with the cache.
551 mWaitingForProxy = false;
552 return ContinueConnect();
553 }
554
555 return NS_OK;
556 }
557
558 if (NS_FAILED(rv)) {
559 LOG(("OpenCacheEntry failed [rv=%" PRIx32 "]\n",
560 static_cast<uint32_t>(rv)));
561 // if this channel is only allowed to pull from the cache, then
562 // we must fail if we were unable to open a cache entry.
563 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
564 // If we have a fallback URI (and we're not already
565 // falling back), process the fallback asynchronously.
566 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
567 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
568 }
569 return NS_ERROR_DOCUMENT_NOT_CACHED;
570 }
571 // otherwise, let's just proceed without using the cache.
572 }
573
574 if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
575 (mDidReval || mCachedContentIsPartial)) ||
576 mIgnoreCacheEntry)) {
577 // We won't send the conditional request because the unconditional
578 // request was already sent (see bug 1377223).
579 AccumulateCategorical(
580 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
581 }
582
583 // When racing, if OnCacheEntryAvailable is called before AsyncOpenURI
584 // returns, then we may not have started reading from the cache.
585 // If the content is valid, we should attempt to do so, as technically the
586 // cache has won the race.
587 if (mRaceCacheWithNetwork && mCachedContentIsValid) {
588 Unused << ReadFromCache(true);
589 }
590
591 return TriggerNetwork();
592 }
593
594 // nsIInputAvailableCallback (nsIStreamTransportService.idl)
595 NS_IMETHODIMP
OnInputAvailableComplete(uint64_t size,nsresult status)596 nsHttpChannel::OnInputAvailableComplete(uint64_t size, nsresult status) {
597 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
598 LOG(("nsHttpChannel::OnInputAvailableComplete %p %" PRIx32 "\n", this,
599 static_cast<uint32_t>(status)));
600 if (NS_SUCCEEDED(status)) {
601 mReqContentLength = size;
602 } else {
603 // fall back to synchronous on the error path. should not happen.
604 if (NS_SUCCEEDED(mUploadStream->Available(&size))) {
605 mReqContentLength = size;
606 }
607 }
608
609 LOG(("nsHttpChannel::DetermineContentLength %p from sts\n", this));
610 mReqContentLengthDetermined = 1;
611 nsresult rv = mCanceled ? mStatus : ContinueConnect();
612 if (NS_FAILED(rv)) {
613 CloseCacheEntry(false);
614 Unused << AsyncAbort(rv);
615 }
616 return NS_OK;
617 }
618
DetermineContentLength()619 void nsHttpChannel::DetermineContentLength() {
620 nsCOMPtr<nsIStreamTransportService> sts(
621 services::GetStreamTransportService());
622
623 if (!mUploadStream || !sts) {
624 LOG(("nsHttpChannel::DetermineContentLength %p no body\n", this));
625 mReqContentLength = 0U;
626 mReqContentLengthDetermined = 1;
627 return;
628 }
629
630 // If this is a stream is blocking, it needs to be sent to a worker thread
631 // to do Available() as it may cause disk/IO.
632 bool nonBlocking = false;
633 if (NS_FAILED(mUploadStream->IsNonBlocking(&nonBlocking)) || nonBlocking) {
634 mUploadStream->Available(&mReqContentLength);
635 LOG(("nsHttpChannel::DetermineContentLength %p from mem\n", this));
636 mReqContentLengthDetermined = 1;
637 return;
638 }
639
640 LOG(("nsHttpChannel::DetermineContentLength Async [this=%p]\n", this));
641 sts->InputAvailable(mUploadStream, this);
642 }
643
ContinueConnect()644 nsresult nsHttpChannel::ContinueConnect() {
645 // If we have a request body that is going to require bouncing to the STS
646 // in order to determine the content-length as doing it on the main thread
647 // will incur file IO some of the time.
648 if (!mReqContentLengthDetermined) {
649 // C-L might be determined sync or async. Sync will set
650 // mReqContentLengthDetermined to true in DetermineContentLength()
651 DetermineContentLength();
652 }
653 if (!mReqContentLengthDetermined) {
654 return NS_OK;
655 }
656
657 // If we need to start a CORS preflight, do it now!
658 // Note that it is important to do this before the early returns below.
659 if (!mIsCorsPreflightDone && mRequireCORSPreflight) {
660 MOZ_ASSERT(!mPreflightChannel);
661 nsresult rv = nsCORSListenerProxy::StartCORSPreflight(
662 this, this, mUnsafeHeaders, getter_AddRefs(mPreflightChannel));
663 return rv;
664 }
665
666 MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
667 "CORS preflight must have been finished by the time we "
668 "do the rest of ContinueConnect");
669
670 // we may or may not have a cache entry at this point
671 if (mCacheEntry) {
672 // read straight from the cache if possible...
673 if (mCachedContentIsValid) {
674 nsRunnableMethod<nsHttpChannel> *event = nullptr;
675 nsresult rv;
676 if (!mCachedContentIsPartial) {
677 rv = AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
678 if (NS_FAILED(rv)) {
679 LOG((" AsyncCall failed (%08x)", static_cast<uint32_t>(rv)));
680 }
681 }
682 rv = ReadFromCache(true);
683 if (NS_FAILED(rv) && event) {
684 event->Revoke();
685 }
686
687 AccumulateCacheHitTelemetry(kCacheHit);
688
689 return rv;
690 } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
691 // the cache contains the requested resource, but it must be
692 // validated before we can reuse it. since we are not allowed
693 // to hit the net, there's nothing more to do. the document
694 // is effectively not in the cache.
695 LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
696 return NS_ERROR_DOCUMENT_NOT_CACHED;
697 }
698 } else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
699 // If we have a fallback URI (and we're not already
700 // falling back), process the fallback asynchronously.
701 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
702 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
703 }
704 LOG((" !mCacheEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
705 return NS_ERROR_DOCUMENT_NOT_CACHED;
706 }
707
708 if (mLoadFlags & LOAD_NO_NETWORK_IO) {
709 LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
710 return NS_ERROR_DOCUMENT_NOT_CACHED;
711 }
712
713 // hit the net...
714 nsresult rv = SetupTransaction();
715 if (NS_FAILED(rv)) return rv;
716
717 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
718 if (NS_FAILED(rv)) return rv;
719
720 rv = mTransactionPump->AsyncRead(this, nullptr);
721 if (NS_FAILED(rv)) return rv;
722
723 uint32_t suspendCount = mSuspendCount;
724 while (suspendCount--) mTransactionPump->Suspend();
725
726 return NS_OK;
727 }
728
SpeculativeConnect()729 void nsHttpChannel::SpeculativeConnect() {
730 // Before we take the latency hit of dealing with the cache, try and
731 // get the TCP (and SSL) handshakes going so they can overlap.
732
733 // don't speculate if we are on uses of the offline application cache,
734 // if we are offline, when doing http upgrade (i.e.
735 // websockets bootstrap), or if we can't do keep-alive (because then we
736 // couldn't reuse the speculative connection anyhow).
737 if (mApplicationCache || gIOService->IsOffline() ||
738 mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
739 return;
740
741 // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
742 // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
743 // so skip preconnects for them.
744 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
745 LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
746 return;
747
748 if (mAllowStaleCacheContent) {
749 return;
750 }
751
752 nsCOMPtr<nsIInterfaceRequestor> callbacks;
753 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
754 getter_AddRefs(callbacks));
755 if (!callbacks) return;
756
757 Unused << gHttpHandler->SpeculativeConnect(mConnectionInfo, callbacks,
758 mCaps & NS_HTTP_DISALLOW_SPDY);
759 }
760
DoNotifyListenerCleanup()761 void nsHttpChannel::DoNotifyListenerCleanup() {
762 // We don't need this info anymore
763 CleanRedirectCacheChainIfNecessary();
764 }
765
ReleaseListeners()766 void nsHttpChannel::ReleaseListeners() {
767 HttpBaseChannel::ReleaseListeners();
768 mChannelClassifier = nullptr;
769 mWarningReporter = nullptr;
770 }
771
HandleAsyncRedirect()772 void nsHttpChannel::HandleAsyncRedirect() {
773 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
774
775 if (mSuspendCount) {
776 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
777 mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
778 return;
779 }
780
781 nsresult rv = NS_OK;
782
783 LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
784
785 // since this event is handled asynchronously, it is possible that this
786 // channel could have been canceled, in which case there would be no point
787 // in processing the redirect.
788 if (NS_SUCCEEDED(mStatus)) {
789 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
790 rv = AsyncProcessRedirection(mResponseHead->Status());
791 if (NS_FAILED(rv)) {
792 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
793 // TODO: if !DoNotRender3xxBody(), render redirect body instead.
794 // But first we need to cache 3xx bodies (bug 748510)
795 rv = ContinueHandleAsyncRedirect(rv);
796 MOZ_ASSERT(NS_SUCCEEDED(rv));
797 }
798 } else {
799 rv = ContinueHandleAsyncRedirect(mStatus);
800 MOZ_ASSERT(NS_SUCCEEDED(rv));
801 }
802 }
803
ContinueHandleAsyncRedirect(nsresult rv)804 nsresult nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv) {
805 if (NS_FAILED(rv)) {
806 // If AsyncProcessRedirection fails, then we have to send out the
807 // OnStart/OnStop notifications.
808 LOG(("ContinueHandleAsyncRedirect got failure result [rv=%" PRIx32 "]\n",
809 static_cast<uint32_t>(rv)));
810
811 bool redirectsEnabled = !mLoadInfo || !mLoadInfo->GetDontFollowRedirects();
812
813 if (redirectsEnabled) {
814 // TODO: stop failing original channel if redirect vetoed?
815 mStatus = rv;
816
817 DoNotifyListener();
818
819 // Blow away cache entry if we couldn't process the redirect
820 // for some reason (the cache entry might be corrupt).
821 if (mCacheEntry) {
822 mCacheEntry->AsyncDoom(nullptr);
823 }
824 } else {
825 DoNotifyListener();
826 }
827 }
828
829 CloseCacheEntry(true);
830
831 mIsPending = false;
832
833 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
834
835 return NS_OK;
836 }
837
HandleAsyncNotModified()838 void nsHttpChannel::HandleAsyncNotModified() {
839 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
840
841 if (mSuspendCount) {
842 LOG(("Waiting until resume to do async not-modified [this=%p]\n", this));
843 mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
844 return;
845 }
846
847 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
848
849 DoNotifyListener();
850
851 CloseCacheEntry(false);
852
853 mIsPending = false;
854
855 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
856 }
857
HandleAsyncFallback()858 void nsHttpChannel::HandleAsyncFallback() {
859 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
860
861 if (mSuspendCount) {
862 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
863 mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
864 return;
865 }
866
867 nsresult rv = NS_OK;
868
869 LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
870
871 // since this event is handled asynchronously, it is possible that this
872 // channel could have been canceled, in which case there would be no point
873 // in processing the fallback.
874 if (!mCanceled) {
875 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
876 bool waitingForRedirectCallback;
877 rv = ProcessFallback(&waitingForRedirectCallback);
878 if (waitingForRedirectCallback) return;
879 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
880 }
881
882 rv = ContinueHandleAsyncFallback(rv);
883 MOZ_ASSERT(NS_SUCCEEDED(rv));
884 }
885
ContinueHandleAsyncFallback(nsresult rv)886 nsresult nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv) {
887 if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
888 // If ProcessFallback fails, then we have to send out the
889 // OnStart/OnStop notifications.
890 LOG(("ProcessFallback failed [rv=%" PRIx32 ", %d]\n",
891 static_cast<uint32_t>(rv), mFallingBack));
892 mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
893 DoNotifyListener();
894 }
895
896 mIsPending = false;
897
898 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, mStatus);
899
900 return rv;
901 }
902
SetupTransaction()903 nsresult nsHttpChannel::SetupTransaction() {
904 LOG(("nsHttpChannel::SetupTransaction [this=%p, cos=%u, prio=%d]\n", this,
905 mClassOfService, mPriority));
906
907 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
908
909 nsresult rv;
910
911 mozilla::MutexAutoLock lock(mRCWNLock);
912
913 // If we're racing cache with network, conditional or byte range header
914 // could be added in OnCacheEntryCheck. We cannot send conditional request
915 // without having the entry, so we need to remove the headers here and
916 // ignore the cache entry in OnCacheEntryAvailable.
917 if (mRaceCacheWithNetwork && AwaitingCacheCallbacks()) {
918 if (mDidReval) {
919 LOG((" Removing conditional request headers"));
920 UntieValidationRequest();
921 mDidReval = false;
922 mIgnoreCacheEntry = true;
923 }
924
925 if (mCachedContentIsPartial) {
926 LOG((" Removing byte range request headers"));
927 UntieByteRangeRequest();
928 mCachedContentIsPartial = false;
929 mIgnoreCacheEntry = true;
930 }
931
932 if (mIgnoreCacheEntry) {
933 if (!mAvailableCachedAltDataType.IsEmpty()) {
934 mAvailableCachedAltDataType.Truncate();
935 mAltDataLength = 0;
936 }
937 mCacheInputStream.CloseAndRelease();
938 }
939 }
940
941 mUsedNetwork = 1;
942
943 if (mTRR) {
944 mCaps |= NS_HTTP_LARGE_KEEPALIVE;
945 }
946 if (!mAllowSpdy) {
947 mCaps |= NS_HTTP_DISALLOW_SPDY;
948 }
949 if (mBeConservative) {
950 mCaps |= NS_HTTP_BE_CONSERVATIVE;
951 }
952
953 // Use the URI path if not proxying (transparent proxying such as proxy
954 // CONNECT does not count here). Also figure out what HTTP version to use.
955 nsAutoCString buf, path;
956 nsCString *requestURI;
957
958 // This is the normal e2e H1 path syntax "/index.html"
959 rv = mURI->GetPathQueryRef(path);
960 if (NS_FAILED(rv)) {
961 return rv;
962 }
963
964 // path may contain UTF-8 characters, so ensure that they're escaped.
965 if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf)) {
966 requestURI = &buf;
967 } else {
968 requestURI = &path;
969 }
970
971 // trim off the #ref portion if any...
972 int32_t ref1 = requestURI->FindChar('#');
973 if (ref1 != kNotFound) {
974 requestURI->SetLength(ref1);
975 }
976
977 if (mConnectionInfo->UsingConnect() || !mConnectionInfo->UsingHttpProxy()) {
978 mRequestHead.SetVersion(gHttpHandler->HttpVersion());
979 } else {
980 mRequestHead.SetPath(*requestURI);
981
982 // RequestURI should be the absolute uri H1 proxy syntax
983 // "http://foo/index.html" so we will overwrite the relative version in
984 // requestURI
985 rv = mURI->GetUserPass(buf);
986 if (NS_FAILED(rv)) return rv;
987 if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
988 strncmp(mSpec.get(), "https:", 6) == 0)) {
989 nsCOMPtr<nsIURI> tempURI;
990 rv = NS_MutateURI(mURI).SetUserPass(EmptyCString()).Finalize(tempURI);
991 if (NS_FAILED(rv)) return rv;
992 rv = tempURI->GetAsciiSpec(path);
993 if (NS_FAILED(rv)) return rv;
994 requestURI = &path;
995 } else {
996 requestURI = &mSpec;
997 }
998
999 // trim off the #ref portion if any...
1000 int32_t ref2 = requestURI->FindChar('#');
1001 if (ref2 != kNotFound) {
1002 requestURI->SetLength(ref2);
1003 }
1004
1005 mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
1006 }
1007
1008 mRequestHead.SetRequestURI(*requestURI);
1009
1010 // set the request time for cache expiration calculations
1011 mRequestTime = NowInSeconds();
1012 mRequestTimeInitialized = true;
1013
1014 // if doing a reload, force end-to-end
1015 if (mLoadFlags & LOAD_BYPASS_CACHE) {
1016 // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
1017 // no proxy is configured since we might be talking with a transparent
1018 // proxy, i.e. one that operates at the network level. See bug #14772.
1019 rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
1020 MOZ_ASSERT(NS_SUCCEEDED(rv));
1021 // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
1022 // no-cache'
1023 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1) {
1024 rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
1025 MOZ_ASSERT(NS_SUCCEEDED(rv));
1026 }
1027 } else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
1028 // We need to send 'Cache-Control: max-age=0' to force each cache along
1029 // the path to the origin server to revalidate its own entry, if any,
1030 // with the next cache or server. See bug #84847.
1031 //
1032 // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
1033 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
1034 rv = mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
1035 else
1036 rv = mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
1037 MOZ_ASSERT(NS_SUCCEEDED(rv));
1038 }
1039
1040 if (mResuming) {
1041 char byteRange[32];
1042 SprintfLiteral(byteRange, "bytes=%" PRIu64 "-", mStartPos);
1043 rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
1044 MOZ_ASSERT(NS_SUCCEEDED(rv));
1045
1046 if (!mEntityID.IsEmpty()) {
1047 // Also, we want an error if this resource changed in the meantime
1048 // Format of the entity id is: escaped_etag/size/lastmod
1049 nsCString::const_iterator start, end, slash;
1050 mEntityID.BeginReading(start);
1051 mEntityID.EndReading(end);
1052 mEntityID.BeginReading(slash);
1053
1054 if (FindCharInReadable('/', slash, end)) {
1055 nsAutoCString ifMatch;
1056 rv = mRequestHead.SetHeader(
1057 nsHttp::If_Match,
1058 NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
1059 MOZ_ASSERT(NS_SUCCEEDED(rv));
1060
1061 ++slash; // Incrementing, so that searching for '/' won't find
1062 // the same slash again
1063 }
1064
1065 if (FindCharInReadable('/', slash, end)) {
1066 rv = mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
1067 Substring(++slash, end));
1068 MOZ_ASSERT(NS_SUCCEEDED(rv));
1069 }
1070 }
1071 }
1072
1073 // create wrapper for this channel's notification callbacks
1074 nsCOMPtr<nsIInterfaceRequestor> callbacks;
1075 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
1076 getter_AddRefs(callbacks));
1077
1078 // create the transaction object
1079 mTransaction = new nsHttpTransaction();
1080 LOG(("nsHttpChannel %p created nsHttpTransaction %p\n", this,
1081 mTransaction.get()));
1082 mTransaction->SetTransactionObserver(mTransactionObserver);
1083 mTransactionObserver = nullptr;
1084
1085 // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
1086 if (mLoadFlags & LOAD_ANONYMOUS) mCaps |= NS_HTTP_LOAD_ANONYMOUS;
1087
1088 if (mTimingEnabled) mCaps |= NS_HTTP_TIMING_ENABLED;
1089
1090 if (mUpgradeProtocolCallback) {
1091 rv = mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
1092 MOZ_ASSERT(NS_SUCCEEDED(rv));
1093 rv = mRequestHead.SetHeaderOnce(nsHttp::Connection, nsHttp::Upgrade.get(),
1094 true);
1095 MOZ_ASSERT(NS_SUCCEEDED(rv));
1096 mCaps |= NS_HTTP_STICKY_CONNECTION;
1097 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
1098 }
1099
1100 if (mPushedStream) {
1101 mTransaction->SetPushedStream(mPushedStream);
1102 mPushedStream = nullptr;
1103 }
1104
1105 nsCOMPtr<nsIHttpPushListener> pushListener;
1106 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
1107 NS_GET_IID(nsIHttpPushListener),
1108 getter_AddRefs(pushListener));
1109 if (pushListener) {
1110 mCaps |= NS_HTTP_ONPUSH_LISTENER;
1111 }
1112
1113 EnsureTopLevelOuterContentWindowId();
1114
1115 nsCOMPtr<nsIAsyncInputStream> responseStream;
1116 rv = mTransaction->Init(
1117 mCaps, mConnectionInfo, &mRequestHead, mUploadStream, mReqContentLength,
1118 mUploadStreamHasHeaders, GetCurrentThreadEventTarget(), callbacks, this,
1119 mTopLevelOuterContentWindowId, getter_AddRefs(responseStream));
1120 if (NS_FAILED(rv)) {
1121 mTransaction = nullptr;
1122 return rv;
1123 }
1124
1125 mTransaction->SetClassOfService(mClassOfService);
1126 if (EnsureRequestContext()) {
1127 mTransaction->SetRequestContext(mRequestContext);
1128 }
1129
1130 rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
1131 responseStream);
1132 return rv;
1133 }
1134
1135 // Helper Function to report messages to the console when loading
1136 // a resource was blocked due to a MIME type mismatch.
ReportTypeBlocking(nsIURI * aURI,nsILoadInfo * aLoadInfo,const char * aMessageName)1137 void ReportTypeBlocking(nsIURI *aURI, nsILoadInfo *aLoadInfo,
1138 const char *aMessageName) {
1139 NS_ConvertUTF8toUTF16 specUTF16(aURI->GetSpecOrDefault());
1140 const char16_t *params[] = {specUTF16.get()};
1141 nsCOMPtr<nsIDocument> doc;
1142 if (aLoadInfo) {
1143 nsCOMPtr<nsIDOMDocument> domDoc;
1144 aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
1145 if (domDoc) {
1146 doc = do_QueryInterface(domDoc);
1147 }
1148 }
1149 nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
1150 NS_LITERAL_CSTRING("MIMEMISMATCH"), doc,
1151 nsContentUtils::eSECURITY_PROPERTIES,
1152 aMessageName, params, ArrayLength(params));
1153 }
1154
1155 // Check and potentially enforce X-Content-Type-Options: nosniff
ProcessXCTO(nsIURI * aURI,nsHttpResponseHead * aResponseHead,nsILoadInfo * aLoadInfo)1156 nsresult ProcessXCTO(nsIURI *aURI, nsHttpResponseHead *aResponseHead,
1157 nsILoadInfo *aLoadInfo) {
1158 if (!aURI || !aResponseHead || !aLoadInfo) {
1159 // if there is no uri, no response head or no loadInfo, then there is
1160 // nothing to do
1161 return NS_OK;
1162 }
1163
1164 // 1) Query the XCTO header and check if 'nosniff' is the first value.
1165 nsAutoCString contentTypeOptionsHeader;
1166 Unused << aResponseHead->GetHeader(nsHttp::X_Content_Type_Options,
1167 contentTypeOptionsHeader);
1168 if (contentTypeOptionsHeader.IsEmpty()) {
1169 // if there is no XCTO header, then there is nothing to do.
1170 return NS_OK;
1171 }
1172 // XCTO header might contain multiple values which are comma separated, so:
1173 // a) let's skip all subsequent values
1174 // e.g. " NoSniFF , foo " will be " NoSniFF "
1175 int32_t idx = contentTypeOptionsHeader.Find(",");
1176 if (idx > 0) {
1177 contentTypeOptionsHeader = Substring(contentTypeOptionsHeader, 0, idx);
1178 }
1179 // b) let's trim all surrounding whitespace
1180 // e.g. " NoSniFF " -> "NoSniFF"
1181 contentTypeOptionsHeader.StripWhitespace();
1182 // c) let's compare the header (ignoring case)
1183 // e.g. "NoSniFF" -> "nosniff"
1184 // if it's not 'nosniff' then there is nothing to do here
1185 if (!contentTypeOptionsHeader.EqualsIgnoreCase("nosniff")) {
1186 // since we are getting here, the XCTO header was sent;
1187 // a non matching value most likely means a mistake happenend;
1188 // e.g. sending 'nosnif' instead of 'nosniff', let's log a warning.
1189 NS_ConvertUTF8toUTF16 char16_header(contentTypeOptionsHeader);
1190 const char16_t *params[] = {char16_header.get()};
1191 nsCOMPtr<nsIDocument> doc;
1192 nsCOMPtr<nsIDOMDocument> domDoc;
1193 aLoadInfo->GetLoadingDocument(getter_AddRefs(domDoc));
1194 if (domDoc) {
1195 doc = do_QueryInterface(domDoc);
1196 }
1197 nsContentUtils::ReportToConsole(
1198 nsIScriptError::warningFlag, NS_LITERAL_CSTRING("XCTO"), doc,
1199 nsContentUtils::eSECURITY_PROPERTIES, "XCTOHeaderValueMissing", params,
1200 ArrayLength(params));
1201 return NS_OK;
1202 }
1203
1204 // 2) Query the content type from the channel
1205 nsAutoCString contentType;
1206 aResponseHead->ContentType(contentType);
1207
1208 // 3) Compare the expected MIME type with the actual type
1209 if (aLoadInfo->GetExternalContentPolicyType() ==
1210 nsIContentPolicy::TYPE_STYLESHEET) {
1211 if (contentType.EqualsLiteral(TEXT_CSS)) {
1212 return NS_OK;
1213 }
1214 ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
1215 return NS_ERROR_CORRUPTED_CONTENT;
1216 }
1217
1218 if (aLoadInfo->GetExternalContentPolicyType() ==
1219 nsIContentPolicy::TYPE_SCRIPT) {
1220 if (nsContentUtils::IsJavascriptMIMEType(
1221 NS_ConvertUTF8toUTF16(contentType))) {
1222 return NS_OK;
1223 }
1224 ReportTypeBlocking(aURI, aLoadInfo, "MimeTypeMismatch");
1225 return NS_ERROR_CORRUPTED_CONTENT;
1226 }
1227 return NS_OK;
1228 }
1229
1230 // Ensure that a load of type script has correct MIME type
EnsureMIMEOfScript(nsIURI * aURI,nsHttpResponseHead * aResponseHead,nsILoadInfo * aLoadInfo)1231 nsresult EnsureMIMEOfScript(nsIURI *aURI, nsHttpResponseHead *aResponseHead,
1232 nsILoadInfo *aLoadInfo) {
1233 if (!aURI || !aResponseHead || !aLoadInfo) {
1234 // if there is no uri, no response head or no loadInfo, then there is
1235 // nothing to do
1236 return NS_OK;
1237 }
1238
1239 if (aLoadInfo->GetExternalContentPolicyType() !=
1240 nsIContentPolicy::TYPE_SCRIPT) {
1241 // if this is not a script load, then there is nothing to do
1242 return NS_OK;
1243 }
1244
1245 nsAutoCString contentType;
1246 aResponseHead->ContentType(contentType);
1247 NS_ConvertUTF8toUTF16 typeString(contentType);
1248
1249 if (nsContentUtils::IsJavascriptMIMEType(typeString)) {
1250 // script load has type script
1251 AccumulateCategorical(
1252 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::javaScript);
1253 return NS_OK;
1254 }
1255
1256 nsCOMPtr<nsIURI> requestURI;
1257 aLoadInfo->LoadingPrincipal()->GetURI(getter_AddRefs(requestURI));
1258
1259 nsIScriptSecurityManager *ssm = nsContentUtils::GetSecurityManager();
1260 nsresult rv = ssm->CheckSameOriginURI(requestURI, aURI, false);
1261 if (NS_SUCCEEDED(rv)) {
1262 // same origin
1263 AccumulateCategorical(
1264 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::same_origin);
1265 } else {
1266 bool cors = false;
1267 nsAutoCString corsOrigin;
1268 rv = aResponseHead->GetHeader(
1269 nsHttp::ResolveAtom("Access-Control-Allow-Origin"), corsOrigin);
1270 if (NS_SUCCEEDED(rv)) {
1271 if (corsOrigin.Equals("*")) {
1272 cors = true;
1273 } else {
1274 nsCOMPtr<nsIURI> corsOriginURI;
1275 rv = NS_NewURI(getter_AddRefs(corsOriginURI), corsOrigin);
1276 if (NS_SUCCEEDED(rv)) {
1277 rv = ssm->CheckSameOriginURI(requestURI, corsOriginURI, false);
1278 if (NS_SUCCEEDED(rv)) {
1279 cors = true;
1280 }
1281 }
1282 }
1283 }
1284 if (cors) {
1285 // cors origin
1286 AccumulateCategorical(
1287 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::CORS_origin);
1288 } else {
1289 // cross origin
1290 AccumulateCategorical(
1291 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::cross_origin);
1292 }
1293 }
1294
1295 bool block = false;
1296 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("image/"))) {
1297 // script load has type image
1298 AccumulateCategorical(
1299 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::image);
1300 block = true;
1301 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("audio/"))) {
1302 // script load has type audio
1303 AccumulateCategorical(
1304 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::audio);
1305 block = true;
1306 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("video/"))) {
1307 // script load has type video
1308 AccumulateCategorical(
1309 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::video);
1310 block = true;
1311 } else if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/csv"))) {
1312 // script load has type text/csv
1313 AccumulateCategorical(
1314 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_csv);
1315 block = true;
1316 }
1317
1318 if (block) {
1319 // Instead of consulting Preferences::GetBool() all the time we
1320 // can cache the result to speed things up.
1321 static bool sCachedBlockScriptWithWrongMime = false;
1322 static bool sIsInited = false;
1323 if (!sIsInited) {
1324 sIsInited = true;
1325 Preferences::AddBoolVarCache(&sCachedBlockScriptWithWrongMime,
1326 "security.block_script_with_wrong_mime");
1327 }
1328
1329 // Do not block the load if the feature is not enabled.
1330 if (!sCachedBlockScriptWithWrongMime) {
1331 return NS_OK;
1332 }
1333
1334 ReportTypeBlocking(aURI, aLoadInfo, "BlockScriptWithWrongMimeType");
1335 return NS_ERROR_CORRUPTED_CONTENT;
1336 }
1337
1338 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/plain"))) {
1339 // script load has type text/plain
1340 AccumulateCategorical(
1341 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_plain);
1342 return NS_OK;
1343 }
1344
1345 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/xml"))) {
1346 // script load has type text/xml
1347 AccumulateCategorical(
1348 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_xml);
1349 return NS_OK;
1350 }
1351
1352 if (StringBeginsWith(contentType,
1353 NS_LITERAL_CSTRING("application/octet-stream"))) {
1354 // script load has type application/octet-stream
1355 AccumulateCategorical(
1356 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::app_octet_stream);
1357 return NS_OK;
1358 }
1359
1360 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("application/xml"))) {
1361 // script load has type application/xml
1362 AccumulateCategorical(
1363 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::app_xml);
1364 return NS_OK;
1365 }
1366
1367 if (StringBeginsWith(contentType, NS_LITERAL_CSTRING("text/html"))) {
1368 // script load has type text/html
1369 AccumulateCategorical(
1370 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::text_html);
1371 return NS_OK;
1372 }
1373
1374 if (contentType.IsEmpty()) {
1375 // script load has no type
1376 AccumulateCategorical(
1377 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::empty);
1378 return NS_OK;
1379 }
1380
1381 // script load has unknown type
1382 AccumulateCategorical(
1383 Telemetry::LABELS_SCRIPT_BLOCK_INCORRECT_MIME_2::unknown);
1384 return NS_OK;
1385 }
1386
CallOnStartRequest()1387 nsresult nsHttpChannel::CallOnStartRequest() {
1388 LOG(("nsHttpChannel::CallOnStartRequest [this=%p]", this));
1389
1390 MOZ_RELEASE_ASSERT(!mRequireCORSPreflight || mIsCorsPreflightDone,
1391 "CORS preflight must have been finished by the time we "
1392 "call OnStartRequest");
1393
1394 if (mOnStartRequestCalled) {
1395 // This can only happen when a range request loading rest of the data
1396 // after interrupted concurrent cache read asynchronously failed, e.g.
1397 // the response range bytes are not as expected or this channel has
1398 // been externally canceled.
1399 //
1400 // It's legal to bypass CallOnStartRequest for that case since we've
1401 // already called OnStartRequest on our listener and also added all
1402 // content converters before.
1403 MOZ_ASSERT(mConcurrentCacheAccess);
1404 LOG(("CallOnStartRequest already invoked before"));
1405 return mStatus;
1406 }
1407
1408 mTracingEnabled = false;
1409
1410 // Ensure mListener->OnStartRequest will be invoked before exiting
1411 // this function.
1412 auto onStartGuard = MakeScopeExit([&] {
1413 LOG(
1414 (" calling mListener->OnStartRequest by ScopeExit [this=%p, "
1415 "listener=%p]\n",
1416 this, mListener.get()));
1417 MOZ_ASSERT(!mOnStartRequestCalled);
1418
1419 if (mListener) {
1420 nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
1421 deleteProtector->OnStartRequest(this, mListenerContext);
1422 }
1423
1424 mOnStartRequestCalled = true;
1425 });
1426
1427 nsresult rv = EnsureMIMEOfScript(mURI, mResponseHead, mLoadInfo);
1428 NS_ENSURE_SUCCESS(rv, rv);
1429
1430 rv = ProcessXCTO(mURI, mResponseHead, mLoadInfo);
1431 NS_ENSURE_SUCCESS(rv, rv);
1432
1433 // Allow consumers to override our content type
1434 if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
1435 // NOTE: We can have both a txn pump and a cache pump when the cache
1436 // content is partial. In that case, we need to read from the cache,
1437 // because that's the one that has the initial contents. If that fails
1438 // then give the transaction pump a shot.
1439
1440 nsIChannel *thisChannel = static_cast<nsIChannel *>(this);
1441
1442 bool typeSniffersCalled = false;
1443 if (mCachePump) {
1444 typeSniffersCalled =
1445 NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
1446 }
1447
1448 if (!typeSniffersCalled && mTransactionPump) {
1449 mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
1450 }
1451 }
1452
1453 bool unknownDecoderStarted = false;
1454 if (mResponseHead && !mResponseHead->HasContentType()) {
1455 MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
1456 if (!mContentTypeHint.IsEmpty())
1457 mResponseHead->SetContentType(mContentTypeHint);
1458 else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 &&
1459 mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort())
1460 mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
1461 else {
1462 // Uh-oh. We had better find out what type we are!
1463 nsCOMPtr<nsIStreamConverterService> serv;
1464 rv = gHttpHandler->GetStreamConverterService(getter_AddRefs(serv));
1465 // If we failed, we just fall through to the "normal" case
1466 if (NS_SUCCEEDED(rv)) {
1467 nsCOMPtr<nsIStreamListener> converter;
1468 rv =
1469 serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE, "*/*", mListener,
1470 mListenerContext, getter_AddRefs(converter));
1471 if (NS_SUCCEEDED(rv)) {
1472 mListener = converter;
1473 unknownDecoderStarted = true;
1474 }
1475 }
1476 }
1477 }
1478
1479 if (mResponseHead && !mResponseHead->HasContentCharset())
1480 mResponseHead->SetContentCharset(mContentCharsetHint);
1481
1482 if (mResponseHead && mCacheEntry) {
1483 // If we have a cache entry, set its predicted size to TotalEntitySize to
1484 // avoid caching an entry that will exceed the max size limit.
1485 rv = mCacheEntry->SetPredictedDataSize(mResponseHead->TotalEntitySize());
1486 if (NS_ERROR_FILE_TOO_BIG == rv) {
1487 // Don't throw the entry away, we will need it later.
1488 LOG((" entry too big"));
1489 } else {
1490 NS_ENSURE_SUCCESS(rv, rv);
1491 }
1492 }
1493
1494 LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
1495 mListener.get()));
1496
1497 // About to call OnStartRequest, dismiss the guard object.
1498 onStartGuard.release();
1499
1500 if (mListener) {
1501 MOZ_ASSERT(!mOnStartRequestCalled,
1502 "We should not call OsStartRequest twice");
1503 nsCOMPtr<nsIStreamListener> deleteProtector(mListener);
1504 rv = deleteProtector->OnStartRequest(this, mListenerContext);
1505 mOnStartRequestCalled = true;
1506 if (NS_FAILED(rv)) return rv;
1507 } else {
1508 NS_WARNING("OnStartRequest skipped because of null listener");
1509 mOnStartRequestCalled = true;
1510 }
1511
1512 // Install stream converter if required.
1513 // If we use unknownDecoder, stream converters will be installed later (in
1514 // nsUnknownDecoder) after OnStartRequest is called for the real listener.
1515 if (!unknownDecoderStarted) {
1516 nsCOMPtr<nsIStreamListener> listener;
1517 nsISupports *ctxt = mListenerContext;
1518 rv = DoApplyContentConversions(mListener, getter_AddRefs(listener), ctxt);
1519 if (NS_FAILED(rv)) {
1520 return rv;
1521 }
1522 if (listener) {
1523 mListener = listener;
1524 mCompressListener = listener;
1525 }
1526 }
1527
1528 // if this channel is for a download, close off access to the cache.
1529 if (mCacheEntry && mChannelIsForDownload) {
1530 mCacheEntry->AsyncDoom(nullptr);
1531
1532 // We must keep the cache entry in case of partial request.
1533 // Concurrent access is the same, we need the entry in
1534 // OnStopRequest.
1535 // We also need the cache entry when racing cache with network to find
1536 // out what is the source of the data.
1537 if (!mCachedContentIsPartial && !mConcurrentCacheAccess &&
1538 !(mRaceCacheWithNetwork &&
1539 mFirstResponseSource == RESPONSE_FROM_CACHE)) {
1540 CloseCacheEntry(false);
1541 }
1542 }
1543
1544 if (!mCanceled) {
1545 // create offline cache entry if offline caching was requested
1546 if (ShouldUpdateOfflineCacheEntry()) {
1547 LOG(("writing to the offline cache"));
1548 rv = InitOfflineCacheEntry();
1549 if (NS_FAILED(rv)) return rv;
1550
1551 // InitOfflineCacheEntry may have closed mOfflineCacheEntry
1552 if (mOfflineCacheEntry) {
1553 rv = InstallOfflineCacheListener();
1554 if (NS_FAILED(rv)) return rv;
1555 }
1556 } else if (mApplicationCacheForWrite) {
1557 LOG(("offline cache is up to date, not updating"));
1558 CloseOfflineCacheEntry();
1559 }
1560 }
1561
1562 // Check for a Content-Signature header and inject mediator if the header is
1563 // requested and available.
1564 // If requested (mLoadInfo->GetVerifySignedContent), but not present, or
1565 // present but not valid, fail this channel and return
1566 // NS_ERROR_INVALID_SIGNATURE to indicate a signature error and trigger a
1567 // fallback load in nsDocShell.
1568 // Note that OnStartRequest has already been called on the target stream
1569 // listener at this point. We have to add the listener here that late to
1570 // ensure that it's the last listener and can thus block the load in
1571 // OnStopRequest.
1572 if (!mCanceled) {
1573 rv = ProcessContentSignatureHeader(mResponseHead);
1574 if (NS_FAILED(rv)) {
1575 LOG(("Content-signature verification failed.\n"));
1576 return rv;
1577 }
1578 }
1579
1580 return NS_OK;
1581 }
1582
ProcessFailedProxyConnect(uint32_t httpStatus)1583 nsresult nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus) {
1584 // Failure to set up a proxy tunnel via CONNECT means one of the following:
1585 // 1) Proxy wants authorization, or forbids.
1586 // 2) DNS at proxy couldn't resolve target URL.
1587 // 3) Proxy connection to target failed or timed out.
1588 // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
1589 //
1590 // Our current architecture would parse the proxy's response content with
1591 // the permission of the target URL. Given #4, we must avoid rendering the
1592 // body of the reply, and instead give the user a (hopefully helpful)
1593 // boilerplate error page, based on just the HTTP status of the reply.
1594
1595 MOZ_ASSERT(mConnectionInfo->UsingConnect(),
1596 "proxy connect failed but not using CONNECT?");
1597 nsresult rv;
1598 switch (httpStatus) {
1599 case 300:
1600 case 301:
1601 case 302:
1602 case 303:
1603 case 307:
1604 case 308:
1605 // Bad redirect: not top-level, or it's a POST, bad/missing Location,
1606 // or ProcessRedirect() failed for some other reason. Legal
1607 // redirects that fail because site not available, etc., are handled
1608 // elsewhere, in the regular codepath.
1609 rv = NS_ERROR_CONNECTION_REFUSED;
1610 break;
1611 case 403: // HTTP/1.1: "Forbidden"
1612 case 407: // ProcessAuthentication() failed
1613 case 501: // HTTP/1.1: "Not Implemented"
1614 // user sees boilerplate Mozilla "Proxy Refused Connection" page.
1615 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
1616 break;
1617 // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
1618 case 404: // HTTP/1.1: "Not Found"
1619 // RFC 2616: "some deployed proxies are known to return 400 or 500 when
1620 // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
1621 // we have a conflict here).
1622 case 400: // HTTP/1.1 "Bad Request"
1623 case 500: // HTTP/1.1: "Internal Server Error"
1624 /* User sees: "Address Not Found: Firefox can't find the server at
1625 * www.foo.com."
1626 */
1627 rv = NS_ERROR_UNKNOWN_HOST;
1628 break;
1629 case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
1630 // Squid returns 503 if target request fails for anything but DNS.
1631 case 503: // HTTP/1.1: "Service Unavailable"
1632 /* User sees: "Failed to Connect:
1633 * Firefox can't establish a connection to the server at
1634 * www.foo.com. Though the site seems valid, the browser
1635 * was unable to establish a connection."
1636 */
1637 rv = NS_ERROR_CONNECTION_REFUSED;
1638 break;
1639 // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
1640 // do here: picking target timeout, as DNS covered by 400/404/500
1641 case 504: // HTTP/1.1: "Gateway Timeout"
1642 // user sees: "Network Timeout: The server at www.foo.com
1643 // is taking too long to respond."
1644 rv = NS_ERROR_NET_TIMEOUT;
1645 break;
1646 // Confused proxy server or malicious response
1647 default:
1648 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
1649 break;
1650 }
1651 LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n", this,
1652 httpStatus));
1653 Cancel(rv);
1654 {
1655 nsresult rv = CallOnStartRequest();
1656 if (NS_FAILED(rv)) {
1657 LOG(("CallOnStartRequest failed [this=%p httpStatus=%u rv=%08x]\n", this,
1658 httpStatus, static_cast<uint32_t>(rv)));
1659 }
1660 }
1661 return rv;
1662 }
1663
GetSTSConsoleErrorTag(uint32_t failureResult,nsAString & consoleErrorTag)1664 static void GetSTSConsoleErrorTag(uint32_t failureResult,
1665 nsAString &consoleErrorTag) {
1666 switch (failureResult) {
1667 case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
1668 consoleErrorTag = NS_LITERAL_STRING("STSUntrustworthyConnection");
1669 break;
1670 case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
1671 consoleErrorTag = NS_LITERAL_STRING("STSCouldNotParseHeader");
1672 break;
1673 case nsISiteSecurityService::ERROR_NO_MAX_AGE:
1674 consoleErrorTag = NS_LITERAL_STRING("STSNoMaxAge");
1675 break;
1676 case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
1677 consoleErrorTag = NS_LITERAL_STRING("STSMultipleMaxAges");
1678 break;
1679 case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
1680 consoleErrorTag = NS_LITERAL_STRING("STSInvalidMaxAge");
1681 break;
1682 case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
1683 consoleErrorTag = NS_LITERAL_STRING("STSMultipleIncludeSubdomains");
1684 break;
1685 case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
1686 consoleErrorTag = NS_LITERAL_STRING("STSInvalidIncludeSubdomains");
1687 break;
1688 case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
1689 consoleErrorTag = NS_LITERAL_STRING("STSCouldNotSaveState");
1690 break;
1691 default:
1692 consoleErrorTag = NS_LITERAL_STRING("STSUnknownError");
1693 break;
1694 }
1695 }
1696
GetPKPConsoleErrorTag(uint32_t failureResult,nsAString & consoleErrorTag)1697 static void GetPKPConsoleErrorTag(uint32_t failureResult,
1698 nsAString &consoleErrorTag) {
1699 switch (failureResult) {
1700 case nsISiteSecurityService::ERROR_UNTRUSTWORTHY_CONNECTION:
1701 consoleErrorTag = NS_LITERAL_STRING("PKPUntrustworthyConnection");
1702 break;
1703 case nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER:
1704 consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotParseHeader");
1705 break;
1706 case nsISiteSecurityService::ERROR_NO_MAX_AGE:
1707 consoleErrorTag = NS_LITERAL_STRING("PKPNoMaxAge");
1708 break;
1709 case nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES:
1710 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleMaxAges");
1711 break;
1712 case nsISiteSecurityService::ERROR_INVALID_MAX_AGE:
1713 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidMaxAge");
1714 break;
1715 case nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS:
1716 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleIncludeSubdomains");
1717 break;
1718 case nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS:
1719 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidIncludeSubdomains");
1720 break;
1721 case nsISiteSecurityService::ERROR_INVALID_PIN:
1722 consoleErrorTag = NS_LITERAL_STRING("PKPInvalidPin");
1723 break;
1724 case nsISiteSecurityService::ERROR_MULTIPLE_REPORT_URIS:
1725 consoleErrorTag = NS_LITERAL_STRING("PKPMultipleReportURIs");
1726 break;
1727 case nsISiteSecurityService::ERROR_PINSET_DOES_NOT_MATCH_CHAIN:
1728 consoleErrorTag = NS_LITERAL_STRING("PKPPinsetDoesNotMatch");
1729 break;
1730 case nsISiteSecurityService::ERROR_NO_BACKUP_PIN:
1731 consoleErrorTag = NS_LITERAL_STRING("PKPNoBackupPin");
1732 break;
1733 case nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE:
1734 consoleErrorTag = NS_LITERAL_STRING("PKPCouldNotSaveState");
1735 break;
1736 case nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN:
1737 consoleErrorTag = NS_LITERAL_STRING("PKPRootNotBuiltIn");
1738 break;
1739 default:
1740 consoleErrorTag = NS_LITERAL_STRING("PKPUnknownError");
1741 break;
1742 }
1743 }
1744
1745 /**
1746 * Process a single security header. Only two types are supported: HSTS and
1747 * HPKP.
1748 */
ProcessSingleSecurityHeader(uint32_t aType,nsISSLStatus * aSSLStatus,uint32_t aFlags)1749 nsresult nsHttpChannel::ProcessSingleSecurityHeader(uint32_t aType,
1750 nsISSLStatus *aSSLStatus,
1751 uint32_t aFlags) {
1752 nsHttpAtom atom;
1753 switch (aType) {
1754 case nsISiteSecurityService::HEADER_HSTS:
1755 atom = nsHttp::ResolveAtom("Strict-Transport-Security");
1756 break;
1757 case nsISiteSecurityService::HEADER_HPKP:
1758 atom = nsHttp::ResolveAtom("Public-Key-Pins");
1759 break;
1760 default:
1761 NS_NOTREACHED("Invalid security header type");
1762 return NS_ERROR_FAILURE;
1763 }
1764
1765 nsAutoCString securityHeader;
1766 nsresult rv = mResponseHead->GetHeader(atom, securityHeader);
1767 if (NS_SUCCEEDED(rv)) {
1768 nsISiteSecurityService *sss = gHttpHandler->GetSSService();
1769 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
1770 // Process header will now discard the headers itself if the channel
1771 // wasn't secure (whereas before it had to be checked manually)
1772 OriginAttributes originAttributes;
1773 NS_GetOriginAttributes(this, originAttributes);
1774 uint32_t failureResult;
1775 uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
1776 rv = sss->ProcessHeader(aType, mURI, securityHeader, aSSLStatus, aFlags,
1777 headerSource, originAttributes, nullptr, nullptr,
1778 &failureResult);
1779 if (NS_FAILED(rv)) {
1780 nsAutoString consoleErrorCategory;
1781 nsAutoString consoleErrorTag;
1782 switch (aType) {
1783 case nsISiteSecurityService::HEADER_HSTS:
1784 GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
1785 consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
1786 break;
1787 case nsISiteSecurityService::HEADER_HPKP:
1788 GetPKPConsoleErrorTag(failureResult, consoleErrorTag);
1789 consoleErrorCategory = NS_LITERAL_STRING("Invalid HPKP Headers");
1790 break;
1791 default:
1792 return NS_ERROR_FAILURE;
1793 }
1794 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
1795 LOG(("nsHttpChannel: Failed to parse %s header, continuing load.\n",
1796 atom.get()));
1797 }
1798 } else {
1799 if (rv != NS_ERROR_NOT_AVAILABLE) {
1800 // All other errors are fatal
1801 NS_ENSURE_SUCCESS(rv, rv);
1802 }
1803 LOG(("nsHttpChannel: No %s header, continuing load.\n", atom.get()));
1804 }
1805 return NS_OK;
1806 }
1807
1808 /**
1809 * Decide whether or not to remember Strict-Transport-Security, and whether
1810 * or not to enforce channel integrity.
1811 *
1812 * @return NS_ERROR_FAILURE if there's security information missing even though
1813 * it's an HTTPS connection.
1814 */
ProcessSecurityHeaders()1815 nsresult nsHttpChannel::ProcessSecurityHeaders() {
1816 nsresult rv;
1817 bool isHttps = false;
1818 rv = mURI->SchemeIs("https", &isHttps);
1819 NS_ENSURE_SUCCESS(rv, rv);
1820
1821 // If this channel is not loading securely, STS or PKP doesn't do anything.
1822 // In the case of HSTS, the upgrade to HTTPS takes place earlier in the
1823 // channel load process.
1824 if (!isHttps) return NS_OK;
1825
1826 nsAutoCString asciiHost;
1827 rv = mURI->GetAsciiHost(asciiHost);
1828 NS_ENSURE_SUCCESS(rv, NS_OK);
1829
1830 // If the channel is not a hostname, but rather an IP, do not process STS
1831 // or PKP headers
1832 PRNetAddr hostAddr;
1833 if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
1834 return NS_OK;
1835
1836 // mSecurityInfo may not always be present, and if it's not then it is okay
1837 // to just disregard any security headers since we know nothing about the
1838 // security of the connection.
1839 NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
1840
1841 uint32_t flags =
1842 NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
1843
1844 // Get the SSLStatus
1845 nsCOMPtr<nsISSLStatusProvider> sslprov = do_QueryInterface(mSecurityInfo);
1846 NS_ENSURE_TRUE(sslprov, NS_ERROR_FAILURE);
1847 nsCOMPtr<nsISSLStatus> sslStatus;
1848 rv = sslprov->GetSSLStatus(getter_AddRefs(sslStatus));
1849 NS_ENSURE_SUCCESS(rv, rv);
1850 NS_ENSURE_TRUE(sslStatus, NS_ERROR_FAILURE);
1851
1852 rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HSTS,
1853 sslStatus, flags);
1854 NS_ENSURE_SUCCESS(rv, rv);
1855
1856 rv = ProcessSingleSecurityHeader(nsISiteSecurityService::HEADER_HPKP,
1857 sslStatus, flags);
1858 NS_ENSURE_SUCCESS(rv, rv);
1859
1860 return NS_OK;
1861 }
1862
ProcessContentSignatureHeader(nsHttpResponseHead * aResponseHead)1863 nsresult nsHttpChannel::ProcessContentSignatureHeader(
1864 nsHttpResponseHead *aResponseHead) {
1865 nsresult rv = NS_OK;
1866
1867 // we only do this if we require it in loadInfo
1868 if (!mLoadInfo || !mLoadInfo->GetVerifySignedContent()) {
1869 return NS_OK;
1870 }
1871
1872 NS_ENSURE_TRUE(aResponseHead, NS_ERROR_ABORT);
1873 nsAutoCString contentSignatureHeader;
1874 nsHttpAtom atom = nsHttp::ResolveAtom("Content-Signature");
1875 rv = aResponseHead->GetHeader(atom, contentSignatureHeader);
1876 if (NS_FAILED(rv)) {
1877 LOG(("Content-Signature header is missing but expected."));
1878 DoInvalidateCacheEntry(mURI);
1879 return NS_ERROR_INVALID_SIGNATURE;
1880 }
1881
1882 // if we require a signature but it is empty, fail
1883 if (contentSignatureHeader.IsEmpty()) {
1884 DoInvalidateCacheEntry(mURI);
1885 LOG(("An expected content-signature header is missing.\n"));
1886 return NS_ERROR_INVALID_SIGNATURE;
1887 }
1888
1889 // we ensure a content type here to avoid running into problems with
1890 // content sniffing, which might sniff parts of the content before we can
1891 // verify the signature
1892 if (!aResponseHead->HasContentType()) {
1893 NS_WARNING(
1894 "Empty content type can get us in trouble when verifying "
1895 "content signatures");
1896 return NS_ERROR_INVALID_SIGNATURE;
1897 }
1898 // create a new listener that meadiates the content
1899 RefPtr<ContentVerifier> contentVerifyingMediator =
1900 new ContentVerifier(mListener, mListenerContext);
1901 rv = contentVerifyingMediator->Init(contentSignatureHeader, this,
1902 mListenerContext);
1903 NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_SIGNATURE);
1904 mListener = contentVerifyingMediator;
1905
1906 return NS_OK;
1907 }
1908
1909 /**
1910 * Decide whether or not to send a security report and, if so, give the
1911 * SecurityReporter the information required to send such a report.
1912 */
ProcessSecurityReport(nsresult status)1913 void nsHttpChannel::ProcessSecurityReport(nsresult status) {
1914 uint32_t errorClass;
1915 nsCOMPtr<nsINSSErrorsService> errSvc =
1916 do_GetService("@mozilla.org/nss_errors_service;1");
1917 // getErrorClass will throw a generic NS_ERROR_FAILURE if the error code is
1918 // not in the set of errors covered by the NSS errors service.
1919 nsresult rv = errSvc->GetErrorClass(status, &errorClass);
1920 if (!NS_SUCCEEDED(rv)) {
1921 return;
1922 }
1923
1924 // if the content was not loaded succesfully and we have security info,
1925 // send a TLS error report - we must do this early as other parts of
1926 // OnStopRequest can return early
1927 bool reportingEnabled =
1928 Preferences::GetBool("security.ssl.errorReporting.enabled");
1929 bool reportingAutomatic =
1930 Preferences::GetBool("security.ssl.errorReporting.automatic");
1931 if (!mSecurityInfo || !reportingEnabled || !reportingAutomatic) {
1932 return;
1933 }
1934
1935 nsCOMPtr<nsITransportSecurityInfo> secInfo = do_QueryInterface(mSecurityInfo);
1936 nsCOMPtr<nsISecurityReporter> errorReporter =
1937 do_GetService("@mozilla.org/securityreporter;1");
1938
1939 if (!secInfo || !mURI) {
1940 return;
1941 }
1942
1943 nsAutoCString hostStr;
1944 int32_t port;
1945 rv = mURI->GetHost(hostStr);
1946 if (!NS_SUCCEEDED(rv)) {
1947 return;
1948 }
1949
1950 rv = mURI->GetPort(&port);
1951
1952 if (NS_SUCCEEDED(rv)) {
1953 errorReporter->ReportTLSError(secInfo, hostStr, port);
1954 }
1955 }
1956
IsHTTPS()1957 bool nsHttpChannel::IsHTTPS() {
1958 bool isHttps;
1959 if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps) return false;
1960 return true;
1961 }
1962
ProcessSSLInformation()1963 void nsHttpChannel::ProcessSSLInformation() {
1964 // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
1965 // can be whitelisted for TLS False Start in future sessions. We could
1966 // do the same for DH but its rarity doesn't justify the lookup.
1967
1968 if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo || !IsHTTPS() ||
1969 mPrivateBrowsing)
1970 return;
1971
1972 nsCOMPtr<nsISSLStatusProvider> statusProvider =
1973 do_QueryInterface(mSecurityInfo);
1974 if (!statusProvider) return;
1975 nsCOMPtr<nsISSLStatus> sslstat;
1976 statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
1977 if (!sslstat) return;
1978
1979 nsCOMPtr<nsITransportSecurityInfo> securityInfo =
1980 do_QueryInterface(mSecurityInfo);
1981 uint32_t state;
1982 if (securityInfo && NS_SUCCEEDED(securityInfo->GetSecurityState(&state)) &&
1983 (state & nsIWebProgressListener::STATE_IS_BROKEN)) {
1984 // Send weak crypto warnings to the web console
1985 if (state & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) {
1986 nsString consoleErrorTag = NS_LITERAL_STRING("WeakCipherSuiteWarning");
1987 nsString consoleErrorCategory = NS_LITERAL_STRING("SSL");
1988 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorCategory);
1989 }
1990 }
1991
1992 // Send (SHA-1) signature algorithm errors to the web console
1993 nsCOMPtr<nsIX509Cert> cert;
1994 sslstat->GetServerCert(getter_AddRefs(cert));
1995 if (cert) {
1996 UniqueCERTCertificate nssCert(cert->GetCert());
1997 if (nssCert) {
1998 SECOidTag tag = SECOID_GetAlgorithmTag(&nssCert->signature);
1999 LOG(("Checking certificate signature: The OID tag is %i [this=%p]\n", tag,
2000 this));
2001 // Check to see if the signature is sha-1 based.
2002 // Not including checks for SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE
2003 // from http://tools.ietf.org/html/rfc2437#section-8 since I
2004 // can't see reference to it outside this spec
2005 if (tag == SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION ||
2006 tag == SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST ||
2007 tag == SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE) {
2008 nsString consoleErrorTag = NS_LITERAL_STRING("SHA1Sig");
2009 nsString consoleErrorMessage = NS_LITERAL_STRING("SHA-1 Signature");
2010 Unused << AddSecurityMessage(consoleErrorTag, consoleErrorMessage);
2011 }
2012 }
2013 }
2014 }
2015
ProcessAltService()2016 void nsHttpChannel::ProcessAltService() {
2017 // e.g. Alt-Svc: h2=":443"; ma=60
2018 // e.g. Alt-Svc: h2="otherhost:443"
2019 // Alt-Svc = 1#( alternative *( OWS ";" OWS parameter ) )
2020 // alternative = protocol-id "=" alt-authority
2021 // protocol-id = token ; percent-encoded ALPN protocol identifier
2022 // alt-authority = quoted-string ; containing [ uri-host ] ":" port
2023
2024 if (!mAllowAltSvc) { // per channel opt out
2025 return;
2026 }
2027
2028 if (!gHttpHandler->AllowAltSvc() || (mCaps & NS_HTTP_DISALLOW_SPDY)) {
2029 return;
2030 }
2031
2032 nsAutoCString scheme;
2033 mURI->GetScheme(scheme);
2034 bool isHttp = scheme.EqualsLiteral("http");
2035 if (!isHttp && !scheme.EqualsLiteral("https")) {
2036 return;
2037 }
2038
2039 nsAutoCString altSvc;
2040 Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, altSvc);
2041 if (altSvc.IsEmpty()) {
2042 return;
2043 }
2044
2045 if (!nsHttp::IsReasonableHeaderValue(altSvc)) {
2046 LOG(("Alt-Svc Response Header seems unreasonable - skipping\n"));
2047 return;
2048 }
2049
2050 nsAutoCString originHost;
2051 int32_t originPort = 80;
2052 mURI->GetPort(&originPort);
2053 if (NS_FAILED(mURI->GetHost(originHost))) {
2054 return;
2055 }
2056
2057 nsCOMPtr<nsIInterfaceRequestor> callbacks;
2058 nsCOMPtr<nsProxyInfo> proxyInfo;
2059 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
2060 getter_AddRefs(callbacks));
2061 if (mProxyInfo) {
2062 proxyInfo = do_QueryInterface(mProxyInfo);
2063 }
2064
2065 OriginAttributes originAttributes;
2066 NS_GetOriginAttributes(this, originAttributes);
2067
2068 AltSvcMapping::ProcessHeader(
2069 altSvc, scheme, originHost, originPort, mUsername, mPrivateBrowsing,
2070 callbacks, proxyInfo, mCaps & NS_HTTP_DISALLOW_SPDY, originAttributes);
2071 }
2072
ProcessResponse()2073 nsresult nsHttpChannel::ProcessResponse() {
2074 uint32_t httpStatus = mResponseHead->Status();
2075
2076 LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n", this,
2077 httpStatus));
2078
2079 // Gather data on whether the transaction and page (if this is
2080 // the initial page load) is being loaded with SSL.
2081 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
2082 mConnectionInfo->EndToEndSSL());
2083 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
2084 Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
2085 mConnectionInfo->EndToEndSSL());
2086 }
2087
2088 if (gHttpHandler->IsTelemetryEnabled()) {
2089 // how often do we see something like Alt-Svc: "443:quic,p=1"
2090 nsAutoCString alt_service;
2091 Unused << mResponseHead->GetHeader(nsHttp::Alternate_Service, alt_service);
2092 bool saw_quic =
2093 (!alt_service.IsEmpty() && PL_strstr(alt_service.get(), "quic")) ? 1
2094 : 0;
2095 Telemetry::Accumulate(Telemetry::HTTP_SAW_QUIC_ALT_PROTOCOL, saw_quic);
2096
2097 // Gather data on how many URLS get redirected
2098 switch (httpStatus) {
2099 case 200:
2100 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 0);
2101 break;
2102 case 301:
2103 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 1);
2104 break;
2105 case 302:
2106 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 2);
2107 break;
2108 case 304:
2109 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 3);
2110 break;
2111 case 307:
2112 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 4);
2113 break;
2114 case 308:
2115 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 5);
2116 break;
2117 case 400:
2118 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 6);
2119 break;
2120 case 401:
2121 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 7);
2122 break;
2123 case 403:
2124 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 8);
2125 break;
2126 case 404:
2127 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 9);
2128 break;
2129 case 500:
2130 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 10);
2131 break;
2132 default:
2133 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_STATUS_CODE, 11);
2134 break;
2135 }
2136 }
2137
2138 // Let the predictor know whether this was a cacheable response or not so
2139 // that it knows whether or not to possibly prefetch this resource in the
2140 // future.
2141 // We use GetReferringPage because mReferrer may not be set at all, or may
2142 // not be a full URI (HttpBaseChannel::SetReferrer has the gorey details).
2143 // If that's null, though, we'll fall back to mReferrer just in case (this
2144 // is especially useful in xpcshell tests, where we don't have an actual
2145 // pageload to get a referrer from).
2146 nsCOMPtr<nsIURI> referrer = GetReferringPage();
2147 if (!referrer) {
2148 referrer = mReferrer;
2149 }
2150 if (referrer) {
2151 nsCOMPtr<nsILoadContextInfo> lci = GetLoadContextInfo(this);
2152 mozilla::net::Predictor::UpdateCacheability(referrer, mURI, httpStatus,
2153 mRequestHead, mResponseHead,
2154 lci, mIsTrackingResource);
2155 }
2156
2157 // Only allow 407 (authentication required) to continue
2158 if (mTransaction && mTransaction->ProxyConnectFailed() && httpStatus != 407) {
2159 return ProcessFailedProxyConnect(httpStatus);
2160 }
2161
2162 MOZ_ASSERT(!mCachedContentIsValid || mRaceCacheWithNetwork,
2163 "We should not be hitting the network if we have valid cached "
2164 "content unless we are racing the network and cache");
2165
2166 ProcessSSLInformation();
2167
2168 // notify "http-on-examine-response" observers
2169 gHttpHandler->OnExamineResponse(this);
2170
2171 return ContinueProcessResponse1();
2172 }
2173
AsyncContinueProcessResponse()2174 void nsHttpChannel::AsyncContinueProcessResponse() {
2175 nsresult rv;
2176 rv = ContinueProcessResponse1();
2177 if (NS_FAILED(rv)) {
2178 // A synchronous failure here would normally be passed as the return
2179 // value from OnStartRequest, which would in turn cancel the request.
2180 // If we're continuing asynchronously, we need to cancel the request
2181 // ourselves.
2182 Unused << Cancel(rv);
2183 }
2184 }
2185
ContinueProcessResponse1()2186 nsresult nsHttpChannel::ContinueProcessResponse1() {
2187 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
2188 nsresult rv;
2189
2190 if (mSuspendCount) {
2191 LOG(("Waiting until resume to finish processing response [this=%p]\n",
2192 this));
2193 mCallOnResume = &nsHttpChannel::AsyncContinueProcessResponse;
2194 return NS_OK;
2195 }
2196
2197 // Check if request was cancelled during http-on-examine-response.
2198 if (mCanceled) {
2199 return CallOnStartRequest();
2200 }
2201
2202 uint32_t httpStatus = mResponseHead->Status();
2203
2204 // STS, Cookies and Alt-Service should not be handled on proxy failure.
2205 // If proxy CONNECT response needs to complete, wait to process connection
2206 // for Strict-Transport-Security.
2207 if (!(mTransaction && mTransaction->ProxyConnectFailed()) &&
2208 (httpStatus != 407)) {
2209 nsAutoCString cookie;
2210 if (NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
2211 SetCookie(cookie.get());
2212 }
2213
2214 // Given a successful connection, process any STS or PKP data that's
2215 // relevant.
2216 DebugOnly<nsresult> rv = ProcessSecurityHeaders();
2217 MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
2218
2219 if ((httpStatus < 500) && (httpStatus != 421)) {
2220 ProcessAltService();
2221 }
2222 }
2223
2224 if (mConcurrentCacheAccess && mCachedContentIsPartial && httpStatus != 206) {
2225 LOG(
2226 (" only expecting 206 when doing partial request during "
2227 "interrupted cache concurrent read"));
2228 return NS_ERROR_CORRUPTED_CONTENT;
2229 }
2230
2231 // handle unused username and password in url (see bug 232567)
2232 if (httpStatus != 401 && httpStatus != 407) {
2233 if (!mAuthRetryPending) {
2234 rv = mAuthProvider->CheckForSuperfluousAuth();
2235 if (NS_FAILED(rv)) {
2236 LOG((" CheckForSuperfluousAuth failed (%08x)",
2237 static_cast<uint32_t>(rv)));
2238 }
2239 }
2240 if (mCanceled) return CallOnStartRequest();
2241
2242 // reset the authentication's current continuation state because our
2243 // last authentication attempt has been completed successfully
2244 rv = mAuthProvider->Disconnect(NS_ERROR_ABORT);
2245 if (NS_FAILED(rv)) {
2246 LOG((" Disconnect failed (%08x)", static_cast<uint32_t>(rv)));
2247 }
2248 mAuthProvider = nullptr;
2249 LOG((" continuation state has been reset"));
2250 }
2251
2252 if (mAPIRedirectToURI && !mCanceled) {
2253 MOZ_ASSERT(!mOnStartRequestCalled);
2254 nsCOMPtr<nsIURI> redirectTo;
2255 mAPIRedirectToURI.swap(redirectTo);
2256
2257 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
2258 rv = StartRedirectChannelToURI(redirectTo,
2259 nsIChannelEventSink::REDIRECT_TEMPORARY);
2260 if (NS_SUCCEEDED(rv)) {
2261 return NS_OK;
2262 }
2263 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse2);
2264 }
2265
2266 // Hack: ContinueProcessResponse2 uses NS_OK to detect successful
2267 // redirects, so we distinguish this codepath (a non-redirect that's
2268 // processing normally) by passing in a bogus error code.
2269 return ContinueProcessResponse2(NS_BINDING_FAILED);
2270 }
2271
ContinueProcessResponse2(nsresult rv)2272 nsresult nsHttpChannel::ContinueProcessResponse2(nsresult rv) {
2273 LOG(("nsHttpChannel::ContinueProcessResponse1 [this=%p, rv=%" PRIx32 "]",
2274 this, static_cast<uint32_t>(rv)));
2275
2276 if (NS_SUCCEEDED(rv)) {
2277 // redirectTo() has passed through, we don't want to go on with
2278 // this channel. It will now be canceled by the redirect handling
2279 // code that called this function.
2280 return NS_OK;
2281 }
2282
2283 rv = NS_OK;
2284
2285 uint32_t httpStatus = mResponseHead->Status();
2286
2287 bool successfulReval = false;
2288 bool partialContentUsed = false;
2289
2290 // handle different server response categories. Note that we handle
2291 // caching or not caching of error pages in
2292 // nsHttpResponseHead::MustValidate; if you change this switch, update that
2293 // one
2294 switch (httpStatus) {
2295 case 200:
2296 case 203:
2297 // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
2298 // So if a server does that and sends 200 instead of 206 that we
2299 // expect, notify our caller.
2300 // However, if we wanted to start from the beginning, let it go through
2301 if (mResuming && mStartPos != 0) {
2302 LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
2303 Cancel(NS_ERROR_NOT_RESUMABLE);
2304 rv = CallOnStartRequest();
2305 break;
2306 }
2307 // these can normally be cached
2308 rv = ProcessNormal();
2309 MaybeInvalidateCacheEntryForSubsequentGet();
2310 break;
2311 case 206:
2312 if (mCachedContentIsPartial) { // an internal byte range request...
2313 rv = ProcessPartialContent();
2314 if (NS_SUCCEEDED(rv)) {
2315 partialContentUsed = true;
2316 }
2317 } else {
2318 mCacheInputStream.CloseAndRelease();
2319 rv = ProcessNormal();
2320 }
2321 break;
2322 case 300:
2323 case 301:
2324 case 302:
2325 case 307:
2326 case 308:
2327 case 303:
2328 #if 0
2329 case 305: // disabled as a security measure (see bug 187996).
2330 #endif
2331 // don't store the response body for redirects
2332 MaybeInvalidateCacheEntryForSubsequentGet();
2333 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
2334 rv = AsyncProcessRedirection(httpStatus);
2335 if (NS_FAILED(rv)) {
2336 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse3);
2337 LOG(("AsyncProcessRedirection failed [rv=%" PRIx32 "]\n",
2338 static_cast<uint32_t>(rv)));
2339 // don't cache failed redirect responses.
2340 if (mCacheEntry) mCacheEntry->AsyncDoom(nullptr);
2341 if (DoNotRender3xxBody(rv)) {
2342 mStatus = rv;
2343 DoNotifyListener();
2344 } else {
2345 rv = ContinueProcessResponse3(rv);
2346 }
2347 }
2348 break;
2349 case 304:
2350 if (!ShouldBypassProcessNotModified()) {
2351 rv = ProcessNotModified();
2352 if (NS_SUCCEEDED(rv)) {
2353 successfulReval = true;
2354 break;
2355 }
2356
2357 LOG(("ProcessNotModified failed [rv=%" PRIx32 "]\n",
2358 static_cast<uint32_t>(rv)));
2359
2360 // We cannot read from the cache entry, it might be in an
2361 // incosistent state. Doom it and redirect the channel
2362 // to the same URI to reload from the network.
2363 mCacheInputStream.CloseAndRelease();
2364 if (mCacheEntry) {
2365 mCacheEntry->AsyncDoom(nullptr);
2366 mCacheEntry = nullptr;
2367 }
2368
2369 rv = StartRedirectChannelToURI(mURI,
2370 nsIChannelEventSink::REDIRECT_INTERNAL);
2371 if (NS_SUCCEEDED(rv)) {
2372 return NS_OK;
2373 }
2374 }
2375
2376 // Don't cache uninformative 304
2377 if (mCustomConditionalRequest) {
2378 CloseCacheEntry(false);
2379 }
2380
2381 if (ShouldBypassProcessNotModified() || NS_FAILED(rv)) {
2382 rv = ProcessNormal();
2383 }
2384 break;
2385 case 401:
2386 case 407:
2387 if (MOZ_UNLIKELY(mCustomAuthHeader) && httpStatus == 401) {
2388 // When a custom auth header fails, we don't want to try
2389 // any cached credentials, nor we want to ask the user.
2390 // It's up to the consumer to re-try w/o setting a custom
2391 // auth header if cached credentials should be attempted.
2392 rv = NS_ERROR_FAILURE;
2393 } else {
2394 rv = mAuthProvider->ProcessAuthentication(
2395 httpStatus, mConnectionInfo->EndToEndSSL() && mTransaction &&
2396 mTransaction->ProxyConnectFailed());
2397 }
2398 if (rv == NS_ERROR_IN_PROGRESS) {
2399 // authentication prompt has been invoked and result
2400 // is expected asynchronously
2401 mAuthRetryPending = true;
2402 if (httpStatus == 407 ||
2403 (mTransaction && mTransaction->ProxyConnectFailed()))
2404 mProxyAuthPending = true;
2405
2406 // suspend the transaction pump to stop receiving the
2407 // unauthenticated content data. We will throw that data
2408 // away when user provides credentials or resume the pump
2409 // when user refuses to authenticate.
2410 LOG(
2411 ("Suspending the transaction, asynchronously prompting for "
2412 "credentials"));
2413 mTransactionPump->Suspend();
2414 rv = NS_OK;
2415 } else if (NS_FAILED(rv)) {
2416 LOG(("ProcessAuthentication failed [rv=%" PRIx32 "]\n",
2417 static_cast<uint32_t>(rv)));
2418 if (mTransaction && mTransaction->ProxyConnectFailed()) {
2419 return ProcessFailedProxyConnect(httpStatus);
2420 }
2421 if (!mAuthRetryPending) {
2422 rv = mAuthProvider->CheckForSuperfluousAuth();
2423 if (NS_FAILED(rv)) {
2424 LOG(("CheckForSuperfluousAuth failed [rv=%x]\n",
2425 static_cast<uint32_t>(rv)));
2426 }
2427 }
2428 rv = ProcessNormal();
2429 } else {
2430 mAuthRetryPending = true; // see DoAuthRetry
2431 }
2432 break;
2433
2434 case 425:
2435 // Do not cache 425.
2436 CloseCacheEntry(false);
2437 MOZ_FALLTHROUGH; // process normally
2438 default:
2439 rv = ProcessNormal();
2440 MaybeInvalidateCacheEntryForSubsequentGet();
2441 break;
2442 }
2443
2444 if (mRaceDelay && !mRaceCacheWithNetwork &&
2445 (mCachedContentIsPartial || mDidReval)) {
2446 if (successfulReval || partialContentUsed) {
2447 AccumulateCategorical(
2448 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::CachedContentUsed);
2449 } else {
2450 AccumulateCategorical(Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::
2451 CachedContentNotUsed);
2452 }
2453 }
2454
2455 if (gHttpHandler->IsTelemetryEnabled()) {
2456 CacheDisposition cacheDisposition;
2457 if (!mDidReval) {
2458 cacheDisposition = kCacheMissed;
2459 } else if (successfulReval) {
2460 cacheDisposition = kCacheHitViaReval;
2461 } else {
2462 cacheDisposition = kCacheMissedViaReval;
2463 }
2464 AccumulateCacheHitTelemetry(cacheDisposition);
2465
2466 Telemetry::Accumulate(Telemetry::HTTP_RESPONSE_VERSION,
2467 mResponseHead->Version());
2468
2469 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
2470 // DefaultPortTopLevel = 0, DefaultPortSubResource = 1,
2471 // NonDefaultPortTopLevel = 2, NonDefaultPortSubResource = 3
2472 uint32_t v09Info = 0;
2473 if (!(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)) {
2474 v09Info += 1;
2475 }
2476 if (mConnectionInfo->OriginPort() != mConnectionInfo->DefaultPort()) {
2477 v09Info += 2;
2478 }
2479 Telemetry::Accumulate(Telemetry::HTTP_09_INFO, v09Info);
2480 }
2481 }
2482 return rv;
2483 }
2484
ContinueProcessResponse3(nsresult rv)2485 nsresult nsHttpChannel::ContinueProcessResponse3(nsresult rv) {
2486 bool doNotRender = DoNotRender3xxBody(rv);
2487
2488 if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
2489 bool isHTTP = false;
2490 if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP))) isHTTP = false;
2491 if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
2492 isHTTP = false;
2493
2494 if (!isHTTP) {
2495 // This was a blocked attempt to redirect and subvert the system by
2496 // redirecting to another protocol (perhaps javascript:)
2497 // In that case we want to throw an error instead of displaying the
2498 // non-redirected response body.
2499 LOG(("ContinueProcessResponse3 detected rejected Non-HTTP Redirection"));
2500 doNotRender = true;
2501 rv = NS_ERROR_CORRUPTED_CONTENT;
2502 }
2503 }
2504
2505 if (doNotRender) {
2506 Cancel(rv);
2507 DoNotifyListener();
2508 return rv;
2509 }
2510
2511 if (NS_SUCCEEDED(rv)) {
2512 UpdateInhibitPersistentCachingFlag();
2513
2514 rv = InitCacheEntry();
2515 if (NS_FAILED(rv)) {
2516 LOG(
2517 ("ContinueProcessResponse3 "
2518 "failed to init cache entry [rv=%x]\n",
2519 static_cast<uint32_t>(rv)));
2520 }
2521 CloseCacheEntry(false);
2522
2523 if (mApplicationCacheForWrite) {
2524 // Store response in the offline cache
2525 Unused << InitOfflineCacheEntry();
2526 CloseOfflineCacheEntry();
2527 }
2528 return NS_OK;
2529 }
2530
2531 LOG(("ContinueProcessResponse3 got failure result [rv=%" PRIx32 "]\n",
2532 static_cast<uint32_t>(rv)));
2533 if (mTransaction && mTransaction->ProxyConnectFailed()) {
2534 return ProcessFailedProxyConnect(mRedirectType);
2535 }
2536 return ProcessNormal();
2537 }
2538
ProcessNormal()2539 nsresult nsHttpChannel::ProcessNormal() {
2540 nsresult rv;
2541
2542 LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
2543
2544 bool succeeded;
2545 rv = GetRequestSucceeded(&succeeded);
2546 if (NS_SUCCEEDED(rv) && !succeeded) {
2547 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
2548 bool waitingForRedirectCallback;
2549 Unused << ProcessFallback(&waitingForRedirectCallback);
2550 if (waitingForRedirectCallback) {
2551 // The transaction has been suspended by ProcessFallback.
2552 return NS_OK;
2553 }
2554 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
2555 }
2556
2557 return ContinueProcessNormal(NS_OK);
2558 }
2559
ContinueProcessNormal(nsresult rv)2560 nsresult nsHttpChannel::ContinueProcessNormal(nsresult rv) {
2561 LOG(("nsHttpChannel::ContinueProcessNormal [this=%p]", this));
2562
2563 if (NS_FAILED(rv)) {
2564 // Fill the failure status here, we have failed to fall back, thus we
2565 // have to report our status as failed.
2566 mStatus = rv;
2567 DoNotifyListener();
2568 return rv;
2569 }
2570
2571 if (mFallingBack) {
2572 // Do not continue with normal processing, fallback is in
2573 // progress now.
2574 return NS_OK;
2575 }
2576
2577 // if we're here, then any byte-range requests failed to result in a partial
2578 // response. we must clear this flag to prevent BufferPartialContent from
2579 // being called inside our OnDataAvailable (see bug 136678).
2580 mCachedContentIsPartial = false;
2581
2582 ClearBogusContentEncodingIfNeeded();
2583
2584 UpdateInhibitPersistentCachingFlag();
2585
2586 // this must be called before firing OnStartRequest, since http clients,
2587 // such as imagelib, expect our cache entry to already have the correct
2588 // expiration time (bug 87710).
2589 if (mCacheEntry) {
2590 rv = InitCacheEntry();
2591 if (NS_FAILED(rv)) CloseCacheEntry(true);
2592 }
2593
2594 // Check that the server sent us what we were asking for
2595 if (mResuming) {
2596 // Create an entity id from the response
2597 nsAutoCString id;
2598 rv = GetEntityID(id);
2599 if (NS_FAILED(rv)) {
2600 // If creating an entity id is not possible -> error
2601 Cancel(NS_ERROR_NOT_RESUMABLE);
2602 } else if (mResponseHead->Status() != 206 &&
2603 mResponseHead->Status() != 200) {
2604 // Probably 404 Not Found, 412 Precondition Failed or
2605 // 416 Invalid Range -> error
2606 LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
2607 this));
2608 Cancel(NS_ERROR_ENTITY_CHANGED);
2609 }
2610 // If we were passed an entity id, verify it's equal to the server's
2611 else if (!mEntityID.IsEmpty()) {
2612 if (!mEntityID.Equals(id)) {
2613 LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
2614 mEntityID.get(), id.get(), this));
2615 Cancel(NS_ERROR_ENTITY_CHANGED);
2616 }
2617 }
2618 }
2619
2620 rv = CallOnStartRequest();
2621 if (NS_FAILED(rv)) return rv;
2622
2623 // install cache listener if we still have a cache entry open
2624 if (mCacheEntry && !mCacheEntryIsReadOnly) {
2625 rv = InstallCacheListener();
2626 if (NS_FAILED(rv)) return rv;
2627 }
2628
2629 return NS_OK;
2630 }
2631
PromptTempRedirect()2632 nsresult nsHttpChannel::PromptTempRedirect() {
2633 if (!gHttpHandler->PromptTempRedirect()) {
2634 return NS_OK;
2635 }
2636 nsresult rv;
2637 nsCOMPtr<nsIStringBundleService> bundleService =
2638 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
2639 if (NS_FAILED(rv)) return rv;
2640
2641 nsCOMPtr<nsIStringBundle> stringBundle;
2642 rv =
2643 bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
2644 if (NS_FAILED(rv)) return rv;
2645
2646 nsAutoString messageString;
2647 rv = stringBundle->GetStringFromName("RepostFormData", messageString);
2648 if (NS_SUCCEEDED(rv)) {
2649 bool repost = false;
2650
2651 nsCOMPtr<nsIPrompt> prompt;
2652 GetCallback(prompt);
2653 if (!prompt) return NS_ERROR_NO_INTERFACE;
2654
2655 prompt->Confirm(nullptr, messageString.get(), &repost);
2656 if (!repost) return NS_ERROR_FAILURE;
2657 }
2658
2659 return rv;
2660 }
2661
ProxyFailover()2662 nsresult nsHttpChannel::ProxyFailover() {
2663 LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
2664
2665 nsresult rv;
2666
2667 nsCOMPtr<nsIProtocolProxyService> pps =
2668 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
2669 if (NS_FAILED(rv)) return rv;
2670
2671 nsCOMPtr<nsIProxyInfo> pi;
2672 rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
2673 getter_AddRefs(pi));
2674 if (NS_FAILED(rv)) return rv;
2675
2676 // XXXbz so where does this codepath remove us from the loadgroup,
2677 // exactly?
2678 return AsyncDoReplaceWithProxy(pi);
2679 }
2680
HandleAsyncRedirectChannelToHttps()2681 void nsHttpChannel::HandleAsyncRedirectChannelToHttps() {
2682 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
2683
2684 if (mSuspendCount) {
2685 LOG(("Waiting until resume to do async redirect to https [this=%p]\n",
2686 this));
2687 mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
2688 return;
2689 }
2690
2691 nsresult rv = StartRedirectChannelToHttps();
2692 if (NS_FAILED(rv)) {
2693 rv = ContinueAsyncRedirectChannelToURI(rv);
2694 if (NS_FAILED(rv)) {
2695 LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
2696 static_cast<uint32_t>(rv), this));
2697 }
2698 }
2699 }
2700
StartRedirectChannelToHttps()2701 nsresult nsHttpChannel::StartRedirectChannelToHttps() {
2702 LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
2703
2704 nsCOMPtr<nsIURI> upgradedURI;
2705 nsresult rv = NS_GetSecureUpgradedURI(mURI, getter_AddRefs(upgradedURI));
2706 NS_ENSURE_SUCCESS(rv, rv);
2707
2708 return StartRedirectChannelToURI(
2709 upgradedURI, nsIChannelEventSink::REDIRECT_PERMANENT |
2710 nsIChannelEventSink::REDIRECT_STS_UPGRADE);
2711 }
2712
HandleAsyncAPIRedirect()2713 void nsHttpChannel::HandleAsyncAPIRedirect() {
2714 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
2715 NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
2716
2717 if (mSuspendCount) {
2718 LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
2719 mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
2720 return;
2721 }
2722
2723 nsresult rv = StartRedirectChannelToURI(
2724 mAPIRedirectToURI, nsIChannelEventSink::REDIRECT_PERMANENT);
2725 if (NS_FAILED(rv)) {
2726 rv = ContinueAsyncRedirectChannelToURI(rv);
2727 if (NS_FAILED(rv)) {
2728 LOG(("ContinueAsyncRedirectChannelToURI failed (%08x) [this=%p]\n",
2729 static_cast<uint32_t>(rv), this));
2730 }
2731 }
2732 }
2733
StartRedirectChannelToURI(nsIURI * upgradedURI,uint32_t flags)2734 nsresult nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI,
2735 uint32_t flags) {
2736 nsresult rv = NS_OK;
2737 LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
2738
2739 nsCOMPtr<nsIChannel> newChannel;
2740 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
2741 CloneLoadInfoForRedirect(upgradedURI, flags);
2742
2743 nsCOMPtr<nsIIOService> ioService;
2744 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
2745 NS_ENSURE_SUCCESS(rv, rv);
2746
2747 rv = NS_NewChannelInternal(getter_AddRefs(newChannel), upgradedURI,
2748 redirectLoadInfo,
2749 nullptr, // PerformanceStorage
2750 nullptr, // aLoadGroup
2751 nullptr, // aCallbacks
2752 nsIRequest::LOAD_NORMAL, ioService);
2753 NS_ENSURE_SUCCESS(rv, rv);
2754
2755 rv = SetupReplacementChannel(upgradedURI, newChannel, true, flags);
2756 NS_ENSURE_SUCCESS(rv, rv);
2757
2758 // Inform consumers about this fake redirect
2759 mRedirectChannel = newChannel;
2760
2761 PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
2762 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
2763
2764 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
2765
2766 if (NS_FAILED(rv)) {
2767 AutoRedirectVetoNotifier notifier(this);
2768
2769 /* Remove the async call to ContinueAsyncRedirectChannelToURI().
2770 * It is called directly by our callers upon return (to clean up
2771 * the failed redirect). */
2772 PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
2773 }
2774
2775 return rv;
2776 }
2777
ContinueAsyncRedirectChannelToURI(nsresult rv)2778 nsresult nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv) {
2779 LOG(("nsHttpChannel::ContinueAsyncRedirectChannelToURI [this=%p]", this));
2780
2781 // Since we handle mAPIRedirectToURI also after on-examine-response handler
2782 // rather drop it here to avoid any redirect loops, even just hypothetical.
2783 mAPIRedirectToURI = nullptr;
2784
2785 if (NS_SUCCEEDED(rv)) {
2786 rv = OpenRedirectChannel(rv);
2787 }
2788
2789 if (NS_FAILED(rv)) {
2790 // Cancel the channel here, the update to https had been vetoed
2791 // but from the security reasons we have to discard the whole channel
2792 // load.
2793 Cancel(rv);
2794 }
2795
2796 if (mLoadGroup) {
2797 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
2798 }
2799
2800 if (NS_FAILED(rv) && !mCachePump && !mTransactionPump) {
2801 // We have to manually notify the listener because there is not any pump
2802 // that would call our OnStart/StopRequest after resume from waiting for
2803 // the redirect callback.
2804 DoNotifyListener();
2805 }
2806
2807 return rv;
2808 }
2809
OpenRedirectChannel(nsresult rv)2810 nsresult nsHttpChannel::OpenRedirectChannel(nsresult rv) {
2811 AutoRedirectVetoNotifier notifier(this);
2812
2813 // Make sure to do this after we received redirect veto answer,
2814 // i.e. after all sinks had been notified
2815 mRedirectChannel->SetOriginalURI(mOriginalURI);
2816
2817 // open new channel
2818 if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
2819 MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
2820 rv = mRedirectChannel->AsyncOpen2(mListener);
2821 } else {
2822 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
2823 }
2824 NS_ENSURE_SUCCESS(rv, rv);
2825
2826 mStatus = NS_BINDING_REDIRECTED;
2827
2828 notifier.RedirectSucceeded();
2829
2830 ReleaseListeners();
2831
2832 return NS_OK;
2833 }
2834
AsyncDoReplaceWithProxy(nsIProxyInfo * pi)2835 nsresult nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo *pi) {
2836 LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
2837 nsresult rv;
2838
2839 nsCOMPtr<nsIChannel> newChannel;
2840 rv = gHttpHandler->NewProxiedChannel2(mURI, pi, mProxyResolveFlags, mProxyURI,
2841 mLoadInfo, getter_AddRefs(newChannel));
2842 if (NS_FAILED(rv)) return rv;
2843
2844 uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
2845
2846 rv = SetupReplacementChannel(mURI, newChannel, true, flags);
2847 if (NS_FAILED(rv)) return rv;
2848
2849 // Inform consumers about this fake redirect
2850 mRedirectChannel = newChannel;
2851
2852 PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
2853 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
2854
2855 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
2856
2857 if (NS_FAILED(rv)) {
2858 AutoRedirectVetoNotifier notifier(this);
2859 PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
2860 }
2861
2862 return rv;
2863 }
2864
ContinueDoReplaceWithProxy(nsresult rv)2865 nsresult nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv) {
2866 AutoRedirectVetoNotifier notifier(this);
2867
2868 if (NS_FAILED(rv)) return rv;
2869
2870 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
2871
2872 // Make sure to do this after we received redirect veto answer,
2873 // i.e. after all sinks had been notified
2874 mRedirectChannel->SetOriginalURI(mOriginalURI);
2875
2876 // open new channel
2877 if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
2878 MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
2879 rv = mRedirectChannel->AsyncOpen2(mListener);
2880 } else {
2881 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
2882 }
2883 NS_ENSURE_SUCCESS(rv, rv);
2884
2885 mStatus = NS_BINDING_REDIRECTED;
2886
2887 notifier.RedirectSucceeded();
2888
2889 ReleaseListeners();
2890
2891 return rv;
2892 }
2893
ResolveProxy()2894 nsresult nsHttpChannel::ResolveProxy() {
2895 LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
2896
2897 nsresult rv;
2898
2899 nsCOMPtr<nsIProtocolProxyService> pps =
2900 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
2901 if (NS_FAILED(rv)) return rv;
2902
2903 // using the nsIProtocolProxyService2 allows a minor performance
2904 // optimization, but if an add-on has only provided the original interface
2905 // then it is ok to use that version.
2906 nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
2907 if (pps2) {
2908 rv = pps2->AsyncResolve2(this, mProxyResolveFlags, this, nullptr,
2909 getter_AddRefs(mProxyRequest));
2910 } else {
2911 rv = pps->AsyncResolve(static_cast<nsIChannel *>(this), mProxyResolveFlags,
2912 this, nullptr, getter_AddRefs(mProxyRequest));
2913 }
2914
2915 return rv;
2916 }
2917
ResponseWouldVary(nsICacheEntry * entry)2918 bool nsHttpChannel::ResponseWouldVary(nsICacheEntry *entry) {
2919 nsresult rv;
2920 nsAutoCString buf, metaKey;
2921 Unused << mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
2922 if (!buf.IsEmpty()) {
2923 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
2924
2925 // enumerate the elements of the Vary header...
2926 char *val = buf.BeginWriting(); // going to munge buf
2927 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
2928 while (token) {
2929 LOG(
2930 ("nsHttpChannel::ResponseWouldVary [channel=%p] "
2931 "processing %s\n",
2932 this, token));
2933 //
2934 // if "*", then assume response would vary. technically speaking,
2935 // "Vary: header, *" is not permitted, but we allow it anyways.
2936 //
2937 // We hash values of cookie-headers for the following reasons:
2938 //
2939 // 1- cookies can be very large in size
2940 //
2941 // 2- cookies may contain sensitive information. (for parity with
2942 // out policy of not storing Set-cookie headers in the cache
2943 // meta data, we likewise do not want to store cookie headers
2944 // here.)
2945 //
2946 if (*token == '*')
2947 return true; // if we encounter this, just get out of here
2948
2949 // build cache meta data key...
2950 metaKey = prefix + nsDependentCString(token);
2951
2952 // check the last value of the given request header to see if it has
2953 // since changed. if so, then indeed the cached response is invalid.
2954 nsCString lastVal;
2955 entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
2956 LOG(
2957 ("nsHttpChannel::ResponseWouldVary [channel=%p] "
2958 "stored value = \"%s\"\n",
2959 this, lastVal.get()));
2960
2961 // Look for value of "Cookie" in the request headers
2962 nsHttpAtom atom = nsHttp::ResolveAtom(token);
2963 nsAutoCString newVal;
2964 bool hasHeader = NS_SUCCEEDED(mRequestHead.GetHeader(atom, newVal));
2965 if (!lastVal.IsEmpty()) {
2966 // value for this header in cache, but no value in request
2967 if (!hasHeader) {
2968 return true; // yes - response would vary
2969 }
2970
2971 // If this is a cookie-header, stored metadata is not
2972 // the value itself but the hash. So we also hash the
2973 // outgoing value here in order to compare the hashes
2974 nsAutoCString hash;
2975 if (atom == nsHttp::Cookie) {
2976 rv = Hash(newVal.get(), hash);
2977 // If hash failed, be conservative (the cached hash
2978 // exists at this point) and claim response would vary
2979 if (NS_FAILED(rv)) return true;
2980 newVal = hash;
2981
2982 LOG(
2983 ("nsHttpChannel::ResponseWouldVary [this=%p] "
2984 "set-cookie value hashed to %s\n",
2985 this, newVal.get()));
2986 }
2987
2988 if (!newVal.Equals(lastVal)) {
2989 return true; // yes, response would vary
2990 }
2991
2992 } else if (hasHeader) { // old value is empty, but newVal is set
2993 return true;
2994 }
2995
2996 // next token...
2997 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
2998 }
2999 }
3000 return false;
3001 }
3002
3003 // We need to have an implementation of this function just so that we can keep
3004 // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
3005 // to set a member function ptr to a base class function.
HandleAsyncAbort()3006 void nsHttpChannel::HandleAsyncAbort() {
3007 HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
3008 }
3009
3010 //-----------------------------------------------------------------------------
3011 // nsHttpChannel <byte-range>
3012 //-----------------------------------------------------------------------------
3013
IsResumable(int64_t partialLen,int64_t contentLength,bool ignoreMissingPartialLen) const3014 bool nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
3015 bool ignoreMissingPartialLen) const {
3016 bool hasContentEncoding =
3017 mCachedResponseHead->HasHeader(nsHttp::Content_Encoding);
3018
3019 nsAutoCString etag;
3020 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, etag);
3021 bool hasWeakEtag =
3022 !etag.IsEmpty() && StringBeginsWith(etag, NS_LITERAL_CSTRING("W/"));
3023
3024 return (partialLen < contentLength) &&
3025 (partialLen > 0 || ignoreMissingPartialLen) && !hasContentEncoding &&
3026 !hasWeakEtag && mCachedResponseHead->IsResumable() &&
3027 !mCustomConditionalRequest && !mCachedResponseHead->NoStore();
3028 }
3029
MaybeSetupByteRangeRequest(int64_t partialLen,int64_t contentLength,bool ignoreMissingPartialLen)3030 nsresult nsHttpChannel::MaybeSetupByteRangeRequest(
3031 int64_t partialLen, int64_t contentLength, bool ignoreMissingPartialLen) {
3032 // Be pesimistic
3033 mIsPartialRequest = false;
3034
3035 if (!IsResumable(partialLen, contentLength, ignoreMissingPartialLen))
3036 return NS_ERROR_NOT_RESUMABLE;
3037
3038 // looks like a partial entry we can reuse; add If-Range
3039 // and Range headers.
3040 nsresult rv = SetupByteRangeRequest(partialLen);
3041 if (NS_FAILED(rv)) {
3042 // Make the request unconditional again.
3043 UntieByteRangeRequest();
3044 }
3045
3046 return rv;
3047 }
3048
SetupByteRangeRequest(int64_t partialLen)3049 nsresult nsHttpChannel::SetupByteRangeRequest(int64_t partialLen) {
3050 // cached content has been found to be partial, add necessary request
3051 // headers to complete cache entry.
3052
3053 // use strongest validator available...
3054 nsAutoCString val;
3055 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
3056 if (val.IsEmpty())
3057 Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
3058 if (val.IsEmpty()) {
3059 // if we hit this code it means mCachedResponseHead->IsResumable() is
3060 // either broken or not being called.
3061 NS_NOTREACHED("no cache validator");
3062 mIsPartialRequest = false;
3063 return NS_ERROR_FAILURE;
3064 }
3065
3066 char buf[64];
3067 SprintfLiteral(buf, "bytes=%" PRId64 "-", partialLen);
3068
3069 DebugOnly<nsresult> rv;
3070 rv = mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
3071 MOZ_ASSERT(NS_SUCCEEDED(rv));
3072 rv = mRequestHead.SetHeader(nsHttp::If_Range, val);
3073 MOZ_ASSERT(NS_SUCCEEDED(rv));
3074 mIsPartialRequest = true;
3075
3076 return NS_OK;
3077 }
3078
UntieByteRangeRequest()3079 void nsHttpChannel::UntieByteRangeRequest() {
3080 DebugOnly<nsresult> rv;
3081 rv = mRequestHead.ClearHeader(nsHttp::Range);
3082 MOZ_ASSERT(NS_SUCCEEDED(rv));
3083 rv = mRequestHead.ClearHeader(nsHttp::If_Range);
3084 MOZ_ASSERT(NS_SUCCEEDED(rv));
3085 }
3086
ProcessPartialContent()3087 nsresult nsHttpChannel::ProcessPartialContent() {
3088 // ok, we've just received a 206
3089 //
3090 // we need to stream whatever data is in the cache out first, and then
3091 // pick up whatever data is on the wire, writing it into the cache.
3092
3093 LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
3094
3095 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
3096 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
3097
3098 // Make sure to clear bogus content-encodings before looking at the header
3099 ClearBogusContentEncodingIfNeeded();
3100
3101 // Check if the content-encoding we now got is different from the one we
3102 // got before
3103 nsAutoCString contentEncoding, cachedContentEncoding;
3104 // It is possible that there is not such headers
3105 Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
3106 Unused << mCachedResponseHead->GetHeader(nsHttp::Content_Encoding,
3107 cachedContentEncoding);
3108 if (PL_strcasecmp(contentEncoding.get(), cachedContentEncoding.get()) != 0) {
3109 Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
3110 return CallOnStartRequest();
3111 }
3112
3113 nsresult rv;
3114
3115 int64_t cachedContentLength = mCachedResponseHead->ContentLength();
3116 int64_t entitySize = mResponseHead->TotalEntitySize();
3117
3118 nsAutoCString contentRange;
3119 Unused << mResponseHead->GetHeader(nsHttp::Content_Range, contentRange);
3120 LOG(
3121 ("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
3122 "original content-length %" PRId64 ", entity-size %" PRId64
3123 ", content-range %s\n",
3124 this, mTransaction.get(), cachedContentLength, entitySize,
3125 contentRange.get()));
3126
3127 if ((entitySize >= 0) && (cachedContentLength >= 0) &&
3128 (entitySize != cachedContentLength)) {
3129 LOG(
3130 ("nsHttpChannel::ProcessPartialContent [this=%p] "
3131 "206 has different total entity size than the content length "
3132 "of the original partially cached entity.\n",
3133 this));
3134
3135 mCacheEntry->AsyncDoom(nullptr);
3136 Cancel(NS_ERROR_CORRUPTED_CONTENT);
3137 return CallOnStartRequest();
3138 }
3139
3140 if (mConcurrentCacheAccess) {
3141 // We started to read cached data sooner than its write has been done.
3142 // But the concurrent write has not finished completely, so we had to
3143 // do a range request. Now let the content coming from the network
3144 // be presented to consumers and also stored to the cache entry.
3145
3146 rv = InstallCacheListener(mLogicalOffset);
3147 if (NS_FAILED(rv)) return rv;
3148
3149 if (mOfflineCacheEntry) {
3150 rv = InstallOfflineCacheListener(mLogicalOffset);
3151 if (NS_FAILED(rv)) return rv;
3152 }
3153 } else {
3154 // suspend the current transaction
3155 rv = mTransactionPump->Suspend();
3156 if (NS_FAILED(rv)) return rv;
3157 }
3158
3159 // merge any new headers with the cached response headers
3160 rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
3161 if (NS_FAILED(rv)) return rv;
3162
3163 // update the cached response head
3164 nsAutoCString head;
3165 mCachedResponseHead->Flatten(head, true);
3166 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
3167 if (NS_FAILED(rv)) return rv;
3168
3169 // make the cached response be the current response
3170 mResponseHead = Move(mCachedResponseHead);
3171
3172 UpdateInhibitPersistentCachingFlag();
3173
3174 rv = UpdateExpirationTime();
3175 if (NS_FAILED(rv)) return rv;
3176
3177 // notify observers interested in looking at a response that has been
3178 // merged with any cached headers (http-on-examine-merged-response).
3179 gHttpHandler->OnExamineMergedResponse(this);
3180
3181 if (mConcurrentCacheAccess) {
3182 mCachedContentIsPartial = false;
3183 // Leave the mConcurrentCacheAccess flag set, we want to use it
3184 // to prevent duplicate OnStartRequest call on the target listener
3185 // in case this channel is canceled before it gets its OnStartRequest
3186 // from the http transaction.
3187
3188 // Now we continue reading the network response.
3189 } else {
3190 // the cached content is valid, although incomplete.
3191 mCachedContentIsValid = true;
3192 rv = ReadFromCache(false);
3193 }
3194
3195 return rv;
3196 }
3197
OnDoneReadingPartialCacheEntry(bool * streamDone)3198 nsresult nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone) {
3199 nsresult rv;
3200
3201 LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
3202
3203 // by default, assume we would have streamed all data or failed...
3204 *streamDone = true;
3205
3206 // setup cache listener to append to cache entry
3207 int64_t size;
3208 rv = mCacheEntry->GetDataSize(&size);
3209 if (NS_FAILED(rv)) return rv;
3210
3211 rv = InstallCacheListener(size);
3212 if (NS_FAILED(rv)) return rv;
3213
3214 // Entry is valid, do it now, after the output stream has been opened,
3215 // otherwise when done earlier, pending readers would consider the cache
3216 // entry still as partial (CacheEntry::GetDataSize would return the partial
3217 // data size) and consumers would do the conditional request again.
3218 rv = mCacheEntry->SetValid();
3219 if (NS_FAILED(rv)) return rv;
3220
3221 // need to track the logical offset of the data being sent to our listener
3222 mLogicalOffset = size;
3223
3224 // we're now completing the cached content, so we can clear this flag.
3225 // this puts us in the state of a regular download.
3226 mCachedContentIsPartial = false;
3227 // The cache input stream pump is finished, we do not need it any more.
3228 // (see bug 1313923)
3229 mCachePump = nullptr;
3230
3231 // resume the transaction if it exists, otherwise the pipe contained the
3232 // remaining part of the document and we've now streamed all of the data.
3233 if (mTransactionPump) {
3234 rv = mTransactionPump->Resume();
3235 if (NS_SUCCEEDED(rv)) *streamDone = false;
3236 } else
3237 NS_NOTREACHED("no transaction");
3238 return rv;
3239 }
3240
3241 //-----------------------------------------------------------------------------
3242 // nsHttpChannel <cache>
3243 //-----------------------------------------------------------------------------
3244
ShouldBypassProcessNotModified()3245 bool nsHttpChannel::ShouldBypassProcessNotModified() {
3246 if (mCustomConditionalRequest) {
3247 LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
3248 return true;
3249 }
3250
3251 if (!mDidReval) {
3252 LOG(
3253 ("Server returned a 304 response even though we did not send a "
3254 "conditional request"));
3255 return true;
3256 }
3257
3258 return false;
3259 }
3260
ProcessNotModified()3261 nsresult nsHttpChannel::ProcessNotModified() {
3262 nsresult rv;
3263
3264 LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
3265
3266 // Assert ShouldBypassProcessNotModified() has been checked before call to
3267 // ProcessNotModified().
3268 MOZ_ASSERT(!ShouldBypassProcessNotModified());
3269
3270 MOZ_ASSERT(mCachedResponseHead);
3271 MOZ_ASSERT(mCacheEntry);
3272 NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
3273
3274 // If the 304 response contains a Last-Modified different than the
3275 // one in our cache that is pretty suspicious and is, in at least the
3276 // case of bug 716840, a sign of the server having previously corrupted
3277 // our cache with a bad response. Take the minor step here of just dooming
3278 // that cache entry so there is a fighting chance of getting things on the
3279 // right track.
3280
3281 nsAutoCString lastModifiedCached;
3282 nsAutoCString lastModified304;
3283
3284 rv =
3285 mCachedResponseHead->GetHeader(nsHttp::Last_Modified, lastModifiedCached);
3286 if (NS_SUCCEEDED(rv)) {
3287 rv = mResponseHead->GetHeader(nsHttp::Last_Modified, lastModified304);
3288 }
3289
3290 if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
3291 LOG(
3292 ("Cache Entry and 304 Last-Modified Headers Do Not Match "
3293 "[%s] and [%s]\n",
3294 lastModifiedCached.get(), lastModified304.get()));
3295
3296 mCacheEntry->AsyncDoom(nullptr);
3297 Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
3298 }
3299
3300 // merge any new headers with the cached response headers
3301 rv = mCachedResponseHead->UpdateHeaders(mResponseHead);
3302 if (NS_FAILED(rv)) return rv;
3303
3304 // update the cached response head
3305 nsAutoCString head;
3306 mCachedResponseHead->Flatten(head, true);
3307 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
3308 if (NS_FAILED(rv)) return rv;
3309
3310 // make the cached response be the current response
3311 mResponseHead = Move(mCachedResponseHead);
3312
3313 UpdateInhibitPersistentCachingFlag();
3314
3315 rv = UpdateExpirationTime();
3316 if (NS_FAILED(rv)) return rv;
3317
3318 rv = AddCacheEntryHeaders(mCacheEntry);
3319 if (NS_FAILED(rv)) return rv;
3320
3321 // notify observers interested in looking at a reponse that has been
3322 // merged with any cached headers
3323 gHttpHandler->OnExamineMergedResponse(this);
3324
3325 mCachedContentIsValid = true;
3326
3327 // Tell other consumers the entry is OK to use
3328 rv = mCacheEntry->SetValid();
3329 if (NS_FAILED(rv)) return rv;
3330
3331 rv = ReadFromCache(false);
3332 if (NS_FAILED(rv)) return rv;
3333
3334 mTransactionReplaced = true;
3335 return NS_OK;
3336 }
3337
ProcessFallback(bool * waitingForRedirectCallback)3338 nsresult nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback) {
3339 LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
3340 nsresult rv;
3341
3342 *waitingForRedirectCallback = false;
3343 mFallingBack = false;
3344
3345 // At this point a load has failed (either due to network problems
3346 // or an error returned on the server). Perform an application
3347 // cache fallback if we have a URI to fall back to.
3348 if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
3349 LOG((" choosing not to fallback [%p,%s,%d]", mApplicationCache.get(),
3350 mFallbackKey.get(), mFallbackChannel));
3351 return NS_OK;
3352 }
3353
3354 // Make sure the fallback entry hasn't been marked as a foreign
3355 // entry.
3356 uint32_t fallbackEntryType;
3357 rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
3358 NS_ENSURE_SUCCESS(rv, rv);
3359
3360 if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
3361 // This cache points to a fallback that refers to a different
3362 // manifest. Refuse to fall back.
3363 return NS_OK;
3364 }
3365
3366 if (!IsInSubpathOfAppCacheManifest(mApplicationCache, mFallbackKey)) {
3367 // Refuse to fallback if the fallback key is not contained in the same
3368 // path as the cache manifest.
3369 return NS_OK;
3370 }
3371
3372 MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
3373 "Fallback entry not marked correctly!");
3374
3375 // Kill any offline cache entry, and disable offline caching for the
3376 // fallback.
3377 if (mOfflineCacheEntry) {
3378 mOfflineCacheEntry->AsyncDoom(nullptr);
3379 mOfflineCacheEntry = nullptr;
3380 }
3381
3382 mApplicationCacheForWrite = nullptr;
3383 mOfflineCacheEntry = nullptr;
3384
3385 // Close the current cache entry.
3386 CloseCacheEntry(true);
3387
3388 // Create a new channel to load the fallback entry.
3389 RefPtr<nsIChannel> newChannel;
3390 rv = gHttpHandler->NewChannel2(mURI, mLoadInfo, getter_AddRefs(newChannel));
3391 NS_ENSURE_SUCCESS(rv, rv);
3392
3393 uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
3394 rv = SetupReplacementChannel(mURI, newChannel, true, redirectFlags);
3395 NS_ENSURE_SUCCESS(rv, rv);
3396
3397 // Make sure the new channel loads from the fallback key.
3398 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
3399 do_QueryInterface(newChannel, &rv);
3400 NS_ENSURE_SUCCESS(rv, rv);
3401
3402 rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
3403 NS_ENSURE_SUCCESS(rv, rv);
3404
3405 // ... and fallbacks should only load from the cache.
3406 uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
3407 rv = newChannel->SetLoadFlags(newLoadFlags);
3408
3409 // Inform consumers about this fake redirect
3410 mRedirectChannel = newChannel;
3411
3412 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
3413 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
3414
3415 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
3416
3417 if (NS_FAILED(rv)) {
3418 AutoRedirectVetoNotifier notifier(this);
3419 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
3420 return rv;
3421 }
3422
3423 // Indicate we are now waiting for the asynchronous redirect callback
3424 // if all went OK.
3425 *waitingForRedirectCallback = true;
3426 return NS_OK;
3427 }
3428
ContinueProcessFallback(nsresult rv)3429 nsresult nsHttpChannel::ContinueProcessFallback(nsresult rv) {
3430 AutoRedirectVetoNotifier notifier(this);
3431
3432 if (NS_FAILED(rv)) return rv;
3433
3434 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
3435
3436 // Make sure to do this after we received redirect veto answer,
3437 // i.e. after all sinks had been notified
3438 mRedirectChannel->SetOriginalURI(mOriginalURI);
3439
3440 if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
3441 MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
3442 rv = mRedirectChannel->AsyncOpen2(mListener);
3443 } else {
3444 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
3445 }
3446 NS_ENSURE_SUCCESS(rv, rv);
3447
3448 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
3449 MaybeWarnAboutAppCache();
3450 }
3451
3452 // close down this channel
3453 Cancel(NS_BINDING_REDIRECTED);
3454
3455 notifier.RedirectSucceeded();
3456
3457 ReleaseListeners();
3458
3459 mFallingBack = true;
3460
3461 return NS_OK;
3462 }
3463
3464 // Determines if a request is a byte range request for a subrange,
3465 // i.e. is a byte range request, but not a 0- byte range request.
IsSubRangeRequest(nsHttpRequestHead & aRequestHead)3466 static bool IsSubRangeRequest(nsHttpRequestHead &aRequestHead) {
3467 nsAutoCString byteRange;
3468 if (NS_FAILED(aRequestHead.GetHeader(nsHttp::Range, byteRange))) {
3469 return false;
3470 }
3471 return !byteRange.EqualsLiteral("bytes=0-");
3472 }
3473
OpenCacheEntry(bool isHttps)3474 nsresult nsHttpChannel::OpenCacheEntry(bool isHttps) {
3475 // Drop this flag here
3476 mConcurrentCacheAccess = 0;
3477
3478 mLoadedFromApplicationCache = false;
3479 mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
3480
3481 LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
3482
3483 // make sure we're not abusing this function
3484 NS_PRECONDITION(!mCacheEntry, "cache entry already open");
3485
3486 if (mRequestHead.IsPost()) {
3487 // If the post id is already set then this is an attempt to replay
3488 // a post transaction via the cache. Otherwise, we need a unique
3489 // post id for this transaction.
3490 if (mPostID == 0) mPostID = gHttpHandler->GenerateUniqueID();
3491 } else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
3492 // don't use the cache for other types of requests
3493 return NS_OK;
3494 }
3495
3496 // Pick up an application cache from the notification
3497 // callbacks if available and if we are not an intercepted channel.
3498 if (!mApplicationCache && mInheritApplicationCache) {
3499 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
3500 GetCallback(appCacheContainer);
3501
3502 if (appCacheContainer) {
3503 appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
3504 }
3505 }
3506
3507 return OpenCacheEntryInternal(isHttps, mApplicationCache, true);
3508 }
3509
OpenCacheEntryInternal(bool isHttps,nsIApplicationCache * applicationCache,bool allowApplicationCache)3510 nsresult nsHttpChannel::OpenCacheEntryInternal(
3511 bool isHttps, nsIApplicationCache *applicationCache,
3512 bool allowApplicationCache) {
3513 MOZ_ASSERT_IF(!allowApplicationCache, !applicationCache);
3514
3515 nsresult rv;
3516
3517 if (mResuming) {
3518 // We don't support caching for requests initiated
3519 // via nsIResumableChannel.
3520 return NS_OK;
3521 }
3522
3523 // Don't cache byte range requests which are subranges, only cache 0-
3524 // byte range requests.
3525 if (IsSubRangeRequest(mRequestHead)) {
3526 return NS_OK;
3527 }
3528
3529 // Handle correctly mCacheEntriesToWaitFor
3530 AutoCacheWaitFlags waitFlags(this);
3531
3532 nsAutoCString cacheKey;
3533 nsAutoCString extension;
3534
3535 nsCOMPtr<nsICacheStorageService> cacheStorageService(
3536 services::GetCacheStorageService());
3537 if (!cacheStorageService) {
3538 return NS_ERROR_NOT_AVAILABLE;
3539 }
3540
3541 nsCOMPtr<nsICacheStorage> cacheStorage;
3542 nsCOMPtr<nsIURI> openURI;
3543 if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
3544 // This is a fallback channel, open fallback URI instead
3545 rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
3546 NS_ENSURE_SUCCESS(rv, rv);
3547 } else {
3548 openURI = mURI;
3549 }
3550
3551 RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
3552 if (!info) {
3553 return NS_ERROR_FAILURE;
3554 }
3555
3556 uint32_t cacheEntryOpenFlags;
3557 bool offline = gIOService->IsOffline();
3558
3559 bool maybeRCWN = false;
3560
3561 nsAutoCString cacheControlRequestHeader;
3562 Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
3563 cacheControlRequestHeader);
3564 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
3565 if (cacheControlRequest.NoStore()) {
3566 goto bypassCacheEntryOpen;
3567 }
3568
3569 if (offline || (mLoadFlags & INHIBIT_CACHING)) {
3570 if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) {
3571 goto bypassCacheEntryOpen;
3572 }
3573 cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
3574 mCacheEntryIsReadOnly = true;
3575 } else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !applicationCache) {
3576 cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
3577 } else {
3578 cacheEntryOpenFlags =
3579 nsICacheStorage::OPEN_NORMALLY | nsICacheStorage::CHECK_MULTITHREADED;
3580 }
3581
3582 // Remember the request is a custom conditional request so that we can
3583 // process any 304 response correctly.
3584 mCustomConditionalRequest =
3585 mRequestHead.HasHeader(nsHttp::If_Modified_Since) ||
3586 mRequestHead.HasHeader(nsHttp::If_None_Match) ||
3587 mRequestHead.HasHeader(nsHttp::If_Unmodified_Since) ||
3588 mRequestHead.HasHeader(nsHttp::If_Match) ||
3589 mRequestHead.HasHeader(nsHttp::If_Range);
3590
3591 if (!mPostID && applicationCache) {
3592 rv = cacheStorageService->AppCacheStorage(info, applicationCache,
3593 getter_AddRefs(cacheStorage));
3594 } else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
3595 rv = cacheStorageService->MemoryCacheStorage(
3596 info, // ? choose app cache as well...
3597 getter_AddRefs(cacheStorage));
3598 } else if (mPinCacheContent) {
3599 rv = cacheStorageService->PinningCacheStorage(info,
3600 getter_AddRefs(cacheStorage));
3601 } else {
3602 bool lookupAppCache =
3603 (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE)) &&
3604 !mPostID && MOZ_LIKELY(allowApplicationCache);
3605 // Try to race only if we use disk cache storage and we don't lookup
3606 // app cache first
3607 maybeRCWN = (!lookupAppCache) && mRequestHead.IsSafeMethod();
3608 rv = cacheStorageService->DiskCacheStorage(info, lookupAppCache,
3609 getter_AddRefs(cacheStorage));
3610 }
3611 NS_ENSURE_SUCCESS(rv, rv);
3612
3613 if ((mClassOfService & nsIClassOfService::Leader) ||
3614 (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
3615 cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
3616
3617 // Only for backward compatibility with the old cache back end.
3618 // When removed, remove the flags and related code snippets.
3619 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
3620 cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
3621
3622 if (mPostID) {
3623 extension.Append(nsPrintfCString("%d", mPostID));
3624 }
3625 if (mTRR) {
3626 extension.Append("TRR");
3627 }
3628
3629 mCacheOpenWithPriority = cacheEntryOpenFlags & nsICacheStorage::OPEN_PRIORITY;
3630 mCacheQueueSizeWhenOpen =
3631 CacheStorageService::CacheQueueSize(mCacheOpenWithPriority);
3632
3633 if (sRCWNEnabled && maybeRCWN && !mApplicationCacheForWrite) {
3634 bool hasAltData = false;
3635 uint32_t sizeInKb = 0;
3636 rv = cacheStorage->GetCacheIndexEntryAttrs(openURI, extension, &hasAltData,
3637 &sizeInKb);
3638
3639 // We will attempt to race the network vs the cache if we've found
3640 // this entry in the cache index, and it has appropriate attributes
3641 // (doesn't have alt-data, and has a small size)
3642 if (NS_SUCCEEDED(rv) && !hasAltData &&
3643 sizeInKb < sRCWNSmallResourceSizeKB) {
3644 MaybeRaceCacheWithNetwork();
3645 }
3646 }
3647
3648 if (!mCacheOpenDelay) {
3649 MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
3650 if (mNetworkTriggered) {
3651 mRaceCacheWithNetwork = sRCWNEnabled;
3652 }
3653 rv = cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags,
3654 this);
3655 } else {
3656 // We pass `this` explicitly as a parameter due to the raw pointer
3657 // to refcounted object in lambda analysis.
3658 mCacheOpenFunc = [openURI, extension, cacheEntryOpenFlags,
3659 cacheStorage](nsHttpChannel *self) -> void {
3660 MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread");
3661 if (self->mNetworkTriggered) {
3662 self->mRaceCacheWithNetwork = true;
3663 }
3664 cacheStorage->AsyncOpenURI(openURI, extension, cacheEntryOpenFlags, self);
3665 };
3666
3667 // calls nsHttpChannel::Notify after `mCacheOpenDelay` milliseconds
3668 NS_NewTimerWithCallback(getter_AddRefs(mCacheOpenTimer), this,
3669 mCacheOpenDelay, nsITimer::TYPE_ONE_SHOT);
3670 }
3671 NS_ENSURE_SUCCESS(rv, rv);
3672
3673 waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
3674
3675 bypassCacheEntryOpen:
3676 if (!mApplicationCacheForWrite || !allowApplicationCache) return NS_OK;
3677
3678 // If there is an app cache to write to, open the entry right now in parallel.
3679
3680 // make sure we're not abusing this function
3681 NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
3682
3683 if (offline) {
3684 // only put things in the offline cache while online
3685 return NS_OK;
3686 }
3687
3688 if (mLoadFlags & INHIBIT_CACHING) {
3689 // respect demand not to cache
3690 return NS_OK;
3691 }
3692
3693 if (!mRequestHead.IsGet()) {
3694 // only cache complete documents offline
3695 return NS_OK;
3696 }
3697
3698 rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
3699 getter_AddRefs(cacheStorage));
3700 NS_ENSURE_SUCCESS(rv, rv);
3701
3702 rv = cacheStorage->AsyncOpenURI(mURI, EmptyCString(),
3703 nsICacheStorage::OPEN_TRUNCATE, this);
3704 NS_ENSURE_SUCCESS(rv, rv);
3705
3706 waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
3707
3708 return NS_OK;
3709 }
3710
CheckPartial(nsICacheEntry * aEntry,int64_t * aSize,int64_t * aContentLength)3711 nsresult nsHttpChannel::CheckPartial(nsICacheEntry *aEntry, int64_t *aSize,
3712 int64_t *aContentLength) {
3713 return nsHttp::CheckPartial(
3714 aEntry, aSize, aContentLength,
3715 mCachedResponseHead ? mCachedResponseHead : mResponseHead);
3716 }
3717
UntieValidationRequest()3718 void nsHttpChannel::UntieValidationRequest() {
3719 DebugOnly<nsresult> rv;
3720 // Make the request unconditional again.
3721 rv = mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
3722 MOZ_ASSERT(NS_SUCCEEDED(rv));
3723 rv = mRequestHead.ClearHeader(nsHttp::If_None_Match);
3724 MOZ_ASSERT(NS_SUCCEEDED(rv));
3725 rv = mRequestHead.ClearHeader(nsHttp::ETag);
3726 MOZ_ASSERT(NS_SUCCEEDED(rv));
3727 }
3728
3729 NS_IMETHODIMP
OnCacheEntryCheck(nsICacheEntry * entry,nsIApplicationCache * appCache,uint32_t * aResult)3730 nsHttpChannel::OnCacheEntryCheck(nsICacheEntry *entry,
3731 nsIApplicationCache *appCache,
3732 uint32_t *aResult) {
3733 nsresult rv = NS_OK;
3734
3735 LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]", this,
3736 entry));
3737
3738 mozilla::MutexAutoLock lock(mRCWNLock);
3739
3740 if (mRaceCacheWithNetwork && mFirstResponseSource == RESPONSE_FROM_NETWORK) {
3741 LOG(
3742 ("Not using cached response because we've already got one from the "
3743 "network\n"));
3744 *aResult = ENTRY_NOT_WANTED;
3745
3746 // Net-win indicates that mOnStartRequestTimestamp is from net.
3747 int64_t savedTime =
3748 (TimeStamp::Now() - mOnStartRequestTimestamp).ToMilliseconds();
3749 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME,
3750 savedTime);
3751 return NS_OK;
3752 } else if (mRaceCacheWithNetwork &&
3753 mFirstResponseSource == RESPONSE_PENDING) {
3754 mOnCacheEntryCheckTimestamp = TimeStamp::Now();
3755 }
3756
3757 nsAutoCString cacheControlRequestHeader;
3758 Unused << mRequestHead.GetHeader(nsHttp::Cache_Control,
3759 cacheControlRequestHeader);
3760 CacheControlParser cacheControlRequest(cacheControlRequestHeader);
3761
3762 if (cacheControlRequest.NoStore()) {
3763 LOG(
3764 ("Not using cached response based on no-store request cache "
3765 "directive\n"));
3766 *aResult = ENTRY_NOT_WANTED;
3767 return NS_OK;
3768 }
3769
3770 // Be pessimistic: assume the cache entry has no useful data.
3771 *aResult = ENTRY_WANTED;
3772 mCachedContentIsValid = false;
3773
3774 nsCString buf;
3775
3776 // Get the method that was used to generate the cached response
3777 rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
3778 NS_ENSURE_SUCCESS(rv, rv);
3779
3780 bool methodWasHead = buf.EqualsLiteral("HEAD");
3781 bool methodWasGet = buf.EqualsLiteral("GET");
3782
3783 if (methodWasHead) {
3784 // The cached response does not contain an entity. We can only reuse
3785 // the response if the current request is also HEAD.
3786 if (!mRequestHead.IsHead()) {
3787 return NS_OK;
3788 }
3789 }
3790 buf.Adopt(0);
3791
3792 // We'll need this value in later computations...
3793 uint32_t lastModifiedTime;
3794 rv = entry->GetLastModified(&lastModifiedTime);
3795 NS_ENSURE_SUCCESS(rv, rv);
3796
3797 // Determine if this is the first time that this cache entry
3798 // has been accessed during this session.
3799 bool fromPreviousSession =
3800 (gHttpHandler->SessionStartTime() > lastModifiedTime);
3801
3802 // Get the cached HTTP response headers
3803 mCachedResponseHead = new nsHttpResponseHead();
3804
3805 rv = nsHttp::GetHttpResponseHeadFromCacheEntry(entry, mCachedResponseHead);
3806 NS_ENSURE_SUCCESS(rv, rv);
3807
3808 bool isCachedRedirect = WillRedirect(mCachedResponseHead);
3809
3810 // Do not return 304 responses from the cache, and also do not return
3811 // any other non-redirect 3xx responses from the cache (see bug 759043).
3812 NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) || isCachedRedirect,
3813 NS_ERROR_ABORT);
3814
3815 if (mCachedResponseHead->NoStore() && mCacheEntryIsReadOnly) {
3816 // This prevents loading no-store responses when navigating back
3817 // while the browser is set to work offline.
3818 LOG((" entry loading as read-only but is no-store, set INHIBIT_CACHING"));
3819 mLoadFlags |= nsIRequest::INHIBIT_CACHING;
3820 }
3821
3822 // Don't bother to validate items that are read-only,
3823 // unless they are read-only because of INHIBIT_CACHING or because
3824 // we're updating the offline cache.
3825 // Don't bother to validate if this is a fallback entry.
3826 if (!mApplicationCacheForWrite &&
3827 (appCache ||
3828 (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
3829 mFallbackChannel)) {
3830 rv = OpenCacheInputStream(entry, true, !!appCache);
3831 if (NS_SUCCEEDED(rv)) {
3832 mCachedContentIsValid = true;
3833 entry->MaybeMarkValid();
3834 }
3835 return rv;
3836 }
3837
3838 bool wantCompleteEntry = false;
3839
3840 if (!methodWasHead && !isCachedRedirect) {
3841 // If the cached content-length is set and it does not match the data
3842 // size of the cached content, then the cached response is partial...
3843 // either we need to issue a byte range request or we need to refetch
3844 // the entire document.
3845 //
3846 // We exclude redirects from this check because we (usually) strip the
3847 // entity when we store the cache entry, and even if we didn't, we
3848 // always ignore a cached redirect's entity anyway. See bug 759043.
3849 int64_t size, contentLength;
3850 rv = CheckPartial(entry, &size, &contentLength);
3851 NS_ENSURE_SUCCESS(rv, rv);
3852
3853 if (size == int64_t(-1)) {
3854 LOG((" write is in progress"));
3855 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
3856 LOG(
3857 (" not interested in the entry, "
3858 "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
3859
3860 *aResult = ENTRY_NOT_WANTED;
3861 return NS_OK;
3862 }
3863
3864 // Ignore !(size > 0) from the resumability condition
3865 if (!IsResumable(size, contentLength, true)) {
3866 if (IsNavigation()) {
3867 LOG(
3868 (" bypassing wait for the entry, "
3869 "this is a navigational load"));
3870 *aResult = ENTRY_NOT_WANTED;
3871 return NS_OK;
3872 }
3873
3874 LOG(
3875 (" wait for entry completion, "
3876 "response is not resumable"));
3877
3878 wantCompleteEntry = true;
3879 } else {
3880 mConcurrentCacheAccess = 1;
3881 }
3882 } else if (contentLength != int64_t(-1) && contentLength != size) {
3883 LOG(
3884 ("Cached data size does not match the Content-Length header "
3885 "[content-length=%" PRId64 " size=%" PRId64 "]\n",
3886 contentLength, size));
3887
3888 rv = MaybeSetupByteRangeRequest(size, contentLength);
3889 mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
3890 if (mCachedContentIsPartial) {
3891 rv = OpenCacheInputStream(entry, false, !!appCache);
3892 if (NS_FAILED(rv)) {
3893 UntieByteRangeRequest();
3894 return rv;
3895 }
3896
3897 *aResult = ENTRY_NEEDS_REVALIDATION;
3898 return NS_OK;
3899 }
3900
3901 if (size == 0 && mCacheOnlyMetadata) {
3902 // Don't break cache entry load when the entry's data size
3903 // is 0 and mCacheOnlyMetadata flag is set. In that case we
3904 // want to proceed since the LOAD_ONLY_IF_MODIFIED flag is
3905 // also set.
3906 MOZ_ASSERT(mLoadFlags & LOAD_ONLY_IF_MODIFIED);
3907 } else {
3908 return rv;
3909 }
3910 }
3911 }
3912
3913 bool isHttps = false;
3914 rv = mURI->SchemeIs("https", &isHttps);
3915 NS_ENSURE_SUCCESS(rv, rv);
3916
3917 bool doValidation = false;
3918 bool canAddImsHeader = true;
3919
3920 bool isForcedValid = false;
3921 entry->GetIsForcedValid(&isForcedValid);
3922
3923 bool weaklyFramed, isImmutable;
3924 nsHttp::DetermineFramingAndImmutability(entry, mCachedResponseHead, isHttps,
3925 &weaklyFramed, &isImmutable);
3926
3927 // Cached entry is not the entity we request (see bug #633743)
3928 if (ResponseWouldVary(entry)) {
3929 LOG(("Validating based on Vary headers returning TRUE\n"));
3930 canAddImsHeader = false;
3931 doValidation = true;
3932 } else {
3933 doValidation = nsHttp::ValidationRequired(
3934 isForcedValid, mCachedResponseHead, mLoadFlags, mAllowStaleCacheContent,
3935 isImmutable, mCustomConditionalRequest, mRequestHead, entry,
3936 cacheControlRequest, fromPreviousSession);
3937 }
3938
3939 // If a content signature is expected to be valid in this load,
3940 // set doValidation to force a signature check.
3941 if (!doValidation && mLoadInfo && mLoadInfo->GetVerifySignedContent()) {
3942 doValidation = true;
3943 }
3944
3945 nsAutoCString requestedETag;
3946 if (!doValidation &&
3947 NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::If_Match, requestedETag)) &&
3948 (methodWasGet || methodWasHead)) {
3949 nsAutoCString cachedETag;
3950 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, cachedETag);
3951 if (!cachedETag.IsEmpty() &&
3952 (StringBeginsWith(cachedETag, NS_LITERAL_CSTRING("W/")) ||
3953 !requestedETag.Equals(cachedETag))) {
3954 // User has defined If-Match header, if the cached entry is not
3955 // matching the provided header value or the cached ETag is weak,
3956 // force validation.
3957 doValidation = true;
3958 }
3959 }
3960
3961 // Previous error should not be propagated.
3962 rv = NS_OK;
3963
3964 if (!doValidation) {
3965 //
3966 // Check the authorization headers used to generate the cache entry.
3967 // We must validate the cache entry if:
3968 //
3969 // 1) the cache entry was generated prior to this session w/
3970 // credentials (see bug 103402).
3971 // 2) the cache entry was generated w/o credentials, but would now
3972 // require credentials (see bug 96705).
3973 //
3974 // NOTE: this does not apply to proxy authentication.
3975 //
3976 entry->GetMetaDataElement("auth", getter_Copies(buf));
3977 doValidation =
3978 (fromPreviousSession && !buf.IsEmpty()) ||
3979 (buf.IsEmpty() && mRequestHead.HasHeader(nsHttp::Authorization));
3980 }
3981
3982 // Bug #561276: We maintain a chain of cache-keys which returns cached
3983 // 3xx-responses (redirects) in order to detect cycles. If a cycle is
3984 // found, ignore the cached response and hit the net. Otherwise, use
3985 // the cached response and add the cache-key to the chain. Note that
3986 // a limited number of redirects (cached or not) is allowed and is
3987 // enforced independently of this mechanism
3988 if (!doValidation && isCachedRedirect) {
3989 nsAutoCString cacheKey;
3990 rv = GenerateCacheKey(mPostID, cacheKey);
3991 MOZ_ASSERT(NS_SUCCEEDED(rv));
3992
3993 if (!mRedirectedCachekeys)
3994 mRedirectedCachekeys = new nsTArray<nsCString>();
3995 else if (mRedirectedCachekeys->Contains(cacheKey))
3996 doValidation = true;
3997
3998 LOG(("Redirection-chain %s key %s\n",
3999 doValidation ? "contains" : "does not contain", cacheKey.get()));
4000
4001 // Append cacheKey if not in the chain already
4002 if (!doValidation) mRedirectedCachekeys->AppendElement(cacheKey);
4003 }
4004
4005 mCachedContentIsValid = !doValidation;
4006
4007 if (doValidation) {
4008 //
4009 // now, we are definitely going to issue a HTTP request to the server.
4010 // make it conditional if possible.
4011 //
4012 // do not attempt to validate no-store content, since servers will not
4013 // expect it to be cached. (we only keep it in our cache for the
4014 // purposes of back/forward, etc.)
4015 //
4016 // the request method MUST be either GET or HEAD (see bug 175641) and
4017 // the cached response code must be < 400
4018 //
4019 // the cached content must not be weakly framed or marked immutable
4020 //
4021 // do not override conditional headers when consumer has defined its own
4022 if (!mCachedResponseHead->NoStore() &&
4023 (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
4024 !mCustomConditionalRequest && !weaklyFramed && !isImmutable &&
4025 (mCachedResponseHead->Status() < 400)) {
4026 if (mConcurrentCacheAccess) {
4027 // In case of concurrent read and also validation request we
4028 // must wait for the current writer to close the output stream
4029 // first. Otherwise, when the writer's job would have been interrupted
4030 // before all the data were downloaded, we'd have to do a range request
4031 // which would be a second request in line during this channel's
4032 // life-time. nsHttpChannel is not designed to do that, so rather
4033 // turn off concurrent read and wait for entry's completion.
4034 // Then only re-validation or range-re-validation request will go out.
4035 mConcurrentCacheAccess = 0;
4036 // This will cause that OnCacheEntryCheck is called again with the same
4037 // entry after the writer is done.
4038 wantCompleteEntry = true;
4039 } else {
4040 nsAutoCString val;
4041 // Add If-Modified-Since header if a Last-Modified was given
4042 // and we are allowed to do this (see bugs 510359 and 269303)
4043 if (canAddImsHeader) {
4044 Unused << mCachedResponseHead->GetHeader(nsHttp::Last_Modified, val);
4045 if (!val.IsEmpty()) {
4046 rv = mRequestHead.SetHeader(nsHttp::If_Modified_Since, val);
4047 MOZ_ASSERT(NS_SUCCEEDED(rv));
4048 }
4049 }
4050 // Add If-None-Match header if an ETag was given in the response
4051 Unused << mCachedResponseHead->GetHeader(nsHttp::ETag, val);
4052 if (!val.IsEmpty()) {
4053 rv = mRequestHead.SetHeader(nsHttp::If_None_Match, val);
4054 MOZ_ASSERT(NS_SUCCEEDED(rv));
4055 }
4056 mDidReval = true;
4057 }
4058 }
4059 }
4060
4061 if (mCachedContentIsValid || mDidReval) {
4062 rv = OpenCacheInputStream(entry, mCachedContentIsValid, !!appCache);
4063 if (NS_FAILED(rv)) {
4064 // If we can't get the entity then we have to act as though we
4065 // don't have the cache entry.
4066 if (mDidReval) {
4067 UntieValidationRequest();
4068 mDidReval = false;
4069 }
4070 mCachedContentIsValid = false;
4071 }
4072 }
4073
4074 if (mDidReval)
4075 *aResult = ENTRY_NEEDS_REVALIDATION;
4076 else if (wantCompleteEntry)
4077 *aResult = RECHECK_AFTER_WRITE_FINISHED;
4078 else {
4079 *aResult = ENTRY_WANTED;
4080 }
4081
4082 if (mCachedContentIsValid) {
4083 entry->MaybeMarkValid();
4084 }
4085
4086 LOG(
4087 ("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d "
4088 "result=%d]\n",
4089 this, doValidation, *aResult));
4090 return rv;
4091 }
4092
4093 NS_IMETHODIMP
OnCacheEntryAvailable(nsICacheEntry * entry,bool aNew,nsIApplicationCache * aAppCache,nsresult status)4094 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry, bool aNew,
4095 nsIApplicationCache *aAppCache,
4096 nsresult status) {
4097 MOZ_ASSERT(NS_IsMainThread());
4098
4099 nsresult rv;
4100
4101 LOG(
4102 ("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
4103 "new=%d appcache=%p status=%" PRIx32
4104 " mAppCache=%p mAppCacheForWrite=%p]\n",
4105 this, entry, aNew, aAppCache, static_cast<uint32_t>(status),
4106 mApplicationCache.get(), mApplicationCacheForWrite.get()));
4107
4108 // if the channel's already fired onStopRequest, then we should ignore
4109 // this event.
4110 if (!mIsPending) {
4111 mCacheInputStream.CloseAndRelease();
4112 return NS_OK;
4113 }
4114
4115 rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
4116 if (NS_FAILED(rv)) {
4117 CloseCacheEntry(false);
4118 if (mRaceCacheWithNetwork && mNetworkTriggered &&
4119 mFirstResponseSource != RESPONSE_FROM_CACHE) {
4120 // Ignore the error if we're racing cache with network and the cache
4121 // didn't win, The network part will handle cancelation or any other
4122 // error. Otherwise we could end up calling the listener twice, see
4123 // bug 1397593.
4124 LOG(
4125 (" not calling AsyncAbort() because we're racing cache with "
4126 "network"));
4127 } else {
4128 Unused << AsyncAbort(rv);
4129 }
4130 }
4131
4132 return NS_OK;
4133 }
4134
OnCacheEntryAvailableInternal(nsICacheEntry * entry,bool aNew,nsIApplicationCache * aAppCache,nsresult status)4135 nsresult nsHttpChannel::OnCacheEntryAvailableInternal(
4136 nsICacheEntry *entry, bool aNew, nsIApplicationCache *aAppCache,
4137 nsresult status) {
4138 nsresult rv;
4139
4140 if (mCanceled) {
4141 LOG(("channel was canceled [this=%p status=%" PRIx32 "]\n", this,
4142 static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
4143 return mStatus;
4144 }
4145
4146 if (mIgnoreCacheEntry) {
4147 if (!entry || aNew) {
4148 // We use this flag later to decide whether to report
4149 // LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent. We didn't have
4150 // an usable entry, so drop the flag.
4151 mIgnoreCacheEntry = false;
4152 }
4153 entry = nullptr;
4154 status = NS_ERROR_NOT_AVAILABLE;
4155 }
4156
4157 if (aAppCache) {
4158 if (mApplicationCache == aAppCache && !mCacheEntry) {
4159 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
4160 } else if (mApplicationCacheForWrite == aAppCache && aNew &&
4161 !mOfflineCacheEntry) {
4162 rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
4163 } else {
4164 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
4165 }
4166 } else {
4167 rv = OnNormalCacheEntryAvailable(entry, aNew, status);
4168 }
4169
4170 if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
4171 // If we have a fallback URI (and we're not already
4172 // falling back), process the fallback asynchronously.
4173 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
4174 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
4175 }
4176
4177 return NS_ERROR_DOCUMENT_NOT_CACHED;
4178 }
4179
4180 if (NS_FAILED(rv)) {
4181 return rv;
4182 }
4183
4184 // We may be waiting for more callbacks...
4185 if (AwaitingCacheCallbacks()) {
4186 return NS_OK;
4187 }
4188
4189 if (mRaceCacheWithNetwork && ((mCacheEntry && !mCachedContentIsValid &&
4190 (mDidReval || mCachedContentIsPartial)) ||
4191 mIgnoreCacheEntry)) {
4192 // We won't send the conditional request because the unconditional
4193 // request was already sent (see bug 1377223).
4194 AccumulateCategorical(
4195 Telemetry::LABELS_NETWORK_RACE_CACHE_VALIDATION::NotSent);
4196 }
4197
4198 if (mRaceCacheWithNetwork && mCachedContentIsValid) {
4199 Unused << ReadFromCache(true);
4200 }
4201
4202 return TriggerNetwork();
4203 }
4204
OnNormalCacheEntryAvailable(nsICacheEntry * aEntry,bool aNew,nsresult aEntryStatus)4205 nsresult nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
4206 bool aNew,
4207 nsresult aEntryStatus) {
4208 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
4209
4210 if (NS_FAILED(aEntryStatus) || aNew) {
4211 // Make sure this flag is dropped. It may happen the entry is doomed
4212 // between OnCacheEntryCheck and OnCacheEntryAvailable.
4213 mCachedContentIsValid = false;
4214
4215 // From the same reason remove any conditional headers added
4216 // in OnCacheEntryCheck.
4217 if (mDidReval) {
4218 LOG((" Removing conditional request headers"));
4219 UntieValidationRequest();
4220 mDidReval = false;
4221 }
4222
4223 if (mCachedContentIsPartial) {
4224 LOG((" Removing byte range request headers"));
4225 UntieByteRangeRequest();
4226 mCachedContentIsPartial = false;
4227 }
4228
4229 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
4230 // if this channel is only allowed to pull from the cache, then
4231 // we must fail if we were unable to open a cache entry for read.
4232 return NS_ERROR_DOCUMENT_NOT_CACHED;
4233 }
4234 }
4235
4236 if (NS_SUCCEEDED(aEntryStatus)) {
4237 mCacheEntry = aEntry;
4238 mCacheEntryIsWriteOnly = aNew;
4239
4240 if (!aNew && !mAsyncOpenTime.IsNull()) {
4241 // We use microseconds for IO operations. For consistency let's use
4242 // microseconds here too.
4243 uint32_t duration = (TimeStamp::Now() - mAsyncOpenTime).ToMicroseconds();
4244 bool isSlow = false;
4245 if ((mCacheOpenWithPriority &&
4246 mCacheQueueSizeWhenOpen >= sRCWNQueueSizePriority) ||
4247 (!mCacheOpenWithPriority &&
4248 mCacheQueueSizeWhenOpen >= sRCWNQueueSizeNormal)) {
4249 isSlow = true;
4250 }
4251 CacheFileUtils::CachePerfStats::AddValue(
4252 CacheFileUtils::CachePerfStats::ENTRY_OPEN, duration, isSlow);
4253 }
4254
4255 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
4256 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, false);
4257 }
4258 }
4259
4260 return NS_OK;
4261 }
4262
OnOfflineCacheEntryAvailable(nsICacheEntry * aEntry,bool aNew,nsIApplicationCache * aAppCache,nsresult aEntryStatus)4263 nsresult nsHttpChannel::OnOfflineCacheEntryAvailable(
4264 nsICacheEntry *aEntry, bool aNew, nsIApplicationCache *aAppCache,
4265 nsresult aEntryStatus) {
4266 MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
4267 MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
4268
4269 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
4270
4271 nsresult rv;
4272
4273 if (NS_SUCCEEDED(aEntryStatus)) {
4274 if (!mApplicationCache) {
4275 mApplicationCache = aAppCache;
4276 }
4277
4278 // We successfully opened an offline cache session and the entry,
4279 // so indicate we will load from the offline cache.
4280 mLoadedFromApplicationCache = true;
4281 mCacheEntryIsReadOnly = true;
4282 mCacheEntry = aEntry;
4283 mCacheEntryIsWriteOnly = false;
4284
4285 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
4286 MaybeWarnAboutAppCache();
4287 }
4288
4289 return NS_OK;
4290 }
4291
4292 if (!mApplicationCacheForWrite && !mFallbackChannel) {
4293 if (!mApplicationCache) {
4294 mApplicationCache = aAppCache;
4295 }
4296
4297 // Check for namespace match.
4298 nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
4299 rv = mApplicationCache->GetMatchingNamespace(
4300 mSpec, getter_AddRefs(namespaceEntry));
4301 NS_ENSURE_SUCCESS(rv, rv);
4302
4303 uint32_t namespaceType = 0;
4304 if (!namespaceEntry ||
4305 NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
4306 (namespaceType & (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
4307 nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) ==
4308 0) {
4309 // When loading from an application cache, only items
4310 // on the whitelist or matching a
4311 // fallback namespace should hit the network...
4312 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
4313
4314 // ... and if there were an application cache entry,
4315 // we would have found it earlier.
4316 return NS_ERROR_CACHE_KEY_NOT_FOUND;
4317 }
4318
4319 if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
4320 nsAutoCString namespaceSpec;
4321 rv = namespaceEntry->GetNamespaceSpec(namespaceSpec);
4322 NS_ENSURE_SUCCESS(rv, rv);
4323
4324 // This prevents fallback attacks injected by an insecure subdirectory
4325 // for the whole origin (or a parent directory).
4326 if (!IsInSubpathOfAppCacheManifest(mApplicationCache, namespaceSpec)) {
4327 return NS_OK;
4328 }
4329
4330 rv = namespaceEntry->GetData(mFallbackKey);
4331 NS_ENSURE_SUCCESS(rv, rv);
4332 }
4333
4334 if (namespaceType & nsIApplicationCacheNamespace::NAMESPACE_BYPASS) {
4335 LOG(
4336 ("nsHttpChannel::OnOfflineCacheEntryAvailable this=%p, URL matches "
4337 "NETWORK,"
4338 " looking for a regular cache entry",
4339 this));
4340
4341 bool isHttps = false;
4342 rv = mURI->SchemeIs("https", &isHttps);
4343 NS_ENSURE_SUCCESS(rv, rv);
4344
4345 rv = OpenCacheEntryInternal(isHttps, nullptr,
4346 false /* don't allow appcache lookups */);
4347 if (NS_FAILED(rv)) {
4348 // Don't let this fail when cache entry can't be synchronously open.
4349 // We want to go forward even without a regular cache entry.
4350 return NS_OK;
4351 }
4352 }
4353 }
4354
4355 return NS_OK;
4356 }
4357
OnOfflineCacheEntryForWritingAvailable(nsICacheEntry * aEntry,nsIApplicationCache * aAppCache,nsresult aEntryStatus)4358 nsresult nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(
4359 nsICacheEntry *aEntry, nsIApplicationCache *aAppCache,
4360 nsresult aEntryStatus) {
4361 MOZ_ASSERT(mApplicationCacheForWrite &&
4362 aAppCache == mApplicationCacheForWrite);
4363
4364 mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
4365
4366 if (NS_SUCCEEDED(aEntryStatus)) {
4367 mOfflineCacheEntry = aEntry;
4368 if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
4369 mOfflineCacheLastModifiedTime = 0;
4370 }
4371 }
4372
4373 return aEntryStatus;
4374 }
4375
4376 // Generates the proper cache-key for this instance of nsHttpChannel
GenerateCacheKey(uint32_t postID,nsACString & cacheKey)4377 nsresult nsHttpChannel::GenerateCacheKey(uint32_t postID,
4378 nsACString &cacheKey) {
4379 AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(), postID,
4380 cacheKey);
4381 return NS_OK;
4382 }
4383
4384 // Assembles a cache-key from the given pieces of information and |mLoadFlags|
AssembleCacheKey(const char * spec,uint32_t postID,nsACString & cacheKey)4385 void nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID,
4386 nsACString &cacheKey) {
4387 cacheKey.Truncate();
4388
4389 if (mLoadFlags & LOAD_ANONYMOUS) {
4390 cacheKey.AssignLiteral("anon&");
4391 }
4392
4393 if (postID) {
4394 char buf[32];
4395 SprintfLiteral(buf, "id=%x&", postID);
4396 cacheKey.Append(buf);
4397 }
4398
4399 if (!cacheKey.IsEmpty()) {
4400 cacheKey.AppendLiteral("uri=");
4401 }
4402
4403 // Strip any trailing #ref from the URL before using it as the key
4404 const char *p = strchr(spec, '#');
4405 if (p)
4406 cacheKey.Append(spec, p - spec);
4407 else
4408 cacheKey.Append(spec);
4409 }
4410
DoUpdateExpirationTime(nsHttpChannel * aSelf,nsICacheEntry * aCacheEntry,nsHttpResponseHead * aResponseHead,uint32_t & aExpirationTime)4411 nsresult DoUpdateExpirationTime(nsHttpChannel *aSelf,
4412 nsICacheEntry *aCacheEntry,
4413 nsHttpResponseHead *aResponseHead,
4414 uint32_t &aExpirationTime) {
4415 MOZ_ASSERT(aExpirationTime == 0);
4416 NS_ENSURE_TRUE(aResponseHead, NS_ERROR_FAILURE);
4417
4418 nsresult rv;
4419
4420 if (!aResponseHead->MustValidate()) {
4421 uint32_t freshnessLifetime = 0;
4422
4423 rv = aResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
4424 if (NS_FAILED(rv)) return rv;
4425
4426 if (freshnessLifetime > 0) {
4427 uint32_t now = NowInSeconds(), currentAge = 0;
4428
4429 rv = aResponseHead->ComputeCurrentAge(now, aSelf->GetRequestTime(),
4430 ¤tAge);
4431 if (NS_FAILED(rv)) return rv;
4432
4433 LOG(("freshnessLifetime = %u, currentAge = %u\n", freshnessLifetime,
4434 currentAge));
4435
4436 if (freshnessLifetime > currentAge) {
4437 uint32_t timeRemaining = freshnessLifetime - currentAge;
4438 // be careful... now + timeRemaining may overflow
4439 if (now + timeRemaining < now)
4440 aExpirationTime = uint32_t(-1);
4441 else
4442 aExpirationTime = now + timeRemaining;
4443 } else
4444 aExpirationTime = 0;
4445 }
4446 }
4447
4448 rv = aCacheEntry->SetExpirationTime(aExpirationTime);
4449 NS_ENSURE_SUCCESS(rv, rv);
4450
4451 return rv;
4452 }
4453
4454 // UpdateExpirationTime is called when a new response comes in from the server.
4455 // It updates the stored response-time and sets the expiration time on the
4456 // cache entry.
4457 //
4458 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
4459 //
4460 // timeRemaining = freshnessLifetime - currentAge
4461 // expirationTime = now + timeRemaining
4462 //
UpdateExpirationTime()4463 nsresult nsHttpChannel::UpdateExpirationTime() {
4464 uint32_t expirationTime = 0;
4465 nsresult rv =
4466 DoUpdateExpirationTime(this, mCacheEntry, mResponseHead, expirationTime);
4467 NS_ENSURE_SUCCESS(rv, rv);
4468
4469 if (mOfflineCacheEntry) {
4470 rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
4471 NS_ENSURE_SUCCESS(rv, rv);
4472 }
4473
4474 return NS_OK;
4475 }
4476
HasQueryString(nsHttpRequestHead::ParsedMethodType method,nsIURI * uri)4477 /*static*/ inline bool nsHttpChannel::HasQueryString(
4478 nsHttpRequestHead::ParsedMethodType method, nsIURI *uri) {
4479 // Must be called on the main thread because nsIURI does not implement
4480 // thread-safe QueryInterface.
4481 MOZ_ASSERT(NS_IsMainThread());
4482
4483 if (method != nsHttpRequestHead::kMethod_Get &&
4484 method != nsHttpRequestHead::kMethod_Head)
4485 return false;
4486
4487 nsAutoCString query;
4488 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
4489 nsresult rv = url->GetQuery(query);
4490 return NS_SUCCEEDED(rv) && !query.IsEmpty();
4491 }
4492
ShouldUpdateOfflineCacheEntry()4493 bool nsHttpChannel::ShouldUpdateOfflineCacheEntry() {
4494 if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
4495 return false;
4496 }
4497
4498 // if we're updating the cache entry, update the offline cache entry too
4499 if (mCacheEntry && mCacheEntryIsWriteOnly) {
4500 return true;
4501 }
4502
4503 // if there's nothing in the offline cache, add it
4504 if (mOfflineCacheEntry) {
4505 return true;
4506 }
4507
4508 // if the document is newer than the offline entry, update it
4509 uint32_t docLastModifiedTime;
4510 nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
4511 if (NS_FAILED(rv)) {
4512 return true;
4513 }
4514
4515 if (mOfflineCacheLastModifiedTime == 0) {
4516 return false;
4517 }
4518
4519 if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
4520 return true;
4521 }
4522
4523 return false;
4524 }
4525
OpenCacheInputStream(nsICacheEntry * cacheEntry,bool startBuffering,bool checkingAppCacheEntry)4526 nsresult nsHttpChannel::OpenCacheInputStream(nsICacheEntry *cacheEntry,
4527 bool startBuffering,
4528 bool checkingAppCacheEntry) {
4529 nsresult rv;
4530
4531 bool isHttps = false;
4532 rv = mURI->SchemeIs("https", &isHttps);
4533 NS_ENSURE_SUCCESS(rv, rv);
4534
4535 if (isHttps) {
4536 rv = cacheEntry->GetSecurityInfo(getter_AddRefs(mCachedSecurityInfo));
4537 if (NS_FAILED(rv)) {
4538 LOG(("failed to parse security-info [channel=%p, entry=%p]", this,
4539 cacheEntry));
4540 NS_WARNING("failed to parse security-info");
4541 cacheEntry->AsyncDoom(nullptr);
4542 return rv;
4543 }
4544
4545 // XXX: We should not be skilling this check in the offline cache
4546 // case, but we have to do so now to work around bug 794507.
4547 bool mustHaveSecurityInfo =
4548 !mLoadedFromApplicationCache && !checkingAppCacheEntry;
4549 MOZ_ASSERT(mCachedSecurityInfo || !mustHaveSecurityInfo);
4550 if (!mCachedSecurityInfo && mustHaveSecurityInfo) {
4551 LOG(
4552 ("mCacheEntry->GetSecurityInfo returned success but did not "
4553 "return the security info [channel=%p, entry=%p]",
4554 this, cacheEntry));
4555 cacheEntry->AsyncDoom(nullptr);
4556 return NS_ERROR_UNEXPECTED; // XXX error code
4557 }
4558 }
4559
4560 // Keep the conditions below in sync with the conditions in ReadFromCache.
4561
4562 rv = NS_OK;
4563
4564 if (WillRedirect(mCachedResponseHead)) {
4565 // Do not even try to read the entity for a redirect because we do not
4566 // return an entity to the application when we process redirects.
4567 LOG(("Will skip read of cached redirect entity\n"));
4568 return NS_OK;
4569 }
4570
4571 if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
4572 !mCachedContentIsPartial) {
4573 // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
4574 // cached entity.
4575 if (!mApplicationCacheForWrite) {
4576 LOG(
4577 ("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
4578 "load flag\n"));
4579 return NS_OK;
4580 }
4581
4582 // If offline caching has been requested and the offline cache needs
4583 // updating, we must complete the call even if the main cache entry
4584 // is up to date. We don't know yet for sure whether the offline
4585 // cache needs updating because at this point we haven't opened it
4586 // for writing yet, so we have to start reading the cached entity now
4587 // just in case.
4588 LOG(
4589 ("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
4590 "load flag\n"));
4591 }
4592
4593 // Open an input stream for the entity, so that the call to OpenInputStream
4594 // happens off the main thread.
4595 nsCOMPtr<nsIInputStream> stream;
4596
4597 // If an alternate representation was requested, try to open the alt
4598 // input stream.
4599 // If the entry has a "is-from-child" metadata, then only open the altdata
4600 // stream if the consumer is also from child.
4601 bool altDataFromChild = false;
4602 {
4603 nsCString value;
4604 rv = cacheEntry->GetMetaDataElement("alt-data-from-child",
4605 getter_Copies(value));
4606 altDataFromChild = !value.IsEmpty();
4607 }
4608
4609 if (!mPreferredCachedAltDataType.IsEmpty() &&
4610 (altDataFromChild == mAltDataForChild)) {
4611 rv = cacheEntry->OpenAlternativeInputStream(mPreferredCachedAltDataType,
4612 getter_AddRefs(stream));
4613 if (NS_SUCCEEDED(rv)) {
4614 // We have succeeded.
4615 mAvailableCachedAltDataType = mPreferredCachedAltDataType;
4616 // Set the correct data size on the channel.
4617 int64_t altDataSize;
4618 if (NS_SUCCEEDED(cacheEntry->GetAltDataSize(&altDataSize))) {
4619 mAltDataLength = altDataSize;
4620 }
4621 }
4622 }
4623
4624 if (!stream) {
4625 rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
4626 }
4627
4628 if (NS_FAILED(rv)) {
4629 LOG(
4630 ("Failed to open cache input stream [channel=%p, "
4631 "mCacheEntry=%p]",
4632 this, cacheEntry));
4633 return rv;
4634 }
4635
4636 if (startBuffering) {
4637 bool nonBlocking;
4638 rv = stream->IsNonBlocking(&nonBlocking);
4639 if (NS_SUCCEEDED(rv) && nonBlocking) startBuffering = false;
4640 }
4641
4642 if (!startBuffering) {
4643 // Bypass wrapping the input stream for the new cache back-end since
4644 // nsIStreamTransportService expects a blocking stream. Preloading of
4645 // the data must be done on the level of the cache backend, internally.
4646 //
4647 // We do not connect the stream to the stream transport service if we
4648 // have to validate the entry with the server. If we did, we would get
4649 // into a race condition between the stream transport service reading
4650 // the existing contents and the opening of the cache entry's output
4651 // stream to write the new contents in the case where we get a non-304
4652 // response.
4653 LOG(
4654 ("Opened cache input stream without buffering [channel=%p, "
4655 "mCacheEntry=%p, stream=%p]",
4656 this, cacheEntry, stream.get()));
4657 mCacheInputStream.takeOver(stream);
4658 return rv;
4659 }
4660
4661 // Have the stream transport service start reading the entity on one of its
4662 // background threads.
4663
4664 nsCOMPtr<nsITransport> transport;
4665 nsCOMPtr<nsIInputStream> wrapper;
4666
4667 nsCOMPtr<nsIStreamTransportService> sts(
4668 services::GetStreamTransportService());
4669 rv = sts ? NS_OK : NS_ERROR_NOT_AVAILABLE;
4670 if (NS_SUCCEEDED(rv)) {
4671 rv = sts->CreateInputTransport(stream, true, getter_AddRefs(transport));
4672 }
4673 if (NS_SUCCEEDED(rv)) {
4674 rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
4675 }
4676 if (NS_SUCCEEDED(rv)) {
4677 LOG(
4678 ("Opened cache input stream [channel=%p, wrapper=%p, "
4679 "transport=%p, stream=%p]",
4680 this, wrapper.get(), transport.get(), stream.get()));
4681 } else {
4682 LOG(
4683 ("Failed to open cache input stream [channel=%p, "
4684 "wrapper=%p, transport=%p, stream=%p]",
4685 this, wrapper.get(), transport.get(), stream.get()));
4686
4687 stream->Close();
4688 return rv;
4689 }
4690
4691 mCacheInputStream.takeOver(wrapper);
4692
4693 return NS_OK;
4694 }
4695
4696 // Actually process the cached response that we started to handle in CheckCache
4697 // and/or StartBufferingCachedEntity.
ReadFromCache(bool alreadyMarkedValid)4698 nsresult nsHttpChannel::ReadFromCache(bool alreadyMarkedValid) {
4699 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
4700 NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
4701 NS_ENSURE_TRUE(!mCachePump, NS_OK); // already opened
4702
4703 LOG(
4704 ("nsHttpChannel::ReadFromCache [this=%p] "
4705 "Using cached copy of: %s\n",
4706 this, mSpec.get()));
4707
4708 // When racing the cache with the network with a timer, and we get data from
4709 // the cache, we should prevent the timer from triggering a network request.
4710 if (mNetworkTriggerTimer) {
4711 mNetworkTriggerTimer->Cancel();
4712 mNetworkTriggerTimer = nullptr;
4713 }
4714
4715 if (mRaceCacheWithNetwork) {
4716 MOZ_ASSERT(mFirstResponseSource != RESPONSE_FROM_CACHE);
4717 if (mFirstResponseSource == RESPONSE_PENDING) {
4718 LOG(("First response from cache\n"));
4719 mFirstResponseSource = RESPONSE_FROM_CACHE;
4720
4721 // Cancel the transaction because we will serve the request from the cache
4722 CancelNetworkRequest(NS_BINDING_ABORTED);
4723 if (mTransactionPump && mSuspendCount) {
4724 uint32_t suspendCount = mSuspendCount;
4725 while (suspendCount--) {
4726 mTransactionPump->Resume();
4727 }
4728 }
4729 mTransaction = nullptr;
4730 mTransactionPump = nullptr;
4731 } else {
4732 MOZ_ASSERT(mFirstResponseSource == RESPONSE_FROM_NETWORK);
4733 LOG(
4734 ("Skipping read from cache because first response was from "
4735 "network\n"));
4736
4737 if (!mOnCacheEntryCheckTimestamp.IsNull()) {
4738 TimeStamp currentTime = TimeStamp::Now();
4739 int64_t savedTime =
4740 (currentTime - mOnStartRequestTimestamp).ToMilliseconds();
4741 Telemetry::Accumulate(
4742 Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_SAVED_TIME, savedTime);
4743
4744 int64_t diffTime =
4745 (currentTime - mOnCacheEntryCheckTimestamp).ToMilliseconds();
4746 Telemetry::Accumulate(
4747 Telemetry::NETWORK_RACE_CACHE_WITH_NETWORK_OCEC_ON_START_DIFF,
4748 diffTime);
4749 }
4750 return NS_OK;
4751 }
4752 }
4753
4754 if (mCachedResponseHead) mResponseHead = Move(mCachedResponseHead);
4755
4756 UpdateInhibitPersistentCachingFlag();
4757
4758 // if we don't already have security info, try to get it from the cache
4759 // entry. there are two cases to consider here: 1) we are just reading
4760 // from the cache, or 2) this may be due to a 304 not modified response,
4761 // in which case we could have security info from a socket transport.
4762 if (!mSecurityInfo) mSecurityInfo = mCachedSecurityInfo;
4763
4764 if (!alreadyMarkedValid && !mCachedContentIsPartial) {
4765 // We validated the entry, and we have write access to the cache, so
4766 // mark the cache entry as valid in order to allow others access to
4767 // this cache entry.
4768 //
4769 // TODO: This should be done asynchronously so we don't take the cache
4770 // service lock on the main thread.
4771 mCacheEntry->MaybeMarkValid();
4772 }
4773
4774 nsresult rv;
4775
4776 // Keep the conditions below in sync with the conditions in
4777 // StartBufferingCachedEntity.
4778
4779 if (WillRedirect(mResponseHead)) {
4780 // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
4781 // to avoid event dispatching latency.
4782 MOZ_ASSERT(!mCacheInputStream);
4783 LOG(("Skipping skip read of cached redirect entity\n"));
4784 return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
4785 }
4786
4787 if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
4788 if (!mApplicationCacheForWrite) {
4789 LOG(
4790 ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
4791 "load flag\n"));
4792 MOZ_ASSERT(!mCacheInputStream);
4793 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
4794 // here, to avoid event dispatching latency.
4795 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
4796 }
4797
4798 if (!ShouldUpdateOfflineCacheEntry()) {
4799 LOG(
4800 ("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
4801 "load flag (mApplicationCacheForWrite not null case)\n"));
4802 mCacheInputStream.CloseAndRelease();
4803 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
4804 // here, to avoid event dispatching latency.
4805 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
4806 }
4807 }
4808
4809 MOZ_ASSERT(mCacheInputStream);
4810 if (!mCacheInputStream) {
4811 NS_ERROR(
4812 "mCacheInputStream is null but we're expecting to "
4813 "be able to read from it.");
4814 return NS_ERROR_UNEXPECTED;
4815 }
4816
4817 nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
4818
4819 rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream, 0, 0,
4820 true);
4821 if (NS_FAILED(rv)) {
4822 inputStream->Close();
4823 return rv;
4824 }
4825
4826 rv = mCachePump->AsyncRead(this, mListenerContext);
4827 if (NS_FAILED(rv)) return rv;
4828
4829 if (mTimingEnabled) mCacheReadStart = TimeStamp::Now();
4830
4831 uint32_t suspendCount = mSuspendCount;
4832 while (suspendCount--) mCachePump->Suspend();
4833
4834 return NS_OK;
4835 }
4836
CloseCacheEntry(bool doomOnFailure)4837 void nsHttpChannel::CloseCacheEntry(bool doomOnFailure) {
4838 mCacheInputStream.CloseAndRelease();
4839
4840 if (!mCacheEntry) return;
4841
4842 LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%" PRIx32
4843 " mCacheEntryIsWriteOnly=%x",
4844 this, static_cast<uint32_t>(static_cast<nsresult>(mStatus)),
4845 mCacheEntryIsWriteOnly));
4846
4847 // If we have begun to create or replace a cache entry, and that cache
4848 // entry is not complete and not resumable, then it needs to be doomed.
4849 // Otherwise, CheckCache will make the mistake of thinking that the
4850 // partial cache entry is complete.
4851
4852 bool doom = false;
4853 if (mInitedCacheEntry) {
4854 MOZ_ASSERT(mResponseHead, "oops");
4855 if (NS_FAILED(mStatus) && doomOnFailure && mCacheEntryIsWriteOnly &&
4856 !mResponseHead->IsResumable())
4857 doom = true;
4858 } else if (mCacheEntryIsWriteOnly)
4859 doom = true;
4860
4861 if (doom) {
4862 LOG((" dooming cache entry!!"));
4863 mCacheEntry->AsyncDoom(nullptr);
4864 } else {
4865 // Store updated security info, makes cached EV status race less likely
4866 // (see bug 1040086)
4867 if (mSecurityInfo) mCacheEntry->SetSecurityInfo(mSecurityInfo);
4868 }
4869
4870 mCachedResponseHead = nullptr;
4871
4872 mCachePump = nullptr;
4873 // This releases the entry for other consumers to use.
4874 // We call Dismiss() in case someone still keeps a reference
4875 // to this entry handle.
4876 mCacheEntry->Dismiss();
4877 mCacheEntry = nullptr;
4878 mCacheEntryIsWriteOnly = false;
4879 mInitedCacheEntry = false;
4880 }
4881
CloseOfflineCacheEntry()4882 void nsHttpChannel::CloseOfflineCacheEntry() {
4883 if (!mOfflineCacheEntry) return;
4884
4885 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
4886
4887 if (NS_FAILED(mStatus)) {
4888 mOfflineCacheEntry->AsyncDoom(nullptr);
4889 } else {
4890 bool succeeded;
4891 if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
4892 mOfflineCacheEntry->AsyncDoom(nullptr);
4893 }
4894
4895 mOfflineCacheEntry = nullptr;
4896 }
4897
4898 // Initialize the cache entry for writing.
4899 // - finalize storage policy
4900 // - store security info
4901 // - update expiration time
4902 // - store headers and other meta data
InitCacheEntry()4903 nsresult nsHttpChannel::InitCacheEntry() {
4904 nsresult rv;
4905
4906 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
4907 // if only reading, nothing to be done here.
4908 if (mCacheEntryIsReadOnly) return NS_OK;
4909
4910 // Don't cache the response again if already cached...
4911 if (mCachedContentIsValid) return NS_OK;
4912
4913 LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n", this,
4914 mCacheEntry.get()));
4915
4916 bool recreate = !mCacheEntryIsWriteOnly;
4917 bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
4918
4919 if (!recreate && dontPersist) {
4920 // If the current entry is persistent but we inhibit peristence
4921 // then force recreation of the entry as memory/only.
4922 rv = mCacheEntry->GetPersistent(&recreate);
4923 if (NS_FAILED(rv)) return rv;
4924 }
4925
4926 if (recreate) {
4927 LOG(
4928 (" we have a ready entry, but reading it again from the server -> "
4929 "recreating cache entry\n"));
4930 // clean the altData cache and reset this to avoid wrong content length
4931 mAvailableCachedAltDataType.Truncate();
4932
4933 nsCOMPtr<nsICacheEntry> currentEntry;
4934 currentEntry.swap(mCacheEntry);
4935 rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
4936 if (NS_FAILED(rv)) {
4937 LOG((" recreation failed, the response will not be cached"));
4938 return NS_OK;
4939 }
4940
4941 mCacheEntryIsWriteOnly = true;
4942 }
4943
4944 // Set the expiration time for this cache entry
4945 rv = UpdateExpirationTime();
4946 if (NS_FAILED(rv)) return rv;
4947
4948 // mark this weakly framed until a response body is seen
4949 mCacheEntry->SetMetaDataElement("strongly-framed", "0");
4950
4951 rv = AddCacheEntryHeaders(mCacheEntry);
4952 if (NS_FAILED(rv)) return rv;
4953
4954 mInitedCacheEntry = true;
4955
4956 // Don't perform the check when writing (doesn't make sense)
4957 mConcurrentCacheAccess = 0;
4958
4959 return NS_OK;
4960 }
4961
UpdateInhibitPersistentCachingFlag()4962 void nsHttpChannel::UpdateInhibitPersistentCachingFlag() {
4963 // The no-store directive within the 'Cache-Control:' header indicates
4964 // that we must not store the response in a persistent cache.
4965 if (mResponseHead->NoStore()) mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
4966
4967 // Only cache SSL content on disk if the pref is set
4968 bool isHttps;
4969 if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
4970 NS_SUCCEEDED(mURI->SchemeIs("https", &isHttps)) && isHttps) {
4971 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
4972 }
4973 }
4974
InitOfflineCacheEntry()4975 nsresult nsHttpChannel::InitOfflineCacheEntry() {
4976 // This function can be called even when we fail to connect (bug 551990)
4977
4978 if (!mOfflineCacheEntry) {
4979 return NS_OK;
4980 }
4981
4982 if (!mResponseHead || mResponseHead->NoStore()) {
4983 if (mResponseHead && mResponseHead->NoStore()) {
4984 mOfflineCacheEntry->AsyncDoom(nullptr);
4985 }
4986
4987 CloseOfflineCacheEntry();
4988
4989 if (mResponseHead && mResponseHead->NoStore()) {
4990 return NS_ERROR_NOT_AVAILABLE;
4991 }
4992
4993 return NS_OK;
4994 }
4995
4996 // This entry's expiration time should match the main entry's expiration
4997 // time. UpdateExpirationTime() will keep it in sync once the offline
4998 // cache entry has been created.
4999 if (mCacheEntry) {
5000 uint32_t expirationTime;
5001 nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
5002 NS_ENSURE_SUCCESS(rv, rv);
5003
5004 mOfflineCacheEntry->SetExpirationTime(expirationTime);
5005 }
5006
5007 return AddCacheEntryHeaders(mOfflineCacheEntry);
5008 }
5009
DoAddCacheEntryHeaders(nsHttpChannel * self,nsICacheEntry * entry,nsHttpRequestHead * requestHead,nsHttpResponseHead * responseHead,nsISupports * securityInfo)5010 nsresult DoAddCacheEntryHeaders(nsHttpChannel *self, nsICacheEntry *entry,
5011 nsHttpRequestHead *requestHead,
5012 nsHttpResponseHead *responseHead,
5013 nsISupports *securityInfo) {
5014 nsresult rv;
5015
5016 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", self));
5017 // Store secure data in memory only
5018 if (securityInfo) entry->SetSecurityInfo(securityInfo);
5019
5020 // Store the HTTP request method with the cache entry so we can distinguish
5021 // for example GET and HEAD responses.
5022 nsAutoCString method;
5023 requestHead->Method(method);
5024 rv = entry->SetMetaDataElement("request-method", method.get());
5025 if (NS_FAILED(rv)) return rv;
5026
5027 // Store the HTTP authorization scheme used if any...
5028 rv = StoreAuthorizationMetaData(entry, requestHead);
5029 if (NS_FAILED(rv)) return rv;
5030
5031 // Iterate over the headers listed in the Vary response header, and
5032 // store the value of the corresponding request header so we can verify
5033 // that it has not varied when we try to re-use the cached response at
5034 // a later time. Take care to store "Cookie" headers only as hashes
5035 // due to security considerations and the fact that they can be pretty
5036 // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
5037 //
5038 // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
5039 // in the cache. we could try to avoid needlessly storing the "accept"
5040 // header in this case, but it doesn't seem worth the extra code to perform
5041 // the check.
5042 {
5043 nsAutoCString buf, metaKey;
5044 Unused << responseHead->GetHeader(nsHttp::Vary, buf);
5045 if (!buf.IsEmpty()) {
5046 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
5047
5048 char *bufData = buf.BeginWriting(); // going to munge buf
5049 char *token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
5050 while (token) {
5051 LOG(
5052 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5053 "processing %s",
5054 self, token));
5055 if (*token != '*') {
5056 nsHttpAtom atom = nsHttp::ResolveAtom(token);
5057 nsAutoCString val;
5058 nsAutoCString hash;
5059 if (NS_SUCCEEDED(requestHead->GetHeader(atom, val))) {
5060 // If cookie-header, store a hash of the value
5061 if (atom == nsHttp::Cookie) {
5062 LOG(
5063 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5064 "cookie-value %s",
5065 self, val.get()));
5066 rv = Hash(val.get(), hash);
5067 // If hash failed, store a string not very likely
5068 // to be the result of subsequent hashes
5069 if (NS_FAILED(rv)) {
5070 val = NS_LITERAL_CSTRING("<hash failed>");
5071 } else {
5072 val = hash;
5073 }
5074
5075 LOG((" hashed to %s\n", val.get()));
5076 }
5077
5078 // build cache meta data key and set meta data element...
5079 metaKey = prefix + nsDependentCString(token);
5080 entry->SetMetaDataElement(metaKey.get(), val.get());
5081 } else {
5082 LOG(
5083 ("nsHttpChannel::AddCacheEntryHeaders [this=%p] "
5084 "clearing metadata for %s",
5085 self, token));
5086 metaKey = prefix + nsDependentCString(token);
5087 entry->SetMetaDataElement(metaKey.get(), nullptr);
5088 }
5089 }
5090 token = nsCRT::strtok(bufData, NS_HTTP_HEADER_SEPS, &bufData);
5091 }
5092 }
5093 }
5094
5095 // Store the received HTTP head with the cache entry as an element of
5096 // the meta data.
5097 nsAutoCString head;
5098 responseHead->Flatten(head, true);
5099 rv = entry->SetMetaDataElement("response-head", head.get());
5100 if (NS_FAILED(rv)) return rv;
5101 head.Truncate();
5102 responseHead->FlattenNetworkOriginalHeaders(head);
5103 rv = entry->SetMetaDataElement("original-response-headers", head.get());
5104 if (NS_FAILED(rv)) return rv;
5105
5106 // Indicate we have successfully finished setting metadata on the cache entry.
5107 rv = entry->MetaDataReady();
5108
5109 return rv;
5110 }
5111
AddCacheEntryHeaders(nsICacheEntry * entry)5112 nsresult nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry) {
5113 return DoAddCacheEntryHeaders(this, entry, &mRequestHead, mResponseHead,
5114 mSecurityInfo);
5115 }
5116
GetAuthType(const char * challenge,nsCString & authType)5117 inline void GetAuthType(const char *challenge, nsCString &authType) {
5118 const char *p;
5119
5120 // get the challenge type
5121 if ((p = strchr(challenge, ' ')) != nullptr)
5122 authType.Assign(challenge, p - challenge);
5123 else
5124 authType.Assign(challenge);
5125 }
5126
StoreAuthorizationMetaData(nsICacheEntry * entry,nsHttpRequestHead * requestHead)5127 nsresult StoreAuthorizationMetaData(nsICacheEntry *entry,
5128 nsHttpRequestHead *requestHead) {
5129 // Not applicable to proxy authorization...
5130 nsAutoCString val;
5131 if (NS_FAILED(requestHead->GetHeader(nsHttp::Authorization, val))) {
5132 return NS_OK;
5133 }
5134
5135 // eg. [Basic realm="wally world"]
5136 nsAutoCString buf;
5137 GetAuthType(val.get(), buf);
5138 return entry->SetMetaDataElement("auth", buf.get());
5139 }
5140
5141 // Finalize the cache entry
5142 // - may need to rewrite response headers if any headers changed
5143 // - may need to recalculate the expiration time if any headers changed
5144 // - called only for freshly written cache entries
FinalizeCacheEntry()5145 nsresult nsHttpChannel::FinalizeCacheEntry() {
5146 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
5147
5148 // Don't update this meta-data on 304
5149 if (mStronglyFramed && !mCachedContentIsValid && mCacheEntry) {
5150 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p] Is Strongly Framed\n",
5151 this));
5152 mCacheEntry->SetMetaDataElement("strongly-framed", "1");
5153 }
5154
5155 if (mResponseHead && mResponseHeadersModified) {
5156 // Set the expiration time for this cache entry
5157 nsresult rv = UpdateExpirationTime();
5158 if (NS_FAILED(rv)) return rv;
5159 }
5160 return NS_OK;
5161 }
5162
5163 // Open an output stream to the cache entry and insert a listener tee into
5164 // the chain of response listeners.
InstallCacheListener(int64_t offset)5165 nsresult nsHttpChannel::InstallCacheListener(int64_t offset) {
5166 nsresult rv;
5167
5168 LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
5169
5170 MOZ_ASSERT(mCacheEntry);
5171 MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial ||
5172 mRaceCacheWithNetwork);
5173 MOZ_ASSERT(mListener);
5174
5175 nsAutoCString contentEncoding, contentType;
5176 Unused << mResponseHead->GetHeader(nsHttp::Content_Encoding, contentEncoding);
5177 mResponseHead->ContentType(contentType);
5178 // If the content is compressible and the server has not compressed it,
5179 // mark the cache entry for compression.
5180 if (contentEncoding.IsEmpty() &&
5181 (contentType.EqualsLiteral(TEXT_HTML) ||
5182 contentType.EqualsLiteral(TEXT_PLAIN) ||
5183 contentType.EqualsLiteral(TEXT_CSS) ||
5184 contentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
5185 contentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
5186 contentType.EqualsLiteral(TEXT_XML) ||
5187 contentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
5188 contentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
5189 contentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
5190 contentType.EqualsLiteral(APPLICATION_XHTML_XML))) {
5191 rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
5192 if (NS_FAILED(rv)) {
5193 LOG(("unable to mark cache entry for compression"));
5194 }
5195 }
5196
5197 LOG(("Trading cache input stream for output stream [channel=%p]", this));
5198
5199 // We must close the input stream first because cache entries do not
5200 // correctly handle having an output stream and input streams open at
5201 // the same time.
5202 mCacheInputStream.CloseAndRelease();
5203
5204 nsCOMPtr<nsIOutputStream> out;
5205 rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
5206 if (rv == NS_ERROR_NOT_AVAILABLE) {
5207 LOG((" entry doomed, not writing it [channel=%p]", this));
5208 // Entry is already doomed.
5209 // This may happen when expiration time is set to past and the entry
5210 // has been removed by the background eviction logic.
5211 return NS_OK;
5212 }
5213 if (NS_FAILED(rv)) return rv;
5214
5215 if (mCacheOnlyMetadata) {
5216 LOG(("Not storing content, cacheOnlyMetadata set"));
5217 // We must open and then close the output stream of the cache entry.
5218 // This way we indicate the content has been written (despite with zero
5219 // length) and the entry is now in the ready state with "having data".
5220
5221 out->Close();
5222 return NS_OK;
5223 }
5224
5225 // XXX disk cache does not support overlapped i/o yet
5226 #if 0
5227 // Mark entry valid inorder to allow simultaneous reading...
5228 rv = mCacheEntry->MarkValid();
5229 if (NS_FAILED(rv)) return rv;
5230 #endif
5231
5232 nsCOMPtr<nsIStreamListenerTee> tee =
5233 do_CreateInstance(kStreamListenerTeeCID, &rv);
5234 if (NS_FAILED(rv)) return rv;
5235
5236 LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%" PRIx32, tee.get(),
5237 static_cast<uint32_t>(rv)));
5238 rv = tee->Init(mListener, out, nullptr);
5239 if (NS_FAILED(rv)) return rv;
5240
5241 mListener = tee;
5242 return NS_OK;
5243 }
5244
InstallOfflineCacheListener(int64_t offset)5245 nsresult nsHttpChannel::InstallOfflineCacheListener(int64_t offset) {
5246 nsresult rv;
5247
5248 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
5249 mSpec.get()));
5250
5251 MOZ_ASSERT(mOfflineCacheEntry);
5252 MOZ_ASSERT(mListener);
5253
5254 nsCOMPtr<nsIOutputStream> out;
5255 rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
5256 if (NS_FAILED(rv)) return rv;
5257
5258 nsCOMPtr<nsIStreamListenerTee> tee =
5259 do_CreateInstance(kStreamListenerTeeCID, &rv);
5260 if (NS_FAILED(rv)) return rv;
5261
5262 rv = tee->Init(mListener, out, nullptr);
5263 if (NS_FAILED(rv)) return rv;
5264
5265 mListener = tee;
5266
5267 return NS_OK;
5268 }
5269
ClearBogusContentEncodingIfNeeded()5270 void nsHttpChannel::ClearBogusContentEncodingIfNeeded() {
5271 // For .gz files, apache sends both a Content-Type: application/x-gzip
5272 // as well as Content-Encoding: gzip, which is completely wrong. In
5273 // this case, we choose to ignore the rogue Content-Encoding header. We
5274 // must do this early on so as to prevent it from being seen up stream.
5275 // The same problem exists for Content-Encoding: compress in default
5276 // Apache installs.
5277 nsAutoCString contentType;
5278 mResponseHead->ContentType(contentType);
5279 if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") &&
5280 (contentType.EqualsLiteral(APPLICATION_GZIP) ||
5281 contentType.EqualsLiteral(APPLICATION_GZIP2) ||
5282 contentType.EqualsLiteral(APPLICATION_GZIP3))) {
5283 // clear the Content-Encoding header
5284 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
5285 } else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding,
5286 "compress") &&
5287 (contentType.EqualsLiteral(APPLICATION_COMPRESS) ||
5288 contentType.EqualsLiteral(APPLICATION_COMPRESS2))) {
5289 // clear the Content-Encoding header
5290 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
5291 }
5292 }
5293
5294 //-----------------------------------------------------------------------------
5295 // nsHttpChannel <redirect>
5296 //-----------------------------------------------------------------------------
5297
SetupReplacementChannel(nsIURI * newURI,nsIChannel * newChannel,bool preserveMethod,uint32_t redirectFlags)5298 nsresult nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
5299 nsIChannel *newChannel,
5300 bool preserveMethod,
5301 uint32_t redirectFlags) {
5302 LOG(
5303 ("nsHttpChannel::SetupReplacementChannel "
5304 "[this=%p newChannel=%p preserveMethod=%d]",
5305 this, newChannel, preserveMethod));
5306
5307 nsresult rv = HttpBaseChannel::SetupReplacementChannel(
5308 newURI, newChannel, preserveMethod, redirectFlags);
5309 if (NS_FAILED(rv)) return rv;
5310
5311 rv = CheckRedirectLimit(redirectFlags);
5312 NS_ENSURE_SUCCESS(rv, rv);
5313
5314 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
5315 if (!httpChannel) return NS_OK; // no other options to set
5316
5317 // convey the mApplyConversion flag (bug 91862)
5318 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
5319 if (encodedChannel) encodedChannel->SetApplyConversion(mApplyConversion);
5320
5321 // transfer the resume information
5322 if (mResuming) {
5323 nsCOMPtr<nsIResumableChannel> resumableChannel(
5324 do_QueryInterface(newChannel));
5325 if (!resumableChannel) {
5326 NS_WARNING(
5327 "Got asked to resume, but redirected to non-resumable channel!");
5328 return NS_ERROR_NOT_RESUMABLE;
5329 }
5330 resumableChannel->ResumeAt(mStartPos, mEntityID);
5331 }
5332
5333 return NS_OK;
5334 }
5335
AsyncProcessRedirection(uint32_t redirectType)5336 nsresult nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType) {
5337 LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n", this,
5338 redirectType));
5339
5340 nsAutoCString location;
5341
5342 // if a location header was not given, then we can't perform the redirect,
5343 // so just carry on as though this were a normal response.
5344 if (NS_FAILED(mResponseHead->GetHeader(nsHttp::Location, location)))
5345 return NS_ERROR_FAILURE;
5346
5347 // make sure non-ASCII characters in the location header are escaped.
5348 nsAutoCString locationBuf;
5349 if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII, locationBuf))
5350 location = locationBuf;
5351
5352 mRedirectType = redirectType;
5353
5354 LOG(("redirecting to: %s [redirection-limit=%u]\n", location.get(),
5355 uint32_t(mRedirectionLimit)));
5356
5357 nsresult rv = CreateNewURI(location.get(), getter_AddRefs(mRedirectURI));
5358
5359 if (NS_FAILED(rv)) {
5360 LOG(("Invalid URI for redirect: Location: %s\n", location.get()));
5361 return NS_ERROR_CORRUPTED_CONTENT;
5362 }
5363
5364 if (mApplicationCache) {
5365 // if we are redirected to a different origin check if there is a fallback
5366 // cache entry to fall back to. we don't care about file strict
5367 // checking, at least mURI is not a file URI.
5368 if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
5369 PushRedirectAsyncFunc(
5370 &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
5371 bool waitingForRedirectCallback;
5372 Unused << ProcessFallback(&waitingForRedirectCallback);
5373 if (waitingForRedirectCallback) return NS_OK;
5374 PopRedirectAsyncFunc(
5375 &nsHttpChannel::ContinueProcessRedirectionAfterFallback);
5376 }
5377 }
5378
5379 return ContinueProcessRedirectionAfterFallback(NS_OK);
5380 }
5381
ContinueProcessRedirectionAfterFallback(nsresult rv)5382 nsresult nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv) {
5383 if (NS_SUCCEEDED(rv) && mFallingBack) {
5384 // do not continue with redirect processing, fallback is in
5385 // progress now.
5386 return NS_OK;
5387 }
5388
5389 // Kill the current cache entry if we are redirecting
5390 // back to ourself.
5391 bool redirectingBackToSameURI = false;
5392 if (mCacheEntry && mCacheEntryIsWriteOnly &&
5393 NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
5394 redirectingBackToSameURI)
5395 mCacheEntry->AsyncDoom(nullptr);
5396
5397 // move the reference of the old location to the new one if the new
5398 // one has none.
5399 PropagateReferenceIfNeeded(mURI, mRedirectURI);
5400
5401 bool rewriteToGET =
5402 ShouldRewriteRedirectToGET(mRedirectType, mRequestHead.ParsedMethod());
5403
5404 // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
5405 if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
5406 rv = PromptTempRedirect();
5407 if (NS_FAILED(rv)) return rv;
5408 }
5409
5410 nsCOMPtr<nsIIOService> ioService;
5411 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
5412 if (NS_FAILED(rv)) return rv;
5413
5414 uint32_t redirectFlags;
5415 if (nsHttp::IsPermanentRedirect(mRedirectType))
5416 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
5417 else
5418 redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
5419
5420 nsCOMPtr<nsIChannel> newChannel;
5421 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
5422 CloneLoadInfoForRedirect(mRedirectURI, redirectFlags);
5423 rv = NS_NewChannelInternal(getter_AddRefs(newChannel), mRedirectURI,
5424 redirectLoadInfo,
5425 nullptr, // PerformanceStorage
5426 nullptr, // aLoadGroup
5427 nullptr, // aCallbacks
5428 nsIRequest::LOAD_NORMAL, ioService);
5429 NS_ENSURE_SUCCESS(rv, rv);
5430
5431 rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET,
5432 redirectFlags);
5433 if (NS_FAILED(rv)) return rv;
5434
5435 // verify that this is a legal redirect
5436 mRedirectChannel = newChannel;
5437
5438 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
5439 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
5440
5441 if (NS_SUCCEEDED(rv)) rv = WaitForRedirectCallback();
5442
5443 if (NS_FAILED(rv)) {
5444 AutoRedirectVetoNotifier notifier(this);
5445 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
5446 }
5447
5448 return rv;
5449 }
5450
ContinueProcessRedirection(nsresult rv)5451 nsresult nsHttpChannel::ContinueProcessRedirection(nsresult rv) {
5452 AutoRedirectVetoNotifier notifier(this);
5453
5454 LOG(("nsHttpChannel::ContinueProcessRedirection [rv=%" PRIx32 ",this=%p]\n",
5455 static_cast<uint32_t>(rv), this));
5456 if (NS_FAILED(rv)) return rv;
5457
5458 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
5459
5460 // Make sure to do this after we received redirect veto answer,
5461 // i.e. after all sinks had been notified
5462 mRedirectChannel->SetOriginalURI(mOriginalURI);
5463
5464 // XXX we used to talk directly with the script security manager, but that
5465 // should really be handled by the event sink implementation.
5466
5467 // begin loading the new channel
5468 if (mLoadInfo && mLoadInfo->GetEnforceSecurity()) {
5469 MOZ_ASSERT(!mListenerContext, "mListenerContext should be null!");
5470 rv = mRedirectChannel->AsyncOpen2(mListener);
5471 } else {
5472 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
5473 }
5474 NS_ENSURE_SUCCESS(rv, rv);
5475
5476 // close down this channel
5477 Cancel(NS_BINDING_REDIRECTED);
5478
5479 notifier.RedirectSucceeded();
5480
5481 ReleaseListeners();
5482
5483 return NS_OK;
5484 }
5485
5486 //-----------------------------------------------------------------------------
5487 // nsHttpChannel <auth>
5488 //-----------------------------------------------------------------------------
5489
OnAuthAvailable()5490 NS_IMETHODIMP nsHttpChannel::OnAuthAvailable() {
5491 LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
5492
5493 // setting mAuthRetryPending flag and resuming the transaction
5494 // triggers process of throwing away the unauthenticated data already
5495 // coming from the network
5496 mAuthRetryPending = true;
5497 mProxyAuthPending = false;
5498 LOG(("Resuming the transaction, we got credentials from user"));
5499 if (mTransactionPump) {
5500 mTransactionPump->Resume();
5501 }
5502
5503 return NS_OK;
5504 }
5505
OnAuthCancelled(bool userCancel)5506 NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel) {
5507 LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
5508
5509 if (mTransactionPump) {
5510 // If the channel is trying to authenticate to a proxy and
5511 // that was canceled we cannot show the http response body
5512 // from the 40x as that might mislead the user into thinking
5513 // it was a end host response instead of a proxy reponse.
5514 // This must check explicitly whether a proxy auth was being done
5515 // because we do want to show the content if this is an error from
5516 // the origin server.
5517 if (mProxyAuthPending) Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
5518
5519 // ensure call of OnStartRequest of the current listener here,
5520 // it would not be called otherwise at all
5521 nsresult rv = CallOnStartRequest();
5522
5523 // drop mAuthRetryPending flag and resume the transaction
5524 // this resumes load of the unauthenticated content data (which
5525 // may have been canceled if we don't want to show it)
5526 mAuthRetryPending = false;
5527 LOG(("Resuming the transaction, user cancelled the auth dialog"));
5528 mTransactionPump->Resume();
5529
5530 if (NS_FAILED(rv)) mTransactionPump->Cancel(rv);
5531 }
5532
5533 mProxyAuthPending = false;
5534 return NS_OK;
5535 }
5536
CloseStickyConnection()5537 NS_IMETHODIMP nsHttpChannel::CloseStickyConnection() {
5538 LOG(("nsHttpChannel::CloseStickyConnection this=%p", this));
5539
5540 // Require we are between OnStartRequest and OnStopRequest, because
5541 // what we do here takes effect in OnStopRequest (not reusing the
5542 // connection for next authentication round).
5543 if (!mIsPending) {
5544 LOG((" channel not pending"));
5545 NS_ERROR(
5546 "CloseStickyConnection not called before OnStopRequest, won't have any "
5547 "effect");
5548 return NS_ERROR_UNEXPECTED;
5549 }
5550
5551 MOZ_ASSERT(mTransaction);
5552 if (!mTransaction) {
5553 return NS_ERROR_UNEXPECTED;
5554 }
5555
5556 if (!(mCaps & NS_HTTP_STICKY_CONNECTION ||
5557 mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
5558 LOG((" not sticky"));
5559 return NS_OK;
5560 }
5561
5562 RefPtr<nsAHttpConnection> conn = mTransaction->GetConnectionReference();
5563 if (!conn) {
5564 LOG((" no connection"));
5565 return NS_OK;
5566 }
5567
5568 // This turns the IsPersistent() indicator on the connection to false,
5569 // and makes us throw it away in OnStopRequest.
5570 conn->DontReuse();
5571 return NS_OK;
5572 }
5573
ConnectionRestartable(bool aRestartable)5574 NS_IMETHODIMP nsHttpChannel::ConnectionRestartable(bool aRestartable) {
5575 LOG(("nsHttpChannel::ConnectionRestartable this=%p, restartable=%d", this,
5576 aRestartable));
5577 mAuthConnectionRestartable = aRestartable;
5578 return NS_OK;
5579 }
5580
5581 //-----------------------------------------------------------------------------
5582 // nsHttpChannel::nsISupports
5583 //-----------------------------------------------------------------------------
5584
5585 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
5586 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
5587
5588 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
5589 NS_INTERFACE_MAP_ENTRY(nsIRequest)
5590 NS_INTERFACE_MAP_ENTRY(nsIChannel)
5591 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
5592 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
5593 NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
5594 NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
5595 NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
5596 NS_INTERFACE_MAP_ENTRY(nsIClassOfService)
5597 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
5598 NS_INTERFACE_MAP_ENTRY(nsIFormPOSTActionChannel)
5599 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
5600 NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
5601 NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
5602 NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
5603 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
5604 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
5605 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
5606 NS_INTERFACE_MAP_ENTRY(nsIInputAvailableCallback)
5607 NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
5608 NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
5609 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
5610 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
5611 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
5612 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
5613 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
5614 NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
5615 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
5616 NS_INTERFACE_MAP_ENTRY(nsICorsPreflightCallback)
5617 NS_INTERFACE_MAP_ENTRY(nsIRaceCacheWithNetwork)
5618 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
5619 NS_INTERFACE_MAP_ENTRY(nsIChannelWithDivertableParentListener)
5620 NS_INTERFACE_MAP_ENTRY(nsIRequestTailUnblockCallback)
5621 // we have no macro that covers this case.
5622 if (aIID.Equals(NS_GET_IID(nsHttpChannel))) {
5623 AddRef();
5624 *aInstancePtr = this;
5625 return NS_OK;
5626 } else
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)5627 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
5628
5629 //-----------------------------------------------------------------------------
5630 // nsHttpChannel::nsIRequest
5631 //-----------------------------------------------------------------------------
5632
5633 NS_IMETHODIMP
5634 nsHttpChannel::Cancel(nsresult status) {
5635 MOZ_ASSERT(NS_IsMainThread());
5636 // We should never have a pump open while a CORS preflight is in progress.
5637 MOZ_ASSERT_IF(mPreflightChannel, !mCachePump);
5638
5639 LOG(("nsHttpChannel::Cancel [this=%p status=%" PRIx32 "]\n", this,
5640 static_cast<uint32_t>(status)));
5641 if (mCanceled) {
5642 LOG((" ignoring; already canceled\n"));
5643 return NS_OK;
5644 }
5645
5646 if (mWaitingForRedirectCallback) {
5647 LOG(("channel canceled during wait for redirect callback"));
5648 }
5649 mCanceled = true;
5650 mStatus = status;
5651 if (mProxyRequest) mProxyRequest->Cancel(status);
5652 CancelNetworkRequest(status);
5653 mCacheInputStream.CloseAndRelease();
5654 if (mCachePump) mCachePump->Cancel(status);
5655 if (mAuthProvider) mAuthProvider->Cancel(status);
5656 if (mPreflightChannel) mPreflightChannel->Cancel(status);
5657 if (mRequestContext && mOnTailUnblock) {
5658 mOnTailUnblock = nullptr;
5659 mRequestContext->CancelTailedRequest(this);
5660 CloseCacheEntry(false);
5661 Unused << AsyncAbort(status);
5662 }
5663 return NS_OK;
5664 }
5665
CancelNetworkRequest(nsresult aStatus)5666 void nsHttpChannel::CancelNetworkRequest(nsresult aStatus) {
5667 if (mTransaction) {
5668 nsresult rv = gHttpHandler->CancelTransaction(mTransaction, aStatus);
5669 if (NS_FAILED(rv)) {
5670 LOG(("failed to cancel the transaction\n"));
5671 }
5672 }
5673 if (mTransactionPump) mTransactionPump->Cancel(aStatus);
5674 }
5675
5676 NS_IMETHODIMP
Suspend()5677 nsHttpChannel::Suspend() {
5678 nsresult rv = SuspendInternal();
5679
5680 nsresult rvParentChannel = NS_OK;
5681 if (mParentChannel) {
5682 rvParentChannel = mParentChannel->SuspendMessageDiversion();
5683 }
5684
5685 return NS_FAILED(rv) ? rv : rvParentChannel;
5686 }
5687
5688 NS_IMETHODIMP
Resume()5689 nsHttpChannel::Resume() {
5690 nsresult rv = ResumeInternal();
5691
5692 nsresult rvParentChannel = NS_OK;
5693 if (mParentChannel) {
5694 rvParentChannel = mParentChannel->ResumeMessageDiversion();
5695 }
5696
5697 return NS_FAILED(rv) ? rv : rvParentChannel;
5698 }
5699
5700 //-----------------------------------------------------------------------------
5701 // nsHttpChannel::nsIChannel
5702 //-----------------------------------------------------------------------------
5703
5704 NS_IMETHODIMP
GetSecurityInfo(nsISupports ** securityInfo)5705 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo) {
5706 NS_ENSURE_ARG_POINTER(securityInfo);
5707 *securityInfo = mSecurityInfo;
5708 NS_IF_ADDREF(*securityInfo);
5709 return NS_OK;
5710 }
5711
5712 // If any of the functions that AsyncOpen calls returns immediately an error
5713 // AsyncAbort(which calls onStart/onStopRequest) does not need to be call.
5714 // To be sure that they are not call ReleaseListeners() is called.
5715 // If AsyncOpen returns NS_OK, after that point AsyncAbort must be called on
5716 // any error.
5717 NS_IMETHODIMP
AsyncOpen(nsIStreamListener * listener,nsISupports * context)5718 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context) {
5719 MOZ_ASSERT(
5720 !mLoadInfo || mLoadInfo->GetSecurityMode() == 0 ||
5721 mLoadInfo->GetInitialSecurityCheckDone() ||
5722 (mLoadInfo->GetSecurityMode() ==
5723 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL &&
5724 nsContentUtils::IsSystemPrincipal(mLoadInfo->LoadingPrincipal())),
5725 "security flags in loadInfo but asyncOpen2() not called");
5726
5727 LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
5728
5729 #ifdef MOZ_TASK_TRACER
5730 if (tasktracer::IsStartLogging()) {
5731 uint64_t sourceEventId, parentTaskId;
5732 tasktracer::SourceEventType sourceEventType;
5733 GetCurTraceInfo(&sourceEventId, &parentTaskId, &sourceEventType);
5734 nsCOMPtr<nsIURI> uri;
5735 GetURI(getter_AddRefs(uri));
5736 nsAutoCString urispec;
5737 uri->GetSpec(urispec);
5738 tasktracer::AddLabel("nsHttpChannel::AsyncOpen %s", urispec.get());
5739 }
5740 #endif
5741
5742 NS_CompareLoadInfoAndLoadContext(this);
5743
5744 #ifdef DEBUG
5745 AssertPrivateBrowsingId();
5746 #endif
5747
5748 NS_ENSURE_ARG_POINTER(listener);
5749 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
5750 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
5751
5752 nsresult rv;
5753
5754 MOZ_ASSERT(NS_IsMainThread());
5755
5756 if (!gHttpHandler->Active()) {
5757 LOG((" after HTTP shutdown..."));
5758 ReleaseListeners();
5759 return NS_ERROR_NOT_AVAILABLE;
5760 }
5761
5762 static bool sRCWNInited = false;
5763 if (!sRCWNInited) {
5764 sRCWNInited = true;
5765 Preferences::AddBoolVarCache(&sRCWNEnabled, "network.http.rcwn.enabled");
5766 Preferences::AddUintVarCache(
5767 &sRCWNQueueSizeNormal,
5768 "network.http.rcwn.cache_queue_normal_threshold");
5769 Preferences::AddUintVarCache(
5770 &sRCWNQueueSizePriority,
5771 "network.http.rcwn.cache_queue_priority_threshold");
5772 Preferences::AddUintVarCache(&sRCWNSmallResourceSizeKB,
5773 "network.http.rcwn.small_resource_size_kb");
5774 Preferences::AddUintVarCache(&sRCWNMinWaitMs,
5775 "network.http.rcwn.min_wait_before_racing_ms");
5776 Preferences::AddUintVarCache(&sRCWNMaxWaitMs,
5777 "network.http.rcwn.max_wait_before_racing_ms");
5778 }
5779
5780 rv = NS_CheckPortSafety(mURI);
5781 if (NS_FAILED(rv)) {
5782 ReleaseListeners();
5783 return rv;
5784 }
5785
5786 if (WaitingForTailUnblock()) {
5787 // This channel is marked as Tail and is part of a request context
5788 // that has positive number of non-tailed requestst, hence this channel
5789 // has been put to a queue.
5790 // When tail is unblocked, OnTailUnblock on this channel will be called
5791 // to continue AsyncOpen.
5792 mListener = listener;
5793 mListenerContext = context;
5794 MOZ_DIAGNOSTIC_ASSERT(!mOnTailUnblock);
5795 mOnTailUnblock = &nsHttpChannel::AsyncOpenOnTailUnblock;
5796
5797 LOG((" put on hold until tail is unblocked"));
5798 return NS_OK;
5799 }
5800
5801 // Remember the cookie header that was set, if any
5802 nsAutoCString cookieHeader;
5803 if (NS_SUCCEEDED(mRequestHead.GetHeader(nsHttp::Cookie, cookieHeader))) {
5804 mUserSetCookieHeader = cookieHeader;
5805 }
5806
5807 AddCookiesToRequest();
5808
5809 // Set user agent override, do so before OnOpeningRequest notification
5810 // since we want to allow consumers of that notification change or remove
5811 // the User-Agent request header.
5812 HttpBaseChannel::SetDocshellUserAgentOverride();
5813
5814 // After we notify any observers (on-opening-request, loadGroup, etc) we
5815 // must return NS_OK and return any errors asynchronously via
5816 // OnStart/OnStopRequest. Observers may add a reference to the channel
5817 // and expect to get OnStopRequest so they know when to drop the reference,
5818 // etc.
5819
5820 // notify "http-on-opening-request" observers, but not if this is a redirect
5821 if (!(mLoadFlags & LOAD_REPLACE)) {
5822 gHttpHandler->OnOpeningRequest(this);
5823 }
5824
5825 mIsPending = true;
5826 mWasOpened = true;
5827
5828 mListener = listener;
5829 mListenerContext = context;
5830
5831 if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr);
5832
5833 // record asyncopen time unconditionally and clear it if we
5834 // don't want it after OnModifyRequest() weighs in. But waiting for
5835 // that to complete would mean we don't include proxy resolution in the
5836 // timing.
5837 mAsyncOpenTime = TimeStamp::Now();
5838
5839 // Remember we have Authorization header set here. We need to check on it
5840 // just once and early, AsyncOpen is the best place.
5841 mCustomAuthHeader = mRequestHead.HasHeader(nsHttp::Authorization);
5842
5843 // The common case for HTTP channels is to begin proxy resolution and return
5844 // at this point. The only time we know mProxyInfo already is if we're
5845 // proxying a non-http protocol like ftp.
5846 if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy())) {
5847 return NS_OK;
5848 }
5849
5850 rv = BeginConnect();
5851 if (NS_FAILED(rv)) {
5852 CloseCacheEntry(false);
5853 Unused << AsyncAbort(rv);
5854 }
5855
5856 return NS_OK;
5857 }
5858
AsyncOpenOnTailUnblock()5859 nsresult nsHttpChannel::AsyncOpenOnTailUnblock() {
5860 return AsyncOpen(mListener, mListenerContext);
5861 }
5862
5863 already_AddRefed<nsChannelClassifier>
GetOrCreateChannelClassifier()5864 nsHttpChannel::GetOrCreateChannelClassifier() {
5865 if (!mChannelClassifier) {
5866 mChannelClassifier = new nsChannelClassifier(this);
5867 LOG(("nsHttpChannel [%p] created nsChannelClassifier [%p]\n", this,
5868 mChannelClassifier.get()));
5869 }
5870
5871 RefPtr<nsChannelClassifier> classifier = mChannelClassifier;
5872 return classifier.forget();
5873 }
5874
5875 NS_IMETHODIMP
AsyncOpen2(nsIStreamListener * aListener)5876 nsHttpChannel::AsyncOpen2(nsIStreamListener *aListener) {
5877 nsCOMPtr<nsIStreamListener> listener = aListener;
5878 nsresult rv =
5879 nsContentSecurityManager::doContentSecurityCheck(this, listener);
5880 if (NS_WARN_IF(NS_FAILED(rv))) {
5881 ReleaseListeners();
5882 return rv;
5883 }
5884 return AsyncOpen(listener, nullptr);
5885 }
5886
5887 // BeginConnect() SHOULD NOT call AsyncAbort(). AsyncAbort will be called by
5888 // functions that called BeginConnect if needed. Only AsyncOpen and
5889 // OnProxyAvailable ever call BeginConnect.
BeginConnect()5890 nsresult nsHttpChannel::BeginConnect() {
5891 LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
5892 nsresult rv;
5893
5894 // Construct connection info object
5895 nsAutoCString host;
5896 nsAutoCString scheme;
5897 int32_t port = -1;
5898 bool isHttps = false;
5899
5900 rv = mURI->GetScheme(scheme);
5901 if (NS_SUCCEEDED(rv)) rv = mURI->SchemeIs("https", &isHttps);
5902 if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiHost(host);
5903 if (NS_SUCCEEDED(rv)) rv = mURI->GetPort(&port);
5904 if (NS_SUCCEEDED(rv)) mURI->GetUsername(mUsername);
5905 if (NS_SUCCEEDED(rv)) rv = mURI->GetAsciiSpec(mSpec);
5906 if (NS_FAILED(rv)) {
5907 return rv;
5908 }
5909
5910 // Reject the URL if it doesn't specify a host
5911 if (host.IsEmpty()) {
5912 rv = NS_ERROR_MALFORMED_URI;
5913 return rv;
5914 }
5915 LOG(("host=%s port=%d\n", host.get(), port));
5916 LOG(("uri=%s\n", mSpec.get()));
5917
5918 nsCOMPtr<nsProxyInfo> proxyInfo;
5919 if (mProxyInfo) proxyInfo = do_QueryInterface(mProxyInfo);
5920
5921 mRequestHead.SetHTTPS(isHttps);
5922 mRequestHead.SetOrigin(scheme, host, port);
5923
5924 SetOriginHeader();
5925 SetDoNotTrack();
5926
5927 OriginAttributes originAttributes;
5928 NS_GetOriginAttributes(this, originAttributes);
5929
5930 RefPtr<AltSvcMapping> mapping;
5931 if (!mConnectionInfo && mAllowAltSvc && // per channel
5932 !(mLoadFlags & LOAD_FRESH_CONNECTION) &&
5933 (scheme.EqualsLiteral("http") || scheme.EqualsLiteral("https")) &&
5934 (!proxyInfo || proxyInfo->IsDirect()) &&
5935 (mapping = gHttpHandler->GetAltServiceMapping(
5936 scheme, host, port, mPrivateBrowsing, originAttributes))) {
5937 LOG(("nsHttpChannel %p Alt Service Mapping Found %s://%s:%d [%s]\n", this,
5938 scheme.get(), mapping->AlternateHost().get(), mapping->AlternatePort(),
5939 mapping->HashKey().get()));
5940
5941 if (!(mLoadFlags & LOAD_ANONYMOUS) && !mPrivateBrowsing) {
5942 nsAutoCString altUsedLine(mapping->AlternateHost());
5943 bool defaultPort =
5944 mapping->AlternatePort() ==
5945 (isHttps ? NS_HTTPS_DEFAULT_PORT : NS_HTTP_DEFAULT_PORT);
5946 if (!defaultPort) {
5947 altUsedLine.AppendLiteral(":");
5948 altUsedLine.AppendInt(mapping->AlternatePort());
5949 }
5950 rv = mRequestHead.SetHeader(nsHttp::Alternate_Service_Used, altUsedLine);
5951 MOZ_ASSERT(NS_SUCCEEDED(rv));
5952 }
5953
5954 nsCOMPtr<nsIConsoleService> consoleService =
5955 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
5956 if (consoleService) {
5957 nsAutoString message(
5958 NS_LITERAL_STRING("Alternate Service Mapping found: "));
5959 AppendASCIItoUTF16(scheme.get(), message);
5960 message.AppendLiteral(u"://");
5961 AppendASCIItoUTF16(host.get(), message);
5962 message.AppendLiteral(u":");
5963 message.AppendInt(port);
5964 message.AppendLiteral(u" to ");
5965 AppendASCIItoUTF16(scheme.get(), message);
5966 message.AppendLiteral(u"://");
5967 AppendASCIItoUTF16(mapping->AlternateHost().get(), message);
5968 message.AppendLiteral(u":");
5969 message.AppendInt(mapping->AlternatePort());
5970 consoleService->LogStringMessage(message.get());
5971 }
5972
5973 LOG(("nsHttpChannel %p Using connection info from altsvc mapping", this));
5974 mapping->GetConnectionInfo(getter_AddRefs(mConnectionInfo), proxyInfo,
5975 originAttributes);
5976 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, true);
5977 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC_OE, !isHttps);
5978 } else if (mConnectionInfo) {
5979 LOG(("nsHttpChannel %p Using channel supplied connection info", this));
5980 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
5981 } else {
5982 LOG(("nsHttpChannel %p Using default connection info", this));
5983
5984 mConnectionInfo =
5985 new nsHttpConnectionInfo(host, port, EmptyCString(), mUsername,
5986 proxyInfo, originAttributes, isHttps);
5987 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_USE_ALTSVC, false);
5988 }
5989
5990 // Set network interface id only when it's not empty to avoid
5991 // rebuilding hash key.
5992 if (!mNetworkInterfaceId.IsEmpty()) {
5993 mConnectionInfo->SetNetworkInterfaceId(mNetworkInterfaceId);
5994 }
5995
5996 mAuthProvider = do_CreateInstance(
5997 "@mozilla.org/network/http-channel-auth-provider;1", &rv);
5998 if (NS_SUCCEEDED(rv)) rv = mAuthProvider->Init(this);
5999 if (NS_FAILED(rv)) {
6000 return rv;
6001 }
6002
6003 // check to see if authorization headers should be included
6004 // mCustomAuthHeader is set in AsyncOpen if we find Authorization header
6005 rv = mAuthProvider->AddAuthorizationHeaders(mCustomAuthHeader);
6006 if (NS_FAILED(rv)) {
6007 LOG(("nsHttpChannel %p AddAuthorizationHeaders failed (%08x)", this,
6008 static_cast<uint32_t>(rv)));
6009 }
6010
6011 // notify "http-on-modify-request" observers
6012 CallOnModifyRequestObservers();
6013
6014 SetLoadGroupUserAgentOverride();
6015
6016 // Check if request was cancelled during on-modify-request or on-useragent.
6017 if (mCanceled) {
6018 return mStatus;
6019 }
6020
6021 if (mSuspendCount) {
6022 LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
6023 MOZ_ASSERT(!mCallOnResume);
6024 mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
6025 return NS_OK;
6026 }
6027
6028 return BeginConnectContinue();
6029 }
6030
HandleBeginConnectContinue()6031 void nsHttpChannel::HandleBeginConnectContinue() {
6032 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
6033 nsresult rv;
6034
6035 if (mSuspendCount) {
6036 LOG(("Waiting until resume BeginConnect [this=%p]\n", this));
6037 mCallOnResume = &nsHttpChannel::HandleBeginConnectContinue;
6038 return;
6039 }
6040
6041 LOG(("nsHttpChannel::HandleBeginConnectContinue [this=%p]\n", this));
6042 rv = BeginConnectContinue();
6043 if (NS_FAILED(rv)) {
6044 CloseCacheEntry(false);
6045 Unused << AsyncAbort(rv);
6046 }
6047 }
6048
BeginConnectContinue()6049 nsresult nsHttpChannel::BeginConnectContinue() {
6050 nsresult rv;
6051
6052 // Check if request was cancelled during suspend AFTER on-modify-request or
6053 // on-useragent.
6054 if (mCanceled) {
6055 return mStatus;
6056 }
6057
6058 // Check to see if we should redirect this channel elsewhere by
6059 // nsIHttpChannel.redirectTo API request
6060 if (mAPIRedirectToURI) {
6061 return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
6062 }
6063
6064 // If mTimingEnabled flag is not set after OnModifyRequest() then
6065 // clear the already recorded AsyncOpen value for consistency.
6066 if (!mTimingEnabled) mAsyncOpenTime = TimeStamp();
6067
6068 // if this somehow fails we can go on without it
6069 Unused << gHttpHandler->AddConnectionHeader(&mRequestHead, mCaps);
6070
6071 if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
6072 mCaps |= NS_HTTP_REFRESH_DNS;
6073
6074 // Adjust mCaps according to our request headers:
6075 // - If "Connection: close" is set as a request header, then do not bother
6076 // trying to establish a keep-alive connection.
6077 if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
6078 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE);
6079
6080 if (gHttpHandler->CriticalRequestPrioritization()) {
6081 if (mClassOfService & nsIClassOfService::Leader) {
6082 mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
6083 }
6084 if (mClassOfService & nsIClassOfService::Unblocked) {
6085 mCaps |= NS_HTTP_LOAD_UNBLOCKED;
6086 }
6087 if (mClassOfService & nsIClassOfService::UrgentStart &&
6088 gHttpHandler->IsUrgentStartEnabled()) {
6089 mCaps |= NS_HTTP_URGENT_START;
6090 SetPriority(nsISupportsPriority::PRIORITY_HIGHEST);
6091 }
6092 }
6093
6094 // Force-Reload should reset the persistent connection pool for this host
6095 if (mLoadFlags & LOAD_FRESH_CONNECTION) {
6096 // just the initial document resets the whole pool
6097 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
6098 gHttpHandler->ConnMgr()->ClearAltServiceMappings();
6099 rv = gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(
6100 mConnectionInfo);
6101 if (NS_FAILED(rv)) {
6102 LOG(
6103 ("nsHttpChannel::BeginConnect "
6104 "DoShiftReloadConnectionCleanup failed: %08x [this=%p]",
6105 static_cast<uint32_t>(rv), this));
6106 }
6107 }
6108 }
6109
6110 // We may have been cancelled already, either by on-modify-request
6111 // listeners or load group observers; in that case, we should not send the
6112 // request to the server
6113 if (mCanceled) {
6114 return mStatus;
6115 }
6116
6117 if (!(mLoadFlags & LOAD_CLASSIFY_URI)) {
6118 return ContinueBeginConnectWithResult();
6119 }
6120
6121 // We are about to do a sync lookup to check if the URI is a
6122 // tracker. If yes, this channel will be canceled by channel classifier.
6123 // Chances are the lookup is not needed so CheckIsTrackerWithLocalTable()
6124 // will return an error and then we can BeginConnectActual() right away.
6125 RefPtr<nsChannelClassifier> channelClassifier =
6126 GetOrCreateChannelClassifier();
6127 RefPtr<nsHttpChannel> self = this;
6128 bool willCallback = NS_SUCCEEDED(
6129 channelClassifier->CheckIsTrackerWithLocalTable([self]() -> void {
6130 nsresult rv = self->BeginConnectActual();
6131 if (NS_FAILED(rv)) {
6132 // Since this error is thrown asynchronously so that the caller
6133 // of BeginConnect() will not do clean up for us. We have to do
6134 // it on our own.
6135 self->CloseCacheEntry(false);
6136 Unused << self->AsyncAbort(rv);
6137 }
6138 }));
6139
6140 if (!willCallback) {
6141 // We can do BeginConnectActual immediately if CheckIsTrackerWithLocalTable
6142 // is failed. Note that we don't need to handle the failure because
6143 // BeginConnect() will return synchronously and the caller will be
6144 // responsible for handling it.
6145 return BeginConnectActual();
6146 }
6147
6148 return NS_OK;
6149 }
6150
BeginConnectActual()6151 nsresult nsHttpChannel::BeginConnectActual() {
6152 if (mCanceled) {
6153 return mStatus;
6154 }
6155
6156 if (!mConnectionInfo->UsingHttpProxy() &&
6157 !(mLoadFlags & (LOAD_NO_NETWORK_IO | LOAD_ONLY_FROM_CACHE))) {
6158 // Start a DNS lookup very early in case the real open is queued the DNS can
6159 // happen in parallel. Do not do so in the presence of an HTTP proxy as
6160 // all lookups other than for the proxy itself are done by the proxy.
6161 // Also we don't do a lookup if the LOAD_NO_NETWORK_IO or
6162 // LOAD_ONLY_FROM_CACHE flags are set.
6163 //
6164 // We keep the DNS prefetch object around so that we can retrieve
6165 // timing information from it. There is no guarantee that we actually
6166 // use the DNS prefetch data for the real connection, but as we keep
6167 // this data around for 3 minutes by default, this should almost always
6168 // be correct, and even when it isn't, the timing still represents _a_
6169 // valid DNS lookup timing for the site, even if it is not _the_
6170 // timing we used.
6171 LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n", this,
6172 mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
6173 OriginAttributes originAttributes;
6174 NS_GetOriginAttributes(this, originAttributes);
6175 mDNSPrefetch =
6176 new nsDNSPrefetch(mURI, originAttributes, this, mTimingEnabled);
6177 mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
6178 }
6179
6180 nsresult rv = ContinueBeginConnectWithResult();
6181 if (NS_FAILED(rv)) {
6182 return rv;
6183 }
6184
6185 // Start nsChannelClassifier to catch phishing and malware URIs.
6186 RefPtr<nsChannelClassifier> channelClassifier =
6187 GetOrCreateChannelClassifier();
6188 LOG(("nsHttpChannel::Starting nsChannelClassifier %p [this=%p]",
6189 channelClassifier.get(), this));
6190 channelClassifier->Start();
6191
6192 return NS_OK;
6193 }
6194
6195 NS_IMETHODIMP
GetEncodedBodySize(uint64_t * aEncodedBodySize)6196 nsHttpChannel::GetEncodedBodySize(uint64_t *aEncodedBodySize) {
6197 if (mCacheEntry && !mCacheEntryIsWriteOnly) {
6198 int64_t dataSize = 0;
6199 mCacheEntry->GetDataSize(&dataSize);
6200 *aEncodedBodySize = dataSize;
6201 } else {
6202 *aEncodedBodySize = mLogicalOffset;
6203 }
6204 return NS_OK;
6205 }
6206
6207 //-----------------------------------------------------------------------------
6208 // nsHttpChannel::nsIHttpChannelInternal
6209 //-----------------------------------------------------------------------------
6210
6211 NS_IMETHODIMP
SetupFallbackChannel(const char * aFallbackKey)6212 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey) {
6213 ENSURE_CALLED_BEFORE_CONNECT();
6214
6215 LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]\n", this,
6216 aFallbackKey));
6217 mFallbackChannel = true;
6218 mFallbackKey = aFallbackKey;
6219
6220 return NS_OK;
6221 }
6222
6223 NS_IMETHODIMP
SetChannelIsForDownload(bool aChannelIsForDownload)6224 nsHttpChannel::SetChannelIsForDownload(bool aChannelIsForDownload) {
6225 if (aChannelIsForDownload) {
6226 AddClassFlags(nsIClassOfService::Throttleable);
6227 } else {
6228 ClearClassFlags(nsIClassOfService::Throttleable);
6229 }
6230
6231 return HttpBaseChannel::SetChannelIsForDownload(aChannelIsForDownload);
6232 }
6233
ProcessId()6234 base::ProcessId nsHttpChannel::ProcessId() {
6235 nsCOMPtr<nsIParentChannel> parentChannel;
6236 NS_QueryNotificationCallbacks(this, parentChannel);
6237 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6238 if (httpParent) {
6239 return httpParent->OtherPid();
6240 }
6241 return base::GetCurrentProcId();
6242 }
6243
AttachStreamFilter(ipc::Endpoint<extensions::PStreamFilterParent> && aEndpoint)6244 bool nsHttpChannel::AttachStreamFilter(
6245 ipc::Endpoint<extensions::PStreamFilterParent> &&aEndpoint)
6246
6247 {
6248 nsCOMPtr<nsIParentChannel> parentChannel;
6249 NS_QueryNotificationCallbacks(this, parentChannel);
6250 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6251 if (httpParent) {
6252 return httpParent->SendAttachStreamFilter(Move(aEndpoint));
6253 }
6254
6255 extensions::StreamFilterParent::Attach(this, Move(aEndpoint));
6256 return true;
6257 }
6258
6259 //-----------------------------------------------------------------------------
6260 // nsHttpChannel::nsISupportsPriority
6261 //-----------------------------------------------------------------------------
6262
6263 NS_IMETHODIMP
SetPriority(int32_t value)6264 nsHttpChannel::SetPriority(int32_t value) {
6265 int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
6266 if (mPriority == newValue) return NS_OK;
6267
6268 LOG(("nsHttpChannel::SetPriority %p p=%d", this, newValue));
6269
6270 mPriority = newValue;
6271 if (mTransaction) {
6272 nsresult rv = gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
6273 if (NS_FAILED(rv)) {
6274 LOG(
6275 ("nsHttpChannel::SetPriority [this=%p] "
6276 "RescheduleTransaction failed (%08x)",
6277 this, static_cast<uint32_t>(rv)));
6278 }
6279 }
6280
6281 // If this channel is the real channel for an e10s channel, notify the
6282 // child side about the priority change as well.
6283 nsCOMPtr<nsIParentChannel> parentChannel;
6284 NS_QueryNotificationCallbacks(this, parentChannel);
6285 RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel);
6286 if (httpParent) {
6287 httpParent->DoSendSetPriority(newValue);
6288 }
6289
6290 return NS_OK;
6291 }
6292
ContinueBeginConnectWithResult()6293 nsresult nsHttpChannel::ContinueBeginConnectWithResult() {
6294 LOG(("nsHttpChannel::ContinueBeginConnectWithResult [this=%p]", this));
6295 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
6296
6297 nsresult rv;
6298
6299 if (mSuspendCount) {
6300 LOG(("Waiting until resume to do async connect [this=%p]\n", this));
6301 mCallOnResume = &nsHttpChannel::ContinueBeginConnect;
6302 rv = NS_OK;
6303 } else if (mCanceled) {
6304 // We may have been cancelled already, by nsChannelClassifier in that
6305 // case, we should not send the request to the server
6306 rv = mStatus;
6307 } else {
6308 rv = OnBeforeConnect();
6309 }
6310
6311 LOG(
6312 ("nsHttpChannel::ContinueBeginConnectWithResult result [this=%p "
6313 "rv=%" PRIx32 " mCanceled=%u]\n",
6314 this, static_cast<uint32_t>(rv), static_cast<bool>(mCanceled)));
6315 return rv;
6316 }
6317
ContinueBeginConnect()6318 void nsHttpChannel::ContinueBeginConnect() {
6319 LOG(("nsHttpChannel::ContinueBeginConnect this=%p", this));
6320
6321 nsresult rv = ContinueBeginConnectWithResult();
6322 if (NS_FAILED(rv)) {
6323 CloseCacheEntry(false);
6324 Unused << AsyncAbort(rv);
6325 }
6326 }
6327
6328 //-----------------------------------------------------------------------------
6329 // HttpChannel::nsIClassOfService
6330 //-----------------------------------------------------------------------------
6331
OnClassOfServiceUpdated()6332 void nsHttpChannel::OnClassOfServiceUpdated() {
6333 LOG(("nsHttpChannel::OnClassOfServiceUpdated this=%p, cos=%u", this,
6334 mClassOfService));
6335
6336 if (mTransaction) {
6337 gHttpHandler->UpdateClassOfServiceOnTransaction(mTransaction,
6338 mClassOfService);
6339 }
6340 if (EligibleForTailing()) {
6341 RemoveAsNonTailRequest();
6342 } else {
6343 AddAsNonTailRequest();
6344 }
6345 }
6346
6347 NS_IMETHODIMP
SetClassFlags(uint32_t inFlags)6348 nsHttpChannel::SetClassFlags(uint32_t inFlags) {
6349 uint32_t previous = mClassOfService;
6350 mClassOfService = inFlags;
6351 if (previous != mClassOfService) {
6352 OnClassOfServiceUpdated();
6353 }
6354 return NS_OK;
6355 }
6356
6357 NS_IMETHODIMP
AddClassFlags(uint32_t inFlags)6358 nsHttpChannel::AddClassFlags(uint32_t inFlags) {
6359 uint32_t previous = mClassOfService;
6360 mClassOfService |= inFlags;
6361 if (previous != mClassOfService) {
6362 OnClassOfServiceUpdated();
6363 }
6364 return NS_OK;
6365 }
6366
6367 NS_IMETHODIMP
ClearClassFlags(uint32_t inFlags)6368 nsHttpChannel::ClearClassFlags(uint32_t inFlags) {
6369 uint32_t previous = mClassOfService;
6370 mClassOfService &= ~inFlags;
6371 if (previous != mClassOfService) {
6372 OnClassOfServiceUpdated();
6373 }
6374 return NS_OK;
6375 }
6376
6377 //-----------------------------------------------------------------------------
6378 // nsHttpChannel::nsIProtocolProxyCallback
6379 //-----------------------------------------------------------------------------
6380
6381 NS_IMETHODIMP
OnProxyAvailable(nsICancelable * request,nsIChannel * channel,nsIProxyInfo * pi,nsresult status)6382 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
6383 nsIProxyInfo *pi, nsresult status) {
6384 LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%" PRIx32
6385 " mStatus=%" PRIx32 "]\n",
6386 this, pi, static_cast<uint32_t>(status),
6387 static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
6388 mProxyRequest = nullptr;
6389
6390 nsresult rv;
6391
6392 // If status is a failure code, then it means that we failed to resolve
6393 // proxy info. That is a non-fatal error assuming it wasn't because the
6394 // request was canceled. We just failover to DIRECT when proxy resolution
6395 // fails (failure can mean that the PAC URL could not be loaded).
6396
6397 if (NS_SUCCEEDED(status)) mProxyInfo = pi;
6398
6399 if (!gHttpHandler->Active()) {
6400 LOG(
6401 ("nsHttpChannel::OnProxyAvailable [this=%p] "
6402 "Handler no longer active.\n",
6403 this));
6404 rv = NS_ERROR_NOT_AVAILABLE;
6405 } else {
6406 rv = BeginConnect();
6407 }
6408
6409 if (NS_FAILED(rv)) {
6410 CloseCacheEntry(false);
6411 Unused << AsyncAbort(rv);
6412 }
6413 return rv;
6414 }
6415
6416 //-----------------------------------------------------------------------------
6417 // nsHttpChannel::nsIProxiedChannel
6418 //-----------------------------------------------------------------------------
6419
6420 NS_IMETHODIMP
GetProxyInfo(nsIProxyInfo ** result)6421 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result) {
6422 if (!mConnectionInfo)
6423 *result = mProxyInfo;
6424 else
6425 *result = mConnectionInfo->ProxyInfo();
6426 NS_IF_ADDREF(*result);
6427 return NS_OK;
6428 }
6429
6430 //-----------------------------------------------------------------------------
6431 // nsHttpChannel::nsITimedChannel
6432 //-----------------------------------------------------------------------------
6433
6434 NS_IMETHODIMP
GetDomainLookupStart(TimeStamp * _retval)6435 nsHttpChannel::GetDomainLookupStart(TimeStamp *_retval) {
6436 if (mTransaction)
6437 *_retval = mTransaction->GetDomainLookupStart();
6438 else
6439 *_retval = mTransactionTimings.domainLookupStart;
6440 return NS_OK;
6441 }
6442
6443 NS_IMETHODIMP
GetDomainLookupEnd(TimeStamp * _retval)6444 nsHttpChannel::GetDomainLookupEnd(TimeStamp *_retval) {
6445 if (mTransaction)
6446 *_retval = mTransaction->GetDomainLookupEnd();
6447 else
6448 *_retval = mTransactionTimings.domainLookupEnd;
6449 return NS_OK;
6450 }
6451
6452 NS_IMETHODIMP
GetConnectStart(TimeStamp * _retval)6453 nsHttpChannel::GetConnectStart(TimeStamp *_retval) {
6454 if (mTransaction)
6455 *_retval = mTransaction->GetConnectStart();
6456 else
6457 *_retval = mTransactionTimings.connectStart;
6458 return NS_OK;
6459 }
6460
6461 NS_IMETHODIMP
GetTcpConnectEnd(TimeStamp * _retval)6462 nsHttpChannel::GetTcpConnectEnd(TimeStamp *_retval) {
6463 if (mTransaction)
6464 *_retval = mTransaction->GetTcpConnectEnd();
6465 else
6466 *_retval = mTransactionTimings.tcpConnectEnd;
6467 return NS_OK;
6468 }
6469
6470 NS_IMETHODIMP
GetSecureConnectionStart(TimeStamp * _retval)6471 nsHttpChannel::GetSecureConnectionStart(TimeStamp *_retval) {
6472 if (mTransaction)
6473 *_retval = mTransaction->GetSecureConnectionStart();
6474 else
6475 *_retval = mTransactionTimings.secureConnectionStart;
6476 return NS_OK;
6477 }
6478
6479 NS_IMETHODIMP
GetConnectEnd(TimeStamp * _retval)6480 nsHttpChannel::GetConnectEnd(TimeStamp *_retval) {
6481 if (mTransaction)
6482 *_retval = mTransaction->GetConnectEnd();
6483 else
6484 *_retval = mTransactionTimings.connectEnd;
6485 return NS_OK;
6486 }
6487
6488 NS_IMETHODIMP
GetRequestStart(TimeStamp * _retval)6489 nsHttpChannel::GetRequestStart(TimeStamp *_retval) {
6490 if (mTransaction)
6491 *_retval = mTransaction->GetRequestStart();
6492 else
6493 *_retval = mTransactionTimings.requestStart;
6494 return NS_OK;
6495 }
6496
6497 NS_IMETHODIMP
GetResponseStart(TimeStamp * _retval)6498 nsHttpChannel::GetResponseStart(TimeStamp *_retval) {
6499 if (mTransaction)
6500 *_retval = mTransaction->GetResponseStart();
6501 else
6502 *_retval = mTransactionTimings.responseStart;
6503 return NS_OK;
6504 }
6505
6506 NS_IMETHODIMP
GetResponseEnd(TimeStamp * _retval)6507 nsHttpChannel::GetResponseEnd(TimeStamp *_retval) {
6508 if (mTransaction)
6509 *_retval = mTransaction->GetResponseEnd();
6510 else
6511 *_retval = mTransactionTimings.responseEnd;
6512 return NS_OK;
6513 }
6514
6515 //-----------------------------------------------------------------------------
6516 // nsHttpChannel::nsIHttpAuthenticableChannel
6517 //-----------------------------------------------------------------------------
6518
6519 NS_IMETHODIMP
GetIsSSL(bool * aIsSSL)6520 nsHttpChannel::GetIsSSL(bool *aIsSSL) {
6521 // this attribute is really misnamed - it wants to know if
6522 // https:// is being used. SSL might be used to cover http://
6523 // in some circumstances (proxies, http/2, etc..)
6524 return mURI->SchemeIs("https", aIsSSL);
6525 }
6526
6527 NS_IMETHODIMP
GetProxyMethodIsConnect(bool * aProxyMethodIsConnect)6528 nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect) {
6529 *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
6530 return NS_OK;
6531 }
6532
6533 NS_IMETHODIMP
GetServerResponseHeader(nsACString & value)6534 nsHttpChannel::GetServerResponseHeader(nsACString &value) {
6535 if (!mResponseHead) return NS_ERROR_NOT_AVAILABLE;
6536 return mResponseHead->GetHeader(nsHttp::Server, value);
6537 }
6538
6539 NS_IMETHODIMP
GetProxyChallenges(nsACString & value)6540 nsHttpChannel::GetProxyChallenges(nsACString &value) {
6541 if (!mResponseHead) return NS_ERROR_UNEXPECTED;
6542 return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
6543 }
6544
6545 NS_IMETHODIMP
GetWWWChallenges(nsACString & value)6546 nsHttpChannel::GetWWWChallenges(nsACString &value) {
6547 if (!mResponseHead) return NS_ERROR_UNEXPECTED;
6548 return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
6549 }
6550
6551 NS_IMETHODIMP
SetProxyCredentials(const nsACString & value)6552 nsHttpChannel::SetProxyCredentials(const nsACString &value) {
6553 return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
6554 }
6555
6556 NS_IMETHODIMP
SetWWWCredentials(const nsACString & value)6557 nsHttpChannel::SetWWWCredentials(const nsACString &value) {
6558 // This method is called when various browser initiated authorization
6559 // code sets the credentials. We need to flag this header as the
6560 // "browser default" so it does not show up in the ServiceWorker
6561 // FetchEvent. This may actually get called more than once, though,
6562 // so we clear the header first since "default" headers are not
6563 // allowed to overwrite normally.
6564 Unused << mRequestHead.ClearHeader(nsHttp::Authorization);
6565 return mRequestHead.SetHeader(nsHttp::Authorization, value, false,
6566 nsHttpHeaderArray::eVarietyRequestDefault);
6567 }
6568
6569 //-----------------------------------------------------------------------------
6570 // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
6571 // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
6572 //
6573
6574 NS_IMETHODIMP
GetLoadFlags(nsLoadFlags * aLoadFlags)6575 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags) {
6576 return HttpBaseChannel::GetLoadFlags(aLoadFlags);
6577 }
6578
6579 NS_IMETHODIMP
GetURI(nsIURI ** aURI)6580 nsHttpChannel::GetURI(nsIURI **aURI) { return HttpBaseChannel::GetURI(aURI); }
6581
6582 NS_IMETHODIMP
GetNotificationCallbacks(nsIInterfaceRequestor ** aCallbacks)6583 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks) {
6584 return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
6585 }
6586
6587 NS_IMETHODIMP
GetLoadGroup(nsILoadGroup ** aLoadGroup)6588 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup) {
6589 return HttpBaseChannel::GetLoadGroup(aLoadGroup);
6590 }
6591
6592 NS_IMETHODIMP
GetRequestMethod(nsACString & aMethod)6593 nsHttpChannel::GetRequestMethod(nsACString &aMethod) {
6594 return HttpBaseChannel::GetRequestMethod(aMethod);
6595 }
6596
6597 //-----------------------------------------------------------------------------
6598 // nsHttpChannel::nsIRequestObserver
6599 //-----------------------------------------------------------------------------
6600
6601 NS_IMETHODIMP
OnStartRequest(nsIRequest * request,nsISupports * ctxt)6602 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt) {
6603 nsresult rv;
6604
6605 AUTO_PROFILER_LABEL("nsHttpChannel::OnStartRequest", NETWORK);
6606
6607 if (!(mCanceled || NS_FAILED(mStatus)) &&
6608 !WRONG_RACING_RESPONSE_SOURCE(request)) {
6609 // capture the request's status, so our consumers will know ASAP of any
6610 // connection failures, etc - bug 93581
6611 nsresult status;
6612 request->GetStatus(&status);
6613 mStatus = status;
6614 }
6615
6616 LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%" PRIx32
6617 "]\n",
6618 this, request, static_cast<uint32_t>(static_cast<nsresult>(mStatus))));
6619
6620 if (mRaceCacheWithNetwork) {
6621 LOG(
6622 (" racingNetAndCache - mFirstResponseSource:%d fromCache:%d "
6623 "fromNet:%d\n",
6624 static_cast<int32_t>(mFirstResponseSource), request == mCachePump,
6625 request == mTransactionPump));
6626 if (mFirstResponseSource == RESPONSE_PENDING) {
6627 // When the cache wins mFirstResponseSource is set to RESPONSE_FROM_CACHE
6628 // earlier in ReadFromCache, so this must be a response from the network.
6629 MOZ_ASSERT(request == mTransactionPump);
6630 LOG((" First response from network\n"));
6631 {
6632 // Race condition with OnCacheEntryCheck, which is not limited
6633 // to main thread.
6634 mozilla::MutexAutoLock lock(mRCWNLock);
6635 mFirstResponseSource = RESPONSE_FROM_NETWORK;
6636 mOnStartRequestTimestamp = TimeStamp::Now();
6637
6638 // Conditional or byte range header could be added in
6639 // OnCacheEntryCheck. We need to remove them because the
6640 // request might be sent again due to auth retry and we must
6641 // not send these headers without having the entry.
6642 if (mDidReval) {
6643 LOG((" Removing conditional request headers"));
6644 UntieValidationRequest();
6645 mDidReval = false;
6646 }
6647 if (mCachedContentIsPartial) {
6648 LOG((" Removing byte range request headers"));
6649 UntieByteRangeRequest();
6650 mCachedContentIsPartial = false;
6651 }
6652 }
6653 mAvailableCachedAltDataType.Truncate();
6654 } else if (WRONG_RACING_RESPONSE_SOURCE(request)) {
6655 LOG((" Early return when racing. This response not needed."));
6656 return NS_OK;
6657 }
6658 }
6659
6660 // Make sure things are what we expect them to be...
6661 MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
6662 "Unexpected request");
6663
6664 MOZ_ASSERT(mRaceCacheWithNetwork || !(mTransactionPump && mCachePump) ||
6665 mCachedContentIsPartial,
6666 "If we have both pumps, the cache content must be partial");
6667
6668 mAfterOnStartRequestBegun = true;
6669 if (mOnStartRequestTimestamp.IsNull()) {
6670 mOnStartRequestTimestamp = TimeStamp::Now();
6671 }
6672
6673 Telemetry::Accumulate(Telemetry::HTTP_ONSTART_SUSPEND_TOTAL_TIME,
6674 mSuspendTotalTime);
6675
6676 if (!mSecurityInfo && !mCachePump && mTransaction) {
6677 // grab the security info from the connection object; the transaction
6678 // is guaranteed to own a reference to the connection.
6679 mSecurityInfo = mTransaction->SecurityInfo();
6680 }
6681
6682 // don't enter this block if we're reading from the cache...
6683 if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
6684 // mTransactionPump doesn't hit OnInputStreamReady and call this until
6685 // all of the response headers have been acquired, so we can take ownership
6686 // of them from the transaction.
6687 mResponseHead = mTransaction->TakeResponseHead();
6688 // the response head may be null if the transaction was cancelled. in
6689 // which case we just need to call OnStartRequest/OnStopRequest.
6690 if (mResponseHead) return ProcessResponse();
6691
6692 NS_WARNING("No response head in OnStartRequest");
6693 }
6694
6695 // cache file could be deleted on our behalf, it could contain errors or
6696 // it failed to allocate memory, reload from network here.
6697 if (mCacheEntry && mCachePump && RECOVER_FROM_CACHE_FILE_ERROR(mStatus)) {
6698 LOG((" cache file error, reloading from server"));
6699 mCacheEntry->AsyncDoom(nullptr);
6700 rv =
6701 StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
6702 if (NS_SUCCEEDED(rv)) return NS_OK;
6703 }
6704
6705 // avoid crashing if mListener happens to be null...
6706 if (!mListener) {
6707 NS_NOTREACHED("mListener is null");
6708 return NS_OK;
6709 }
6710
6711 // before we start any content load, check for redirectTo being called
6712 // this code is executed mainly before we start load from the cache
6713 if (mAPIRedirectToURI && !mCanceled) {
6714 nsAutoCString redirectToSpec;
6715 mAPIRedirectToURI->GetAsciiSpec(redirectToSpec);
6716 LOG((" redirectTo called with uri=%s", redirectToSpec.BeginReading()));
6717
6718 MOZ_ASSERT(!mOnStartRequestCalled);
6719
6720 nsCOMPtr<nsIURI> redirectTo;
6721 mAPIRedirectToURI.swap(redirectTo);
6722
6723 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
6724 rv = StartRedirectChannelToURI(redirectTo,
6725 nsIChannelEventSink::REDIRECT_TEMPORARY);
6726 if (NS_SUCCEEDED(rv)) {
6727 return NS_OK;
6728 }
6729 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
6730 }
6731
6732 // Hack: ContinueOnStartRequest1 uses NS_OK to detect successful redirects,
6733 // so we distinguish this codepath (a non-redirect that's processing
6734 // normally) by passing in a bogus error code.
6735 return ContinueOnStartRequest1(NS_BINDING_FAILED);
6736 }
6737
ContinueOnStartRequest1(nsresult result)6738 nsresult nsHttpChannel::ContinueOnStartRequest1(nsresult result) {
6739 if (NS_SUCCEEDED(result)) {
6740 // Redirect has passed through, we don't want to go on with this
6741 // channel. It will now be canceled by the redirect handling code
6742 // that called this function.
6743 return NS_OK;
6744 }
6745
6746 // on proxy errors, try to failover
6747 if (mConnectionInfo->ProxyInfo() &&
6748 (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
6749 mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
6750 mStatus == NS_ERROR_NET_TIMEOUT)) {
6751 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
6752 if (NS_SUCCEEDED(ProxyFailover())) return NS_OK;
6753 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest2);
6754 }
6755
6756 // Hack: ContinueOnStartRequest2 uses NS_OK to detect successful redirects,
6757 // so we distinguish this codepath (a non-redirect that's processing
6758 // normally) by passing in a bogus error code.
6759 return ContinueOnStartRequest2(NS_BINDING_FAILED);
6760 }
6761
ContinueOnStartRequest2(nsresult result)6762 nsresult nsHttpChannel::ContinueOnStartRequest2(nsresult result) {
6763 if (NS_SUCCEEDED(result)) {
6764 // Redirect has passed through, we don't want to go on with this
6765 // channel. It will now be canceled by the redirect handling code
6766 // that called this function.
6767 return NS_OK;
6768 }
6769
6770 // on other request errors, try to fall back
6771 if (NS_FAILED(mStatus)) {
6772 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
6773 bool waitingForRedirectCallback;
6774 Unused << ProcessFallback(&waitingForRedirectCallback);
6775 if (waitingForRedirectCallback) return NS_OK;
6776 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
6777 }
6778
6779 return ContinueOnStartRequest3(NS_OK);
6780 }
6781
ContinueOnStartRequest3(nsresult result)6782 nsresult nsHttpChannel::ContinueOnStartRequest3(nsresult result) {
6783 LOG(("nsHttpChannel::ContinueOnStartRequest3 [this=%p]", this));
6784
6785 if (mFallingBack) return NS_OK;
6786
6787 return CallOnStartRequest();
6788 }
6789
6790 NS_IMETHODIMP
OnStopRequest(nsIRequest * request,nsISupports * ctxt,nsresult status)6791 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
6792 nsresult status) {
6793 AUTO_PROFILER_LABEL("nsHttpChannel::OnStopRequest", NETWORK);
6794
6795 LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%" PRIx32 "]\n",
6796 this, request, static_cast<uint32_t>(status)));
6797
6798 LOG(("OnStopRequest %p requestFromCache: %d mFirstResponseSource: %d\n", this,
6799 request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
6800
6801 MOZ_ASSERT(NS_IsMainThread(),
6802 "OnStopRequest should only be called from the main thread");
6803
6804 if (WRONG_RACING_RESPONSE_SOURCE(request)) {
6805 return NS_OK;
6806 }
6807
6808 if (NS_FAILED(status)) {
6809 ProcessSecurityReport(status);
6810 }
6811
6812 // If this load failed because of a security error, it may be because we
6813 // are in a captive portal - trigger an async check to make sure.
6814 int32_t nsprError = -1 * NS_ERROR_GET_CODE(status);
6815 if (mozilla::psm::IsNSSErrorCode(nsprError)) {
6816 gIOService->RecheckCaptivePortal();
6817 }
6818
6819 if (mTimingEnabled && request == mCachePump) {
6820 mCacheReadEnd = TimeStamp::Now();
6821
6822 ReportNetVSCacheTelemetry();
6823 }
6824
6825 // allow content to be cached if it was loaded successfully (bug #482935)
6826 bool contentComplete = NS_SUCCEEDED(status);
6827
6828 // honor the cancelation status even if the underlying transaction completed.
6829 if (mCanceled || NS_FAILED(mStatus)) status = mStatus;
6830
6831 if (mCachedContentIsPartial) {
6832 if (NS_SUCCEEDED(status)) {
6833 // mTransactionPump should be suspended
6834 MOZ_ASSERT(request != mTransactionPump,
6835 "byte-range transaction finished prematurely");
6836
6837 if (request == mCachePump) {
6838 bool streamDone;
6839 status = OnDoneReadingPartialCacheEntry(&streamDone);
6840 if (NS_SUCCEEDED(status) && !streamDone) return status;
6841 // otherwise, fall through and fire OnStopRequest...
6842 } else if (request == mTransactionPump) {
6843 MOZ_ASSERT(mConcurrentCacheAccess);
6844 } else
6845 NS_NOTREACHED("unexpected request");
6846 }
6847 // Do not to leave the transaction in a suspended state in error cases.
6848 if (NS_FAILED(status) && mTransaction) {
6849 nsresult rv = gHttpHandler->CancelTransaction(mTransaction, status);
6850 if (NS_FAILED(rv)) {
6851 LOG((" CancelTransaction failed (%08x)", static_cast<uint32_t>(rv)));
6852 }
6853 }
6854 }
6855
6856 nsCOMPtr<nsICompressConvStats> conv = do_QueryInterface(mCompressListener);
6857 if (conv) {
6858 conv->GetDecodedDataLength(&mDecodedBodySize);
6859 }
6860
6861 bool isFromNet = request == mTransactionPump;
6862
6863 if (mTransaction) {
6864 // determine if we should call DoAuthRetry
6865 bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
6866 mStronglyFramed = mTransaction->ResponseIsComplete();
6867 LOG(("nsHttpChannel %p has a strongly framed transaction: %d", this,
6868 mStronglyFramed));
6869
6870 //
6871 // grab references to connection in case we need to retry an
6872 // authentication request over it or use it for an upgrade
6873 // to another protocol.
6874 //
6875 // this code relies on the code in nsHttpTransaction::Close, which
6876 // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
6877 // keep the connection around after the transaction is finished.
6878 //
6879 RefPtr<nsAHttpConnection> conn;
6880 LOG((" mAuthRetryPending=%d, status=%" PRIx32 ", sticky conn cap=%d",
6881 mAuthRetryPending, static_cast<uint32_t>(status),
6882 mCaps & NS_HTTP_STICKY_CONNECTION));
6883 // We must check caps for stickinness also on the transaction because it
6884 // might have been updated by the transaction itself during inspection of
6885 // the reposnse headers yet on the socket thread (found connection based
6886 // auth schema).
6887 if ((mAuthRetryPending || NS_FAILED(status)) &&
6888 (mCaps & NS_HTTP_STICKY_CONNECTION ||
6889 mTransaction->Caps() & NS_HTTP_STICKY_CONNECTION)) {
6890 conn = mTransaction->GetConnectionReference();
6891 LOG((" transaction %p provides connection %p", mTransaction.get(),
6892 conn.get()));
6893
6894 if (conn) {
6895 if (NS_FAILED(status)) {
6896 // Close (don't reuse) the sticky connection if it's in the middle
6897 // of an NTLM negotiation and this channel has been cancelled.
6898 // There are proxy servers known to get confused when we send
6899 // a new request over such a half-stated connection.
6900 if (!mAuthConnectionRestartable) {
6901 LOG((" not reusing a half-authenticated sticky connection"));
6902 conn->DontReuse();
6903 }
6904 conn = nullptr;
6905 } else if (!conn->IsPersistent()) {
6906 // This is so far a workaround to fix leak when reusing unpersistent
6907 // connection for authentication retry. See bug 459620 comment 4
6908 // for details.
6909 LOG((" connection is not persistent, not reusing it"));
6910 conn = nullptr;
6911 }
6912 }
6913 }
6914
6915 RefPtr<nsAHttpConnection> stickyConn;
6916 if (mCaps & NS_HTTP_STICKY_CONNECTION) {
6917 stickyConn = mTransaction->GetConnectionReference();
6918 }
6919
6920 mTransferSize = mTransaction->GetTransferSize();
6921
6922 // If we are using the transaction to serve content, we also save the
6923 // time since async open in the cache entry so we can compare telemetry
6924 // between cache and net response.
6925 // Do not store the time of conditional requests because even if we
6926 // fetch the data from the server, the time includes loading of the old
6927 // cache entry which would skew the network load time.
6928 if (request == mTransactionPump && mCacheEntry && !mDidReval &&
6929 !mCustomConditionalRequest && !mAsyncOpenTime.IsNull() &&
6930 !mOnStartRequestTimestamp.IsNull()) {
6931 uint64_t onStartTime =
6932 (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
6933 uint64_t onStopTime =
6934 (TimeStamp::Now() - mAsyncOpenTime).ToMilliseconds();
6935 Unused << mCacheEntry->SetNetworkTimes(onStartTime, onStopTime);
6936 }
6937
6938 mResponseTrailers = mTransaction->TakeResponseTrailers();
6939
6940 // at this point, we're done with the transaction
6941 mTransactionTimings = mTransaction->Timings();
6942 mTransaction = nullptr;
6943 mTransactionPump = nullptr;
6944
6945 // We no longer need the dns prefetch object
6946 if (mDNSPrefetch && mDNSPrefetch->TimingsValid() &&
6947 !mTransactionTimings.requestStart.IsNull() &&
6948 !mTransactionTimings.connectStart.IsNull() &&
6949 mDNSPrefetch->EndTimestamp() <= mTransactionTimings.connectStart) {
6950 // We only need the domainLookup timestamps when not using a
6951 // persistent connection, meaning if the endTimestamp < connectStart
6952 mTransactionTimings.domainLookupStart = mDNSPrefetch->StartTimestamp();
6953 mTransactionTimings.domainLookupEnd = mDNSPrefetch->EndTimestamp();
6954 }
6955 mDNSPrefetch = nullptr;
6956
6957 // handle auth retry...
6958 if (authRetry) {
6959 mAuthRetryPending = false;
6960 status = DoAuthRetry(conn);
6961 if (NS_SUCCEEDED(status)) return NS_OK;
6962 }
6963
6964 // If DoAuthRetry failed, or if we have been cancelled since showing
6965 // the auth. dialog, then we need to send OnStartRequest now
6966 if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
6967 MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
6968 // NOTE: since we have a failure status, we can ignore the return
6969 // value from onStartRequest.
6970 LOG((" calling mListener->OnStartRequest [this=%p, listener=%p]\n", this,
6971 mListener.get()));
6972 if (mListener) {
6973 MOZ_ASSERT(!mOnStartRequestCalled,
6974 "We should not call OnStartRequest twice.");
6975 nsCOMPtr<nsIStreamListener> listener(mListener);
6976 listener->OnStartRequest(this, mListenerContext);
6977 mOnStartRequestCalled = true;
6978 } else {
6979 NS_WARNING("OnStartRequest skipped because of null listener");
6980 }
6981 }
6982
6983 // if this transaction has been replaced, then bail.
6984 if (mTransactionReplaced) {
6985 LOG(("Transaction replaced\n"));
6986 // This was just the network check for a 304 response.
6987 mFirstResponseSource = RESPONSE_PENDING;
6988 return NS_OK;
6989 }
6990
6991 if (mUpgradeProtocolCallback && stickyConn && mResponseHead &&
6992 mResponseHead->Status() == 101) {
6993 nsresult rv = gHttpHandler->ConnMgr()->CompleteUpgrade(
6994 stickyConn, mUpgradeProtocolCallback);
6995 if (NS_FAILED(rv)) {
6996 LOG((" CompleteUpgrade failed with %08x", static_cast<uint32_t>(rv)));
6997 }
6998 }
6999 }
7000
7001 // HTTP_CHANNEL_DISPOSITION TELEMETRY
7002 enum ChannelDisposition {
7003 kHttpCanceled = 0,
7004 kHttpDisk = 1,
7005 kHttpNetOK = 2,
7006 kHttpNetEarlyFail = 3,
7007 kHttpNetLateFail = 4,
7008 kHttpsCanceled = 8,
7009 kHttpsDisk = 9,
7010 kHttpsNetOK = 10,
7011 kHttpsNetEarlyFail = 11,
7012 kHttpsNetLateFail = 12
7013 } chanDisposition = kHttpCanceled;
7014
7015 // HTTP 0.9 is more likely to be an error than really 0.9, so count it that
7016 // way
7017 if (mCanceled) {
7018 chanDisposition = kHttpCanceled;
7019 } else if (!mUsedNetwork || (mRaceCacheWithNetwork &&
7020 mFirstResponseSource == RESPONSE_FROM_CACHE)) {
7021 chanDisposition = kHttpDisk;
7022 } else if (NS_SUCCEEDED(status) && mResponseHead &&
7023 mResponseHead->Version() != NS_HTTP_VERSION_0_9) {
7024 chanDisposition = kHttpNetOK;
7025 } else if (!mTransferSize) {
7026 chanDisposition = kHttpNetEarlyFail;
7027 } else {
7028 chanDisposition = kHttpNetLateFail;
7029 }
7030 if (IsHTTPS()) {
7031 // shift http to https disposition enums
7032 chanDisposition =
7033 static_cast<ChannelDisposition>(chanDisposition + kHttpsCanceled);
7034 }
7035 LOG((" nsHttpChannel::OnStopRequest ChannelDisposition %d\n",
7036 chanDisposition));
7037 Telemetry::Accumulate(Telemetry::HTTP_CHANNEL_DISPOSITION, chanDisposition);
7038
7039 // if needed, check cache entry has all data we expect
7040 if (mCacheEntry && mCachePump && mConcurrentCacheAccess && contentComplete) {
7041 int64_t size, contentLength;
7042 nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
7043 if (NS_SUCCEEDED(rv)) {
7044 if (size == int64_t(-1)) {
7045 // mayhemer TODO - we have to restart read from cache here at the size
7046 // offset
7047 MOZ_ASSERT(false);
7048 LOG(
7049 (" cache entry write is still in progress, but we just "
7050 "finished reading the cache entry"));
7051 } else if (contentLength != int64_t(-1) && contentLength != size) {
7052 LOG((" concurrent cache entry write has been interrupted"));
7053 mCachedResponseHead = Move(mResponseHead);
7054 // Ignore zero partial length because we also want to resume when
7055 // no data at all has been read from the cache.
7056 rv = MaybeSetupByteRangeRequest(size, contentLength, true);
7057 if (NS_SUCCEEDED(rv) && mIsPartialRequest) {
7058 // Prevent read from cache again
7059 mCachedContentIsValid = 0;
7060 mCachedContentIsPartial = 1;
7061
7062 // Perform the range request
7063 rv = ContinueConnect();
7064 if (NS_SUCCEEDED(rv)) {
7065 LOG((" performing range request"));
7066 mCachePump = nullptr;
7067 return NS_OK;
7068 } else {
7069 LOG((" but range request perform failed 0x%08" PRIx32,
7070 static_cast<uint32_t>(rv)));
7071 status = NS_ERROR_NET_INTERRUPT;
7072 }
7073 } else {
7074 LOG((" but range request setup failed rv=0x%08" PRIx32
7075 ", failing load",
7076 static_cast<uint32_t>(rv)));
7077 }
7078 }
7079 }
7080 }
7081
7082 mIsPending = false;
7083 mStatus = status;
7084
7085 // perform any final cache operations before we close the cache entry.
7086 if (mCacheEntry && mRequestTimeInitialized) {
7087 bool writeAccess;
7088 // New implementation just returns value of the !mCacheEntryIsReadOnly flag
7089 // passed in. Old implementation checks on nsICache::ACCESS_WRITE flag.
7090 mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
7091 if (writeAccess) {
7092 nsresult rv = FinalizeCacheEntry();
7093 if (NS_FAILED(rv)) {
7094 LOG(("FinalizeCacheEntry failed (%08x)", static_cast<uint32_t>(rv)));
7095 }
7096 }
7097 }
7098
7099 ReportRcwnStats(isFromNet);
7100
7101 // Register entry to the PerformanceStorage resource timing
7102 mozilla::dom::PerformanceStorage *performanceStorage =
7103 GetPerformanceStorage();
7104 if (performanceStorage) {
7105 performanceStorage->AddEntry(this, this);
7106 }
7107
7108 if (mListener) {
7109 LOG(("nsHttpChannel %p calling OnStopRequest\n", this));
7110 MOZ_ASSERT(mOnStartRequestCalled,
7111 "OnStartRequest should be called before OnStopRequest");
7112 MOZ_ASSERT(!mOnStopRequestCalled, "We should not call OnStopRequest twice");
7113 mListener->OnStopRequest(this, mListenerContext, status);
7114 mOnStopRequestCalled = true;
7115 }
7116
7117 // notify "http-on-stop-connect" observers
7118 gHttpHandler->OnStopRequest(this);
7119
7120 RemoveAsNonTailRequest();
7121
7122 // If a preferred alt-data type was set, this signals the consumer is
7123 // interested in reading and/or writing the alt-data representation.
7124 // We need to hold a reference to the cache entry in case the listener calls
7125 // openAlternativeOutputStream() after CloseCacheEntry() clears mCacheEntry.
7126 if (!mPreferredCachedAltDataType.IsEmpty()) {
7127 mAltDataCacheEntry = mCacheEntry;
7128 }
7129
7130 CloseCacheEntry(!contentComplete);
7131
7132 if (mOfflineCacheEntry) CloseOfflineCacheEntry();
7133
7134 if (mLoadGroup) mLoadGroup->RemoveRequest(this, nullptr, status);
7135
7136 // We don't need this info anymore
7137 CleanRedirectCacheChainIfNecessary();
7138
7139 ReleaseListeners();
7140
7141 return NS_OK;
7142 }
7143
7144 //-----------------------------------------------------------------------------
7145 // nsHttpChannel::nsIStreamListener
7146 //-----------------------------------------------------------------------------
7147
7148 class OnTransportStatusAsyncEvent : public Runnable {
7149 public:
OnTransportStatusAsyncEvent(nsITransportEventSink * aEventSink,nsresult aTransportStatus,int64_t aProgress,int64_t aProgressMax)7150 OnTransportStatusAsyncEvent(nsITransportEventSink *aEventSink,
7151 nsresult aTransportStatus, int64_t aProgress,
7152 int64_t aProgressMax)
7153 : Runnable("net::OnTransportStatusAsyncEvent"),
7154 mEventSink(aEventSink),
7155 mTransportStatus(aTransportStatus),
7156 mProgress(aProgress),
7157 mProgressMax(aProgressMax) {
7158 MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
7159 }
7160
Run()7161 NS_IMETHOD Run() override {
7162 MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
7163 if (mEventSink) {
7164 mEventSink->OnTransportStatus(nullptr, mTransportStatus, mProgress,
7165 mProgressMax);
7166 }
7167 return NS_OK;
7168 }
7169
7170 private:
7171 nsCOMPtr<nsITransportEventSink> mEventSink;
7172 nsresult mTransportStatus;
7173 int64_t mProgress;
7174 int64_t mProgressMax;
7175 };
7176
7177 NS_IMETHODIMP
OnDataAvailable(nsIRequest * request,nsISupports * ctxt,nsIInputStream * input,uint64_t offset,uint32_t count)7178 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
7179 nsIInputStream *input, uint64_t offset,
7180 uint32_t count) {
7181 nsresult rv;
7182 AUTO_PROFILER_LABEL("nsHttpChannel::OnDataAvailable", NETWORK);
7183
7184 LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%" PRIu64
7185 " count=%" PRIu32 "]\n",
7186 this, request, offset, count));
7187
7188 LOG((" requestFromCache: %d mFirstResponseSource: %d\n",
7189 request == mCachePump, static_cast<int32_t>(mFirstResponseSource)));
7190
7191 // don't send out OnDataAvailable notifications if we've been canceled.
7192 if (mCanceled) return mStatus;
7193
7194 if (mAuthRetryPending || WRONG_RACING_RESPONSE_SOURCE(request) ||
7195 (request == mTransactionPump && mTransactionReplaced)) {
7196 uint32_t n;
7197 return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
7198 }
7199
7200 MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
7201
7202 MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
7203 "transaction pump not suspended");
7204
7205 mIsReadingFromCache = (request == mCachePump);
7206
7207 if (mListener) {
7208 //
7209 // synthesize transport progress event. we do this here since we want
7210 // to delay OnProgress events until we start streaming data. this is
7211 // crucially important since it impacts the lock icon (see bug 240053).
7212 //
7213 nsresult transportStatus;
7214 if (request == mCachePump)
7215 transportStatus = NS_NET_STATUS_READING;
7216 else
7217 transportStatus = NS_NET_STATUS_RECEIVING_FROM;
7218
7219 // mResponseHead may reference new or cached headers, but either way it
7220 // holds our best estimate of the total content length. Even in the case
7221 // of a byte range request, the content length stored in the cached
7222 // response headers is what we want to use here.
7223
7224 int64_t progressMax = -1;
7225 rv = GetContentLength(&progressMax);
7226 if (NS_FAILED(rv)) {
7227 NS_WARNING("GetContentLength failed");
7228 }
7229 int64_t progress = mLogicalOffset + count;
7230
7231 if ((progress > progressMax) && (progressMax != -1)) {
7232 NS_WARNING(
7233 "unexpected progress values - "
7234 "is server exceeding content length?");
7235 }
7236
7237 // make sure params are in range for js
7238 if (!InScriptableRange(progressMax)) {
7239 progressMax = -1;
7240 }
7241
7242 if (!InScriptableRange(progress)) {
7243 progress = -1;
7244 }
7245
7246 if (NS_IsMainThread()) {
7247 OnTransportStatus(nullptr, transportStatus, progress, progressMax);
7248 } else {
7249 rv = NS_DispatchToMainThread(new OnTransportStatusAsyncEvent(
7250 this, transportStatus, progress, progressMax));
7251 NS_ENSURE_SUCCESS(rv, rv);
7252 }
7253
7254 //
7255 // we have to manually keep the logical offset of the stream up-to-date.
7256 // we cannot depend solely on the offset provided, since we may have
7257 // already streamed some data from another source (see, for example,
7258 // OnDoneReadingPartialCacheEntry).
7259 //
7260 int64_t offsetBefore = 0;
7261 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
7262 if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
7263 seekable = nullptr;
7264 }
7265
7266 nsresult rv = mListener->OnDataAvailable(this, mListenerContext, input,
7267 mLogicalOffset, count);
7268 if (NS_SUCCEEDED(rv)) {
7269 // by contract mListener must read all of "count" bytes, but
7270 // nsInputStreamPump is tolerant to seekable streams that violate that
7271 // and it will redeliver incompletely read data. So we need to do
7272 // the same thing when updating the progress counter to stay in sync.
7273 int64_t offsetAfter, delta;
7274 if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
7275 delta = offsetAfter - offsetBefore;
7276 if (delta != count) {
7277 count = delta;
7278
7279 NS_WARNING("Listener OnDataAvailable contract violation");
7280 nsCOMPtr<nsIConsoleService> consoleService =
7281 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
7282 nsAutoString message(NS_LITERAL_STRING(
7283 "http channel Listener OnDataAvailable contract violation"));
7284 if (consoleService) {
7285 consoleService->LogStringMessage(message.get());
7286 }
7287 }
7288 }
7289 mLogicalOffset += count;
7290 }
7291
7292 return rv;
7293 }
7294
7295 return NS_ERROR_ABORT;
7296 }
7297
7298 //-----------------------------------------------------------------------------
7299 // nsHttpChannel::nsIThreadRetargetableRequest
7300 //-----------------------------------------------------------------------------
7301
7302 NS_IMETHODIMP
RetargetDeliveryTo(nsIEventTarget * aNewTarget)7303 nsHttpChannel::RetargetDeliveryTo(nsIEventTarget *aNewTarget) {
7304 MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
7305
7306 NS_ENSURE_ARG(aNewTarget);
7307 if (aNewTarget->IsOnCurrentThread()) {
7308 NS_WARNING("Retargeting delivery to same thread");
7309 return NS_OK;
7310 }
7311 if (!mTransactionPump && !mCachePump) {
7312 LOG(("nsHttpChannel::RetargetDeliveryTo %p %p no pump available\n", this,
7313 aNewTarget));
7314 return NS_ERROR_NOT_AVAILABLE;
7315 }
7316
7317 nsresult rv = NS_OK;
7318 // If both cache pump and transaction pump exist, we're probably dealing
7319 // with partially cached content. So, we must be able to retarget both.
7320 nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
7321 nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
7322 if (mCachePump) {
7323 retargetableCachePump = do_QueryObject(mCachePump);
7324 // nsInputStreamPump should implement this interface.
7325 MOZ_ASSERT(retargetableCachePump);
7326 rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
7327 }
7328 if (NS_SUCCEEDED(rv) && mTransactionPump) {
7329 retargetableTransactionPump = do_QueryObject(mTransactionPump);
7330 // nsInputStreamPump should implement this interface.
7331 MOZ_ASSERT(retargetableTransactionPump);
7332 rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
7333
7334 // If retarget fails for transaction pump, we must restore mCachePump.
7335 if (NS_FAILED(rv) && retargetableCachePump) {
7336 nsCOMPtr<nsIEventTarget> main = GetMainThreadEventTarget();
7337 NS_ENSURE_TRUE(main, NS_ERROR_UNEXPECTED);
7338 rv = retargetableCachePump->RetargetDeliveryTo(main);
7339 }
7340 }
7341 return rv;
7342 }
7343
7344 NS_IMETHODIMP
GetDeliveryTarget(nsIEventTarget ** aEventTarget)7345 nsHttpChannel::GetDeliveryTarget(nsIEventTarget **aEventTarget) {
7346 if (mCachePump) {
7347 return mCachePump->GetDeliveryTarget(aEventTarget);
7348 }
7349 if (mTransactionPump) {
7350 return mTransactionPump->GetDeliveryTarget(aEventTarget);
7351 }
7352 return NS_ERROR_NOT_AVAILABLE;
7353 }
7354
7355 //-----------------------------------------------------------------------------
7356 // nsHttpChannel::nsThreadRetargetableStreamListener
7357 //-----------------------------------------------------------------------------
7358
7359 NS_IMETHODIMP
CheckListenerChain()7360 nsHttpChannel::CheckListenerChain() {
7361 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
7362 nsresult rv = NS_OK;
7363 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
7364 do_QueryInterface(mListener, &rv);
7365 if (retargetableListener) {
7366 rv = retargetableListener->CheckListenerChain();
7367 }
7368 return rv;
7369 }
7370
7371 //-----------------------------------------------------------------------------
7372 // nsHttpChannel::nsITransportEventSink
7373 //-----------------------------------------------------------------------------
7374
7375 NS_IMETHODIMP
OnTransportStatus(nsITransport * trans,nsresult status,int64_t progress,int64_t progressMax)7376 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
7377 int64_t progress, int64_t progressMax) {
7378 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
7379 // cache the progress sink so we don't have to query for it each time.
7380 if (!mProgressSink) GetCallback(mProgressSink);
7381
7382 if (status == NS_NET_STATUS_CONNECTED_TO ||
7383 status == NS_NET_STATUS_WAITING_FOR) {
7384 if (mTransaction) {
7385 mTransaction->GetNetworkAddresses(mSelfAddr, mPeerAddr);
7386 } else {
7387 nsCOMPtr<nsISocketTransport> socketTransport = do_QueryInterface(trans);
7388 if (socketTransport) {
7389 socketTransport->GetSelfAddr(&mSelfAddr);
7390 socketTransport->GetPeerAddr(&mPeerAddr);
7391 }
7392 }
7393 }
7394
7395 // block socket status event after Cancel or OnStopRequest has been called.
7396 if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending) {
7397 LOG(("sending progress%s notification [this=%p status=%" PRIx32
7398 " progress=%" PRId64 "/%" PRId64 "]\n",
7399 (mLoadFlags & LOAD_BACKGROUND) ? "" : " and status", this,
7400 static_cast<uint32_t>(status), progress, progressMax));
7401
7402 if (!(mLoadFlags & LOAD_BACKGROUND)) {
7403 nsAutoCString host;
7404 mURI->GetHost(host);
7405 mProgressSink->OnStatus(this, nullptr, status,
7406 NS_ConvertUTF8toUTF16(host).get());
7407 }
7408
7409 if (progress > 0) {
7410 if ((progress > progressMax) && (progressMax != -1)) {
7411 NS_WARNING("unexpected progress values");
7412 }
7413
7414 // Try to get mProgressSink if it was nulled out during OnStatus.
7415 if (!mProgressSink) {
7416 GetCallback(mProgressSink);
7417 }
7418 if (mProgressSink) {
7419 mProgressSink->OnProgress(this, nullptr, progress, progressMax);
7420 }
7421 }
7422 }
7423
7424 return NS_OK;
7425 }
7426
7427 //-----------------------------------------------------------------------------
7428 // nsHttpChannel::nsICacheInfoChannel
7429 //-----------------------------------------------------------------------------
7430
7431 NS_IMETHODIMP
IsFromCache(bool * value)7432 nsHttpChannel::IsFromCache(bool *value) {
7433 if (!mIsPending) return NS_ERROR_NOT_AVAILABLE;
7434
7435 if (!mRaceCacheWithNetwork) {
7436 // return false if reading a partial cache entry; the data isn't
7437 // entirely from the cache!
7438 *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
7439 mCachedContentIsValid && !mCachedContentIsPartial;
7440 return NS_OK;
7441 }
7442
7443 // If we are racing network and cache (or skipping the cache)
7444 // we just return the first response source.
7445 *value = mFirstResponseSource == RESPONSE_FROM_CACHE;
7446
7447 return NS_OK;
7448 }
7449
7450 NS_IMETHODIMP
GetCacheEntryId(uint64_t * aCacheEntryId)7451 nsHttpChannel::GetCacheEntryId(uint64_t *aCacheEntryId) {
7452 bool fromCache = false;
7453 if (NS_FAILED(IsFromCache(&fromCache)) || !fromCache || !mCacheEntry ||
7454 NS_FAILED(mCacheEntry->GetCacheEntryId(aCacheEntryId))) {
7455 return NS_ERROR_NOT_AVAILABLE;
7456 }
7457
7458 return NS_OK;
7459 }
7460
7461 NS_IMETHODIMP
GetCacheTokenFetchCount(int32_t * _retval)7462 nsHttpChannel::GetCacheTokenFetchCount(int32_t *_retval) {
7463 NS_ENSURE_ARG_POINTER(_retval);
7464 nsCOMPtr<nsICacheEntry> cacheEntry =
7465 mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
7466 if (!cacheEntry) {
7467 return NS_ERROR_NOT_AVAILABLE;
7468 }
7469
7470 return cacheEntry->GetFetchCount(_retval);
7471 }
7472
7473 NS_IMETHODIMP
GetCacheTokenExpirationTime(uint32_t * _retval)7474 nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval) {
7475 NS_ENSURE_ARG_POINTER(_retval);
7476 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
7477
7478 return mCacheEntry->GetExpirationTime(_retval);
7479 }
7480
7481 NS_IMETHODIMP
GetCacheTokenCachedCharset(nsACString & _retval)7482 nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval) {
7483 nsresult rv;
7484
7485 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
7486
7487 nsCString cachedCharset;
7488 rv = mCacheEntry->GetMetaDataElement("charset", getter_Copies(cachedCharset));
7489 if (NS_SUCCEEDED(rv)) _retval = cachedCharset;
7490
7491 return rv;
7492 }
7493
7494 NS_IMETHODIMP
SetCacheTokenCachedCharset(const nsACString & aCharset)7495 nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset) {
7496 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
7497
7498 return mCacheEntry->SetMetaDataElement("charset",
7499 PromiseFlatCString(aCharset).get());
7500 }
7501
7502 NS_IMETHODIMP
SetAllowStaleCacheContent(bool aAllowStaleCacheContent)7503 nsHttpChannel::SetAllowStaleCacheContent(bool aAllowStaleCacheContent) {
7504 LOG(("nsHttpChannel::SetAllowStaleCacheContent [this=%p, allow=%d]", this,
7505 aAllowStaleCacheContent));
7506 mAllowStaleCacheContent = aAllowStaleCacheContent;
7507 return NS_OK;
7508 }
7509 NS_IMETHODIMP
GetAllowStaleCacheContent(bool * aAllowStaleCacheContent)7510 nsHttpChannel::GetAllowStaleCacheContent(bool *aAllowStaleCacheContent) {
7511 NS_ENSURE_ARG(aAllowStaleCacheContent);
7512 *aAllowStaleCacheContent = mAllowStaleCacheContent;
7513 return NS_OK;
7514 }
7515
7516 NS_IMETHODIMP
PreferAlternativeDataType(const nsACString & aType)7517 nsHttpChannel::PreferAlternativeDataType(const nsACString &aType) {
7518 ENSURE_CALLED_BEFORE_ASYNC_OPEN();
7519 mPreferredCachedAltDataType = aType;
7520 return NS_OK;
7521 }
7522
7523 NS_IMETHODIMP
GetPreferredAlternativeDataType(nsACString & aType)7524 nsHttpChannel::GetPreferredAlternativeDataType(nsACString &aType) {
7525 aType = mPreferredCachedAltDataType;
7526 return NS_OK;
7527 }
7528
7529 NS_IMETHODIMP
GetAlternativeDataType(nsACString & aType)7530 nsHttpChannel::GetAlternativeDataType(nsACString &aType) {
7531 // must be called during or after OnStartRequest
7532 if (!mAfterOnStartRequestBegun) {
7533 return NS_ERROR_NOT_AVAILABLE;
7534 }
7535 aType = mAvailableCachedAltDataType;
7536 return NS_OK;
7537 }
7538
7539 NS_IMETHODIMP
OpenAlternativeOutputStream(const nsACString & type,nsIOutputStream ** _retval)7540 nsHttpChannel::OpenAlternativeOutputStream(const nsACString &type,
7541 nsIOutputStream **_retval) {
7542 // OnStopRequest will clear mCacheEntry, but we may use mAltDataCacheEntry
7543 // if the consumer called PreferAlternativeDataType()
7544 nsCOMPtr<nsICacheEntry> cacheEntry =
7545 mCacheEntry ? mCacheEntry : mAltDataCacheEntry;
7546 if (!cacheEntry) {
7547 return NS_ERROR_NOT_AVAILABLE;
7548 }
7549 nsresult rv = cacheEntry->OpenAlternativeOutputStream(type, _retval);
7550 if (NS_SUCCEEDED(rv)) {
7551 // Clear this metadata flag in case it exists.
7552 // The caller of this method may set it again.
7553 cacheEntry->SetMetaDataElement("alt-data-from-child", nullptr);
7554 }
7555 return rv;
7556 }
7557
7558 //-----------------------------------------------------------------------------
7559 // nsHttpChannel::nsICachingChannel
7560 //-----------------------------------------------------------------------------
7561
7562 NS_IMETHODIMP
GetCacheToken(nsISupports ** token)7563 nsHttpChannel::GetCacheToken(nsISupports **token) {
7564 NS_ENSURE_ARG_POINTER(token);
7565 if (!mCacheEntry) return NS_ERROR_NOT_AVAILABLE;
7566 return CallQueryInterface(mCacheEntry, token);
7567 }
7568
7569 NS_IMETHODIMP
SetCacheToken(nsISupports * token)7570 nsHttpChannel::SetCacheToken(nsISupports *token) {
7571 return NS_ERROR_NOT_IMPLEMENTED;
7572 }
7573
7574 NS_IMETHODIMP
GetOfflineCacheToken(nsISupports ** token)7575 nsHttpChannel::GetOfflineCacheToken(nsISupports **token) {
7576 NS_ENSURE_ARG_POINTER(token);
7577 if (!mOfflineCacheEntry) return NS_ERROR_NOT_AVAILABLE;
7578 return CallQueryInterface(mOfflineCacheEntry, token);
7579 }
7580
7581 NS_IMETHODIMP
SetOfflineCacheToken(nsISupports * token)7582 nsHttpChannel::SetOfflineCacheToken(nsISupports *token) {
7583 return NS_ERROR_NOT_IMPLEMENTED;
7584 }
7585
7586 NS_IMETHODIMP
GetCacheKey(nsISupports ** key)7587 nsHttpChannel::GetCacheKey(nsISupports **key) {
7588 nsresult rv;
7589 NS_ENSURE_ARG_POINTER(key);
7590
7591 LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
7592
7593 *key = nullptr;
7594
7595 nsCOMPtr<nsISupportsPRUint32> container =
7596 do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
7597
7598 if (!container) return NS_ERROR_OUT_OF_MEMORY;
7599
7600 rv = container->SetData(mPostID);
7601 if (NS_FAILED(rv)) return rv;
7602
7603 return CallQueryInterface(container.get(), key);
7604 }
7605
7606 NS_IMETHODIMP
SetCacheKey(nsISupports * key)7607 nsHttpChannel::SetCacheKey(nsISupports *key) {
7608 nsresult rv;
7609
7610 LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
7611
7612 ENSURE_CALLED_BEFORE_CONNECT();
7613
7614 if (!key)
7615 mPostID = 0;
7616 else {
7617 // extract the post id
7618 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
7619 if (NS_FAILED(rv)) return rv;
7620
7621 rv = container->GetData(&mPostID);
7622 if (NS_FAILED(rv)) return rv;
7623 }
7624 return NS_OK;
7625 }
7626
7627 NS_IMETHODIMP
GetCacheOnlyMetadata(bool * aOnlyMetadata)7628 nsHttpChannel::GetCacheOnlyMetadata(bool *aOnlyMetadata) {
7629 NS_ENSURE_ARG(aOnlyMetadata);
7630 *aOnlyMetadata = mCacheOnlyMetadata;
7631 return NS_OK;
7632 }
7633
7634 NS_IMETHODIMP
SetCacheOnlyMetadata(bool aOnlyMetadata)7635 nsHttpChannel::SetCacheOnlyMetadata(bool aOnlyMetadata) {
7636 LOG(("nsHttpChannel::SetCacheOnlyMetadata [this=%p only-metadata=%d]\n", this,
7637 aOnlyMetadata));
7638
7639 ENSURE_CALLED_BEFORE_ASYNC_OPEN();
7640
7641 mCacheOnlyMetadata = aOnlyMetadata;
7642 if (aOnlyMetadata) {
7643 mLoadFlags |= LOAD_ONLY_IF_MODIFIED;
7644 }
7645
7646 return NS_OK;
7647 }
7648
7649 NS_IMETHODIMP
GetPin(bool * aPin)7650 nsHttpChannel::GetPin(bool *aPin) {
7651 NS_ENSURE_ARG(aPin);
7652 *aPin = mPinCacheContent;
7653 return NS_OK;
7654 }
7655
7656 NS_IMETHODIMP
SetPin(bool aPin)7657 nsHttpChannel::SetPin(bool aPin) {
7658 LOG(("nsHttpChannel::SetPin [this=%p pin=%d]\n", this, aPin));
7659
7660 ENSURE_CALLED_BEFORE_CONNECT();
7661
7662 mPinCacheContent = aPin;
7663 return NS_OK;
7664 }
7665
7666 NS_IMETHODIMP
ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture)7667 nsHttpChannel::ForceCacheEntryValidFor(uint32_t aSecondsToTheFuture) {
7668 if (!mCacheEntry) {
7669 LOG(
7670 ("nsHttpChannel::ForceCacheEntryValidFor found no cache entry "
7671 "for this channel [this=%p].",
7672 this));
7673 } else {
7674 mCacheEntry->ForceValidFor(aSecondsToTheFuture);
7675
7676 nsAutoCString key;
7677 mCacheEntry->GetKey(key);
7678
7679 LOG(
7680 ("nsHttpChannel::ForceCacheEntryValidFor successfully forced valid "
7681 "entry with key %s for %d seconds. [this=%p]",
7682 key.get(), aSecondsToTheFuture, this));
7683 }
7684
7685 return NS_OK;
7686 }
7687
7688 //-----------------------------------------------------------------------------
7689 // nsHttpChannel::nsIResumableChannel
7690 //-----------------------------------------------------------------------------
7691
7692 NS_IMETHODIMP
ResumeAt(uint64_t aStartPos,const nsACString & aEntityID)7693 nsHttpChannel::ResumeAt(uint64_t aStartPos, const nsACString &aEntityID) {
7694 LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%" PRIu64 " id='%s']\n", this,
7695 aStartPos, PromiseFlatCString(aEntityID).get()));
7696 mEntityID = aEntityID;
7697 mStartPos = aStartPos;
7698 mResuming = true;
7699 return NS_OK;
7700 }
7701
DoAuthRetry(nsAHttpConnection * conn)7702 nsresult nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn) {
7703 LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
7704
7705 MOZ_ASSERT(!mTransaction, "should not have a transaction");
7706 nsresult rv;
7707
7708 // toggle mIsPending to allow nsIObserver implementations to modify
7709 // the request headers (bug 95044).
7710 mIsPending = false;
7711
7712 // fetch cookies, and add them to the request header.
7713 // the server response could have included cookies that must be sent with
7714 // this authentication attempt (bug 84794).
7715 // TODO: save cookies from auth response and send them here (bug 572151).
7716 AddCookiesToRequest();
7717
7718 // notify "http-on-modify-request" observers
7719 CallOnModifyRequestObservers();
7720
7721 mIsPending = true;
7722
7723 // get rid of the old response headers
7724 mResponseHead = nullptr;
7725
7726 // rewind the upload stream
7727 if (mUploadStream) {
7728 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
7729 if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
7730 }
7731
7732 // always set sticky connection flag
7733 mCaps |= NS_HTTP_STICKY_CONNECTION;
7734 // and when needed, allow restart regardless the sticky flag
7735 if (mAuthConnectionRestartable) {
7736 LOG((" connection made restartable"));
7737 mCaps |= NS_HTTP_CONNECTION_RESTARTABLE;
7738 mAuthConnectionRestartable = false;
7739 } else {
7740 LOG((" connection made non-restartable"));
7741 mCaps &= ~NS_HTTP_CONNECTION_RESTARTABLE;
7742 }
7743
7744 // and create a new one...
7745 rv = SetupTransaction();
7746 if (NS_FAILED(rv)) return rv;
7747
7748 // transfer ownership of connection to transaction
7749 if (conn) mTransaction->SetConnection(conn);
7750
7751 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
7752 if (NS_FAILED(rv)) return rv;
7753
7754 rv = mTransactionPump->AsyncRead(this, nullptr);
7755 if (NS_FAILED(rv)) return rv;
7756
7757 uint32_t suspendCount = mSuspendCount;
7758 while (suspendCount--) mTransactionPump->Suspend();
7759
7760 return NS_OK;
7761 }
7762
7763 //-----------------------------------------------------------------------------
7764 // nsHttpChannel::nsIApplicationCacheChannel
7765 //-----------------------------------------------------------------------------
7766
7767 NS_IMETHODIMP
GetApplicationCache(nsIApplicationCache ** out)7768 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out) {
7769 NS_IF_ADDREF(*out = mApplicationCache);
7770 return NS_OK;
7771 }
7772
7773 NS_IMETHODIMP
SetApplicationCache(nsIApplicationCache * appCache)7774 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache) {
7775 ENSURE_CALLED_BEFORE_CONNECT();
7776
7777 mApplicationCache = appCache;
7778 return NS_OK;
7779 }
7780
7781 NS_IMETHODIMP
GetApplicationCacheForWrite(nsIApplicationCache ** out)7782 nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out) {
7783 NS_IF_ADDREF(*out = mApplicationCacheForWrite);
7784 return NS_OK;
7785 }
7786
7787 NS_IMETHODIMP
SetApplicationCacheForWrite(nsIApplicationCache * appCache)7788 nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache) {
7789 ENSURE_CALLED_BEFORE_CONNECT();
7790
7791 mApplicationCacheForWrite = appCache;
7792 return NS_OK;
7793 }
7794
7795 NS_IMETHODIMP
GetLoadedFromApplicationCache(bool * aLoadedFromApplicationCache)7796 nsHttpChannel::GetLoadedFromApplicationCache(
7797 bool *aLoadedFromApplicationCache) {
7798 *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
7799 return NS_OK;
7800 }
7801
7802 NS_IMETHODIMP
GetInheritApplicationCache(bool * aInherit)7803 nsHttpChannel::GetInheritApplicationCache(bool *aInherit) {
7804 *aInherit = mInheritApplicationCache;
7805 return NS_OK;
7806 }
7807
7808 NS_IMETHODIMP
SetInheritApplicationCache(bool aInherit)7809 nsHttpChannel::SetInheritApplicationCache(bool aInherit) {
7810 ENSURE_CALLED_BEFORE_CONNECT();
7811
7812 mInheritApplicationCache = aInherit;
7813 return NS_OK;
7814 }
7815
7816 NS_IMETHODIMP
GetChooseApplicationCache(bool * aChoose)7817 nsHttpChannel::GetChooseApplicationCache(bool *aChoose) {
7818 *aChoose = mChooseApplicationCache;
7819 return NS_OK;
7820 }
7821
7822 NS_IMETHODIMP
SetChooseApplicationCache(bool aChoose)7823 nsHttpChannel::SetChooseApplicationCache(bool aChoose) {
7824 ENSURE_CALLED_BEFORE_CONNECT();
7825
7826 mChooseApplicationCache = aChoose;
7827 return NS_OK;
7828 }
7829
7830 nsHttpChannel::OfflineCacheEntryAsForeignMarker *
GetOfflineCacheEntryAsForeignMarker()7831 nsHttpChannel::GetOfflineCacheEntryAsForeignMarker() {
7832 if (!mApplicationCache) return nullptr;
7833
7834 return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
7835 }
7836
MarkAsForeign()7837 nsresult nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign() {
7838 nsresult rv;
7839
7840 nsCOMPtr<nsIURI> noRefURI;
7841 rv = mCacheURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
7842 NS_ENSURE_SUCCESS(rv, rv);
7843
7844 nsAutoCString spec;
7845 rv = noRefURI->GetAsciiSpec(spec);
7846 NS_ENSURE_SUCCESS(rv, rv);
7847
7848 return mApplicationCache->MarkEntry(spec, nsIApplicationCache::ITEM_FOREIGN);
7849 }
7850
7851 NS_IMETHODIMP
MarkOfflineCacheEntryAsForeign()7852 nsHttpChannel::MarkOfflineCacheEntryAsForeign() {
7853 nsresult rv;
7854
7855 nsAutoPtr<OfflineCacheEntryAsForeignMarker> marker(
7856 GetOfflineCacheEntryAsForeignMarker());
7857
7858 if (!marker) return NS_ERROR_NOT_AVAILABLE;
7859
7860 rv = marker->MarkAsForeign();
7861 NS_ENSURE_SUCCESS(rv, rv);
7862
7863 return NS_OK;
7864 }
7865
7866 //-----------------------------------------------------------------------------
7867 // nsHttpChannel::nsIAsyncVerifyRedirectCallback
7868 //-----------------------------------------------------------------------------
7869
WaitForRedirectCallback()7870 nsresult nsHttpChannel::WaitForRedirectCallback() {
7871 nsresult rv;
7872 LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
7873
7874 if (mTransactionPump) {
7875 rv = mTransactionPump->Suspend();
7876 NS_ENSURE_SUCCESS(rv, rv);
7877 }
7878 if (mCachePump) {
7879 rv = mCachePump->Suspend();
7880 if (NS_FAILED(rv) && mTransactionPump) {
7881 #ifdef DEBUG
7882 nsresult resume =
7883 #endif
7884 mTransactionPump->Resume();
7885 MOZ_ASSERT(NS_SUCCEEDED(resume), "Failed to resume transaction pump");
7886 }
7887 NS_ENSURE_SUCCESS(rv, rv);
7888 }
7889
7890 mWaitingForRedirectCallback = true;
7891 return NS_OK;
7892 }
7893
7894 NS_IMETHODIMP
OnRedirectVerifyCallback(nsresult result)7895 nsHttpChannel::OnRedirectVerifyCallback(nsresult result) {
7896 LOG(
7897 ("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
7898 "result=%" PRIx32 " stack=%zu mWaitingForRedirectCallback=%u\n",
7899 this, static_cast<uint32_t>(result), mRedirectFuncStack.Length(),
7900 mWaitingForRedirectCallback));
7901 MOZ_ASSERT(mWaitingForRedirectCallback,
7902 "Someone forgot to call WaitForRedirectCallback() ?!");
7903 mWaitingForRedirectCallback = false;
7904
7905 if (mCanceled && NS_SUCCEEDED(result)) result = NS_BINDING_ABORTED;
7906
7907 for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
7908 --i;
7909 // Pop the last function pushed to the stack
7910 nsContinueRedirectionFunc func = mRedirectFuncStack[i];
7911 mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
7912
7913 // Call it with the result we got from the callback or the deeper
7914 // function call.
7915 result = (this->*func)(result);
7916
7917 // If a new function has been pushed to the stack and placed us in the
7918 // waiting state, we need to break the chain and wait for the callback
7919 // again.
7920 if (mWaitingForRedirectCallback) break;
7921 }
7922
7923 if (NS_FAILED(result) && !mCanceled) {
7924 // First, cancel this channel if we are in failure state to set mStatus
7925 // and let it be propagated to pumps.
7926 Cancel(result);
7927 }
7928
7929 if (!mWaitingForRedirectCallback) {
7930 // We are not waiting for the callback. At this moment we must release
7931 // reference to the redirect target channel, otherwise we may leak.
7932 mRedirectChannel = nullptr;
7933 }
7934
7935 // We always resume the pumps here. If all functions on stack have been
7936 // called we need OnStopRequest to be triggered, and if we broke out of the
7937 // loop above (and are thus waiting for a new callback) the suspension
7938 // count must be balanced in the pumps.
7939 if (mTransactionPump) mTransactionPump->Resume();
7940 if (mCachePump) mCachePump->Resume();
7941
7942 return result;
7943 }
7944
PushRedirectAsyncFunc(nsContinueRedirectionFunc func)7945 void nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func) {
7946 mRedirectFuncStack.AppendElement(func);
7947 }
7948
PopRedirectAsyncFunc(nsContinueRedirectionFunc func)7949 void nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func) {
7950 MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
7951 "Trying to pop wrong method from redirect async stack!");
7952
7953 mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
7954 }
7955
7956 //-----------------------------------------------------------------------------
7957 // nsIDNSListener functions
7958 //-----------------------------------------------------------------------------
7959
7960 NS_IMETHODIMP
OnLookupComplete(nsICancelable * request,nsIDNSRecord * rec,nsresult status)7961 nsHttpChannel::OnLookupComplete(nsICancelable *request, nsIDNSRecord *rec,
7962 nsresult status) {
7963 MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
7964
7965 LOG(
7966 ("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
7967 "%s status[0x%" PRIx32 "]\n",
7968 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
7969 NS_SUCCEEDED(status) ? "success" : "failure",
7970 static_cast<uint32_t>(status)));
7971
7972 // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
7973 // validly null if OnStopRequest has already been called.
7974 // We only need the domainLookup timestamps when not loading from cache
7975 if (mDNSPrefetch && mDNSPrefetch->TimingsValid() && mTransaction) {
7976 TimeStamp connectStart = mTransaction->GetConnectStart();
7977 TimeStamp requestStart = mTransaction->GetRequestStart();
7978 // We only set the domainLookup timestamps if we're not using a
7979 // persistent connection.
7980 if (requestStart.IsNull() && connectStart.IsNull()) {
7981 mTransaction->SetDomainLookupStart(mDNSPrefetch->StartTimestamp());
7982 mTransaction->SetDomainLookupEnd(mDNSPrefetch->EndTimestamp());
7983 }
7984 }
7985 mDNSPrefetch = nullptr;
7986
7987 // Unset DNS cache refresh if it was requested,
7988 if (mCaps & NS_HTTP_REFRESH_DNS) {
7989 mCaps &= ~NS_HTTP_REFRESH_DNS;
7990 if (mTransaction) {
7991 mTransaction->SetDNSWasRefreshed();
7992 }
7993 }
7994
7995 return NS_OK;
7996 }
7997
7998 //-----------------------------------------------------------------------------
7999 // nsHttpChannel internal functions
8000 //-----------------------------------------------------------------------------
8001
8002 // Creates an URI to the given location using current URI for base and charset
CreateNewURI(const char * loc,nsIURI ** newURI)8003 nsresult nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI) {
8004 nsCOMPtr<nsIIOService> ioService;
8005 nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
8006 if (NS_FAILED(rv)) return rv;
8007
8008 return ioService->NewURI(nsDependentCString(loc), nullptr, mURI, newURI);
8009 }
8010
MaybeInvalidateCacheEntryForSubsequentGet()8011 void nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet() {
8012 // See RFC 2616 section 5.1.1. These are considered valid
8013 // methods which DO NOT invalidate cache-entries for the
8014 // referred resource. POST, PUT and DELETE as well as any
8015 // other method not listed here will potentially invalidate
8016 // any cached copy of the resource
8017 if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
8018 mRequestHead.IsHead() || mRequestHead.IsTrace() ||
8019 mRequestHead.IsConnect()) {
8020 return;
8021 }
8022
8023 // Invalidate the request-uri.
8024 if (LOG_ENABLED()) {
8025 nsAutoCString key;
8026 mURI->GetAsciiSpec(key);
8027 LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n", this,
8028 key.get()));
8029 }
8030
8031 DoInvalidateCacheEntry(mURI);
8032
8033 // Invalidate Location-header if set
8034 nsAutoCString location;
8035 Unused << mResponseHead->GetHeader(nsHttp::Location, location);
8036 if (!location.IsEmpty()) {
8037 LOG((" Location-header=%s\n", location.get()));
8038 InvalidateCacheEntryForLocation(location.get());
8039 }
8040
8041 // Invalidate Content-Location-header if set
8042 Unused << mResponseHead->GetHeader(nsHttp::Content_Location, location);
8043 if (!location.IsEmpty()) {
8044 LOG((" Content-Location-header=%s\n", location.get()));
8045 InvalidateCacheEntryForLocation(location.get());
8046 }
8047 }
8048
InvalidateCacheEntryForLocation(const char * location)8049 void nsHttpChannel::InvalidateCacheEntryForLocation(const char *location) {
8050 nsAutoCString tmpCacheKey, tmpSpec;
8051 nsCOMPtr<nsIURI> resultingURI;
8052 nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
8053 if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
8054 DoInvalidateCacheEntry(resultingURI);
8055 } else {
8056 LOG((" hosts not matching\n"));
8057 }
8058 }
8059
DoInvalidateCacheEntry(nsIURI * aURI)8060 void nsHttpChannel::DoInvalidateCacheEntry(nsIURI *aURI) {
8061 // NOTE:
8062 // Following comments 24,32 and 33 in bug #327765, we only care about
8063 // the cache in the protocol-handler, not the application cache.
8064 // The logic below deviates from the original logic in OpenCacheEntry on
8065 // one point by using only READ_ONLY access-policy. I think this is safe.
8066
8067 nsresult rv;
8068
8069 nsAutoCString key;
8070 if (LOG_ENABLED()) {
8071 aURI->GetAsciiSpec(key);
8072 }
8073
8074 LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
8075
8076 nsCOMPtr<nsICacheStorageService> cacheStorageService(
8077 services::GetCacheStorageService());
8078 rv = cacheStorageService ? NS_OK : NS_ERROR_FAILURE;
8079
8080 nsCOMPtr<nsICacheStorage> cacheStorage;
8081 if (NS_SUCCEEDED(rv)) {
8082 RefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
8083 rv = cacheStorageService->DiskCacheStorage(info, false,
8084 getter_AddRefs(cacheStorage));
8085 }
8086
8087 if (NS_SUCCEEDED(rv)) {
8088 rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr);
8089 }
8090
8091 LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(),
8092 int(rv)));
8093 }
8094
AsyncOnExamineCachedResponse()8095 void nsHttpChannel::AsyncOnExamineCachedResponse() {
8096 gHttpHandler->OnExamineCachedResponse(this);
8097 }
8098
UpdateAggregateCallbacks()8099 void nsHttpChannel::UpdateAggregateCallbacks() {
8100 if (!mTransaction) {
8101 return;
8102 }
8103 nsCOMPtr<nsIInterfaceRequestor> callbacks;
8104 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
8105 GetCurrentThreadEventTarget(),
8106 getter_AddRefs(callbacks));
8107 mTransaction->SetSecurityCallbacks(callbacks);
8108 }
8109
8110 NS_IMETHODIMP
SetLoadGroup(nsILoadGroup * aLoadGroup)8111 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup) {
8112 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
8113
8114 nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
8115 if (NS_SUCCEEDED(rv)) {
8116 UpdateAggregateCallbacks();
8117 }
8118 return rv;
8119 }
8120
8121 NS_IMETHODIMP
SetNotificationCallbacks(nsIInterfaceRequestor * aCallbacks)8122 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks) {
8123 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
8124
8125 nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
8126 if (NS_SUCCEEDED(rv)) {
8127 UpdateAggregateCallbacks();
8128 }
8129 return rv;
8130 }
8131
8132 NS_IMETHODIMP
GetResponseSynthesized(bool * aSynthesized)8133 nsHttpChannel::GetResponseSynthesized(bool *aSynthesized) {
8134 NS_ENSURE_ARG_POINTER(aSynthesized);
8135 *aSynthesized = false;
8136 return NS_OK;
8137 }
8138
AwaitingCacheCallbacks()8139 bool nsHttpChannel::AwaitingCacheCallbacks() {
8140 return mCacheEntriesToWaitFor != 0;
8141 }
8142
SetPushedStream(Http2PushedStreamWrapper * stream)8143 void nsHttpChannel::SetPushedStream(Http2PushedStreamWrapper *stream) {
8144 MOZ_ASSERT(stream);
8145 MOZ_ASSERT(!mPushedStream);
8146 mPushedStream = stream;
8147 }
8148
OnPush(const nsACString & url,Http2PushedStreamWrapper * pushedStream)8149 nsresult nsHttpChannel::OnPush(const nsACString &url,
8150 Http2PushedStreamWrapper *pushedStream) {
8151 MOZ_ASSERT(NS_IsMainThread());
8152 LOG(("nsHttpChannel::OnPush [this=%p]\n", this));
8153
8154 MOZ_ASSERT(mCaps & NS_HTTP_ONPUSH_LISTENER);
8155 nsCOMPtr<nsIHttpPushListener> pushListener;
8156 NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
8157 NS_GET_IID(nsIHttpPushListener),
8158 getter_AddRefs(pushListener));
8159
8160 MOZ_ASSERT(pushListener);
8161 if (!pushListener) {
8162 LOG(
8163 ("nsHttpChannel::OnPush [this=%p] notification callbacks do not "
8164 "implement nsIHttpPushListener\n",
8165 this));
8166 return NS_ERROR_UNEXPECTED;
8167 }
8168
8169 nsCOMPtr<nsIURI> pushResource;
8170 nsresult rv;
8171
8172 // Create a Channel for the Push Resource
8173 rv = NS_NewURI(getter_AddRefs(pushResource), url);
8174 if (NS_FAILED(rv)) {
8175 return NS_ERROR_FAILURE;
8176 }
8177
8178 nsCOMPtr<nsIIOService> ioService;
8179 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
8180 NS_ENSURE_SUCCESS(rv, rv);
8181
8182 nsCOMPtr<nsIChannel> pushChannel;
8183 rv = NS_NewChannelInternal(getter_AddRefs(pushChannel), pushResource,
8184 mLoadInfo,
8185 nullptr, // PerformanceStorage
8186 nullptr, // aLoadGroup
8187 nullptr, // aCallbacks
8188 nsIRequest::LOAD_NORMAL, ioService);
8189 NS_ENSURE_SUCCESS(rv, rv);
8190
8191 nsCOMPtr<nsIHttpChannel> pushHttpChannel = do_QueryInterface(pushChannel);
8192 MOZ_ASSERT(pushHttpChannel);
8193 if (!pushHttpChannel) {
8194 return NS_ERROR_UNEXPECTED;
8195 }
8196
8197 RefPtr<nsHttpChannel> channel;
8198 CallQueryInterface(pushHttpChannel, channel.StartAssignment());
8199 MOZ_ASSERT(channel);
8200 if (!channel) {
8201 return NS_ERROR_UNEXPECTED;
8202 }
8203
8204 // new channel needs mrqeuesthead and headers from pushedStream
8205 channel->mRequestHead.ParseHeaderSet(
8206 pushedStream->GetRequestString().BeginWriting());
8207
8208 channel->mLoadGroup = mLoadGroup;
8209 channel->mLoadInfo = mLoadInfo;
8210 channel->mCallbacks = mCallbacks;
8211
8212 // Link the pushed stream with the new channel and call listener
8213 channel->SetPushedStream(pushedStream);
8214 rv = pushListener->OnPush(this, pushHttpChannel);
8215 return rv;
8216 }
8217
8218 // static
IsRedirectStatus(uint32_t status)8219 bool nsHttpChannel::IsRedirectStatus(uint32_t status) {
8220 // 305 disabled as a security measure (see bug 187996).
8221 return status == 300 || status == 301 || status == 302 || status == 303 ||
8222 status == 307 || status == 308;
8223 }
8224
SetCouldBeSynthesized()8225 void nsHttpChannel::SetCouldBeSynthesized() {
8226 MOZ_ASSERT(!BypassServiceWorker());
8227 mResponseCouldBeSynthesized = true;
8228 }
8229
SetConnectionInfo(nsHttpConnectionInfo * aCI)8230 void nsHttpChannel::SetConnectionInfo(nsHttpConnectionInfo *aCI) {
8231 mConnectionInfo = aCI ? aCI->Clone() : nullptr;
8232 }
8233
8234 NS_IMETHODIMP
OnPreflightSucceeded()8235 nsHttpChannel::OnPreflightSucceeded() {
8236 MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
8237 mIsCorsPreflightDone = 1;
8238 mPreflightChannel = nullptr;
8239
8240 return ContinueConnect();
8241 }
8242
8243 NS_IMETHODIMP
OnPreflightFailed(nsresult aError)8244 nsHttpChannel::OnPreflightFailed(nsresult aError) {
8245 MOZ_ASSERT(mRequireCORSPreflight, "Why did a preflight happen?");
8246 mIsCorsPreflightDone = 1;
8247 mPreflightChannel = nullptr;
8248
8249 CloseCacheEntry(false);
8250 Unused << AsyncAbort(aError);
8251 return NS_OK;
8252 }
8253
8254 //-----------------------------------------------------------------------------
8255 // AChannelHasDivertableParentChannelAsListener internal functions
8256 //-----------------------------------------------------------------------------
8257
8258 NS_IMETHODIMP
MessageDiversionStarted(ADivertableParentChannel * aParentChannel)8259 nsHttpChannel::MessageDiversionStarted(
8260 ADivertableParentChannel *aParentChannel) {
8261 LOG(("nsHttpChannel::MessageDiversionStarted [this=%p]", this));
8262 MOZ_ASSERT(!mParentChannel);
8263 mParentChannel = aParentChannel;
8264 // If the channel is suspended, propagate that info to the parent's mEventQ.
8265 uint32_t suspendCount = mSuspendCount;
8266 while (suspendCount--) {
8267 mParentChannel->SuspendMessageDiversion();
8268 }
8269 return NS_OK;
8270 }
8271
8272 NS_IMETHODIMP
MessageDiversionStop()8273 nsHttpChannel::MessageDiversionStop() {
8274 LOG(("nsHttpChannel::MessageDiversionStop [this=%p]", this));
8275 MOZ_ASSERT(mParentChannel);
8276 mParentChannel = nullptr;
8277 return NS_OK;
8278 }
8279
8280 NS_IMETHODIMP
SuspendInternal()8281 nsHttpChannel::SuspendInternal() {
8282 NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
8283
8284 LOG(("nsHttpChannel::SuspendInternal [this=%p]\n", this));
8285
8286 ++mSuspendCount;
8287
8288 if (mSuspendCount == 1) {
8289 mSuspendTimestamp = TimeStamp::NowLoRes();
8290 }
8291
8292 nsresult rvTransaction = NS_OK;
8293 if (mTransactionPump) {
8294 rvTransaction = mTransactionPump->Suspend();
8295 }
8296 nsresult rvCache = NS_OK;
8297 if (mCachePump) {
8298 rvCache = mCachePump->Suspend();
8299 }
8300
8301 return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
8302 }
8303
8304 NS_IMETHODIMP
ResumeInternal()8305 nsHttpChannel::ResumeInternal() {
8306 NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
8307
8308 LOG(("nsHttpChannel::ResumeInternal [this=%p]\n", this));
8309
8310 if (--mSuspendCount == 0) {
8311 mSuspendTotalTime +=
8312 (TimeStamp::NowLoRes() - mSuspendTimestamp).ToMilliseconds();
8313
8314 if (mCallOnResume) {
8315 // Resume the interrupted procedure first, then resume
8316 // the pump to continue process the input stream.
8317 RefPtr<nsRunnableMethod<nsHttpChannel>> callOnResume =
8318 NewRunnableMethod("CallOnResume", this, mCallOnResume);
8319 // Should not resume pump that created after resumption.
8320 RefPtr<nsInputStreamPump> transactionPump = mTransactionPump;
8321 RefPtr<nsInputStreamPump> cachePump = mCachePump;
8322
8323 nsresult rv = NS_DispatchToCurrentThread(
8324 NS_NewRunnableFunction("nsHttpChannel::CallOnResume",
8325 [callOnResume, transactionPump, cachePump]() {
8326 callOnResume->Run();
8327
8328 if (transactionPump) {
8329 transactionPump->Resume();
8330 }
8331
8332 if (cachePump) {
8333 cachePump->Resume();
8334 }
8335 }));
8336 mCallOnResume = nullptr;
8337 NS_ENSURE_SUCCESS(rv, rv);
8338 return rv;
8339 }
8340 }
8341
8342 nsresult rvTransaction = NS_OK;
8343 if (mTransactionPump) {
8344 rvTransaction = mTransactionPump->Resume();
8345 }
8346
8347 nsresult rvCache = NS_OK;
8348 if (mCachePump) {
8349 rvCache = mCachePump->Resume();
8350 }
8351
8352 return NS_FAILED(rvTransaction) ? rvTransaction : rvCache;
8353 }
8354
MaybeWarnAboutAppCache()8355 void nsHttpChannel::MaybeWarnAboutAppCache() {
8356 // First, accumulate a telemetry ping about appcache usage.
8357 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD, true);
8358
8359 // Then, issue a deprecation warning.
8360 nsCOMPtr<nsIDeprecationWarner> warner;
8361 GetCallback(warner);
8362 if (warner) {
8363 warner->IssueWarning(nsIDocument::eAppCache, false);
8364 // When the page is insecure and the API is still enabled
8365 // provide an additional warning for developers of removal
8366 if (!IsHTTPS() &&
8367 Preferences::GetBool("browser.cache.offline.insecure.enable")) {
8368 warner->IssueWarning(nsIDocument::eAppCacheInsecure, true);
8369 }
8370 }
8371 }
8372
SetLoadGroupUserAgentOverride()8373 void nsHttpChannel::SetLoadGroupUserAgentOverride() {
8374 nsCOMPtr<nsIURI> uri;
8375 GetURI(getter_AddRefs(uri));
8376 nsAutoCString uriScheme;
8377 if (uri) {
8378 uri->GetScheme(uriScheme);
8379 }
8380
8381 // We don't need a UA for file: protocols.
8382 if (uriScheme.EqualsLiteral("file")) {
8383 gHttpHandler->OnUserAgentRequest(this);
8384 return;
8385 }
8386
8387 nsIRequestContextService *rcsvc = gHttpHandler->GetRequestContextService();
8388 nsCOMPtr<nsIRequestContext> rc;
8389 if (rcsvc) {
8390 rcsvc->GetRequestContext(mRequestContextID, getter_AddRefs(rc));
8391 }
8392
8393 nsAutoCString ua;
8394 if (nsContentUtils::IsNonSubresourceRequest(this)) {
8395 gHttpHandler->OnUserAgentRequest(this);
8396 if (rc) {
8397 GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
8398 rc->SetUserAgentOverride(ua);
8399 }
8400 } else {
8401 GetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua);
8402 // Don't overwrite the UA if it is already set (eg by an XHR with explicit
8403 // UA).
8404 if (ua.IsEmpty()) {
8405 if (rc) {
8406 rc->GetUserAgentOverride(ua);
8407 SetRequestHeader(NS_LITERAL_CSTRING("User-Agent"), ua, false);
8408 } else {
8409 gHttpHandler->OnUserAgentRequest(this);
8410 }
8411 }
8412 }
8413 }
8414
8415 // Step 10 of HTTP-network-or-cache fetch
SetOriginHeader()8416 void nsHttpChannel::SetOriginHeader() {
8417 if (mRequestHead.IsGet() || mRequestHead.IsHead()) {
8418 return;
8419 }
8420 nsAutoCString existingHeader;
8421 Unused << mRequestHead.GetHeader(nsHttp::Origin, existingHeader);
8422 if (!existingHeader.IsEmpty()) {
8423 LOG(("nsHttpChannel::SetOriginHeader Origin header already present"));
8424 return;
8425 }
8426
8427 DebugOnly<nsresult> rv;
8428
8429 // Instead of consulting Preferences::GetInt() all the time we
8430 // can cache the result to speed things up.
8431 static int32_t sSendOriginHeader = 0;
8432 static bool sIsInited = false;
8433 if (!sIsInited) {
8434 sIsInited = true;
8435 Preferences::AddIntVarCache(&sSendOriginHeader,
8436 "network.http.sendOriginHeader");
8437 }
8438 if (sSendOriginHeader == 0) {
8439 // Origin header suppressed by user setting
8440 return;
8441 }
8442
8443 nsCOMPtr<nsIURI> referrer;
8444 mLoadInfo->TriggeringPrincipal()->GetURI(getter_AddRefs(referrer));
8445
8446 nsAutoCString origin("null");
8447 if (referrer && IsReferrerSchemeAllowed(referrer)) {
8448 nsContentUtils::GetASCIIOrigin(referrer, origin);
8449 }
8450
8451 // Restrict Origin to same-origin loads if requested by user
8452 if (sSendOriginHeader == 1) {
8453 nsAutoCString currentOrigin;
8454 nsContentUtils::GetASCIIOrigin(mURI, currentOrigin);
8455 if (!origin.EqualsIgnoreCase(currentOrigin.get())) {
8456 // Origin header suppressed by user setting
8457 return;
8458 }
8459 }
8460
8461 rv = mRequestHead.SetHeader(nsHttp::Origin, origin, false /* merge */);
8462 MOZ_ASSERT(NS_SUCCEEDED(rv));
8463 }
8464
SetDoNotTrack()8465 void nsHttpChannel::SetDoNotTrack() {
8466 /**
8467 * 'DoNotTrack' header should be added if 'privacy.donottrackheader.enabled'
8468 * is true or tracking protection is enabled. See bug 1258033.
8469 */
8470 nsCOMPtr<nsILoadContext> loadContext;
8471 NS_QueryNotificationCallbacks(this, loadContext);
8472
8473 if ((loadContext && loadContext->UseTrackingProtection()) ||
8474 nsContentUtils::DoNotTrackEnabled()) {
8475 DebugOnly<nsresult> rv = mRequestHead.SetHeader(
8476 nsHttp::DoNotTrack, NS_LITERAL_CSTRING("1"), false);
8477 MOZ_ASSERT(NS_SUCCEEDED(rv));
8478 }
8479 }
8480
ReportRcwnStats(bool isFromNet)8481 void nsHttpChannel::ReportRcwnStats(bool isFromNet) {
8482 if (!sRCWNEnabled) {
8483 return;
8484 }
8485
8486 if (isFromNet) {
8487 if (mRaceCacheWithNetwork) {
8488 gIOService->IncrementNetWonRequestNumber();
8489 Telemetry::Accumulate(
8490 Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_NETWORK_WIN,
8491 mTransferSize);
8492 if (mRaceDelay) {
8493 AccumulateCategorical(
8494 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8495 NetworkDelayedRace);
8496 } else {
8497 AccumulateCategorical(
8498 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8499 NetworkRace);
8500 }
8501 } else {
8502 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
8503 mTransferSize);
8504 AccumulateCategorical(
8505 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8506 NetworkNoRace);
8507 }
8508 } else {
8509 if (mRaceCacheWithNetwork || mRaceDelay) {
8510 gIOService->IncrementCacheWonRequestNumber();
8511 Telemetry::Accumulate(
8512 Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_RACE_CACHE_WIN,
8513 mTransferSize);
8514 if (mRaceDelay) {
8515 AccumulateCategorical(
8516 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8517 CacheDelayedRace);
8518 } else {
8519 AccumulateCategorical(
8520 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8521 CacheRace);
8522 }
8523 } else {
8524 Telemetry::Accumulate(Telemetry::NETWORK_RACE_CACHE_BANDWIDTH_NOT_RACE,
8525 mTransferSize);
8526 AccumulateCategorical(
8527 Telemetry::LABELS_NETWORK_RACE_CACHE_WITH_NETWORK_USAGE_2::
8528 CacheNoRace);
8529 }
8530 }
8531
8532 gIOService->IncrementRequestNumber();
8533 }
8534
8535 static const size_t kPositiveBucketNumbers = 34;
8536 static const int64_t kPositiveBucketLevels[kPositiveBucketNumbers] = {
8537 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200,
8538 300, 400, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000,
8539 6000, 7000, 8000, 9000, 10000, 20000, 30000, 40000, 50000, 60000};
8540
8541 /**
8542 * For space efficiency, we collect finer resolution for small difference
8543 * between net and cache time, coarser for larger.
8544 * Bucket #40 for a tie.
8545 * #41 to #50 indicates cache wins by 1ms to 100ms, split equally.
8546 * #51 to #59 indicates cache wins by 101ms to 1000ms.
8547 * #60 to #68 indicates cache wins by 1s to 10s.
8548 * #69 to #73 indicates cache wins by 11s to 60s.
8549 * #74 indicates cache wins by more than 1 minute.
8550 *
8551 * #39 to #30 indicates network wins by 1ms to 100ms, split equally.
8552 * #29 to #21 indicates network wins by 101ms to 1000ms.
8553 * #20 to #12 indicates network wins by 1s to 10s.
8554 * #11 to #7 indicates network wins by 11s to 60s.
8555 * #6 indicates network wins by more than 1 minute.
8556 *
8557 * Other bucket numbers are reserved.
8558 */
ComputeTelemetryBucketNumber(int64_t difftime_ms)8559 inline int64_t nsHttpChannel::ComputeTelemetryBucketNumber(
8560 int64_t difftime_ms) {
8561 int64_t absBucketIndex =
8562 std::lower_bound(kPositiveBucketLevels,
8563 kPositiveBucketLevels + kPositiveBucketNumbers,
8564 static_cast<int64_t>(mozilla::Abs(difftime_ms))) -
8565 kPositiveBucketLevels;
8566
8567 return difftime_ms >= 0 ? 40 + absBucketIndex : 40 - absBucketIndex;
8568 }
8569
ReportNetVSCacheTelemetry()8570 void nsHttpChannel::ReportNetVSCacheTelemetry() {
8571 nsresult rv;
8572 if (!mCacheEntry) {
8573 return;
8574 }
8575
8576 // We only report telemetry if the entry is persistent (on disk)
8577 bool persistent;
8578 rv = mCacheEntry->GetPersistent(&persistent);
8579 if (NS_FAILED(rv) || !persistent) {
8580 return;
8581 }
8582
8583 uint64_t onStartNetTime = 0;
8584 if (NS_FAILED(mCacheEntry->GetOnStartTime(&onStartNetTime))) {
8585 return;
8586 }
8587
8588 uint64_t onStopNetTime = 0;
8589 if (NS_FAILED(mCacheEntry->GetOnStopTime(&onStopNetTime))) {
8590 return;
8591 }
8592
8593 uint64_t onStartCacheTime =
8594 (mOnStartRequestTimestamp - mAsyncOpenTime).ToMilliseconds();
8595 int64_t onStartDiff = onStartNetTime - onStartCacheTime;
8596 onStartDiff = ComputeTelemetryBucketNumber(onStartDiff);
8597
8598 uint64_t onStopCacheTime = (mCacheReadEnd - mAsyncOpenTime).ToMilliseconds();
8599 int64_t onStopDiff = onStopNetTime - onStopCacheTime;
8600 onStopDiff = ComputeTelemetryBucketNumber(onStopDiff);
8601
8602 if (mDidReval) {
8603 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTART_REVALIDATED_V2,
8604 onStartDiff);
8605 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_REVALIDATED_V2,
8606 onStopDiff);
8607 } else {
8608 Telemetry::Accumulate(
8609 Telemetry::HTTP_NET_VS_CACHE_ONSTART_NOTREVALIDATED_V2, onStartDiff);
8610 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_NOTREVALIDATED_V2,
8611 onStopDiff);
8612 }
8613
8614 if (mDidReval) {
8615 // We don't report revalidated probes as the data would be skewed.
8616 return;
8617 }
8618
8619 if (mCacheOpenWithPriority) {
8620 if (mCacheQueueSizeWhenOpen < 5) {
8621 Telemetry::Accumulate(
8622 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_HIGHPRI_V2, onStartDiff);
8623 Telemetry::Accumulate(
8624 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_HIGHPRI_V2, onStopDiff);
8625 } else if (mCacheQueueSizeWhenOpen < 10) {
8626 Telemetry::Accumulate(
8627 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_HIGHPRI_V2, onStartDiff);
8628 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_HIGHPRI_V2,
8629 onStopDiff);
8630 } else {
8631 Telemetry::Accumulate(
8632 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_HIGHPRI_V2, onStartDiff);
8633 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_HIGHPRI_V2,
8634 onStopDiff);
8635 }
8636 } else { // The limits are higher for normal priority cache queues
8637 if (mCacheQueueSizeWhenOpen < 10) {
8638 Telemetry::Accumulate(
8639 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QSMALL_NORMALPRI_V2,
8640 onStartDiff);
8641 Telemetry::Accumulate(
8642 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QSMALL_NORMALPRI_V2, onStopDiff);
8643 } else if (mCacheQueueSizeWhenOpen < 50) {
8644 Telemetry::Accumulate(
8645 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QMED_NORMALPRI_V2, onStartDiff);
8646 Telemetry::Accumulate(
8647 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QMED_NORMALPRI_V2, onStopDiff);
8648 } else {
8649 Telemetry::Accumulate(
8650 Telemetry::HTTP_NET_VS_CACHE_ONSTART_QBIG_NORMALPRI_V2, onStartDiff);
8651 Telemetry::Accumulate(
8652 Telemetry::HTTP_NET_VS_CACHE_ONSTOP_QBIG_NORMALPRI_V2, onStopDiff);
8653 }
8654 }
8655
8656 uint32_t diskStorageSizeK = 0;
8657 rv = mCacheEntry->GetDiskStorageSizeInKB(&diskStorageSizeK);
8658 if (NS_FAILED(rv)) {
8659 return;
8660 }
8661
8662 // No significant difference was observed between different sizes for
8663 // |onStartDiff|
8664 if (diskStorageSizeK < 256) {
8665 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_SMALL_V2,
8666 onStopDiff);
8667 } else {
8668 Telemetry::Accumulate(Telemetry::HTTP_NET_VS_CACHE_ONSTOP_LARGE_V2,
8669 onStopDiff);
8670 }
8671 }
8672
8673 NS_IMETHODIMP
Test_delayCacheEntryOpeningBy(int32_t aTimeout)8674 nsHttpChannel::Test_delayCacheEntryOpeningBy(int32_t aTimeout) {
8675 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
8676 mCacheOpenDelay = aTimeout;
8677 return NS_OK;
8678 }
8679
8680 NS_IMETHODIMP
Test_triggerDelayedOpenCacheEntry()8681 nsHttpChannel::Test_triggerDelayedOpenCacheEntry() {
8682 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
8683 nsresult rv;
8684 if (!mCacheOpenDelay) {
8685 // No delay was set.
8686 return NS_ERROR_NOT_AVAILABLE;
8687 }
8688 if (!mCacheOpenFunc) {
8689 // There should be a runnable.
8690 return NS_ERROR_FAILURE;
8691 }
8692 if (mCacheOpenTimer) {
8693 rv = mCacheOpenTimer->Cancel();
8694 if (NS_FAILED(rv)) {
8695 return rv;
8696 }
8697 mCacheOpenTimer = nullptr;
8698 }
8699 mCacheOpenDelay = 0;
8700 // Avoid re-entrancy issues by nulling our mCacheOpenFunc before calling it.
8701 std::function<void(nsHttpChannel *)> cacheOpenFunc = nullptr;
8702 std::swap(cacheOpenFunc, mCacheOpenFunc);
8703 cacheOpenFunc(this);
8704
8705 return NS_OK;
8706 }
8707
TriggerNetworkWithDelay(uint32_t aDelay)8708 nsresult nsHttpChannel::TriggerNetworkWithDelay(uint32_t aDelay) {
8709 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
8710
8711 LOG(("nsHttpChannel::TriggerNetworkWithDelay [this=%p, delay=%u]\n", this,
8712 aDelay));
8713
8714 if (mCanceled) {
8715 LOG((" channel was canceled.\n"));
8716 return mStatus;
8717 }
8718
8719 // If a network request has already gone out, there is no point in
8720 // doing this again.
8721 if (mNetworkTriggered) {
8722 LOG((" network already triggered. Returning.\n"));
8723 return NS_OK;
8724 }
8725
8726 if (!aDelay) {
8727 // We cannot call TriggerNetwork() directly here, because it would
8728 // cause performance regression in tp6 tests, see bug 1398847.
8729 return NS_DispatchToMainThread(
8730 NewRunnableMethod("net::nsHttpChannel::TriggerNetworkWithDelay", this,
8731 &nsHttpChannel::TriggerNetwork),
8732 NS_DISPATCH_NORMAL);
8733 }
8734
8735 if (!mNetworkTriggerTimer) {
8736 mNetworkTriggerTimer = NS_NewTimer();
8737 }
8738 mNetworkTriggerTimer->InitWithCallback(this, aDelay, nsITimer::TYPE_ONE_SHOT);
8739 return NS_OK;
8740 }
8741
TriggerNetwork()8742 nsresult nsHttpChannel::TriggerNetwork() {
8743 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
8744
8745 LOG(("nsHttpChannel::TriggerNetwork [this=%p]\n", this));
8746
8747 if (mCanceled) {
8748 LOG((" channel was canceled.\n"));
8749 return mStatus;
8750 }
8751
8752 // If a network request has already gone out, there is no point in
8753 // doing this again.
8754 if (mNetworkTriggered) {
8755 LOG((" network already triggered. Returning.\n"));
8756 return NS_OK;
8757 }
8758
8759 mNetworkTriggered = true;
8760 if (mNetworkTriggerTimer) {
8761 mNetworkTriggerTimer->Cancel();
8762 mNetworkTriggerTimer = nullptr;
8763 }
8764
8765 // If we are waiting for a proxy request, that means we can't trigger
8766 // the next step just yet. We need for mConnectionInfo to be non-null
8767 // before we call ContinueConnect. OnProxyAvailable will trigger
8768 // BeginConnect, and Connect will call ContinueConnect even if it's
8769 // for the cache callbacks.
8770 if (mProxyRequest) {
8771 LOG((" proxy request in progress. Delaying network trigger.\n"));
8772 mWaitingForProxy = true;
8773 return NS_OK;
8774 }
8775
8776 if (AwaitingCacheCallbacks()) {
8777 mRaceCacheWithNetwork = sRCWNEnabled;
8778 }
8779
8780 LOG((" triggering network\n"));
8781 return ContinueConnect();
8782 }
8783
MaybeRaceCacheWithNetwork()8784 nsresult nsHttpChannel::MaybeRaceCacheWithNetwork() {
8785 // Don't trigger the network if the load flags say so.
8786 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_NO_NETWORK_IO)) {
8787 return NS_OK;
8788 }
8789
8790 // We must not race if the channel has a failure status code.
8791 if (NS_FAILED(mStatus)) {
8792 return NS_OK;
8793 }
8794
8795 // If a CORS Preflight is required we must not race.
8796 if (mRequireCORSPreflight && !mIsCorsPreflightDone) {
8797 return NS_OK;
8798 }
8799
8800 if (CacheFileUtils::CachePerfStats::IsCacheSlow()) {
8801 // If the cache is slow, trigger the network request immediately.
8802 mRaceDelay = 0;
8803 } else {
8804 // Give cache a headstart of 3 times the average cache entry open time.
8805 mRaceDelay = CacheFileUtils::CachePerfStats::GetAverage(
8806 CacheFileUtils::CachePerfStats::ENTRY_OPEN, true) *
8807 3;
8808 // We use microseconds in CachePerfStats but we need milliseconds
8809 // for TriggerNetwork.
8810 mRaceDelay /= 1000;
8811 }
8812
8813 mRaceDelay = clamped<uint32_t>(mRaceDelay, sRCWNMinWaitMs, sRCWNMaxWaitMs);
8814
8815 MOZ_ASSERT(sRCWNEnabled, "The pref must be turned on.");
8816 LOG(("nsHttpChannel::MaybeRaceCacheWithNetwork [this=%p, delay=%u]\n", this,
8817 mRaceDelay));
8818
8819 return TriggerNetworkWithDelay(mRaceDelay);
8820 }
8821
8822 NS_IMETHODIMP
Test_triggerNetwork(int32_t aTimeout)8823 nsHttpChannel::Test_triggerNetwork(int32_t aTimeout) {
8824 MOZ_ASSERT(NS_IsMainThread(), "Must be called on the main thread");
8825 return TriggerNetworkWithDelay(aTimeout);
8826 }
8827
8828 NS_IMETHODIMP
Notify(nsITimer * aTimer)8829 nsHttpChannel::Notify(nsITimer *aTimer) {
8830 RefPtr<nsHttpChannel> self(this);
8831 if (aTimer == mCacheOpenTimer) {
8832 return Test_triggerDelayedOpenCacheEntry();
8833 } else if (aTimer == mNetworkTriggerTimer) {
8834 return TriggerNetwork();
8835 } else {
8836 MOZ_CRASH("Unknown timer");
8837 }
8838
8839 return NS_OK;
8840 }
8841
EligibleForTailing()8842 bool nsHttpChannel::EligibleForTailing() {
8843 if (!(mClassOfService & nsIClassOfService::Tail)) {
8844 return false;
8845 }
8846
8847 if (mClassOfService &
8848 (nsIClassOfService::UrgentStart | nsIClassOfService::Leader |
8849 nsIClassOfService::TailForbidden)) {
8850 return false;
8851 }
8852
8853 if (mClassOfService & nsIClassOfService::Unblocked &&
8854 !(mClassOfService & nsIClassOfService::TailAllowed)) {
8855 return false;
8856 }
8857
8858 if (IsNavigation()) {
8859 return false;
8860 }
8861
8862 return true;
8863 }
8864
WaitingForTailUnblock()8865 bool nsHttpChannel::WaitingForTailUnblock() {
8866 nsresult rv;
8867
8868 if (!gHttpHandler->IsTailBlockingEnabled()) {
8869 LOG(("nsHttpChannel %p tail-blocking disabled", this));
8870 return false;
8871 }
8872
8873 if (!EligibleForTailing()) {
8874 LOG(("nsHttpChannel %p not eligible for tail-blocking", this));
8875 AddAsNonTailRequest();
8876 return false;
8877 }
8878
8879 if (!EnsureRequestContext()) {
8880 LOG(("nsHttpChannel %p no request context", this));
8881 return false;
8882 }
8883
8884 LOG(("nsHttpChannel::WaitingForTailUnblock this=%p, rc=%p", this,
8885 mRequestContext.get()));
8886
8887 bool blocked;
8888 rv = mRequestContext->IsContextTailBlocked(this, &blocked);
8889 if (NS_FAILED(rv)) {
8890 return false;
8891 }
8892
8893 LOG((" blocked=%d", blocked));
8894
8895 return blocked;
8896 }
8897
8898 //-----------------------------------------------------------------------------
8899 // nsHttpChannel::nsIRequestTailUnblockCallback
8900 //-----------------------------------------------------------------------------
8901
8902 // Must be implemented in the leaf class because we don't have
8903 // AsyncAbort in HttpBaseChannel.
8904 NS_IMETHODIMP
OnTailUnblock(nsresult rv)8905 nsHttpChannel::OnTailUnblock(nsresult rv) {
8906 LOG(("nsHttpChannel::OnTailUnblock this=%p rv=%" PRIx32 " rc=%p", this,
8907 static_cast<uint32_t>(rv), mRequestContext.get()));
8908
8909 MOZ_RELEASE_ASSERT(mOnTailUnblock);
8910
8911 if (NS_FAILED(mStatus)) {
8912 rv = mStatus;
8913 }
8914
8915 if (NS_SUCCEEDED(rv)) {
8916 auto callback = mOnTailUnblock;
8917 mOnTailUnblock = nullptr;
8918 rv = (this->*callback)();
8919 }
8920
8921 if (NS_FAILED(rv)) {
8922 CloseCacheEntry(false);
8923 return AsyncAbort(rv);
8924 }
8925
8926 return NS_OK;
8927 }
8928
SetWarningReporter(HttpChannelSecurityWarningReporter * aReporter)8929 void nsHttpChannel::SetWarningReporter(
8930 HttpChannelSecurityWarningReporter *aReporter) {
8931 LOG(("nsHttpChannel [this=%p] SetWarningReporter [%p]", this, aReporter));
8932 mWarningReporter = aReporter;
8933 }
8934
GetWarningReporter()8935 HttpChannelSecurityWarningReporter *nsHttpChannel::GetWarningReporter() {
8936 LOG(("nsHttpChannel [this=%p] GetWarningReporter [%p]", this,
8937 mWarningReporter.get()));
8938 return mWarningReporter.get();
8939 }
8940
RedirectToInterceptedChannel()8941 nsresult nsHttpChannel::RedirectToInterceptedChannel() {
8942 nsCOMPtr<nsINetworkInterceptController> controller;
8943 GetCallback(controller);
8944
8945 RefPtr<InterceptedHttpChannel> intercepted =
8946 InterceptedHttpChannel::CreateForInterception(
8947 mChannelCreationTime, mChannelCreationTimestamp, mAsyncOpenTime);
8948
8949 nsresult rv = intercepted->Init(mURI, mCaps,
8950 static_cast<nsProxyInfo *>(mProxyInfo.get()),
8951 mProxyResolveFlags, mProxyURI, mChannelId);
8952
8953 nsCOMPtr<nsILoadInfo> redirectLoadInfo =
8954 CloneLoadInfoForRedirect(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
8955 intercepted->SetLoadInfo(redirectLoadInfo);
8956
8957 rv = SetupReplacementChannel(mURI, intercepted, true,
8958 nsIChannelEventSink::REDIRECT_INTERNAL);
8959 NS_ENSURE_SUCCESS(rv, rv);
8960
8961 mRedirectChannel = intercepted;
8962
8963 PushRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
8964
8965 rv = gHttpHandler->AsyncOnChannelRedirect(
8966 this, intercepted, nsIChannelEventSink::REDIRECT_INTERNAL);
8967
8968 if (NS_SUCCEEDED(rv)) {
8969 rv = WaitForRedirectCallback();
8970 }
8971
8972 if (NS_FAILED(rv)) {
8973 AutoRedirectVetoNotifier notifier(this);
8974
8975 PopRedirectAsyncFunc(&nsHttpChannel::ContinueAsyncRedirectChannelToURI);
8976 }
8977
8978 return rv;
8979 }
8980
8981 } // namespace net
8982 } // namespace mozilla
8983