|
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, ¤tAge); |
|
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 |