netwerk/protocol/http/nsHttpChannel.cpp

branch
TOR_BUG_9701
changeset 11
deefc01c0e14
equal deleted inserted replaced
-1:000000000000 0:876c8c95adda
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 "nsHttp.h"
11 #include "nsHttpChannel.h"
12 #include "nsHttpHandler.h"
13 #include "nsIApplicationCacheService.h"
14 #include "nsIApplicationCacheContainer.h"
15 #include "nsICacheStorageService.h"
16 #include "nsICacheStorage.h"
17 #include "nsICacheEntry.h"
18 #include "nsICryptoHash.h"
19 #include "nsIStringBundle.h"
20 #include "nsIStreamListenerTee.h"
21 #include "nsISeekableStream.h"
22 #include "nsILoadGroupChild.h"
23 #include "nsIProtocolProxyService2.h"
24 #include "nsMimeTypes.h"
25 #include "nsNetUtil.h"
26 #include "prprf.h"
27 #include "prnetdb.h"
28 #include "nsEscape.h"
29 #include "nsStreamUtils.h"
30 #include "nsIOService.h"
31 #include "nsDNSPrefetch.h"
32 #include "nsChannelClassifier.h"
33 #include "nsIRedirectResultListener.h"
34 #include "mozilla/TimeStamp.h"
35 #include "nsError.h"
36 #include "nsPrintfCString.h"
37 #include "nsAlgorithm.h"
38 #include "GeckoProfiler.h"
39 #include "nsIConsoleService.h"
40 #include "mozilla/Attributes.h"
41 #include "mozilla/VisualEventTracer.h"
42 #include "nsISSLSocketControl.h"
43 #include "sslt.h"
44 #include "nsContentUtils.h"
45 #include "nsIPermissionManager.h"
46 #include "nsIPrincipal.h"
47 #include "nsIScriptSecurityManager.h"
48 #include "nsISSLStatus.h"
49 #include "nsISSLStatusProvider.h"
50 #include "LoadContextInfo.h"
51 #include "netCore.h"
52 #include "nsHttpTransaction.h"
53 #include "nsICacheEntryDescriptor.h"
54 #include "nsICancelable.h"
55 #include "nsIHttpChannelAuthProvider.h"
56 #include "nsIHttpEventSink.h"
57 #include "nsIPrompt.h"
58 #include "nsInputStreamPump.h"
59 #include "nsURLHelper.h"
60 #include "nsISocketTransport.h"
61 #include "nsICacheSession.h"
62 #include "nsIStreamConverterService.h"
63 #include "nsISiteSecurityService.h"
64 #include "nsCRT.h"
65 #include "nsPIDOMWindow.h"
66 #include "nsPerformance.h"
67 #include "CacheObserver.h"
68 #include "mozilla/Telemetry.h"
69 #include "mozIThirdPartyUtil.h"
70
71 namespace mozilla { namespace net {
72
73 namespace {
74
75 // True if the local cache should be bypassed when processing a request.
76 #define BYPASS_LOCAL_CACHE(loadFlags) \
77 (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
78 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
79
80 #define CACHE_FILE_GONE(result) \
81 ((result) == NS_ERROR_FILE_NOT_FOUND || \
82 (result) == NS_ERROR_FILE_CORRUPTED)
83
84 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
85 static NS_DEFINE_CID(kStreamTransportServiceCID,
86 NS_STREAMTRANSPORTSERVICE_CID);
87
88 enum CacheDisposition {
89 kCacheHit = 1,
90 kCacheHitViaReval = 2,
91 kCacheMissedViaReval = 3,
92 kCacheMissed = 4
93 };
94
95 void
96 AccumulateCacheHitTelemetry(CacheDisposition hitOrMiss)
97 {
98 if (!CacheObserver::UseNewCache()) {
99 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2, hitOrMiss);
100 }
101 else {
102 Telemetry::Accumulate(Telemetry::HTTP_CACHE_DISPOSITION_2_V2, hitOrMiss);
103
104 int32_t experiment = CacheObserver::HalfLifeExperiment();
105 if (experiment > 0 && hitOrMiss == kCacheMissed) {
106 Telemetry::Accumulate(Telemetry::HTTP_CACHE_MISS_HALFLIFE_EXPERIMENT,
107 experiment - 1);
108 }
109 }
110 }
111
112 // Computes and returns a SHA1 hash of the input buffer. The input buffer
113 // must be a null-terminated string.
114 nsresult
115 Hash(const char *buf, nsACString &hash)
116 {
117 nsresult rv;
118
119 nsCOMPtr<nsICryptoHash> hasher
120 = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
121 NS_ENSURE_SUCCESS(rv, rv);
122
123 rv = hasher->Init(nsICryptoHash::SHA1);
124 NS_ENSURE_SUCCESS(rv, rv);
125
126 rv = hasher->Update(reinterpret_cast<unsigned const char*>(buf),
127 strlen(buf));
128 NS_ENSURE_SUCCESS(rv, rv);
129
130 rv = hasher->Finish(true, hash);
131 NS_ENSURE_SUCCESS(rv, rv);
132
133 return NS_OK;
134 }
135
136 bool IsRedirectStatus(uint32_t status)
137 {
138 // 305 disabled as a security measure (see bug 187996).
139 return status == 300 || status == 301 || status == 302 || status == 303 ||
140 status == 307 || status == 308;
141 }
142
143 // We only treat 3xx responses as redirects if they have a Location header and
144 // the status code is in a whitelist.
145 bool
146 WillRedirect(const nsHttpResponseHead * response)
147 {
148 return IsRedirectStatus(response->Status()) &&
149 response->PeekHeader(nsHttp::Location);
150 }
151
152 } // unnamed namespace
153
154 class AutoRedirectVetoNotifier
155 {
156 public:
157 AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel)
158 {
159 if (mChannel->mHasAutoRedirectVetoNotifier) {
160 MOZ_CRASH("Nested AutoRedirectVetoNotifier on the stack");
161 mChannel = nullptr;
162 return;
163 }
164
165 mChannel->mHasAutoRedirectVetoNotifier = true;
166 }
167 ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);}
168 void RedirectSucceeded() {ReportRedirectResult(true);}
169
170 private:
171 nsHttpChannel* mChannel;
172 void ReportRedirectResult(bool succeeded);
173 };
174
175 void
176 AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
177 {
178 if (!mChannel)
179 return;
180
181 mChannel->mRedirectChannel = nullptr;
182
183 nsCOMPtr<nsIRedirectResultListener> vetoHook;
184 NS_QueryNotificationCallbacks(mChannel,
185 NS_GET_IID(nsIRedirectResultListener),
186 getter_AddRefs(vetoHook));
187
188 nsHttpChannel* channel = mChannel;
189 mChannel = nullptr;
190
191 if (vetoHook)
192 vetoHook->OnRedirectResult(succeeded);
193
194 // Drop after the notification
195 channel->mHasAutoRedirectVetoNotifier = false;
196
197 MOZ_EVENT_TRACER_DONE(channel, "net::http::redirect-callbacks");
198 }
199
200 //-----------------------------------------------------------------------------
201 // nsHttpChannel <public>
202 //-----------------------------------------------------------------------------
203
204 nsHttpChannel::nsHttpChannel()
205 : HttpAsyncAborter<nsHttpChannel>(MOZ_THIS_IN_INITIALIZER_LIST())
206 , mLogicalOffset(0)
207 , mPostID(0)
208 , mRequestTime(0)
209 , mOfflineCacheLastModifiedTime(0)
210 , mCachedContentIsValid(false)
211 , mCachedContentIsPartial(false)
212 , mTransactionReplaced(false)
213 , mAuthRetryPending(false)
214 , mProxyAuthPending(false)
215 , mResuming(false)
216 , mInitedCacheEntry(false)
217 , mFallbackChannel(false)
218 , mCustomConditionalRequest(false)
219 , mFallingBack(false)
220 , mWaitingForRedirectCallback(false)
221 , mRequestTimeInitialized(false)
222 , mCacheEntryIsReadOnly(false)
223 , mCacheEntryIsWriteOnly(false)
224 , mCacheEntriesToWaitFor(0)
225 , mHasQueryString(0)
226 , mConcurentCacheAccess(0)
227 , mIsPartialRequest(0)
228 , mHasAutoRedirectVetoNotifier(0)
229 , mDidReval(false)
230 , mForcePending(false)
231 {
232 LOG(("Creating nsHttpChannel [this=%p]\n", this));
233 mChannelCreationTime = PR_Now();
234 mChannelCreationTimestamp = TimeStamp::Now();
235 }
236
237 nsHttpChannel::~nsHttpChannel()
238 {
239 LOG(("Destroying nsHttpChannel [this=%p]\n", this));
240
241 if (mAuthProvider)
242 mAuthProvider->Disconnect(NS_ERROR_ABORT);
243 }
244
245 nsresult
246 nsHttpChannel::Init(nsIURI *uri,
247 uint32_t caps,
248 nsProxyInfo *proxyInfo,
249 uint32_t proxyResolveFlags,
250 nsIURI *proxyURI)
251 {
252 #ifdef MOZ_VISUAL_EVENT_TRACER
253 nsAutoCString url;
254 uri->GetAsciiSpec(url);
255 MOZ_EVENT_TRACER_NAME_OBJECT(this, url.get());
256 #endif
257
258 nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo,
259 proxyResolveFlags, proxyURI);
260 if (NS_FAILED(rv))
261 return rv;
262
263 LOG(("nsHttpChannel::Init [this=%p]\n", this));
264
265 return rv;
266 }
267 //-----------------------------------------------------------------------------
268 // nsHttpChannel <private>
269 //-----------------------------------------------------------------------------
270
271 nsresult
272 nsHttpChannel::Connect()
273 {
274 nsresult rv;
275
276 LOG(("nsHttpChannel::Connect [this=%p]\n", this));
277
278 // Even if we're in private browsing mode, we still enforce existing STS
279 // data (it is read-only).
280 // if the connection is not using SSL and either the exact host matches or
281 // a superdomain wants to force HTTPS, do it.
282 bool usingSSL = false;
283 rv = mURI->SchemeIs("https", &usingSSL);
284 NS_ENSURE_SUCCESS(rv,rv);
285
286 if (!usingSSL) {
287 // enforce Strict-Transport-Security
288 nsISiteSecurityService* sss = gHttpHandler->GetSSService();
289 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
290
291 bool isStsHost = false;
292 uint32_t flags = mPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
293 rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
294 &isStsHost);
295
296 // if the SSS check fails, it's likely because this load is on a
297 // malformed URI or something else in the setup is wrong, so any error
298 // should be reported.
299 NS_ENSURE_SUCCESS(rv, rv);
300
301 if (isStsHost) {
302 LOG(("nsHttpChannel::Connect() STS permissions found\n"));
303 return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
304 }
305 }
306
307 // ensure that we are using a valid hostname
308 if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
309 return NS_ERROR_UNKNOWN_HOST;
310
311 // Finalize ConnectionInfo flags before SpeculativeConnect
312 mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
313 mConnectionInfo->SetPrivate(mPrivateBrowsing);
314
315 // Consider opening a TCP connection right away
316 RetrieveSSLOptions();
317 SpeculativeConnect();
318
319 // Don't allow resuming when cache must be used
320 if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
321 LOG(("Resuming from cache is not supported yet"));
322 return NS_ERROR_DOCUMENT_NOT_CACHED;
323 }
324
325 if (!gHttpHandler->UseCache()) {
326 return ContinueConnect();
327 }
328
329 // open a cache entry for this channel...
330 rv = OpenCacheEntry(usingSSL);
331
332 // do not continue if asyncOpenCacheEntry is in progress
333 if (mCacheEntriesToWaitFor) {
334 MOZ_ASSERT(NS_SUCCEEDED(rv), "Unexpected state");
335 return NS_OK;
336 }
337
338 if (NS_FAILED(rv)) {
339 LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
340 // if this channel is only allowed to pull from the cache, then
341 // we must fail if we were unable to open a cache entry.
342 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
343 // If we have a fallback URI (and we're not already
344 // falling back), process the fallback asynchronously.
345 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
346 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
347 }
348 return NS_ERROR_DOCUMENT_NOT_CACHED;
349 }
350 // otherwise, let's just proceed without using the cache.
351 }
352
353 return ContinueConnect();
354 }
355
356 nsresult
357 nsHttpChannel::ContinueConnect()
358 {
359 // we may or may not have a cache entry at this point
360 if (mCacheEntry) {
361 // read straight from the cache if possible...
362 if (mCachedContentIsValid) {
363 nsRunnableMethod<nsHttpChannel> *event = nullptr;
364 if (!mCachedContentIsPartial) {
365 AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
366 }
367 nsresult rv = ReadFromCache(true);
368 if (NS_FAILED(rv) && event) {
369 event->Revoke();
370 }
371
372 AccumulateCacheHitTelemetry(kCacheHit);
373
374 return rv;
375 }
376 else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
377 // the cache contains the requested resource, but it must be
378 // validated before we can reuse it. since we are not allowed
379 // to hit the net, there's nothing more to do. the document
380 // is effectively not in the cache.
381 LOG((" !mCachedContentIsValid && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
382 return NS_ERROR_DOCUMENT_NOT_CACHED;
383 }
384 }
385 else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
386 // If we have a fallback URI (and we're not already
387 // falling back), process the fallback asynchronously.
388 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
389 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
390 }
391 LOG((" !mCachedEntry && mLoadFlags & LOAD_ONLY_FROM_CACHE"));
392 return NS_ERROR_DOCUMENT_NOT_CACHED;
393 }
394
395 if (mLoadFlags & LOAD_NO_NETWORK_IO) {
396 LOG((" mLoadFlags & LOAD_NO_NETWORK_IO"));
397 return NS_ERROR_DOCUMENT_NOT_CACHED;
398 }
399
400 // hit the net...
401 nsresult rv = SetupTransaction();
402 if (NS_FAILED(rv)) return rv;
403
404 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
405 if (NS_FAILED(rv)) return rv;
406
407 rv = mTransactionPump->AsyncRead(this, nullptr);
408 if (NS_FAILED(rv)) return rv;
409
410 uint32_t suspendCount = mSuspendCount;
411 while (suspendCount--)
412 mTransactionPump->Suspend();
413
414 return NS_OK;
415 }
416
417 void
418 nsHttpChannel::SpeculativeConnect()
419 {
420 // Before we take the latency hit of dealing with the cache, try and
421 // get the TCP (and SSL) handshakes going so they can overlap.
422
423 // don't speculate on uses of the offline application cache,
424 // if we are offline, when doing http upgrade (i.e. websockets bootstrap),
425 // or if we can't do keep-alive (because then we couldn't reuse
426 // the speculative connection anyhow).
427 if (mApplicationCache || gIOService->IsOffline() ||
428 mUpgradeProtocolCallback || !(mCaps & NS_HTTP_ALLOW_KEEPALIVE))
429 return;
430
431 // LOAD_ONLY_FROM_CACHE and LOAD_NO_NETWORK_IO must not hit network.
432 // LOAD_FROM_CACHE and LOAD_CHECK_OFFLINE_CACHE are unlikely to hit network,
433 // so skip preconnects for them.
434 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | LOAD_FROM_CACHE |
435 LOAD_NO_NETWORK_IO | LOAD_CHECK_OFFLINE_CACHE))
436 return;
437
438 nsCOMPtr<nsIInterfaceRequestor> callbacks;
439 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
440 getter_AddRefs(callbacks));
441 if (!callbacks)
442 return;
443
444 gHttpHandler->SpeculativeConnect(
445 mConnectionInfo, callbacks,
446 mCaps & (NS_HTTP_ALLOW_RSA_FALSESTART | NS_HTTP_DISALLOW_SPDY));
447 }
448
449 void
450 nsHttpChannel::DoNotifyListenerCleanup()
451 {
452 // We don't need this info anymore
453 CleanRedirectCacheChainIfNecessary();
454 }
455
456 void
457 nsHttpChannel::HandleAsyncRedirect()
458 {
459 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
460
461 if (mSuspendCount) {
462 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
463 mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
464 return;
465 }
466
467 nsresult rv = NS_OK;
468
469 LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
470
471 // since this event is handled asynchronously, it is possible that this
472 // channel could have been canceled, in which case there would be no point
473 // in processing the redirect.
474 if (NS_SUCCEEDED(mStatus)) {
475 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
476 rv = AsyncProcessRedirection(mResponseHead->Status());
477 if (NS_FAILED(rv)) {
478 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
479 // TODO: if !DoNotRender3xxBody(), render redirect body instead.
480 // But first we need to cache 3xx bodies (bug 748510)
481 ContinueHandleAsyncRedirect(rv);
482 }
483 }
484 else {
485 ContinueHandleAsyncRedirect(NS_OK);
486 }
487 }
488
489 nsresult
490 nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv)
491 {
492 if (NS_FAILED(rv)) {
493 // If AsyncProcessRedirection fails, then we have to send out the
494 // OnStart/OnStop notifications.
495 LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv));
496 mStatus = rv;
497 DoNotifyListener();
498 }
499
500 // close the cache entry. Blow it away if we couldn't process the redirect
501 // for some reason (the cache entry might be corrupt).
502 if (mCacheEntry) {
503 if (NS_FAILED(rv))
504 mCacheEntry->AsyncDoom(nullptr);
505 }
506 CloseCacheEntry(false);
507
508 mIsPending = false;
509
510 if (mLoadGroup)
511 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
512
513 return NS_OK;
514 }
515
516 void
517 nsHttpChannel::HandleAsyncNotModified()
518 {
519 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
520
521 if (mSuspendCount) {
522 LOG(("Waiting until resume to do async not-modified [this=%p]\n",
523 this));
524 mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
525 return;
526 }
527
528 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
529
530 DoNotifyListener();
531
532 CloseCacheEntry(true);
533
534 mIsPending = false;
535
536 if (mLoadGroup)
537 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
538 }
539
540 void
541 nsHttpChannel::HandleAsyncFallback()
542 {
543 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
544
545 if (mSuspendCount) {
546 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
547 mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
548 return;
549 }
550
551 nsresult rv = NS_OK;
552
553 LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
554
555 // since this event is handled asynchronously, it is possible that this
556 // channel could have been canceled, in which case there would be no point
557 // in processing the fallback.
558 if (!mCanceled) {
559 PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
560 bool waitingForRedirectCallback;
561 rv = ProcessFallback(&waitingForRedirectCallback);
562 if (waitingForRedirectCallback)
563 return;
564 PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
565 }
566
567 ContinueHandleAsyncFallback(rv);
568 }
569
570 nsresult
571 nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
572 {
573 if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
574 // If ProcessFallback fails, then we have to send out the
575 // OnStart/OnStop notifications.
576 LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
577 mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
578 DoNotifyListener();
579 }
580
581 mIsPending = false;
582
583 if (mLoadGroup)
584 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
585
586 return rv;
587 }
588
589 void
590 nsHttpChannel::SetupTransactionLoadGroupInfo()
591 {
592 // Find the loadgroup at the end of the chain in order
593 // to make sure all channels derived from the load group
594 // use the same connection scope.
595 nsCOMPtr<nsILoadGroupChild> childLoadGroup = do_QueryInterface(mLoadGroup);
596 if (!childLoadGroup)
597 return;
598
599 nsCOMPtr<nsILoadGroup> rootLoadGroup;
600 childLoadGroup->GetRootLoadGroup(getter_AddRefs(rootLoadGroup));
601 if (!rootLoadGroup)
602 return;
603
604 // Set the load group connection scope on the transaction
605 nsCOMPtr<nsILoadGroupConnectionInfo> ci;
606 rootLoadGroup->GetConnectionInfo(getter_AddRefs(ci));
607 if (ci)
608 mTransaction->SetLoadGroupConnectionInfo(ci);
609 }
610
611 void
612 nsHttpChannel::RetrieveSSLOptions()
613 {
614 if (!IsHTTPS() || mPrivateBrowsing)
615 return;
616
617 nsIPrincipal *principal = GetPrincipal();
618 if (!principal)
619 return;
620
621 nsCOMPtr<nsIPermissionManager> permMgr =
622 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
623 if (!permMgr)
624 return;
625
626 uint32_t perm;
627 nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
628 "falsestart-rsa", &perm);
629 if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) {
630 LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] "
631 "falsestart-rsa permission found\n", this));
632 mCaps |= NS_HTTP_ALLOW_RSA_FALSESTART;
633 }
634 }
635
636 static bool
637 SafeForPipelining(nsHttpRequestHead::ParsedMethodType method,
638 const nsCString &methodString)
639 {
640 if (method == nsHttpRequestHead::kMethod_Get ||
641 method == nsHttpRequestHead::kMethod_Head ||
642 method == nsHttpRequestHead::kMethod_Options) {
643 return true;
644 }
645
646 if (method != nsHttpRequestHead::kMethod_Custom) {
647 return false;
648 }
649
650 return (!strcmp(methodString.get(), "PROPFIND") ||
651 !strcmp(methodString.get(), "PROPPATCH"));
652 }
653
654 nsresult
655 nsHttpChannel::SetupTransaction()
656 {
657 LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
658
659 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
660
661 nsresult rv;
662
663 if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
664 //
665 // disable pipelining if:
666 // (1) pipelining has been disabled by config
667 // (2) pipelining has been disabled by connection mgr info
668 // (3) request corresponds to a top-level document load (link click)
669 // (4) request method is non-idempotent
670 // (5) request is marked slow (e.g XHR)
671 //
672 if (!mAllowPipelining ||
673 (mLoadFlags & (LOAD_INITIAL_DOCUMENT_URI | INHIBIT_PIPELINE)) ||
674 !SafeForPipelining(mRequestHead.ParsedMethod(), mRequestHead.Method())) {
675 LOG((" pipelining disallowed\n"));
676 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
677 }
678 }
679
680 if (!mAllowSpdy)
681 mCaps |= NS_HTTP_DISALLOW_SPDY;
682
683 // Use the URI path if not proxying (transparent proxying such as proxy
684 // CONNECT does not count here). Also figure out what HTTP version to use.
685 nsAutoCString buf, path;
686 nsCString* requestURI;
687 if (mConnectionInfo->UsingConnect() ||
688 !mConnectionInfo->UsingHttpProxy()) {
689 rv = mURI->GetPath(path);
690 if (NS_FAILED(rv)) return rv;
691 // path may contain UTF-8 characters, so ensure that they're escaped.
692 if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
693 requestURI = &buf;
694 else
695 requestURI = &path;
696 mRequestHead.SetVersion(gHttpHandler->HttpVersion());
697 }
698 else {
699 rv = mURI->GetUserPass(buf);
700 if (NS_FAILED(rv)) return rv;
701 if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
702 strncmp(mSpec.get(), "https:", 6) == 0)) {
703 nsCOMPtr<nsIURI> tempURI;
704 rv = mURI->Clone(getter_AddRefs(tempURI));
705 if (NS_FAILED(rv)) return rv;
706 rv = tempURI->SetUserPass(EmptyCString());
707 if (NS_FAILED(rv)) return rv;
708 rv = tempURI->GetAsciiSpec(path);
709 if (NS_FAILED(rv)) return rv;
710 requestURI = &path;
711 }
712 else
713 requestURI = &mSpec;
714 mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
715 }
716
717 // trim off the #ref portion if any...
718 int32_t ref = requestURI->FindChar('#');
719 if (ref != kNotFound)
720 requestURI->SetLength(ref);
721
722 mRequestHead.SetRequestURI(*requestURI);
723
724 // set the request time for cache expiration calculations
725 mRequestTime = NowInSeconds();
726 mRequestTimeInitialized = true;
727
728 // if doing a reload, force end-to-end
729 if (mLoadFlags & LOAD_BYPASS_CACHE) {
730 // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
731 // no proxy is configured since we might be talking with a transparent
732 // proxy, i.e. one that operates at the network level. See bug #14772.
733 mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
734 // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
735 // no-cache'
736 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
737 mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "no-cache", true);
738 }
739 else if ((mLoadFlags & VALIDATE_ALWAYS) && !mCacheEntryIsWriteOnly) {
740 // We need to send 'Cache-Control: max-age=0' to force each cache along
741 // the path to the origin server to revalidate its own entry, if any,
742 // with the next cache or server. See bug #84847.
743 //
744 // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
745 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
746 mRequestHead.SetHeaderOnce(nsHttp::Cache_Control, "max-age=0", true);
747 else
748 mRequestHead.SetHeaderOnce(nsHttp::Pragma, "no-cache", true);
749 }
750
751 if (mResuming) {
752 char byteRange[32];
753 PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
754 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
755
756 if (!mEntityID.IsEmpty()) {
757 // Also, we want an error if this resource changed in the meantime
758 // Format of the entity id is: escaped_etag/size/lastmod
759 nsCString::const_iterator start, end, slash;
760 mEntityID.BeginReading(start);
761 mEntityID.EndReading(end);
762 mEntityID.BeginReading(slash);
763
764 if (FindCharInReadable('/', slash, end)) {
765 nsAutoCString ifMatch;
766 mRequestHead.SetHeader(nsHttp::If_Match,
767 NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
768
769 ++slash; // Incrementing, so that searching for '/' won't find
770 // the same slash again
771 }
772
773 if (FindCharInReadable('/', slash, end)) {
774 mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
775 Substring(++slash, end));
776 }
777 }
778 }
779
780 // create wrapper for this channel's notification callbacks
781 nsCOMPtr<nsIInterfaceRequestor> callbacks;
782 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
783 getter_AddRefs(callbacks));
784 if (!callbacks)
785 return NS_ERROR_OUT_OF_MEMORY;
786
787 // create the transaction object
788 mTransaction = new nsHttpTransaction();
789 if (!mTransaction)
790 return NS_ERROR_OUT_OF_MEMORY;
791
792 // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
793 if (mLoadFlags & LOAD_ANONYMOUS)
794 mCaps |= NS_HTTP_LOAD_ANONYMOUS;
795
796 if (mTimingEnabled)
797 mCaps |= NS_HTTP_TIMING_ENABLED;
798
799 if (mUpgradeProtocolCallback) {
800 mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
801 mRequestHead.SetHeaderOnce(nsHttp::Connection,
802 nsHttp::Upgrade.get(),
803 true);
804 mCaps |= NS_HTTP_STICKY_CONNECTION;
805 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
806 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
807 mCaps |= NS_HTTP_DISALLOW_SPDY;
808 }
809
810 nsCOMPtr<nsIAsyncInputStream> responseStream;
811 rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
812 mUploadStream, mUploadStreamHasHeaders,
813 NS_GetCurrentThread(), callbacks, this,
814 getter_AddRefs(responseStream));
815 if (NS_FAILED(rv)) {
816 mTransaction = nullptr;
817 return rv;
818 }
819
820 SetupTransactionLoadGroupInfo();
821
822 rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
823 responseStream);
824 return rv;
825 }
826
827 // NOTE: This function duplicates code from nsBaseChannel. This will go away
828 // once HTTP uses nsBaseChannel (part of bug 312760)
829 static void
830 CallTypeSniffers(void *aClosure, const uint8_t *aData, uint32_t aCount)
831 {
832 nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
833
834 nsAutoCString newType;
835 NS_SniffContent(NS_CONTENT_SNIFFER_CATEGORY, chan, aData, aCount, newType);
836 if (!newType.IsEmpty()) {
837 chan->SetContentType(newType);
838 }
839 }
840
841 nsresult
842 nsHttpChannel::CallOnStartRequest()
843 {
844 nsresult rv;
845
846 mTracingEnabled = false;
847
848 // Allow consumers to override our content type
849 if (mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) {
850 // NOTE: We can have both a txn pump and a cache pump when the cache
851 // content is partial. In that case, we need to read from the cache,
852 // because that's the one that has the initial contents. If that fails
853 // then give the transaction pump a shot.
854
855 nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
856
857 bool typeSniffersCalled = false;
858 if (mCachePump) {
859 typeSniffersCalled =
860 NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
861 }
862
863 if (!typeSniffersCalled && mTransactionPump) {
864 mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
865 }
866 }
867
868 bool shouldSniff = mResponseHead && (mResponseHead->ContentType().IsEmpty() ||
869 ((mResponseHead->ContentType().EqualsLiteral(APPLICATION_OCTET_STREAM) &&
870 (mLoadFlags & LOAD_TREAT_APPLICATION_OCTET_STREAM_AS_UNKNOWN))));
871
872 if (shouldSniff) {
873 MOZ_ASSERT(mConnectionInfo, "Should have connection info here");
874 if (!mContentTypeHint.IsEmpty())
875 mResponseHead->SetContentType(mContentTypeHint);
876 else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 &&
877 mConnectionInfo->Port() != mConnectionInfo->DefaultPort())
878 mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
879 else {
880 // Uh-oh. We had better find out what type we are!
881
882 // XXX This does not work with content-encodings... but
883 // neither does applying the conversion from the URILoader
884
885 nsCOMPtr<nsIStreamConverterService> serv;
886 rv = gHttpHandler->
887 GetStreamConverterService(getter_AddRefs(serv));
888 // If we failed, we just fall through to the "normal" case
889 if (NS_SUCCEEDED(rv)) {
890 nsCOMPtr<nsIStreamListener> converter;
891 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
892 "*/*",
893 mListener,
894 mListenerContext,
895 getter_AddRefs(converter));
896 if (NS_SUCCEEDED(rv)) {
897 mListener = converter;
898 }
899 }
900 }
901 }
902
903 if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
904 mResponseHead->SetContentCharset(mContentCharsetHint);
905
906 if (mResponseHead && mCacheEntry) {
907 // If we have a cache entry, set its predicted size to ContentLength to
908 // avoid caching an entry that will exceed the max size limit.
909 rv = mCacheEntry->SetPredictedDataSize(
910 mResponseHead->ContentLength());
911 if (NS_ERROR_FILE_TOO_BIG == rv) {
912 mCacheEntry = nullptr;
913 LOG((" entry too big, throwing away"));
914 } else {
915 NS_ENSURE_SUCCESS(rv, rv);
916 }
917 }
918
919 LOG((" calling mListener->OnStartRequest\n"));
920 if (mListener) {
921 rv = mListener->OnStartRequest(this, mListenerContext);
922 if (NS_FAILED(rv))
923 return rv;
924 } else {
925 NS_WARNING("OnStartRequest skipped because of null listener");
926 }
927
928 // install stream converter if required
929 rv = ApplyContentConversions();
930 if (NS_FAILED(rv)) return rv;
931
932 rv = EnsureAssocReq();
933 if (NS_FAILED(rv))
934 return rv;
935
936 // if this channel is for a download, close off access to the cache.
937 if (mCacheEntry && mChannelIsForDownload) {
938 mCacheEntry->AsyncDoom(nullptr);
939
940 // We must keep the cache entry in case of partial request.
941 // Concurrent access is the same, we need the entry in
942 // OnStopRequest.
943 if (!mCachedContentIsPartial && !mConcurentCacheAccess)
944 CloseCacheEntry(false);
945 }
946
947 if (!mCanceled) {
948 // create offline cache entry if offline caching was requested
949 if (ShouldUpdateOfflineCacheEntry()) {
950 LOG(("writing to the offline cache"));
951 rv = InitOfflineCacheEntry();
952 if (NS_FAILED(rv)) return rv;
953
954 // InitOfflineCacheEntry may have closed mOfflineCacheEntry
955 if (mOfflineCacheEntry) {
956 rv = InstallOfflineCacheListener();
957 if (NS_FAILED(rv)) return rv;
958 }
959 } else if (mApplicationCacheForWrite) {
960 LOG(("offline cache is up to date, not updating"));
961 CloseOfflineCacheEntry();
962 }
963 }
964
965 return NS_OK;
966 }
967
968 nsresult
969 nsHttpChannel::ProcessFailedProxyConnect(uint32_t httpStatus)
970 {
971 // Failure to set up a proxy tunnel via CONNECT means one of the following:
972 // 1) Proxy wants authorization, or forbids.
973 // 2) DNS at proxy couldn't resolve target URL.
974 // 3) Proxy connection to target failed or timed out.
975 // 4) Eve intercepted our CONNECT, and is replying with malicious HTML.
976 //
977 // Our current architecture would parse the proxy's response content with
978 // the permission of the target URL. Given #4, we must avoid rendering the
979 // body of the reply, and instead give the user a (hopefully helpful)
980 // boilerplate error page, based on just the HTTP status of the reply.
981
982 MOZ_ASSERT(mConnectionInfo->UsingConnect(),
983 "proxy connect failed but not using CONNECT?");
984 nsresult rv;
985 switch (httpStatus)
986 {
987 case 300: case 301: case 302: case 303: case 307: case 308:
988 // Bad redirect: not top-level, or it's a POST, bad/missing Location,
989 // or ProcessRedirect() failed for some other reason. Legal
990 // redirects that fail because site not available, etc., are handled
991 // elsewhere, in the regular codepath.
992 rv = NS_ERROR_CONNECTION_REFUSED;
993 break;
994 case 403: // HTTP/1.1: "Forbidden"
995 case 407: // ProcessAuthentication() failed
996 case 501: // HTTP/1.1: "Not Implemented"
997 // user sees boilerplate Mozilla "Proxy Refused Connection" page.
998 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
999 break;
1000 // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
1001 case 404: // HTTP/1.1: "Not Found"
1002 // RFC 2616: "some deployed proxies are known to return 400 or 500 when
1003 // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
1004 // we have a conflict here).
1005 case 400: // HTTP/1.1 "Bad Request"
1006 case 500: // HTTP/1.1: "Internal Server Error"
1007 /* User sees: "Address Not Found: Firefox can't find the server at
1008 * www.foo.com."
1009 */
1010 rv = NS_ERROR_UNKNOWN_HOST;
1011 break;
1012 case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
1013 // Squid returns 503 if target request fails for anything but DNS.
1014 case 503: // HTTP/1.1: "Service Unavailable"
1015 /* User sees: "Failed to Connect:
1016 * Firefox can't establish a connection to the server at
1017 * www.foo.com. Though the site seems valid, the browser
1018 * was unable to establish a connection."
1019 */
1020 rv = NS_ERROR_CONNECTION_REFUSED;
1021 break;
1022 // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
1023 // do here: picking target timeout, as DNS covered by 400/404/500
1024 case 504: // HTTP/1.1: "Gateway Timeout"
1025 // user sees: "Network Timeout: The server at www.foo.com
1026 // is taking too long to respond."
1027 rv = NS_ERROR_NET_TIMEOUT;
1028 break;
1029 // Confused proxy server or malicious response
1030 default:
1031 rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
1032 break;
1033 }
1034 LOG(("Cancelling failed proxy CONNECT [this=%p httpStatus=%u]\n",
1035 this, httpStatus));
1036 Cancel(rv);
1037 CallOnStartRequest();
1038 return rv;
1039 }
1040
1041 /**
1042 * Decide whether or not to remember Strict-Transport-Security, and whether
1043 * or not to enforce channel integrity.
1044 *
1045 * @return NS_ERROR_FAILURE if there's security information missing even though
1046 * it's an HTTPS connection.
1047 */
1048 nsresult
1049 nsHttpChannel::ProcessSTSHeader()
1050 {
1051 nsresult rv;
1052 bool isHttps = false;
1053 rv = mURI->SchemeIs("https", &isHttps);
1054 NS_ENSURE_SUCCESS(rv, rv);
1055
1056 // If this channel is not loading securely, STS doesn't do anything.
1057 // The upgrade to HTTPS takes place earlier in the channel load process.
1058 if (!isHttps)
1059 return NS_OK;
1060
1061 nsAutoCString asciiHost;
1062 rv = mURI->GetAsciiHost(asciiHost);
1063 NS_ENSURE_SUCCESS(rv, NS_OK);
1064
1065 // If the channel is not a hostname, but rather an IP, STS doesn't do
1066 // anything.
1067 PRNetAddr hostAddr;
1068 if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
1069 return NS_OK;
1070
1071 nsISiteSecurityService* sss = gHttpHandler->GetSSService();
1072 NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
1073
1074 // mSecurityInfo may not always be present, and if it's not then it is okay
1075 // to just disregard any STS headers since we know nothing about the
1076 // security of the connection.
1077 NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
1078
1079 // Check the trustworthiness of the channel (are there any cert errors?)
1080 // If there are certificate errors, we still load the data, we just ignore
1081 // any STS headers that are present.
1082 bool tlsIsBroken = false;
1083 rv = sss->ShouldIgnoreHeaders(mSecurityInfo, &tlsIsBroken);
1084 NS_ENSURE_SUCCESS(rv, NS_OK);
1085
1086 // If this was already an STS host, the connection should have been aborted
1087 // by the bad cert handler in the case of cert errors. If it didn't abort the connection,
1088 // there's probably something funny going on.
1089 // If this wasn't an STS host, errors are allowed, but no more STS processing
1090 // will happen during the session.
1091 bool wasAlreadySTSHost;
1092 uint32_t flags =
1093 NS_UsePrivateBrowsing(this) ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
1094 rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, mURI, flags,
1095 &wasAlreadySTSHost);
1096 // Failure here means STS is broken. Don't prevent the load, but this
1097 // shouldn't fail.
1098 NS_ENSURE_SUCCESS(rv, NS_OK);
1099 MOZ_ASSERT(!(wasAlreadySTSHost && tlsIsBroken),
1100 "connection should have been aborted by nss-bad-cert-handler");
1101
1102 // Any STS header is ignored if the channel is not trusted due to
1103 // certificate errors (STS Spec 7.1) -- there is nothing else to do, and
1104 // the load may progress.
1105 if (tlsIsBroken) {
1106 LOG(("STS: Transport layer is not trustworthy, ignoring "
1107 "STS headers and continuing load\n"));
1108 return NS_OK;
1109 }
1110
1111 // If there's a STS header, process it (STS Spec 7.1). At this point in
1112 // processing, the channel is trusted, so the header should not be ignored.
1113 const nsHttpAtom atom = nsHttp::ResolveAtom("Strict-Transport-Security");
1114 nsAutoCString stsHeader;
1115 rv = mResponseHead->GetHeader(atom, stsHeader);
1116 if (rv == NS_ERROR_NOT_AVAILABLE) {
1117 LOG(("STS: No STS header, continuing load.\n"));
1118 return NS_OK;
1119 }
1120 // All other failures are fatal.
1121 NS_ENSURE_SUCCESS(rv, rv);
1122
1123 rv = sss->ProcessHeader(nsISiteSecurityService::HEADER_HSTS, mURI,
1124 stsHeader.get(), flags, nullptr, nullptr);
1125 if (NS_FAILED(rv)) {
1126 AddSecurityMessage(NS_LITERAL_STRING("InvalidSTSHeaders"),
1127 NS_LITERAL_STRING("Invalid HSTS Headers"));
1128 LOG(("STS: Failed to parse STS header, continuing load.\n"));
1129 }
1130
1131 return NS_OK;
1132 }
1133
1134 bool
1135 nsHttpChannel::IsHTTPS()
1136 {
1137 bool isHttps;
1138 if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
1139 return false;
1140 return true;
1141 }
1142
1143 void
1144 nsHttpChannel::ProcessSSLInformation()
1145 {
1146 // If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
1147 // can be whitelisted for TLS False Start in future sessions. We could
1148 // do the same for DH but its rarity doesn't justify the lookup.
1149
1150 if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo ||
1151 !IsHTTPS() || mPrivateBrowsing)
1152 return;
1153
1154 nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo);
1155 nsCOMPtr<nsISSLStatusProvider> statusProvider =
1156 do_QueryInterface(mSecurityInfo);
1157 if (!ssl || !statusProvider)
1158 return;
1159 nsCOMPtr<nsISSLStatus> sslstat;
1160 statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
1161 if (!sslstat)
1162 return;
1163
1164 // If certificate exceptions are being used don't record this information
1165 // in the permission manager.
1166 bool trustCheck;
1167 if (NS_FAILED(sslstat->GetIsDomainMismatch(&trustCheck)) || trustCheck)
1168 return;
1169 if (NS_FAILED(sslstat->GetIsNotValidAtThisTime(&trustCheck)) || trustCheck)
1170 return;
1171 if (NS_FAILED(sslstat->GetIsUntrusted(&trustCheck)) || trustCheck)
1172 return;
1173
1174 int16_t kea = ssl->GetKEAUsed();
1175
1176 nsIPrincipal *principal = GetPrincipal();
1177 if (!principal)
1178 return;
1179
1180 // set a permission manager flag that future transactions can
1181 // use via RetrieveSSLOptions(()
1182
1183 nsCOMPtr<nsIPermissionManager> permMgr =
1184 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
1185 if (!permMgr)
1186 return;
1187
1188 // Allow this to stand for a week
1189 int64_t expireTime = (PR_Now() / PR_USEC_PER_MSEC) +
1190 (86400 * 7 * PR_MSEC_PER_SEC);
1191
1192 if (kea == ssl_kea_rsa) {
1193 permMgr->AddFromPrincipal(principal, "falsestart-rsa",
1194 nsIPermissionManager::ALLOW_ACTION,
1195 nsIPermissionManager::EXPIRE_TIME,
1196 expireTime);
1197 LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] "
1198 "falsestart-rsa permission granted for this host\n", this));
1199 } else {
1200 permMgr->RemoveFromPrincipal(principal, "falsestart-rsa");
1201 }
1202 }
1203
1204 nsresult
1205 nsHttpChannel::ProcessResponse()
1206 {
1207 nsresult rv;
1208 uint32_t httpStatus = mResponseHead->Status();
1209
1210 // Gather data on whether the transaction and page (if this is
1211 // the initial page load) is being loaded with SSL.
1212 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_IS_SSL,
1213 mConnectionInfo->UsingSSL());
1214 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
1215 Telemetry::Accumulate(Telemetry::HTTP_PAGELOAD_IS_SSL,
1216 mConnectionInfo->UsingSSL());
1217 }
1218
1219 LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
1220 this, httpStatus));
1221
1222 if (mTransaction->ProxyConnectFailed()) {
1223 // Only allow 407 (authentication required) to continue
1224 if (httpStatus != 407)
1225 return ProcessFailedProxyConnect(httpStatus);
1226 // If proxy CONNECT response needs to complete, wait to process connection
1227 // for Strict-Transport-Security.
1228 } else {
1229 // Given a successful connection, process any STS data that's relevant.
1230 rv = ProcessSTSHeader();
1231 MOZ_ASSERT(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
1232 }
1233
1234 MOZ_ASSERT(!mCachedContentIsValid);
1235
1236 ProcessSSLInformation();
1237
1238 // notify "http-on-examine-response" observers
1239 gHttpHandler->OnExamineResponse(this);
1240
1241 SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
1242
1243 // handle unused username and password in url (see bug 232567)
1244 if (httpStatus != 401 && httpStatus != 407) {
1245 if (!mAuthRetryPending)
1246 mAuthProvider->CheckForSuperfluousAuth();
1247 if (mCanceled)
1248 return CallOnStartRequest();
1249
1250 // reset the authentication's current continuation state because our
1251 // last authentication attempt has been completed successfully
1252 mAuthProvider->Disconnect(NS_ERROR_ABORT);
1253 mAuthProvider = nullptr;
1254 LOG((" continuation state has been reset"));
1255 }
1256
1257 bool successfulReval = false;
1258
1259 // handle different server response categories. Note that we handle
1260 // caching or not caching of error pages in
1261 // nsHttpResponseHead::MustValidate; if you change this switch, update that
1262 // one
1263 switch (httpStatus) {
1264 case 200:
1265 case 203:
1266 // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
1267 // So if a server does that and sends 200 instead of 206 that we
1268 // expect, notify our caller.
1269 // However, if we wanted to start from the beginning, let it go through
1270 if (mResuming && mStartPos != 0) {
1271 LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
1272 Cancel(NS_ERROR_NOT_RESUMABLE);
1273 rv = CallOnStartRequest();
1274 break;
1275 }
1276 // these can normally be cached
1277 rv = ProcessNormal();
1278 MaybeInvalidateCacheEntryForSubsequentGet();
1279 break;
1280 case 206:
1281 if (mCachedContentIsPartial) // an internal byte range request...
1282 rv = ProcessPartialContent();
1283 else {
1284 mCacheInputStream.CloseAndRelease();
1285 rv = ProcessNormal();
1286 }
1287 break;
1288 case 300:
1289 case 301:
1290 case 302:
1291 case 307:
1292 case 308:
1293 case 303:
1294 #if 0
1295 case 305: // disabled as a security measure (see bug 187996).
1296 #endif
1297 // don't store the response body for redirects
1298 MaybeInvalidateCacheEntryForSubsequentGet();
1299 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
1300 rv = AsyncProcessRedirection(httpStatus);
1301 if (NS_FAILED(rv)) {
1302 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
1303 LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
1304 // don't cache failed redirect responses.
1305 if (mCacheEntry)
1306 mCacheEntry->AsyncDoom(nullptr);
1307 if (DoNotRender3xxBody(rv)) {
1308 mStatus = rv;
1309 DoNotifyListener();
1310 } else {
1311 rv = ContinueProcessResponse(rv);
1312 }
1313 }
1314 break;
1315 case 304:
1316 rv = ProcessNotModified();
1317 if (NS_FAILED(rv)) {
1318 LOG(("ProcessNotModified failed [rv=%x]\n", rv));
1319 mCacheInputStream.CloseAndRelease();
1320 rv = ProcessNormal();
1321 }
1322 else {
1323 successfulReval = true;
1324 }
1325 break;
1326 case 401:
1327 case 407:
1328 rv = mAuthProvider->ProcessAuthentication(
1329 httpStatus, mConnectionInfo->UsingSSL() &&
1330 mTransaction->ProxyConnectFailed());
1331 if (rv == NS_ERROR_IN_PROGRESS) {
1332 // authentication prompt has been invoked and result
1333 // is expected asynchronously
1334 mAuthRetryPending = true;
1335 if (httpStatus == 407 || mTransaction->ProxyConnectFailed())
1336 mProxyAuthPending = true;
1337
1338 // suspend the transaction pump to stop receiving the
1339 // unauthenticated content data. We will throw that data
1340 // away when user provides credentials or resume the pump
1341 // when user refuses to authenticate.
1342 LOG(("Suspending the transaction, asynchronously prompting for credentials"));
1343 mTransactionPump->Suspend();
1344 rv = NS_OK;
1345 }
1346 else if (NS_FAILED(rv)) {
1347 LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
1348 if (mTransaction->ProxyConnectFailed())
1349 return ProcessFailedProxyConnect(httpStatus);
1350 if (!mAuthRetryPending)
1351 mAuthProvider->CheckForSuperfluousAuth();
1352 rv = ProcessNormal();
1353 }
1354 else
1355 mAuthRetryPending = true; // see DoAuthRetry
1356 break;
1357 default:
1358 rv = ProcessNormal();
1359 MaybeInvalidateCacheEntryForSubsequentGet();
1360 break;
1361 }
1362
1363 CacheDisposition cacheDisposition;
1364 if (!mDidReval)
1365 cacheDisposition = kCacheMissed;
1366 else if (successfulReval)
1367 cacheDisposition = kCacheHitViaReval;
1368 else
1369 cacheDisposition = kCacheMissedViaReval;
1370
1371 AccumulateCacheHitTelemetry(cacheDisposition);
1372
1373 return rv;
1374 }
1375
1376 nsresult
1377 nsHttpChannel::ContinueProcessResponse(nsresult rv)
1378 {
1379 bool doNotRender = DoNotRender3xxBody(rv);
1380
1381 if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
1382 bool isHTTP = false;
1383 if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
1384 isHTTP = false;
1385 if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
1386 isHTTP = false;
1387
1388 if (!isHTTP) {
1389 // This was a blocked attempt to redirect and subvert the system by
1390 // redirecting to another protocol (perhaps javascript:)
1391 // In that case we want to throw an error instead of displaying the
1392 // non-redirected response body.
1393 LOG(("ContinueProcessResponse detected rejected Non-HTTP Redirection"));
1394 doNotRender = true;
1395 rv = NS_ERROR_CORRUPTED_CONTENT;
1396 }
1397 }
1398
1399 if (doNotRender) {
1400 Cancel(rv);
1401 DoNotifyListener();
1402 return rv;
1403 }
1404
1405 if (NS_SUCCEEDED(rv)) {
1406 UpdateInhibitPersistentCachingFlag();
1407
1408 InitCacheEntry();
1409 CloseCacheEntry(false);
1410
1411 if (mApplicationCacheForWrite) {
1412 // Store response in the offline cache
1413 InitOfflineCacheEntry();
1414 CloseOfflineCacheEntry();
1415 }
1416 return NS_OK;
1417 }
1418
1419 LOG(("ContinueProcessResponse got failure result [rv=%x]\n", rv));
1420 if (mTransaction->ProxyConnectFailed()) {
1421 return ProcessFailedProxyConnect(mRedirectType);
1422 }
1423 return ProcessNormal();
1424 }
1425
1426 nsresult
1427 nsHttpChannel::ProcessNormal()
1428 {
1429 nsresult rv;
1430
1431 LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
1432
1433 bool succeeded;
1434 rv = GetRequestSucceeded(&succeeded);
1435 if (NS_SUCCEEDED(rv) && !succeeded) {
1436 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
1437 bool waitingForRedirectCallback;
1438 (void)ProcessFallback(&waitingForRedirectCallback);
1439 if (waitingForRedirectCallback) {
1440 // The transaction has been suspended by ProcessFallback.
1441 return NS_OK;
1442 }
1443 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
1444 }
1445
1446 return ContinueProcessNormal(NS_OK);
1447 }
1448
1449 nsresult
1450 nsHttpChannel::ContinueProcessNormal(nsresult rv)
1451 {
1452 if (NS_FAILED(rv)) {
1453 // Fill the failure status here, we have failed to fall back, thus we
1454 // have to report our status as failed.
1455 mStatus = rv;
1456 DoNotifyListener();
1457 return rv;
1458 }
1459
1460 if (mFallingBack) {
1461 // Do not continue with normal processing, fallback is in
1462 // progress now.
1463 return NS_OK;
1464 }
1465
1466 // if we're here, then any byte-range requests failed to result in a partial
1467 // response. we must clear this flag to prevent BufferPartialContent from
1468 // being called inside our OnDataAvailable (see bug 136678).
1469 mCachedContentIsPartial = false;
1470
1471 ClearBogusContentEncodingIfNeeded();
1472
1473 UpdateInhibitPersistentCachingFlag();
1474
1475 // this must be called before firing OnStartRequest, since http clients,
1476 // such as imagelib, expect our cache entry to already have the correct
1477 // expiration time (bug 87710).
1478 if (mCacheEntry) {
1479 rv = InitCacheEntry();
1480 if (NS_FAILED(rv))
1481 CloseCacheEntry(true);
1482 }
1483
1484 // Check that the server sent us what we were asking for
1485 if (mResuming) {
1486 // Create an entity id from the response
1487 nsAutoCString id;
1488 rv = GetEntityID(id);
1489 if (NS_FAILED(rv)) {
1490 // If creating an entity id is not possible -> error
1491 Cancel(NS_ERROR_NOT_RESUMABLE);
1492 }
1493 else if (mResponseHead->Status() != 206 &&
1494 mResponseHead->Status() != 200) {
1495 // Probably 404 Not Found, 412 Precondition Failed or
1496 // 416 Invalid Range -> error
1497 LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
1498 this));
1499 Cancel(NS_ERROR_ENTITY_CHANGED);
1500 }
1501 // If we were passed an entity id, verify it's equal to the server's
1502 else if (!mEntityID.IsEmpty()) {
1503 if (!mEntityID.Equals(id)) {
1504 LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
1505 mEntityID.get(), id.get(), this));
1506 Cancel(NS_ERROR_ENTITY_CHANGED);
1507 }
1508 }
1509 }
1510
1511 rv = CallOnStartRequest();
1512 if (NS_FAILED(rv)) return rv;
1513
1514 // install cache listener if we still have a cache entry open
1515 if (mCacheEntry && !mLoadedFromApplicationCache) {
1516 rv = InstallCacheListener();
1517 if (NS_FAILED(rv)) return rv;
1518 }
1519
1520 return NS_OK;
1521 }
1522
1523 nsresult
1524 nsHttpChannel::PromptTempRedirect()
1525 {
1526 if (!gHttpHandler->PromptTempRedirect()) {
1527 return NS_OK;
1528 }
1529 nsresult rv;
1530 nsCOMPtr<nsIStringBundleService> bundleService =
1531 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
1532 if (NS_FAILED(rv)) return rv;
1533
1534 nsCOMPtr<nsIStringBundle> stringBundle;
1535 rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
1536 if (NS_FAILED(rv)) return rv;
1537
1538 nsXPIDLString messageString;
1539 rv = stringBundle->GetStringFromName(MOZ_UTF16("RepostFormData"), getter_Copies(messageString));
1540 // GetStringFromName can return NS_OK and nullptr messageString.
1541 if (NS_SUCCEEDED(rv) && messageString) {
1542 bool repost = false;
1543
1544 nsCOMPtr<nsIPrompt> prompt;
1545 GetCallback(prompt);
1546 if (!prompt)
1547 return NS_ERROR_NO_INTERFACE;
1548
1549 prompt->Confirm(nullptr, messageString, &repost);
1550 if (!repost)
1551 return NS_ERROR_FAILURE;
1552 }
1553
1554 return rv;
1555 }
1556
1557 nsresult
1558 nsHttpChannel::ProxyFailover()
1559 {
1560 LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
1561
1562 nsresult rv;
1563
1564 nsCOMPtr<nsIProtocolProxyService> pps =
1565 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1566 if (NS_FAILED(rv))
1567 return rv;
1568
1569 nsCOMPtr<nsIProxyInfo> pi;
1570 rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
1571 getter_AddRefs(pi));
1572 if (NS_FAILED(rv))
1573 return rv;
1574
1575 // XXXbz so where does this codepath remove us from the loadgroup,
1576 // exactly?
1577 return AsyncDoReplaceWithProxy(pi);
1578 }
1579
1580 void
1581 nsHttpChannel::HandleAsyncRedirectChannelToHttps()
1582 {
1583 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
1584
1585 if (mSuspendCount) {
1586 LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
1587 mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
1588 return;
1589 }
1590
1591 nsresult rv = StartRedirectChannelToHttps();
1592 if (NS_FAILED(rv))
1593 ContinueAsyncRedirectChannelToURI(rv);
1594 }
1595
1596 nsresult
1597 nsHttpChannel::StartRedirectChannelToHttps()
1598 {
1599 nsresult rv = NS_OK;
1600 LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
1601
1602 nsCOMPtr<nsIURI> upgradedURI;
1603
1604 rv = mURI->Clone(getter_AddRefs(upgradedURI));
1605 NS_ENSURE_SUCCESS(rv,rv);
1606
1607 upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
1608
1609 int32_t oldPort = -1;
1610 rv = mURI->GetPort(&oldPort);
1611 if (NS_FAILED(rv)) return rv;
1612
1613 // Keep any nonstandard ports so only the scheme is changed.
1614 // For example:
1615 // http://foo.com:80 -> https://foo.com:443
1616 // http://foo.com:81 -> https://foo.com:81
1617
1618 if (oldPort == 80 || oldPort == -1)
1619 upgradedURI->SetPort(-1);
1620 else
1621 upgradedURI->SetPort(oldPort);
1622
1623 return StartRedirectChannelToURI(upgradedURI,
1624 nsIChannelEventSink::REDIRECT_PERMANENT);
1625 }
1626
1627 void
1628 nsHttpChannel::HandleAsyncAPIRedirect()
1629 {
1630 NS_PRECONDITION(!mCallOnResume, "How did that happen?");
1631 NS_PRECONDITION(mAPIRedirectToURI, "How did that happen?");
1632
1633 if (mSuspendCount) {
1634 LOG(("Waiting until resume to do async API redirect [this=%p]\n", this));
1635 mCallOnResume = &nsHttpChannel::HandleAsyncAPIRedirect;
1636 return;
1637 }
1638
1639 nsresult rv = StartRedirectChannelToURI(mAPIRedirectToURI,
1640 nsIChannelEventSink::REDIRECT_PERMANENT);
1641 if (NS_FAILED(rv))
1642 ContinueAsyncRedirectChannelToURI(rv);
1643
1644 return;
1645 }
1646
1647 nsresult
1648 nsHttpChannel::StartRedirectChannelToURI(nsIURI *upgradedURI, uint32_t flags)
1649 {
1650 nsresult rv = NS_OK;
1651 LOG(("nsHttpChannel::StartRedirectChannelToURI()\n"));
1652
1653 nsCOMPtr<nsIChannel> newChannel;
1654
1655 nsCOMPtr<nsIIOService> ioService;
1656 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
1657 NS_ENSURE_SUCCESS(rv, rv);
1658
1659 rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel));
1660 NS_ENSURE_SUCCESS(rv, rv);
1661
1662 rv = SetupReplacementChannel(upgradedURI, newChannel, true);
1663 NS_ENSURE_SUCCESS(rv, rv);
1664
1665 // Inform consumers about this fake redirect
1666 mRedirectChannel = newChannel;
1667
1668 PushRedirectAsyncFunc(
1669 &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
1670 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
1671
1672 if (NS_SUCCEEDED(rv))
1673 rv = WaitForRedirectCallback();
1674
1675 if (NS_FAILED(rv)) {
1676 AutoRedirectVetoNotifier notifier(this);
1677
1678 /* Remove the async call to ContinueAsyncRedirectChannelToURI().
1679 * It is called directly by our callers upon return (to clean up
1680 * the failed redirect). */
1681 PopRedirectAsyncFunc(
1682 &nsHttpChannel::ContinueAsyncRedirectChannelToURI);
1683 }
1684
1685 return rv;
1686 }
1687
1688 nsresult
1689 nsHttpChannel::ContinueAsyncRedirectChannelToURI(nsresult rv)
1690 {
1691 if (NS_SUCCEEDED(rv))
1692 rv = OpenRedirectChannel(rv);
1693
1694 if (NS_FAILED(rv)) {
1695 // Fill the failure status here, the update to https had been vetoed
1696 // but from the security reasons we have to discard the whole channel
1697 // load.
1698 mStatus = rv;
1699 }
1700
1701 if (mLoadGroup)
1702 mLoadGroup->RemoveRequest(this, nullptr, mStatus);
1703
1704 if (NS_FAILED(rv)) {
1705 // We have to manually notify the listener because there is not any pump
1706 // that would call our OnStart/StopRequest after resume from waiting for
1707 // the redirect callback.
1708 DoNotifyListener();
1709 }
1710
1711 return rv;
1712 }
1713
1714 nsresult
1715 nsHttpChannel::OpenRedirectChannel(nsresult rv)
1716 {
1717 AutoRedirectVetoNotifier notifier(this);
1718
1719 // Make sure to do this _after_ calling OnChannelRedirect
1720 mRedirectChannel->SetOriginalURI(mOriginalURI);
1721
1722 // And now, notify observers the deprecated way
1723 nsCOMPtr<nsIHttpEventSink> httpEventSink;
1724 GetCallback(httpEventSink);
1725 if (httpEventSink) {
1726 // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
1727 // versions.
1728 rv = httpEventSink->OnRedirect(this, mRedirectChannel);
1729 if (NS_FAILED(rv)) {
1730 return rv;
1731 }
1732 }
1733
1734 // open new channel
1735 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
1736 if (NS_FAILED(rv)) {
1737 return rv;
1738 }
1739
1740 mStatus = NS_BINDING_REDIRECTED;
1741
1742 notifier.RedirectSucceeded();
1743
1744 ReleaseListeners();
1745
1746 return NS_OK;
1747 }
1748
1749 nsresult
1750 nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi)
1751 {
1752 LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
1753 nsresult rv;
1754
1755 nsCOMPtr<nsIChannel> newChannel;
1756 rv = gHttpHandler->NewProxiedChannel(mURI, pi, mProxyResolveFlags,
1757 mProxyURI, getter_AddRefs(newChannel));
1758 if (NS_FAILED(rv))
1759 return rv;
1760
1761 rv = SetupReplacementChannel(mURI, newChannel, true);
1762 if (NS_FAILED(rv))
1763 return rv;
1764
1765 // Inform consumers about this fake redirect
1766 mRedirectChannel = newChannel;
1767 uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL;
1768
1769 PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
1770 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
1771
1772 if (NS_SUCCEEDED(rv))
1773 rv = WaitForRedirectCallback();
1774
1775 if (NS_FAILED(rv)) {
1776 AutoRedirectVetoNotifier notifier(this);
1777 PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
1778 }
1779
1780 return rv;
1781 }
1782
1783 nsresult
1784 nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv)
1785 {
1786 AutoRedirectVetoNotifier notifier(this);
1787
1788 if (NS_FAILED(rv))
1789 return rv;
1790
1791 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
1792
1793 // Make sure to do this _after_ calling OnChannelRedirect
1794 mRedirectChannel->SetOriginalURI(mOriginalURI);
1795
1796 // open new channel
1797 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
1798 if (NS_FAILED(rv))
1799 return rv;
1800
1801 mStatus = NS_BINDING_REDIRECTED;
1802
1803 notifier.RedirectSucceeded();
1804
1805 ReleaseListeners();
1806
1807 return rv;
1808 }
1809
1810 nsresult
1811 nsHttpChannel::ResolveProxy()
1812 {
1813 LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
1814
1815 nsresult rv;
1816
1817 nsCOMPtr<nsIProtocolProxyService> pps =
1818 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1819 if (NS_FAILED(rv))
1820 return rv;
1821
1822 // using the nsIProtocolProxyService2 allows a minor performance
1823 // optimization, but if an add-on has only provided the original interface
1824 // then it is ok to use that version.
1825 nsCOMPtr<nsIProtocolProxyService2> pps2 = do_QueryInterface(pps);
1826 if (pps2) {
1827 rv = pps2->AsyncResolve2(this, mProxyResolveFlags,
1828 this, getter_AddRefs(mProxyRequest));
1829 } else {
1830 rv = pps->AsyncResolve(this, mProxyResolveFlags,
1831 this, getter_AddRefs(mProxyRequest));
1832 }
1833
1834 return rv;
1835 }
1836
1837 bool
1838 nsHttpChannel::ResponseWouldVary(nsICacheEntry* entry) const
1839 {
1840 nsresult rv;
1841 nsAutoCString buf, metaKey;
1842 mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
1843 if (!buf.IsEmpty()) {
1844 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
1845
1846 // enumerate the elements of the Vary header...
1847 char *val = buf.BeginWriting(); // going to munge buf
1848 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1849 while (token) {
1850 LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] " \
1851 "processing %s\n",
1852 this, token));
1853 //
1854 // if "*", then assume response would vary. technically speaking,
1855 // "Vary: header, *" is not permitted, but we allow it anyways.
1856 //
1857 // We hash values of cookie-headers for the following reasons:
1858 //
1859 // 1- cookies can be very large in size
1860 //
1861 // 2- cookies may contain sensitive information. (for parity with
1862 // out policy of not storing Set-cookie headers in the cache
1863 // meta data, we likewise do not want to store cookie headers
1864 // here.)
1865 //
1866 if (*token == '*')
1867 return true; // if we encounter this, just get out of here
1868
1869 // build cache meta data key...
1870 metaKey = prefix + nsDependentCString(token);
1871
1872 // check the last value of the given request header to see if it has
1873 // since changed. if so, then indeed the cached response is invalid.
1874 nsXPIDLCString lastVal;
1875 entry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
1876 LOG(("nsHttpChannel::ResponseWouldVary [channel=%p] "
1877 "stored value = \"%s\"\n",
1878 this, lastVal.get()));
1879
1880 // Look for value of "Cookie" in the request headers
1881 nsHttpAtom atom = nsHttp::ResolveAtom(token);
1882 const char *newVal = mRequestHead.PeekHeader(atom);
1883 if (!lastVal.IsEmpty()) {
1884 // value for this header in cache, but no value in request
1885 if (!newVal)
1886 return true; // yes - response would vary
1887
1888 // If this is a cookie-header, stored metadata is not
1889 // the value itself but the hash. So we also hash the
1890 // outgoing value here in order to compare the hashes
1891 nsAutoCString hash;
1892 if (atom == nsHttp::Cookie) {
1893 rv = Hash(newVal, hash);
1894 // If hash failed, be conservative (the cached hash
1895 // exists at this point) and claim response would vary
1896 if (NS_FAILED(rv))
1897 return true;
1898 newVal = hash.get();
1899
1900 LOG(("nsHttpChannel::ResponseWouldVary [this=%p] " \
1901 "set-cookie value hashed to %s\n",
1902 this, newVal));
1903 }
1904
1905 if (strcmp(newVal, lastVal))
1906 return true; // yes, response would vary
1907
1908 } else if (newVal) { // old value is empty, but newVal is set
1909 return true;
1910 }
1911
1912 // next token...
1913 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1914 }
1915 }
1916 return false;
1917 }
1918
1919 // We need to have an implementation of this function just so that we can keep
1920 // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
1921 // to set a member function ptr to a base class function.
1922 void
1923 nsHttpChannel::HandleAsyncAbort()
1924 {
1925 HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
1926 }
1927
1928
1929 nsresult
1930 nsHttpChannel::EnsureAssocReq()
1931 {
1932 // Confirm Assoc-Req response header on pipelined transactions
1933 // per draft-nottingham-http-pipeline-01.txt
1934 // of the form: GET http://blah.com/foo/bar?qv
1935 // return NS_OK as long as we don't find a violation
1936 // (i.e. no header is ok, as are malformed headers, as are
1937 // transactions that have not been pipelined (unless those have been
1938 // opted in via pragma))
1939
1940 if (!mResponseHead)
1941 return NS_OK;
1942
1943 const char *assoc_val = mResponseHead->PeekHeader(nsHttp::Assoc_Req);
1944 if (!assoc_val)
1945 return NS_OK;
1946
1947 if (!mTransaction || !mURI)
1948 return NS_OK;
1949
1950 if (!mTransaction->PipelinePosition()) {
1951 // "Pragma: X-Verify-Assoc-Req" can be used to verify even non pipelined
1952 // transactions. It is used by test harness.
1953
1954 const char *pragma_val = mResponseHead->PeekHeader(nsHttp::Pragma);
1955 if (!pragma_val ||
1956 !nsHttp::FindToken(pragma_val, "X-Verify-Assoc-Req",
1957 HTTP_HEADER_VALUE_SEPS))
1958 return NS_OK;
1959 }
1960
1961 char *method = net_FindCharNotInSet(assoc_val, HTTP_LWS);
1962 if (!method)
1963 return NS_OK;
1964
1965 bool equals;
1966 char *endofmethod;
1967
1968 assoc_val = nullptr;
1969 endofmethod = net_FindCharInSet(method, HTTP_LWS);
1970 if (endofmethod)
1971 assoc_val = net_FindCharNotInSet(endofmethod, HTTP_LWS);
1972 if (!assoc_val)
1973 return NS_OK;
1974
1975 // check the method
1976 int32_t methodlen = strlen(mRequestHead.Method().get());
1977 if ((methodlen != (endofmethod - method)) ||
1978 PL_strncmp(method,
1979 mRequestHead.Method().get(),
1980 endofmethod - method)) {
1981 LOG((" Assoc-Req failure Method %s", method));
1982 if (mConnectionInfo)
1983 gHttpHandler->ConnMgr()->
1984 PipelineFeedbackInfo(mConnectionInfo,
1985 nsHttpConnectionMgr::RedCorruptedContent,
1986 nullptr, 0);
1987
1988 nsCOMPtr<nsIConsoleService> consoleService =
1989 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
1990 if (consoleService) {
1991 nsAutoString message
1992 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
1993 AppendASCIItoUTF16(
1994 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
1995 message);
1996 message += NS_LITERAL_STRING(" expected method ");
1997 AppendASCIItoUTF16(mRequestHead.Method().get(), message);
1998 consoleService->LogStringMessage(message.get());
1999 }
2000
2001 if (gHttpHandler->EnforceAssocReq())
2002 return NS_ERROR_CORRUPTED_CONTENT;
2003 return NS_OK;
2004 }
2005
2006 // check the URL
2007 nsCOMPtr<nsIURI> assoc_url;
2008 if (NS_FAILED(NS_NewURI(getter_AddRefs(assoc_url), assoc_val)) ||
2009 !assoc_url)
2010 return NS_OK;
2011
2012 mURI->Equals(assoc_url, &equals);
2013 if (!equals) {
2014 LOG((" Assoc-Req failure URL %s", assoc_val));
2015 if (mConnectionInfo)
2016 gHttpHandler->ConnMgr()->
2017 PipelineFeedbackInfo(mConnectionInfo,
2018 nsHttpConnectionMgr::RedCorruptedContent,
2019 nullptr, 0);
2020
2021 nsCOMPtr<nsIConsoleService> consoleService =
2022 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
2023 if (consoleService) {
2024 nsAutoString message
2025 (NS_LITERAL_STRING("Failed Assoc-Req. Received "));
2026 AppendASCIItoUTF16(
2027 mResponseHead->PeekHeader(nsHttp::Assoc_Req),
2028 message);
2029 message += NS_LITERAL_STRING(" expected URL ");
2030 AppendASCIItoUTF16(mSpec.get(), message);
2031 consoleService->LogStringMessage(message.get());
2032 }
2033
2034 if (gHttpHandler->EnforceAssocReq())
2035 return NS_ERROR_CORRUPTED_CONTENT;
2036 }
2037 return NS_OK;
2038 }
2039
2040 //-----------------------------------------------------------------------------
2041 // nsHttpChannel <byte-range>
2042 //-----------------------------------------------------------------------------
2043
2044 bool
2045 nsHttpChannel::IsResumable(int64_t partialLen, int64_t contentLength,
2046 bool ignoreMissingPartialLen) const
2047 {
2048 bool hasContentEncoding =
2049 mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)
2050 != nullptr;
2051
2052 return (partialLen < contentLength) &&
2053 (partialLen > 0 || ignoreMissingPartialLen) &&
2054 !hasContentEncoding &&
2055 mCachedResponseHead->IsResumable() &&
2056 !mCustomConditionalRequest &&
2057 !mCachedResponseHead->NoStore();
2058 }
2059
2060 nsresult
2061 nsHttpChannel::MaybeSetupByteRangeRequest(int64_t partialLen, int64_t contentLength)
2062 {
2063 // Be pesimistic
2064 mIsPartialRequest = false;
2065
2066 if (!IsResumable(partialLen, contentLength))
2067 return NS_ERROR_NOT_RESUMABLE;
2068
2069 // looks like a partial entry we can reuse; add If-Range
2070 // and Range headers.
2071 nsresult rv = SetupByteRangeRequest(partialLen);
2072 if (NS_FAILED(rv)) {
2073 // Make the request unconditional again.
2074 mRequestHead.ClearHeader(nsHttp::Range);
2075 mRequestHead.ClearHeader(nsHttp::If_Range);
2076 }
2077
2078 return rv;
2079 }
2080
2081 nsresult
2082 nsHttpChannel::SetupByteRangeRequest(int64_t partialLen)
2083 {
2084 // cached content has been found to be partial, add necessary request
2085 // headers to complete cache entry.
2086
2087 // use strongest validator available...
2088 const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
2089 if (!val)
2090 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
2091 if (!val) {
2092 // if we hit this code it means mCachedResponseHead->IsResumable() is
2093 // either broken or not being called.
2094 NS_NOTREACHED("no cache validator");
2095 mIsPartialRequest = false;
2096 return NS_ERROR_FAILURE;
2097 }
2098
2099 char buf[64];
2100 PR_snprintf(buf, sizeof(buf), "bytes=%lld-", partialLen);
2101
2102 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
2103 mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
2104 mIsPartialRequest = true;
2105
2106 return NS_OK;
2107 }
2108
2109 nsresult
2110 nsHttpChannel::ProcessPartialContent()
2111 {
2112 // ok, we've just received a 206
2113 //
2114 // we need to stream whatever data is in the cache out first, and then
2115 // pick up whatever data is on the wire, writing it into the cache.
2116
2117 LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
2118
2119 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
2120 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
2121
2122 // Make sure to clear bogus content-encodings before looking at the header
2123 ClearBogusContentEncodingIfNeeded();
2124
2125 // Check if the content-encoding we now got is different from the one we
2126 // got before
2127 if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
2128 mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
2129 != 0) {
2130 Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
2131 return CallOnStartRequest();
2132 }
2133
2134 nsresult rv;
2135
2136 int64_t cachedContentLength = mCachedResponseHead->ContentLength();
2137 int64_t entitySize = mResponseHead->TotalEntitySize();
2138
2139 LOG(("nsHttpChannel::ProcessPartialContent [this=%p trans=%p] "
2140 "original content-length %lld, entity-size %lld, content-range %s\n",
2141 this, mTransaction.get(), cachedContentLength, entitySize,
2142 mResponseHead->PeekHeader(nsHttp::Content_Range)));
2143
2144 if ((entitySize >= 0) && (cachedContentLength >= 0) &&
2145 (entitySize != cachedContentLength)) {
2146 LOG(("nsHttpChannel::ProcessPartialContent [this=%p] "
2147 "206 has different total entity size than the content length "
2148 "of the original partially cached entity.\n", this));
2149
2150 mCacheEntry->AsyncDoom(nullptr);
2151 Cancel(NS_ERROR_CORRUPTED_CONTENT);
2152 return CallOnStartRequest();
2153 }
2154
2155 if (mConcurentCacheAccess) {
2156 // We started to read cached data sooner than its write has been done.
2157 // But the concurrent write has not finished completely, so we had to
2158 // do a range request. Now let the content coming from the network
2159 // be presented to consumers and also stored to the cache entry.
2160
2161 rv = InstallCacheListener(mLogicalOffset);
2162 if (NS_FAILED(rv)) return rv;
2163
2164 if (mOfflineCacheEntry) {
2165 rv = InstallOfflineCacheListener(mLogicalOffset);
2166 if (NS_FAILED(rv)) return rv;
2167 }
2168
2169 // merge any new headers with the cached response headers
2170 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
2171 if (NS_FAILED(rv)) return rv;
2172
2173 // update the cached response head
2174 nsAutoCString head;
2175 mCachedResponseHead->Flatten(head, true);
2176 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
2177 if (NS_FAILED(rv)) return rv;
2178
2179 UpdateInhibitPersistentCachingFlag();
2180
2181 rv = UpdateExpirationTime();
2182 if (NS_FAILED(rv)) return rv;
2183
2184 mCachedContentIsPartial = false;
2185
2186 // notify observers interested in looking at a response that has been
2187 // merged with any cached headers (http-on-examine-merged-response).
2188 gHttpHandler->OnExamineMergedResponse(this);
2189 }
2190 else {
2191 // suspend the current transaction
2192 rv = mTransactionPump->Suspend();
2193 if (NS_FAILED(rv)) return rv;
2194
2195 // merge any new headers with the cached response headers
2196 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
2197 if (NS_FAILED(rv)) return rv;
2198
2199 // update the cached response head
2200 nsAutoCString head;
2201 mCachedResponseHead->Flatten(head, true);
2202 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
2203 if (NS_FAILED(rv)) return rv;
2204
2205 // make the cached response be the current response
2206 mResponseHead = mCachedResponseHead;
2207
2208 UpdateInhibitPersistentCachingFlag();
2209
2210 rv = UpdateExpirationTime();
2211 if (NS_FAILED(rv)) return rv;
2212
2213 // notify observers interested in looking at a response that has been
2214 // merged with any cached headers (http-on-examine-merged-response).
2215 gHttpHandler->OnExamineMergedResponse(this);
2216
2217 // the cached content is valid, although incomplete.
2218 mCachedContentIsValid = true;
2219 rv = ReadFromCache(false);
2220 }
2221
2222 return rv;
2223 }
2224
2225 nsresult
2226 nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone)
2227 {
2228 nsresult rv;
2229
2230 LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
2231
2232 // by default, assume we would have streamed all data or failed...
2233 *streamDone = true;
2234
2235 // setup cache listener to append to cache entry
2236 int64_t size;
2237 rv = mCacheEntry->GetDataSize(&size);
2238 if (NS_FAILED(rv)) return rv;
2239
2240 rv = InstallCacheListener(size);
2241 if (NS_FAILED(rv)) return rv;
2242
2243 // Entry is valid, do it now, after the output stream has been opened,
2244 // otherwise when done earlier, pending readers would consider the cache
2245 // entry still as partial (CacheEntry::GetDataSize would return the partial
2246 // data size) and consumers would do the conditional request again.
2247 rv = mCacheEntry->SetValid();
2248 if (NS_FAILED(rv)) return rv;
2249
2250 // need to track the logical offset of the data being sent to our listener
2251 mLogicalOffset = size;
2252
2253 // we're now completing the cached content, so we can clear this flag.
2254 // this puts us in the state of a regular download.
2255 mCachedContentIsPartial = false;
2256
2257 // resume the transaction if it exists, otherwise the pipe contained the
2258 // remaining part of the document and we've now streamed all of the data.
2259 if (mTransactionPump) {
2260 rv = mTransactionPump->Resume();
2261 if (NS_SUCCEEDED(rv))
2262 *streamDone = false;
2263 }
2264 else
2265 NS_NOTREACHED("no transaction");
2266 return rv;
2267 }
2268
2269 //-----------------------------------------------------------------------------
2270 // nsHttpChannel <cache>
2271 //-----------------------------------------------------------------------------
2272
2273 nsresult
2274 nsHttpChannel::ProcessNotModified()
2275 {
2276 nsresult rv;
2277
2278 LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
2279
2280 if (mCustomConditionalRequest) {
2281 LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
2282 return NS_ERROR_FAILURE;
2283 }
2284
2285 if (!mDidReval) {
2286 LOG(("Server returned a 304 response even though we did not send a "
2287 "conditional request"));
2288 return NS_ERROR_FAILURE;
2289 }
2290
2291 MOZ_ASSERT(mCachedResponseHead);
2292 MOZ_ASSERT(mCacheEntry);
2293 NS_ENSURE_TRUE(mCachedResponseHead && mCacheEntry, NS_ERROR_UNEXPECTED);
2294
2295 // If the 304 response contains a Last-Modified different than the
2296 // one in our cache that is pretty suspicious and is, in at least the
2297 // case of bug 716840, a sign of the server having previously corrupted
2298 // our cache with a bad response. Take the minor step here of just dooming
2299 // that cache entry so there is a fighting chance of getting things on the
2300 // right track as well as disabling pipelining for that host.
2301
2302 nsAutoCString lastModifiedCached;
2303 nsAutoCString lastModified304;
2304
2305 rv = mCachedResponseHead->GetHeader(nsHttp::Last_Modified,
2306 lastModifiedCached);
2307 if (NS_SUCCEEDED(rv)) {
2308 rv = mResponseHead->GetHeader(nsHttp::Last_Modified,
2309 lastModified304);
2310 }
2311
2312 if (NS_SUCCEEDED(rv) && !lastModified304.Equals(lastModifiedCached)) {
2313 LOG(("Cache Entry and 304 Last-Modified Headers Do Not Match "
2314 "[%s] and [%s]\n",
2315 lastModifiedCached.get(), lastModified304.get()));
2316
2317 mCacheEntry->AsyncDoom(nullptr);
2318 if (mConnectionInfo)
2319 gHttpHandler->ConnMgr()->
2320 PipelineFeedbackInfo(mConnectionInfo,
2321 nsHttpConnectionMgr::RedCorruptedContent,
2322 nullptr, 0);
2323 Telemetry::Accumulate(Telemetry::CACHE_LM_INCONSISTENT, true);
2324 }
2325
2326 // merge any new headers with the cached response headers
2327 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
2328 if (NS_FAILED(rv)) return rv;
2329
2330 // update the cached response head
2331 nsAutoCString head;
2332 mCachedResponseHead->Flatten(head, true);
2333 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
2334 if (NS_FAILED(rv)) return rv;
2335
2336 // make the cached response be the current response
2337 mResponseHead = mCachedResponseHead;
2338
2339 UpdateInhibitPersistentCachingFlag();
2340
2341 rv = UpdateExpirationTime();
2342 if (NS_FAILED(rv)) return rv;
2343
2344 rv = AddCacheEntryHeaders(mCacheEntry);
2345 if (NS_FAILED(rv)) return rv;
2346
2347 // notify observers interested in looking at a reponse that has been
2348 // merged with any cached headers
2349 gHttpHandler->OnExamineMergedResponse(this);
2350
2351 mCachedContentIsValid = true;
2352
2353 // Tell other consumers the entry is OK to use
2354 rv = mCacheEntry->SetValid();
2355 if (NS_FAILED(rv)) return rv;
2356
2357 rv = ReadFromCache(false);
2358 if (NS_FAILED(rv)) return rv;
2359
2360 mTransactionReplaced = true;
2361 return NS_OK;
2362 }
2363
2364 nsresult
2365 nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
2366 {
2367 LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
2368 nsresult rv;
2369
2370 *waitingForRedirectCallback = false;
2371 mFallingBack = false;
2372
2373 // At this point a load has failed (either due to network problems
2374 // or an error returned on the server). Perform an application
2375 // cache fallback if we have a URI to fall back to.
2376 if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
2377 LOG((" choosing not to fallback [%p,%s,%d]",
2378 mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
2379 return NS_OK;
2380 }
2381
2382 // Make sure the fallback entry hasn't been marked as a foreign
2383 // entry.
2384 uint32_t fallbackEntryType;
2385 rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
2386 NS_ENSURE_SUCCESS(rv, rv);
2387
2388 if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
2389 // This cache points to a fallback that refers to a different
2390 // manifest. Refuse to fall back.
2391 return NS_OK;
2392 }
2393
2394 MOZ_ASSERT(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
2395 "Fallback entry not marked correctly!");
2396
2397 // Kill any offline cache entry, and disable offline caching for the
2398 // fallback.
2399 if (mOfflineCacheEntry) {
2400 mOfflineCacheEntry->AsyncDoom(nullptr);
2401 mOfflineCacheEntry = nullptr;
2402 }
2403
2404 mApplicationCacheForWrite = nullptr;
2405 mOfflineCacheEntry = nullptr;
2406
2407 // Close the current cache entry.
2408 CloseCacheEntry(true);
2409
2410 // Create a new channel to load the fallback entry.
2411 nsRefPtr<nsIChannel> newChannel;
2412 rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
2413 NS_ENSURE_SUCCESS(rv, rv);
2414
2415 rv = SetupReplacementChannel(mURI, newChannel, true);
2416 NS_ENSURE_SUCCESS(rv, rv);
2417
2418 // Make sure the new channel loads from the fallback key.
2419 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
2420 do_QueryInterface(newChannel, &rv);
2421 NS_ENSURE_SUCCESS(rv, rv);
2422
2423 rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
2424 NS_ENSURE_SUCCESS(rv, rv);
2425
2426 // ... and fallbacks should only load from the cache.
2427 uint32_t newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
2428 rv = newChannel->SetLoadFlags(newLoadFlags);
2429
2430 // Inform consumers about this fake redirect
2431 mRedirectChannel = newChannel;
2432 uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
2433
2434 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
2435 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
2436
2437 if (NS_SUCCEEDED(rv))
2438 rv = WaitForRedirectCallback();
2439
2440 if (NS_FAILED(rv)) {
2441 AutoRedirectVetoNotifier notifier(this);
2442 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
2443 return rv;
2444 }
2445
2446 // Indicate we are now waiting for the asynchronous redirect callback
2447 // if all went OK.
2448 *waitingForRedirectCallback = true;
2449 return NS_OK;
2450 }
2451
2452 nsresult
2453 nsHttpChannel::ContinueProcessFallback(nsresult rv)
2454 {
2455 AutoRedirectVetoNotifier notifier(this);
2456
2457 if (NS_FAILED(rv))
2458 return rv;
2459
2460 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
2461
2462 // Make sure to do this _after_ calling OnChannelRedirect
2463 mRedirectChannel->SetOriginalURI(mOriginalURI);
2464
2465 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
2466 if (NS_FAILED(rv))
2467 return rv;
2468
2469 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
2470 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
2471 true);
2472 }
2473
2474 // close down this channel
2475 Cancel(NS_BINDING_REDIRECTED);
2476
2477 notifier.RedirectSucceeded();
2478
2479 ReleaseListeners();
2480
2481 mFallingBack = true;
2482
2483 return NS_OK;
2484 }
2485
2486 // Determines if a request is a byte range request for a subrange,
2487 // i.e. is a byte range request, but not a 0- byte range request.
2488 static bool
2489 IsSubRangeRequest(nsHttpRequestHead &aRequestHead)
2490 {
2491 if (!aRequestHead.PeekHeader(nsHttp::Range))
2492 return false;
2493 nsAutoCString byteRange;
2494 aRequestHead.GetHeader(nsHttp::Range, byteRange);
2495 return !byteRange.EqualsLiteral("bytes=0-");
2496 }
2497
2498 nsresult
2499 nsHttpChannel::OpenCacheEntry(bool usingSSL)
2500 {
2501 MOZ_EVENT_TRACER_EXEC(this, "net::http::OpenCacheEntry");
2502
2503 // Handle correctly mCacheEntriesToWaitFor
2504 AutoCacheWaitFlags waitFlags(this);
2505
2506 // Drop this flag here
2507 mConcurentCacheAccess = 0;
2508
2509 nsresult rv;
2510
2511 mLoadedFromApplicationCache = false;
2512 mHasQueryString = HasQueryString(mRequestHead.ParsedMethod(), mURI);
2513
2514 LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
2515
2516 // make sure we're not abusing this function
2517 NS_PRECONDITION(!mCacheEntry, "cache entry already open");
2518
2519 nsAutoCString cacheKey;
2520
2521 if (mRequestHead.IsPost()) {
2522 // If the post id is already set then this is an attempt to replay
2523 // a post transaction via the cache. Otherwise, we need a unique
2524 // post id for this transaction.
2525 if (mPostID == 0)
2526 mPostID = gHttpHandler->GenerateUniqueID();
2527 }
2528 else if (!mRequestHead.IsGet() && !mRequestHead.IsHead()) {
2529 // don't use the cache for other types of requests
2530 return NS_OK;
2531 }
2532
2533 if (mResuming) {
2534 // We don't support caching for requests initiated
2535 // via nsIResumableChannel.
2536 return NS_OK;
2537 }
2538
2539 // Don't cache byte range requests which are subranges, only cache 0-
2540 // byte range requests.
2541 if (IsSubRangeRequest(mRequestHead))
2542 return NS_OK;
2543
2544 // Pick up an application cache from the notification
2545 // callbacks if available
2546 if (!mApplicationCache && mInheritApplicationCache) {
2547 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
2548 GetCallback(appCacheContainer);
2549
2550 if (appCacheContainer) {
2551 appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
2552 }
2553 }
2554
2555 nsCOMPtr<nsICacheStorageService> cacheStorageService =
2556 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
2557 NS_ENSURE_SUCCESS(rv, rv);
2558
2559 nsRefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
2560 nsCOMPtr<nsICacheStorage> cacheStorage;
2561 nsCOMPtr<nsIURI> openURI;
2562
2563 /* Obtain optional third party isolation domain */
2564 nsAutoCString cacheDomain;
2565 nsCOMPtr<nsIURI> firstPartyIsolationURI;
2566 nsCOMPtr<mozIThirdPartyUtil> thirdPartySvc
2567 = do_GetService(THIRDPARTYUTIL_CONTRACTID);
2568 rv = thirdPartySvc->GetFirstPartyIsolationURI(this, nullptr,
2569 getter_AddRefs(firstPartyIsolationURI));
2570 if (NS_SUCCEEDED(rv) && firstPartyIsolationURI) {
2571 thirdPartySvc->GetFirstPartyHostForIsolation(firstPartyIsolationURI,
2572 cacheDomain);
2573 }
2574
2575 if (!mFallbackKey.IsEmpty() && mFallbackChannel) {
2576 // This is a fallback channel, open fallback URI instead
2577 rv = NS_NewURI(getter_AddRefs(openURI), mFallbackKey);
2578 NS_ENSURE_SUCCESS(rv, rv);
2579 }
2580 else {
2581 openURI = mURI;
2582 }
2583
2584 uint32_t cacheEntryOpenFlags;
2585 bool offline = gIOService->IsOffline();
2586 if (offline || (mLoadFlags & INHIBIT_CACHING)) {
2587 if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline) {
2588 goto bypassCacheEntryOpen;
2589 }
2590 cacheEntryOpenFlags = nsICacheStorage::OPEN_READONLY;
2591 mCacheEntryIsReadOnly = true;
2592 }
2593 else if (BYPASS_LOCAL_CACHE(mLoadFlags) && !mApplicationCache) {
2594 cacheEntryOpenFlags = nsICacheStorage::OPEN_TRUNCATE;
2595 }
2596 else {
2597 cacheEntryOpenFlags = nsICacheStorage::OPEN_NORMALLY
2598 | nsICacheStorage::CHECK_MULTITHREADED;
2599 }
2600
2601 if (mApplicationCache) {
2602 rv = cacheStorageService->AppCacheStorage(info,
2603 mApplicationCache,
2604 getter_AddRefs(cacheStorage));
2605 }
2606 else if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
2607 rv = cacheStorageService->MemoryCacheStorage(info, // ? choose app cache as well...
2608 getter_AddRefs(cacheStorage));
2609 }
2610 else {
2611 rv = cacheStorageService->DiskCacheStorage(info,
2612 mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE),
2613 getter_AddRefs(cacheStorage));
2614 }
2615 NS_ENSURE_SUCCESS(rv, rv);
2616
2617 // Don't consider mLoadUnblocked here, since it's not indication of a demand
2618 // to load prioritly. It's mostly used to load XHR requests, but those should
2619 // not be considered as influencing the page load performance.
2620 if (mLoadAsBlocking || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
2621 cacheEntryOpenFlags |= nsICacheStorage::OPEN_PRIORITY;
2622
2623 // Only for backward compatibility with the old cache back end.
2624 // When removed, remove the flags and related code snippets.
2625 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY)
2626 cacheEntryOpenFlags |= nsICacheStorage::OPEN_BYPASS_IF_BUSY;
2627
2628 rv = cacheStorage->AsyncOpenURI(
2629 openURI, nsPrintfCString("%s@%d", cacheDomain.get(), mPostID),
2630 cacheEntryOpenFlags, this);
2631 NS_ENSURE_SUCCESS(rv, rv);
2632
2633 waitFlags.Keep(WAIT_FOR_CACHE_ENTRY);
2634
2635 bypassCacheEntryOpen:
2636 if (!mApplicationCacheForWrite)
2637 return NS_OK;
2638
2639 // If there is an app cache to write to, open the entry right now in parallel.
2640
2641 // make sure we're not abusing this function
2642 NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
2643
2644 if (offline) {
2645 // only put things in the offline cache while online
2646 return NS_OK;
2647 }
2648
2649 if (mLoadFlags & INHIBIT_CACHING) {
2650 // respect demand not to cache
2651 return NS_OK;
2652 }
2653
2654 if (!mRequestHead.IsGet()) {
2655 // only cache complete documents offline
2656 return NS_OK;
2657 }
2658
2659 rv = cacheStorageService->AppCacheStorage(info, mApplicationCacheForWrite,
2660 getter_AddRefs(cacheStorage));
2661 NS_ENSURE_SUCCESS(rv, rv);
2662
2663 rv = cacheStorage->AsyncOpenURI(
2664 mURI, cacheDomain, nsICacheStorage::OPEN_TRUNCATE, this);
2665 NS_ENSURE_SUCCESS(rv, rv);
2666
2667 waitFlags.Keep(WAIT_FOR_OFFLINE_CACHE_ENTRY);
2668
2669 return NS_OK;
2670 }
2671
2672 nsresult
2673 nsHttpChannel::CheckPartial(nsICacheEntry* aEntry, int64_t *aSize, int64_t *aContentLength)
2674 {
2675 nsresult rv;
2676
2677 rv = aEntry->GetDataSize(aSize);
2678
2679 if (NS_ERROR_IN_PROGRESS == rv) {
2680 *aSize = -1;
2681 rv = NS_OK;
2682 }
2683
2684 NS_ENSURE_SUCCESS(rv, rv);
2685
2686 nsHttpResponseHead* responseHead = mCachedResponseHead
2687 ? mCachedResponseHead
2688 : mResponseHead;
2689
2690 if (!responseHead)
2691 return NS_ERROR_UNEXPECTED;
2692
2693 *aContentLength = responseHead->ContentLength();
2694
2695 return NS_OK;
2696 }
2697
2698 NS_IMETHODIMP
2699 nsHttpChannel::OnCacheEntryCheck(nsICacheEntry* entry, nsIApplicationCache* appCache,
2700 uint32_t* aResult)
2701 {
2702 nsresult rv = NS_OK;
2703
2704 LOG(("nsHttpChannel::OnCacheEntryCheck enter [channel=%p entry=%p]",
2705 this, entry));
2706
2707 // Remember the request is a custom conditional request so that we can
2708 // process any 304 response correctly.
2709 mCustomConditionalRequest =
2710 mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
2711 mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
2712 mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
2713 mRequestHead.PeekHeader(nsHttp::If_Match) ||
2714 mRequestHead.PeekHeader(nsHttp::If_Range);
2715
2716 // Be pessimistic: assume the cache entry has no useful data.
2717 *aResult = ENTRY_WANTED;
2718 mCachedContentIsValid = false;
2719
2720 nsXPIDLCString buf;
2721
2722 // Get the method that was used to generate the cached response
2723 rv = entry->GetMetaDataElement("request-method", getter_Copies(buf));
2724 NS_ENSURE_SUCCESS(rv, rv);
2725
2726 bool methodWasHead = buf.EqualsLiteral("HEAD");
2727 bool methodWasGet = buf.EqualsLiteral("GET");
2728
2729 if (methodWasHead) {
2730 // The cached response does not contain an entity. We can only reuse
2731 // the response if the current request is also HEAD.
2732 if (!mRequestHead.IsHead()) {
2733 return NS_OK;
2734 }
2735 }
2736 buf.Adopt(0);
2737
2738 // We'll need this value in later computations...
2739 uint32_t lastModifiedTime;
2740 rv = entry->GetLastModified(&lastModifiedTime);
2741 NS_ENSURE_SUCCESS(rv, rv);
2742
2743 // Determine if this is the first time that this cache entry
2744 // has been accessed during this session.
2745 bool fromPreviousSession =
2746 (gHttpHandler->SessionStartTime() > lastModifiedTime);
2747
2748 // Get the cached HTTP response headers
2749 rv = entry->GetMetaDataElement("response-head", getter_Copies(buf));
2750 NS_ENSURE_SUCCESS(rv, rv);
2751
2752 // Parse the cached HTTP response headers
2753 mCachedResponseHead = new nsHttpResponseHead();
2754 rv = mCachedResponseHead->Parse((char *) buf.get());
2755 NS_ENSURE_SUCCESS(rv, rv);
2756 buf.Adopt(0);
2757
2758 bool isCachedRedirect = WillRedirect(mCachedResponseHead);
2759
2760 // Do not return 304 responses from the cache, and also do not return
2761 // any other non-redirect 3xx responses from the cache (see bug 759043).
2762 NS_ENSURE_TRUE((mCachedResponseHead->Status() / 100 != 3) ||
2763 isCachedRedirect, NS_ERROR_ABORT);
2764
2765 // Don't bother to validate items that are read-only,
2766 // unless they are read-only because of INHIBIT_CACHING or because
2767 // we're updating the offline cache.
2768 // Don't bother to validate if this is a fallback entry.
2769 if (!mApplicationCacheForWrite &&
2770 (appCache ||
2771 (mCacheEntryIsReadOnly && !(mLoadFlags & nsIRequest::INHIBIT_CACHING)) ||
2772 mFallbackChannel)) {
2773 rv = OpenCacheInputStream(entry, true);
2774 if (NS_SUCCEEDED(rv)) {
2775 mCachedContentIsValid = true;
2776 entry->MaybeMarkValid();
2777 }
2778 return rv;
2779 }
2780
2781 bool wantCompleteEntry = false;
2782
2783 if (!methodWasHead && !isCachedRedirect) {
2784 // If the cached content-length is set and it does not match the data
2785 // size of the cached content, then the cached response is partial...
2786 // either we need to issue a byte range request or we need to refetch
2787 // the entire document.
2788 //
2789 // We exclude redirects from this check because we (usually) strip the
2790 // entity when we store the cache entry, and even if we didn't, we
2791 // always ignore a cached redirect's entity anyway. See bug 759043.
2792 int64_t size, contentLength;
2793 rv = CheckPartial(entry, &size, &contentLength);
2794 NS_ENSURE_SUCCESS(rv,rv);
2795
2796 if (size == int64_t(-1)) {
2797 LOG((" write is in progress"));
2798 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
2799 LOG((" not interested in the entry, "
2800 "LOAD_BYPASS_LOCAL_CACHE_IF_BUSY specified"));
2801
2802 *aResult = ENTRY_NOT_WANTED;
2803 return NS_OK;
2804 }
2805
2806 // Ignore !(size > 0) from the resumability condition
2807 if (!IsResumable(size, contentLength, true)) {
2808 LOG((" wait for entry completion, "
2809 "response is not resumable"));
2810
2811 wantCompleteEntry = true;
2812 }
2813 else {
2814 mConcurentCacheAccess = 1;
2815 }
2816 }
2817 else if (contentLength != int64_t(-1) && contentLength != size) {
2818 LOG(("Cached data size does not match the Content-Length header "
2819 "[content-length=%lld size=%lld]\n", contentLength, size));
2820
2821 rv = MaybeSetupByteRangeRequest(size, contentLength);
2822 mCachedContentIsPartial = NS_SUCCEEDED(rv) && mIsPartialRequest;
2823 if (mCachedContentIsPartial) {
2824 rv = OpenCacheInputStream(entry, false);
2825 *aResult = ENTRY_NEEDS_REVALIDATION;
2826 }
2827 return rv;
2828 }
2829 }
2830
2831 bool usingSSL = false;
2832 rv = mURI->SchemeIs("https", &usingSSL);
2833 NS_ENSURE_SUCCESS(rv,rv);
2834
2835 bool doValidation = false;
2836 bool canAddImsHeader = true;
2837
2838 // Cached entry is not the entity we request (see bug #633743)
2839 if (ResponseWouldVary(entry)) {
2840 LOG(("Validating based on Vary headers returning TRUE\n"));
2841 canAddImsHeader = false;
2842 doValidation = true;
2843 }
2844 // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
2845 else if (mLoadFlags & nsIRequest::LOAD_FROM_CACHE) {
2846 LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
2847 doValidation = false;
2848 }
2849 // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
2850 // it's revalidated with the server.
2851 else if (mLoadFlags & nsIRequest::VALIDATE_ALWAYS) {
2852 LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
2853 doValidation = true;
2854 }
2855 // Even if the VALIDATE_NEVER flag is set, there are still some cases in
2856 // which we must validate the cached response with the server.
2857 else if (mLoadFlags & nsIRequest::VALIDATE_NEVER) {
2858 LOG(("VALIDATE_NEVER set\n"));
2859 // if no-store or if no-cache and ssl, validate cached response (see
2860 // bug 112564 for an explanation of this logic)
2861 if (mCachedResponseHead->NoStore() ||
2862 (mCachedResponseHead->NoCache() && usingSSL)) {
2863 LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
2864 doValidation = true;
2865 }
2866 else {
2867 LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
2868 doValidation = false;
2869 }
2870 }
2871 // check if validation is strictly required...
2872 else if (mCachedResponseHead->MustValidate()) {
2873 LOG(("Validating based on MustValidate() returning TRUE\n"));
2874 doValidation = true;
2875 }
2876 else if (MustValidateBasedOnQueryUrl()) {
2877 LOG(("Validating based on RFC 2616 section 13.9 "
2878 "(query-url w/o explicit expiration-time)\n"));
2879 doValidation = true;
2880 }
2881 // Check if the cache entry has expired...
2882 else {
2883 uint32_t time = 0; // a temporary variable for storing time values...
2884
2885 rv = entry->GetExpirationTime(&time);
2886 NS_ENSURE_SUCCESS(rv, rv);
2887
2888 LOG((" NowInSeconds()=%u, time=%u", NowInSeconds(), time));
2889 if (NowInSeconds() <= time)
2890 doValidation = false;
2891 else if (mCachedResponseHead->MustValidateIfExpired())
2892 doValidation = true;
2893 else if (mLoadFlags & nsIRequest::VALIDATE_ONCE_PER_SESSION) {
2894 // If the cached response does not include expiration infor-
2895 // mation, then we must validate the response, despite whether
2896 // or not this is the first access this session. This behavior
2897 // is consistent with existing browsers and is generally expected
2898 // by web authors.
2899 rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
2900 NS_ENSURE_SUCCESS(rv, rv);
2901
2902 if (time == 0)
2903 doValidation = true;
2904 else
2905 doValidation = fromPreviousSession;
2906 }
2907 else
2908 doValidation = true;
2909
2910 LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
2911 }
2912
2913 if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) &&
2914 (methodWasGet || methodWasHead)) {
2915 const char *requestedETag, *cachedETag;
2916 cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag);
2917 requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match);
2918 if (cachedETag && (!strncmp(cachedETag, "W/", 2) ||
2919 strcmp(requestedETag, cachedETag))) {
2920 // User has defined If-Match header, if the cached entry is not
2921 // matching the provided header value or the cached ETag is weak,
2922 // force validation.
2923 doValidation = true;
2924 }
2925 }
2926
2927 if (!doValidation) {
2928 //
2929 // Check the authorization headers used to generate the cache entry.
2930 // We must validate the cache entry if:
2931 //
2932 // 1) the cache entry was generated prior to this session w/
2933 // credentials (see bug 103402).
2934 // 2) the cache entry was generated w/o credentials, but would now
2935 // require credentials (see bug 96705).
2936 //
2937 // NOTE: this does not apply to proxy authentication.
2938 //
2939 entry->GetMetaDataElement("auth", getter_Copies(buf));
2940 doValidation =
2941 (fromPreviousSession && !buf.IsEmpty()) ||
2942 (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
2943 }
2944
2945 // Bug #561276: We maintain a chain of cache-keys which returns cached
2946 // 3xx-responses (redirects) in order to detect cycles. If a cycle is
2947 // found, ignore the cached response and hit the net. Otherwise, use
2948 // the cached response and add the cache-key to the chain. Note that
2949 // a limited number of redirects (cached or not) is allowed and is
2950 // enforced independently of this mechanism
2951 if (!doValidation && isCachedRedirect) {
2952 nsAutoCString cacheKey;
2953 GenerateCacheKey(mPostID, cacheKey);
2954
2955 if (!mRedirectedCachekeys)
2956 mRedirectedCachekeys = new nsTArray<nsCString>();
2957 else if (mRedirectedCachekeys->Contains(cacheKey))
2958 doValidation = true;
2959
2960 LOG(("Redirection-chain %s key %s\n",
2961 doValidation ? "contains" : "does not contain", cacheKey.get()));
2962
2963 // Append cacheKey if not in the chain already
2964 if (!doValidation)
2965 mRedirectedCachekeys->AppendElement(cacheKey);
2966 }
2967
2968 mCachedContentIsValid = !doValidation;
2969
2970 if (doValidation) {
2971 //
2972 // now, we are definitely going to issue a HTTP request to the server.
2973 // make it conditional if possible.
2974 //
2975 // do not attempt to validate no-store content, since servers will not
2976 // expect it to be cached. (we only keep it in our cache for the
2977 // purposes of back/forward, etc.)
2978 //
2979 // the request method MUST be either GET or HEAD (see bug 175641).
2980 //
2981 // do not override conditional headers when consumer has defined its own
2982 if (!mCachedResponseHead->NoStore() &&
2983 (mRequestHead.IsGet() || mRequestHead.IsHead()) &&
2984 !mCustomConditionalRequest) {
2985
2986 if (mConcurentCacheAccess) {
2987 // In case of concurrent read and also validation request we
2988 // must wait for the current writer to close the output stream
2989 // first. Otherwise, when the writer's job would have been interrupted
2990 // before all the data were downloaded, we'd have to do a range request
2991 // which would be a second request in line during this channel's
2992 // life-time. nsHttpChannel is not designed to do that, so rather
2993 // turn off concurrent read and wait for entry's completion.
2994 // Then only re-validation or range-re-validation request will go out.
2995 mConcurentCacheAccess = 0;
2996 // This will cause that OnCacheEntryCheck is called again with the same
2997 // entry after the writer is done.
2998 wantCompleteEntry = true;
2999 } else {
3000 const char *val;
3001 // Add If-Modified-Since header if a Last-Modified was given
3002 // and we are allowed to do this (see bugs 510359 and 269303)
3003 if (canAddImsHeader) {
3004 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
3005 if (val)
3006 mRequestHead.SetHeader(nsHttp::If_Modified_Since,
3007 nsDependentCString(val));
3008 }
3009 // Add If-None-Match header if an ETag was given in the response
3010 val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
3011 if (val)
3012 mRequestHead.SetHeader(nsHttp::If_None_Match,
3013 nsDependentCString(val));
3014 mDidReval = true;
3015 }
3016 }
3017 }
3018
3019 if (mCachedContentIsValid || mDidReval) {
3020 rv = OpenCacheInputStream(entry, mCachedContentIsValid);
3021 if (NS_FAILED(rv)) {
3022 // If we can't get the entity then we have to act as though we
3023 // don't have the cache entry.
3024 if (mDidReval) {
3025 // Make the request unconditional again.
3026 mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
3027 mRequestHead.ClearHeader(nsHttp::If_None_Match);
3028 mRequestHead.ClearHeader(nsHttp::ETag);
3029 mDidReval = false;
3030 }
3031 mCachedContentIsValid = false;
3032 }
3033 }
3034
3035 if (mDidReval)
3036 *aResult = ENTRY_NEEDS_REVALIDATION;
3037 else if (wantCompleteEntry)
3038 *aResult = RECHECK_AFTER_WRITE_FINISHED;
3039 else
3040 *aResult = ENTRY_WANTED;
3041
3042 if (mCachedContentIsValid) {
3043 entry->MaybeMarkValid();
3044 }
3045
3046 LOG(("nsHTTPChannel::OnCacheEntryCheck exit [this=%p doValidation=%d result=%d]\n",
3047 this, doValidation, *aResult));
3048 return rv;
3049 }
3050
3051 NS_IMETHODIMP
3052 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntry *entry,
3053 bool aNew,
3054 nsIApplicationCache* aAppCache,
3055 nsresult status)
3056 {
3057 MOZ_ASSERT(NS_IsMainThread());
3058
3059 nsresult rv;
3060
3061 LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
3062 "new=%d appcache=%p status=%x]\n", this, entry, aNew, aAppCache, status));
3063
3064 // if the channel's already fired onStopRequest, then we should ignore
3065 // this event.
3066 if (!mIsPending) {
3067 mCacheInputStream.CloseAndRelease();
3068 return NS_OK;
3069 }
3070
3071 rv = OnCacheEntryAvailableInternal(entry, aNew, aAppCache, status);
3072 if (NS_FAILED(rv)) {
3073 CloseCacheEntry(true);
3074 AsyncAbort(rv);
3075 }
3076
3077 return NS_OK;
3078 }
3079
3080 nsresult
3081 nsHttpChannel::OnCacheEntryAvailableInternal(nsICacheEntry *entry,
3082 bool aNew,
3083 nsIApplicationCache* aAppCache,
3084 nsresult status)
3085 {
3086 nsresult rv;
3087
3088 if (mCanceled) {
3089 LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
3090 return mStatus;
3091 }
3092
3093 if (aAppCache) {
3094 if (mApplicationCache == aAppCache && !mCacheEntry) {
3095 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
3096 }
3097 else if (mApplicationCacheForWrite == aAppCache && aNew && !mOfflineCacheEntry) {
3098 rv = OnOfflineCacheEntryForWritingAvailable(entry, aAppCache, status);
3099 }
3100 else {
3101 rv = OnOfflineCacheEntryAvailable(entry, aNew, aAppCache, status);
3102 }
3103 }
3104 else {
3105 rv = OnNormalCacheEntryAvailable(entry, aNew, status);
3106 }
3107
3108 if (NS_FAILED(rv) && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
3109 // If we have a fallback URI (and we're not already
3110 // falling back), process the fallback asynchronously.
3111 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
3112 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
3113 }
3114 return NS_ERROR_DOCUMENT_NOT_CACHED;
3115 }
3116
3117 if (NS_FAILED(rv))
3118 return rv;
3119
3120 // We may be waiting for more callbacks...
3121 if (mCacheEntriesToWaitFor)
3122 return NS_OK;
3123
3124 return ContinueConnect();
3125 }
3126
3127 nsresult
3128 nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntry *aEntry,
3129 bool aNew,
3130 nsresult aEntryStatus)
3131 {
3132 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
3133
3134 if (NS_FAILED(aEntryStatus) || aNew) {
3135 // Make sure this flag is dropped. It may happen the entry is doomed
3136 // between OnCacheEntryCheck and OnCacheEntryAvailable.
3137 mCachedContentIsValid = false;
3138
3139 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
3140 // if this channel is only allowed to pull from the cache, then
3141 // we must fail if we were unable to open a cache entry for read.
3142 return NS_ERROR_DOCUMENT_NOT_CACHED;
3143 }
3144 }
3145
3146 if (NS_SUCCEEDED(aEntryStatus)) {
3147 mCacheEntry = aEntry;
3148 mCacheEntryIsWriteOnly = aNew;
3149
3150 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
3151 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
3152 false);
3153 }
3154 }
3155
3156 return NS_OK;
3157 }
3158
3159 nsresult
3160 nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntry *aEntry,
3161 bool aNew,
3162 nsIApplicationCache* aAppCache,
3163 nsresult aEntryStatus)
3164 {
3165 MOZ_ASSERT(!mApplicationCache || aAppCache == mApplicationCache);
3166 MOZ_ASSERT(!aNew || !aEntry || mApplicationCacheForWrite);
3167
3168 mCacheEntriesToWaitFor &= ~WAIT_FOR_CACHE_ENTRY;
3169
3170 nsresult rv;
3171
3172 if (!mApplicationCache)
3173 mApplicationCache = aAppCache;
3174
3175 if (NS_SUCCEEDED(aEntryStatus)) {
3176 // We successfully opened an offline cache session and the entry,
3177 // so indicate we will load from the offline cache.
3178 mLoadedFromApplicationCache = true;
3179 mCacheEntryIsReadOnly = true;
3180 mCacheEntry = aEntry;
3181 mCacheEntryIsWriteOnly = false;
3182
3183 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI && !mApplicationCacheForWrite) {
3184 Telemetry::Accumulate(Telemetry::HTTP_OFFLINE_CACHE_DOCUMENT_LOAD,
3185 true);
3186 }
3187
3188 return NS_OK;
3189 }
3190
3191 if (!mApplicationCacheForWrite && !mFallbackChannel) {
3192 // Check for namespace match.
3193 nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
3194 rv = mApplicationCache->GetMatchingNamespace(mSpec,
3195 getter_AddRefs(namespaceEntry));
3196 NS_ENSURE_SUCCESS(rv, rv);
3197
3198 uint32_t namespaceType = 0;
3199 if (!namespaceEntry ||
3200 NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
3201 (namespaceType &
3202 (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
3203 nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
3204 // When loading from an application cache, only items
3205 // on the whitelist or matching a
3206 // fallback namespace should hit the network...
3207 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
3208
3209 // ... and if there were an application cache entry,
3210 // we would have found it earlier.
3211 return NS_ERROR_CACHE_KEY_NOT_FOUND;
3212 }
3213
3214 if (namespaceType &
3215 nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
3216 rv = namespaceEntry->GetData(mFallbackKey);
3217 NS_ENSURE_SUCCESS(rv, rv);
3218 }
3219 }
3220
3221 return NS_OK;
3222 }
3223
3224 nsresult
3225 nsHttpChannel::OnOfflineCacheEntryForWritingAvailable(nsICacheEntry *aEntry,
3226 nsIApplicationCache* aAppCache,
3227 nsresult aEntryStatus)
3228 {
3229 MOZ_ASSERT(mApplicationCacheForWrite && aAppCache == mApplicationCacheForWrite);
3230
3231 mCacheEntriesToWaitFor &= ~WAIT_FOR_OFFLINE_CACHE_ENTRY;
3232
3233 if (NS_SUCCEEDED(aEntryStatus)) {
3234 mOfflineCacheEntry = aEntry;
3235 if (NS_FAILED(aEntry->GetLastModified(&mOfflineCacheLastModifiedTime))) {
3236 mOfflineCacheLastModifiedTime = 0;
3237 }
3238 }
3239
3240 return aEntryStatus;
3241 }
3242
3243 // Generates the proper cache-key for this instance of nsHttpChannel
3244 nsresult
3245 nsHttpChannel::GenerateCacheKey(uint32_t postID, nsACString &cacheKey)
3246 {
3247 AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(),
3248 postID, cacheKey);
3249 return NS_OK;
3250 }
3251
3252 // Assembles a cache-key from the given pieces of information and |mLoadFlags|
3253 void
3254 nsHttpChannel::AssembleCacheKey(const char *spec, uint32_t postID,
3255 nsACString &cacheKey)
3256 {
3257 cacheKey.Truncate();
3258
3259 if (mLoadFlags & LOAD_ANONYMOUS) {
3260 cacheKey.AssignLiteral("anon&");
3261 }
3262
3263 if (postID) {
3264 char buf[32];
3265 PR_snprintf(buf, sizeof(buf), "id=%x&", postID);
3266 cacheKey.Append(buf);
3267 }
3268
3269 if (strlen(mCacheDomain.get()) > 0) {
3270 cacheKey.AppendLiteral("domain=");
3271 cacheKey.Append(mCacheDomain.get());
3272 cacheKey.AppendLiteral("&");
3273 }
3274
3275 if (!cacheKey.IsEmpty()) {
3276 cacheKey.AppendLiteral("uri=");
3277 }
3278
3279 // Strip any trailing #ref from the URL before using it as the key
3280 const char *p = strchr(spec, '#');
3281 if (p)
3282 cacheKey.Append(spec, p - spec);
3283 else
3284 cacheKey.Append(spec);
3285 }
3286
3287 // UpdateExpirationTime is called when a new response comes in from the server.
3288 // It updates the stored response-time and sets the expiration time on the
3289 // cache entry.
3290 //
3291 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
3292 //
3293 // timeRemaining = freshnessLifetime - currentAge
3294 // expirationTime = now + timeRemaining
3295 //
3296 nsresult
3297 nsHttpChannel::UpdateExpirationTime()
3298 {
3299 NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
3300
3301 nsresult rv;
3302
3303 uint32_t expirationTime = 0;
3304 if (!mResponseHead->MustValidate()) {
3305 uint32_t freshnessLifetime = 0;
3306
3307 rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
3308 if (NS_FAILED(rv)) return rv;
3309
3310 if (freshnessLifetime > 0) {
3311 uint32_t now = NowInSeconds(), currentAge = 0;
3312
3313 rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
3314 if (NS_FAILED(rv)) return rv;
3315
3316 LOG(("freshnessLifetime = %u, currentAge = %u\n",
3317 freshnessLifetime, currentAge));
3318
3319 if (freshnessLifetime > currentAge) {
3320 uint32_t timeRemaining = freshnessLifetime - currentAge;
3321 // be careful... now + timeRemaining may overflow
3322 if (now + timeRemaining < now)
3323 expirationTime = uint32_t(-1);
3324 else
3325 expirationTime = now + timeRemaining;
3326 }
3327 else
3328 expirationTime = now;
3329 }
3330 }
3331
3332 rv = mCacheEntry->SetExpirationTime(expirationTime);
3333 NS_ENSURE_SUCCESS(rv, rv);
3334
3335 if (mOfflineCacheEntry) {
3336 rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
3337 NS_ENSURE_SUCCESS(rv, rv);
3338 }
3339
3340 return NS_OK;
3341 }
3342
3343 /*static*/ inline bool
3344 nsHttpChannel::HasQueryString(nsHttpRequestHead::ParsedMethodType method, nsIURI * uri)
3345 {
3346 // Must be called on the main thread because nsIURI does not implement
3347 // thread-safe QueryInterface.
3348 MOZ_ASSERT(NS_IsMainThread());
3349
3350 if (method != nsHttpRequestHead::kMethod_Get &&
3351 method != nsHttpRequestHead::kMethod_Head)
3352 return false;
3353
3354 nsAutoCString query;
3355 nsCOMPtr<nsIURL> url = do_QueryInterface(uri);
3356 nsresult rv = url->GetQuery(query);
3357 return NS_SUCCEEDED(rv) && !query.IsEmpty();
3358 }
3359
3360 bool
3361 nsHttpChannel::MustValidateBasedOnQueryUrl() const
3362 {
3363 // RFC 2616, section 13.9 states that GET-requests with a query-url
3364 // MUST NOT be treated as fresh unless the server explicitly provides
3365 // an expiration-time in the response. See bug #468594
3366 // Section 13.2.1 (6th paragraph) defines "explicit expiration time"
3367 if (mHasQueryString)
3368 {
3369 uint32_t tmp; // we don't need the value, just whether it's set
3370 nsresult rv = mCachedResponseHead->GetExpiresValue(&tmp);
3371 if (NS_FAILED(rv)) {
3372 rv = mCachedResponseHead->GetMaxAgeValue(&tmp);
3373 if (NS_FAILED(rv)) {
3374 return true;
3375 }
3376 }
3377 }
3378 return false;
3379 }
3380
3381
3382 bool
3383 nsHttpChannel::ShouldUpdateOfflineCacheEntry()
3384 {
3385 if (!mApplicationCacheForWrite || !mOfflineCacheEntry) {
3386 return false;
3387 }
3388
3389 // if we're updating the cache entry, update the offline cache entry too
3390 if (mCacheEntry && mCacheEntryIsWriteOnly) {
3391 return true;
3392 }
3393
3394 // if there's nothing in the offline cache, add it
3395 if (mOfflineCacheEntry) {
3396 return true;
3397 }
3398
3399 // if the document is newer than the offline entry, update it
3400 uint32_t docLastModifiedTime;
3401 nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
3402 if (NS_FAILED(rv)) {
3403 return true;
3404 }
3405
3406 if (mOfflineCacheLastModifiedTime == 0) {
3407 return false;
3408 }
3409
3410 if (docLastModifiedTime > mOfflineCacheLastModifiedTime) {
3411 return true;
3412 }
3413
3414 return false;
3415 }
3416
3417 nsresult
3418 nsHttpChannel::OpenCacheInputStream(nsICacheEntry* cacheEntry, bool startBuffering)
3419 {
3420 nsresult rv;
3421
3422 bool usingSSL = false;
3423 rv = mURI->SchemeIs("https", &usingSSL);
3424 NS_ENSURE_SUCCESS(rv,rv);
3425
3426 if (usingSSL) {
3427 rv = cacheEntry->GetSecurityInfo(
3428 getter_AddRefs(mCachedSecurityInfo));
3429 if (NS_FAILED(rv)) {
3430 LOG(("failed to parse security-info [channel=%p, entry=%p]",
3431 this, cacheEntry));
3432 NS_WARNING("failed to parse security-info");
3433 return rv;
3434 }
3435
3436 // XXX: We should not be skilling this check in the offline cache
3437 // case, but we have to do so now to work around bug 794507.
3438 MOZ_ASSERT(mCachedSecurityInfo || mLoadedFromApplicationCache);
3439 if (!mCachedSecurityInfo && !mLoadedFromApplicationCache) {
3440 LOG(("mCacheEntry->GetSecurityInfo returned success but did not "
3441 "return the security info [channel=%p, entry=%p]",
3442 this, cacheEntry));
3443 return NS_ERROR_UNEXPECTED; // XXX error code
3444 }
3445 }
3446
3447 // Keep the conditions below in sync with the conditions in ReadFromCache.
3448
3449 rv = NS_OK;
3450
3451 if (WillRedirect(mCachedResponseHead)) {
3452 // Do not even try to read the entity for a redirect because we do not
3453 // return an entity to the application when we process redirects.
3454 LOG(("Will skip read of cached redirect entity\n"));
3455 return NS_OK;
3456 }
3457
3458 if ((mLoadFlags & nsICachingChannel::LOAD_ONLY_IF_MODIFIED) &&
3459 !mCachedContentIsPartial) {
3460 // For LOAD_ONLY_IF_MODIFIED, we usually don't have to deal with the
3461 // cached entity.
3462 if (!mApplicationCacheForWrite) {
3463 LOG(("Will skip read from cache based on LOAD_ONLY_IF_MODIFIED "
3464 "load flag\n"));
3465 return NS_OK;
3466 }
3467
3468 // If offline caching has been requested and the offline cache needs
3469 // updating, we must complete the call even if the main cache entry
3470 // is up to date. We don't know yet for sure whether the offline
3471 // cache needs updating because at this point we haven't opened it
3472 // for writing yet, so we have to start reading the cached entity now
3473 // just in case.
3474 LOG(("May skip read from cache based on LOAD_ONLY_IF_MODIFIED "
3475 "load flag\n"));
3476 }
3477
3478 // Open an input stream for the entity, so that the call to OpenInputStream
3479 // happens off the main thread.
3480 nsCOMPtr<nsIInputStream> stream;
3481 rv = cacheEntry->OpenInputStream(0, getter_AddRefs(stream));
3482
3483 if (NS_FAILED(rv)) {
3484 LOG(("Failed to open cache input stream [channel=%p, "
3485 "mCacheEntry=%p]", this, cacheEntry));
3486 return rv;
3487 }
3488
3489 if (startBuffering) {
3490 bool nonBlocking;
3491 rv = stream->IsNonBlocking(&nonBlocking);
3492 if (NS_SUCCEEDED(rv) && nonBlocking)
3493 startBuffering = false;
3494 }
3495
3496 if (!startBuffering) {
3497 // Bypass wrapping the input stream for the new cache back-end since
3498 // nsIStreamTransportService expects a blocking stream. Preloading of
3499 // the data must be done on the level of the cache backend, internally.
3500 //
3501 // We do not connect the stream to the stream transport service if we
3502 // have to validate the entry with the server. If we did, we would get
3503 // into a race condition between the stream transport service reading
3504 // the existing contents and the opening of the cache entry's output
3505 // stream to write the new contents in the case where we get a non-304
3506 // response.
3507 LOG(("Opened cache input stream without buffering [channel=%p, "
3508 "mCacheEntry=%p, stream=%p]", this,
3509 cacheEntry, stream.get()));
3510 mCacheInputStream.takeOver(stream);
3511 return rv;
3512 }
3513
3514 // Have the stream transport service start reading the entity on one of its
3515 // background threads.
3516
3517 nsCOMPtr<nsITransport> transport;
3518 nsCOMPtr<nsIInputStream> wrapper;
3519
3520 nsCOMPtr<nsIStreamTransportService> sts =
3521 do_GetService(kStreamTransportServiceCID, &rv);
3522 if (NS_SUCCEEDED(rv)) {
3523 rv = sts->CreateInputTransport(stream, int64_t(-1), int64_t(-1),
3524 true, getter_AddRefs(transport));
3525 }
3526 if (NS_SUCCEEDED(rv)) {
3527 rv = transport->OpenInputStream(0, 0, 0, getter_AddRefs(wrapper));
3528 }
3529 if (NS_SUCCEEDED(rv)) {
3530 LOG(("Opened cache input stream [channel=%p, wrapper=%p, "
3531 "transport=%p, stream=%p]", this, wrapper.get(),
3532 transport.get(), stream.get()));
3533 } else {
3534 LOG(("Failed to open cache input stream [channel=%p, "
3535 "wrapper=%p, transport=%p, stream=%p]", this,
3536 wrapper.get(), transport.get(), stream.get()));
3537
3538 stream->Close();
3539 return rv;
3540 }
3541
3542 mCacheInputStream.takeOver(wrapper);
3543
3544 return NS_OK;
3545 }
3546
3547 // Actually process the cached response that we started to handle in CheckCache
3548 // and/or StartBufferingCachedEntity.
3549 nsresult
3550 nsHttpChannel::ReadFromCache(bool alreadyMarkedValid)
3551 {
3552 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
3553 NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
3554
3555 LOG(("nsHttpChannel::ReadFromCache [this=%p] "
3556 "Using cached copy of: %s\n", this, mSpec.get()));
3557
3558 if (mCachedResponseHead)
3559 mResponseHead = mCachedResponseHead;
3560
3561 UpdateInhibitPersistentCachingFlag();
3562
3563 // if we don't already have security info, try to get it from the cache
3564 // entry. there are two cases to consider here: 1) we are just reading
3565 // from the cache, or 2) this may be due to a 304 not modified response,
3566 // in which case we could have security info from a socket transport.
3567 if (!mSecurityInfo)
3568 mSecurityInfo = mCachedSecurityInfo;
3569
3570 if (!alreadyMarkedValid && !mCachedContentIsPartial) {
3571 // We validated the entry, and we have write access to the cache, so
3572 // mark the cache entry as valid in order to allow others access to
3573 // this cache entry.
3574 //
3575 // TODO: This should be done asynchronously so we don't take the cache
3576 // service lock on the main thread.
3577 mCacheEntry->MaybeMarkValid();
3578 }
3579
3580 nsresult rv;
3581
3582 // Keep the conditions below in sync with the conditions in
3583 // StartBufferingCachedEntity.
3584
3585 if (WillRedirect(mResponseHead)) {
3586 // TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
3587 // to avoid event dispatching latency.
3588 MOZ_ASSERT(!mCacheInputStream);
3589 LOG(("Skipping skip read of cached redirect entity\n"));
3590 return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
3591 }
3592
3593 if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
3594 if (!mApplicationCacheForWrite) {
3595 LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
3596 "load flag\n"));
3597 MOZ_ASSERT(!mCacheInputStream);
3598 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
3599 // here, to avoid event dispatching latency.
3600 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
3601 }
3602
3603 if (!ShouldUpdateOfflineCacheEntry()) {
3604 LOG(("Skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
3605 "load flag (mApplicationCacheForWrite not null case)\n"));
3606 mCacheInputStream.CloseAndRelease();
3607 // TODO: Bug 759040 - We should call HandleAsyncNotModified directly
3608 // here, to avoid event dispatching latency.
3609 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
3610 }
3611 }
3612
3613 MOZ_ASSERT(mCacheInputStream);
3614 if (!mCacheInputStream) {
3615 NS_ERROR("mCacheInputStream is null but we're expecting to "
3616 "be able to read from it.");
3617 return NS_ERROR_UNEXPECTED;
3618 }
3619
3620
3621 nsCOMPtr<nsIInputStream> inputStream = mCacheInputStream.forget();
3622
3623 rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), inputStream,
3624 int64_t(-1), int64_t(-1), 0, 0, true);
3625 if (NS_FAILED(rv)) {
3626 inputStream->Close();
3627 return rv;
3628 }
3629
3630 rv = mCachePump->AsyncRead(this, mListenerContext);
3631 if (NS_FAILED(rv)) return rv;
3632
3633 if (mTimingEnabled)
3634 mCacheReadStart = TimeStamp::Now();
3635
3636 uint32_t suspendCount = mSuspendCount;
3637 while (suspendCount--)
3638 mCachePump->Suspend();
3639
3640 return NS_OK;
3641 }
3642
3643 void
3644 nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
3645 {
3646 mCacheInputStream.CloseAndRelease();
3647
3648 if (!mCacheEntry)
3649 return;
3650
3651 LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheEntryIsWriteOnly=%x",
3652 this, mStatus, mCacheEntryIsWriteOnly));
3653
3654 // If we have begun to create or replace a cache entry, and that cache
3655 // entry is not complete and not resumable, then it needs to be doomed.
3656 // Otherwise, CheckCache will make the mistake of thinking that the
3657 // partial cache entry is complete.
3658
3659 bool doom = false;
3660 if (mInitedCacheEntry) {
3661 MOZ_ASSERT(mResponseHead, "oops");
3662 if (NS_FAILED(mStatus) && doomOnFailure &&
3663 mCacheEntryIsWriteOnly && !mResponseHead->IsResumable())
3664 doom = true;
3665 }
3666 else if (mCacheEntryIsWriteOnly)
3667 doom = true;
3668
3669 if (doom) {
3670 LOG((" dooming cache entry!!"));
3671 mCacheEntry->AsyncDoom(nullptr);
3672 }
3673
3674 mCachedResponseHead = nullptr;
3675
3676 mCachePump = nullptr;
3677 mCacheEntry = nullptr;
3678 mCacheEntryIsWriteOnly = false;
3679 mInitedCacheEntry = false;
3680 }
3681
3682
3683 void
3684 nsHttpChannel::CloseOfflineCacheEntry()
3685 {
3686 if (!mOfflineCacheEntry)
3687 return;
3688
3689 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
3690
3691 if (NS_FAILED(mStatus)) {
3692 mOfflineCacheEntry->AsyncDoom(nullptr);
3693 }
3694 else {
3695 bool succeeded;
3696 if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
3697 mOfflineCacheEntry->AsyncDoom(nullptr);
3698 }
3699
3700 mOfflineCacheEntry = nullptr;
3701 }
3702
3703
3704 // Initialize the cache entry for writing.
3705 // - finalize storage policy
3706 // - store security info
3707 // - update expiration time
3708 // - store headers and other meta data
3709 nsresult
3710 nsHttpChannel::InitCacheEntry()
3711 {
3712 nsresult rv;
3713
3714 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
3715 // if only reading, nothing to be done here.
3716 if (mCacheEntryIsReadOnly)
3717 return NS_OK;
3718
3719 // Don't cache the response again if already cached...
3720 if (mCachedContentIsValid)
3721 return NS_OK;
3722
3723 LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n",
3724 this, mCacheEntry.get()));
3725
3726 bool recreate = !mCacheEntryIsWriteOnly;
3727 bool dontPersist = mLoadFlags & INHIBIT_PERSISTENT_CACHING;
3728
3729 if (!recreate && dontPersist) {
3730 // If the current entry is persistent but we inhibit peristence
3731 // then force recreation of the entry as memory/only.
3732 rv = mCacheEntry->GetPersistent(&recreate);
3733 if (NS_FAILED(rv))
3734 return rv;
3735 }
3736
3737 if (recreate) {
3738 LOG((" we have a ready entry, but reading it again from the server -> recreating cache entry\n"));
3739 nsCOMPtr<nsICacheEntry> currentEntry;
3740 currentEntry.swap(mCacheEntry);
3741 rv = currentEntry->Recreate(dontPersist, getter_AddRefs(mCacheEntry));
3742 if (NS_FAILED(rv)) {
3743 LOG((" recreation failed, the response will not be cached"));
3744 return NS_OK;
3745 }
3746
3747 mCacheEntryIsWriteOnly = true;
3748 }
3749
3750 // Set the expiration time for this cache entry
3751 rv = UpdateExpirationTime();
3752 if (NS_FAILED(rv)) return rv;
3753
3754 rv = AddCacheEntryHeaders(mCacheEntry);
3755 if (NS_FAILED(rv)) return rv;
3756
3757 mInitedCacheEntry = true;
3758
3759 // Don't perform the check when writing (doesn't make sense)
3760 mConcurentCacheAccess = 0;
3761
3762 return NS_OK;
3763 }
3764
3765 void
3766 nsHttpChannel::UpdateInhibitPersistentCachingFlag()
3767 {
3768 // The no-store directive within the 'Cache-Control:' header indicates
3769 // that we must not store the response in a persistent cache.
3770 if (mResponseHead->NoStore())
3771 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
3772
3773 // Only cache SSL content on disk if the pref is set
3774 if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
3775 mConnectionInfo->UsingSSL())
3776 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
3777 }
3778
3779 nsresult
3780 nsHttpChannel::InitOfflineCacheEntry()
3781 {
3782 // This function can be called even when we fail to connect (bug 551990)
3783
3784 if (!mOfflineCacheEntry) {
3785 return NS_OK;
3786 }
3787
3788 if (!mResponseHead || mResponseHead->NoStore()) {
3789 if (mResponseHead && mResponseHead->NoStore()) {
3790 mOfflineCacheEntry->AsyncDoom(nullptr);
3791 }
3792
3793 CloseOfflineCacheEntry();
3794
3795 if (mResponseHead && mResponseHead->NoStore()) {
3796 return NS_ERROR_NOT_AVAILABLE;
3797 }
3798
3799 return NS_OK;
3800 }
3801
3802 // This entry's expiration time should match the main entry's expiration
3803 // time. UpdateExpirationTime() will keep it in sync once the offline
3804 // cache entry has been created.
3805 if (mCacheEntry) {
3806 uint32_t expirationTime;
3807 nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
3808 NS_ENSURE_SUCCESS(rv, rv);
3809
3810 mOfflineCacheEntry->SetExpirationTime(expirationTime);
3811 }
3812
3813 return AddCacheEntryHeaders(mOfflineCacheEntry);
3814 }
3815
3816
3817 nsresult
3818 nsHttpChannel::AddCacheEntryHeaders(nsICacheEntry *entry)
3819 {
3820 nsresult rv;
3821
3822 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] begin", this));
3823 // Store secure data in memory only
3824 if (mSecurityInfo)
3825 entry->SetSecurityInfo(mSecurityInfo);
3826
3827 // Store the HTTP request method with the cache entry so we can distinguish
3828 // for example GET and HEAD responses.
3829 rv = entry->SetMetaDataElement("request-method",
3830 mRequestHead.Method().get());
3831 if (NS_FAILED(rv)) return rv;
3832
3833 // Store the HTTP authorization scheme used if any...
3834 rv = StoreAuthorizationMetaData(entry);
3835 if (NS_FAILED(rv)) return rv;
3836
3837 // Iterate over the headers listed in the Vary response header, and
3838 // store the value of the corresponding request header so we can verify
3839 // that it has not varied when we try to re-use the cached response at
3840 // a later time. Take care to store "Cookie" headers only as hashes
3841 // due to security considerations and the fact that they can be pretty
3842 // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
3843 //
3844 // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
3845 // in the cache. we could try to avoid needlessly storing the "accept"
3846 // header in this case, but it doesn't seem worth the extra code to perform
3847 // the check.
3848 {
3849 nsAutoCString buf, metaKey;
3850 mResponseHead->GetHeader(nsHttp::Vary, buf);
3851 if (!buf.IsEmpty()) {
3852 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
3853
3854 char *val = buf.BeginWriting(); // going to munge buf
3855 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3856 while (token) {
3857 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
3858 "processing %s", this, token));
3859 if (*token != '*') {
3860 nsHttpAtom atom = nsHttp::ResolveAtom(token);
3861 const char *val = mRequestHead.PeekHeader(atom);
3862 nsAutoCString hash;
3863 if (val) {
3864 // If cookie-header, store a hash of the value
3865 if (atom == nsHttp::Cookie) {
3866 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
3867 "cookie-value %s", this, val));
3868 rv = Hash(val, hash);
3869 // If hash failed, store a string not very likely
3870 // to be the result of subsequent hashes
3871 if (NS_FAILED(rv))
3872 val = "<hash failed>";
3873 else
3874 val = hash.get();
3875
3876 LOG((" hashed to %s\n", val));
3877 }
3878
3879 // build cache meta data key and set meta data element...
3880 metaKey = prefix + nsDependentCString(token);
3881 entry->SetMetaDataElement(metaKey.get(), val);
3882 } else {
3883 LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%p] " \
3884 "clearing metadata for %s", this, token));
3885 metaKey = prefix + nsDependentCString(token);
3886 entry->SetMetaDataElement(metaKey.get(), nullptr);
3887 }
3888 }
3889 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3890 }
3891 }
3892 }
3893
3894
3895 // Store the received HTTP head with the cache entry as an element of
3896 // the meta data.
3897 nsAutoCString head;
3898 mResponseHead->Flatten(head, true);
3899 rv = entry->SetMetaDataElement("response-head", head.get());
3900 if (NS_FAILED(rv)) return rv;
3901
3902 // Indicate we have successfully finished setting metadata on the cache entry.
3903 rv = entry->MetaDataReady();
3904
3905 return rv;
3906 }
3907
3908 inline void
3909 GetAuthType(const char *challenge, nsCString &authType)
3910 {
3911 const char *p;
3912
3913 // get the challenge type
3914 if ((p = strchr(challenge, ' ')) != nullptr)
3915 authType.Assign(challenge, p - challenge);
3916 else
3917 authType.Assign(challenge);
3918 }
3919
3920 nsresult
3921 nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntry *entry)
3922 {
3923 // Not applicable to proxy authorization...
3924 const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
3925 if (!val)
3926 return NS_OK;
3927
3928 // eg. [Basic realm="wally world"]
3929 nsAutoCString buf;
3930 GetAuthType(val, buf);
3931 return entry->SetMetaDataElement("auth", buf.get());
3932 }
3933
3934 // Finalize the cache entry
3935 // - may need to rewrite response headers if any headers changed
3936 // - may need to recalculate the expiration time if any headers changed
3937 // - called only for freshly written cache entries
3938 nsresult
3939 nsHttpChannel::FinalizeCacheEntry()
3940 {
3941 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
3942
3943 if (mResponseHead && mResponseHeadersModified) {
3944 // Set the expiration time for this cache entry
3945 nsresult rv = UpdateExpirationTime();
3946 if (NS_FAILED(rv)) return rv;
3947 }
3948 return NS_OK;
3949 }
3950
3951 // Open an output stream to the cache entry and insert a listener tee into
3952 // the chain of response listeners.
3953 nsresult
3954 nsHttpChannel::InstallCacheListener(int64_t offset)
3955 {
3956 nsresult rv;
3957
3958 LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
3959
3960 MOZ_ASSERT(mCacheEntry);
3961 MOZ_ASSERT(mCacheEntryIsWriteOnly || mCachedContentIsPartial);
3962 MOZ_ASSERT(mListener);
3963
3964 // If the content is compressible and the server has not compressed it,
3965 // mark the cache entry for compression.
3966 if ((mResponseHead->PeekHeader(nsHttp::Content_Encoding) == nullptr) && (
3967 mResponseHead->ContentType().EqualsLiteral(TEXT_HTML) ||
3968 mResponseHead->ContentType().EqualsLiteral(TEXT_PLAIN) ||
3969 mResponseHead->ContentType().EqualsLiteral(TEXT_CSS) ||
3970 mResponseHead->ContentType().EqualsLiteral(TEXT_JAVASCRIPT) ||
3971 mResponseHead->ContentType().EqualsLiteral(TEXT_ECMASCRIPT) ||
3972 mResponseHead->ContentType().EqualsLiteral(TEXT_XML) ||
3973 mResponseHead->ContentType().EqualsLiteral(APPLICATION_JAVASCRIPT) ||
3974 mResponseHead->ContentType().EqualsLiteral(APPLICATION_ECMASCRIPT) ||
3975 mResponseHead->ContentType().EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
3976 mResponseHead->ContentType().EqualsLiteral(APPLICATION_XHTML_XML))) {
3977 rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
3978 if (NS_FAILED(rv)) {
3979 LOG(("unable to mark cache entry for compression"));
3980 }
3981 }
3982
3983 LOG(("Trading cache input stream for output stream [channel=%p]", this));
3984
3985 // We must close the input stream first because cache entries do not
3986 // correctly handle having an output stream and input streams open at
3987 // the same time.
3988 mCacheInputStream.CloseAndRelease();
3989
3990 nsCOMPtr<nsIOutputStream> out;
3991 rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
3992 if (rv == NS_ERROR_NOT_AVAILABLE) {
3993 LOG((" entry doomed, not writing it [channel=%p]", this));
3994 // Entry is already doomed.
3995 // This may happen when expiration time is set to past and the entry
3996 // has been removed by the background eviction logic.
3997 return NS_OK;
3998 }
3999 if (NS_FAILED(rv)) return rv;
4000
4001 // XXX disk cache does not support overlapped i/o yet
4002 #if 0
4003 // Mark entry valid inorder to allow simultaneous reading...
4004 rv = mCacheEntry->MarkValid();
4005 if (NS_FAILED(rv)) return rv;
4006 #endif
4007
4008 nsCOMPtr<nsIStreamListenerTee> tee =
4009 do_CreateInstance(kStreamListenerTeeCID, &rv);
4010 if (NS_FAILED(rv)) return rv;
4011
4012 nsCOMPtr<nsICacheStorageService> serv =
4013 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
4014 NS_ENSURE_SUCCESS(rv, rv);
4015
4016 nsCOMPtr<nsIEventTarget> cacheIOTarget;
4017 serv->GetIoTarget(getter_AddRefs(cacheIOTarget));
4018
4019 if (!cacheIOTarget) {
4020 LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x "
4021 "cacheIOTarget=%p", tee.get(), rv, cacheIOTarget.get()));
4022 rv = tee->Init(mListener, out, nullptr);
4023 } else {
4024 LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
4025 rv = tee->InitAsync(mListener, cacheIOTarget, out, nullptr);
4026 }
4027
4028 if (NS_FAILED(rv)) return rv;
4029 mListener = tee;
4030 return NS_OK;
4031 }
4032
4033 nsresult
4034 nsHttpChannel::InstallOfflineCacheListener(int64_t offset)
4035 {
4036 nsresult rv;
4037
4038 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
4039 mSpec.get()));
4040
4041 MOZ_ASSERT(mOfflineCacheEntry);
4042 MOZ_ASSERT(mListener);
4043
4044 nsCOMPtr<nsIOutputStream> out;
4045 rv = mOfflineCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
4046 if (NS_FAILED(rv)) return rv;
4047
4048 nsCOMPtr<nsIStreamListenerTee> tee =
4049 do_CreateInstance(kStreamListenerTeeCID, &rv);
4050 if (NS_FAILED(rv)) return rv;
4051
4052 rv = tee->Init(mListener, out, nullptr);
4053 if (NS_FAILED(rv)) return rv;
4054
4055 mListener = tee;
4056
4057 return NS_OK;
4058 }
4059
4060 void
4061 nsHttpChannel::ClearBogusContentEncodingIfNeeded()
4062 {
4063 // For .gz files, apache sends both a Content-Type: application/x-gzip
4064 // as well as Content-Encoding: gzip, which is completely wrong. In
4065 // this case, we choose to ignore the rogue Content-Encoding header. We
4066 // must do this early on so as to prevent it from being seen up stream.
4067 // The same problem exists for Content-Encoding: compress in default
4068 // Apache installs.
4069 if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
4070 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
4071 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
4072 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
4073 // clear the Content-Encoding header
4074 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
4075 }
4076 else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
4077 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
4078 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
4079 // clear the Content-Encoding header
4080 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
4081 }
4082 }
4083
4084 //-----------------------------------------------------------------------------
4085 // nsHttpChannel <redirect>
4086 //-----------------------------------------------------------------------------
4087
4088 nsresult
4089 nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
4090 nsIChannel *newChannel,
4091 bool preserveMethod)
4092 {
4093 LOG(("nsHttpChannel::SetupReplacementChannel "
4094 "[this=%p newChannel=%p preserveMethod=%d]",
4095 this, newChannel, preserveMethod));
4096
4097 nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel, preserveMethod);
4098 if (NS_FAILED(rv))
4099 return rv;
4100
4101 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
4102 if (!httpChannel)
4103 return NS_OK; // no other options to set
4104
4105 // convey the mApplyConversion flag (bug 91862)
4106 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
4107 if (encodedChannel)
4108 encodedChannel->SetApplyConversion(mApplyConversion);
4109
4110 // transfer the resume information
4111 if (mResuming) {
4112 nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
4113 if (!resumableChannel) {
4114 NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
4115 return NS_ERROR_NOT_RESUMABLE;
4116 }
4117 resumableChannel->ResumeAt(mStartPos, mEntityID);
4118 }
4119
4120 return NS_OK;
4121 }
4122
4123 nsresult
4124 nsHttpChannel::AsyncProcessRedirection(uint32_t redirectType)
4125 {
4126 LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
4127 this, redirectType));
4128
4129 // The channel is actually starting its operation now, at least because
4130 // we want it to appear like being in the waiting phase until now.
4131 MOZ_EVENT_TRACER_EXEC(this, "net::http::channel");
4132 MOZ_EVENT_TRACER_EXEC(this, "net::http::redirect-callbacks");
4133
4134 const char *location = mResponseHead->PeekHeader(nsHttp::Location);
4135
4136 // if a location header was not given, then we can't perform the redirect,
4137 // so just carry on as though this were a normal response.
4138 if (!location)
4139 return NS_ERROR_FAILURE;
4140
4141 // make sure non-ASCII characters in the location header are escaped.
4142 nsAutoCString locationBuf;
4143 if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
4144 location = locationBuf.get();
4145
4146 if (mRedirectionLimit == 0) {
4147 LOG(("redirection limit reached!\n"));
4148 return NS_ERROR_REDIRECT_LOOP;
4149 }
4150
4151 mRedirectType = redirectType;
4152
4153 LOG(("redirecting to: %s [redirection-limit=%u]\n",
4154 location, uint32_t(mRedirectionLimit)));
4155
4156 nsresult rv = CreateNewURI(location, getter_AddRefs(mRedirectURI));
4157
4158 if (NS_FAILED(rv)) {
4159 LOG(("Invalid URI for redirect: Location: %s\n", location));
4160 return NS_ERROR_CORRUPTED_CONTENT;
4161 }
4162
4163 if (mApplicationCache) {
4164 // if we are redirected to a different origin check if there is a fallback
4165 // cache entry to fall back to. we don't care about file strict
4166 // checking, at least mURI is not a file URI.
4167 if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
4168 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
4169 bool waitingForRedirectCallback;
4170 (void)ProcessFallback(&waitingForRedirectCallback);
4171 if (waitingForRedirectCallback)
4172 return NS_OK;
4173 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
4174 }
4175 }
4176
4177 return ContinueProcessRedirectionAfterFallback(NS_OK);
4178 }
4179
4180 // Creates an URI to the given location using current URI for base and charset
4181 nsresult
4182 nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
4183 {
4184 nsCOMPtr<nsIIOService> ioService;
4185 nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
4186 if (NS_FAILED(rv)) return rv;
4187
4188 // the new uri should inherit the origin charset of the current uri
4189 nsAutoCString originCharset;
4190 rv = mURI->GetOriginCharset(originCharset);
4191 if (NS_FAILED(rv))
4192 originCharset.Truncate();
4193
4194 return ioService->NewURI(nsDependentCString(loc),
4195 originCharset.get(),
4196 mURI,
4197 newURI);
4198 }
4199
4200 nsresult
4201 nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
4202 {
4203 if (NS_SUCCEEDED(rv) && mFallingBack) {
4204 // do not continue with redirect processing, fallback is in
4205 // progress now.
4206 return NS_OK;
4207 }
4208
4209 // Kill the current cache entry if we are redirecting
4210 // back to ourself.
4211 bool redirectingBackToSameURI = false;
4212 if (mCacheEntry && mCacheEntryIsWriteOnly &&
4213 NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
4214 redirectingBackToSameURI)
4215 mCacheEntry->AsyncDoom(nullptr);
4216
4217 // move the reference of the old location to the new one if the new
4218 // one has none.
4219 nsAutoCString ref;
4220 rv = mRedirectURI->GetRef(ref);
4221 if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
4222 mURI->GetRef(ref);
4223 if (!ref.IsEmpty()) {
4224 // NOTE: SetRef will fail if mRedirectURI is immutable
4225 // (e.g. an about: URI)... Oh well.
4226 mRedirectURI->SetRef(ref);
4227 }
4228 }
4229
4230 bool rewriteToGET = ShouldRewriteRedirectToGET(mRedirectType,
4231 mRequestHead.ParsedMethod());
4232
4233 // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
4234 if (!rewriteToGET && !mRequestHead.IsSafeMethod()) {
4235 rv = PromptTempRedirect();
4236 if (NS_FAILED(rv)) return rv;
4237 }
4238
4239 nsCOMPtr<nsIIOService> ioService;
4240 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
4241 if (NS_FAILED(rv)) return rv;
4242
4243 nsCOMPtr<nsIChannel> newChannel;
4244 rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel));
4245 if (NS_FAILED(rv)) return rv;
4246
4247 rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET);
4248 if (NS_FAILED(rv)) return rv;
4249
4250 uint32_t redirectFlags;
4251 if (nsHttp::IsPermanentRedirect(mRedirectType))
4252 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
4253 else
4254 redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
4255
4256 // verify that this is a legal redirect
4257 mRedirectChannel = newChannel;
4258
4259 PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
4260 rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
4261
4262 if (NS_SUCCEEDED(rv))
4263 rv = WaitForRedirectCallback();
4264
4265 if (NS_FAILED(rv)) {
4266 AutoRedirectVetoNotifier notifier(this);
4267 PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
4268 }
4269
4270 return rv;
4271 }
4272
4273 nsresult
4274 nsHttpChannel::ContinueProcessRedirection(nsresult rv)
4275 {
4276 AutoRedirectVetoNotifier notifier(this);
4277
4278 LOG(("ContinueProcessRedirection [rv=%x]\n", rv));
4279 if (NS_FAILED(rv))
4280 return rv;
4281
4282 NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
4283
4284 // Make sure to do this _after_ calling OnChannelRedirect
4285 mRedirectChannel->SetOriginalURI(mOriginalURI);
4286
4287 // And now, the deprecated way
4288 nsCOMPtr<nsIHttpEventSink> httpEventSink;
4289 GetCallback(httpEventSink);
4290 if (httpEventSink) {
4291 // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
4292 // versions.
4293 rv = httpEventSink->OnRedirect(this, mRedirectChannel);
4294 if (NS_FAILED(rv))
4295 return rv;
4296 }
4297 // XXX we used to talk directly with the script security manager, but that
4298 // should really be handled by the event sink implementation.
4299
4300 // begin loading the new channel
4301 rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
4302
4303 if (NS_FAILED(rv))
4304 return rv;
4305
4306 // close down this channel
4307 Cancel(NS_BINDING_REDIRECTED);
4308
4309 notifier.RedirectSucceeded();
4310
4311 ReleaseListeners();
4312
4313 return NS_OK;
4314 }
4315
4316 //-----------------------------------------------------------------------------
4317 // nsHttpChannel <auth>
4318 //-----------------------------------------------------------------------------
4319
4320 NS_IMETHODIMP nsHttpChannel::OnAuthAvailable()
4321 {
4322 LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
4323
4324 // setting mAuthRetryPending flag and resuming the transaction
4325 // triggers process of throwing away the unauthenticated data already
4326 // coming from the network
4327 mAuthRetryPending = true;
4328 mProxyAuthPending = false;
4329 LOG(("Resuming the transaction, we got credentials from user"));
4330 mTransactionPump->Resume();
4331
4332 return NS_OK;
4333 }
4334
4335 NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel)
4336 {
4337 LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
4338
4339 if (mTransactionPump) {
4340 // If the channel is trying to authenticate to a proxy and
4341 // that was canceled we cannot show the http response body
4342 // from the 40x as that might mislead the user into thinking
4343 // it was a end host response instead of a proxy reponse.
4344 // This must check explicitly whether a proxy auth was being done
4345 // because we do want to show the content if this is an error from
4346 // the origin server.
4347 if (mProxyAuthPending)
4348 Cancel(NS_ERROR_PROXY_CONNECTION_REFUSED);
4349
4350 // ensure call of OnStartRequest of the current listener here,
4351 // it would not be called otherwise at all
4352 nsresult rv = CallOnStartRequest();
4353
4354 // drop mAuthRetryPending flag and resume the transaction
4355 // this resumes load of the unauthenticated content data (which
4356 // may have been canceled if we don't want to show it)
4357 mAuthRetryPending = false;
4358 LOG(("Resuming the transaction, user cancelled the auth dialog"));
4359 mTransactionPump->Resume();
4360
4361 if (NS_FAILED(rv))
4362 mTransactionPump->Cancel(rv);
4363 }
4364
4365 mProxyAuthPending = false;
4366 return NS_OK;
4367 }
4368
4369 //-----------------------------------------------------------------------------
4370 // nsHttpChannel::nsISupports
4371 //-----------------------------------------------------------------------------
4372
4373 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
4374 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
4375
4376 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
4377 NS_INTERFACE_MAP_ENTRY(nsIRequest)
4378 NS_INTERFACE_MAP_ENTRY(nsIChannel)
4379 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
4380 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
4381 NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
4382 NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
4383 NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
4384 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
4385 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
4386 NS_INTERFACE_MAP_ENTRY(nsICacheEntryOpenCallback)
4387 NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
4388 NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
4389 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
4390 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
4391 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
4392 NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
4393 NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
4394 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
4395 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
4396 NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
4397 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableRequest)
4398 NS_INTERFACE_MAP_ENTRY(nsIThreadRetargetableStreamListener)
4399 NS_INTERFACE_MAP_ENTRY(nsIDNSListener)
4400 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
4401 NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
4402
4403 //-----------------------------------------------------------------------------
4404 // nsHttpChannel::nsIRequest
4405 //-----------------------------------------------------------------------------
4406
4407 NS_IMETHODIMP
4408 nsHttpChannel::Cancel(nsresult status)
4409 {
4410 MOZ_ASSERT(NS_IsMainThread());
4411
4412 LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status));
4413 if (mCanceled) {
4414 LOG((" ignoring; already canceled\n"));
4415 return NS_OK;
4416 }
4417 if (mWaitingForRedirectCallback) {
4418 LOG(("channel canceled during wait for redirect callback"));
4419 }
4420 mCanceled = true;
4421 mStatus = status;
4422 if (mProxyRequest)
4423 mProxyRequest->Cancel(status);
4424 if (mTransaction)
4425 gHttpHandler->CancelTransaction(mTransaction, status);
4426 if (mTransactionPump)
4427 mTransactionPump->Cancel(status);
4428 mCacheInputStream.CloseAndRelease();
4429 if (mCachePump)
4430 mCachePump->Cancel(status);
4431 if (mAuthProvider)
4432 mAuthProvider->Cancel(status);
4433 return NS_OK;
4434 }
4435
4436 NS_IMETHODIMP
4437 nsHttpChannel::Suspend()
4438 {
4439 NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
4440
4441 LOG(("nsHttpChannel::Suspend [this=%p]\n", this));
4442
4443 ++mSuspendCount;
4444
4445 if (mTransactionPump)
4446 return mTransactionPump->Suspend();
4447 if (mCachePump)
4448 return mCachePump->Suspend();
4449
4450 return NS_OK;
4451 }
4452
4453 NS_IMETHODIMP
4454 nsHttpChannel::Resume()
4455 {
4456 NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
4457
4458 LOG(("nsHttpChannel::Resume [this=%p]\n", this));
4459
4460 if (--mSuspendCount == 0 && mCallOnResume) {
4461 nsresult rv = AsyncCall(mCallOnResume);
4462 mCallOnResume = nullptr;
4463 NS_ENSURE_SUCCESS(rv, rv);
4464 }
4465
4466 if (mTransactionPump)
4467 return mTransactionPump->Resume();
4468 if (mCachePump)
4469 return mCachePump->Resume();
4470
4471 return NS_OK;
4472 }
4473
4474 //-----------------------------------------------------------------------------
4475 // nsHttpChannel::nsIChannel
4476 //-----------------------------------------------------------------------------
4477
4478 NS_IMETHODIMP
4479 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
4480 {
4481 NS_ENSURE_ARG_POINTER(securityInfo);
4482 *securityInfo = mSecurityInfo;
4483 NS_IF_ADDREF(*securityInfo);
4484 return NS_OK;
4485 }
4486
4487 NS_IMETHODIMP
4488 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
4489 {
4490 MOZ_EVENT_TRACER_WAIT(this, "net::http::channel");
4491
4492 LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
4493
4494 NS_ENSURE_ARG_POINTER(listener);
4495 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4496 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
4497
4498 nsresult rv;
4499
4500 rv = NS_CheckPortSafety(mURI);
4501 if (NS_FAILED(rv)) {
4502 ReleaseListeners();
4503 return rv;
4504 }
4505
4506 // Remember the cookie header that was set, if any
4507 const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
4508 if (cookieHeader) {
4509 mUserSetCookieHeader = cookieHeader;
4510 }
4511
4512 AddCookiesToRequest();
4513
4514 // notify "http-on-opening-request" observers, but not if this is a redirect
4515 if (!(mLoadFlags & LOAD_REPLACE)) {
4516 gHttpHandler->OnOpeningRequest(this);
4517 }
4518
4519 mIsPending = true;
4520 mWasOpened = true;
4521
4522 mListener = listener;
4523 mListenerContext = context;
4524
4525 // add ourselves to the load group. from this point forward, we'll report
4526 // all failures asynchronously.
4527 if (mLoadGroup)
4528 mLoadGroup->AddRequest(this, nullptr);
4529
4530 // record asyncopen time unconditionally and clear it if we
4531 // don't want it after OnModifyRequest() weighs in. But waiting for
4532 // that to complete would mean we don't include proxy resolution in the
4533 // timing.
4534 mAsyncOpenTime = TimeStamp::Now();
4535
4536 // the only time we would already know the proxy information at this
4537 // point would be if we were proxying a non-http protocol like ftp
4538 if (!mProxyInfo && NS_SUCCEEDED(ResolveProxy()))
4539 return NS_OK;
4540
4541 rv = BeginConnect();
4542 if (NS_FAILED(rv))
4543 ReleaseListeners();
4544
4545 return rv;
4546 }
4547
4548 nsresult
4549 nsHttpChannel::BeginConnect()
4550 {
4551 LOG(("nsHttpChannel::BeginConnect [this=%p]\n", this));
4552 nsresult rv;
4553
4554 // Construct connection info object
4555 nsAutoCString host;
4556 int32_t port = -1;
4557 nsAutoCString username;
4558 bool usingSSL = false;
4559
4560 rv = mURI->SchemeIs("https", &usingSSL);
4561 if (NS_SUCCEEDED(rv))
4562 rv = mURI->GetAsciiHost(host);
4563 if (NS_SUCCEEDED(rv))
4564 rv = mURI->GetPort(&port);
4565 if (NS_SUCCEEDED(rv))
4566 mURI->GetUsername(username);
4567 if (NS_SUCCEEDED(rv))
4568 rv = mURI->GetAsciiSpec(mSpec);
4569 if (NS_FAILED(rv))
4570 return rv;
4571
4572 // Reject the URL if it doesn't specify a host
4573 if (host.IsEmpty())
4574 return NS_ERROR_MALFORMED_URI;
4575 LOG(("host=%s port=%d\n", host.get(), port));
4576 LOG(("uri=%s\n", mSpec.get()));
4577
4578 nsCOMPtr<nsProxyInfo> proxyInfo;
4579 if (mProxyInfo)
4580 proxyInfo = do_QueryInterface(mProxyInfo);
4581
4582 mConnectionInfo = new nsHttpConnectionInfo(host, port, username, proxyInfo, usingSSL);
4583
4584 mAuthProvider =
4585 do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
4586 &rv);
4587 if (NS_SUCCEEDED(rv))
4588 rv = mAuthProvider->Init(this);
4589 if (NS_FAILED(rv))
4590 return rv;
4591
4592 // check to see if authorization headers should be included
4593 mAuthProvider->AddAuthorizationHeaders();
4594
4595 // notify "http-on-modify-request" observers
4596 CallOnModifyRequestObservers();
4597
4598 // Check to see if we should redirect this channel elsewhere by
4599 // nsIHttpChannel.redirectTo API request
4600 if (mAPIRedirectToURI) {
4601 return AsyncCall(&nsHttpChannel::HandleAsyncAPIRedirect);
4602 }
4603
4604 // If mTimingEnabled flag is not set after OnModifyRequest() then
4605 // clear the already recorded AsyncOpen value for consistency.
4606 if (!mTimingEnabled)
4607 mAsyncOpenTime = TimeStamp();
4608
4609 // when proxying only use the pipeline bit if ProxyPipelining() allows it.
4610 if (!mConnectionInfo->UsingConnect() && mConnectionInfo->UsingHttpProxy()) {
4611 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
4612 if (gHttpHandler->ProxyPipelining())
4613 mCaps |= NS_HTTP_ALLOW_PIPELINING;
4614 }
4615
4616 // if this somehow fails we can go on without it
4617 gHttpHandler->AddConnectionHeader(&mRequestHead.Headers(), mCaps);
4618
4619 if (mLoadFlags & VALIDATE_ALWAYS || BYPASS_LOCAL_CACHE(mLoadFlags))
4620 mCaps |= NS_HTTP_REFRESH_DNS;
4621
4622 if (!mConnectionInfo->UsingHttpProxy()) {
4623 // Start a DNS lookup very early in case the real open is queued the DNS can
4624 // happen in parallel. Do not do so in the presence of an HTTP proxy as
4625 // all lookups other than for the proxy itself are done by the proxy.
4626 //
4627 // We keep the DNS prefetch object around so that we can retrieve
4628 // timing information from it. There is no guarantee that we actually
4629 // use the DNS prefetch data for the real connection, but as we keep
4630 // this data around for 3 minutes by default, this should almost always
4631 // be correct, and even when it isn't, the timing still represents _a_
4632 // valid DNS lookup timing for the site, even if it is not _the_
4633 // timing we used.
4634 LOG(("nsHttpChannel::BeginConnect [this=%p] prefetching%s\n",
4635 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : ""));
4636 mDNSPrefetch = new nsDNSPrefetch(mURI, this, mTimingEnabled);
4637 mDNSPrefetch->PrefetchHigh(mCaps & NS_HTTP_REFRESH_DNS);
4638 }
4639
4640 // Adjust mCaps according to our request headers:
4641 // - If "Connection: close" is set as a request header, then do not bother
4642 // trying to establish a keep-alive connection.
4643 if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
4644 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
4645
4646 if (gHttpHandler->CriticalRequestPrioritization()) {
4647 if (mLoadAsBlocking)
4648 mCaps |= NS_HTTP_LOAD_AS_BLOCKING;
4649 if (mLoadUnblocked)
4650 mCaps |= NS_HTTP_LOAD_UNBLOCKED;
4651 }
4652
4653 // Force-Reload should reset the persistent connection pool for this host
4654 if (mLoadFlags & LOAD_FRESH_CONNECTION) {
4655 // just the initial document resets the whole pool
4656 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) {
4657 gHttpHandler->ConnMgr()->DoShiftReloadConnectionCleanup(mConnectionInfo);
4658 }
4659 // each sub resource gets a fresh connection
4660 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
4661 }
4662
4663 // We may have been cancelled already, either by on-modify-request
4664 // listeners or by load group observers; in that case, we should
4665 // not send the request to the server
4666 if (mCanceled)
4667 rv = mStatus;
4668 else
4669 rv = Connect();
4670 if (NS_FAILED(rv)) {
4671 LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
4672 CloseCacheEntry(true);
4673 AsyncAbort(rv);
4674 } else if (mLoadFlags & LOAD_CLASSIFY_URI) {
4675 nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
4676 rv = classifier->Start(this);
4677 if (NS_FAILED(rv)) {
4678 Cancel(rv);
4679 return rv;
4680 }
4681 }
4682
4683 return NS_OK;
4684 }
4685
4686 //-----------------------------------------------------------------------------
4687 // nsHttpChannel::nsIHttpChannelInternal
4688 //-----------------------------------------------------------------------------
4689
4690 NS_IMETHODIMP
4691 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
4692 {
4693 ENSURE_CALLED_BEFORE_CONNECT();
4694
4695 LOG(("nsHttpChannel::SetupFallbackChannel [this=%p, key=%s]",
4696 this, aFallbackKey));
4697 mFallbackChannel = true;
4698 mFallbackKey = aFallbackKey;
4699
4700 return NS_OK;
4701 }
4702
4703 //-----------------------------------------------------------------------------
4704 // nsHttpChannel::nsISupportsPriority
4705 //-----------------------------------------------------------------------------
4706
4707 NS_IMETHODIMP
4708 nsHttpChannel::SetPriority(int32_t value)
4709 {
4710 int16_t newValue = clamped<int32_t>(value, INT16_MIN, INT16_MAX);
4711 if (mPriority == newValue)
4712 return NS_OK;
4713 mPriority = newValue;
4714 if (mTransaction)
4715 gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
4716 return NS_OK;
4717 }
4718
4719 //-----------------------------------------------------------------------------
4720 // nsHttpChannel::nsIProtocolProxyCallback
4721 //-----------------------------------------------------------------------------
4722
4723 NS_IMETHODIMP
4724 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
4725 nsIProxyInfo *pi, nsresult status)
4726 {
4727 LOG(("nsHttpChannel::OnProxyAvailable [this=%p pi=%p status=%x mStatus=%x]\n",
4728 this, pi, status, mStatus));
4729 mProxyRequest = nullptr;
4730
4731 nsresult rv;
4732
4733 // If status is a failure code, then it means that we failed to resolve
4734 // proxy info. That is a non-fatal error assuming it wasn't because the
4735 // request was canceled. We just failover to DIRECT when proxy resolution
4736 // fails (failure can mean that the PAC URL could not be loaded).
4737
4738 if (NS_SUCCEEDED(status))
4739 mProxyInfo = pi;
4740
4741 if (!gHttpHandler->Active()) {
4742 LOG(("nsHttpChannel::OnProxyAvailable [this=%p] "
4743 "Handler no longer active.\n", this));
4744 rv = NS_ERROR_NOT_AVAILABLE;
4745 }
4746 else {
4747 rv = BeginConnect();
4748 }
4749
4750 if (NS_FAILED(rv)) {
4751 Cancel(rv);
4752 // Calling OnStart/OnStop synchronously here would mean doing it before
4753 // returning from AsyncOpen which is a contract violation. Do it async.
4754 nsRefPtr<nsRunnableMethod<HttpBaseChannel> > event =
4755 NS_NewRunnableMethod(this, &nsHttpChannel::DoNotifyListener);
4756 rv = NS_DispatchToCurrentThread(event);
4757 if (NS_FAILED(rv)) {
4758 NS_WARNING("Failed To Dispatch DoNotifyListener");
4759 }
4760 }
4761 return rv;
4762 }
4763
4764 //-----------------------------------------------------------------------------
4765 // nsHttpChannel::nsIProxiedChannel
4766 //-----------------------------------------------------------------------------
4767
4768 NS_IMETHODIMP
4769 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
4770 {
4771 if (!mConnectionInfo)
4772 *result = mProxyInfo;
4773 else
4774 *result = mConnectionInfo->ProxyInfo();
4775 NS_IF_ADDREF(*result);
4776 return NS_OK;
4777 }
4778
4779 //-----------------------------------------------------------------------------
4780 // nsHttpChannel::nsITimedChannel
4781 //-----------------------------------------------------------------------------
4782
4783 NS_IMETHODIMP
4784 nsHttpChannel::GetDomainLookupStart(TimeStamp* _retval) {
4785 if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
4786 *_retval = mDNSPrefetch->StartTimestamp();
4787 else if (mTransaction)
4788 *_retval = mTransaction->Timings().domainLookupStart;
4789 else
4790 *_retval = mTransactionTimings.domainLookupStart;
4791 return NS_OK;
4792 }
4793
4794 NS_IMETHODIMP
4795 nsHttpChannel::GetDomainLookupEnd(TimeStamp* _retval) {
4796 if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
4797 *_retval = mDNSPrefetch->EndTimestamp();
4798 else if (mTransaction)
4799 *_retval = mTransaction->Timings().domainLookupEnd;
4800 else
4801 *_retval = mTransactionTimings.domainLookupEnd;
4802 return NS_OK;
4803 }
4804
4805 NS_IMETHODIMP
4806 nsHttpChannel::GetConnectStart(TimeStamp* _retval) {
4807 if (mTransaction)
4808 *_retval = mTransaction->Timings().connectStart;
4809 else
4810 *_retval = mTransactionTimings.connectStart;
4811 return NS_OK;
4812 }
4813
4814 NS_IMETHODIMP
4815 nsHttpChannel::GetConnectEnd(TimeStamp* _retval) {
4816 if (mTransaction)
4817 *_retval = mTransaction->Timings().connectEnd;
4818 else
4819 *_retval = mTransactionTimings.connectEnd;
4820 return NS_OK;
4821 }
4822
4823 NS_IMETHODIMP
4824 nsHttpChannel::GetRequestStart(TimeStamp* _retval) {
4825 if (mTransaction)
4826 *_retval = mTransaction->Timings().requestStart;
4827 else
4828 *_retval = mTransactionTimings.requestStart;
4829 return NS_OK;
4830 }
4831
4832 NS_IMETHODIMP
4833 nsHttpChannel::GetResponseStart(TimeStamp* _retval) {
4834 if (mTransaction)
4835 *_retval = mTransaction->Timings().responseStart;
4836 else
4837 *_retval = mTransactionTimings.responseStart;
4838 return NS_OK;
4839 }
4840
4841 NS_IMETHODIMP
4842 nsHttpChannel::GetResponseEnd(TimeStamp* _retval) {
4843 if (mTransaction)
4844 *_retval = mTransaction->Timings().responseEnd;
4845 else
4846 *_retval = mTransactionTimings.responseEnd;
4847 return NS_OK;
4848 }
4849
4850 //-----------------------------------------------------------------------------
4851 // nsHttpChannel::nsIHttpAuthenticableChannel
4852 //-----------------------------------------------------------------------------
4853
4854 NS_IMETHODIMP
4855 nsHttpChannel::GetIsSSL(bool *aIsSSL)
4856 {
4857 *aIsSSL = mConnectionInfo->UsingSSL();
4858 return NS_OK;
4859 }
4860
4861 NS_IMETHODIMP
4862 nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect)
4863 {
4864 *aProxyMethodIsConnect = mConnectionInfo->UsingConnect();
4865 return NS_OK;
4866 }
4867
4868 NS_IMETHODIMP
4869 nsHttpChannel::GetServerResponseHeader(nsACString &value)
4870 {
4871 if (!mResponseHead)
4872 return NS_ERROR_NOT_AVAILABLE;
4873 return mResponseHead->GetHeader(nsHttp::Server, value);
4874 }
4875
4876 NS_IMETHODIMP
4877 nsHttpChannel::GetProxyChallenges(nsACString &value)
4878 {
4879 if (!mResponseHead)
4880 return NS_ERROR_UNEXPECTED;
4881 return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
4882 }
4883
4884 NS_IMETHODIMP
4885 nsHttpChannel::GetWWWChallenges(nsACString &value)
4886 {
4887 if (!mResponseHead)
4888 return NS_ERROR_UNEXPECTED;
4889 return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
4890 }
4891
4892 NS_IMETHODIMP
4893 nsHttpChannel::SetProxyCredentials(const nsACString &value)
4894 {
4895 return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
4896 }
4897
4898 NS_IMETHODIMP
4899 nsHttpChannel::SetWWWCredentials(const nsACString &value)
4900 {
4901 return mRequestHead.SetHeader(nsHttp::Authorization, value);
4902 }
4903
4904 //-----------------------------------------------------------------------------
4905 // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
4906 // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
4907 //
4908
4909 NS_IMETHODIMP
4910 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
4911 {
4912 return HttpBaseChannel::GetLoadFlags(aLoadFlags);
4913 }
4914
4915 NS_IMETHODIMP
4916 nsHttpChannel::GetURI(nsIURI **aURI)
4917 {
4918 return HttpBaseChannel::GetURI(aURI);
4919 }
4920
4921 NS_IMETHODIMP
4922 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
4923 {
4924 return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
4925 }
4926
4927 NS_IMETHODIMP
4928 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
4929 {
4930 return HttpBaseChannel::GetLoadGroup(aLoadGroup);
4931 }
4932
4933 NS_IMETHODIMP
4934 nsHttpChannel::GetRequestMethod(nsACString& aMethod)
4935 {
4936 return HttpBaseChannel::GetRequestMethod(aMethod);
4937 }
4938
4939
4940 //-----------------------------------------------------------------------------
4941 // nsHttpChannel::nsIRequestObserver
4942 //-----------------------------------------------------------------------------
4943
4944 NS_IMETHODIMP
4945 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
4946 {
4947 PROFILER_LABEL("nsHttpChannel", "OnStartRequest");
4948 if (!(mCanceled || NS_FAILED(mStatus))) {
4949 // capture the request's status, so our consumers will know ASAP of any
4950 // connection failures, etc - bug 93581
4951 request->GetStatus(&mStatus);
4952 }
4953
4954 LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n",
4955 this, request, mStatus));
4956
4957 // Make sure things are what we expect them to be...
4958 MOZ_ASSERT(request == mCachePump || request == mTransactionPump,
4959 "Unexpected request");
4960 MOZ_ASSERT(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
4961 "If we have both pumps, the cache content must be partial");
4962
4963 if (!mSecurityInfo && !mCachePump && mTransaction) {
4964 // grab the security info from the connection object; the transaction
4965 // is guaranteed to own a reference to the connection.
4966 mSecurityInfo = mTransaction->SecurityInfo();
4967 }
4968
4969 // don't enter this block if we're reading from the cache...
4970 if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
4971 // mTransactionPump doesn't hit OnInputStreamReady and call this until
4972 // all of the response headers have been acquired, so we can take ownership
4973 // of them from the transaction.
4974 mResponseHead = mTransaction->TakeResponseHead();
4975 // the response head may be null if the transaction was cancelled. in
4976 // which case we just need to call OnStartRequest/OnStopRequest.
4977 if (mResponseHead)
4978 return ProcessResponse();
4979
4980 NS_WARNING("No response head in OnStartRequest");
4981 }
4982
4983 // cache file could be deleted on our behalf, reload from network here.
4984 if (mCacheEntry && mCachePump && CACHE_FILE_GONE(mStatus)) {
4985 LOG((" cache file gone, reloading from server"));
4986 mCacheEntry->AsyncDoom(nullptr);
4987 nsresult rv = StartRedirectChannelToURI(mURI, nsIChannelEventSink::REDIRECT_INTERNAL);
4988 if (NS_SUCCEEDED(rv))
4989 return NS_OK;
4990 }
4991
4992 // avoid crashing if mListener happens to be null...
4993 if (!mListener) {
4994 NS_NOTREACHED("mListener is null");
4995 return NS_OK;
4996 }
4997
4998 // on proxy errors, try to failover
4999 if (mConnectionInfo->ProxyInfo() &&
5000 (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
5001 mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
5002 mStatus == NS_ERROR_NET_TIMEOUT)) {
5003
5004 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
5005 if (NS_SUCCEEDED(ProxyFailover()))
5006 return NS_OK;
5007 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
5008 }
5009
5010 return ContinueOnStartRequest2(NS_OK);
5011 }
5012
5013 nsresult
5014 nsHttpChannel::ContinueOnStartRequest1(nsresult result)
5015 {
5016 // Success indicates we passed ProxyFailover, in that case we must not continue
5017 // with this code chain.
5018 if (NS_SUCCEEDED(result))
5019 return NS_OK;
5020
5021 return ContinueOnStartRequest2(result);
5022 }
5023
5024 nsresult
5025 nsHttpChannel::ContinueOnStartRequest2(nsresult result)
5026 {
5027 // on other request errors, try to fall back
5028 if (NS_FAILED(mStatus)) {
5029 PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
5030 bool waitingForRedirectCallback;
5031 ProcessFallback(&waitingForRedirectCallback);
5032 if (waitingForRedirectCallback)
5033 return NS_OK;
5034 PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
5035 }
5036
5037 return ContinueOnStartRequest3(NS_OK);
5038 }
5039
5040 nsresult
5041 nsHttpChannel::ContinueOnStartRequest3(nsresult result)
5042 {
5043 if (mFallingBack)
5044 return NS_OK;
5045
5046 return CallOnStartRequest();
5047 }
5048
5049 NS_IMETHODIMP
5050 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
5051 {
5052 PROFILER_LABEL("network", "nsHttpChannel::OnStopRequest");
5053 LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
5054 this, request, status));
5055
5056 if (mTimingEnabled && request == mCachePump) {
5057 mCacheReadEnd = TimeStamp::Now();
5058 }
5059
5060 // allow content to be cached if it was loaded successfully (bug #482935)
5061 bool contentComplete = NS_SUCCEEDED(status);
5062
5063 // honor the cancelation status even if the underlying transaction completed.
5064 if (mCanceled || NS_FAILED(mStatus))
5065 status = mStatus;
5066
5067 if (mCachedContentIsPartial) {
5068 if (NS_SUCCEEDED(status)) {
5069 // mTransactionPump should be suspended
5070 MOZ_ASSERT(request != mTransactionPump,
5071 "byte-range transaction finished prematurely");
5072
5073 if (request == mCachePump) {
5074 bool streamDone;
5075 status = OnDoneReadingPartialCacheEntry(&streamDone);
5076 if (NS_SUCCEEDED(status) && !streamDone)
5077 return status;
5078 // otherwise, fall through and fire OnStopRequest...
5079 }
5080 else if (request == mTransactionPump) {
5081 MOZ_ASSERT(mConcurentCacheAccess);
5082 }
5083 else
5084 NS_NOTREACHED("unexpected request");
5085 }
5086 // Do not to leave the transaction in a suspended state in error cases.
5087 if (NS_FAILED(status) && mTransaction)
5088 gHttpHandler->CancelTransaction(mTransaction, status);
5089 }
5090
5091 if (mTransaction) {
5092 // determine if we should call DoAuthRetry
5093 bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
5094
5095 //
5096 // grab references to connection in case we need to retry an
5097 // authentication request over it or use it for an upgrade
5098 // to another protocol.
5099 //
5100 // this code relies on the code in nsHttpTransaction::Close, which
5101 // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
5102 // keep the connection around after the transaction is finished.
5103 //
5104 nsRefPtr<nsAHttpConnection> conn;
5105 if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION)) {
5106 conn = mTransaction->GetConnectionReference();
5107 // This is so far a workaround to fix leak when reusing unpersistent
5108 // connection for authentication retry. See bug 459620 comment 4
5109 // for details.
5110 if (conn && !conn->IsPersistent())
5111 conn = nullptr;
5112 }
5113
5114 nsRefPtr<nsAHttpConnection> stickyConn;
5115 if (mCaps & NS_HTTP_STICKY_CONNECTION)
5116 stickyConn = mTransaction->GetConnectionReference();
5117
5118 // at this point, we're done with the transaction
5119 mTransactionTimings = mTransaction->Timings();
5120 mTransaction = nullptr;
5121 mTransactionPump = nullptr;
5122
5123 // We no longer need the dns prefetch object
5124 if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) {
5125 mTransactionTimings.domainLookupStart =
5126 mDNSPrefetch->StartTimestamp();
5127 mTransactionTimings.domainLookupEnd =
5128 mDNSPrefetch->EndTimestamp();
5129 }
5130 mDNSPrefetch = nullptr;
5131
5132 // handle auth retry...
5133 if (authRetry) {
5134 mAuthRetryPending = false;
5135 status = DoAuthRetry(conn);
5136 if (NS_SUCCEEDED(status))
5137 return NS_OK;
5138 }
5139
5140 // If DoAuthRetry failed, or if we have been cancelled since showing
5141 // the auth. dialog, then we need to send OnStartRequest now
5142 if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
5143 MOZ_ASSERT(NS_FAILED(status), "should have a failure code here");
5144 // NOTE: since we have a failure status, we can ignore the return
5145 // value from onStartRequest.
5146 if (mListener) {
5147 mListener->OnStartRequest(this, mListenerContext);
5148 } else {
5149 NS_WARNING("OnStartRequest skipped because of null listener");
5150 }
5151 }
5152
5153 // if this transaction has been replaced, then bail.
5154 if (mTransactionReplaced)
5155 return NS_OK;
5156
5157 if (mUpgradeProtocolCallback && stickyConn &&
5158 mResponseHead && mResponseHead->Status() == 101) {
5159 gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn,
5160 mUpgradeProtocolCallback);
5161 }
5162 }
5163
5164 mIsPending = false;
5165
5166 // if needed, check cache entry has all data we expect
5167 if (mCacheEntry && mCachePump &&
5168 mConcurentCacheAccess && contentComplete) {
5169 int64_t size, contentLength;
5170 nsresult rv = CheckPartial(mCacheEntry, &size, &contentLength);
5171 if (NS_SUCCEEDED(rv)) {
5172 if (size == int64_t(-1)) {
5173 // mayhemer TODO - we have to restart read from cache here at the size offset
5174 MOZ_ASSERT(false);
5175 LOG((" cache entry write is still in progress, but we just "
5176 "finished reading the cache entry"));
5177 }
5178 else if (contentLength != int64_t(-1) && contentLength != size) {
5179 LOG((" concurrent cache entry write has been interrupted"));
5180 mCachedResponseHead = mResponseHead;
5181 rv = MaybeSetupByteRangeRequest(size, contentLength);
5182 if (NS_SUCCEEDED(rv) && mIsPartialRequest) {
5183 // Prevent read from cache again
5184 mCachedContentIsValid = 0;
5185 mCachedContentIsPartial = 1;
5186
5187 // Perform the range request
5188 rv = ContinueConnect();
5189 if (NS_SUCCEEDED(rv)) {
5190 LOG((" performing range request"));
5191 mCachePump = nullptr;
5192 return NS_OK;
5193 } else {
5194 LOG((" but range request perform failed 0x%08x", rv));
5195 status = NS_ERROR_NET_INTERRUPT;
5196 }
5197 }
5198 else {
5199 LOG((" but range request setup failed rv=0x%08x, failing load", rv));
5200 }
5201 }
5202 }
5203 }
5204
5205 mStatus = status;
5206
5207 // perform any final cache operations before we close the cache entry.
5208 if (mCacheEntry && mRequestTimeInitialized) {
5209 bool writeAccess;
5210 // New implementation just returns value of the !mCacheEntryIsReadOnly flag passed in.
5211 // Old implementation checks on nsICache::ACCESS_WRITE flag.
5212 mCacheEntry->HasWriteAccess(!mCacheEntryIsReadOnly, &writeAccess);
5213 if (writeAccess) {
5214 FinalizeCacheEntry();
5215 }
5216 }
5217
5218 // Register entry to the Performance resource timing
5219 nsPerformance* documentPerformance = GetPerformance();
5220 if (documentPerformance) {
5221 documentPerformance->AddEntry(this, this);
5222 }
5223
5224 if (mListener) {
5225 LOG((" calling OnStopRequest\n"));
5226 mListener->OnStopRequest(this, mListenerContext, status);
5227 }
5228
5229 MOZ_EVENT_TRACER_DONE(this, "net::http::channel");
5230
5231 CloseCacheEntry(!contentComplete);
5232
5233 if (mOfflineCacheEntry)
5234 CloseOfflineCacheEntry();
5235
5236 if (mLoadGroup)
5237 mLoadGroup->RemoveRequest(this, nullptr, status);
5238
5239 // We don't need this info anymore
5240 CleanRedirectCacheChainIfNecessary();
5241
5242 ReleaseListeners();
5243
5244 return NS_OK;
5245 }
5246
5247 //-----------------------------------------------------------------------------
5248 // nsHttpChannel::nsIStreamListener
5249 //-----------------------------------------------------------------------------
5250
5251 class OnTransportStatusAsyncEvent : public nsRunnable
5252 {
5253 public:
5254 OnTransportStatusAsyncEvent(nsITransportEventSink* aEventSink,
5255 nsresult aTransportStatus,
5256 uint64_t aProgress,
5257 uint64_t aProgressMax)
5258 : mEventSink(aEventSink)
5259 , mTransportStatus(aTransportStatus)
5260 , mProgress(aProgress)
5261 , mProgressMax(aProgressMax)
5262 {
5263 MOZ_ASSERT(!NS_IsMainThread(), "Shouldn't be created on main thread");
5264 }
5265
5266 NS_IMETHOD Run()
5267 {
5268 MOZ_ASSERT(NS_IsMainThread(), "Should run on main thread");
5269 if (mEventSink) {
5270 mEventSink->OnTransportStatus(nullptr, mTransportStatus,
5271 mProgress, mProgressMax);
5272 }
5273 return NS_OK;
5274 }
5275 private:
5276 nsCOMPtr<nsITransportEventSink> mEventSink;
5277 nsresult mTransportStatus;
5278 uint64_t mProgress;
5279 uint64_t mProgressMax;
5280 };
5281
5282 NS_IMETHODIMP
5283 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
5284 nsIInputStream *input,
5285 uint64_t offset, uint32_t count)
5286 {
5287 PROFILER_LABEL("network", "nsHttpChannel::OnDataAvailable");
5288 LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%llu count=%u]\n",
5289 this, request, offset, count));
5290
5291 // don't send out OnDataAvailable notifications if we've been canceled.
5292 if (mCanceled)
5293 return mStatus;
5294
5295 MOZ_ASSERT(mResponseHead, "No response head in ODA!!");
5296
5297 MOZ_ASSERT(!(mCachedContentIsPartial && (request == mTransactionPump)),
5298 "transaction pump not suspended");
5299
5300 if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
5301 uint32_t n;
5302 return input->ReadSegments(NS_DiscardSegment, nullptr, count, &n);
5303 }
5304
5305 if (mListener) {
5306 //
5307 // synthesize transport progress event. we do this here since we want
5308 // to delay OnProgress events until we start streaming data. this is
5309 // crucially important since it impacts the lock icon (see bug 240053).
5310 //
5311 nsresult transportStatus;
5312 if (request == mCachePump)
5313 transportStatus = NS_NET_STATUS_READING;
5314 else
5315 transportStatus = NS_NET_STATUS_RECEIVING_FROM;
5316
5317 // mResponseHead may reference new or cached headers, but either way it
5318 // holds our best estimate of the total content length. Even in the case
5319 // of a byte range request, the content length stored in the cached
5320 // response headers is what we want to use here.
5321
5322 uint64_t progressMax(uint64_t(mResponseHead->ContentLength()));
5323 uint64_t progress = mLogicalOffset + uint64_t(count);
5324
5325 if (progress > progressMax)
5326 NS_WARNING("unexpected progress values - "
5327 "is server exceeding content length?");
5328
5329 if (NS_IsMainThread()) {
5330 OnTransportStatus(nullptr, transportStatus, progress, progressMax);
5331 } else {
5332 nsresult rv = NS_DispatchToMainThread(
5333 new OnTransportStatusAsyncEvent(this, transportStatus,
5334 progress, progressMax));
5335 NS_ENSURE_SUCCESS(rv, rv);
5336 }
5337
5338 //
5339 // we have to manually keep the logical offset of the stream up-to-date.
5340 // we cannot depend solely on the offset provided, since we may have
5341 // already streamed some data from another source (see, for example,
5342 // OnDoneReadingPartialCacheEntry).
5343 //
5344 if (!mLogicalOffset)
5345 MOZ_EVENT_TRACER_EXEC(this, "net::http::channel");
5346
5347 int64_t offsetBefore = 0;
5348 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(input);
5349 if (seekable && NS_FAILED(seekable->Tell(&offsetBefore))) {
5350 seekable = nullptr;
5351 }
5352
5353 nsresult rv = mListener->OnDataAvailable(this,
5354 mListenerContext,
5355 input,
5356 mLogicalOffset,
5357 count);
5358 if (NS_SUCCEEDED(rv)) {
5359 // by contract mListener must read all of "count" bytes, but
5360 // nsInputStreamPump is tolerant to seekable streams that violate that
5361 // and it will redeliver incompletely read data. So we need to do
5362 // the same thing when updating the progress counter to stay in sync.
5363 int64_t offsetAfter, delta;
5364 if (seekable && NS_SUCCEEDED(seekable->Tell(&offsetAfter))) {
5365 delta = offsetAfter - offsetBefore;
5366 if (delta != count) {
5367 count = delta;
5368
5369 NS_WARNING("Listener OnDataAvailable contract violation");
5370 nsCOMPtr<nsIConsoleService> consoleService =
5371 do_GetService(NS_CONSOLESERVICE_CONTRACTID);
5372 nsAutoString message
5373 (NS_LITERAL_STRING(
5374 "http channel Listener OnDataAvailable contract violation"));
5375 if (consoleService) {
5376 consoleService->LogStringMessage(message.get());
5377 }
5378 }
5379 }
5380 mLogicalOffset += count;
5381 }
5382
5383 return rv;
5384 }
5385
5386 return NS_ERROR_ABORT;
5387 }
5388
5389 //-----------------------------------------------------------------------------
5390 // nsHttpChannel::nsIThreadRetargetableRequest
5391 //-----------------------------------------------------------------------------
5392
5393 NS_IMETHODIMP
5394 nsHttpChannel::RetargetDeliveryTo(nsIEventTarget* aNewTarget)
5395 {
5396 MOZ_ASSERT(NS_IsMainThread(), "Should be called on main thread only");
5397
5398 NS_ENSURE_ARG(aNewTarget);
5399 if (aNewTarget == NS_GetCurrentThread()) {
5400 NS_WARNING("Retargeting delivery to same thread");
5401 return NS_OK;
5402 }
5403 NS_ENSURE_TRUE(mTransactionPump || mCachePump, NS_ERROR_NOT_AVAILABLE);
5404
5405 nsresult rv = NS_OK;
5406 // If both cache pump and transaction pump exist, we're probably dealing
5407 // with partially cached content. So, we must be able to retarget both.
5408 nsCOMPtr<nsIThreadRetargetableRequest> retargetableCachePump;
5409 nsCOMPtr<nsIThreadRetargetableRequest> retargetableTransactionPump;
5410 if (mCachePump) {
5411 retargetableCachePump = do_QueryObject(mCachePump);
5412 // nsInputStreamPump should implement this interface.
5413 MOZ_ASSERT(retargetableCachePump);
5414 rv = retargetableCachePump->RetargetDeliveryTo(aNewTarget);
5415 }
5416 if (NS_SUCCEEDED(rv) && mTransactionPump) {
5417 retargetableTransactionPump = do_QueryObject(mTransactionPump);
5418 // nsInputStreamPump should implement this interface.
5419 MOZ_ASSERT(retargetableTransactionPump);
5420 rv = retargetableTransactionPump->RetargetDeliveryTo(aNewTarget);
5421
5422 // If retarget fails for transaction pump, we must restore mCachePump.
5423 if (NS_FAILED(rv) && retargetableCachePump) {
5424 nsCOMPtr<nsIThread> mainThread;
5425 rv = NS_GetMainThread(getter_AddRefs(mainThread));
5426 NS_ENSURE_SUCCESS(rv, rv);
5427 rv = retargetableCachePump->RetargetDeliveryTo(mainThread);
5428 }
5429 }
5430 return rv;
5431 }
5432
5433 //-----------------------------------------------------------------------------
5434 // nsHttpChannel::nsThreadRetargetableStreamListener
5435 //-----------------------------------------------------------------------------
5436
5437 NS_IMETHODIMP
5438 nsHttpChannel::CheckListenerChain()
5439 {
5440 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread!");
5441 nsresult rv = NS_OK;
5442 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener =
5443 do_QueryInterface(mListener, &rv);
5444 if (retargetableListener) {
5445 rv = retargetableListener->CheckListenerChain();
5446 }
5447 return rv;
5448 }
5449
5450 //-----------------------------------------------------------------------------
5451 // nsHttpChannel::nsITransportEventSink
5452 //-----------------------------------------------------------------------------
5453
5454 NS_IMETHODIMP
5455 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
5456 uint64_t progress, uint64_t progressMax)
5457 {
5458 MOZ_ASSERT(NS_IsMainThread(), "Should be on main thread only");
5459 // cache the progress sink so we don't have to query for it each time.
5460 if (!mProgressSink)
5461 GetCallback(mProgressSink);
5462
5463 if (status == NS_NET_STATUS_CONNECTED_TO ||
5464 status == NS_NET_STATUS_WAITING_FOR) {
5465 nsCOMPtr<nsISocketTransport> socketTransport =
5466 do_QueryInterface(trans);
5467 if (socketTransport) {
5468 socketTransport->GetSelfAddr(&mSelfAddr);
5469 socketTransport->GetPeerAddr(&mPeerAddr);
5470 }
5471 }
5472
5473 // block socket status event after Cancel or OnStopRequest has been called.
5474 if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
5475 LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n",
5476 this, status, progress, progressMax));
5477
5478 nsAutoCString host;
5479 mURI->GetHost(host);
5480 mProgressSink->OnStatus(this, nullptr, status,
5481 NS_ConvertUTF8toUTF16(host).get());
5482
5483 if (progress > 0) {
5484 MOZ_ASSERT(progress <= progressMax, "unexpected progress values");
5485 // Try to get mProgressSink if it was nulled out during OnStatus.
5486 if (!mProgressSink) {
5487 GetCallback(mProgressSink);
5488 }
5489 if (mProgressSink) {
5490 mProgressSink->OnProgress(this, nullptr, progress, progressMax);
5491 }
5492 }
5493 }
5494 #ifdef DEBUG
5495 else
5496 LOG(("skipping status notification [this=%p sink=%p pending=%u background=%x]\n",
5497 this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
5498 #endif
5499
5500 return NS_OK;
5501 }
5502
5503 //-----------------------------------------------------------------------------
5504 // nsHttpChannel::nsICacheInfoChannel
5505 //-----------------------------------------------------------------------------
5506
5507 NS_IMETHODIMP
5508 nsHttpChannel::IsFromCache(bool *value)
5509 {
5510 if (!mIsPending)
5511 return NS_ERROR_NOT_AVAILABLE;
5512
5513 // return false if reading a partial cache entry; the data isn't entirely
5514 // from the cache!
5515
5516 *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
5517 mCachedContentIsValid && !mCachedContentIsPartial;
5518
5519 return NS_OK;
5520 }
5521
5522 NS_IMETHODIMP
5523 nsHttpChannel::GetCacheTokenExpirationTime(uint32_t *_retval)
5524 {
5525 NS_ENSURE_ARG_POINTER(_retval);
5526 if (!mCacheEntry)
5527 return NS_ERROR_NOT_AVAILABLE;
5528
5529 return mCacheEntry->GetExpirationTime(_retval);
5530 }
5531
5532 NS_IMETHODIMP
5533 nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval)
5534 {
5535 nsresult rv;
5536
5537 if (!mCacheEntry)
5538 return NS_ERROR_NOT_AVAILABLE;
5539
5540 nsXPIDLCString cachedCharset;
5541 rv = mCacheEntry->GetMetaDataElement("charset",
5542 getter_Copies(cachedCharset));
5543 if (NS_SUCCEEDED(rv))
5544 _retval = cachedCharset;
5545
5546 return rv;
5547 }
5548
5549 NS_IMETHODIMP
5550 nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset)
5551 {
5552 if (!mCacheEntry)
5553 return NS_ERROR_NOT_AVAILABLE;
5554
5555 return mCacheEntry->SetMetaDataElement("charset",
5556 PromiseFlatCString(aCharset).get());
5557 }
5558
5559
5560 NS_IMETHODIMP
5561 nsHttpChannel::GetCacheDomain(nsACString &value)
5562 {
5563 value = mCacheDomain;
5564
5565 return NS_OK;
5566 }
5567
5568 NS_IMETHODIMP
5569 nsHttpChannel::SetCacheDomain(const nsACString &value)
5570 {
5571 mCacheDomain = value;
5572
5573 return NS_OK;
5574 }
5575
5576 //-----------------------------------------------------------------------------
5577 // nsHttpChannel::nsICachingChannel
5578 //-----------------------------------------------------------------------------
5579
5580 NS_IMETHODIMP
5581 nsHttpChannel::GetCacheToken(nsISupports **token)
5582 {
5583 NS_ENSURE_ARG_POINTER(token);
5584 if (!mCacheEntry)
5585 return NS_ERROR_NOT_AVAILABLE;
5586 return CallQueryInterface(mCacheEntry, token);
5587 }
5588
5589 NS_IMETHODIMP
5590 nsHttpChannel::SetCacheToken(nsISupports *token)
5591 {
5592 return NS_ERROR_NOT_IMPLEMENTED;
5593 }
5594
5595 NS_IMETHODIMP
5596 nsHttpChannel::GetOfflineCacheToken(nsISupports **token)
5597 {
5598 NS_ENSURE_ARG_POINTER(token);
5599 if (!mOfflineCacheEntry)
5600 return NS_ERROR_NOT_AVAILABLE;
5601 return CallQueryInterface(mOfflineCacheEntry, token);
5602 }
5603
5604 NS_IMETHODIMP
5605 nsHttpChannel::SetOfflineCacheToken(nsISupports *token)
5606 {
5607 return NS_ERROR_NOT_IMPLEMENTED;
5608 }
5609
5610 class nsHttpChannelCacheKey MOZ_FINAL : public nsISupportsPRUint32,
5611 public nsISupportsCString
5612 {
5613 NS_DECL_ISUPPORTS
5614
5615 NS_DECL_NSISUPPORTSPRIMITIVE
5616 NS_FORWARD_NSISUPPORTSPRUINT32(mSupportsPRUint32->)
5617
5618 // Both interfaces declares toString method with the same signature.
5619 // Thus we have to delegate only to nsISupportsPRUint32 implementation.
5620 NS_IMETHOD GetData(nsACString & aData)
5621 {
5622 return mSupportsCString->GetData(aData);
5623 }
5624 NS_IMETHOD SetData(const nsACString & aData)
5625 {
5626 return mSupportsCString->SetData(aData);
5627 }
5628
5629 public:
5630 nsresult SetData(uint32_t aPostID, const nsACString& aKey);
5631
5632 protected:
5633 nsCOMPtr<nsISupportsPRUint32> mSupportsPRUint32;
5634 nsCOMPtr<nsISupportsCString> mSupportsCString;
5635 };
5636
5637 NS_IMPL_ADDREF(nsHttpChannelCacheKey)
5638 NS_IMPL_RELEASE(nsHttpChannelCacheKey)
5639 NS_INTERFACE_TABLE_HEAD(nsHttpChannelCacheKey)
5640 NS_INTERFACE_TABLE_BEGIN
5641 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
5642 nsISupports, nsISupportsPRUint32)
5643 NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
5644 nsISupportsPrimitive, nsISupportsPRUint32)
5645 NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
5646 nsISupportsPRUint32)
5647 NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
5648 nsISupportsCString)
5649 NS_INTERFACE_TABLE_END
5650 NS_INTERFACE_TABLE_TAIL
5651
5652 NS_IMETHODIMP nsHttpChannelCacheKey::GetType(uint16_t *aType)
5653 {
5654 NS_ENSURE_ARG_POINTER(aType);
5655
5656 *aType = TYPE_PRUINT32;
5657 return NS_OK;
5658 }
5659
5660 nsresult nsHttpChannelCacheKey::SetData(uint32_t aPostID,
5661 const nsACString& aKey)
5662 {
5663 nsresult rv;
5664
5665 mSupportsCString =
5666 do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
5667 if (NS_FAILED(rv)) return rv;
5668
5669 mSupportsCString->SetData(aKey);
5670 if (NS_FAILED(rv)) return rv;
5671
5672 mSupportsPRUint32 =
5673 do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
5674 if (NS_FAILED(rv)) return rv;
5675
5676 mSupportsPRUint32->SetData(aPostID);
5677 if (NS_FAILED(rv)) return rv;
5678
5679 return NS_OK;
5680 }
5681
5682 NS_IMETHODIMP
5683 nsHttpChannel::GetCacheKey(nsISupports **key)
5684 {
5685 // mayhemer: TODO - do we need this API?
5686
5687 nsresult rv;
5688 NS_ENSURE_ARG_POINTER(key);
5689
5690 LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
5691
5692 *key = nullptr;
5693
5694 nsRefPtr<nsHttpChannelCacheKey> container =
5695 new nsHttpChannelCacheKey();
5696
5697 if (!container)
5698 return NS_ERROR_OUT_OF_MEMORY;
5699
5700 nsAutoCString cacheKey;
5701 rv = GenerateCacheKey(mPostID, cacheKey);
5702 if (NS_FAILED(rv)) return rv;
5703
5704 rv = container->SetData(mPostID, cacheKey);
5705 if (NS_FAILED(rv)) return rv;
5706
5707 return CallQueryInterface(container.get(), key);
5708 }
5709
5710 NS_IMETHODIMP
5711 nsHttpChannel::SetCacheKey(nsISupports *key)
5712 {
5713 nsresult rv;
5714
5715 LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
5716
5717 ENSURE_CALLED_BEFORE_CONNECT();
5718
5719 if (!key)
5720 mPostID = 0;
5721 else {
5722 // extract the post id
5723 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
5724 if (NS_FAILED(rv)) return rv;
5725
5726 rv = container->GetData(&mPostID);
5727 if (NS_FAILED(rv)) return rv;
5728 }
5729 return NS_OK;
5730 }
5731
5732 //-----------------------------------------------------------------------------
5733 // nsHttpChannel::nsIResumableChannel
5734 //-----------------------------------------------------------------------------
5735
5736 NS_IMETHODIMP
5737 nsHttpChannel::ResumeAt(uint64_t aStartPos,
5738 const nsACString& aEntityID)
5739 {
5740 LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n",
5741 this, aStartPos, PromiseFlatCString(aEntityID).get()));
5742 mEntityID = aEntityID;
5743 mStartPos = aStartPos;
5744 mResuming = true;
5745 return NS_OK;
5746 }
5747
5748 nsresult
5749 nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
5750 {
5751 LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
5752
5753 MOZ_ASSERT(!mTransaction, "should not have a transaction");
5754 nsresult rv;
5755
5756 // toggle mIsPending to allow nsIObserver implementations to modify
5757 // the request headers (bug 95044).
5758 mIsPending = false;
5759
5760 // fetch cookies, and add them to the request header.
5761 // the server response could have included cookies that must be sent with
5762 // this authentication attempt (bug 84794).
5763 // TODO: save cookies from auth response and send them here (bug 572151).
5764 AddCookiesToRequest();
5765
5766 // notify "http-on-modify-request" observers
5767 CallOnModifyRequestObservers();
5768
5769 mIsPending = true;
5770
5771 // get rid of the old response headers
5772 mResponseHead = nullptr;
5773
5774 // rewind the upload stream
5775 if (mUploadStream) {
5776 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
5777 if (seekable)
5778 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
5779 }
5780
5781 // set sticky connection flag and disable pipelining.
5782 mCaps |= NS_HTTP_STICKY_CONNECTION;
5783 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
5784
5785 // and create a new one...
5786 rv = SetupTransaction();
5787 if (NS_FAILED(rv)) return rv;
5788
5789 // transfer ownership of connection to transaction
5790 if (conn)
5791 mTransaction->SetConnection(conn);
5792
5793 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
5794 if (NS_FAILED(rv)) return rv;
5795
5796 rv = mTransactionPump->AsyncRead(this, nullptr);
5797 if (NS_FAILED(rv)) return rv;
5798
5799 uint32_t suspendCount = mSuspendCount;
5800 while (suspendCount--)
5801 mTransactionPump->Suspend();
5802
5803 return NS_OK;
5804 }
5805
5806 //-----------------------------------------------------------------------------
5807 // nsHttpChannel::nsIApplicationCacheChannel
5808 //-----------------------------------------------------------------------------
5809
5810 NS_IMETHODIMP
5811 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
5812 {
5813 NS_IF_ADDREF(*out = mApplicationCache);
5814 return NS_OK;
5815 }
5816
5817 NS_IMETHODIMP
5818 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
5819 {
5820 ENSURE_CALLED_BEFORE_CONNECT();
5821
5822 mApplicationCache = appCache;
5823 return NS_OK;
5824 }
5825
5826 NS_IMETHODIMP
5827 nsHttpChannel::GetApplicationCacheForWrite(nsIApplicationCache **out)
5828 {
5829 NS_IF_ADDREF(*out = mApplicationCacheForWrite);
5830 return NS_OK;
5831 }
5832
5833 NS_IMETHODIMP
5834 nsHttpChannel::SetApplicationCacheForWrite(nsIApplicationCache *appCache)
5835 {
5836 ENSURE_CALLED_BEFORE_CONNECT();
5837
5838 mApplicationCacheForWrite = appCache;
5839 return NS_OK;
5840 }
5841
5842 NS_IMETHODIMP
5843 nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
5844 {
5845 *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
5846 return NS_OK;
5847 }
5848
5849 NS_IMETHODIMP
5850 nsHttpChannel::GetInheritApplicationCache(bool *aInherit)
5851 {
5852 *aInherit = mInheritApplicationCache;
5853 return NS_OK;
5854 }
5855
5856 NS_IMETHODIMP
5857 nsHttpChannel::SetInheritApplicationCache(bool aInherit)
5858 {
5859 ENSURE_CALLED_BEFORE_CONNECT();
5860
5861 mInheritApplicationCache = aInherit;
5862 return NS_OK;
5863 }
5864
5865 NS_IMETHODIMP
5866 nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
5867 {
5868 *aChoose = mChooseApplicationCache;
5869 return NS_OK;
5870 }
5871
5872 NS_IMETHODIMP
5873 nsHttpChannel::SetChooseApplicationCache(bool aChoose)
5874 {
5875 ENSURE_CALLED_BEFORE_CONNECT();
5876
5877 mChooseApplicationCache = aChoose;
5878 return NS_OK;
5879 }
5880
5881 nsHttpChannel::OfflineCacheEntryAsForeignMarker*
5882 nsHttpChannel::GetOfflineCacheEntryAsForeignMarker()
5883 {
5884 if (!mApplicationCache)
5885 return nullptr;
5886
5887 return new OfflineCacheEntryAsForeignMarker(mApplicationCache, mURI);
5888 }
5889
5890 nsresult
5891 nsHttpChannel::OfflineCacheEntryAsForeignMarker::MarkAsForeign()
5892 {
5893 nsresult rv;
5894
5895 nsCOMPtr<nsIURI> noRefURI;
5896 rv = mCacheURI->CloneIgnoringRef(getter_AddRefs(noRefURI));
5897 NS_ENSURE_SUCCESS(rv, rv);
5898
5899 nsAutoCString spec;
5900 rv = noRefURI->GetAsciiSpec(spec);
5901 NS_ENSURE_SUCCESS(rv, rv);
5902
5903 return mApplicationCache->MarkEntry(spec,
5904 nsIApplicationCache::ITEM_FOREIGN);
5905 }
5906
5907 NS_IMETHODIMP
5908 nsHttpChannel::MarkOfflineCacheEntryAsForeign()
5909 {
5910 nsresult rv;
5911
5912 nsAutoPtr<OfflineCacheEntryAsForeignMarker> marker(
5913 GetOfflineCacheEntryAsForeignMarker());
5914
5915 if (!marker)
5916 return NS_ERROR_NOT_AVAILABLE;
5917
5918 rv = marker->MarkAsForeign();
5919 NS_ENSURE_SUCCESS(rv, rv);
5920
5921 return NS_OK;
5922 }
5923
5924 //-----------------------------------------------------------------------------
5925 // nsHttpChannel::nsIAsyncVerifyRedirectCallback
5926 //-----------------------------------------------------------------------------
5927
5928 nsresult
5929 nsHttpChannel::WaitForRedirectCallback()
5930 {
5931 nsresult rv;
5932 LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
5933
5934 if (mTransactionPump) {
5935 rv = mTransactionPump->Suspend();
5936 NS_ENSURE_SUCCESS(rv, rv);
5937 }
5938 if (mCachePump) {
5939 rv = mCachePump->Suspend();
5940 if (NS_FAILED(rv) && mTransactionPump) {
5941 #ifdef DEBUG
5942 nsresult resume =
5943 #endif
5944 mTransactionPump->Resume();
5945 MOZ_ASSERT(NS_SUCCEEDED(resume),
5946 "Failed to resume transaction pump");
5947 }
5948 NS_ENSURE_SUCCESS(rv, rv);
5949 }
5950
5951 mWaitingForRedirectCallback = true;
5952 return NS_OK;
5953 }
5954
5955 NS_IMETHODIMP
5956 nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
5957 {
5958 LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
5959 "result=%x stack=%d mWaitingForRedirectCallback=%u\n",
5960 this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback));
5961 MOZ_ASSERT(mWaitingForRedirectCallback,
5962 "Someone forgot to call WaitForRedirectCallback() ?!");
5963 mWaitingForRedirectCallback = false;
5964
5965 if (mCanceled && NS_SUCCEEDED(result))
5966 result = NS_BINDING_ABORTED;
5967
5968 for (uint32_t i = mRedirectFuncStack.Length(); i > 0;) {
5969 --i;
5970 // Pop the last function pushed to the stack
5971 nsContinueRedirectionFunc func = mRedirectFuncStack[i];
5972 mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
5973
5974 // Call it with the result we got from the callback or the deeper
5975 // function call.
5976 result = (this->*func)(result);
5977
5978 // If a new function has been pushed to the stack and placed us in the
5979 // waiting state, we need to break the chain and wait for the callback
5980 // again.
5981 if (mWaitingForRedirectCallback)
5982 break;
5983 }
5984
5985 if (NS_FAILED(result) && !mCanceled) {
5986 // First, cancel this channel if we are in failure state to set mStatus
5987 // and let it be propagated to pumps.
5988 Cancel(result);
5989 }
5990
5991 if (!mWaitingForRedirectCallback) {
5992 // We are not waiting for the callback. At this moment we must release
5993 // reference to the redirect target channel, otherwise we may leak.
5994 mRedirectChannel = nullptr;
5995 MOZ_EVENT_TRACER_DONE(this, "net::http::channel");
5996 }
5997
5998 // We always resume the pumps here. If all functions on stack have been
5999 // called we need OnStopRequest to be triggered, and if we broke out of the
6000 // loop above (and are thus waiting for a new callback) the suspension
6001 // count must be balanced in the pumps.
6002 if (mTransactionPump)
6003 mTransactionPump->Resume();
6004 if (mCachePump)
6005 mCachePump->Resume();
6006
6007 return result;
6008 }
6009
6010 void
6011 nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func)
6012 {
6013 mRedirectFuncStack.AppendElement(func);
6014 }
6015
6016 void
6017 nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func)
6018 {
6019 MOZ_ASSERT(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
6020 "Trying to pop wrong method from redirect async stack!");
6021
6022 mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
6023 }
6024
6025 //-----------------------------------------------------------------------------
6026 // nsIDNSListener functions
6027 //-----------------------------------------------------------------------------
6028
6029 NS_IMETHODIMP
6030 nsHttpChannel::OnLookupComplete(nsICancelable *request,
6031 nsIDNSRecord *rec,
6032 nsresult status)
6033 {
6034 MOZ_ASSERT(NS_IsMainThread(), "Expecting DNS callback on main thread.");
6035
6036 LOG(("nsHttpChannel::OnLookupComplete [this=%p] prefetch complete%s: "
6037 "%s status[0x%x]\n",
6038 this, mCaps & NS_HTTP_REFRESH_DNS ? ", refresh requested" : "",
6039 NS_SUCCEEDED(status) ? "success" : "failure", status));
6040
6041 // We no longer need the dns prefetch object. Note: mDNSPrefetch could be
6042 // validly null if OnStopRequest has already been called.
6043 if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) {
6044 mTransactionTimings.domainLookupStart =
6045 mDNSPrefetch->StartTimestamp();
6046 mTransactionTimings.domainLookupEnd =
6047 mDNSPrefetch->EndTimestamp();
6048 }
6049 mDNSPrefetch = nullptr;
6050
6051 // Unset DNS cache refresh if it was requested,
6052 if (mCaps & NS_HTTP_REFRESH_DNS) {
6053 mCaps &= ~NS_HTTP_REFRESH_DNS;
6054 if (mTransaction) {
6055 mTransaction->SetDNSWasRefreshed();
6056 }
6057 }
6058
6059 return NS_OK;
6060 }
6061
6062 //-----------------------------------------------------------------------------
6063 // nsHttpChannel internal functions
6064 //-----------------------------------------------------------------------------
6065
6066 void
6067 nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
6068 {
6069 // See RFC 2616 section 5.1.1. These are considered valid
6070 // methods which DO NOT invalidate cache-entries for the
6071 // referred resource. POST, PUT and DELETE as well as any
6072 // other method not listed here will potentially invalidate
6073 // any cached copy of the resource
6074 if (mRequestHead.IsGet() || mRequestHead.IsOptions() ||
6075 mRequestHead.IsHead() || mRequestHead.IsTrace() ||
6076 mRequestHead.IsConnect()) {
6077 return;
6078 }
6079
6080 // Invalidate the request-uri.
6081 #ifdef PR_LOGGING
6082 nsAutoCString key;
6083 mURI->GetAsciiSpec(key);
6084 LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n",
6085 this, key.get()));
6086 #endif
6087
6088 DoInvalidateCacheEntry(mURI);
6089
6090 // Invalidate Location-header if set
6091 const char *location = mResponseHead->PeekHeader(nsHttp::Location);
6092 if (location) {
6093 LOG((" Location-header=%s\n", location));
6094 InvalidateCacheEntryForLocation(location);
6095 }
6096
6097 // Invalidate Content-Location-header if set
6098 location = mResponseHead->PeekHeader(nsHttp::Content_Location);
6099 if (location) {
6100 LOG((" Content-Location-header=%s\n", location));
6101 InvalidateCacheEntryForLocation(location);
6102 }
6103 }
6104
6105 void
6106 nsHttpChannel::InvalidateCacheEntryForLocation(const char *location)
6107 {
6108 nsAutoCString tmpCacheKey, tmpSpec;
6109 nsCOMPtr<nsIURI> resultingURI;
6110 nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
6111 if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
6112 DoInvalidateCacheEntry(resultingURI);
6113 } else {
6114 LOG((" hosts not matching\n"));
6115 }
6116 }
6117
6118 void
6119 nsHttpChannel::DoInvalidateCacheEntry(nsIURI* aURI)
6120 {
6121 // NOTE:
6122 // Following comments 24,32 and 33 in bug #327765, we only care about
6123 // the cache in the protocol-handler, not the application cache.
6124 // The logic below deviates from the original logic in OpenCacheEntry on
6125 // one point by using only READ_ONLY access-policy. I think this is safe.
6126
6127 nsresult rv;
6128
6129 #ifdef PR_LOGGING
6130 nsAutoCString key;
6131 aURI->GetAsciiSpec(key);
6132 LOG(("DoInvalidateCacheEntry [channel=%p key=%s]", this, key.get()));
6133 #endif
6134
6135 nsCOMPtr<nsICacheStorageService> cacheStorageService =
6136 do_GetService("@mozilla.org/netwerk/cache-storage-service;1", &rv);
6137
6138 nsCOMPtr<nsICacheStorage> cacheStorage;
6139 if (NS_SUCCEEDED(rv)) {
6140 nsRefPtr<LoadContextInfo> info = GetLoadContextInfo(this);
6141 rv = cacheStorageService->DiskCacheStorage(info, false, getter_AddRefs(cacheStorage));
6142 }
6143
6144 if (NS_SUCCEEDED(rv)) {
6145 rv = cacheStorage->AsyncDoomURI(aURI, EmptyCString(), nullptr);
6146 }
6147
6148 LOG(("DoInvalidateCacheEntry [channel=%p key=%s rv=%d]", this, key.get(), int(rv)));
6149 }
6150
6151 void
6152 nsHttpChannel::AsyncOnExamineCachedResponse()
6153 {
6154 gHttpHandler->OnExamineCachedResponse(this);
6155
6156 }
6157
6158 void
6159 nsHttpChannel::UpdateAggregateCallbacks()
6160 {
6161 if (!mTransaction) {
6162 return;
6163 }
6164 nsCOMPtr<nsIInterfaceRequestor> callbacks;
6165 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
6166 NS_GetCurrentThread(),
6167 getter_AddRefs(callbacks));
6168 mTransaction->SetSecurityCallbacks(callbacks);
6169 }
6170
6171 nsIPrincipal *
6172 nsHttpChannel::GetPrincipal()
6173 {
6174 if (mPrincipal)
6175 return mPrincipal;
6176
6177 nsIScriptSecurityManager *securityManager =
6178 nsContentUtils::GetSecurityManager();
6179
6180 if (!securityManager)
6181 return nullptr;
6182
6183 securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal));
6184 if (!mPrincipal)
6185 return nullptr;
6186
6187 // principals with unknown app ids do not work with the permission manager
6188 if (mPrincipal->GetUnknownAppId())
6189 mPrincipal = nullptr;
6190
6191 return mPrincipal;
6192 }
6193
6194
6195 NS_IMETHODIMP
6196 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
6197 {
6198 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
6199
6200 nsresult rv = HttpBaseChannel::SetLoadGroup(aLoadGroup);
6201 if (NS_SUCCEEDED(rv)) {
6202 UpdateAggregateCallbacks();
6203 }
6204 return rv;
6205 }
6206
6207 NS_IMETHODIMP
6208 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
6209 {
6210 MOZ_ASSERT(NS_IsMainThread(), "Wrong thread.");
6211
6212 nsresult rv = HttpBaseChannel::SetNotificationCallbacks(aCallbacks);
6213 if (NS_SUCCEEDED(rv)) {
6214 UpdateAggregateCallbacks();
6215 }
6216 return rv;
6217 }
6218
6219 nsPerformance*
6220 nsHttpChannel::GetPerformance()
6221 {
6222 // If performance timing is disabled, there is no need for the nsPerformance
6223 // object anymore.
6224 if (!mTimingEnabled) {
6225 return nullptr;
6226 }
6227 nsCOMPtr<nsILoadContext> loadContext;
6228 NS_QueryNotificationCallbacks(this, loadContext);
6229 if (!loadContext) {
6230 return nullptr;
6231 }
6232 nsCOMPtr<nsIDOMWindow> domWindow;
6233 loadContext->GetAssociatedWindow(getter_AddRefs(domWindow));
6234 if (!domWindow) {
6235 return nullptr;
6236 }
6237 nsCOMPtr<nsPIDOMWindow> pDomWindow = do_QueryInterface(domWindow);
6238 if (!pDomWindow) {
6239 return nullptr;
6240 }
6241 if (!pDomWindow->IsInnerWindow()) {
6242 pDomWindow = pDomWindow->GetCurrentInnerWindow();
6243 if (!pDomWindow) {
6244 return nullptr;
6245 }
6246 }
6247
6248 nsPerformance* docPerformance = pDomWindow->GetPerformance();
6249 if (!docPerformance) {
6250 return nullptr;
6251 }
6252 // iframes should be added to the parent's entries list.
6253 if (mLoadFlags & LOAD_DOCUMENT_URI) {
6254 return docPerformance->GetParentPerformance();
6255 }
6256 return docPerformance;
6257 }
6258
6259 void
6260 nsHttpChannel::ForcePending(bool aForcePending)
6261 {
6262 // Set true here so IsPending will return true.
6263 // Required for callback diversion from child back to parent. In such cases
6264 // OnStopRequest can be called in the parent before callbacks are diverted
6265 // back from the child to the listener in the parent.
6266 mForcePending = aForcePending;
6267 }
6268
6269 NS_IMETHODIMP
6270 nsHttpChannel::IsPending(bool *aIsPending)
6271 {
6272 NS_ENSURE_ARG_POINTER(aIsPending);
6273 *aIsPending = mIsPending || mForcePending;
6274 return NS_OK;
6275 }
6276
6277 } } // namespace mozilla::net

mercurial