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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications.
20 : * Portions created by the Initial Developer are Copyright (C) 2001
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Darin Fisher <darin@meer.net> (original author)
25 : * Christian Biesinger <cbiesinger@web.de>
26 : * Google Inc.
27 : * Jan Wrobel <wrobel@blues.ath.cx>
28 : * Jan Odvarko <odvarko@gmail.com>
29 : * Dave Camp <dcamp@mozilla.com>
30 : * Honza Bambas <honzab@firemni.cz>
31 : * Daniel Witte <dwitte@mozilla.com>
32 : * Jason Duell <jduell.mcbugs@gmail.com>
33 : *
34 : * Alternatively, the contents of this file may be used under the terms of
35 : * either the GNU General Public License Version 2 or later (the "GPL"), or
36 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
37 : * in which case the provisions of the GPL or the LGPL are applicable instead
38 : * of those above. If you wish to allow use of your version of this file only
39 : * under the terms of either the GPL or the LGPL, and not to allow others to
40 : * use your version of this file under the terms of the MPL, indicate your
41 : * decision by deleting the provisions above and replace them with the notice
42 : * and other provisions required by the GPL or the LGPL. If you do not delete
43 : * the provisions above, a recipient may use your version of this file under
44 : * the terms of any one of the MPL, the GPL or the LGPL.
45 : *
46 : * ***** END LICENSE BLOCK ***** */
47 :
48 : #include "nsHttpChannel.h"
49 : #include "nsHttpHandler.h"
50 : #include "nsIApplicationCacheService.h"
51 : #include "nsIApplicationCacheContainer.h"
52 : #include "nsIAuthInformation.h"
53 : #include "nsIStringBundle.h"
54 : #include "nsIIDNService.h"
55 : #include "nsIStreamListenerTee.h"
56 : #include "nsISeekableStream.h"
57 : #include "nsMimeTypes.h"
58 : #include "nsNetUtil.h"
59 : #include "prprf.h"
60 : #include "prnetdb.h"
61 : #include "nsEscape.h"
62 : #include "nsStreamUtils.h"
63 : #include "nsIOService.h"
64 : #include "nsICacheService.h"
65 : #include "nsDNSPrefetch.h"
66 : #include "nsChannelClassifier.h"
67 : #include "nsIRedirectResultListener.h"
68 : #include "mozilla/TimeStamp.h"
69 : #include "mozilla/Telemetry.h"
70 : #include "nsDOMError.h"
71 : #include "nsAlgorithm.h"
72 : #include "sampler.h"
73 :
74 : using namespace mozilla;
75 :
76 : // Device IDs for various cache types
77 : const char kDiskDeviceID[] = "disk";
78 : const char kMemoryDeviceID[] = "memory";
79 : const char kOfflineDeviceID[] = "offline";
80 :
81 : // True if the local cache should be bypassed when processing a request.
82 : #define BYPASS_LOCAL_CACHE(loadFlags) \
83 : (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
84 : nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
85 :
86 : static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
87 :
88 : class AutoRedirectVetoNotifier
89 : {
90 : public:
91 155 : AutoRedirectVetoNotifier(nsHttpChannel* channel) : mChannel(channel) {}
92 155 : ~AutoRedirectVetoNotifier() {ReportRedirectResult(false);}
93 140 : void RedirectSucceeded() {ReportRedirectResult(true);}
94 :
95 : private:
96 : nsHttpChannel* mChannel;
97 : void ReportRedirectResult(bool succeeded);
98 : };
99 :
100 : void
101 295 : AutoRedirectVetoNotifier::ReportRedirectResult(bool succeeded)
102 : {
103 295 : if (!mChannel)
104 140 : return;
105 :
106 155 : mChannel->mRedirectChannel = nsnull;
107 :
108 310 : nsCOMPtr<nsIRedirectResultListener> vetoHook;
109 : NS_QueryNotificationCallbacks(mChannel,
110 : NS_GET_IID(nsIRedirectResultListener),
111 155 : getter_AddRefs(vetoHook));
112 155 : mChannel = nsnull;
113 155 : if (vetoHook)
114 0 : vetoHook->OnRedirectResult(succeeded);
115 : }
116 :
117 : //-----------------------------------------------------------------------------
118 : // nsHttpChannel <public>
119 : //-----------------------------------------------------------------------------
120 :
121 3514 : nsHttpChannel::nsHttpChannel()
122 : : HttpAsyncAborter<nsHttpChannel>(this)
123 : , mLogicalOffset(0)
124 : , mCacheAccess(0)
125 : , mPostID(0)
126 : , mRequestTime(0)
127 : , mOnCacheEntryAvailableCallback(nsnull)
128 : , mAsyncCacheOpen(false)
129 : , mCachedContentIsValid(false)
130 : , mCachedContentIsPartial(false)
131 : , mTransactionReplaced(false)
132 : , mAuthRetryPending(false)
133 : , mResuming(false)
134 : , mInitedCacheEntry(false)
135 : , mCacheForOfflineUse(false)
136 : , mCachingOpportunistically(false)
137 : , mFallbackChannel(false)
138 : , mCustomConditionalRequest(false)
139 : , mFallingBack(false)
140 : , mWaitingForRedirectCallback(false)
141 : , mRequestTimeInitialized(false)
142 3514 : , mDidReval(false)
143 : {
144 3514 : LOG(("Creating nsHttpChannel [this=%p]\n", this));
145 3514 : mChannelCreationTime = PR_Now();
146 3514 : mChannelCreationTimestamp = mozilla::TimeStamp::Now();
147 3514 : }
148 :
149 9720 : nsHttpChannel::~nsHttpChannel()
150 : {
151 3240 : LOG(("Destroying nsHttpChannel [this=%p]\n", this));
152 :
153 3240 : if (mAuthProvider)
154 480 : mAuthProvider->Disconnect(NS_ERROR_ABORT);
155 12960 : }
156 :
157 : nsresult
158 3514 : nsHttpChannel::Init(nsIURI *uri,
159 : PRUint8 caps,
160 : nsProxyInfo *proxyInfo)
161 : {
162 3514 : nsresult rv = HttpBaseChannel::Init(uri, caps, proxyInfo);
163 3514 : if (NS_FAILED(rv))
164 0 : return rv;
165 :
166 3514 : LOG(("nsHttpChannel::Init [this=%p]\n", this));
167 :
168 : mAuthProvider =
169 : do_CreateInstance("@mozilla.org/network/http-channel-auth-provider;1",
170 3514 : &rv);
171 3514 : if (NS_FAILED(rv))
172 0 : return rv;
173 3514 : rv = mAuthProvider->Init(this);
174 :
175 3514 : return rv;
176 : }
177 : //-----------------------------------------------------------------------------
178 : // nsHttpChannel <private>
179 : //-----------------------------------------------------------------------------
180 :
181 : nsresult
182 5055 : nsHttpChannel::Connect(bool firstTime)
183 : {
184 : nsresult rv;
185 :
186 5055 : LOG(("nsHttpChannel::Connect [this=%p]\n", this));
187 :
188 : // Even if we're in private browsing mode, we still enforce existing STS
189 : // data (it is read-only).
190 : // if the connection is not using SSL and either the exact host matches or
191 : // a superdomain wants to force HTTPS, do it.
192 5055 : bool usingSSL = false;
193 5055 : rv = mURI->SchemeIs("https", &usingSSL);
194 5055 : NS_ENSURE_SUCCESS(rv,rv);
195 :
196 5055 : if (!usingSSL) {
197 : // enforce Strict-Transport-Security
198 5047 : nsIStrictTransportSecurityService* stss = gHttpHandler->GetSTSService();
199 5047 : NS_ENSURE_TRUE(stss, NS_ERROR_OUT_OF_MEMORY);
200 :
201 5047 : bool isStsHost = false;
202 5047 : rv = stss->IsStsURI(mURI, &isStsHost);
203 :
204 : // if STS fails, there's no reason to cancel the load, but it's
205 : // worrisome.
206 5047 : NS_ASSERTION(NS_SUCCEEDED(rv),
207 : "Something is wrong with STS: IsStsURI failed.");
208 :
209 5047 : if (NS_SUCCEEDED(rv) && isStsHost) {
210 0 : LOG(("nsHttpChannel::Connect() STS permissions found\n"));
211 0 : return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
212 : }
213 :
214 : // Check for a previous SPDY Alternate-Protocol directive
215 5047 : if (gHttpHandler->IsSpdyEnabled() && mAllowSpdy) {
216 10094 : nsCAutoString hostPort;
217 :
218 10094 : if (NS_SUCCEEDED(mURI->GetHostPort(hostPort)) &&
219 5047 : gHttpHandler->ConnMgr()->GetSpdyAlternateProtocol(hostPort)) {
220 0 : LOG(("nsHttpChannel::Connect() Alternate-Protocol found\n"));
221 : return AsyncCall(
222 0 : &nsHttpChannel::HandleAsyncRedirectChannelToHttps);
223 : }
224 : }
225 : }
226 :
227 : // ensure that we are using a valid hostname
228 5055 : if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
229 0 : return NS_ERROR_UNKNOWN_HOST;
230 :
231 : // true when called from AsyncOpen
232 5055 : if (firstTime) {
233 : // are we offline?
234 3482 : bool offline = gIOService->IsOffline();
235 3482 : if (offline)
236 3 : mLoadFlags |= LOAD_ONLY_FROM_CACHE;
237 3479 : else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
238 10 : return ResolveProxy(); // Lazily resolve proxy info
239 :
240 : // Don't allow resuming when cache must be used
241 3472 : if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
242 0 : LOG(("Resuming from cache is not supported yet"));
243 0 : return NS_ERROR_DOCUMENT_NOT_CACHED;
244 : }
245 :
246 : // open a cache entry for this channel...
247 3472 : rv = OpenCacheEntry();
248 :
249 3472 : if (NS_FAILED(rv)) {
250 1007 : LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
251 : // if this channel is only allowed to pull from the cache, then
252 : // we must fail if we were unable to open a cache entry.
253 1007 : if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
254 : // If we have a fallback URI (and we're not already
255 : // falling back), process the fallback asynchronously.
256 0 : if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
257 0 : return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
258 : }
259 0 : return NS_ERROR_DOCUMENT_NOT_CACHED;
260 : }
261 : // otherwise, let's just proceed without using the cache.
262 : }
263 :
264 : // if cacheForOfflineUse has been set, open up an offline cache
265 : // entry to update
266 3472 : if (mCacheForOfflineUse) {
267 24 : rv = OpenOfflineCacheEntryForWriting();
268 24 : if (NS_FAILED(rv)) return rv;
269 : }
270 :
271 3472 : if (NS_SUCCEEDED(rv) && mAsyncCacheOpen)
272 1853 : return NS_OK;
273 : }
274 :
275 : // we may or may not have a cache entry at this point
276 3192 : if (mCacheEntry) {
277 : // inspect the cache entry to determine whether or not we need to go
278 : // out to net to validate it. this call sets mCachedContentIsValid
279 : // and may set request headers as required for cache validation.
280 1616 : rv = CheckCache();
281 1616 : if (NS_FAILED(rv))
282 0 : NS_WARNING("cache check failed");
283 :
284 : // read straight from the cache if possible...
285 1616 : if (mCachedContentIsValid) {
286 220 : nsRunnableMethod<nsHttpChannel> *event = nsnull;
287 220 : if (!mCachedContentIsPartial) {
288 220 : AsyncCall(&nsHttpChannel::AsyncOnExamineCachedResponse, &event);
289 : }
290 220 : rv = ReadFromCache();
291 220 : if (NS_FAILED(rv) && event) {
292 0 : event->Revoke();
293 : }
294 : mozilla::Telemetry::Accumulate(
295 220 : mozilla::Telemetry::HTTP_CACHE_DISPOSITION, kCacheHit);
296 :
297 220 : char* cacheDeviceID = nsnull;
298 220 : mCacheEntry->GetDeviceID(&cacheDeviceID);
299 220 : if (cacheDeviceID) {
300 220 : if (!strcmp(cacheDeviceID, kDiskDeviceID))
301 : mozilla::Telemetry::Accumulate(
302 : mozilla::Telemetry::HTTP_DISK_CACHE_DISPOSITION,
303 167 : kCacheHit);
304 53 : else if (!strcmp(cacheDeviceID, kMemoryDeviceID))
305 : mozilla::Telemetry::Accumulate(
306 : mozilla::Telemetry::HTTP_MEMORY_CACHE_DISPOSITION,
307 49 : kCacheHit);
308 4 : else if (!strcmp(cacheDeviceID, kOfflineDeviceID))
309 : mozilla::Telemetry::Accumulate(
310 : mozilla::Telemetry::HTTP_OFFLINE_CACHE_DISPOSITION,
311 4 : kCacheHit);
312 : }
313 220 : return rv;
314 : }
315 1396 : else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
316 : // the cache contains the requested resource, but it must be
317 : // validated before we can reuse it. since we are not allowed
318 : // to hit the net, there's nothing more to do. the document
319 : // is effectively not in the cache.
320 5 : return NS_ERROR_DOCUMENT_NOT_CACHED;
321 : }
322 : }
323 1576 : else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
324 : // If we have a fallback URI (and we're not already
325 : // falling back), process the fallback asynchronously.
326 1 : if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
327 0 : return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
328 : }
329 1 : return NS_ERROR_DOCUMENT_NOT_CACHED;
330 : }
331 :
332 2966 : if (mLoadFlags & LOAD_NO_NETWORK_IO) {
333 0 : return NS_ERROR_DOCUMENT_NOT_CACHED;
334 : }
335 :
336 : // hit the net...
337 2966 : rv = SetupTransaction();
338 2966 : if (NS_FAILED(rv)) return rv;
339 :
340 2966 : rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
341 2966 : if (NS_FAILED(rv)) return rv;
342 :
343 2954 : rv = mTransactionPump->AsyncRead(this, nsnull);
344 2954 : if (NS_FAILED(rv)) return rv;
345 :
346 2954 : PRUint32 suspendCount = mSuspendCount;
347 5908 : while (suspendCount--)
348 0 : mTransactionPump->Suspend();
349 :
350 2954 : return NS_OK;
351 : }
352 :
353 : void
354 42 : nsHttpChannel::DoNotifyListenerCleanup()
355 : {
356 : // We don't need this info anymore
357 42 : CleanRedirectCacheChainIfNecessary();
358 42 : }
359 :
360 : void
361 11 : nsHttpChannel::HandleAsyncRedirect()
362 : {
363 11 : NS_PRECONDITION(!mCallOnResume, "How did that happen?");
364 :
365 11 : if (mSuspendCount) {
366 0 : LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
367 0 : mCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
368 0 : return;
369 : }
370 :
371 11 : nsresult rv = NS_OK;
372 :
373 11 : LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
374 :
375 : // since this event is handled asynchronously, it is possible that this
376 : // channel could have been canceled, in which case there would be no point
377 : // in processing the redirect.
378 11 : if (NS_SUCCEEDED(mStatus)) {
379 11 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
380 11 : rv = AsyncProcessRedirection(mResponseHead->Status());
381 11 : if (NS_FAILED(rv)) {
382 1 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncRedirect);
383 1 : ContinueHandleAsyncRedirect(rv);
384 : }
385 : }
386 : else {
387 0 : ContinueHandleAsyncRedirect(NS_OK);
388 : }
389 : }
390 :
391 : nsresult
392 11 : nsHttpChannel::ContinueHandleAsyncRedirect(nsresult rv)
393 : {
394 11 : if (NS_FAILED(rv)) {
395 : // If AsyncProcessRedirection fails, then we have to send out the
396 : // OnStart/OnStop notifications.
397 3 : LOG(("ContinueHandleAsyncRedirect got failure result [rv=%x]\n", rv));
398 3 : mStatus = rv;
399 3 : DoNotifyListener();
400 : }
401 :
402 : // close the cache entry. Blow it away if we couldn't process the redirect
403 : // for some reason (the cache entry might be corrupt).
404 11 : if (mCacheEntry) {
405 11 : if (NS_FAILED(rv))
406 3 : mCacheEntry->Doom();
407 11 : CloseCacheEntry(false);
408 : }
409 :
410 11 : mIsPending = false;
411 :
412 11 : if (mLoadGroup)
413 0 : mLoadGroup->RemoveRequest(this, nsnull, mStatus);
414 :
415 11 : return NS_OK;
416 : }
417 :
418 : void
419 0 : nsHttpChannel::HandleAsyncNotModified()
420 : {
421 0 : NS_PRECONDITION(!mCallOnResume, "How did that happen?");
422 :
423 0 : if (mSuspendCount) {
424 0 : LOG(("Waiting until resume to do async not-modified [this=%p]\n",
425 : this));
426 0 : mCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
427 0 : return;
428 : }
429 :
430 0 : LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
431 :
432 0 : DoNotifyListener();
433 :
434 0 : CloseCacheEntry(true);
435 :
436 0 : mIsPending = false;
437 :
438 0 : if (mLoadGroup)
439 0 : mLoadGroup->RemoveRequest(this, nsnull, mStatus);
440 : }
441 :
442 : void
443 2 : nsHttpChannel::HandleAsyncFallback()
444 : {
445 2 : NS_PRECONDITION(!mCallOnResume, "How did that happen?");
446 :
447 2 : if (mSuspendCount) {
448 0 : LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
449 0 : mCallOnResume = &nsHttpChannel::HandleAsyncFallback;
450 0 : return;
451 : }
452 :
453 2 : nsresult rv = NS_OK;
454 :
455 2 : LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
456 :
457 : // since this event is handled asynchronously, it is possible that this
458 : // channel could have been canceled, in which case there would be no point
459 : // in processing the fallback.
460 2 : if (!mCanceled) {
461 2 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
462 : bool waitingForRedirectCallback;
463 2 : rv = ProcessFallback(&waitingForRedirectCallback);
464 2 : if (waitingForRedirectCallback)
465 2 : return;
466 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncFallback);
467 : }
468 :
469 0 : ContinueHandleAsyncFallback(rv);
470 : }
471 :
472 : nsresult
473 2 : nsHttpChannel::ContinueHandleAsyncFallback(nsresult rv)
474 : {
475 2 : if (!mCanceled && (NS_FAILED(rv) || !mFallingBack)) {
476 : // If ProcessFallback fails, then we have to send out the
477 : // OnStart/OnStop notifications.
478 1 : LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, mFallingBack));
479 1 : mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
480 1 : DoNotifyListener();
481 : }
482 :
483 2 : mIsPending = false;
484 :
485 2 : if (mLoadGroup)
486 0 : mLoadGroup->RemoveRequest(this, nsnull, mStatus);
487 :
488 2 : return rv;
489 : }
490 :
491 : nsresult
492 2989 : nsHttpChannel::SetupTransaction()
493 : {
494 2989 : LOG(("nsHttpChannel::SetupTransaction [this=%p]\n", this));
495 :
496 2989 : NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
497 :
498 : nsresult rv;
499 :
500 2989 : if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
501 : //
502 : // disable pipelining if:
503 : // (1) pipelining has been explicitly disabled
504 : // (2) request corresponds to a top-level document load (link click)
505 : // (3) request method is non-idempotent
506 : //
507 : // XXX does the toplevel document check really belong here? or, should
508 : // we push it out entirely to necko consumers?
509 : //
510 0 : if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) ||
511 0 : !(mRequestHead.Method() == nsHttp::Get ||
512 0 : mRequestHead.Method() == nsHttp::Head ||
513 0 : mRequestHead.Method() == nsHttp::Propfind ||
514 0 : mRequestHead.Method() == nsHttp::Proppatch)) {
515 0 : LOG((" pipelining disallowed\n"));
516 0 : mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
517 : }
518 : }
519 :
520 2989 : if (!mAllowSpdy)
521 0 : mCaps |= NS_HTTP_DISALLOW_SPDY;
522 :
523 : // use the URI path if not proxying (transparent proxying such as SSL proxy
524 : // does not count here). also, figure out what version we should be speaking.
525 5978 : nsCAutoString buf, path;
526 : nsCString* requestURI;
527 8959 : if (mConnectionInfo->UsingSSL() ||
528 2985 : mConnectionInfo->ShouldForceConnectMethod() ||
529 2985 : !mConnectionInfo->UsingHttpProxy()) {
530 2962 : rv = mURI->GetPath(path);
531 2962 : if (NS_FAILED(rv)) return rv;
532 : // path may contain UTF-8 characters, so ensure that they're escaped.
533 2962 : if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
534 0 : requestURI = &buf;
535 : else
536 2962 : requestURI = &path;
537 2962 : mRequestHead.SetVersion(gHttpHandler->HttpVersion());
538 : }
539 : else {
540 27 : rv = mURI->GetUserPass(buf);
541 27 : if (NS_FAILED(rv)) return rv;
542 27 : if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
543 0 : strncmp(mSpec.get(), "https:", 6) == 0)) {
544 0 : nsCOMPtr<nsIURI> tempURI;
545 0 : rv = mURI->Clone(getter_AddRefs(tempURI));
546 0 : if (NS_FAILED(rv)) return rv;
547 0 : rv = tempURI->SetUserPass(EmptyCString());
548 0 : if (NS_FAILED(rv)) return rv;
549 0 : rv = tempURI->GetAsciiSpec(path);
550 0 : if (NS_FAILED(rv)) return rv;
551 0 : requestURI = &path;
552 : }
553 : else
554 27 : requestURI = &mSpec;
555 27 : mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
556 : }
557 :
558 : // trim off the #ref portion if any...
559 2989 : PRInt32 ref = requestURI->FindChar('#');
560 2989 : if (ref != kNotFound)
561 0 : requestURI->SetLength(ref);
562 :
563 2989 : mRequestHead.SetRequestURI(*requestURI);
564 :
565 : // set the request time for cache expiration calculations
566 2989 : mRequestTime = NowInSeconds();
567 2989 : mRequestTimeInitialized = true;
568 :
569 : // if doing a reload, force end-to-end
570 2989 : if (mLoadFlags & LOAD_BYPASS_CACHE) {
571 : // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
572 : // no proxy is configured since we might be talking with a transparent
573 : // proxy, i.e. one that operates at the network level. See bug #14772.
574 1920 : mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), true);
575 : // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
576 : // no-cache'
577 1920 : if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
578 1920 : mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("no-cache"), true);
579 : }
580 1069 : else if ((mLoadFlags & VALIDATE_ALWAYS) && (mCacheAccess & nsICache::ACCESS_READ)) {
581 : // We need to send 'Cache-Control: max-age=0' to force each cache along
582 : // the path to the origin server to revalidate its own entry, if any,
583 : // with the next cache or server. See bug #84847.
584 : //
585 : // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
586 3 : if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
587 3 : mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("max-age=0"), true);
588 : else
589 0 : mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), true);
590 : }
591 :
592 2989 : if (mResuming) {
593 : char byteRange[32];
594 28 : PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
595 28 : mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
596 :
597 28 : if (!mEntityID.IsEmpty()) {
598 : // Also, we want an error if this resource changed in the meantime
599 : // Format of the entity id is: escaped_etag/size/lastmod
600 28 : nsCString::const_iterator start, end, slash;
601 28 : mEntityID.BeginReading(start);
602 28 : mEntityID.EndReading(end);
603 28 : mEntityID.BeginReading(slash);
604 :
605 28 : if (FindCharInReadable('/', slash, end)) {
606 56 : nsCAutoString ifMatch;
607 : mRequestHead.SetHeader(nsHttp::If_Match,
608 28 : NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
609 :
610 28 : ++slash; // Incrementing, so that searching for '/' won't find
611 : // the same slash again
612 : }
613 :
614 28 : if (FindCharInReadable('/', slash, end)) {
615 : mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
616 28 : Substring(++slash, end));
617 : }
618 : }
619 : }
620 :
621 : // create wrapper for this channel's notification callbacks
622 5978 : nsCOMPtr<nsIInterfaceRequestor> callbacks;
623 : NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
624 2989 : getter_AddRefs(callbacks));
625 2989 : if (!callbacks)
626 0 : return NS_ERROR_OUT_OF_MEMORY;
627 :
628 : // create the transaction object
629 2989 : mTransaction = new nsHttpTransaction();
630 2989 : if (!mTransaction)
631 0 : return NS_ERROR_OUT_OF_MEMORY;
632 :
633 : // See bug #466080. Transfer LOAD_ANONYMOUS flag to socket-layer.
634 2989 : if (mLoadFlags & LOAD_ANONYMOUS)
635 1 : mCaps |= NS_HTTP_LOAD_ANONYMOUS;
636 :
637 2989 : if (mTimingEnabled)
638 2 : mCaps |= NS_HTTP_TIMING_ENABLED;
639 :
640 2989 : mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
641 :
642 2989 : if (mUpgradeProtocolCallback) {
643 0 : mRequestHead.SetHeader(nsHttp::Upgrade, mUpgradeProtocol, false);
644 : mRequestHead.SetHeader(nsHttp::Connection,
645 0 : nsDependentCString(nsHttp::Upgrade.get()),
646 0 : true);
647 0 : mCaps |= NS_HTTP_STICKY_CONNECTION;
648 0 : mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
649 0 : mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
650 0 : mCaps |= NS_HTTP_DISALLOW_SPDY;
651 : }
652 :
653 5978 : nsCOMPtr<nsIAsyncInputStream> responseStream;
654 : rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
655 : mUploadStream, mUploadStreamHasHeaders,
656 2989 : NS_GetCurrentThread(), callbacks, this,
657 5978 : getter_AddRefs(responseStream));
658 2989 : if (NS_FAILED(rv)) {
659 0 : mTransaction = nsnull;
660 0 : return rv;
661 : }
662 :
663 2989 : rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
664 5978 : responseStream);
665 2989 : return rv;
666 : }
667 :
668 : // NOTE: This function duplicates code from nsBaseChannel. This will go away
669 : // once HTTP uses nsBaseChannel (part of bug 312760)
670 : static void
671 1 : CallTypeSniffers(void *aClosure, const PRUint8 *aData, PRUint32 aCount)
672 : {
673 1 : nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
674 :
675 : const nsCOMArray<nsIContentSniffer>& sniffers =
676 1 : gIOService->GetContentSniffers();
677 1 : PRUint32 length = sniffers.Count();
678 3 : for (PRUint32 i = 0; i < length; ++i) {
679 6 : nsCAutoString newType;
680 : nsresult rv =
681 3 : sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType);
682 3 : if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) {
683 1 : chan->SetContentType(newType);
684 : break;
685 : }
686 : }
687 1 : }
688 :
689 : nsresult
690 3037 : nsHttpChannel::CallOnStartRequest()
691 : {
692 3037 : mTracingEnabled = false;
693 :
694 3037 : if (mResponseHead && mResponseHead->ContentType().IsEmpty()) {
695 948 : NS_ASSERTION(mConnectionInfo, "Should have connection info here");
696 948 : if (!mContentTypeHint.IsEmpty())
697 57 : mResponseHead->SetContentType(mContentTypeHint);
698 892 : else if (mResponseHead->Version() == NS_HTTP_VERSION_0_9 &&
699 1 : mConnectionInfo->Port() != mConnectionInfo->DefaultPort())
700 1 : mResponseHead->SetContentType(NS_LITERAL_CSTRING(TEXT_PLAIN));
701 : else {
702 : // Uh-oh. We had better find out what type we are!
703 :
704 : // XXX This does not work with content-encodings... but
705 : // neither does applying the conversion from the URILoader
706 :
707 1780 : nsCOMPtr<nsIStreamConverterService> serv;
708 : nsresult rv = gHttpHandler->
709 890 : GetStreamConverterService(getter_AddRefs(serv));
710 : // If we failed, we just fall through to the "normal" case
711 890 : if (NS_SUCCEEDED(rv)) {
712 1780 : nsCOMPtr<nsIStreamListener> converter;
713 890 : rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
714 : "*/*",
715 : mListener,
716 : mListenerContext,
717 890 : getter_AddRefs(converter));
718 890 : if (NS_SUCCEEDED(rv)) {
719 890 : mListener = converter;
720 : }
721 : }
722 : }
723 : }
724 :
725 3037 : if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
726 2877 : mResponseHead->SetContentCharset(mContentCharsetHint);
727 :
728 3037 : if (mResponseHead) {
729 : SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
730 2881 : mResponseHead->ContentLength());
731 : // If we have a cache entry, set its predicted size to ContentLength to
732 : // avoid caching an entry that will exceed the max size limit.
733 2881 : if (mCacheEntry) {
734 : nsresult rv;
735 1363 : PRInt64 predictedDataSize = -1; // -1 in case GetAsInt64 fails.
736 : GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
737 1363 : &predictedDataSize);
738 1363 : rv = mCacheEntry->SetPredictedDataSize(predictedDataSize);
739 1363 : if (NS_FAILED(rv)) return rv;
740 : }
741 : }
742 : // Allow consumers to override our content type
743 3038 : if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) &&
744 1 : gIOService->GetContentSniffers().Count() != 0) {
745 : // NOTE: We can have both a txn pump and a cache pump when the cache
746 : // content is partial. In that case, we need to read from the cache,
747 : // because that's the one that has the initial contents. If that fails
748 : // then give the transaction pump a shot.
749 :
750 1 : nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
751 :
752 1 : bool typeSniffersCalled = false;
753 1 : if (mCachePump) {
754 : typeSniffersCalled =
755 0 : NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
756 : }
757 :
758 1 : if (!typeSniffersCalled && mTransactionPump) {
759 1 : mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
760 : }
761 : }
762 :
763 3037 : LOG((" calling mListener->OnStartRequest\n"));
764 3037 : nsresult rv = mListener->OnStartRequest(this, mListenerContext);
765 3037 : if (NS_FAILED(rv)) return rv;
766 :
767 : // install stream converter if required
768 3021 : rv = ApplyContentConversions();
769 3021 : if (NS_FAILED(rv)) return rv;
770 :
771 : // if this channel is for a download, close off access to the cache.
772 3021 : if (mCacheEntry && mChannelIsForDownload) {
773 6 : mCacheEntry->Doom();
774 6 : CloseCacheEntry(false);
775 : }
776 :
777 3021 : if (!mCanceled) {
778 : // create offline cache entry if offline caching was requested
779 3003 : if (mCacheForOfflineUse) {
780 : bool shouldCacheForOfflineUse;
781 24 : rv = ShouldUpdateOfflineCacheEntry(&shouldCacheForOfflineUse);
782 24 : if (NS_FAILED(rv)) return rv;
783 :
784 24 : if (shouldCacheForOfflineUse) {
785 24 : LOG(("writing to the offline cache"));
786 24 : rv = InitOfflineCacheEntry();
787 24 : if (NS_FAILED(rv)) return rv;
788 :
789 24 : if (mOfflineCacheEntry) {
790 24 : rv = InstallOfflineCacheListener();
791 24 : if (NS_FAILED(rv)) return rv;
792 : }
793 : } else {
794 0 : LOG(("offline cache is up to date, not updating"));
795 0 : CloseOfflineCacheEntry();
796 : }
797 : }
798 : }
799 :
800 3021 : return NS_OK;
801 : }
802 :
803 : nsresult
804 0 : nsHttpChannel::ProcessFailedSSLConnect(PRUint32 httpStatus)
805 : {
806 : // Failure to set up SSL proxy tunnel means one of the following:
807 : // 1) Proxy wants authorization, or forbids.
808 : // 2) DNS at proxy couldn't resolve target URL.
809 : // 3) Proxy connection to target failed or timed out.
810 : // 4) Eve noticed our proxy CONNECT, and is replying with malicious HTML.
811 : //
812 : // Our current architecture will parse response content with the
813 : // permission of the target URL! Given #4, we must avoid rendering the
814 : // body of the reply, and instead give the user a (hopefully helpful)
815 : // boilerplate error page, based on just the HTTP status of the reply.
816 :
817 0 : NS_ABORT_IF_FALSE(mConnectionInfo->UsingSSL(),
818 : "SSL connect failed but not using SSL?");
819 : nsresult rv;
820 0 : switch (httpStatus)
821 : {
822 : case 300: case 301: case 302: case 303: case 307:
823 : // Bad redirect: not top-level, or it's a POST, bad/missing Location,
824 : // or ProcessRedirect() failed for some other reason. Legal
825 : // redirects that fail because site not available, etc., are handled
826 : // elsewhere, in the regular codepath.
827 0 : rv = NS_ERROR_CONNECTION_REFUSED;
828 0 : break;
829 : case 403: // HTTP/1.1: "Forbidden"
830 : case 407: // ProcessAuthentication() failed
831 : case 501: // HTTP/1.1: "Not Implemented"
832 : // user sees boilerplate Mozilla "Proxy Refused Connection" page.
833 0 : rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
834 0 : break;
835 : // Squid sends 404 if DNS fails (regular 404 from target is tunneled)
836 : case 404: // HTTP/1.1: "Not Found"
837 : // RFC 2616: "some deployed proxies are known to return 400 or 500 when
838 : // DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
839 : // we have a conflict here).
840 : case 400: // HTTP/1.1 "Bad Request"
841 : case 500: // HTTP/1.1: "Internal Server Error"
842 : /* User sees: "Address Not Found: Firefox can't find the server at
843 : * www.foo.com."
844 : */
845 0 : rv = NS_ERROR_UNKNOWN_HOST;
846 0 : break;
847 : case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
848 : // Squid returns 503 if target request fails for anything but DNS.
849 : case 503: // HTTP/1.1: "Service Unavailable"
850 : /* User sees: "Failed to Connect:
851 : * Firefox can't establish a connection to the server at
852 : * www.foo.com. Though the site seems valid, the browser
853 : * was unable to establish a connection."
854 : */
855 0 : rv = NS_ERROR_CONNECTION_REFUSED;
856 0 : break;
857 : // RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
858 : // do here: picking target timeout, as DNS covered by 400/404/500
859 : case 504: // HTTP/1.1: "Gateway Timeout"
860 : // user sees: "Network Timeout: The server at www.foo.com
861 : // is taking too long to respond."
862 0 : rv = NS_ERROR_NET_TIMEOUT;
863 0 : break;
864 : // Confused proxy server or malicious response
865 : default:
866 0 : rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
867 0 : break;
868 : }
869 0 : LOG(("Cancelling failed SSL proxy connection [this=%p httpStatus=%u]\n",
870 : this, httpStatus));
871 0 : Cancel(rv);
872 0 : CallOnStartRequest();
873 0 : return rv;
874 : }
875 :
876 : bool
877 0 : nsHttpChannel::ShouldSSLProxyResponseContinue(PRUint32 httpStatus)
878 : {
879 : // When SSL connect has failed, allow proxy reply to continue only if it's
880 : // a 407 (proxy authentication required) response
881 0 : return (httpStatus == 407);
882 : }
883 :
884 : /**
885 : * Decide whether or not to remember Strict-Transport-Security, and whether
886 : * or not to enforce channel integrity.
887 : *
888 : * @return NS_ERROR_FAILURE if there's security information missing even though
889 : * it's an HTTPS connection.
890 : */
891 : nsresult
892 2819 : nsHttpChannel::ProcessSTSHeader()
893 : {
894 : nsresult rv;
895 2819 : bool isHttps = false;
896 2819 : rv = mURI->SchemeIs("https", &isHttps);
897 2819 : NS_ENSURE_SUCCESS(rv, rv);
898 :
899 : // If this channel is not loading securely, STS doesn't do anything.
900 : // The upgrade to HTTPS takes place earlier in the channel load process.
901 2819 : if (!isHttps)
902 2815 : return NS_OK;
903 :
904 8 : nsCAutoString asciiHost;
905 4 : rv = mURI->GetAsciiHost(asciiHost);
906 4 : NS_ENSURE_SUCCESS(rv, NS_OK);
907 :
908 : // If the channel is not a hostname, but rather an IP, STS doesn't do
909 : // anything.
910 : PRNetAddr hostAddr;
911 4 : if (PR_SUCCESS == PR_StringToNetAddr(asciiHost.get(), &hostAddr))
912 0 : return NS_OK;
913 :
914 4 : nsIStrictTransportSecurityService* stss = gHttpHandler->GetSTSService();
915 4 : NS_ENSURE_TRUE(stss, NS_ERROR_OUT_OF_MEMORY);
916 :
917 : // mSecurityInfo may not always be present, and if it's not then it is okay
918 : // to just disregard any STS headers since we know nothing about the
919 : // security of the connection.
920 4 : NS_ENSURE_TRUE(mSecurityInfo, NS_OK);
921 :
922 : // Check the trustworthiness of the channel (are there any cert errors?)
923 : // If there are certificate errors, we still load the data, we just ignore
924 : // any STS headers that are present.
925 4 : bool tlsIsBroken = false;
926 4 : rv = stss->ShouldIgnoreStsHeader(mSecurityInfo, &tlsIsBroken);
927 4 : NS_ENSURE_SUCCESS(rv, NS_OK);
928 :
929 : // If this was already an STS host, the connection should have been aborted
930 : // by the bad cert handler in the case of cert errors. If it didn't abort the connection,
931 : // there's probably something funny going on.
932 : // If this wasn't an STS host, errors are allowed, but no more STS processing
933 : // will happen during the session.
934 : bool wasAlreadySTSHost;
935 4 : rv = stss->IsStsURI(mURI, &wasAlreadySTSHost);
936 : // Failure here means STS is broken. Don't prevent the load, but this
937 : // shouldn't fail.
938 4 : NS_ENSURE_SUCCESS(rv, NS_OK);
939 4 : NS_ASSERTION(!(wasAlreadySTSHost && tlsIsBroken),
940 : "connection should have been aborted by nss-bad-cert-handler");
941 :
942 : // Any STS header is ignored if the channel is not trusted due to
943 : // certificate errors (STS Spec 7.1) -- there is nothing else to do, and
944 : // the load may progress.
945 4 : if (tlsIsBroken) {
946 0 : LOG(("STS: Transport layer is not trustworthy, ignoring "
947 : "STS headers and continuing load\n"));
948 0 : return NS_OK;
949 : }
950 :
951 : // If there's a STS header, process it (STS Spec 7.1). At this point in
952 : // processing, the channel is trusted, so the header should not be ignored.
953 4 : const nsHttpAtom atom = nsHttp::ResolveAtom("Strict-Transport-Security");
954 8 : nsCAutoString stsHeader;
955 4 : rv = mResponseHead->GetHeader(atom, stsHeader);
956 4 : if (rv == NS_ERROR_NOT_AVAILABLE) {
957 0 : LOG(("STS: No STS header, continuing load.\n"));
958 0 : return NS_OK;
959 : }
960 : // All other failures are fatal.
961 4 : NS_ENSURE_SUCCESS(rv, rv);
962 :
963 4 : rv = stss->ProcessStsHeader(mURI, stsHeader.get());
964 4 : if (NS_FAILED(rv)) {
965 0 : LOG(("STS: Failed to parse STS header, continuing load.\n"));
966 0 : return NS_OK;
967 : }
968 :
969 4 : return NS_OK;
970 : }
971 :
972 : nsresult
973 2819 : nsHttpChannel::ProcessResponse()
974 : {
975 : nsresult rv;
976 2819 : PRUint32 httpStatus = mResponseHead->Status();
977 :
978 2819 : LOG(("nsHttpChannel::ProcessResponse [this=%p httpStatus=%u]\n",
979 : this, httpStatus));
980 :
981 2819 : if (mTransaction->SSLConnectFailed()) {
982 0 : if (!ShouldSSLProxyResponseContinue(httpStatus))
983 0 : return ProcessFailedSSLConnect(httpStatus);
984 : // If SSL proxy response needs to complete, wait to process connection
985 : // for Strict-Transport-Security.
986 : } else {
987 : // Given a successful connection, process any STS data that's relevant.
988 2819 : rv = ProcessSTSHeader();
989 2819 : NS_ASSERTION(NS_SUCCEEDED(rv), "ProcessSTSHeader failed, continuing load.");
990 : }
991 :
992 : // notify "http-on-examine-response" observers
993 2819 : gHttpHandler->OnExamineResponse(this);
994 :
995 2819 : SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
996 :
997 : // handle unused username and password in url (see bug 232567)
998 2819 : if (httpStatus != 401 && httpStatus != 407) {
999 2760 : if (!mAuthRetryPending)
1000 2760 : mAuthProvider->CheckForSuperfluousAuth();
1001 2760 : if (mCanceled)
1002 0 : return CallOnStartRequest();
1003 :
1004 : // reset the authentication's current continuation state because our
1005 : // last authentication attempt has been completed successfully
1006 2760 : mAuthProvider->Disconnect(NS_ERROR_ABORT);
1007 2760 : mAuthProvider = nsnull;
1008 2760 : LOG((" continuation state has been reset"));
1009 : }
1010 :
1011 2819 : bool successfulReval = false;
1012 :
1013 : // handle different server response categories. Note that we handle
1014 : // caching or not caching of error pages in
1015 : // nsHttpResponseHead::MustValidate; if you change this switch, update that
1016 : // one
1017 2819 : switch (httpStatus) {
1018 : case 200:
1019 : case 203:
1020 : // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
1021 : // So if a server does that and sends 200 instead of 206 that we
1022 : // expect, notify our caller.
1023 : // However, if we wanted to start from the beginning, let it go through
1024 1969 : if (mResuming && mStartPos != 0) {
1025 3 : LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
1026 3 : Cancel(NS_ERROR_NOT_RESUMABLE);
1027 3 : rv = CallOnStartRequest();
1028 3 : break;
1029 : }
1030 : // these can normally be cached
1031 1966 : rv = ProcessNormal();
1032 1966 : MaybeInvalidateCacheEntryForSubsequentGet();
1033 1966 : break;
1034 : case 206:
1035 19 : if (mCachedContentIsPartial) // an internal byte range request...
1036 2 : rv = ProcessPartialContent();
1037 : else
1038 17 : rv = ProcessNormal();
1039 19 : break;
1040 : case 300:
1041 : case 301:
1042 : case 302:
1043 : case 307:
1044 : case 303:
1045 : #if 0
1046 : case 305: // disabled as a security measure (see bug 187996).
1047 : #endif
1048 : // don't store the response body for redirects
1049 145 : MaybeInvalidateCacheEntryForSubsequentGet();
1050 145 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
1051 145 : rv = AsyncProcessRedirection(httpStatus);
1052 145 : if (NS_FAILED(rv)) {
1053 20 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessResponse);
1054 20 : LOG(("AsyncProcessRedirection failed [rv=%x]\n", rv));
1055 20 : rv = ContinueProcessResponse(rv);
1056 : }
1057 145 : break;
1058 : case 304:
1059 25 : rv = ProcessNotModified();
1060 25 : if (NS_FAILED(rv)) {
1061 19 : LOG(("ProcessNotModified failed [rv=%x]\n", rv));
1062 19 : rv = ProcessNormal();
1063 : }
1064 : else {
1065 6 : successfulReval = true;
1066 : }
1067 25 : break;
1068 : case 401:
1069 : case 407:
1070 59 : rv = mAuthProvider->ProcessAuthentication(
1071 59 : httpStatus, mConnectionInfo->UsingSSL() &&
1072 118 : mTransaction->SSLConnectFailed());
1073 59 : if (rv == NS_ERROR_IN_PROGRESS) {
1074 : // authentication prompt has been invoked and result
1075 : // is expected asynchronously
1076 18 : mAuthRetryPending = true;
1077 : // suspend the transaction pump to stop receiving the
1078 : // unauthenticated content data. We will throw that data
1079 : // away when user provides credentials or resume the pump
1080 : // when user refuses to authenticate.
1081 18 : LOG(("Suspending the transaction, asynchronously prompting for credentials"));
1082 18 : mTransactionPump->Suspend();
1083 18 : rv = NS_OK;
1084 : }
1085 41 : else if (NS_FAILED(rv)) {
1086 33 : LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
1087 33 : if (mTransaction->SSLConnectFailed())
1088 0 : return ProcessFailedSSLConnect(httpStatus);
1089 33 : if (!mAuthRetryPending)
1090 33 : mAuthProvider->CheckForSuperfluousAuth();
1091 33 : rv = ProcessNormal();
1092 : }
1093 : else
1094 8 : mAuthRetryPending = true; // see DoAuthRetry
1095 59 : break;
1096 : default:
1097 602 : rv = ProcessNormal();
1098 602 : MaybeInvalidateCacheEntryForSubsequentGet();
1099 602 : break;
1100 : }
1101 :
1102 : int cacheDisposition;
1103 2819 : if (!mDidReval)
1104 2468 : cacheDisposition = kCacheMissed;
1105 351 : else if (successfulReval)
1106 6 : cacheDisposition = kCacheHitViaReval;
1107 : else
1108 345 : cacheDisposition = kCacheMissedViaReval;
1109 :
1110 : mozilla::Telemetry::Accumulate(mozilla::Telemetry::HTTP_CACHE_DISPOSITION,
1111 2819 : cacheDisposition);
1112 2819 : if (mCacheEntry) {
1113 1267 : char* cacheDeviceID = nsnull;
1114 1267 : mCacheEntry->GetDeviceID(&cacheDeviceID);
1115 1267 : if (cacheDeviceID) {
1116 367 : if (!strcmp(cacheDeviceID, kDiskDeviceID))
1117 : mozilla::Telemetry::Accumulate(
1118 : mozilla::Telemetry::HTTP_DISK_CACHE_DISPOSITION,
1119 6 : cacheDisposition);
1120 361 : else if (!strcmp(cacheDeviceID, kMemoryDeviceID))
1121 : mozilla::Telemetry::Accumulate(
1122 : mozilla::Telemetry::HTTP_MEMORY_CACHE_DISPOSITION,
1123 361 : cacheDisposition);
1124 0 : else if (!strcmp(cacheDeviceID, kOfflineDeviceID))
1125 : mozilla::Telemetry::Accumulate(
1126 : mozilla::Telemetry::HTTP_OFFLINE_CACHE_DISPOSITION,
1127 0 : cacheDisposition);
1128 : }
1129 : }
1130 :
1131 2819 : return rv;
1132 : }
1133 :
1134 : nsresult
1135 145 : nsHttpChannel::ContinueProcessResponse(nsresult rv)
1136 : {
1137 145 : if (rv == NS_ERROR_DOM_BAD_URI && mRedirectURI) {
1138 :
1139 3 : bool isHTTP = false;
1140 3 : if (NS_FAILED(mRedirectURI->SchemeIs("http", &isHTTP)))
1141 0 : isHTTP = false;
1142 3 : if (!isHTTP && NS_FAILED(mRedirectURI->SchemeIs("https", &isHTTP)))
1143 0 : isHTTP = false;
1144 :
1145 3 : if (!isHTTP) {
1146 : // This was a blocked attempt to redirect and subvert the system by
1147 : // redirecting to another protocol (perhaps javascript:)
1148 : // In that case we want to throw an error instead of displaying the
1149 : // non-redirected response body.
1150 :
1151 2 : LOG(("ContinueProcessResponse detected rejected Non-HTTP Redirection"));
1152 2 : return NS_ERROR_CORRUPTED_CONTENT;
1153 : }
1154 : }
1155 :
1156 143 : if (NS_SUCCEEDED(rv)) {
1157 119 : InitCacheEntry();
1158 119 : CloseCacheEntry(false);
1159 :
1160 119 : if (mCacheForOfflineUse) {
1161 : // Store response in the offline cache
1162 0 : InitOfflineCacheEntry();
1163 0 : CloseOfflineCacheEntry();
1164 : }
1165 119 : return NS_OK;
1166 : }
1167 :
1168 24 : LOG(("ContinueProcessResponse got failure result [rv=%x]\n", rv));
1169 24 : if (mTransaction->SSLConnectFailed()) {
1170 0 : return ProcessFailedSSLConnect(mRedirectType);
1171 : }
1172 24 : return ProcessNormal();
1173 : }
1174 :
1175 : nsresult
1176 2661 : nsHttpChannel::ProcessNormal()
1177 : {
1178 : nsresult rv;
1179 :
1180 2661 : LOG(("nsHttpChannel::ProcessNormal [this=%p]\n", this));
1181 :
1182 : bool succeeded;
1183 2661 : rv = GetRequestSucceeded(&succeeded);
1184 2661 : if (NS_SUCCEEDED(rv) && !succeeded) {
1185 676 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
1186 : bool waitingForRedirectCallback;
1187 676 : (void)ProcessFallback(&waitingForRedirectCallback);
1188 676 : if (waitingForRedirectCallback) {
1189 : // The transaction has been suspended by ProcessFallback.
1190 3 : return NS_OK;
1191 : }
1192 673 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessNormal);
1193 : }
1194 :
1195 2658 : return ContinueProcessNormal(NS_OK);
1196 : }
1197 :
1198 : nsresult
1199 2661 : nsHttpChannel::ContinueProcessNormal(nsresult rv)
1200 : {
1201 2661 : if (NS_FAILED(rv)) {
1202 : // Fill the failure status here, we have failed to fall back, thus we
1203 : // have to report our status as failed.
1204 2 : mStatus = rv;
1205 2 : DoNotifyListener();
1206 2 : return rv;
1207 : }
1208 :
1209 2659 : if (mFallingBack) {
1210 : // Do not continue with normal processing, fallback is in
1211 : // progress now.
1212 1 : return NS_OK;
1213 : }
1214 :
1215 : // if we're here, then any byte-range requests failed to result in a partial
1216 : // response. we must clear this flag to prevent BufferPartialContent from
1217 : // being called inside our OnDataAvailable (see bug 136678).
1218 2658 : mCachedContentIsPartial = false;
1219 :
1220 2658 : ClearBogusContentEncodingIfNeeded();
1221 :
1222 2658 : UpdateInhibitPersistentCachingFlag();
1223 :
1224 : // this must be called before firing OnStartRequest, since http clients,
1225 : // such as imagelib, expect our cache entry to already have the correct
1226 : // expiration time (bug 87710).
1227 2658 : if (mCacheEntry) {
1228 1143 : rv = InitCacheEntry();
1229 1143 : if (NS_FAILED(rv))
1230 0 : CloseCacheEntry(true);
1231 : }
1232 :
1233 : // Check that the server sent us what we were asking for
1234 2658 : if (mResuming) {
1235 : // Create an entity id from the response
1236 40 : nsCAutoString id;
1237 20 : rv = GetEntityID(id);
1238 20 : if (NS_FAILED(rv)) {
1239 : // If creating an entity id is not possible -> error
1240 4 : Cancel(NS_ERROR_NOT_RESUMABLE);
1241 : }
1242 22 : else if (mResponseHead->Status() != 206 &&
1243 6 : mResponseHead->Status() != 200) {
1244 : // Probably 404 Not Found, 412 Precondition Failed or
1245 : // 416 Invalid Range -> error
1246 3 : LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
1247 : this));
1248 3 : Cancel(NS_ERROR_ENTITY_CHANGED);
1249 : }
1250 : // If we were passed an entity id, verify it's equal to the server's
1251 13 : else if (!mEntityID.IsEmpty()) {
1252 13 : if (!mEntityID.Equals(id)) {
1253 0 : LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
1254 : mEntityID.get(), id.get(), this));
1255 0 : Cancel(NS_ERROR_ENTITY_CHANGED);
1256 : }
1257 : }
1258 : }
1259 :
1260 2658 : rv = CallOnStartRequest();
1261 2658 : if (NS_FAILED(rv)) return rv;
1262 :
1263 : // install cache listener if we still have a cache entry open
1264 2648 : if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) {
1265 1132 : rv = InstallCacheListener();
1266 1132 : if (NS_FAILED(rv)) return rv;
1267 : }
1268 :
1269 2648 : return NS_OK;
1270 : }
1271 :
1272 : nsresult
1273 16 : nsHttpChannel::PromptTempRedirect()
1274 : {
1275 16 : if (!gHttpHandler->PromptTempRedirect()) {
1276 2 : return NS_OK;
1277 : }
1278 : nsresult rv;
1279 : nsCOMPtr<nsIStringBundleService> bundleService =
1280 28 : do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
1281 14 : if (NS_FAILED(rv)) return rv;
1282 :
1283 28 : nsCOMPtr<nsIStringBundle> stringBundle;
1284 14 : rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
1285 14 : if (NS_FAILED(rv)) return rv;
1286 :
1287 28 : nsXPIDLString messageString;
1288 14 : rv = stringBundle->GetStringFromName(NS_LITERAL_STRING("RepostFormData").get(), getter_Copies(messageString));
1289 : // GetStringFromName can return NS_OK and NULL messageString.
1290 14 : if (NS_SUCCEEDED(rv) && messageString) {
1291 14 : bool repost = false;
1292 :
1293 28 : nsCOMPtr<nsIPrompt> prompt;
1294 14 : GetCallback(prompt);
1295 14 : if (!prompt)
1296 14 : return NS_ERROR_NO_INTERFACE;
1297 :
1298 0 : prompt->Confirm(nsnull, messageString, &repost);
1299 0 : if (!repost)
1300 0 : return NS_ERROR_FAILURE;
1301 : }
1302 :
1303 0 : return rv;
1304 : }
1305 :
1306 : nsresult
1307 2 : nsHttpChannel::ProxyFailover()
1308 : {
1309 2 : LOG(("nsHttpChannel::ProxyFailover [this=%p]\n", this));
1310 :
1311 : nsresult rv;
1312 :
1313 : nsCOMPtr<nsIProtocolProxyService> pps =
1314 4 : do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1315 2 : if (NS_FAILED(rv))
1316 0 : return rv;
1317 :
1318 4 : nsCOMPtr<nsIProxyInfo> pi;
1319 4 : rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
1320 4 : getter_AddRefs(pi));
1321 2 : if (NS_FAILED(rv))
1322 0 : return rv;
1323 :
1324 : // XXXbz so where does this codepath remove us from the loadgroup,
1325 : // exactly?
1326 2 : return AsyncDoReplaceWithProxy(pi);
1327 : }
1328 :
1329 : void
1330 10 : nsHttpChannel::HandleAsyncReplaceWithProxy()
1331 : {
1332 10 : NS_PRECONDITION(!mCallOnResume, "How did that happen?");
1333 :
1334 10 : if (mSuspendCount) {
1335 0 : LOG(("Waiting until resume to do async proxy replacement [this=%p]\n",
1336 : this));
1337 0 : mCallOnResume = &nsHttpChannel::HandleAsyncReplaceWithProxy;
1338 0 : return;
1339 : }
1340 :
1341 10 : nsresult status = mStatus;
1342 :
1343 20 : nsCOMPtr<nsIProxyInfo> pi;
1344 10 : pi.swap(mTargetProxyInfo);
1345 10 : if (!mCanceled) {
1346 10 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncReplaceWithProxy);
1347 10 : status = AsyncDoReplaceWithProxy(pi);
1348 10 : if (NS_SUCCEEDED(status))
1349 : return;
1350 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueHandleAsyncReplaceWithProxy);
1351 : }
1352 :
1353 0 : if (NS_FAILED(status)) {
1354 0 : ContinueHandleAsyncReplaceWithProxy(status);
1355 : }
1356 : }
1357 :
1358 : nsresult
1359 10 : nsHttpChannel::ContinueHandleAsyncReplaceWithProxy(nsresult status)
1360 : {
1361 10 : if (mLoadGroup && NS_SUCCEEDED(status)) {
1362 0 : mLoadGroup->RemoveRequest(this, nsnull, mStatus);
1363 : }
1364 10 : else if (NS_FAILED(status)) {
1365 1 : AsyncAbort(status);
1366 : }
1367 :
1368 : // Return NS_OK here, even it seems to be breaking the async function stack
1369 : // contract (i.e. passing the result code to a function bellow).
1370 : // ContinueHandleAsyncReplaceWithProxy will always be at the bottom of the
1371 : // stack. If we would return the failure code, the async function stack
1372 : // logic would cancel the channel synchronously, which is undesired after
1373 : // invoking AsyncAbort above.
1374 10 : return NS_OK;
1375 : }
1376 :
1377 : void
1378 0 : nsHttpChannel::HandleAsyncRedirectChannelToHttps()
1379 : {
1380 0 : NS_PRECONDITION(!mCallOnResume, "How did that happen?");
1381 :
1382 0 : if (mSuspendCount) {
1383 0 : LOG(("Waiting until resume to do async redirect to https [this=%p]\n", this));
1384 0 : mCallOnResume = &nsHttpChannel::HandleAsyncRedirectChannelToHttps;
1385 0 : return;
1386 : }
1387 :
1388 0 : nsresult rv = AsyncRedirectChannelToHttps();
1389 0 : if (NS_FAILED(rv))
1390 0 : ContinueAsyncRedirectChannelToHttps(rv);
1391 : }
1392 :
1393 : nsresult
1394 0 : nsHttpChannel::AsyncRedirectChannelToHttps()
1395 : {
1396 0 : nsresult rv = NS_OK;
1397 0 : LOG(("nsHttpChannel::HandleAsyncRedirectChannelToHttps() [STS]\n"));
1398 :
1399 0 : nsCOMPtr<nsIChannel> newChannel;
1400 0 : nsCOMPtr<nsIURI> upgradedURI;
1401 :
1402 0 : rv = mURI->Clone(getter_AddRefs(upgradedURI));
1403 0 : NS_ENSURE_SUCCESS(rv,rv);
1404 :
1405 0 : upgradedURI->SetScheme(NS_LITERAL_CSTRING("https"));
1406 :
1407 0 : PRInt32 oldPort = -1;
1408 0 : rv = mURI->GetPort(&oldPort);
1409 0 : if (NS_FAILED(rv)) return rv;
1410 :
1411 : // Keep any nonstandard ports so only the scheme is changed.
1412 : // For example:
1413 : // http://foo.com:80 -> https://foo.com:443
1414 : // http://foo.com:81 -> https://foo.com:81
1415 :
1416 0 : if (oldPort == 80 || oldPort == -1)
1417 0 : upgradedURI->SetPort(-1);
1418 : else
1419 0 : upgradedURI->SetPort(oldPort);
1420 :
1421 0 : nsCOMPtr<nsIIOService> ioService;
1422 0 : rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
1423 0 : NS_ENSURE_SUCCESS(rv, rv);
1424 :
1425 0 : rv = ioService->NewChannelFromURI(upgradedURI, getter_AddRefs(newChannel));
1426 0 : NS_ENSURE_SUCCESS(rv, rv);
1427 :
1428 0 : rv = SetupReplacementChannel(upgradedURI, newChannel, true, false);
1429 0 : NS_ENSURE_SUCCESS(rv, rv);
1430 :
1431 : // Inform consumers about this fake redirect
1432 0 : mRedirectChannel = newChannel;
1433 0 : PRUint32 flags = nsIChannelEventSink::REDIRECT_PERMANENT;
1434 :
1435 : PushRedirectAsyncFunc(
1436 0 : &nsHttpChannel::ContinueAsyncRedirectChannelToHttps);
1437 0 : rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
1438 :
1439 0 : if (NS_SUCCEEDED(rv))
1440 0 : rv = WaitForRedirectCallback();
1441 :
1442 0 : if (NS_FAILED(rv)) {
1443 0 : AutoRedirectVetoNotifier notifier(this);
1444 : PopRedirectAsyncFunc(
1445 0 : &nsHttpChannel::ContinueAsyncRedirectChannelToHttps);
1446 : }
1447 :
1448 0 : return rv;
1449 : }
1450 :
1451 : nsresult
1452 0 : nsHttpChannel::ContinueAsyncRedirectChannelToHttps(nsresult rv)
1453 : {
1454 0 : AutoRedirectVetoNotifier notifier(this);
1455 :
1456 0 : if (NS_FAILED(rv)) {
1457 : // Fill the failure status here, the update to https had been vetoed
1458 : // but from the security reasons we have to discard the whole channel
1459 : // load.
1460 0 : mStatus = rv;
1461 : }
1462 :
1463 0 : if (mLoadGroup)
1464 0 : mLoadGroup->RemoveRequest(this, nsnull, mStatus);
1465 :
1466 0 : if (NS_FAILED(rv)) {
1467 : // We have to manually notify the listener because there is not any pump
1468 : // that would call our OnStart/StopRequest after resume from waiting for
1469 : // the redirect callback.
1470 0 : DoNotifyListener();
1471 0 : return rv;
1472 : }
1473 :
1474 : // Make sure to do this _after_ calling OnChannelRedirect
1475 0 : mRedirectChannel->SetOriginalURI(mOriginalURI);
1476 :
1477 : // And now, notify observers the deprecated way
1478 0 : nsCOMPtr<nsIHttpEventSink> httpEventSink;
1479 0 : GetCallback(httpEventSink);
1480 0 : if (httpEventSink) {
1481 : // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
1482 : // versions.
1483 0 : rv = httpEventSink->OnRedirect(this, mRedirectChannel);
1484 0 : if (NS_FAILED(rv)) {
1485 0 : mStatus = rv;
1486 0 : DoNotifyListener();
1487 0 : return rv;
1488 : }
1489 : }
1490 :
1491 : // open new channel
1492 0 : rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
1493 0 : if (NS_FAILED(rv)) {
1494 0 : mStatus = rv;
1495 0 : DoNotifyListener();
1496 0 : return rv;
1497 : }
1498 :
1499 0 : mStatus = NS_BINDING_REDIRECTED;
1500 :
1501 0 : notifier.RedirectSucceeded();
1502 :
1503 : // disconnect from the old listeners...
1504 0 : mListener = nsnull;
1505 0 : mListenerContext = nsnull;
1506 :
1507 : // ...and the old callbacks
1508 0 : mCallbacks = nsnull;
1509 0 : mProgressSink = nsnull;
1510 :
1511 0 : return rv;
1512 : }
1513 :
1514 : nsresult
1515 12 : nsHttpChannel::AsyncDoReplaceWithProxy(nsIProxyInfo* pi)
1516 : {
1517 12 : LOG(("nsHttpChannel::AsyncDoReplaceWithProxy [this=%p pi=%p]", this, pi));
1518 : nsresult rv;
1519 :
1520 24 : nsCOMPtr<nsIChannel> newChannel;
1521 12 : rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel));
1522 12 : if (NS_FAILED(rv))
1523 0 : return rv;
1524 :
1525 12 : rv = SetupReplacementChannel(mURI, newChannel, true, true);
1526 12 : if (NS_FAILED(rv))
1527 0 : return rv;
1528 :
1529 : // Inform consumers about this fake redirect
1530 12 : mRedirectChannel = newChannel;
1531 12 : PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
1532 :
1533 12 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
1534 12 : rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, flags);
1535 :
1536 12 : if (NS_SUCCEEDED(rv))
1537 12 : rv = WaitForRedirectCallback();
1538 :
1539 12 : if (NS_FAILED(rv)) {
1540 0 : AutoRedirectVetoNotifier notifier(this);
1541 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueDoReplaceWithProxy);
1542 : }
1543 :
1544 12 : return rv;
1545 : }
1546 :
1547 : nsresult
1548 12 : nsHttpChannel::ContinueDoReplaceWithProxy(nsresult rv)
1549 : {
1550 24 : AutoRedirectVetoNotifier notifier(this);
1551 :
1552 12 : if (NS_FAILED(rv))
1553 2 : return rv;
1554 :
1555 10 : NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
1556 :
1557 : // Make sure to do this _after_ calling OnChannelRedirect
1558 10 : mRedirectChannel->SetOriginalURI(mOriginalURI);
1559 :
1560 : // open new channel
1561 10 : rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
1562 10 : if (NS_FAILED(rv))
1563 0 : return rv;
1564 :
1565 10 : mStatus = NS_BINDING_REDIRECTED;
1566 :
1567 10 : notifier.RedirectSucceeded();
1568 :
1569 : // disconnect from the old listeners...
1570 10 : mListener = nsnull;
1571 10 : mListenerContext = nsnull;
1572 :
1573 : // ...and the old callbacks
1574 10 : mCallbacks = nsnull;
1575 10 : mProgressSink = nsnull;
1576 :
1577 10 : return rv;
1578 : }
1579 :
1580 : nsresult
1581 10 : nsHttpChannel::ResolveProxy()
1582 : {
1583 10 : LOG(("nsHttpChannel::ResolveProxy [this=%p]\n", this));
1584 :
1585 : nsresult rv;
1586 :
1587 : nsCOMPtr<nsIProtocolProxyService> pps =
1588 20 : do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1589 10 : if (NS_FAILED(rv))
1590 0 : return rv;
1591 :
1592 10 : PRUint32 resolveFlags = 0;
1593 10 : if (mConnectionInfo->ProxyInfo())
1594 10 : mConnectionInfo->ProxyInfo()->GetResolveFlags(&resolveFlags);
1595 :
1596 10 : return pps->AsyncResolve(mURI, resolveFlags, this, getter_AddRefs(mProxyRequest));
1597 : }
1598 :
1599 : bool
1600 545 : nsHttpChannel::ResponseWouldVary()
1601 : {
1602 : nsresult rv;
1603 1090 : nsCAutoString buf, metaKey;
1604 545 : mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
1605 545 : if (!buf.IsEmpty()) {
1606 44 : NS_NAMED_LITERAL_CSTRING(prefix, "request-");
1607 :
1608 : // enumerate the elements of the Vary header...
1609 22 : char *val = buf.BeginWriting(); // going to munge buf
1610 22 : char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1611 22 : while (token) {
1612 22 : LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
1613 : "processing %s\n",
1614 : this, token));
1615 : //
1616 : // if "*", then assume response would vary. technically speaking,
1617 : // "Vary: header, *" is not permitted, but we allow it anyways.
1618 : //
1619 : // We hash values of cookie-headers for the following reasons:
1620 : //
1621 : // 1- cookies can be very large in size
1622 : //
1623 : // 2- cookies may contain sensitive information. (for parity with
1624 : // out policy of not storing Set-cookie headers in the cache
1625 : // meta data, we likewise do not want to store cookie headers
1626 : // here.)
1627 : //
1628 22 : if (*token == '*')
1629 0 : return true; // if we encounter this, just get out of here
1630 :
1631 : // build cache meta data key...
1632 22 : metaKey = prefix + nsDependentCString(token);
1633 :
1634 : // check the last value of the given request header to see if it has
1635 : // since changed. if so, then indeed the cached response is invalid.
1636 44 : nsXPIDLCString lastVal;
1637 22 : mCacheEntry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
1638 22 : LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
1639 : "stored value = %c%s%c\n", this, '"', lastVal.get(), '"'));
1640 :
1641 : // Look for value of "Cookie" in the request headers
1642 22 : nsHttpAtom atom = nsHttp::ResolveAtom(token);
1643 22 : const char *newVal = mRequestHead.PeekHeader(atom);
1644 22 : if (!lastVal.IsEmpty()) {
1645 : // value for this header in cache, but no value in request
1646 20 : if (!newVal)
1647 1 : return true; // yes - response would vary
1648 :
1649 : // If this is a cookie-header, stored metadata is not
1650 : // the value itself but the hash. So we also hash the
1651 : // outgoing value here in order to compare the hashes
1652 38 : nsCAutoString hash;
1653 19 : if (atom == nsHttp::Cookie) {
1654 4 : rv = Hash(newVal, hash);
1655 : // If hash failed, be conservative (the cached hash
1656 : // exists at this point) and claim response would vary
1657 4 : if (NS_FAILED(rv))
1658 0 : return true;
1659 4 : newVal = hash.get();
1660 :
1661 4 : LOG(("nsHttpChannel::ResponseWouldVary [this=%x] " \
1662 : "set-cookie value hashed to %s\n",
1663 : this, newVal));
1664 : }
1665 :
1666 19 : if (strcmp(newVal, lastVal))
1667 10 : return true; // yes, response would vary
1668 :
1669 2 : } else if (newVal) { // old value is empty, but newVal is set
1670 1 : return true;
1671 : }
1672 :
1673 : // next token...
1674 32 : token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1675 : }
1676 : }
1677 533 : return false;
1678 : }
1679 :
1680 : // We need to have an implementation of this function just so that we can keep
1681 : // all references to mCallOnResume of type nsHttpChannel: it's not OK in C++
1682 : // to set a member function ptr to a base class function.
1683 : void
1684 36 : nsHttpChannel::HandleAsyncAbort()
1685 : {
1686 36 : HttpAsyncAborter<nsHttpChannel>::HandleAsyncAbort();
1687 36 : }
1688 :
1689 :
1690 : nsresult
1691 8 : nsHttpChannel::Hash(const char *buf, nsACString &hash)
1692 : {
1693 : nsresult rv;
1694 8 : if (!mHasher) {
1695 6 : mHasher = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
1696 6 : if (NS_FAILED(rv)) {
1697 0 : LOG(("nsHttpChannel: Failed to instantiate crypto-hasher"));
1698 0 : return rv;
1699 : }
1700 : }
1701 :
1702 8 : rv = mHasher->Init(nsICryptoHash::SHA1);
1703 8 : NS_ENSURE_SUCCESS(rv, rv);
1704 :
1705 8 : rv = mHasher->Update(reinterpret_cast<unsigned const char*>(buf),
1706 8 : strlen(buf));
1707 8 : NS_ENSURE_SUCCESS(rv, rv);
1708 :
1709 8 : rv = mHasher->Finish(true, hash);
1710 8 : NS_ENSURE_SUCCESS(rv, rv);
1711 :
1712 8 : return NS_OK;
1713 : }
1714 :
1715 : //-----------------------------------------------------------------------------
1716 : // nsHttpChannel <byte-range>
1717 : //-----------------------------------------------------------------------------
1718 :
1719 : nsresult
1720 2 : nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
1721 : {
1722 : // cached content has been found to be partial, add necessary request
1723 : // headers to complete cache entry.
1724 :
1725 : // use strongest validator available...
1726 2 : const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
1727 2 : if (!val)
1728 1 : val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
1729 2 : if (!val) {
1730 : // if we hit this code it means mCachedResponseHead->IsResumable() is
1731 : // either broken or not being called.
1732 0 : NS_NOTREACHED("no cache validator");
1733 0 : return NS_ERROR_FAILURE;
1734 : }
1735 :
1736 : char buf[32];
1737 2 : PR_snprintf(buf, sizeof(buf), "bytes=%u-", partialLen);
1738 :
1739 2 : mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
1740 2 : mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
1741 :
1742 2 : return NS_OK;
1743 : }
1744 :
1745 : nsresult
1746 2 : nsHttpChannel::ProcessPartialContent()
1747 : {
1748 : // ok, we've just received a 206
1749 : //
1750 : // we need to stream whatever data is in the cache out first, and then
1751 : // pick up whatever data is on the wire, writing it into the cache.
1752 :
1753 2 : LOG(("nsHttpChannel::ProcessPartialContent [this=%p]\n", this));
1754 :
1755 2 : NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
1756 2 : NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
1757 :
1758 : // Make sure to clear bogus content-encodings before looking at the header
1759 2 : ClearBogusContentEncodingIfNeeded();
1760 :
1761 : // Check if the content-encoding we now got is different from the one we
1762 : // got before
1763 2 : if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
1764 2 : mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
1765 : != 0) {
1766 0 : Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
1767 0 : return CallOnStartRequest();
1768 : }
1769 :
1770 :
1771 : // suspend the current transaction
1772 2 : nsresult rv = mTransactionPump->Suspend();
1773 2 : if (NS_FAILED(rv)) return rv;
1774 :
1775 : // merge any new headers with the cached response headers
1776 2 : rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
1777 2 : if (NS_FAILED(rv)) return rv;
1778 :
1779 : // update the cached response head
1780 4 : nsCAutoString head;
1781 2 : mCachedResponseHead->Flatten(head, true);
1782 2 : rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
1783 2 : if (NS_FAILED(rv)) return rv;
1784 :
1785 : // make the cached response be the current response
1786 2 : mResponseHead = mCachedResponseHead;
1787 :
1788 2 : UpdateInhibitPersistentCachingFlag();
1789 :
1790 2 : rv = UpdateExpirationTime();
1791 2 : if (NS_FAILED(rv)) return rv;
1792 :
1793 : // notify observers interested in looking at a response that has been
1794 : // merged with any cached headers (http-on-examine-merged-response).
1795 2 : gHttpHandler->OnExamineMergedResponse(this);
1796 :
1797 : // the cached content is valid, although incomplete.
1798 2 : mCachedContentIsValid = true;
1799 2 : return ReadFromCache();
1800 : }
1801 :
1802 : nsresult
1803 2 : nsHttpChannel::OnDoneReadingPartialCacheEntry(bool *streamDone)
1804 : {
1805 : nsresult rv;
1806 :
1807 2 : LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%p]", this));
1808 :
1809 : // by default, assume we would have streamed all data or failed...
1810 2 : *streamDone = true;
1811 :
1812 : // setup cache listener to append to cache entry
1813 : PRUint32 size;
1814 2 : rv = mCacheEntry->GetDataSize(&size);
1815 2 : if (NS_FAILED(rv)) return rv;
1816 :
1817 2 : rv = InstallCacheListener(size);
1818 2 : if (NS_FAILED(rv)) return rv;
1819 :
1820 : // need to track the logical offset of the data being sent to our listener
1821 2 : mLogicalOffset = size;
1822 :
1823 : // we're now completing the cached content, so we can clear this flag.
1824 : // this puts us in the state of a regular download.
1825 2 : mCachedContentIsPartial = false;
1826 :
1827 : // resume the transaction if it exists, otherwise the pipe contained the
1828 : // remaining part of the document and we've now streamed all of the data.
1829 2 : if (mTransactionPump) {
1830 2 : rv = mTransactionPump->Resume();
1831 2 : if (NS_SUCCEEDED(rv))
1832 2 : *streamDone = false;
1833 : }
1834 : else
1835 0 : NS_NOTREACHED("no transaction");
1836 2 : return rv;
1837 : }
1838 :
1839 : //-----------------------------------------------------------------------------
1840 : // nsHttpChannel <cache>
1841 : //-----------------------------------------------------------------------------
1842 :
1843 : nsresult
1844 25 : nsHttpChannel::ProcessNotModified()
1845 : {
1846 : nsresult rv;
1847 :
1848 25 : LOG(("nsHttpChannel::ProcessNotModified [this=%p]\n", this));
1849 :
1850 25 : if (mCustomConditionalRequest) {
1851 0 : LOG(("Bypassing ProcessNotModified due to custom conditional headers"));
1852 0 : return NS_ERROR_FAILURE;
1853 : }
1854 :
1855 25 : NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
1856 6 : NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
1857 :
1858 : // merge any new headers with the cached response headers
1859 6 : rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
1860 6 : if (NS_FAILED(rv)) return rv;
1861 :
1862 : // update the cached response head
1863 12 : nsCAutoString head;
1864 6 : mCachedResponseHead->Flatten(head, true);
1865 6 : rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
1866 6 : if (NS_FAILED(rv)) return rv;
1867 :
1868 : // make the cached response be the current response
1869 6 : mResponseHead = mCachedResponseHead;
1870 :
1871 6 : UpdateInhibitPersistentCachingFlag();
1872 :
1873 6 : rv = UpdateExpirationTime();
1874 6 : if (NS_FAILED(rv)) return rv;
1875 :
1876 6 : rv = AddCacheEntryHeaders(mCacheEntry);
1877 6 : if (NS_FAILED(rv)) return rv;
1878 :
1879 : // notify observers interested in looking at a reponse that has been
1880 : // merged with any cached headers
1881 6 : gHttpHandler->OnExamineMergedResponse(this);
1882 :
1883 6 : mCachedContentIsValid = true;
1884 6 : rv = ReadFromCache();
1885 6 : if (NS_FAILED(rv)) return rv;
1886 :
1887 6 : mTransactionReplaced = true;
1888 6 : return NS_OK;
1889 : }
1890 :
1891 : nsresult
1892 837 : nsHttpChannel::ProcessFallback(bool *waitingForRedirectCallback)
1893 : {
1894 837 : LOG(("nsHttpChannel::ProcessFallback [this=%p]\n", this));
1895 : nsresult rv;
1896 :
1897 837 : *waitingForRedirectCallback = false;
1898 837 : mFallingBack = false;
1899 :
1900 : // At this point a load has failed (either due to network problems
1901 : // or an error returned on the server). Perform an application
1902 : // cache fallback if we have a URI to fall back to.
1903 837 : if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
1904 828 : LOG((" choosing not to fallback [%p,%s,%d]",
1905 : mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
1906 828 : return NS_OK;
1907 : }
1908 :
1909 : // Make sure the fallback entry hasn't been marked as a foreign
1910 : // entry.
1911 : PRUint32 fallbackEntryType;
1912 9 : rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
1913 9 : NS_ENSURE_SUCCESS(rv, rv);
1914 :
1915 9 : if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
1916 : // This cache points to a fallback that refers to a different
1917 : // manifest. Refuse to fall back.
1918 0 : return NS_OK;
1919 : }
1920 :
1921 9 : NS_ASSERTION(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
1922 : "Fallback entry not marked correctly!");
1923 :
1924 : // Kill any opportunistic cache entry, and disable opportunistic
1925 : // caching for the fallback.
1926 9 : if (mOfflineCacheEntry) {
1927 0 : mOfflineCacheEntry->Doom();
1928 0 : mOfflineCacheEntry = 0;
1929 0 : mOfflineCacheAccess = 0;
1930 : }
1931 :
1932 9 : mCacheForOfflineUse = false;
1933 9 : mCachingOpportunistically = false;
1934 9 : mOfflineCacheClientID.Truncate();
1935 9 : mOfflineCacheEntry = 0;
1936 9 : mOfflineCacheAccess = 0;
1937 :
1938 : // Close the current cache entry.
1939 9 : if (mCacheEntry)
1940 6 : CloseCacheEntry(true);
1941 :
1942 : // Create a new channel to load the fallback entry.
1943 18 : nsRefPtr<nsIChannel> newChannel;
1944 9 : rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
1945 9 : NS_ENSURE_SUCCESS(rv, rv);
1946 :
1947 9 : rv = SetupReplacementChannel(mURI, newChannel, true, false);
1948 9 : NS_ENSURE_SUCCESS(rv, rv);
1949 :
1950 : // Make sure the new channel loads from the fallback key.
1951 : nsCOMPtr<nsIHttpChannelInternal> httpInternal =
1952 18 : do_QueryInterface(newChannel, &rv);
1953 9 : NS_ENSURE_SUCCESS(rv, rv);
1954 :
1955 9 : rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
1956 9 : NS_ENSURE_SUCCESS(rv, rv);
1957 :
1958 : // ... and fallbacks should only load from the cache.
1959 9 : PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
1960 9 : rv = newChannel->SetLoadFlags(newLoadFlags);
1961 :
1962 : // Inform consumers about this fake redirect
1963 9 : mRedirectChannel = newChannel;
1964 9 : PRUint32 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
1965 :
1966 9 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
1967 9 : rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
1968 :
1969 9 : if (NS_SUCCEEDED(rv))
1970 9 : rv = WaitForRedirectCallback();
1971 :
1972 9 : if (NS_FAILED(rv)) {
1973 0 : AutoRedirectVetoNotifier notifier(this);
1974 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessFallback);
1975 0 : return rv;
1976 : }
1977 :
1978 : // Indicate we are now waiting for the asynchronous redirect callback
1979 : // if all went OK.
1980 9 : *waitingForRedirectCallback = true;
1981 9 : return NS_OK;
1982 : }
1983 :
1984 : nsresult
1985 9 : nsHttpChannel::ContinueProcessFallback(nsresult rv)
1986 : {
1987 18 : AutoRedirectVetoNotifier notifier(this);
1988 :
1989 9 : if (NS_FAILED(rv))
1990 5 : return rv;
1991 :
1992 4 : NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
1993 :
1994 : // Make sure to do this _after_ calling OnChannelRedirect
1995 4 : mRedirectChannel->SetOriginalURI(mOriginalURI);
1996 :
1997 4 : rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
1998 4 : if (NS_FAILED(rv))
1999 0 : return rv;
2000 :
2001 : // close down this channel
2002 4 : Cancel(NS_BINDING_REDIRECTED);
2003 :
2004 4 : notifier.RedirectSucceeded();
2005 :
2006 : // disconnect from our listener
2007 4 : mListener = 0;
2008 4 : mListenerContext = 0;
2009 :
2010 : // and from our callbacks
2011 4 : mCallbacks = nsnull;
2012 4 : mProgressSink = nsnull;
2013 :
2014 4 : mFallingBack = true;
2015 :
2016 4 : return NS_OK;
2017 : }
2018 :
2019 : // Determines if a request is a byte range request for a subrange,
2020 : // i.e. is a byte range request, but not a 0- byte range request.
2021 : static bool
2022 2950 : IsSubRangeRequest(nsHttpRequestHead &aRequestHead)
2023 : {
2024 2950 : if (!aRequestHead.PeekHeader(nsHttp::Range))
2025 2927 : return false;
2026 46 : nsCAutoString byteRange;
2027 23 : aRequestHead.GetHeader(nsHttp::Range, byteRange);
2028 23 : return !byteRange.EqualsLiteral("bytes=0-");
2029 : }
2030 :
2031 : nsresult
2032 3472 : nsHttpChannel::OpenCacheEntry()
2033 : {
2034 : nsresult rv;
2035 :
2036 3472 : mAsyncCacheOpen = false;
2037 3472 : mLoadedFromApplicationCache = false;
2038 :
2039 3472 : LOG(("nsHttpChannel::OpenCacheEntry [this=%p]", this));
2040 :
2041 : // make sure we're not abusing this function
2042 3472 : NS_PRECONDITION(!mCacheEntry, "cache entry already open");
2043 :
2044 6944 : nsCAutoString cacheKey;
2045 :
2046 3472 : if (mRequestHead.Method() == nsHttp::Post) {
2047 : // If the post id is already set then this is an attempt to replay
2048 : // a post transaction via the cache. Otherwise, we need a unique
2049 : // post id for this transaction.
2050 176 : if (mPostID == 0)
2051 176 : mPostID = gHttpHandler->GenerateUniqueID();
2052 : }
2053 3831 : else if ((mRequestHead.Method() != nsHttp::Get) &&
2054 535 : (mRequestHead.Method() != nsHttp::Head)) {
2055 : // don't use the cache for other types of requests
2056 519 : return NS_OK;
2057 : }
2058 :
2059 2953 : if (mResuming) {
2060 : // We don't support caching for requests initiated
2061 : // via nsIResumableChannel.
2062 27 : return NS_OK;
2063 : }
2064 :
2065 : // Don't cache byte range requests which are subranges, only cache 0-
2066 : // byte range requests.
2067 2926 : if (IsSubRangeRequest(mRequestHead))
2068 23 : return NS_OK;
2069 :
2070 2903 : GenerateCacheKey(mPostID, cacheKey);
2071 :
2072 : // Set the desired cache access mode accordingly...
2073 : nsCacheAccessMode accessRequested;
2074 2903 : rv = DetermineCacheAccess(&accessRequested);
2075 2903 : if (NS_FAILED(rv)) return rv;
2076 :
2077 1899 : if (!mApplicationCache && mInheritApplicationCache) {
2078 : // Pick up an application cache from the notification
2079 : // callbacks if available
2080 3790 : nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
2081 1895 : GetCallback(appCacheContainer);
2082 :
2083 1895 : if (appCacheContainer) {
2084 0 : appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
2085 : }
2086 : }
2087 :
2088 1899 : if (!mApplicationCache &&
2089 : (mChooseApplicationCache || (mLoadFlags & LOAD_CHECK_OFFLINE_CACHE))) {
2090 : // We're supposed to load from an application cache, but
2091 : // one was not supplied by the load group. Ask the
2092 : // application cache service to choose one for us.
2093 : nsCOMPtr<nsIApplicationCacheService> appCacheService =
2094 64 : do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
2095 32 : if (appCacheService) {
2096 32 : nsresult rv = appCacheService->ChooseApplicationCache
2097 32 : (cacheKey, getter_AddRefs(mApplicationCache));
2098 32 : NS_ENSURE_SUCCESS(rv, rv);
2099 : }
2100 : }
2101 :
2102 3798 : nsCOMPtr<nsICacheSession> session;
2103 :
2104 : // If we have an application cache, we check it first.
2105 1899 : if (mApplicationCache) {
2106 24 : nsCAutoString appCacheClientID;
2107 12 : mApplicationCache->GetClientID(appCacheClientID);
2108 :
2109 : nsCOMPtr<nsICacheService> serv =
2110 24 : do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
2111 12 : NS_ENSURE_SUCCESS(rv, rv);
2112 :
2113 12 : rv = serv->CreateSession(appCacheClientID.get(),
2114 : nsICache::STORE_OFFLINE,
2115 : nsICache::STREAM_BASED,
2116 12 : getter_AddRefs(session));
2117 12 : NS_ENSURE_SUCCESS(rv, rv);
2118 :
2119 12 : if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
2120 : // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
2121 0 : rv = session->OpenCacheEntry(cacheKey,
2122 : nsICache::ACCESS_READ, false,
2123 0 : getter_AddRefs(mCacheEntry));
2124 0 : if (NS_SUCCEEDED(rv)) {
2125 0 : mCacheEntry->GetAccessGranted(&mCacheAccess);
2126 0 : LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]",
2127 : this, mCacheAccess));
2128 0 : mLoadedFromApplicationCache = true;
2129 0 : return NS_OK;
2130 0 : } else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
2131 0 : LOG(("bypassing local cache since it is busy\n"));
2132 : // Don't try to load normal cache entry
2133 0 : return NS_ERROR_NOT_AVAILABLE;
2134 : }
2135 : } else {
2136 : mOnCacheEntryAvailableCallback =
2137 12 : &nsHttpChannel::OnOfflineCacheEntryAvailable;
2138 : // We open with ACCESS_READ only, because we don't want to
2139 : // overwrite the offline cache entry non-atomically.
2140 : // ACCESS_READ will prevent us from writing to the offline
2141 : // cache as a normal cache entry.
2142 12 : rv = session->AsyncOpenCacheEntry(cacheKey,
2143 : nsICache::ACCESS_READ,
2144 12 : this);
2145 :
2146 12 : if (NS_SUCCEEDED(rv)) {
2147 12 : mAsyncCacheOpen = true;
2148 12 : return NS_OK;
2149 : }
2150 : }
2151 :
2152 : // sync or async opening failed
2153 : return OnOfflineCacheEntryAvailable(nsnull, nsICache::ACCESS_NONE,
2154 0 : rv, true);
2155 : }
2156 :
2157 1887 : return OpenNormalCacheEntry(true);
2158 : }
2159 :
2160 : nsresult
2161 12 : nsHttpChannel::OnOfflineCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
2162 : nsCacheAccessMode aAccess,
2163 : nsresult aEntryStatus,
2164 : bool aIsSync)
2165 : {
2166 : nsresult rv;
2167 :
2168 12 : if (NS_SUCCEEDED(aEntryStatus)) {
2169 : // We successfully opened an offline cache session and the entry,
2170 : // so indicate we will load from the offline cache.
2171 4 : mLoadedFromApplicationCache = true;
2172 4 : mCacheEntry = aEntry;
2173 4 : mCacheAccess = aAccess;
2174 : }
2175 :
2176 12 : if (mCanceled && NS_FAILED(mStatus)) {
2177 0 : LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
2178 0 : return mStatus;
2179 : }
2180 :
2181 12 : if (NS_SUCCEEDED(aEntryStatus))
2182 : // Called from OnCacheEntryAvailable, advance to the next state
2183 4 : return Connect(false);
2184 :
2185 8 : if (!mCacheForOfflineUse && !mFallbackChannel) {
2186 16 : nsCAutoString cacheKey;
2187 8 : GenerateCacheKey(mPostID, cacheKey);
2188 :
2189 : // Check for namespace match.
2190 16 : nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
2191 8 : rv = mApplicationCache->GetMatchingNamespace
2192 8 : (cacheKey, getter_AddRefs(namespaceEntry));
2193 8 : if (NS_FAILED(rv) && !aIsSync)
2194 0 : return Connect(false);
2195 8 : NS_ENSURE_SUCCESS(rv, rv);
2196 :
2197 8 : PRUint32 namespaceType = 0;
2198 16 : if (!namespaceEntry ||
2199 8 : NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
2200 : (namespaceType &
2201 : (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
2202 : nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC |
2203 : nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
2204 : // When loading from an application cache, only items
2205 : // on the whitelist or matching a
2206 : // fallback/opportunistic namespace should hit the
2207 : // network...
2208 0 : mLoadFlags |= LOAD_ONLY_FROM_CACHE;
2209 :
2210 : // ... and if there were an application cache entry,
2211 : // we would have found it earlier.
2212 0 : return aIsSync ? NS_ERROR_CACHE_KEY_NOT_FOUND : Connect(false);
2213 : }
2214 :
2215 8 : if (namespaceType &
2216 : nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
2217 8 : rv = namespaceEntry->GetData(mFallbackKey);
2218 8 : if (NS_FAILED(rv) && !aIsSync)
2219 0 : return Connect(false);
2220 8 : NS_ENSURE_SUCCESS(rv, rv);
2221 : }
2222 :
2223 8 : if ((namespaceType &
2224 : nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC) &&
2225 : mLoadFlags & LOAD_DOCUMENT_URI) {
2226 : // Document loads for items in an opportunistic namespace
2227 : // should be placed in the offline cache.
2228 0 : nsCString clientID;
2229 0 : mApplicationCache->GetClientID(clientID);
2230 :
2231 0 : mCacheForOfflineUse = !clientID.IsEmpty();
2232 0 : SetOfflineCacheClientID(clientID);
2233 0 : mCachingOpportunistically = true;
2234 : }
2235 : }
2236 :
2237 8 : return OpenNormalCacheEntry(aIsSync);
2238 : }
2239 :
2240 :
2241 : nsresult
2242 1895 : nsHttpChannel::OpenNormalCacheEntry(bool aIsSync)
2243 : {
2244 1895 : NS_ASSERTION(!mCacheEntry, "We have already mCacheEntry");
2245 :
2246 : nsresult rv;
2247 :
2248 3790 : nsCAutoString cacheKey;
2249 1895 : GenerateCacheKey(mPostID, cacheKey);
2250 :
2251 1895 : nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
2252 :
2253 3790 : nsCOMPtr<nsICacheSession> session;
2254 : rv = gHttpHandler->GetCacheSession(storagePolicy,
2255 1895 : getter_AddRefs(session));
2256 1895 : if (NS_FAILED(rv)) return rv;
2257 :
2258 : nsCacheAccessMode accessRequested;
2259 1895 : rv = DetermineCacheAccess(&accessRequested);
2260 1895 : if (NS_FAILED(rv)) return rv;
2261 :
2262 1895 : if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
2263 46 : if (!aIsSync) {
2264 : // Unexpected state: we were called from OnCacheEntryAvailable(),
2265 : // so LOAD_BYPASS_LOCAL_CACHE_IF_BUSY shouldn't be set. Unless
2266 : // somebody altered mLoadFlags between OpenCacheEntry() and
2267 : // OnCacheEntryAvailable()...
2268 : NS_WARNING(
2269 : "OpenNormalCacheEntry() called from OnCacheEntryAvailable() "
2270 0 : "when LOAD_BYPASS_LOCAL_CACHE_IF_BUSY was specified");
2271 : }
2272 :
2273 : // must use synchronous open for LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
2274 46 : rv = session->OpenCacheEntry(cacheKey, accessRequested, false,
2275 46 : getter_AddRefs(mCacheEntry));
2276 46 : if (NS_SUCCEEDED(rv)) {
2277 43 : mCacheEntry->GetAccessGranted(&mCacheAccess);
2278 43 : LOG(("nsHttpChannel::OpenCacheEntry [this=%p grantedAccess=%d]",
2279 : this, mCacheAccess));
2280 : }
2281 3 : else if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
2282 3 : LOG(("bypassing local cache since it is busy\n"));
2283 3 : rv = NS_ERROR_NOT_AVAILABLE;
2284 : }
2285 : }
2286 : else {
2287 : mOnCacheEntryAvailableCallback =
2288 1849 : &nsHttpChannel::OnNormalCacheEntryAvailable;
2289 1849 : rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
2290 1849 : if (NS_SUCCEEDED(rv)) {
2291 1849 : mAsyncCacheOpen = true;
2292 1849 : return NS_OK;
2293 : }
2294 : }
2295 :
2296 46 : if (!aIsSync)
2297 : // Called from OnCacheEntryAvailable, advance to the next state
2298 0 : rv = Connect(false);
2299 :
2300 46 : return rv;
2301 : }
2302 :
2303 : nsresult
2304 1587 : nsHttpChannel::OnNormalCacheEntryAvailable(nsICacheEntryDescriptor *aEntry,
2305 : nsCacheAccessMode aAccess,
2306 : nsresult aEntryStatus,
2307 : bool aIsSync)
2308 : {
2309 1587 : NS_ASSERTION(!aIsSync, "aIsSync should be false");
2310 :
2311 1587 : if (NS_SUCCEEDED(aEntryStatus)) {
2312 1583 : mCacheEntry = aEntry;
2313 1583 : mCacheAccess = aAccess;
2314 : }
2315 :
2316 1587 : if (mCanceled && NS_FAILED(mStatus)) {
2317 14 : LOG(("channel was canceled [this=%p status=%x]\n", this, mStatus));
2318 14 : return mStatus;
2319 : }
2320 :
2321 1573 : if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(aEntryStatus))
2322 : // if this channel is only allowed to pull from the cache, then
2323 : // we must fail if we were unable to open a cache entry.
2324 4 : return NS_ERROR_DOCUMENT_NOT_CACHED;
2325 :
2326 : // advance to the next state...
2327 1569 : return Connect(false);
2328 : }
2329 :
2330 :
2331 : nsresult
2332 24 : nsHttpChannel::OpenOfflineCacheEntryForWriting()
2333 : {
2334 : nsresult rv;
2335 :
2336 24 : LOG(("nsHttpChannel::OpenOfflineCacheEntryForWriting [this=%p]", this));
2337 :
2338 : // make sure we're not abusing this function
2339 24 : NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
2340 :
2341 24 : bool offline = gIOService->IsOffline();
2342 24 : if (offline) {
2343 : // only put things in the offline cache while online
2344 0 : return NS_OK;
2345 : }
2346 :
2347 24 : if (mRequestHead.Method() != nsHttp::Get) {
2348 : // only cache complete documents offline
2349 0 : return NS_OK;
2350 : }
2351 :
2352 : // Don't cache byte range requests which are subranges, only cache 0-
2353 : // byte range requests.
2354 24 : if (IsSubRangeRequest(mRequestHead))
2355 0 : return NS_OK;
2356 :
2357 48 : nsCAutoString cacheKey;
2358 24 : GenerateCacheKey(mPostID, cacheKey);
2359 :
2360 24 : NS_ENSURE_TRUE(!mOfflineCacheClientID.IsEmpty(),
2361 : NS_ERROR_NOT_AVAILABLE);
2362 :
2363 48 : nsCOMPtr<nsICacheSession> session;
2364 : nsCOMPtr<nsICacheService> serv =
2365 48 : do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
2366 24 : if (NS_FAILED(rv)) return rv;
2367 :
2368 24 : rv = serv->CreateSession(mOfflineCacheClientID.get(),
2369 : nsICache::STORE_OFFLINE,
2370 : nsICache::STREAM_BASED,
2371 24 : getter_AddRefs(session));
2372 24 : if (NS_FAILED(rv)) return rv;
2373 :
2374 24 : rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE,
2375 24 : false, getter_AddRefs(mOfflineCacheEntry));
2376 :
2377 24 : if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
2378 : // access to the cache entry has been denied (because the cache entry
2379 : // is probably in use by another channel). Either the cache is being
2380 : // read from (we're offline) or it's being updated elsewhere.
2381 0 : return NS_OK;
2382 : }
2383 :
2384 24 : if (NS_SUCCEEDED(rv)) {
2385 24 : mOfflineCacheEntry->GetAccessGranted(&mOfflineCacheAccess);
2386 24 : LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess));
2387 : }
2388 :
2389 24 : return rv;
2390 : }
2391 :
2392 : // Generates the proper cache-key for this instance of nsHttpChannel
2393 : nsresult
2394 5529 : nsHttpChannel::GenerateCacheKey(PRUint32 postID, nsACString &cacheKey)
2395 : {
2396 5529 : AssembleCacheKey(mFallbackChannel ? mFallbackKey.get() : mSpec.get(),
2397 11058 : postID, cacheKey);
2398 5529 : return NS_OK;
2399 : }
2400 :
2401 : // Assembles a cache-key from the given pieces of information and |mLoadFlags|
2402 : void
2403 5567 : nsHttpChannel::AssembleCacheKey(const char *spec, PRUint32 postID,
2404 : nsACString &cacheKey)
2405 : {
2406 5567 : cacheKey.Truncate();
2407 :
2408 5567 : if (mLoadFlags & LOAD_ANONYMOUS) {
2409 2 : cacheKey.AssignLiteral("anon&");
2410 : }
2411 :
2412 5567 : if (postID) {
2413 : char buf[32];
2414 190 : PR_snprintf(buf, sizeof(buf), "id=%x&", postID);
2415 190 : cacheKey.Append(buf);
2416 : }
2417 :
2418 5567 : if (!cacheKey.IsEmpty()) {
2419 192 : cacheKey.AppendLiteral("uri=");
2420 : }
2421 :
2422 : // Strip any trailing #ref from the URL before using it as the key
2423 5567 : const char *p = strchr(spec, '#');
2424 5567 : if (p)
2425 0 : cacheKey.Append(spec, p - spec);
2426 : else
2427 5567 : cacheKey.Append(spec);
2428 5567 : }
2429 :
2430 : // UpdateExpirationTime is called when a new response comes in from the server.
2431 : // It updates the stored response-time and sets the expiration time on the
2432 : // cache entry.
2433 : //
2434 : // From section 13.2.4 of RFC2616, we compute expiration time as follows:
2435 : //
2436 : // timeRemaining = freshnessLifetime - currentAge
2437 : // expirationTime = now + timeRemaining
2438 : //
2439 : nsresult
2440 1246 : nsHttpChannel::UpdateExpirationTime()
2441 : {
2442 1246 : NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
2443 :
2444 : nsresult rv;
2445 :
2446 1246 : PRUint32 expirationTime = 0;
2447 1246 : if (!mResponseHead->MustValidate()) {
2448 851 : PRUint32 freshnessLifetime = 0;
2449 :
2450 851 : rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
2451 851 : if (NS_FAILED(rv)) return rv;
2452 :
2453 851 : if (freshnessLifetime > 0) {
2454 613 : PRUint32 now = NowInSeconds(), currentAge = 0;
2455 :
2456 613 : rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, ¤tAge);
2457 613 : if (NS_FAILED(rv)) return rv;
2458 :
2459 613 : LOG(("freshnessLifetime = %u, currentAge = %u\n",
2460 : freshnessLifetime, currentAge));
2461 :
2462 613 : if (freshnessLifetime > currentAge) {
2463 612 : PRUint32 timeRemaining = freshnessLifetime - currentAge;
2464 : // be careful... now + timeRemaining may overflow
2465 612 : if (now + timeRemaining < now)
2466 76 : expirationTime = PRUint32(-1);
2467 : else
2468 536 : expirationTime = now + timeRemaining;
2469 : }
2470 : else
2471 1 : expirationTime = now;
2472 : }
2473 : }
2474 :
2475 1246 : rv = mCacheEntry->SetExpirationTime(expirationTime);
2476 1246 : NS_ENSURE_SUCCESS(rv, rv);
2477 :
2478 1246 : if (mOfflineCacheEntry) {
2479 24 : rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
2480 24 : NS_ENSURE_SUCCESS(rv, rv);
2481 : }
2482 :
2483 1246 : return NS_OK;
2484 : }
2485 :
2486 : // CheckCache is called from Connect after a cache entry has been opened for
2487 : // this URL but before going out to net. It's purpose is to set or clear the
2488 : // mCachedContentIsValid flag, and to configure an If-Modified-Since request
2489 : // if validation is required.
2490 : nsresult
2491 1616 : nsHttpChannel::CheckCache()
2492 : {
2493 1616 : nsresult rv = NS_OK;
2494 :
2495 1616 : LOG(("nsHTTPChannel::CheckCache enter [this=%p entry=%p access=%d]",
2496 : this, mCacheEntry.get(), mCacheAccess));
2497 :
2498 : // Be pessimistic: assume the cache entry has no useful data.
2499 1616 : mCachedContentIsValid = false;
2500 :
2501 : // Don't proceed unless we have opened a cache entry for reading.
2502 1616 : if (!mCacheEntry || !(mCacheAccess & nsICache::ACCESS_READ))
2503 1043 : return NS_OK;
2504 :
2505 1146 : nsXPIDLCString buf;
2506 :
2507 : // Get the method that was used to generate the cached response
2508 573 : rv = mCacheEntry->GetMetaDataElement("request-method", getter_Copies(buf));
2509 573 : NS_ENSURE_SUCCESS(rv, rv);
2510 :
2511 573 : nsHttpAtom method = nsHttp::ResolveAtom(buf);
2512 573 : if (method == nsHttp::Head) {
2513 : // The cached response does not contain an entity. We can only reuse
2514 : // the response if the current request is also HEAD.
2515 6 : if (mRequestHead.Method() != nsHttp::Head)
2516 6 : return NS_OK;
2517 : }
2518 567 : buf.Adopt(0);
2519 :
2520 : // We'll need this value in later computations...
2521 : PRUint32 lastModifiedTime;
2522 567 : rv = mCacheEntry->GetLastModified(&lastModifiedTime);
2523 567 : NS_ENSURE_SUCCESS(rv, rv);
2524 :
2525 : // Determine if this is the first time that this cache entry
2526 : // has been accessed during this session.
2527 : bool fromPreviousSession =
2528 567 : (gHttpHandler->SessionStartTime() > lastModifiedTime);
2529 :
2530 : // Get the cached HTTP response headers
2531 567 : rv = mCacheEntry->GetMetaDataElement("response-head", getter_Copies(buf));
2532 567 : NS_ENSURE_SUCCESS(rv, rv);
2533 :
2534 : // Parse the cached HTTP response headers
2535 567 : mCachedResponseHead = new nsHttpResponseHead();
2536 567 : if (!mCachedResponseHead)
2537 0 : return NS_ERROR_OUT_OF_MEMORY;
2538 567 : rv = mCachedResponseHead->Parse((char *) buf.get());
2539 567 : NS_ENSURE_SUCCESS(rv, rv);
2540 567 : buf.Adopt(0);
2541 :
2542 : // Don't bother to validate items that are read-only,
2543 : // unless they are read-only because of INHIBIT_CACHING or because
2544 : // we're updating the offline cache.
2545 : // Don't bother to validate if this is a fallback entry.
2546 577 : if (!mCacheForOfflineUse &&
2547 : (mLoadedFromApplicationCache ||
2548 : (mCacheAccess == nsICache::ACCESS_READ &&
2549 10 : !(mLoadFlags & INHIBIT_CACHING)) ||
2550 : mFallbackChannel)) {
2551 14 : mCachedContentIsValid = true;
2552 14 : return NS_OK;
2553 : }
2554 :
2555 553 : PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
2556 :
2557 : mCustomConditionalRequest =
2558 553 : mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
2559 553 : mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
2560 553 : mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
2561 553 : mRequestHead.PeekHeader(nsHttp::If_Match) ||
2562 2212 : mRequestHead.PeekHeader(nsHttp::If_Range);
2563 :
2564 553 : if (method != nsHttp::Head && !isCachedRedirect) {
2565 : // If the cached content-length is set and it does not match the data
2566 : // size of the cached content, then the cached response is partial...
2567 : // either we need to issue a byte range request or we need to refetch
2568 : // the entire document.
2569 532 : PRInt64 contentLength = mCachedResponseHead->ContentLength();
2570 532 : if (contentLength != PRInt64(-1)) {
2571 : PRUint32 size;
2572 531 : rv = mCacheEntry->GetDataSize(&size);
2573 531 : NS_ENSURE_SUCCESS(rv, rv);
2574 :
2575 531 : if (PRInt64(size) != contentLength) {
2576 8 : LOG(("Cached data size does not match the Content-Length header "
2577 : "[content-length=%lld size=%u]\n", PRInt64(contentLength), size));
2578 :
2579 : bool hasContentEncoding =
2580 8 : mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding)
2581 8 : != nsnull;
2582 26 : if ((PRInt64(size) < contentLength) &&
2583 : size > 0 &&
2584 6 : !hasContentEncoding &&
2585 5 : mCachedResponseHead->IsResumable() &&
2586 4 : !mCustomConditionalRequest &&
2587 3 : !mCachedResponseHead->NoStore()) {
2588 : // looks like a partial entry we can reuse
2589 2 : rv = SetupByteRangeRequest(size);
2590 2 : NS_ENSURE_SUCCESS(rv, rv);
2591 2 : mCachedContentIsPartial = true;
2592 : }
2593 8 : return NS_OK;
2594 : }
2595 : }
2596 : }
2597 :
2598 545 : bool doValidation = false;
2599 545 : bool canAddImsHeader = true;
2600 :
2601 : // Cached entry is not the entity we request (see bug #633743)
2602 545 : if (ResponseWouldVary()) {
2603 12 : LOG(("Validating based on Vary headers returning TRUE\n"));
2604 12 : canAddImsHeader = false;
2605 12 : doValidation = true;
2606 : }
2607 : // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used
2608 533 : else if (mLoadFlags & LOAD_FROM_CACHE) {
2609 10 : LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
2610 10 : doValidation = false;
2611 : }
2612 : // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
2613 : // it's revalidated with the server.
2614 523 : else if (mLoadFlags & VALIDATE_ALWAYS) {
2615 3 : LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
2616 3 : doValidation = true;
2617 : }
2618 : // Even if the VALIDATE_NEVER flag is set, there are still some cases in
2619 : // which we must validate the cached response with the server.
2620 520 : else if (mLoadFlags & VALIDATE_NEVER) {
2621 5 : LOG(("VALIDATE_NEVER set\n"));
2622 : // if no-store or if no-cache and ssl, validate cached response (see
2623 : // bug 112564 for an explanation of this logic)
2624 10 : if (mCachedResponseHead->NoStore() ||
2625 5 : (mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
2626 1 : LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
2627 1 : doValidation = true;
2628 : }
2629 : else {
2630 4 : LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
2631 4 : doValidation = false;
2632 : }
2633 : }
2634 : // check if validation is strictly required...
2635 515 : else if (mCachedResponseHead->MustValidate()) {
2636 265 : LOG(("Validating based on MustValidate() returning TRUE\n"));
2637 265 : doValidation = true;
2638 : }
2639 :
2640 250 : else if (MustValidateBasedOnQueryUrl()) {
2641 6 : LOG(("Validating based on RFC 2616 section 13.9 "
2642 : "(query-url w/o explicit expiration-time)\n"));
2643 6 : doValidation = true;
2644 : }
2645 : // Check if the cache entry has expired...
2646 : else {
2647 244 : PRUint32 time = 0; // a temporary variable for storing time values...
2648 :
2649 244 : rv = mCacheEntry->GetExpirationTime(&time);
2650 244 : NS_ENSURE_SUCCESS(rv, rv);
2651 :
2652 244 : if (NowInSeconds() <= time)
2653 194 : doValidation = false;
2654 50 : else if (mCachedResponseHead->MustValidateIfExpired())
2655 0 : doValidation = true;
2656 50 : else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
2657 : // If the cached response does not include expiration infor-
2658 : // mation, then we must validate the response, despite whether
2659 : // or not this is the first access this session. This behavior
2660 : // is consistent with existing browsers and is generally expected
2661 : // by web authors.
2662 0 : rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
2663 0 : NS_ENSURE_SUCCESS(rv, rv);
2664 :
2665 0 : if (time == 0)
2666 0 : doValidation = true;
2667 : else
2668 0 : doValidation = fromPreviousSession;
2669 : }
2670 : else
2671 50 : doValidation = true;
2672 :
2673 244 : LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
2674 : }
2675 :
2676 545 : if (!doValidation && mRequestHead.PeekHeader(nsHttp::If_Match) &&
2677 0 : (method == nsHttp::Get || method == nsHttp::Head)) {
2678 : const char *requestedETag, *cachedETag;
2679 0 : cachedETag = mCachedResponseHead->PeekHeader(nsHttp::ETag);
2680 0 : requestedETag = mRequestHead.PeekHeader(nsHttp::If_Match);
2681 0 : if (cachedETag && (!strncmp(cachedETag, "W/", 2) ||
2682 0 : strcmp(requestedETag, cachedETag))) {
2683 : // User has defined If-Match header, if the cached entry is not
2684 : // matching the provided header value or the cached ETag is weak,
2685 : // force validation.
2686 0 : doValidation = true;
2687 : }
2688 : }
2689 :
2690 545 : if (!doValidation) {
2691 : //
2692 : // Check the authorization headers used to generate the cache entry.
2693 : // We must validate the cache entry if:
2694 : //
2695 : // 1) the cache entry was generated prior to this session w/
2696 : // credentials (see bug 103402).
2697 : // 2) the cache entry was generated w/o credentials, but would now
2698 : // require credentials (see bug 96705).
2699 : //
2700 : // NOTE: this does not apply to proxy authentication.
2701 : //
2702 208 : mCacheEntry->GetMetaDataElement("auth", getter_Copies(buf));
2703 : doValidation =
2704 0 : (fromPreviousSession && !buf.IsEmpty()) ||
2705 208 : (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
2706 : }
2707 :
2708 : // Bug #561276: We maintain a chain of cache-keys which returns cached
2709 : // 3xx-responses (redirects) in order to detect cycles. If a cycle is
2710 : // found, ignore the cached response and hit the net. Otherwise, use
2711 : // the cached response and add the cache-key to the chain. Note that
2712 : // a limited number of redirects (cached or not) is allowed and is
2713 : // enforced independently of this mechanism
2714 545 : if (!doValidation && isCachedRedirect) {
2715 26 : nsCAutoString cacheKey;
2716 13 : GenerateCacheKey(mPostID, cacheKey);
2717 :
2718 13 : if (!mRedirectedCachekeys)
2719 10 : mRedirectedCachekeys = new nsTArray<nsCString>();
2720 3 : else if (mRedirectedCachekeys->Contains(cacheKey))
2721 2 : doValidation = true;
2722 :
2723 13 : LOG(("Redirection-chain %s key %s\n",
2724 : doValidation ? "contains" : "does not contain", cacheKey.get()));
2725 :
2726 : // Append cacheKey if not in the chain already
2727 13 : if (!doValidation)
2728 11 : mRedirectedCachekeys->AppendElement(cacheKey);
2729 : }
2730 :
2731 545 : mCachedContentIsValid = !doValidation;
2732 :
2733 545 : if (doValidation) {
2734 : //
2735 : // now, we are definitely going to issue a HTTP request to the server.
2736 : // make it conditional if possible.
2737 : //
2738 : // do not attempt to validate no-store content, since servers will not
2739 : // expect it to be cached. (we only keep it in our cache for the
2740 : // purposes of back/forward, etc.)
2741 : //
2742 : // the request method MUST be either GET or HEAD (see bug 175641).
2743 : //
2744 : // do not override conditional headers when consumer has defined its own
2745 1023 : if (!mCachedResponseHead->NoStore() &&
2746 335 : (mRequestHead.Method() == nsHttp::Get ||
2747 14 : mRequestHead.Method() == nsHttp::Head) &&
2748 335 : !mCustomConditionalRequest) {
2749 : const char *val;
2750 : // Add If-Modified-Since header if a Last-Modified was given
2751 : // and we are allowed to do this (see bugs 510359 and 269303)
2752 335 : if (canAddImsHeader) {
2753 324 : val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
2754 324 : if (val)
2755 : mRequestHead.SetHeader(nsHttp::If_Modified_Since,
2756 6 : nsDependentCString(val));
2757 : }
2758 : // Add If-None-Match header if an ETag was given in the response
2759 335 : val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
2760 335 : if (val)
2761 : mRequestHead.SetHeader(nsHttp::If_None_Match,
2762 9 : nsDependentCString(val));
2763 335 : mDidReval = true;
2764 : }
2765 : }
2766 :
2767 545 : LOG(("nsHTTPChannel::CheckCache exit [this=%p doValidation=%d]\n", this, doValidation));
2768 545 : return NS_OK;
2769 : }
2770 :
2771 : bool
2772 250 : nsHttpChannel::MustValidateBasedOnQueryUrl()
2773 : {
2774 : // RFC 2616, section 13.9 states that GET-requests with a query-url
2775 : // MUST NOT be treated as fresh unless the server explicitly provides
2776 : // an expiration-time in the response. See bug #468594
2777 : // Section 13.2.1 (6th paragraph) defines "explicit expiration time"
2778 250 : if (mRequestHead.Method() == nsHttp::Get)
2779 : {
2780 472 : nsCAutoString query;
2781 472 : nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
2782 236 : nsresult rv = url->GetQuery(query);
2783 236 : if (NS_SUCCEEDED(rv) && !query.IsEmpty()) {
2784 : PRUint32 tmp; // we don't need the value, just whether it's set
2785 16 : rv = mCachedResponseHead->GetExpiresValue(&tmp);
2786 16 : if (NS_FAILED(rv)) {
2787 11 : rv = mCachedResponseHead->GetMaxAgeValue(&tmp);
2788 11 : if (NS_FAILED(rv)) {
2789 6 : return true;
2790 : }
2791 : }
2792 : }
2793 : }
2794 244 : return false;
2795 : }
2796 :
2797 :
2798 : nsresult
2799 24 : nsHttpChannel::ShouldUpdateOfflineCacheEntry(bool *shouldCacheForOfflineUse)
2800 : {
2801 24 : *shouldCacheForOfflineUse = false;
2802 :
2803 24 : if (!mOfflineCacheEntry) {
2804 0 : return NS_OK;
2805 : }
2806 :
2807 : // if we're updating the cache entry, update the offline cache entry too
2808 24 : if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) {
2809 24 : *shouldCacheForOfflineUse = true;
2810 24 : return NS_OK;
2811 : }
2812 :
2813 : // if there's nothing in the offline cache, add it
2814 0 : if (mOfflineCacheEntry && (mOfflineCacheAccess == nsICache::ACCESS_WRITE)) {
2815 0 : *shouldCacheForOfflineUse = true;
2816 0 : return NS_OK;
2817 : }
2818 :
2819 : // if the document is newer than the offline entry, update it
2820 : PRUint32 docLastModifiedTime;
2821 0 : nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
2822 0 : if (NS_FAILED(rv)) {
2823 0 : *shouldCacheForOfflineUse = true;
2824 0 : return NS_OK;
2825 : }
2826 :
2827 : PRUint32 offlineLastModifiedTime;
2828 0 : rv = mOfflineCacheEntry->GetLastModified(&offlineLastModifiedTime);
2829 0 : NS_ENSURE_SUCCESS(rv, rv);
2830 :
2831 0 : if (docLastModifiedTime > offlineLastModifiedTime) {
2832 0 : *shouldCacheForOfflineUse = true;
2833 0 : return NS_OK;
2834 : }
2835 :
2836 0 : return NS_OK;
2837 : }
2838 :
2839 : // If the data in the cache hasn't expired, then there's no need to
2840 : // talk with the server, not even to do an if-modified-since. This
2841 : // method creates a stream from the cache, synthesizing all the various
2842 : // channel-related events.
2843 : nsresult
2844 228 : nsHttpChannel::ReadFromCache()
2845 : {
2846 : nsresult rv;
2847 :
2848 228 : NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
2849 228 : NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
2850 :
2851 228 : LOG(("nsHttpChannel::ReadFromCache [this=%p] "
2852 : "Using cached copy of: %s\n", this, mSpec.get()));
2853 :
2854 228 : if (mCachedResponseHead)
2855 220 : mResponseHead = mCachedResponseHead;
2856 :
2857 228 : UpdateInhibitPersistentCachingFlag();
2858 :
2859 : // if we don't already have security info, try to get it from the cache
2860 : // entry. there are two cases to consider here: 1) we are just reading
2861 : // from the cache, or 2) this may be due to a 304 not modified response,
2862 : // in which case we could have security info from a socket transport.
2863 228 : if (!mSecurityInfo)
2864 228 : mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
2865 :
2866 228 : if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) {
2867 : // We have write access to the cache, but we don't need to go to the
2868 : // server to validate at this time, so just mark the cache entry as
2869 : // valid in order to allow others access to this cache entry.
2870 212 : mCacheEntry->MarkValid();
2871 : }
2872 :
2873 : // if this is a cached redirect, we must process the redirect asynchronously
2874 : // since AsyncOpen may not have returned yet. Make sure there is a Location
2875 : // header, otherwise we'll have to treat this like a normal 200 response.
2876 239 : if (mResponseHead && (mResponseHead->Status() / 100 == 3)
2877 11 : && (mResponseHead->PeekHeader(nsHttp::Location)))
2878 11 : return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
2879 :
2880 : // have we been configured to skip reading from the cache?
2881 217 : if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
2882 : // if offline caching has been requested and the offline cache needs
2883 : // updating, complete the call even if the main cache entry is
2884 : // up-to-date
2885 : bool shouldUpdateOffline;
2886 0 : if (!mCacheForOfflineUse ||
2887 0 : NS_FAILED(ShouldUpdateOfflineCacheEntry(&shouldUpdateOffline)) ||
2888 0 : !shouldUpdateOffline) {
2889 :
2890 0 : LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
2891 : "load flag\n"));
2892 0 : return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
2893 : }
2894 : }
2895 :
2896 : // open input stream for reading...
2897 434 : nsCOMPtr<nsIInputStream> stream;
2898 217 : rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
2899 217 : if (NS_FAILED(rv)) return rv;
2900 :
2901 217 : rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump),
2902 : stream, PRInt64(-1), PRInt64(-1), 0, 0,
2903 434 : true);
2904 217 : if (NS_FAILED(rv)) return rv;
2905 :
2906 217 : rv = mCachePump->AsyncRead(this, mListenerContext);
2907 217 : if (NS_FAILED(rv)) return rv;
2908 :
2909 217 : if (mTimingEnabled)
2910 0 : mCacheReadStart = mozilla::TimeStamp::Now();
2911 :
2912 217 : PRUint32 suspendCount = mSuspendCount;
2913 434 : while (suspendCount--)
2914 0 : mCachePump->Suspend();
2915 :
2916 217 : return NS_OK;
2917 : }
2918 :
2919 : void
2920 1658 : nsHttpChannel::CloseCacheEntry(bool doomOnFailure)
2921 : {
2922 1658 : if (!mCacheEntry)
2923 28 : return;
2924 :
2925 1630 : LOG(("nsHttpChannel::CloseCacheEntry [this=%p] mStatus=%x mCacheAccess=%x",
2926 : this, mStatus, mCacheAccess));
2927 :
2928 : // If we have begun to create or replace a cache entry, and that cache
2929 : // entry is not complete and not resumable, then it needs to be doomed.
2930 : // Otherwise, CheckCache will make the mistake of thinking that the
2931 : // partial cache entry is complete.
2932 :
2933 1630 : bool doom = false;
2934 1630 : if (mInitedCacheEntry) {
2935 1238 : NS_ASSERTION(mResponseHead, "oops");
2936 1265 : if (NS_FAILED(mStatus) && doomOnFailure &&
2937 : (mCacheAccess & nsICache::ACCESS_WRITE) &&
2938 27 : !mResponseHead->IsResumable())
2939 25 : doom = true;
2940 : }
2941 392 : else if (mCacheAccess == nsICache::ACCESS_WRITE)
2942 156 : doom = true;
2943 :
2944 1630 : if (doom) {
2945 181 : LOG((" dooming cache entry!!"));
2946 181 : mCacheEntry->Doom();
2947 : }
2948 :
2949 1630 : mCachedResponseHead = nsnull;
2950 :
2951 1630 : mCachePump = 0;
2952 1630 : mCacheEntry = 0;
2953 1630 : mCacheAccess = 0;
2954 1630 : mInitedCacheEntry = false;
2955 : }
2956 :
2957 :
2958 : void
2959 24 : nsHttpChannel::CloseOfflineCacheEntry()
2960 : {
2961 24 : if (!mOfflineCacheEntry)
2962 0 : return;
2963 :
2964 24 : LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%p]", this));
2965 :
2966 24 : if (NS_FAILED(mStatus)) {
2967 0 : mOfflineCacheEntry->Doom();
2968 : }
2969 : else {
2970 : bool succeeded;
2971 24 : if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
2972 0 : mOfflineCacheEntry->Doom();
2973 : }
2974 :
2975 24 : mOfflineCacheEntry = 0;
2976 24 : mOfflineCacheAccess = 0;
2977 :
2978 24 : if (mCachingOpportunistically) {
2979 : nsCOMPtr<nsIApplicationCacheService> appCacheService =
2980 0 : do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
2981 0 : if (appCacheService) {
2982 0 : nsCAutoString cacheKey;
2983 0 : GenerateCacheKey(mPostID, cacheKey);
2984 0 : appCacheService->CacheOpportunistically(mApplicationCache,
2985 0 : cacheKey);
2986 : }
2987 : }
2988 : }
2989 :
2990 :
2991 : // Initialize the cache entry for writing.
2992 : // - finalize storage policy
2993 : // - store security info
2994 : // - update expiration time
2995 : // - store headers and other meta data
2996 : nsresult
2997 1262 : nsHttpChannel::InitCacheEntry()
2998 : {
2999 : nsresult rv;
3000 :
3001 1262 : NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
3002 : // if only reading, nothing to be done here.
3003 1238 : if (mCacheAccess == nsICache::ACCESS_READ)
3004 0 : return NS_OK;
3005 :
3006 : // Don't cache the response again if already cached...
3007 1238 : if (mCachedContentIsValid)
3008 0 : return NS_OK;
3009 :
3010 1238 : LOG(("nsHttpChannel::InitCacheEntry [this=%p entry=%p]\n",
3011 : this, mCacheEntry.get()));
3012 :
3013 1238 : if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
3014 5 : rv = mCacheEntry->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
3015 5 : if (NS_FAILED(rv)) return rv;
3016 : }
3017 :
3018 : // Set the expiration time for this cache entry
3019 1238 : rv = UpdateExpirationTime();
3020 1238 : if (NS_FAILED(rv)) return rv;
3021 :
3022 1238 : rv = AddCacheEntryHeaders(mCacheEntry);
3023 1238 : if (NS_FAILED(rv)) return rv;
3024 :
3025 1238 : mInitedCacheEntry = true;
3026 1238 : return NS_OK;
3027 : }
3028 :
3029 : void
3030 2894 : nsHttpChannel::UpdateInhibitPersistentCachingFlag()
3031 : {
3032 : // The no-store directive within the 'Cache-Control:' header indicates
3033 : // that we must not store the response in a persistent cache.
3034 2894 : if (mResponseHead->NoStore())
3035 6 : mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
3036 :
3037 : // Only cache SSL content on disk if the pref is set
3038 2894 : if (!gHttpHandler->IsPersistentHttpsCachingEnabled() &&
3039 0 : mConnectionInfo->UsingSSL())
3040 0 : mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
3041 2894 : }
3042 :
3043 : nsresult
3044 24 : nsHttpChannel::InitOfflineCacheEntry()
3045 : {
3046 : // This function can be called even when we fail to connect (bug 551990)
3047 :
3048 24 : if (!mOfflineCacheEntry) {
3049 0 : return NS_OK;
3050 : }
3051 :
3052 24 : if (mResponseHead && mResponseHead->NoStore()) {
3053 0 : CloseOfflineCacheEntry();
3054 :
3055 0 : return NS_OK;
3056 : }
3057 :
3058 : // This entry's expiration time should match the main entry's expiration
3059 : // time. UpdateExpirationTime() will keep it in sync once the offline
3060 : // cache entry has been created.
3061 24 : if (mCacheEntry) {
3062 : PRUint32 expirationTime;
3063 24 : nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
3064 24 : NS_ENSURE_SUCCESS(rv, rv);
3065 :
3066 24 : mOfflineCacheEntry->SetExpirationTime(expirationTime);
3067 : }
3068 :
3069 24 : return AddCacheEntryHeaders(mOfflineCacheEntry);
3070 : }
3071 :
3072 :
3073 : nsresult
3074 1268 : nsHttpChannel::AddCacheEntryHeaders(nsICacheEntryDescriptor *entry)
3075 : {
3076 : nsresult rv;
3077 :
3078 1268 : LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%x] begin", this));
3079 : // Store secure data in memory only
3080 1268 : if (mSecurityInfo)
3081 4 : entry->SetSecurityInfo(mSecurityInfo);
3082 :
3083 : // Store the HTTP request method with the cache entry so we can distinguish
3084 : // for example GET and HEAD responses.
3085 : rv = entry->SetMetaDataElement("request-method",
3086 1268 : mRequestHead.Method().get());
3087 1268 : if (NS_FAILED(rv)) return rv;
3088 :
3089 : // Store the HTTP authorization scheme used if any...
3090 1268 : rv = StoreAuthorizationMetaData(entry);
3091 1268 : if (NS_FAILED(rv)) return rv;
3092 :
3093 : // Iterate over the headers listed in the Vary response header, and
3094 : // store the value of the corresponding request header so we can verify
3095 : // that it has not varied when we try to re-use the cached response at
3096 : // a later time. Take care to store "Cookie" headers only as hashes
3097 : // due to security considerations and the fact that they can be pretty
3098 : // large (bug 468426). We take care of "Vary: cookie" in ResponseWouldVary.
3099 : //
3100 : // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
3101 : // in the cache. we could try to avoid needlessly storing the "accept"
3102 : // header in this case, but it doesn't seem worth the extra code to perform
3103 : // the check.
3104 : {
3105 2536 : nsCAutoString buf, metaKey;
3106 1268 : mResponseHead->GetHeader(nsHttp::Vary, buf);
3107 1268 : if (!buf.IsEmpty()) {
3108 40 : NS_NAMED_LITERAL_CSTRING(prefix, "request-");
3109 :
3110 20 : char *val = buf.BeginWriting(); // going to munge buf
3111 20 : char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3112 64 : while (token) {
3113 24 : LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%x] " \
3114 : "processing %s", this, token));
3115 24 : if (*token != '*') {
3116 24 : nsHttpAtom atom = nsHttp::ResolveAtom(token);
3117 24 : const char *val = mRequestHead.PeekHeader(atom);
3118 48 : nsCAutoString hash;
3119 24 : if (val) {
3120 : // If cookie-header, store a hash of the value
3121 18 : if (atom == nsHttp::Cookie) {
3122 4 : LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%x] " \
3123 : "cookie-value %s", this, val));
3124 4 : rv = Hash(val, hash);
3125 : // If hash failed, store a string not very likely
3126 : // to be the result of subsequent hashes
3127 4 : if (NS_FAILED(rv))
3128 0 : val = "<hash failed>";
3129 : else
3130 4 : val = hash.get();
3131 :
3132 4 : LOG((" hashed to %s\n", val));
3133 : }
3134 :
3135 : // build cache meta data key and set meta data element...
3136 18 : metaKey = prefix + nsDependentCString(token);
3137 18 : entry->SetMetaDataElement(metaKey.get(), val);
3138 : } else {
3139 6 : LOG(("nsHttpChannel::AddCacheEntryHeaders [this=%x] " \
3140 : "clearing metadata for %s", this, token));
3141 6 : metaKey = prefix + nsDependentCString(token);
3142 6 : entry->SetMetaDataElement(metaKey.get(), nsnull);
3143 : }
3144 : }
3145 24 : token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
3146 : }
3147 : }
3148 : }
3149 :
3150 :
3151 : // Store the received HTTP head with the cache entry as an element of
3152 : // the meta data.
3153 2536 : nsCAutoString head;
3154 1268 : mResponseHead->Flatten(head, true);
3155 1268 : rv = entry->SetMetaDataElement("response-head", head.get());
3156 :
3157 1268 : return rv;
3158 : }
3159 :
3160 : inline void
3161 65 : GetAuthType(const char *challenge, nsCString &authType)
3162 : {
3163 : const char *p;
3164 :
3165 : // get the challenge type
3166 65 : if ((p = strchr(challenge, ' ')) != nsnull)
3167 64 : authType.Assign(challenge, p - challenge);
3168 : else
3169 1 : authType.Assign(challenge);
3170 65 : }
3171 :
3172 : nsresult
3173 1268 : nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry)
3174 : {
3175 : // Not applicable to proxy authorization...
3176 1268 : const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
3177 1268 : if (!val)
3178 1259 : return NS_OK;
3179 :
3180 : // eg. [Basic realm="wally world"]
3181 18 : nsCAutoString buf;
3182 9 : GetAuthType(val, buf);
3183 9 : return entry->SetMetaDataElement("auth", buf.get());
3184 : }
3185 :
3186 : // Finalize the cache entry
3187 : // - may need to rewrite response headers if any headers changed
3188 : // - may need to recalculate the expiration time if any headers changed
3189 : // - called only for freshly written cache entries
3190 : nsresult
3191 1272 : nsHttpChannel::FinalizeCacheEntry()
3192 : {
3193 1272 : LOG(("nsHttpChannel::FinalizeCacheEntry [this=%p]\n", this));
3194 :
3195 1272 : if (mResponseHead && mResponseHeadersModified) {
3196 : // Set the expiration time for this cache entry
3197 0 : nsresult rv = UpdateExpirationTime();
3198 0 : if (NS_FAILED(rv)) return rv;
3199 : }
3200 1272 : return NS_OK;
3201 : }
3202 :
3203 : // Open an output stream to the cache entry and insert a listener tee into
3204 : // the chain of response listeners.
3205 : nsresult
3206 1134 : nsHttpChannel::InstallCacheListener(PRUint32 offset)
3207 : {
3208 : nsresult rv;
3209 :
3210 1134 : LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
3211 :
3212 1134 : NS_ASSERTION(mCacheEntry, "no cache entry");
3213 1134 : NS_ASSERTION(mListener, "no listener");
3214 :
3215 : nsCacheStoragePolicy policy;
3216 1134 : rv = mCacheEntry->GetStoragePolicy(&policy);
3217 1134 : if (NS_FAILED(rv)) {
3218 0 : policy = nsICache::STORE_ON_DISK_AS_FILE;
3219 : }
3220 :
3221 : // If the content is compressible and the server has not compressed it,
3222 : // mark the cache entry for compression.
3223 6675 : if ((mResponseHead->PeekHeader(nsHttp::Content_Encoding) == nsnull) && (
3224 : policy != nsICache::STORE_ON_DISK_AS_FILE) && (
3225 1131 : mResponseHead->ContentType().EqualsLiteral(TEXT_HTML) ||
3226 813 : mResponseHead->ContentType().EqualsLiteral(TEXT_PLAIN) ||
3227 667 : mResponseHead->ContentType().EqualsLiteral(TEXT_CSS) ||
3228 667 : mResponseHead->ContentType().EqualsLiteral(TEXT_JAVASCRIPT) ||
3229 667 : mResponseHead->ContentType().EqualsLiteral(TEXT_ECMASCRIPT) ||
3230 667 : mResponseHead->ContentType().EqualsLiteral(TEXT_XML) ||
3231 242 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_JAVASCRIPT) ||
3232 229 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_ECMASCRIPT) ||
3233 229 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
3234 229 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_XHTML_XML))) {
3235 902 : rv = mCacheEntry->SetMetaDataElement("uncompressed-len", "0");
3236 902 : if (NS_FAILED(rv)) {
3237 0 : LOG(("unable to mark cache entry for compression"));
3238 : }
3239 : }
3240 :
3241 2268 : nsCOMPtr<nsIOutputStream> out;
3242 1134 : rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
3243 1134 : if (NS_FAILED(rv)) return rv;
3244 :
3245 : // XXX disk cache does not support overlapped i/o yet
3246 : #if 0
3247 : // Mark entry valid inorder to allow simultaneous reading...
3248 : rv = mCacheEntry->MarkValid();
3249 : if (NS_FAILED(rv)) return rv;
3250 : #endif
3251 :
3252 : nsCOMPtr<nsIStreamListenerTee> tee =
3253 2268 : do_CreateInstance(kStreamListenerTeeCID, &rv);
3254 1134 : if (NS_FAILED(rv)) return rv;
3255 :
3256 : nsCOMPtr<nsICacheService> serv =
3257 2268 : do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
3258 1134 : NS_ENSURE_SUCCESS(rv, rv);
3259 :
3260 2268 : nsCOMPtr<nsIEventTarget> cacheIOTarget;
3261 1134 : serv->GetCacheIOTarget(getter_AddRefs(cacheIOTarget));
3262 :
3263 2268 : if (policy == nsICache::STORE_ON_DISK_AS_FILE ||
3264 1134 : !cacheIOTarget) {
3265 0 : LOG(("nsHttpChannel::InstallCacheListener sync tee %p rv=%x policy=%d "
3266 : "cacheIOTarget=%p", tee.get(), rv, policy, cacheIOTarget.get()));
3267 0 : rv = tee->Init(mListener, out, nsnull);
3268 : } else {
3269 1134 : LOG(("nsHttpChannel::InstallCacheListener async tee %p", tee.get()));
3270 1134 : rv = tee->InitAsync(mListener, cacheIOTarget, out, nsnull);
3271 : }
3272 :
3273 1134 : if (NS_FAILED(rv)) return rv;
3274 1134 : mListener = tee;
3275 1134 : return NS_OK;
3276 : }
3277 :
3278 : nsresult
3279 24 : nsHttpChannel::InstallOfflineCacheListener()
3280 : {
3281 : nsresult rv;
3282 :
3283 24 : LOG(("Preparing to write data into the offline cache [uri=%s]\n",
3284 : mSpec.get()));
3285 :
3286 24 : NS_ASSERTION(mOfflineCacheEntry, "no offline cache entry");
3287 24 : NS_ASSERTION(mListener, "no listener");
3288 :
3289 48 : nsCOMPtr<nsIOutputStream> out;
3290 24 : rv = mOfflineCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
3291 24 : if (NS_FAILED(rv)) return rv;
3292 :
3293 : nsCOMPtr<nsIStreamListenerTee> tee =
3294 48 : do_CreateInstance(kStreamListenerTeeCID, &rv);
3295 24 : if (NS_FAILED(rv)) return rv;
3296 :
3297 24 : rv = tee->Init(mListener, out, nsnull);
3298 24 : if (NS_FAILED(rv)) return rv;
3299 :
3300 24 : mListener = tee;
3301 :
3302 24 : return NS_OK;
3303 : }
3304 :
3305 : void
3306 2660 : nsHttpChannel::ClearBogusContentEncodingIfNeeded()
3307 : {
3308 : // For .gz files, apache sends both a Content-Type: application/x-gzip
3309 : // as well as Content-Encoding: gzip, which is completely wrong. In
3310 : // this case, we choose to ignore the rogue Content-Encoding header. We
3311 : // must do this early on so as to prevent it from being seen up stream.
3312 : // The same problem exists for Content-Encoding: compress in default
3313 : // Apache installs.
3314 2671 : if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
3315 5 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
3316 3 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
3317 3 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
3318 : // clear the Content-Encoding header
3319 2 : mResponseHead->ClearHeader(nsHttp::Content_Encoding);
3320 : }
3321 2658 : else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
3322 0 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
3323 0 : mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
3324 : // clear the Content-Encoding header
3325 0 : mResponseHead->ClearHeader(nsHttp::Content_Encoding);
3326 : }
3327 2660 : }
3328 :
3329 : //-----------------------------------------------------------------------------
3330 : // nsHttpChannel <redirect>
3331 : //-----------------------------------------------------------------------------
3332 :
3333 : nsresult
3334 155 : nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
3335 : nsIChannel *newChannel,
3336 : bool preserveMethod,
3337 : bool forProxy)
3338 : {
3339 155 : LOG(("nsHttpChannel::SetupReplacementChannel "
3340 : "[this=%p newChannel=%p preserveMethod=%d]",
3341 : this, newChannel, preserveMethod));
3342 :
3343 : nsresult rv = HttpBaseChannel::SetupReplacementChannel(newURI, newChannel,
3344 155 : preserveMethod, forProxy);
3345 155 : if (NS_FAILED(rv))
3346 0 : return rv;
3347 :
3348 310 : nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
3349 155 : if (!httpChannel)
3350 2 : return NS_OK; // no other options to set
3351 :
3352 : // convey the mApplyConversion flag (bug 91862)
3353 306 : nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
3354 153 : if (encodedChannel)
3355 153 : encodedChannel->SetApplyConversion(mApplyConversion);
3356 :
3357 : // transfer the resume information
3358 153 : if (mResuming) {
3359 4 : nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
3360 2 : if (!resumableChannel) {
3361 0 : NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
3362 0 : return NS_ERROR_NOT_RESUMABLE;
3363 : }
3364 4 : resumableChannel->ResumeAt(mStartPos, mEntityID);
3365 : }
3366 :
3367 153 : if (forProxy) {
3368 : // Transfer the cache info to the new channel, if needed.
3369 24 : nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(newChannel);
3370 12 : if (cachingChannel) {
3371 : // cacheKey is just mPostID wrapped in an nsISupportsPRUint32,
3372 : // we don't need to transfer it if it's 0.
3373 12 : if (mPostID) {
3374 2 : nsCOMPtr<nsISupports> cacheKey;
3375 1 : GetCacheKey(getter_AddRefs(cacheKey));
3376 1 : if (cacheKey) {
3377 1 : cachingChannel->SetCacheKey(cacheKey);
3378 : }
3379 : }
3380 :
3381 : // cacheClientID, cacheForOfflineUse
3382 12 : cachingChannel->SetOfflineCacheClientID(mOfflineCacheClientID);
3383 12 : cachingChannel->SetCacheForOfflineUse(mCacheForOfflineUse);
3384 : }
3385 : }
3386 :
3387 153 : return NS_OK;
3388 : }
3389 :
3390 : nsresult
3391 156 : nsHttpChannel::AsyncProcessRedirection(PRUint32 redirectType)
3392 : {
3393 156 : LOG(("nsHttpChannel::AsyncProcessRedirection [this=%p type=%u]\n",
3394 : this, redirectType));
3395 :
3396 156 : const char *location = mResponseHead->PeekHeader(nsHttp::Location);
3397 :
3398 : // if a location header was not given, then we can't perform the redirect,
3399 : // so just carry on as though this were a normal response.
3400 156 : if (!location)
3401 1 : return NS_ERROR_FAILURE;
3402 :
3403 : // make sure non-ASCII characters in the location header are escaped.
3404 310 : nsCAutoString locationBuf;
3405 155 : if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
3406 0 : location = locationBuf.get();
3407 :
3408 155 : if (mRedirectionLimit == 0) {
3409 3 : LOG(("redirection limit reached!\n"));
3410 : // this error code is fatal, and should be conveyed to our listener.
3411 3 : Cancel(NS_ERROR_REDIRECT_LOOP);
3412 3 : return NS_ERROR_REDIRECT_LOOP;
3413 : }
3414 :
3415 152 : mRedirectType = redirectType;
3416 :
3417 152 : LOG(("redirecting to: %s [redirection-limit=%u]\n",
3418 : location, PRUint32(mRedirectionLimit)));
3419 :
3420 152 : nsresult rv = CreateNewURI(location, getter_AddRefs(mRedirectURI));
3421 :
3422 152 : if (NS_FAILED(rv)) return rv;
3423 :
3424 152 : if (mApplicationCache) {
3425 : // if we are redirected to a different origin check if there is a fallback
3426 : // cache entry to fall back to. we don't care about file strict
3427 : // checking, at least mURI is not a file URI.
3428 2 : if (!NS_SecurityCompareURIs(mURI, mRedirectURI, false)) {
3429 2 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
3430 : bool waitingForRedirectCallback;
3431 2 : (void)ProcessFallback(&waitingForRedirectCallback);
3432 2 : if (waitingForRedirectCallback)
3433 2 : return NS_OK;
3434 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirectionAfterFallback);
3435 : }
3436 : }
3437 :
3438 150 : return ContinueProcessRedirectionAfterFallback(NS_OK);
3439 : }
3440 :
3441 : // Creates an URI to the given location using current URI for base and charset
3442 : nsresult
3443 190 : nsHttpChannel::CreateNewURI(const char *loc, nsIURI **newURI)
3444 : {
3445 380 : nsCOMPtr<nsIIOService> ioService;
3446 190 : nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
3447 190 : if (NS_FAILED(rv)) return rv;
3448 :
3449 : // the new uri should inherit the origin charset of the current uri
3450 380 : nsCAutoString originCharset;
3451 190 : rv = mURI->GetOriginCharset(originCharset);
3452 190 : if (NS_FAILED(rv))
3453 0 : originCharset.Truncate();
3454 :
3455 380 : return ioService->NewURI(nsDependentCString(loc),
3456 : originCharset.get(),
3457 : mURI,
3458 190 : newURI);
3459 : }
3460 :
3461 : nsresult
3462 152 : nsHttpChannel::ContinueProcessRedirectionAfterFallback(nsresult rv)
3463 : {
3464 152 : if (NS_SUCCEEDED(rv) && mFallingBack) {
3465 : // do not continue with redirect processing, fallback is in
3466 : // progress now.
3467 1 : return NS_OK;
3468 : }
3469 :
3470 : // Kill the current cache entry if we are redirecting
3471 : // back to ourself.
3472 151 : bool redirectingBackToSameURI = false;
3473 264 : if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
3474 113 : NS_SUCCEEDED(mURI->Equals(mRedirectURI, &redirectingBackToSameURI)) &&
3475 : redirectingBackToSameURI)
3476 60 : mCacheEntry->Doom();
3477 :
3478 : // move the reference of the old location to the new one if the new
3479 : // one has none.
3480 302 : nsCAutoString ref;
3481 151 : rv = mRedirectURI->GetRef(ref);
3482 151 : if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
3483 151 : mURI->GetRef(ref);
3484 151 : if (!ref.IsEmpty()) {
3485 : // NOTE: SetRef will fail if mRedirectURI is immutable
3486 : // (e.g. an about: URI)... Oh well.
3487 0 : mRedirectURI->SetRef(ref);
3488 : }
3489 : }
3490 :
3491 : bool rewriteToGET = HttpBaseChannel::ShouldRewriteRedirectToGET(
3492 151 : mRedirectType, mRequestHead.Method());
3493 :
3494 : // prompt if the method is not safe (such as POST, PUT, DELETE, ...)
3495 286 : if (!rewriteToGET &&
3496 135 : !HttpBaseChannel::IsSafeMethod(mRequestHead.Method())) {
3497 16 : rv = PromptTempRedirect();
3498 16 : if (NS_FAILED(rv)) return rv;
3499 : }
3500 :
3501 274 : nsCOMPtr<nsIIOService> ioService;
3502 137 : rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
3503 137 : if (NS_FAILED(rv)) return rv;
3504 :
3505 274 : nsCOMPtr<nsIChannel> newChannel;
3506 137 : rv = ioService->NewChannelFromURI(mRedirectURI, getter_AddRefs(newChannel));
3507 137 : if (NS_FAILED(rv)) return rv;
3508 :
3509 134 : rv = SetupReplacementChannel(mRedirectURI, newChannel, !rewriteToGET, false);
3510 134 : if (NS_FAILED(rv)) return rv;
3511 :
3512 : PRUint32 redirectFlags;
3513 134 : if (mRedirectType == 301) // Moved Permanently
3514 89 : redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
3515 : else
3516 45 : redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
3517 :
3518 : // verify that this is a legal redirect
3519 134 : mRedirectChannel = newChannel;
3520 :
3521 134 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
3522 134 : rv = gHttpHandler->AsyncOnChannelRedirect(this, newChannel, redirectFlags);
3523 :
3524 134 : if (NS_SUCCEEDED(rv))
3525 134 : rv = WaitForRedirectCallback();
3526 :
3527 134 : if (NS_FAILED(rv)) {
3528 0 : AutoRedirectVetoNotifier notifier(this);
3529 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueProcessRedirection);
3530 : }
3531 :
3532 134 : return rv;
3533 : }
3534 :
3535 : nsresult
3536 134 : nsHttpChannel::ContinueProcessRedirection(nsresult rv)
3537 : {
3538 268 : AutoRedirectVetoNotifier notifier(this);
3539 :
3540 134 : LOG(("ContinueProcessRedirection [rv=%x]\n", rv));
3541 134 : if (NS_FAILED(rv))
3542 8 : return rv;
3543 :
3544 126 : NS_PRECONDITION(mRedirectChannel, "No redirect channel?");
3545 :
3546 : // Make sure to do this _after_ calling OnChannelRedirect
3547 126 : mRedirectChannel->SetOriginalURI(mOriginalURI);
3548 :
3549 : // And now, the deprecated way
3550 252 : nsCOMPtr<nsIHttpEventSink> httpEventSink;
3551 126 : GetCallback(httpEventSink);
3552 126 : if (httpEventSink) {
3553 : // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
3554 : // versions.
3555 0 : rv = httpEventSink->OnRedirect(this, mRedirectChannel);
3556 0 : if (NS_FAILED(rv))
3557 0 : return rv;
3558 : }
3559 : // XXX we used to talk directly with the script security manager, but that
3560 : // should really be handled by the event sink implementation.
3561 :
3562 : // begin loading the new channel
3563 126 : rv = mRedirectChannel->AsyncOpen(mListener, mListenerContext);
3564 :
3565 126 : if (NS_FAILED(rv))
3566 0 : return rv;
3567 :
3568 : // close down this channel
3569 126 : Cancel(NS_BINDING_REDIRECTED);
3570 :
3571 126 : notifier.RedirectSucceeded();
3572 :
3573 : // disconnect from our listener
3574 126 : mListener = 0;
3575 126 : mListenerContext = 0;
3576 :
3577 : // and from our callbacks
3578 126 : mCallbacks = nsnull;
3579 126 : mProgressSink = nsnull;
3580 126 : return NS_OK;
3581 : }
3582 :
3583 : //-----------------------------------------------------------------------------
3584 : // nsHttpChannel <auth>
3585 : //-----------------------------------------------------------------------------
3586 :
3587 15 : NS_IMETHODIMP nsHttpChannel::OnAuthAvailable()
3588 : {
3589 15 : LOG(("nsHttpChannel::OnAuthAvailable [this=%p]", this));
3590 :
3591 : // setting mAuthRetryPending flag and resuming the transaction
3592 : // triggers process of throwing away the unauthenticated data already
3593 : // coming from the network
3594 15 : mAuthRetryPending = true;
3595 15 : LOG(("Resuming the transaction, we got credentials from user"));
3596 15 : mTransactionPump->Resume();
3597 :
3598 15 : return NS_OK;
3599 : }
3600 :
3601 3 : NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(bool userCancel)
3602 : {
3603 3 : LOG(("nsHttpChannel::OnAuthCancelled [this=%p]", this));
3604 :
3605 3 : if (mTransactionPump) {
3606 : // ensure call of OnStartRequest of the current listener here,
3607 : // it would not be called otherwise at all
3608 3 : nsresult rv = CallOnStartRequest();
3609 :
3610 : // drop mAuthRetryPending flag and resume the transaction
3611 : // this resumes load of the unauthenticated content data
3612 3 : mAuthRetryPending = false;
3613 3 : LOG(("Resuming the transaction, user cancelled the auth dialog"));
3614 3 : mTransactionPump->Resume();
3615 :
3616 3 : if (NS_FAILED(rv))
3617 0 : mTransactionPump->Cancel(rv);
3618 : }
3619 :
3620 3 : return NS_OK;
3621 : }
3622 :
3623 : //-----------------------------------------------------------------------------
3624 : // nsHttpChannel::nsISupports
3625 : //-----------------------------------------------------------------------------
3626 :
3627 89779 : NS_IMPL_ADDREF_INHERITED(nsHttpChannel, HttpBaseChannel)
3628 89505 : NS_IMPL_RELEASE_INHERITED(nsHttpChannel, HttpBaseChannel)
3629 :
3630 140226 : NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
3631 140226 : NS_INTERFACE_MAP_ENTRY(nsIRequest)
3632 133688 : NS_INTERFACE_MAP_ENTRY(nsIChannel)
3633 122224 : NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3634 122224 : NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3635 119030 : NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
3636 103486 : NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
3637 103486 : NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
3638 102677 : NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
3639 101782 : NS_INTERFACE_MAP_ENTRY(nsIUploadChannel2)
3640 97575 : NS_INTERFACE_MAP_ENTRY(nsICacheListener)
3641 97575 : NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
3642 95516 : NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
3643 95471 : NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
3644 95471 : NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
3645 95469 : NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
3646 95459 : NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
3647 95459 : NS_INTERFACE_MAP_ENTRY(nsIHttpAuthenticableChannel)
3648 95459 : NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
3649 95459 : NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
3650 95266 : NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
3651 94956 : NS_INTERFACE_MAP_ENTRY(nsITimedChannel)
3652 94797 : NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
3653 :
3654 : //-----------------------------------------------------------------------------
3655 : // nsHttpChannel::nsIRequest
3656 : //-----------------------------------------------------------------------------
3657 :
3658 : NS_IMETHODIMP
3659 192 : nsHttpChannel::Cancel(nsresult status)
3660 : {
3661 192 : LOG(("nsHttpChannel::Cancel [this=%p status=%x]\n", this, status));
3662 192 : if (mCanceled) {
3663 0 : LOG((" ignoring; already canceled\n"));
3664 0 : return NS_OK;
3665 : }
3666 192 : if (mWaitingForRedirectCallback) {
3667 0 : LOG(("channel canceled during wait for redirect callback"));
3668 : }
3669 192 : mCanceled = true;
3670 192 : mStatus = status;
3671 192 : if (mProxyRequest)
3672 0 : mProxyRequest->Cancel(status);
3673 192 : if (mTransaction)
3674 162 : gHttpHandler->CancelTransaction(mTransaction, status);
3675 192 : if (mTransactionPump)
3676 162 : mTransactionPump->Cancel(status);
3677 192 : if (mCachePump)
3678 4 : mCachePump->Cancel(status);
3679 192 : if (mAuthProvider)
3680 36 : mAuthProvider->Cancel(status);
3681 192 : return NS_OK;
3682 : }
3683 :
3684 : NS_IMETHODIMP
3685 8 : nsHttpChannel::Suspend()
3686 : {
3687 8 : NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
3688 :
3689 8 : LOG(("nsHttpChannel::Suspend [this=%p]\n", this));
3690 :
3691 8 : ++mSuspendCount;
3692 :
3693 8 : if (mTransactionPump)
3694 8 : return mTransactionPump->Suspend();
3695 0 : if (mCachePump)
3696 0 : return mCachePump->Suspend();
3697 :
3698 0 : return NS_OK;
3699 : }
3700 :
3701 : NS_IMETHODIMP
3702 8 : nsHttpChannel::Resume()
3703 : {
3704 8 : NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
3705 :
3706 8 : LOG(("nsHttpChannel::Resume [this=%p]\n", this));
3707 :
3708 8 : if (--mSuspendCount == 0 && mCallOnResume) {
3709 0 : nsresult rv = AsyncCall(mCallOnResume);
3710 0 : mCallOnResume = nsnull;
3711 0 : NS_ENSURE_SUCCESS(rv, rv);
3712 : }
3713 :
3714 8 : if (mTransactionPump)
3715 8 : return mTransactionPump->Resume();
3716 0 : if (mCachePump)
3717 0 : return mCachePump->Resume();
3718 :
3719 0 : return NS_OK;
3720 : }
3721 :
3722 : //-----------------------------------------------------------------------------
3723 : // nsHttpChannel::nsIChannel
3724 : //-----------------------------------------------------------------------------
3725 :
3726 : NS_IMETHODIMP
3727 4 : nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
3728 : {
3729 4 : NS_ENSURE_ARG_POINTER(securityInfo);
3730 4 : *securityInfo = mSecurityInfo;
3731 4 : NS_IF_ADDREF(*securityInfo);
3732 4 : return NS_OK;
3733 : }
3734 :
3735 : NS_IMETHODIMP
3736 3489 : nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
3737 : {
3738 3489 : LOG(("nsHttpChannel::AsyncOpen [this=%p]\n", this));
3739 :
3740 3489 : NS_ENSURE_ARG_POINTER(listener);
3741 3489 : NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
3742 3486 : NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
3743 :
3744 : nsresult rv;
3745 :
3746 3483 : if (mCanceled)
3747 0 : return mStatus;
3748 :
3749 3483 : rv = NS_CheckPortSafety(mURI);
3750 3483 : if (NS_FAILED(rv))
3751 0 : return rv;
3752 :
3753 3483 : if (!(mConnectionInfo && mConnectionInfo->UsingHttpProxy())) {
3754 : // Start a DNS lookup very early in case the real open is queued the DNS can
3755 : // happen in parallel. Do not do so in the presence of an HTTP proxy as
3756 : // all lookups other than for the proxy itself are done by the proxy.
3757 : //
3758 : // We keep the DNS prefetch object around so that we can retrieve
3759 : // timing information from it. There is no guarantee that we actually
3760 : // use the DNS prefetch data for the real connection, but as we keep
3761 : // this data around for 3 minutes by default, this should almost always
3762 : // be correct, and even when it isn't, the timing still represents _a_
3763 : // valid DNS lookup timing for the site, even if it is not _the_
3764 : // timing we used.
3765 6942 : mDNSPrefetch = new nsDNSPrefetch(mURI, mTimingEnabled);
3766 3471 : mDNSPrefetch->PrefetchHigh();
3767 : }
3768 :
3769 : // Remember the cookie header that was set, if any
3770 3483 : const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
3771 3483 : if (cookieHeader) {
3772 8 : mUserSetCookieHeader = cookieHeader;
3773 : }
3774 :
3775 3483 : AddCookiesToRequest();
3776 :
3777 : // check to see if authorization headers should be included
3778 3483 : mAuthProvider->AddAuthorizationHeaders();
3779 :
3780 : // notify "http-on-modify-request" observers
3781 3483 : gHttpHandler->OnModifyRequest(this);
3782 :
3783 : // Adjust mCaps according to our request headers:
3784 : // - If "Connection: close" is set as a request header, then do not bother
3785 : // trying to establish a keep-alive connection.
3786 3483 : if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
3787 0 : mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
3788 :
3789 3483 : if ((mLoadFlags & VALIDATE_ALWAYS) ||
3790 : (BYPASS_LOCAL_CACHE(mLoadFlags)))
3791 1931 : mCaps |= NS_HTTP_REFRESH_DNS;
3792 :
3793 : // Force-Reload should reset the persistent connection pool for this host
3794 3483 : if (mLoadFlags & LOAD_FRESH_CONNECTION)
3795 0 : mCaps |= NS_HTTP_CLEAR_KEEPALIVES;
3796 :
3797 3483 : mIsPending = true;
3798 3483 : mWasOpened = true;
3799 :
3800 3483 : mListener = listener;
3801 3483 : mListenerContext = context;
3802 :
3803 : // add ourselves to the load group. from this point forward, we'll report
3804 : // all failures asynchronously.
3805 3483 : if (mLoadGroup)
3806 8 : mLoadGroup->AddRequest(this, nsnull);
3807 :
3808 : // Collect mAsyncOpenTime after we have called all obsrevers like
3809 : // "http-on-modify-request" and load group observers that may set
3810 : // mTimingEnabled flag.
3811 3483 : if (mTimingEnabled)
3812 2 : mAsyncOpenTime = mozilla::TimeStamp::Now();
3813 :
3814 : // We may have been cancelled already, either by on-modify-request
3815 : // listeners or by load group observers; in that case, we should
3816 : // not send the request to the server
3817 3483 : if (mCanceled)
3818 1 : rv = mStatus;
3819 : else
3820 3482 : rv = Connect();
3821 3483 : if (NS_FAILED(rv)) {
3822 2 : LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
3823 2 : CloseCacheEntry(true);
3824 2 : AsyncAbort(rv);
3825 3481 : } else if (mLoadFlags & LOAD_CLASSIFY_URI) {
3826 0 : nsRefPtr<nsChannelClassifier> classifier = new nsChannelClassifier();
3827 0 : if (!classifier) {
3828 0 : Cancel(NS_ERROR_OUT_OF_MEMORY);
3829 0 : return NS_OK;
3830 : }
3831 :
3832 0 : rv = classifier->Start(this);
3833 0 : if (NS_FAILED(rv)) {
3834 0 : Cancel(rv);
3835 : }
3836 : }
3837 :
3838 3483 : return NS_OK;
3839 : }
3840 :
3841 : //-----------------------------------------------------------------------------
3842 : // nsHttpChannel::nsIHttpChannelInternal
3843 : //-----------------------------------------------------------------------------
3844 :
3845 : NS_IMETHODIMP
3846 9 : nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
3847 : {
3848 9 : LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
3849 : this, aFallbackKey));
3850 9 : mFallbackChannel = true;
3851 9 : mFallbackKey = aFallbackKey;
3852 :
3853 9 : return NS_OK;
3854 : }
3855 :
3856 : //-----------------------------------------------------------------------------
3857 : // nsHttpChannel::nsISupportsPriority
3858 : //-----------------------------------------------------------------------------
3859 :
3860 : NS_IMETHODIMP
3861 2 : nsHttpChannel::SetPriority(PRInt32 value)
3862 : {
3863 2 : PRInt16 newValue = clamped(value, PR_INT16_MIN, PR_INT16_MAX);
3864 2 : if (mPriority == newValue)
3865 0 : return NS_OK;
3866 2 : mPriority = newValue;
3867 2 : if (mTransaction)
3868 0 : gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
3869 2 : return NS_OK;
3870 : }
3871 :
3872 : //-----------------------------------------------------------------------------
3873 : // nsHttpChannel::nsIProtocolProxyCallback
3874 : //-----------------------------------------------------------------------------
3875 :
3876 : NS_IMETHODIMP
3877 10 : nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIURI *uri,
3878 : nsIProxyInfo *pi, nsresult status)
3879 : {
3880 10 : mProxyRequest = nsnull;
3881 :
3882 : // If status is a failure code, then it means that we failed to resolve
3883 : // proxy info. That is a non-fatal error assuming it wasn't because the
3884 : // request was canceled. We just failover to DIRECT when proxy resolution
3885 : // fails (failure can mean that the PAC URL could not be loaded).
3886 :
3887 : // Need to replace this channel with a new one. It would be complex to try
3888 : // to change the value of mConnectionInfo since so much of our state may
3889 : // depend on its state.
3890 10 : mTargetProxyInfo = pi;
3891 10 : HandleAsyncReplaceWithProxy();
3892 10 : return NS_OK;
3893 : }
3894 :
3895 : //-----------------------------------------------------------------------------
3896 : // nsHttpChannel::nsIProxiedChannel
3897 : //-----------------------------------------------------------------------------
3898 :
3899 : NS_IMETHODIMP
3900 3542 : nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
3901 : {
3902 3542 : if (!mConnectionInfo)
3903 0 : *result = nsnull;
3904 : else {
3905 3542 : *result = mConnectionInfo->ProxyInfo();
3906 3542 : NS_IF_ADDREF(*result);
3907 : }
3908 3542 : return NS_OK;
3909 : }
3910 :
3911 : //-----------------------------------------------------------------------------
3912 : // nsHttpChannel::nsITimedChannel
3913 : //-----------------------------------------------------------------------------
3914 :
3915 : NS_IMETHODIMP
3916 155 : nsHttpChannel::SetTimingEnabled(bool enabled) {
3917 155 : mTimingEnabled = enabled;
3918 155 : return NS_OK;
3919 : }
3920 :
3921 : NS_IMETHODIMP
3922 0 : nsHttpChannel::GetTimingEnabled(bool* _retval) {
3923 0 : *_retval = mTimingEnabled;
3924 0 : return NS_OK;
3925 : }
3926 :
3927 : NS_IMETHODIMP
3928 0 : nsHttpChannel::GetChannelCreation(mozilla::TimeStamp* _retval) {
3929 0 : *_retval = mChannelCreationTimestamp;
3930 0 : return NS_OK;
3931 : }
3932 :
3933 : NS_IMETHODIMP
3934 0 : nsHttpChannel::GetAsyncOpen(mozilla::TimeStamp* _retval) {
3935 0 : *_retval = mAsyncOpenTime;
3936 0 : return NS_OK;
3937 : }
3938 :
3939 : NS_IMETHODIMP
3940 0 : nsHttpChannel::GetDomainLookupStart(mozilla::TimeStamp* _retval) {
3941 0 : if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
3942 0 : *_retval = mDNSPrefetch->StartTimestamp();
3943 0 : else if (mTransaction)
3944 0 : *_retval = mTransaction->Timings().domainLookupStart;
3945 : else
3946 0 : *_retval = mTransactionTimings.domainLookupStart;
3947 0 : return NS_OK;
3948 : }
3949 :
3950 : NS_IMETHODIMP
3951 0 : nsHttpChannel::GetDomainLookupEnd(mozilla::TimeStamp* _retval) {
3952 0 : if (mDNSPrefetch && mDNSPrefetch->TimingsValid())
3953 0 : *_retval = mDNSPrefetch->EndTimestamp();
3954 0 : else if (mTransaction)
3955 0 : *_retval = mTransaction->Timings().domainLookupEnd;
3956 : else
3957 0 : *_retval = mTransactionTimings.domainLookupEnd;
3958 0 : return NS_OK;
3959 : }
3960 :
3961 : NS_IMETHODIMP
3962 0 : nsHttpChannel::GetConnectStart(mozilla::TimeStamp* _retval) {
3963 0 : if (mTransaction)
3964 0 : *_retval = mTransaction->Timings().connectStart;
3965 : else
3966 0 : *_retval = mTransactionTimings.connectStart;
3967 0 : return NS_OK;
3968 : }
3969 :
3970 : NS_IMETHODIMP
3971 0 : nsHttpChannel::GetConnectEnd(mozilla::TimeStamp* _retval) {
3972 0 : if (mTransaction)
3973 0 : *_retval = mTransaction->Timings().connectEnd;
3974 : else
3975 0 : *_retval = mTransactionTimings.connectEnd;
3976 0 : return NS_OK;
3977 : }
3978 :
3979 : NS_IMETHODIMP
3980 0 : nsHttpChannel::GetRequestStart(mozilla::TimeStamp* _retval) {
3981 0 : if (mTransaction)
3982 0 : *_retval = mTransaction->Timings().requestStart;
3983 : else
3984 0 : *_retval = mTransactionTimings.requestStart;
3985 0 : return NS_OK;
3986 : }
3987 :
3988 : NS_IMETHODIMP
3989 0 : nsHttpChannel::GetResponseStart(mozilla::TimeStamp* _retval) {
3990 0 : if (mTransaction)
3991 0 : *_retval = mTransaction->Timings().responseStart;
3992 : else
3993 0 : *_retval = mTransactionTimings.responseStart;
3994 0 : return NS_OK;
3995 : }
3996 :
3997 : NS_IMETHODIMP
3998 0 : nsHttpChannel::GetResponseEnd(mozilla::TimeStamp* _retval) {
3999 0 : if (mTransaction)
4000 0 : *_retval = mTransaction->Timings().responseEnd;
4001 : else
4002 0 : *_retval = mTransactionTimings.responseEnd;
4003 0 : return NS_OK;
4004 : }
4005 :
4006 : NS_IMETHODIMP
4007 0 : nsHttpChannel::GetCacheReadStart(mozilla::TimeStamp* _retval) {
4008 0 : *_retval = mCacheReadStart;
4009 0 : return NS_OK;
4010 : }
4011 :
4012 : NS_IMETHODIMP
4013 0 : nsHttpChannel::GetCacheReadEnd(mozilla::TimeStamp* _retval) {
4014 0 : *_retval = mCacheReadEnd;
4015 0 : return NS_OK;
4016 : }
4017 :
4018 : #define IMPL_TIMING_ATTR(name) \
4019 : NS_IMETHODIMP \
4020 : nsHttpChannel::Get##name##Time(PRTime* _retval) { \
4021 : mozilla::TimeStamp stamp; \
4022 : Get##name(&stamp); \
4023 : if (stamp.IsNull()) { \
4024 : *_retval = 0; \
4025 : return NS_OK; \
4026 : } \
4027 : *_retval = mChannelCreationTime + \
4028 : (stamp - mChannelCreationTimestamp).ToSeconds() * 1e6; \
4029 : return NS_OK; \
4030 : }
4031 :
4032 0 : IMPL_TIMING_ATTR(ChannelCreation)
4033 0 : IMPL_TIMING_ATTR(AsyncOpen)
4034 0 : IMPL_TIMING_ATTR(DomainLookupStart)
4035 0 : IMPL_TIMING_ATTR(DomainLookupEnd)
4036 0 : IMPL_TIMING_ATTR(ConnectStart)
4037 0 : IMPL_TIMING_ATTR(ConnectEnd)
4038 0 : IMPL_TIMING_ATTR(RequestStart)
4039 0 : IMPL_TIMING_ATTR(ResponseStart)
4040 0 : IMPL_TIMING_ATTR(ResponseEnd)
4041 0 : IMPL_TIMING_ATTR(CacheReadStart)
4042 0 : IMPL_TIMING_ATTR(CacheReadEnd)
4043 :
4044 : #undef IMPL_TIMING_ATTR
4045 :
4046 : //-----------------------------------------------------------------------------
4047 : // nsHttpChannel::nsIHttpAuthenticableChannel
4048 : //-----------------------------------------------------------------------------
4049 :
4050 : NS_IMETHODIMP
4051 3514 : nsHttpChannel::GetIsSSL(bool *aIsSSL)
4052 : {
4053 3514 : *aIsSSL = mConnectionInfo->UsingSSL();
4054 3514 : return NS_OK;
4055 : }
4056 :
4057 : NS_IMETHODIMP
4058 2 : nsHttpChannel::GetProxyMethodIsConnect(bool *aProxyMethodIsConnect)
4059 : {
4060 : *aProxyMethodIsConnect =
4061 2 : (mConnectionInfo->UsingHttpProxy() && mConnectionInfo->UsingSSL()) ||
4062 2 : mConnectionInfo->ShouldForceConnectMethod();
4063 2 : return NS_OK;
4064 : }
4065 :
4066 : NS_IMETHODIMP
4067 2 : nsHttpChannel::GetServerResponseHeader(nsACString &value)
4068 : {
4069 2 : if (!mResponseHead)
4070 0 : return NS_ERROR_NOT_AVAILABLE;
4071 2 : return mResponseHead->GetHeader(nsHttp::Server, value);
4072 : }
4073 :
4074 : NS_IMETHODIMP
4075 10 : nsHttpChannel::GetProxyChallenges(nsACString &value)
4076 : {
4077 10 : if (!mResponseHead)
4078 0 : return NS_ERROR_UNEXPECTED;
4079 10 : return mResponseHead->GetHeader(nsHttp::Proxy_Authenticate, value);
4080 : }
4081 :
4082 : NS_IMETHODIMP
4083 49 : nsHttpChannel::GetWWWChallenges(nsACString &value)
4084 : {
4085 49 : if (!mResponseHead)
4086 0 : return NS_ERROR_UNEXPECTED;
4087 49 : return mResponseHead->GetHeader(nsHttp::WWW_Authenticate, value);
4088 : }
4089 :
4090 : NS_IMETHODIMP
4091 9 : nsHttpChannel::SetProxyCredentials(const nsACString &value)
4092 : {
4093 9 : return mRequestHead.SetHeader(nsHttp::Proxy_Authorization, value);
4094 : }
4095 :
4096 : NS_IMETHODIMP
4097 21 : nsHttpChannel::SetWWWCredentials(const nsACString &value)
4098 : {
4099 21 : return mRequestHead.SetHeader(nsHttp::Authorization, value);
4100 : }
4101 :
4102 : //-----------------------------------------------------------------------------
4103 : // Methods that nsIHttpAuthenticableChannel dupes from other IDLs, which we
4104 : // get from HttpBaseChannel, must be explicitly forwarded, because C++ sucks.
4105 : //
4106 :
4107 : NS_IMETHODIMP
4108 12499 : nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
4109 : {
4110 12499 : return HttpBaseChannel::GetLoadFlags(aLoadFlags);
4111 : }
4112 :
4113 : NS_IMETHODIMP
4114 11346 : nsHttpChannel::GetURI(nsIURI **aURI)
4115 : {
4116 11346 : return HttpBaseChannel::GetURI(aURI);
4117 : }
4118 :
4119 : NS_IMETHODIMP
4120 955 : nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
4121 : {
4122 955 : return HttpBaseChannel::GetNotificationCallbacks(aCallbacks);
4123 : }
4124 :
4125 : NS_IMETHODIMP
4126 784 : nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
4127 : {
4128 784 : return HttpBaseChannel::GetLoadGroup(aLoadGroup);
4129 : }
4130 :
4131 : NS_IMETHODIMP
4132 5400 : nsHttpChannel::GetRequestMethod(nsACString& aMethod)
4133 : {
4134 5400 : return HttpBaseChannel::GetRequestMethod(aMethod);
4135 : }
4136 :
4137 :
4138 : //-----------------------------------------------------------------------------
4139 : // nsHttpChannel::nsIRequestObserver
4140 : //-----------------------------------------------------------------------------
4141 :
4142 : NS_IMETHODIMP
4143 3194 : nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
4144 : {
4145 6388 : SAMPLE_LABEL("nsHttpChannel", "OnStartRequest");
4146 3194 : if (!(mCanceled || NS_FAILED(mStatus))) {
4147 : // capture the request's status, so our consumers will know ASAP of any
4148 : // connection failures, etc - bug 93581
4149 3189 : request->GetStatus(&mStatus);
4150 : }
4151 :
4152 3194 : LOG(("nsHttpChannel::OnStartRequest [this=%p request=%p status=%x]\n",
4153 : this, request, mStatus));
4154 :
4155 : // Make sure things are what we expect them to be...
4156 3194 : NS_ASSERTION(request == mCachePump || request == mTransactionPump,
4157 : "Unexpected request");
4158 3194 : NS_ASSERTION(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
4159 : "If we have both pumps, the cache content must be partial");
4160 :
4161 3194 : if (!mSecurityInfo && !mCachePump && mTransaction) {
4162 : // grab the security info from the connection object; the transaction
4163 : // is guaranteed to own a reference to the connection.
4164 2977 : mSecurityInfo = mTransaction->SecurityInfo();
4165 : }
4166 :
4167 3196 : if (!mCachePump && NS_FAILED(mStatus) &&
4168 2 : (mLoadFlags & LOAD_REPLACE) && mOriginalURI && mAllowSpdy) {
4169 : // For sanity's sake we may want to cancel an alternate protocol
4170 : // redirection involving the original host name
4171 :
4172 4 : nsCAutoString hostPort;
4173 2 : if (NS_SUCCEEDED(mOriginalURI->GetHostPort(hostPort)))
4174 2 : gHttpHandler->ConnMgr()->RemoveSpdyAlternateProtocol(hostPort);
4175 : }
4176 :
4177 : // don't enter this block if we're reading from the cache...
4178 3194 : if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
4179 : // mTransactionPump doesn't hit OnInputStreamReady and call this until
4180 : // all of the response headers have been acquired, so we can take ownership
4181 : // of them from the transaction.
4182 2819 : mResponseHead = mTransaction->TakeResponseHead();
4183 : // the response head may be null if the transaction was cancelled. in
4184 : // which case we just need to call OnStartRequest/OnStopRequest.
4185 2819 : if (mResponseHead)
4186 2819 : return ProcessResponse();
4187 :
4188 0 : NS_WARNING("No response head in OnStartRequest");
4189 : }
4190 :
4191 : // avoid crashing if mListener happens to be null...
4192 375 : if (!mListener) {
4193 0 : NS_NOTREACHED("mListener is null");
4194 0 : return NS_OK;
4195 : }
4196 :
4197 : // on proxy errors, try to failover
4198 375 : if (mConnectionInfo->ProxyInfo() &&
4199 : (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
4200 : mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
4201 : mStatus == NS_ERROR_NET_TIMEOUT)) {
4202 :
4203 2 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
4204 2 : if (NS_SUCCEEDED(ProxyFailover()))
4205 2 : return NS_OK;
4206 0 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest1);
4207 : }
4208 :
4209 373 : return ContinueOnStartRequest2(NS_OK);
4210 : }
4211 :
4212 : nsresult
4213 2 : nsHttpChannel::ContinueOnStartRequest1(nsresult result)
4214 : {
4215 : // Success indicates we passed ProxyFailover, in that case we must not continue
4216 : // with this code chain.
4217 2 : if (NS_SUCCEEDED(result))
4218 1 : return NS_OK;
4219 :
4220 1 : return ContinueOnStartRequest2(result);
4221 : }
4222 :
4223 : nsresult
4224 374 : nsHttpChannel::ContinueOnStartRequest2(nsresult result)
4225 : {
4226 : // on other request errors, try to fall back
4227 374 : if (NS_FAILED(mStatus)) {
4228 157 : PushRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
4229 : bool waitingForRedirectCallback;
4230 157 : ProcessFallback(&waitingForRedirectCallback);
4231 157 : if (waitingForRedirectCallback)
4232 2 : return NS_OK;
4233 155 : PopRedirectAsyncFunc(&nsHttpChannel::ContinueOnStartRequest3);
4234 : }
4235 :
4236 372 : return ContinueOnStartRequest3(NS_OK);
4237 : }
4238 :
4239 : nsresult
4240 374 : nsHttpChannel::ContinueOnStartRequest3(nsresult result)
4241 : {
4242 374 : if (mFallingBack)
4243 1 : return NS_OK;
4244 :
4245 373 : return CallOnStartRequest();
4246 : }
4247 :
4248 : NS_IMETHODIMP
4249 3194 : nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
4250 : {
4251 6388 : SAMPLE_LABEL("network", "nsHttpChannel::OnStopRequest");
4252 3194 : LOG(("nsHttpChannel::OnStopRequest [this=%p request=%p status=%x]\n",
4253 : this, request, status));
4254 :
4255 3194 : if (mTimingEnabled && request == mCachePump) {
4256 0 : mCacheReadEnd = mozilla::TimeStamp::Now();
4257 : }
4258 :
4259 : // allow content to be cached if it was loaded successfully (bug #482935)
4260 3194 : bool contentComplete = NS_SUCCEEDED(status);
4261 :
4262 : // honor the cancelation status even if the underlying transaction completed.
4263 3194 : if (mCanceled || NS_FAILED(mStatus))
4264 318 : status = mStatus;
4265 :
4266 3194 : if (mCachedContentIsPartial) {
4267 2 : if (NS_SUCCEEDED(status)) {
4268 : // mTransactionPump should be suspended
4269 2 : NS_ASSERTION(request != mTransactionPump,
4270 : "byte-range transaction finished prematurely");
4271 :
4272 2 : if (request == mCachePump) {
4273 : bool streamDone;
4274 2 : status = OnDoneReadingPartialCacheEntry(&streamDone);
4275 2 : if (NS_SUCCEEDED(status) && !streamDone)
4276 2 : return status;
4277 : // otherwise, fall through and fire OnStopRequest...
4278 : }
4279 : else
4280 0 : NS_NOTREACHED("unexpected request");
4281 : }
4282 : // Do not to leave the transaction in a suspended state in error cases.
4283 0 : if (NS_FAILED(status) && mTransaction)
4284 0 : gHttpHandler->CancelTransaction(mTransaction, status);
4285 : }
4286 :
4287 3192 : if (mTransaction) {
4288 : // determine if we should call DoAuthRetry
4289 2977 : bool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
4290 :
4291 : //
4292 : // grab references to connection in case we need to retry an
4293 : // authentication request over it or use it for an upgrade
4294 : // to another protocol.
4295 : //
4296 : // this code relies on the code in nsHttpTransaction::Close, which
4297 : // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
4298 : // keep the connection around after the transaction is finished.
4299 : //
4300 5954 : nsRefPtr<nsAHttpConnection> conn;
4301 2977 : if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION)) {
4302 11 : conn = mTransaction->Connection();
4303 : // This is so far a workaround to fix leak when reusing unpersistent
4304 : // connection for authentication retry. See bug 459620 comment 4
4305 : // for details.
4306 11 : if (conn && !conn->IsPersistent())
4307 11 : conn = nsnull;
4308 : }
4309 :
4310 5954 : nsRefPtr<nsAHttpConnection> stickyConn;
4311 2977 : if (mCaps & NS_HTTP_STICKY_CONNECTION)
4312 23 : stickyConn = mTransaction->Connection();
4313 :
4314 : // at this point, we're done with the transaction
4315 2977 : mTransactionTimings = mTransaction->Timings();
4316 2977 : mTransaction = nsnull;
4317 2977 : mTransactionPump = 0;
4318 :
4319 : // We no longer need the dns prefetch object
4320 2977 : if (mDNSPrefetch && mDNSPrefetch->TimingsValid()) {
4321 : mTransactionTimings.domainLookupStart =
4322 0 : mDNSPrefetch->StartTimestamp();
4323 : mTransactionTimings.domainLookupEnd =
4324 0 : mDNSPrefetch->EndTimestamp();
4325 : }
4326 2977 : mDNSPrefetch = nsnull;
4327 :
4328 : // handle auth retry...
4329 2977 : if (authRetry) {
4330 23 : mAuthRetryPending = false;
4331 23 : status = DoAuthRetry(conn);
4332 23 : if (NS_SUCCEEDED(status))
4333 23 : return NS_OK;
4334 : }
4335 :
4336 : // If DoAuthRetry failed, or if we have been cancelled since showing
4337 : // the auth. dialog, then we need to send OnStartRequest now
4338 2954 : if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
4339 0 : NS_ASSERTION(NS_FAILED(status), "should have a failure code here");
4340 : // NOTE: since we have a failure status, we can ignore the return
4341 : // value from onStartRequest.
4342 0 : mListener->OnStartRequest(this, mListenerContext);
4343 : }
4344 :
4345 : // if this transaction has been replaced, then bail.
4346 2954 : if (mTransactionReplaced)
4347 6 : return NS_OK;
4348 :
4349 2948 : if (mUpgradeProtocolCallback && stickyConn &&
4350 0 : mResponseHead && mResponseHead->Status() == 101) {
4351 0 : nsCOMPtr<nsISocketTransport> socketTransport;
4352 0 : nsCOMPtr<nsIAsyncInputStream> socketIn;
4353 0 : nsCOMPtr<nsIAsyncOutputStream> socketOut;
4354 :
4355 : nsresult rv;
4356 0 : rv = stickyConn->TakeTransport(getter_AddRefs(socketTransport),
4357 0 : getter_AddRefs(socketIn),
4358 0 : getter_AddRefs(socketOut));
4359 0 : if (NS_SUCCEEDED(rv))
4360 0 : mUpgradeProtocolCallback->OnTransportAvailable(socketTransport,
4361 : socketIn,
4362 0 : socketOut);
4363 : }
4364 : }
4365 :
4366 3163 : mIsPending = false;
4367 3163 : mStatus = status;
4368 :
4369 : // perform any final cache operations before we close the cache entry.
4370 3163 : if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
4371 : mRequestTimeInitialized){
4372 1272 : FinalizeCacheEntry();
4373 : }
4374 :
4375 3163 : if (mListener) {
4376 3039 : LOG((" calling OnStopRequest\n"));
4377 3039 : mListener->OnStopRequest(this, mListenerContext, status);
4378 3039 : mListener = 0;
4379 3039 : mListenerContext = 0;
4380 : }
4381 :
4382 3163 : if (mCacheEntry) {
4383 1481 : bool asFile = false;
4384 3728 : if (mInitedCacheEntry && !mCachedContentIsPartial &&
4385 1137 : (NS_SUCCEEDED(mStatus) || contentComplete) &&
4386 : (mCacheAccess & nsICache::ACCESS_WRITE) &&
4387 1110 : NS_SUCCEEDED(GetCacheAsFile(&asFile)) && asFile) {
4388 : // We can allow others access to the cache entry
4389 : // because we don't write to the cache anymore.
4390 : // CloseCacheEntry may not actually close the cache
4391 : // entry immediately because someone (such as XHR2
4392 : // blob response) may hold the token to the cache
4393 : // entry. So we mark the cache valid here.
4394 : // We also need to check the entry is stored as file
4395 : // because we write to the cache asynchronously when
4396 : // it isn't stored in the file and it isn't completely
4397 : // written to the disk yet.
4398 0 : mCacheEntry->MarkValid();
4399 : }
4400 1481 : CloseCacheEntry(!contentComplete);
4401 : }
4402 :
4403 3163 : if (mOfflineCacheEntry)
4404 24 : CloseOfflineCacheEntry();
4405 :
4406 3163 : if (mLoadGroup)
4407 2 : mLoadGroup->RemoveRequest(this, nsnull, status);
4408 :
4409 : // We don't need this info anymore
4410 3163 : CleanRedirectCacheChainIfNecessary();
4411 :
4412 3163 : mCallbacks = nsnull;
4413 3163 : mProgressSink = nsnull;
4414 :
4415 3163 : return NS_OK;
4416 : }
4417 :
4418 : //-----------------------------------------------------------------------------
4419 : // nsHttpChannel::nsIStreamListener
4420 : //-----------------------------------------------------------------------------
4421 :
4422 : NS_IMETHODIMP
4423 3064 : nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
4424 : nsIInputStream *input,
4425 : PRUint32 offset, PRUint32 count)
4426 : {
4427 6128 : SAMPLE_LABEL("network", "nsHttpChannel::OnDataAvailable");
4428 3064 : LOG(("nsHttpChannel::OnDataAvailable [this=%p request=%p offset=%u count=%u]\n",
4429 : this, request, offset, count));
4430 :
4431 : // don't send out OnDataAvailable notifications if we've been canceled.
4432 3064 : if (mCanceled)
4433 0 : return mStatus;
4434 :
4435 3064 : NS_ASSERTION(mResponseHead, "No response head in ODA!!");
4436 :
4437 3064 : NS_ASSERTION(!(mCachedContentIsPartial && (request == mTransactionPump)),
4438 : "transaction pump not suspended");
4439 :
4440 3064 : if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
4441 : PRUint32 n;
4442 23 : return input->ReadSegments(NS_DiscardSegment, nsnull, count, &n);
4443 : }
4444 :
4445 3041 : if (mListener) {
4446 : //
4447 : // synthesize transport progress event. we do this here since we want
4448 : // to delay OnProgress events until we start streaming data. this is
4449 : // crucially important since it impacts the lock icon (see bug 240053).
4450 : //
4451 : nsresult transportStatus;
4452 3041 : if (request == mCachePump)
4453 217 : transportStatus = nsITransport::STATUS_READING;
4454 : else
4455 2824 : transportStatus = nsISocketTransport::STATUS_RECEIVING_FROM;
4456 :
4457 : // mResponseHead may reference new or cached headers, but either way it
4458 : // holds our best estimate of the total content length. Even in the case
4459 : // of a byte range request, the content length stored in the cached
4460 : // response headers is what we want to use here.
4461 :
4462 3041 : PRUint64 progressMax(PRUint64(mResponseHead->ContentLength()));
4463 3041 : PRUint64 progress = mLogicalOffset + PRUint64(count);
4464 3041 : NS_ASSERTION(progress <= progressMax, "unexpected progress values");
4465 :
4466 3041 : OnTransportStatus(nsnull, transportStatus, progress, progressMax);
4467 :
4468 : //
4469 : // we have to manually keep the logical offset of the stream up-to-date.
4470 : // we cannot depend solely on the offset provided, since we may have
4471 : // already streamed some data from another source (see, for example,
4472 : // OnDoneReadingPartialCacheEntry).
4473 : //
4474 3041 : nsresult rv = mListener->OnDataAvailable(this,
4475 : mListenerContext,
4476 : input,
4477 : mLogicalOffset,
4478 3041 : count);
4479 3041 : if (NS_SUCCEEDED(rv))
4480 3037 : mLogicalOffset = progress;
4481 3041 : return rv;
4482 : }
4483 :
4484 0 : return NS_ERROR_ABORT;
4485 : }
4486 :
4487 : //-----------------------------------------------------------------------------
4488 : // nsHttpChannel::nsITransportEventSink
4489 : //-----------------------------------------------------------------------------
4490 :
4491 : NS_IMETHODIMP
4492 6914 : nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
4493 : PRUint64 progress, PRUint64 progressMax)
4494 : {
4495 : // cache the progress sink so we don't have to query for it each time.
4496 6914 : if (!mProgressSink)
4497 5920 : GetCallback(mProgressSink);
4498 :
4499 6914 : if (status == nsISocketTransport::STATUS_CONNECTED_TO ||
4500 : status == nsISocketTransport::STATUS_WAITING_FOR) {
4501 : nsCOMPtr<nsISocketTransport> socketTransport =
4502 5862 : do_QueryInterface(trans);
4503 2931 : if (socketTransport) {
4504 2931 : socketTransport->GetSelfAddr(&mSelfAddr);
4505 2931 : socketTransport->GetPeerAddr(&mPeerAddr);
4506 : }
4507 : }
4508 :
4509 : // block socket status event after Cancel or OnStopRequest has been called.
4510 6914 : if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
4511 219 : LOG(("sending status notification [this=%p status=%x progress=%llu/%llu]\n",
4512 : this, status, progress, progressMax));
4513 :
4514 438 : nsCAutoString host;
4515 219 : mURI->GetHost(host);
4516 219 : mProgressSink->OnStatus(this, nsnull, status,
4517 219 : NS_ConvertUTF8toUTF16(host).get());
4518 :
4519 219 : if (progress > 0) {
4520 120 : NS_ASSERTION(progress <= progressMax, "unexpected progress values");
4521 120 : mProgressSink->OnProgress(this, nsnull, progress, progressMax);
4522 : }
4523 : }
4524 : #ifdef DEBUG
4525 : else
4526 6695 : LOG(("skipping status notification [this=%p sink=%p pending=%u background=%x]\n",
4527 : this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
4528 : #endif
4529 :
4530 6914 : return NS_OK;
4531 : }
4532 :
4533 : //-----------------------------------------------------------------------------
4534 : // nsHttpChannel::nsICacheInfoChannel
4535 : //-----------------------------------------------------------------------------
4536 :
4537 : NS_IMETHODIMP
4538 19 : nsHttpChannel::IsFromCache(bool *value)
4539 : {
4540 19 : if (!mIsPending)
4541 0 : return NS_ERROR_NOT_AVAILABLE;
4542 :
4543 : // return false if reading a partial cache entry; the data isn't entirely
4544 : // from the cache!
4545 :
4546 19 : *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
4547 19 : mCachedContentIsValid && !mCachedContentIsPartial;
4548 :
4549 19 : return NS_OK;
4550 : }
4551 :
4552 : NS_IMETHODIMP
4553 0 : nsHttpChannel::GetCacheTokenExpirationTime(PRUint32 *_retval)
4554 : {
4555 0 : NS_ENSURE_ARG_POINTER(_retval);
4556 0 : if (!mCacheEntry)
4557 0 : return NS_ERROR_NOT_AVAILABLE;
4558 :
4559 0 : return mCacheEntry->GetExpirationTime(_retval);
4560 : }
4561 :
4562 : NS_IMETHODIMP
4563 0 : nsHttpChannel::GetCacheTokenCachedCharset(nsACString &_retval)
4564 : {
4565 : nsresult rv;
4566 :
4567 0 : if (!mCacheEntry)
4568 0 : return NS_ERROR_NOT_AVAILABLE;
4569 :
4570 0 : nsXPIDLCString cachedCharset;
4571 0 : rv = mCacheEntry->GetMetaDataElement("charset",
4572 0 : getter_Copies(cachedCharset));
4573 0 : if (NS_SUCCEEDED(rv))
4574 0 : _retval = cachedCharset;
4575 :
4576 0 : return rv;
4577 : }
4578 :
4579 : NS_IMETHODIMP
4580 0 : nsHttpChannel::SetCacheTokenCachedCharset(const nsACString &aCharset)
4581 : {
4582 0 : if (!mCacheEntry)
4583 0 : return NS_ERROR_NOT_AVAILABLE;
4584 :
4585 0 : return mCacheEntry->SetMetaDataElement("charset",
4586 0 : PromiseFlatCString(aCharset).get());
4587 : }
4588 :
4589 : //-----------------------------------------------------------------------------
4590 : // nsHttpChannel::nsICachingChannel
4591 : //-----------------------------------------------------------------------------
4592 :
4593 : NS_IMETHODIMP
4594 373 : nsHttpChannel::GetCacheToken(nsISupports **token)
4595 : {
4596 373 : NS_ENSURE_ARG_POINTER(token);
4597 373 : if (!mCacheEntry)
4598 0 : return NS_ERROR_NOT_AVAILABLE;
4599 373 : return CallQueryInterface(mCacheEntry, token);
4600 : }
4601 :
4602 : NS_IMETHODIMP
4603 0 : nsHttpChannel::SetCacheToken(nsISupports *token)
4604 : {
4605 0 : return NS_ERROR_NOT_IMPLEMENTED;
4606 : }
4607 :
4608 : NS_IMETHODIMP
4609 8 : nsHttpChannel::GetOfflineCacheToken(nsISupports **token)
4610 : {
4611 8 : NS_ENSURE_ARG_POINTER(token);
4612 8 : if (!mOfflineCacheEntry)
4613 0 : return NS_ERROR_NOT_AVAILABLE;
4614 8 : return CallQueryInterface(mOfflineCacheEntry, token);
4615 : }
4616 :
4617 : NS_IMETHODIMP
4618 0 : nsHttpChannel::SetOfflineCacheToken(nsISupports *token)
4619 : {
4620 0 : return NS_ERROR_NOT_IMPLEMENTED;
4621 : }
4622 :
4623 : class nsHttpChannelCacheKey : public nsISupportsPRUint32,
4624 : public nsISupportsCString
4625 10 : {
4626 : NS_DECL_ISUPPORTS
4627 :
4628 : NS_DECL_NSISUPPORTSPRIMITIVE
4629 9 : NS_FORWARD_NSISUPPORTSPRUINT32(mSupportsPRUint32->)
4630 :
4631 : // Both interfaces declares toString method with the same signature.
4632 : // Thus we have to delegate only to nsISupportsPRUint32 implementation.
4633 0 : NS_SCRIPTABLE NS_IMETHOD GetData(nsACString & aData)
4634 : {
4635 0 : return mSupportsCString->GetData(aData);
4636 : }
4637 0 : NS_SCRIPTABLE NS_IMETHOD SetData(const nsACString & aData)
4638 : {
4639 0 : return mSupportsCString->SetData(aData);
4640 : }
4641 :
4642 : public:
4643 : nsresult SetData(PRUint32 aPostID, const nsACString& aKey);
4644 :
4645 : protected:
4646 : nsCOMPtr<nsISupportsPRUint32> mSupportsPRUint32;
4647 : nsCOMPtr<nsISupportsCString> mSupportsCString;
4648 : };
4649 :
4650 36 : NS_IMPL_ADDREF(nsHttpChannelCacheKey)
4651 41 : NS_IMPL_RELEASE(nsHttpChannelCacheKey)
4652 64 : NS_INTERFACE_TABLE_HEAD(nsHttpChannelCacheKey)
4653 : NS_INTERFACE_TABLE_BEGIN
4654 : NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
4655 : nsISupports, nsISupportsPRUint32)
4656 : NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(nsHttpChannelCacheKey,
4657 : nsISupportsPrimitive, nsISupportsPRUint32)
4658 : NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
4659 : nsISupportsPRUint32)
4660 : NS_INTERFACE_TABLE_ENTRY(nsHttpChannelCacheKey,
4661 : nsISupportsCString)
4662 64 : NS_INTERFACE_TABLE_END
4663 64 : NS_INTERFACE_TABLE_TAIL
4664 :
4665 0 : NS_IMETHODIMP nsHttpChannelCacheKey::GetType(PRUint16 *aType)
4666 : {
4667 0 : NS_ENSURE_ARG_POINTER(aType);
4668 :
4669 0 : *aType = TYPE_PRUINT32;
4670 0 : return NS_OK;
4671 : }
4672 :
4673 5 : nsresult nsHttpChannelCacheKey::SetData(PRUint32 aPostID,
4674 : const nsACString& aKey)
4675 : {
4676 : nsresult rv;
4677 :
4678 : mSupportsCString =
4679 5 : do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
4680 5 : if (NS_FAILED(rv)) return rv;
4681 :
4682 5 : mSupportsCString->SetData(aKey);
4683 5 : if (NS_FAILED(rv)) return rv;
4684 :
4685 : mSupportsPRUint32 =
4686 5 : do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
4687 5 : if (NS_FAILED(rv)) return rv;
4688 :
4689 5 : mSupportsPRUint32->SetData(aPostID);
4690 5 : if (NS_FAILED(rv)) return rv;
4691 :
4692 5 : return NS_OK;
4693 : }
4694 :
4695 : NS_IMETHODIMP
4696 5 : nsHttpChannel::GetCacheKey(nsISupports **key)
4697 : {
4698 : nsresult rv;
4699 5 : NS_ENSURE_ARG_POINTER(key);
4700 :
4701 5 : LOG(("nsHttpChannel::GetCacheKey [this=%p]\n", this));
4702 :
4703 5 : *key = nsnull;
4704 :
4705 : nsRefPtr<nsHttpChannelCacheKey> container =
4706 10 : new nsHttpChannelCacheKey();
4707 :
4708 5 : if (!container)
4709 0 : return NS_ERROR_OUT_OF_MEMORY;
4710 :
4711 10 : nsCAutoString cacheKey;
4712 5 : rv = GenerateCacheKey(mPostID, cacheKey);
4713 5 : if (NS_FAILED(rv)) return rv;
4714 :
4715 5 : rv = container->SetData(mPostID, cacheKey);
4716 5 : if (NS_FAILED(rv)) return rv;
4717 :
4718 5 : return CallQueryInterface(container.get(), key);
4719 : }
4720 :
4721 : NS_IMETHODIMP
4722 3 : nsHttpChannel::SetCacheKey(nsISupports *key)
4723 : {
4724 : nsresult rv;
4725 :
4726 3 : LOG(("nsHttpChannel::SetCacheKey [this=%p key=%p]\n", this, key));
4727 :
4728 : // can only set the cache key if a load is not in progress
4729 3 : NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4730 :
4731 3 : if (!key)
4732 0 : mPostID = 0;
4733 : else {
4734 : // extract the post id
4735 6 : nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
4736 3 : if (NS_FAILED(rv)) return rv;
4737 :
4738 3 : rv = container->GetData(&mPostID);
4739 3 : if (NS_FAILED(rv)) return rv;
4740 : }
4741 3 : return NS_OK;
4742 : }
4743 :
4744 : NS_IMETHODIMP
4745 1110 : nsHttpChannel::GetCacheAsFile(bool *value)
4746 : {
4747 1110 : NS_ENSURE_ARG_POINTER(value);
4748 1110 : if (!mCacheEntry)
4749 0 : return NS_ERROR_NOT_AVAILABLE;
4750 : nsCacheStoragePolicy storagePolicy;
4751 1110 : mCacheEntry->GetStoragePolicy(&storagePolicy);
4752 1110 : *value = (storagePolicy == nsICache::STORE_ON_DISK_AS_FILE);
4753 1110 : return NS_OK;
4754 : }
4755 :
4756 : NS_IMETHODIMP
4757 1 : nsHttpChannel::SetCacheAsFile(bool value)
4758 : {
4759 1 : if (!mCacheEntry || mLoadFlags & INHIBIT_PERSISTENT_CACHING)
4760 0 : return NS_ERROR_NOT_AVAILABLE;
4761 : nsCacheStoragePolicy policy;
4762 1 : if (value)
4763 1 : policy = nsICache::STORE_ON_DISK_AS_FILE;
4764 : else
4765 0 : policy = nsICache::STORE_ANYWHERE;
4766 1 : return mCacheEntry->SetStoragePolicy(policy);
4767 : }
4768 :
4769 :
4770 : NS_IMETHODIMP
4771 0 : nsHttpChannel::GetCacheForOfflineUse(bool *value)
4772 : {
4773 0 : *value = mCacheForOfflineUse;
4774 :
4775 0 : return NS_OK;
4776 : }
4777 :
4778 : NS_IMETHODIMP
4779 36 : nsHttpChannel::SetCacheForOfflineUse(bool value)
4780 : {
4781 36 : mCacheForOfflineUse = value;
4782 :
4783 36 : return NS_OK;
4784 : }
4785 :
4786 : NS_IMETHODIMP
4787 0 : nsHttpChannel::GetOfflineCacheClientID(nsACString &value)
4788 : {
4789 0 : value = mOfflineCacheClientID;
4790 :
4791 0 : return NS_OK;
4792 : }
4793 :
4794 : NS_IMETHODIMP
4795 36 : nsHttpChannel::SetOfflineCacheClientID(const nsACString &value)
4796 : {
4797 36 : mOfflineCacheClientID = value;
4798 :
4799 36 : return NS_OK;
4800 : }
4801 :
4802 : NS_IMETHODIMP
4803 0 : nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
4804 : {
4805 0 : if (!mCacheEntry)
4806 0 : return NS_ERROR_NOT_AVAILABLE;
4807 0 : return mCacheEntry->GetFile(cacheFile);
4808 : }
4809 :
4810 : //-----------------------------------------------------------------------------
4811 : // nsHttpChannel::nsIResumableChannel
4812 : //-----------------------------------------------------------------------------
4813 :
4814 : NS_IMETHODIMP
4815 27 : nsHttpChannel::ResumeAt(PRUint64 aStartPos,
4816 : const nsACString& aEntityID)
4817 : {
4818 27 : LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n",
4819 : this, aStartPos, PromiseFlatCString(aEntityID).get()));
4820 27 : mEntityID = aEntityID;
4821 27 : mStartPos = aStartPos;
4822 27 : mResuming = true;
4823 27 : return NS_OK;
4824 : }
4825 :
4826 : //-----------------------------------------------------------------------------
4827 : // nsHttpChannel::nsICacheListener
4828 : //-----------------------------------------------------------------------------
4829 :
4830 : NS_IMETHODIMP
4831 1599 : nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
4832 : nsCacheAccessMode access,
4833 : nsresult status)
4834 : {
4835 : nsresult rv;
4836 :
4837 1599 : LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%p entry=%p "
4838 : "access=%x status=%x]\n", this, entry, access, status));
4839 :
4840 : // if the channel's already fired onStopRequest, then we should ignore
4841 : // this event.
4842 1599 : if (!mIsPending)
4843 0 : return NS_OK;
4844 :
4845 1599 : nsOnCacheEntryAvailableCallback callback = mOnCacheEntryAvailableCallback;
4846 1599 : mOnCacheEntryAvailableCallback = nsnull;
4847 :
4848 1599 : NS_ASSERTION(callback,
4849 : "nsHttpChannel::OnCacheEntryAvailable called without callback");
4850 1599 : rv = ((*this).*callback)(entry, access, status, false);
4851 :
4852 1599 : if (NS_FAILED(rv)) {
4853 35 : LOG(("AsyncOpenCacheEntry failed [rv=%x]\n", rv));
4854 35 : if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
4855 : // If we have a fallback URI (and we're not already
4856 : // falling back), process the fallback asynchronously.
4857 9 : if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
4858 2 : rv = AsyncCall(&nsHttpChannel::HandleAsyncFallback);
4859 2 : if (NS_SUCCEEDED(rv))
4860 2 : return rv;
4861 : }
4862 : }
4863 33 : CloseCacheEntry(true);
4864 33 : AsyncAbort(rv);
4865 : }
4866 :
4867 1597 : return NS_OK;
4868 : }
4869 :
4870 : nsresult
4871 23 : nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
4872 : {
4873 23 : LOG(("nsHttpChannel::DoAuthRetry [this=%p]\n", this));
4874 :
4875 23 : NS_ASSERTION(!mTransaction, "should not have a transaction");
4876 : nsresult rv;
4877 :
4878 : // toggle mIsPending to allow nsIObserver implementations to modify
4879 : // the request headers (bug 95044).
4880 23 : mIsPending = false;
4881 :
4882 : // fetch cookies, and add them to the request header.
4883 : // the server response could have included cookies that must be sent with
4884 : // this authentication attempt (bug 84794).
4885 : // TODO: save cookies from auth response and send them here (bug 572151).
4886 23 : AddCookiesToRequest();
4887 :
4888 : // notify "http-on-modify-request" observers
4889 23 : gHttpHandler->OnModifyRequest(this);
4890 :
4891 23 : mIsPending = true;
4892 :
4893 : // get rid of the old response headers
4894 23 : mResponseHead = nsnull;
4895 :
4896 : // set sticky connection flag and disable pipelining.
4897 23 : mCaps |= NS_HTTP_STICKY_CONNECTION;
4898 23 : mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
4899 :
4900 : // and create a new one...
4901 23 : rv = SetupTransaction();
4902 23 : if (NS_FAILED(rv)) return rv;
4903 :
4904 : // transfer ownership of connection to transaction
4905 23 : if (conn)
4906 0 : mTransaction->SetConnection(conn);
4907 :
4908 : // rewind the upload stream
4909 23 : if (mUploadStream) {
4910 0 : nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
4911 0 : if (seekable)
4912 0 : seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
4913 : }
4914 :
4915 23 : rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
4916 23 : if (NS_FAILED(rv)) return rv;
4917 :
4918 23 : rv = mTransactionPump->AsyncRead(this, nsnull);
4919 23 : if (NS_FAILED(rv)) return rv;
4920 :
4921 23 : PRUint32 suspendCount = mSuspendCount;
4922 46 : while (suspendCount--)
4923 0 : mTransactionPump->Suspend();
4924 :
4925 23 : return NS_OK;
4926 : }
4927 :
4928 : //-----------------------------------------------------------------------------
4929 : // nsHttpChannel::nsIApplicationCacheChannel
4930 : //-----------------------------------------------------------------------------
4931 : NS_IMETHODIMP
4932 0 : nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
4933 : {
4934 0 : NS_IF_ADDREF(*out = mApplicationCache);
4935 0 : return NS_OK;
4936 : }
4937 :
4938 : NS_IMETHODIMP
4939 177 : nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
4940 : {
4941 177 : NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
4942 :
4943 177 : mApplicationCache = appCache;
4944 177 : return NS_OK;
4945 : }
4946 :
4947 : NS_IMETHODIMP
4948 0 : nsHttpChannel::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
4949 : {
4950 0 : *aLoadedFromApplicationCache = mLoadedFromApplicationCache;
4951 0 : return NS_OK;
4952 : }
4953 :
4954 : NS_IMETHODIMP
4955 0 : nsHttpChannel::GetInheritApplicationCache(bool *aInherit)
4956 : {
4957 0 : *aInherit = mInheritApplicationCache;
4958 0 : return NS_OK;
4959 : }
4960 :
4961 : NS_IMETHODIMP
4962 153 : nsHttpChannel::SetInheritApplicationCache(bool aInherit)
4963 : {
4964 153 : NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
4965 :
4966 153 : mInheritApplicationCache = aInherit;
4967 153 : return NS_OK;
4968 : }
4969 :
4970 : NS_IMETHODIMP
4971 0 : nsHttpChannel::GetChooseApplicationCache(bool *aChoose)
4972 : {
4973 0 : *aChoose = mChooseApplicationCache;
4974 0 : return NS_OK;
4975 : }
4976 :
4977 : NS_IMETHODIMP
4978 8 : nsHttpChannel::SetChooseApplicationCache(bool aChoose)
4979 : {
4980 8 : NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
4981 :
4982 8 : mChooseApplicationCache = aChoose;
4983 8 : return NS_OK;
4984 : }
4985 :
4986 : NS_IMETHODIMP
4987 0 : nsHttpChannel::MarkOfflineCacheEntryAsForeign()
4988 : {
4989 0 : if (!mApplicationCache)
4990 0 : return NS_ERROR_NOT_AVAILABLE;
4991 :
4992 : nsresult rv;
4993 :
4994 0 : nsCAutoString cacheKey;
4995 0 : rv = GenerateCacheKey(mPostID, cacheKey);
4996 0 : NS_ENSURE_SUCCESS(rv, rv);
4997 :
4998 0 : rv = mApplicationCache->MarkEntry(cacheKey,
4999 0 : nsIApplicationCache::ITEM_FOREIGN);
5000 0 : NS_ENSURE_SUCCESS(rv, rv);
5001 :
5002 0 : return NS_OK;
5003 : }
5004 :
5005 : //-----------------------------------------------------------------------------
5006 : // nsHttpChannel::nsIAsyncVerifyRedirectCallback
5007 : //-----------------------------------------------------------------------------
5008 :
5009 : nsresult
5010 155 : nsHttpChannel::WaitForRedirectCallback()
5011 : {
5012 : nsresult rv;
5013 155 : LOG(("nsHttpChannel::WaitForRedirectCallback [this=%p]\n", this));
5014 :
5015 155 : if (mTransactionPump) {
5016 133 : rv = mTransactionPump->Suspend();
5017 133 : NS_ENSURE_SUCCESS(rv, rv);
5018 : }
5019 155 : if (mCachePump) {
5020 0 : rv = mCachePump->Suspend();
5021 0 : if (NS_FAILED(rv) && mTransactionPump) {
5022 : #ifdef DEBUG
5023 : nsresult resume =
5024 : #endif
5025 0 : mTransactionPump->Resume();
5026 0 : NS_ASSERTION(NS_SUCCEEDED(resume),
5027 : "Failed to resume transaction pump");
5028 : }
5029 0 : NS_ENSURE_SUCCESS(rv, rv);
5030 : }
5031 :
5032 155 : mWaitingForRedirectCallback = true;
5033 155 : return NS_OK;
5034 : }
5035 :
5036 : NS_IMETHODIMP
5037 155 : nsHttpChannel::OnRedirectVerifyCallback(nsresult result)
5038 : {
5039 155 : LOG(("nsHttpChannel::OnRedirectVerifyCallback [this=%p] "
5040 : "result=%x stack=%d mWaitingForRedirectCallback=%u\n",
5041 : this, result, mRedirectFuncStack.Length(), mWaitingForRedirectCallback));
5042 155 : NS_ASSERTION(mWaitingForRedirectCallback,
5043 : "Someone forgot to call WaitForRedirectCallback() ?!");
5044 155 : mWaitingForRedirectCallback = false;
5045 :
5046 155 : if (mCanceled && NS_SUCCEEDED(result))
5047 0 : result = NS_BINDING_ABORTED;
5048 :
5049 619 : for (PRUint32 i = mRedirectFuncStack.Length(); i > 0;) {
5050 311 : --i;
5051 : // Pop the last function pushed to the stack
5052 311 : nsContinueRedirectionFunc func = mRedirectFuncStack[i];
5053 311 : mRedirectFuncStack.RemoveElementAt(mRedirectFuncStack.Length() - 1);
5054 :
5055 : // Call it with the result we got from the callback or the deeper
5056 : // function call.
5057 311 : result = (this->*func)(result);
5058 :
5059 : // If a new function has been pushed to the stack and placed us in the
5060 : // waiting state, we need to break the chain and wait for the callback
5061 : // again.
5062 311 : if (mWaitingForRedirectCallback)
5063 2 : break;
5064 : }
5065 :
5066 155 : if (NS_FAILED(result) && !mCanceled) {
5067 : // First, cancel this channel if we are in failure state to set mStatus
5068 : // and let it be propagated to pumps.
5069 5 : Cancel(result);
5070 : }
5071 :
5072 155 : if (!mWaitingForRedirectCallback) {
5073 : // We are not waiting for the callback. At this moment we must release
5074 : // reference to the redirect target channel, otherwise we may leak.
5075 153 : mRedirectChannel = nsnull;
5076 : }
5077 :
5078 : // We always resume the pumps here. If all functions on stack have been
5079 : // called we need OnStopRequest to be triggered, and if we broke out of the
5080 : // loop above (and are thus waiting for a new callback) the suspension
5081 : // count must be balanced in the pumps.
5082 155 : if (mTransactionPump)
5083 133 : mTransactionPump->Resume();
5084 155 : if (mCachePump)
5085 0 : mCachePump->Resume();
5086 :
5087 155 : return result;
5088 : }
5089 :
5090 : void
5091 1160 : nsHttpChannel::PushRedirectAsyncFunc(nsContinueRedirectionFunc func)
5092 : {
5093 1160 : mRedirectFuncStack.AppendElement(func);
5094 1160 : }
5095 :
5096 : void
5097 849 : nsHttpChannel::PopRedirectAsyncFunc(nsContinueRedirectionFunc func)
5098 : {
5099 849 : NS_ASSERTION(func == mRedirectFuncStack[mRedirectFuncStack.Length() - 1],
5100 : "Trying to pop wrong method from redirect async stack!");
5101 :
5102 849 : mRedirectFuncStack.TruncateLength(mRedirectFuncStack.Length() - 1);
5103 849 : }
5104 :
5105 :
5106 : //-----------------------------------------------------------------------------
5107 : // nsHttpChannel internal functions
5108 : //-----------------------------------------------------------------------------
5109 :
5110 : void
5111 2713 : nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
5112 : {
5113 : // See RFC 2616 section 5.1.1. These are considered valid
5114 : // methods which DO NOT invalidate cache-entries for the
5115 : // referred resource. POST, PUT and DELETE as well as any
5116 : // other method not listed here will potentially invalidate
5117 : // any cached copy of the resource
5118 7483 : if (mRequestHead.Method() == nsHttp::Options ||
5119 2713 : mRequestHead.Method() == nsHttp::Get ||
5120 695 : mRequestHead.Method() == nsHttp::Head ||
5121 681 : mRequestHead.Method() == nsHttp::Trace ||
5122 681 : mRequestHead.Method() == nsHttp::Connect)
5123 2032 : return;
5124 :
5125 :
5126 : // Invalidate the request-uri.
5127 : // Pass 0 in first param to get the cache-key for a GET-request.
5128 1362 : nsCAutoString tmpCacheKey;
5129 681 : GenerateCacheKey(0, tmpCacheKey);
5130 681 : LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%p uri=%s]\n",
5131 : this, tmpCacheKey.get()));
5132 681 : DoInvalidateCacheEntry(tmpCacheKey);
5133 :
5134 : // Invalidate Location-header if set
5135 681 : const char *location = mResponseHead->PeekHeader(nsHttp::Location);
5136 681 : if (location) {
5137 36 : LOG((" Location-header=%s\n", location));
5138 36 : InvalidateCacheEntryForLocation(location);
5139 : }
5140 :
5141 : // Invalidate Content-Location-header if set
5142 681 : location = mResponseHead->PeekHeader(nsHttp::Content_Location);
5143 681 : if (location) {
5144 2 : LOG((" Content-Location-header=%s\n", location));
5145 2 : InvalidateCacheEntryForLocation(location);
5146 : }
5147 : }
5148 :
5149 : void
5150 38 : nsHttpChannel::InvalidateCacheEntryForLocation(const char *location)
5151 : {
5152 76 : nsCAutoString tmpCacheKey, tmpSpec;
5153 76 : nsCOMPtr<nsIURI> resultingURI;
5154 38 : nsresult rv = CreateNewURI(location, getter_AddRefs(resultingURI));
5155 38 : if (NS_SUCCEEDED(rv) && HostPartIsTheSame(resultingURI)) {
5156 38 : if (NS_SUCCEEDED(resultingURI->GetAsciiSpec(tmpSpec))) {
5157 38 : location = tmpSpec.get(); //reusing |location|
5158 :
5159 : // key for a GET-request to |location| with current load-flags
5160 38 : AssembleCacheKey(location, 0, tmpCacheKey);
5161 38 : DoInvalidateCacheEntry(tmpCacheKey);
5162 : } else
5163 0 : NS_WARNING((" failed getting ascii-spec\n"));
5164 : } else {
5165 0 : LOG((" hosts not matching\n"));
5166 : }
5167 38 : }
5168 :
5169 : void
5170 719 : nsHttpChannel::DoInvalidateCacheEntry(nsACString &key)
5171 : {
5172 : // NOTE:
5173 : // Following comments 24,32 and 33 in bug #327765, we only care about
5174 : // the cache in the protocol-handler, not the application cache.
5175 : // The logic below deviates from the original logic in OpenCacheEntry on
5176 : // one point by using only READ_ONLY access-policy. I think this is safe.
5177 :
5178 : // First, find session holding the cache-entry - use current storage-policy
5179 1438 : nsCOMPtr<nsICacheSession> session;
5180 719 : nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
5181 :
5182 : nsresult rv = gHttpHandler->GetCacheSession(storagePolicy,
5183 719 : getter_AddRefs(session));
5184 :
5185 719 : if (NS_FAILED(rv))
5186 : return;
5187 :
5188 : // Now, find the actual cache-entry
5189 1438 : nsCOMPtr<nsICacheEntryDescriptor> tmpCacheEntry;
5190 719 : rv = session->OpenCacheEntry(key, nsICache::ACCESS_READ,
5191 : false,
5192 719 : getter_AddRefs(tmpCacheEntry));
5193 :
5194 : // If entry was found, set its expiration-time = 0
5195 719 : if(NS_SUCCEEDED(rv)) {
5196 63 : tmpCacheEntry->SetExpirationTime(0);
5197 63 : LOG((" cache-entry invalidated [key=%s]\n", key.Data()));
5198 : } else {
5199 656 : LOG((" cache-entry not found [key=%s]\n", key.Data()));
5200 : }
5201 : }
5202 :
5203 : nsCacheStoragePolicy
5204 2614 : nsHttpChannel::DetermineStoragePolicy()
5205 : {
5206 2614 : nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
5207 2614 : if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
5208 0 : policy = nsICache::STORE_IN_MEMORY;
5209 :
5210 2614 : return policy;
5211 : }
5212 :
5213 : nsresult
5214 4798 : nsHttpChannel::DetermineCacheAccess(nsCacheAccessMode *_retval)
5215 : {
5216 4798 : bool offline = gIOService->IsOffline();
5217 :
5218 4798 : if (offline || (mLoadFlags & INHIBIT_CACHING)) {
5219 : // If we have been asked to bypass the cache and not write to the
5220 : // cache, then don't use the cache at all. Unless we're actually
5221 : // offline, which takes precedence over BYPASS_LOCAL_CACHE.
5222 1013 : if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
5223 1004 : return NS_ERROR_NOT_AVAILABLE;
5224 9 : *_retval = nsICache::ACCESS_READ;
5225 : }
5226 3785 : else if (BYPASS_LOCAL_CACHE(mLoadFlags))
5227 866 : *_retval = nsICache::ACCESS_WRITE; // replace cache entry
5228 : else
5229 2919 : *_retval = nsICache::ACCESS_READ_WRITE; // normal browsing
5230 :
5231 3794 : return NS_OK;
5232 : }
5233 :
5234 : void
5235 220 : nsHttpChannel::AsyncOnExamineCachedResponse()
5236 : {
5237 220 : gHttpHandler->OnExamineCachedResponse(this);
5238 220 : }
|