Bug 458256. Use LoadLibraryW instead of LoadLibrary (patch by DougT). r+sr=vlad
[wine-gecko.git] / netwerk / protocol / http / src / nsHttpChannel.cpp
blobc0b2b7645018795f22ff10e8b0cd0a6827d69331
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
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/
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.
16 * The Original Code is Mozilla.
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.
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>
32 * Alternatively, the contents of this file may be used under the terms of
33 * either the GNU General Public License Version 2 or later (the "GPL"), or
34 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
35 * in which case the provisions of the GPL or the LGPL are applicable instead
36 * of those above. If you wish to allow use of your version of this file only
37 * under the terms of either the GPL or the LGPL, and not to allow others to
38 * use your version of this file under the terms of the MPL, indicate your
39 * decision by deleting the provisions above and replace them with the notice
40 * and other provisions required by the GPL or the LGPL. If you do not delete
41 * the provisions above, a recipient may use your version of this file under
42 * the terms of any one of the MPL, the GPL or the LGPL.
44 * ***** END LICENSE BLOCK ***** */
46 #include "nsHttpChannel.h"
47 #include "nsHttpTransaction.h"
48 #include "nsHttpConnection.h"
49 #include "nsHttpHandler.h"
50 #include "nsHttpAuthCache.h"
51 #include "nsHttpResponseHead.h"
52 #include "nsHttp.h"
53 #include "nsIHttpAuthenticator.h"
54 #include "nsIApplicationCacheService.h"
55 #include "nsIAuthInformation.h"
56 #include "nsIAuthPrompt2.h"
57 #include "nsIAuthPromptProvider.h"
58 #include "nsIStringBundle.h"
59 #include "nsXPCOM.h"
60 #include "nsISupportsPrimitives.h"
61 #include "nsIURL.h"
62 #include "nsIIDNService.h"
63 #include "nsIStreamListenerTee.h"
64 #include "nsISeekableStream.h"
65 #include "nsMimeTypes.h"
66 #include "nsNetUtil.h"
67 #include "nsString.h"
68 #include "nsPrintfCString.h"
69 #include "nsReadableUtils.h"
70 #include "nsUnicharUtils.h"
71 #include "nsAutoPtr.h"
72 #include "plstr.h"
73 #include "prprf.h"
74 #include "nsEscape.h"
75 #include "nsICookieService.h"
76 #include "nsIResumableChannel.h"
77 #include "nsInt64.h"
78 #include "nsIVariant.h"
79 #include "nsChannelProperties.h"
80 #include "nsStreamUtils.h"
81 #include "nsIOService.h"
82 #include "nsAuthInformationHolder.h"
83 #include "nsICacheService.h"
85 // True if the local cache should be bypassed when processing a request.
86 #define BYPASS_LOCAL_CACHE(loadFlags) \
87 (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
88 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
90 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
92 //-----------------------------------------------------------------------------
93 // nsHttpChannel <public>
94 //-----------------------------------------------------------------------------
96 nsHttpChannel::nsHttpChannel()
97 : mResponseHead(nsnull)
98 , mTransaction(nsnull)
99 , mConnectionInfo(nsnull)
100 , mLoadFlags(LOAD_NORMAL)
101 , mStatus(NS_OK)
102 , mLogicalOffset(0)
103 , mCaps(0)
104 , mPriority(PRIORITY_NORMAL)
105 , mCachedResponseHead(nsnull)
106 , mCacheAccess(0)
107 , mPostID(0)
108 , mRequestTime(0)
109 , mProxyAuthContinuationState(nsnull)
110 , mAuthContinuationState(nsnull)
111 , mStartPos(LL_MAXUINT)
112 , mPendingAsyncCallOnResume(nsnull)
113 , mSuspendCount(0)
114 , mRedirectionLimit(gHttpHandler->RedirectionLimit())
115 , mIsPending(PR_FALSE)
116 , mWasOpened(PR_FALSE)
117 , mApplyConversion(PR_TRUE)
118 , mAllowPipelining(PR_TRUE)
119 , mCachedContentIsValid(PR_FALSE)
120 , mCachedContentIsPartial(PR_FALSE)
121 , mResponseHeadersModified(PR_FALSE)
122 , mCanceled(PR_FALSE)
123 , mTransactionReplaced(PR_FALSE)
124 , mUploadStreamHasHeaders(PR_FALSE)
125 , mAuthRetryPending(PR_FALSE)
126 , mSuppressDefensiveAuth(PR_FALSE)
127 , mResuming(PR_FALSE)
128 , mInitedCacheEntry(PR_FALSE)
129 , mCacheForOfflineUse(PR_FALSE)
130 , mCachingOpportunistically(PR_FALSE)
131 , mFallbackChannel(PR_FALSE)
132 , mTracingEnabled(PR_TRUE)
134 LOG(("Creating nsHttpChannel @%x\n", this));
136 // grab a reference to the handler to ensure that it doesn't go away.
137 nsHttpHandler *handler = gHttpHandler;
138 NS_ADDREF(handler);
141 nsHttpChannel::~nsHttpChannel()
143 LOG(("Destroying nsHttpChannel @%x\n", this));
145 NS_IF_RELEASE(mConnectionInfo);
146 NS_IF_RELEASE(mTransaction);
148 NS_IF_RELEASE(mProxyAuthContinuationState);
149 NS_IF_RELEASE(mAuthContinuationState);
151 delete mResponseHead;
152 delete mCachedResponseHead;
154 // release our reference to the handler
155 nsHttpHandler *handler = gHttpHandler;
156 NS_RELEASE(handler);
159 nsresult
160 nsHttpChannel::Init(nsIURI *uri,
161 PRUint8 caps,
162 nsProxyInfo *proxyInfo)
164 LOG(("nsHttpChannel::Init [this=%x]\n", this));
166 NS_PRECONDITION(uri, "null uri");
168 nsresult rv = nsHashPropertyBag::Init();
169 if (NS_FAILED(rv))
170 return rv;
172 mURI = uri;
173 mOriginalURI = uri;
174 mDocumentURI = nsnull;
175 mCaps = caps;
178 // Construct connection info object
180 nsCAutoString host;
181 PRInt32 port = -1;
182 PRBool usingSSL = PR_FALSE;
184 rv = mURI->SchemeIs("https", &usingSSL);
185 if (NS_FAILED(rv)) return rv;
187 rv = mURI->GetAsciiHost(host);
188 if (NS_FAILED(rv)) return rv;
190 // reject the URL if it doesn't specify a host
191 if (host.IsEmpty())
192 return NS_ERROR_MALFORMED_URI;
194 rv = mURI->GetPort(&port);
195 if (NS_FAILED(rv)) return rv;
197 LOG(("host=%s port=%d\n", host.get(), port));
199 rv = mURI->GetAsciiSpec(mSpec);
200 if (NS_FAILED(rv)) return rv;
202 LOG(("uri=%s\n", mSpec.get()));
204 mConnectionInfo = new nsHttpConnectionInfo(host, port,
205 proxyInfo, usingSSL);
206 if (!mConnectionInfo)
207 return NS_ERROR_OUT_OF_MEMORY;
208 NS_ADDREF(mConnectionInfo);
210 // Set default request method
211 mRequestHead.SetMethod(nsHttp::Get);
214 // Set request headers
216 nsCAutoString hostLine;
217 if (strchr(host.get(), ':')) {
218 // host is an IPv6 address literal and must be encapsulated in []'s
219 hostLine.Assign('[');
220 hostLine.Append(host);
221 hostLine.Append(']');
223 else
224 hostLine.Assign(host);
225 if (port != -1) {
226 hostLine.Append(':');
227 hostLine.AppendInt(port);
230 rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
231 if (NS_FAILED(rv)) return rv;
233 rv = gHttpHandler->
234 AddStandardRequestHeaders(&mRequestHead.Headers(), caps,
235 !mConnectionInfo->UsingSSL() &&
236 mConnectionInfo->UsingHttpProxy());
238 return rv;
241 //-----------------------------------------------------------------------------
242 // nsHttpChannel <private>
243 //-----------------------------------------------------------------------------
245 nsresult
246 nsHttpChannel::AsyncCall(nsAsyncCallback funcPtr)
248 nsCOMPtr<nsIRunnable> event =
249 new nsRunnableMethod<nsHttpChannel>(this, funcPtr);
250 return NS_DispatchToCurrentThread(event);
253 PRBool
254 nsHttpChannel::RequestIsConditional()
256 // Is our consumer issuing a conditional request?
257 return mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
258 mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
259 mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
260 mRequestHead.PeekHeader(nsHttp::If_Match) ||
261 mRequestHead.PeekHeader(nsHttp::If_Range);
264 nsresult
265 nsHttpChannel::Connect(PRBool firstTime)
267 nsresult rv;
269 LOG(("nsHttpChannel::Connect [this=%x]\n", this));
271 // ensure that we are using a valid hostname
272 if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
273 return NS_ERROR_UNKNOWN_HOST;
275 // true when called from AsyncOpen
276 if (firstTime) {
277 PRBool delayed = PR_FALSE;
279 // are we offline?
280 PRBool offline = gIOService->IsOffline();
281 if (offline)
282 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
283 else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
284 return ResolveProxy(); // Lazily resolve proxy info
286 // Don't allow resuming when cache must be used
287 if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
288 LOG(("Resuming from cache is not supported yet"));
289 return NS_ERROR_DOCUMENT_NOT_CACHED;
292 // open a cache entry for this channel...
293 rv = OpenCacheEntry(offline, &delayed);
295 if (NS_FAILED(rv)) {
296 LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
297 // if this channel is only allowed to pull from the cache, then
298 // we must fail if we were unable to open a cache entry.
299 if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
300 // If we have a fallback URI (and we're not already
301 // falling back), process the fallback asynchronously.
302 if (!mFallbackChannel && !mFallbackKey.IsEmpty()) {
303 return AsyncCall(&nsHttpChannel::HandleAsyncFallback);
305 return NS_ERROR_DOCUMENT_NOT_CACHED;
307 // otherwise, let's just proceed without using the cache.
310 // if cacheForOfflineUse has been set, open up an offline cache
311 // entry to update
312 if (mCacheForOfflineUse) {
313 rv = OpenOfflineCacheEntryForWriting();
314 if (NS_FAILED(rv)) return rv;
317 if (NS_SUCCEEDED(rv) && delayed)
318 return NS_OK;
321 // we may or may not have a cache entry at this point
322 if (mCacheEntry) {
323 // inspect the cache entry to determine whether or not we need to go
324 // out to net to validate it. this call sets mCachedContentIsValid
325 // and may set request headers as required for cache validation.
326 rv = CheckCache();
327 if (NS_FAILED(rv))
328 NS_WARNING("cache check failed");
330 // read straight from the cache if possible...
331 if (mCachedContentIsValid) {
332 return ReadFromCache();
334 else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
335 // the cache contains the requested resource, but it must be
336 // validated before we can reuse it. since we are not allowed
337 // to hit the net, there's nothing more to do. the document
338 // is effectively not in the cache.
339 return NS_ERROR_DOCUMENT_NOT_CACHED;
343 // check to see if authorization headers should be included
344 AddAuthorizationHeaders();
346 if (mLoadFlags & LOAD_NO_NETWORK_IO) {
347 return NS_ERROR_DOCUMENT_NOT_CACHED;
350 // hit the net...
351 rv = SetupTransaction();
352 if (NS_FAILED(rv)) return rv;
354 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
355 if (NS_FAILED(rv)) return rv;
357 return mTransactionPump->AsyncRead(this, nsnull);
360 // called when Connect fails
361 nsresult
362 nsHttpChannel::AsyncAbort(nsresult status)
364 LOG(("nsHttpChannel::AsyncAbort [this=%x status=%x]\n", this, status));
366 mStatus = status;
367 mIsPending = PR_FALSE;
369 nsresult rv = AsyncCall(&nsHttpChannel::HandleAsyncNotifyListener);
370 // And if that fails? Callers ignore our return value anyway....
372 // finally remove ourselves from the load group.
373 if (mLoadGroup)
374 mLoadGroup->RemoveRequest(this, nsnull, status);
376 return rv;
379 void
380 nsHttpChannel::HandleAsyncNotifyListener()
382 NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
384 if (mSuspendCount) {
385 LOG(("Waiting until resume to do async notification [this=%p]\n",
386 this));
387 mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncNotifyListener;
388 return;
391 DoNotifyListener();
394 void
395 nsHttpChannel::DoNotifyListener()
397 if (mListener) {
398 mListener->OnStartRequest(this, mListenerContext);
399 mListener->OnStopRequest(this, mListenerContext, mStatus);
400 mListener = 0;
401 mListenerContext = 0;
403 // We have to make sure to drop the reference to the callbacks too
404 mCallbacks = nsnull;
405 mProgressSink = nsnull;
408 void
409 nsHttpChannel::HandleAsyncRedirect()
411 NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
413 if (mSuspendCount) {
414 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
415 mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncRedirect;
416 return;
419 nsresult rv = NS_OK;
421 LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
423 // since this event is handled asynchronously, it is possible that this
424 // channel could have been canceled, in which case there would be no point
425 // in processing the redirect.
426 if (NS_SUCCEEDED(mStatus)) {
427 rv = ProcessRedirection(mResponseHead->Status());
428 if (NS_FAILED(rv)) {
429 // If ProcessRedirection fails, then we have to send out the
430 // OnStart/OnStop notifications.
431 LOG(("ProcessRedirection failed [rv=%x]\n", rv));
432 mStatus = rv;
433 DoNotifyListener();
437 // close the cache entry. Blow it away if we couldn't process the redirect
438 // for some reason (the cache entry might be corrupt).
439 if (mCacheEntry) {
440 if (NS_FAILED(rv))
441 mCacheEntry->Doom();
442 CloseCacheEntry(PR_FALSE);
445 mIsPending = PR_FALSE;
447 if (mLoadGroup)
448 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
451 void
452 nsHttpChannel::HandleAsyncNotModified()
454 NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
456 if (mSuspendCount) {
457 LOG(("Waiting until resume to do async not-modified [this=%p]\n",
458 this));
459 mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncNotModified;
460 return;
463 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
465 DoNotifyListener();
467 CloseCacheEntry(PR_TRUE);
469 mIsPending = PR_FALSE;
471 if (mLoadGroup)
472 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
475 void
476 nsHttpChannel::HandleAsyncFallback()
478 NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
480 if (mSuspendCount) {
481 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
482 mPendingAsyncCallOnResume = &nsHttpChannel::HandleAsyncFallback;
483 return;
486 nsresult rv = NS_OK;
488 LOG(("nsHttpChannel::HandleAsyncFallback [this=%p]\n", this));
490 // since this event is handled asynchronously, it is possible that this
491 // channel could have been canceled, in which case there would be no point
492 // in processing the fallback.
493 if (!mCanceled) {
494 PRBool fallingBack;
495 rv = ProcessFallback(&fallingBack);
496 if (NS_FAILED(rv) || !fallingBack) {
497 // If ProcessFallback fails, then we have to send out the
498 // OnStart/OnStop notifications.
499 LOG(("ProcessFallback failed [rv=%x, %d]\n", rv, fallingBack));
500 mStatus = NS_FAILED(rv) ? rv : NS_ERROR_DOCUMENT_NOT_CACHED;
501 DoNotifyListener();
505 mIsPending = PR_FALSE;
507 if (mLoadGroup)
508 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
511 nsresult
512 nsHttpChannel::SetupTransaction()
514 LOG(("nsHttpChannel::SetupTransaction [this=%x]\n", this));
516 NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
518 nsresult rv;
520 if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
522 // disable pipelining if:
523 // (1) pipelining has been explicitly disabled
524 // (2) request corresponds to a top-level document load (link click)
525 // (3) request method is non-idempotent
527 // XXX does the toplevel document check really belong here? or, should
528 // we push it out entirely to necko consumers?
530 if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) ||
531 !(mRequestHead.Method() == nsHttp::Get ||
532 mRequestHead.Method() == nsHttp::Head ||
533 mRequestHead.Method() == nsHttp::Propfind ||
534 mRequestHead.Method() == nsHttp::Proppatch)) {
535 LOG((" pipelining disallowed\n"));
536 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
540 // use the URI path if not proxying (transparent proxying such as SSL proxy
541 // does not count here). also, figure out what version we should be speaking.
542 nsCAutoString buf, path;
543 nsCString* requestURI;
544 if (mConnectionInfo->UsingSSL() || !mConnectionInfo->UsingHttpProxy()) {
545 rv = mURI->GetPath(path);
546 if (NS_FAILED(rv)) return rv;
547 // path may contain UTF-8 characters, so ensure that they're escaped.
548 if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
549 requestURI = &buf;
550 else
551 requestURI = &path;
552 mRequestHead.SetVersion(gHttpHandler->HttpVersion());
554 else {
555 rv = mURI->GetUserPass(buf);
556 if (NS_FAILED(rv)) return rv;
557 if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
558 strncmp(mSpec.get(), "https:", 6) == 0)) {
559 nsCOMPtr<nsIURI> tempURI;
560 rv = mURI->Clone(getter_AddRefs(tempURI));
561 if (NS_FAILED(rv)) return rv;
562 rv = tempURI->SetUserPass(EmptyCString());
563 if (NS_FAILED(rv)) return rv;
564 rv = tempURI->GetAsciiSpec(path);
565 if (NS_FAILED(rv)) return rv;
566 requestURI = &path;
568 else
569 requestURI = &mSpec;
570 mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
573 // trim off the #ref portion if any...
574 PRInt32 ref = requestURI->FindChar('#');
575 if (ref != kNotFound)
576 requestURI->SetLength(ref);
578 mRequestHead.SetRequestURI(*requestURI);
580 // set the request time for cache expiration calculations
581 mRequestTime = NowInSeconds();
583 // if doing a reload, force end-to-end
584 if (mLoadFlags & LOAD_BYPASS_CACHE) {
585 // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
586 // no proxy is configured since we might be talking with a transparent
587 // proxy, i.e. one that operates at the network level. See bug #14772.
588 mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
589 // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
590 // no-cache'
591 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
592 mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
594 else if ((mLoadFlags & VALIDATE_ALWAYS) && (mCacheAccess & nsICache::ACCESS_READ)) {
595 // We need to send 'Cache-Control: max-age=0' to force each cache along
596 // the path to the origin server to revalidate its own entry, if any,
597 // with the next cache or server. See bug #84847.
599 // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
600 if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
601 mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("max-age=0"), PR_TRUE);
602 else
603 mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
606 if (mResuming) {
607 char byteRange[32];
608 PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
609 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
611 if (!mEntityID.IsEmpty()) {
612 // Also, we want an error if this resource changed in the meantime
613 // Format of the entity id is: escaped_etag/size/lastmod
614 nsCString::const_iterator start, end, slash;
615 mEntityID.BeginReading(start);
616 mEntityID.EndReading(end);
617 mEntityID.BeginReading(slash);
619 if (FindCharInReadable('/', slash, end)) {
620 nsCAutoString ifMatch;
621 mRequestHead.SetHeader(nsHttp::If_Match,
622 NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
624 ++slash; // Incrementing, so that searching for '/' won't find
625 // the same slash again
628 if (FindCharInReadable('/', slash, end)) {
629 mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
630 Substring(++slash, end));
635 // create wrapper for this channel's notification callbacks
636 nsCOMPtr<nsIInterfaceRequestor> callbacks;
637 NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
638 getter_AddRefs(callbacks));
639 if (!callbacks)
640 return NS_ERROR_OUT_OF_MEMORY;
642 // create the transaction object
643 mTransaction = new nsHttpTransaction();
644 if (!mTransaction)
645 return NS_ERROR_OUT_OF_MEMORY;
646 NS_ADDREF(mTransaction);
648 nsCOMPtr<nsIAsyncInputStream> responseStream;
649 rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
650 mUploadStream, mUploadStreamHasHeaders,
651 NS_GetCurrentThread(), callbacks, this,
652 getter_AddRefs(responseStream));
653 if (NS_FAILED(rv)) {
654 NS_RELEASE(mTransaction);
655 return rv;
658 rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
659 responseStream);
660 return rv;
663 void
664 nsHttpChannel::AddCookiesToRequest()
666 if (mLoadFlags & LOAD_ANONYMOUS) {
667 return;
670 nsXPIDLCString cookie;
672 nsICookieService *cs = gHttpHandler->GetCookieService();
673 if (cs)
674 cs->GetCookieStringFromHttp(mURI,
675 mDocumentURI ? mDocumentURI : mOriginalURI,
676 this,
677 getter_Copies(cookie));
678 if (cookie.IsEmpty())
679 cookie = mUserSetCookieHeader;
680 else if (!mUserSetCookieHeader.IsEmpty())
681 cookie.Append(NS_LITERAL_CSTRING("; ") + mUserSetCookieHeader);
683 // overwrite any existing cookie headers. be sure to clear any
684 // existing cookies if we have no cookies to set or if the cookie
685 // service is unavailable.
686 mRequestHead.SetHeader(nsHttp::Cookie, cookie, PR_FALSE);
689 nsresult
690 nsHttpChannel::ApplyContentConversions()
692 if (!mResponseHead)
693 return NS_OK;
695 LOG(("nsHttpChannel::ApplyContentConversions [this=%x]\n", this));
697 if (!mApplyConversion) {
698 LOG(("not applying conversion per mApplyConversion\n"));
699 return NS_OK;
702 const char *val = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
703 if (gHttpHandler->IsAcceptableEncoding(val)) {
704 nsCOMPtr<nsIStreamConverterService> serv;
705 nsresult rv = gHttpHandler->
706 GetStreamConverterService(getter_AddRefs(serv));
707 // we won't fail to load the page just because we couldn't load the
708 // stream converter service.. carry on..
709 if (NS_SUCCEEDED(rv)) {
710 nsCOMPtr<nsIStreamListener> converter;
711 nsCAutoString from(val);
712 ToLowerCase(from);
713 rv = serv->AsyncConvertData(from.get(),
714 "uncompressed",
715 mListener,
716 mListenerContext,
717 getter_AddRefs(converter));
718 if (NS_SUCCEEDED(rv)) {
719 LOG(("converter installed from \'%s\' to \'uncompressed\'\n", val));
720 mListener = converter;
723 } else if (val != nsnull) {
724 LOG(("Unknown content encoding '%s', ignoring\n", val));
727 return NS_OK;
730 // NOTE: This function duplicates code from nsBaseChannel. This will go away
731 // once HTTP uses nsBaseChannel (part of bug 312760)
732 static void
733 CallTypeSniffers(void *aClosure, const PRUint8 *aData, PRUint32 aCount)
735 nsIChannel *chan = static_cast<nsIChannel*>(aClosure);
737 const nsCOMArray<nsIContentSniffer>& sniffers =
738 gIOService->GetContentSniffers();
739 PRUint32 length = sniffers.Count();
740 for (PRUint32 i = 0; i < length; ++i) {
741 nsCAutoString newType;
742 nsresult rv =
743 sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType);
744 if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) {
745 chan->SetContentType(newType);
746 break;
751 nsresult
752 nsHttpChannel::CallOnStartRequest()
754 mTracingEnabled = PR_FALSE;
756 if (mResponseHead && mResponseHead->ContentType().IsEmpty()) {
757 if (!mContentTypeHint.IsEmpty())
758 mResponseHead->SetContentType(mContentTypeHint);
759 else {
760 // Uh-oh. We had better find out what type we are!
762 // XXX This does not work with content-encodings... but
763 // neither does applying the conversion from the URILoader
765 nsCOMPtr<nsIStreamConverterService> serv;
766 nsresult rv = gHttpHandler->
767 GetStreamConverterService(getter_AddRefs(serv));
768 // If we failed, we just fall through to the "normal" case
769 if (NS_SUCCEEDED(rv)) {
770 nsCOMPtr<nsIStreamListener> converter;
771 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
772 "*/*",
773 mListener,
774 mListenerContext,
775 getter_AddRefs(converter));
776 if (NS_SUCCEEDED(rv)) {
777 mListener = converter;
783 if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
784 mResponseHead->SetContentCharset(mContentCharsetHint);
786 if (mResponseHead)
787 SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
788 mResponseHead->ContentLength());
790 // Allow consumers to override our content type
791 if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) &&
792 gIOService->GetContentSniffers().Count() != 0) {
793 // NOTE: We can have both a txn pump and a cache pump when the cache
794 // content is partial. In that case, we need to read from the cache,
795 // because that's the one that has the initial contents. If that fails
796 // then give the transaction pump a shot.
798 nsIChannel* thisChannel = static_cast<nsIChannel*>(this);
800 PRBool typeSniffersCalled = PR_FALSE;
801 if (mCachePump) {
802 typeSniffersCalled =
803 NS_SUCCEEDED(mCachePump->PeekStream(CallTypeSniffers, thisChannel));
806 if (!typeSniffersCalled && mTransactionPump) {
807 mTransactionPump->PeekStream(CallTypeSniffers, thisChannel);
811 LOG((" calling mListener->OnStartRequest\n"));
812 nsresult rv = mListener->OnStartRequest(this, mListenerContext);
813 if (NS_FAILED(rv)) return rv;
815 // install stream converter if required
816 rv = ApplyContentConversions();
818 return rv;
821 nsresult
822 nsHttpChannel::ProcessResponse()
824 nsresult rv;
825 PRUint32 httpStatus = mResponseHead->Status();
827 LOG(("nsHttpChannel::ProcessResponse [this=%x httpStatus=%u]\n",
828 this, httpStatus));
830 // notify "http-on-examine-response" observers
831 gHttpHandler->OnExamineResponse(this);
833 // set cookies, if any exist; done after OnExamineResponse to allow those
834 // observers to modify the cookie response headers
835 SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
837 // handle unused username and password in url (see bug 232567)
838 if (httpStatus != 401 && httpStatus != 407) {
839 CheckForSuperfluousAuth();
840 if (mCanceled)
841 return CallOnStartRequest();
843 if (mAuthContinuationState) {
844 // reset the current continuation state because our last
845 // authentication attempt has been completed successfully
846 NS_RELEASE(mAuthContinuationState);
847 LOG((" continuation state has been reset"));
851 // handle different server response categories. Note that we handle
852 // caching or not caching of error pages in
853 // nsHttpResponseHead::MustValidate; if you change this switch, update that
854 // one
855 switch (httpStatus) {
856 case 200:
857 case 203:
858 // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
859 // So if a server does that and sends 200 instead of 206 that we
860 // expect, notify our caller.
861 // However, if we wanted to start from the beginning, let it go through
862 if (mResuming && mStartPos != 0) {
863 LOG(("Server ignored our Range header, cancelling [this=%p]\n", this));
864 Cancel(NS_ERROR_NOT_RESUMABLE);
865 rv = CallOnStartRequest();
866 break;
868 // these can normally be cached
869 rv = ProcessNormal();
870 MaybeInvalidateCacheEntryForSubsequentGet();
871 break;
872 case 206:
873 if (mCachedContentIsPartial) // an internal byte range request...
874 rv = ProcessPartialContent();
875 else
876 rv = ProcessNormal();
877 break;
878 case 300:
879 case 301:
880 case 302:
881 case 307:
882 case 303:
883 #if 0
884 case 305: // disabled as a security measure (see bug 187996).
885 #endif
886 // don't store the response body for redirects
887 MaybeInvalidateCacheEntryForSubsequentGet();
888 rv = ProcessRedirection(httpStatus);
889 if (NS_SUCCEEDED(rv)) {
890 InitCacheEntry();
891 CloseCacheEntry(PR_FALSE);
893 if (mCacheForOfflineUse) {
894 // Store response in the offline cache
895 InitOfflineCacheEntry();
896 CloseOfflineCacheEntry();
899 else {
900 LOG(("ProcessRedirection failed [rv=%x]\n", rv));
901 rv = ProcessNormal();
903 break;
904 case 304:
905 rv = ProcessNotModified();
906 if (NS_FAILED(rv)) {
907 LOG(("ProcessNotModified failed [rv=%x]\n", rv));
908 rv = ProcessNormal();
910 break;
911 case 401:
912 case 407:
913 rv = ProcessAuthentication(httpStatus);
914 if (NS_FAILED(rv)) {
915 LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
916 CheckForSuperfluousAuth();
917 rv = ProcessNormal();
919 break;
920 default:
921 rv = ProcessNormal();
922 MaybeInvalidateCacheEntryForSubsequentGet();
923 break;
926 return rv;
929 nsresult
930 nsHttpChannel::ProcessNormal()
932 nsresult rv;
934 LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
936 PRBool succeeded;
937 rv = GetRequestSucceeded(&succeeded);
938 if (NS_SUCCEEDED(rv) && !succeeded) {
939 PRBool fallingBack;
940 rv = ProcessFallback(&fallingBack);
941 if (NS_FAILED(rv)) {
942 DoNotifyListener();
943 return rv;
946 if (fallingBack) {
947 // Do not continue with normal processing, fallback is in
948 // progress now.
949 return NS_OK;
953 // if we're here, then any byte-range requests failed to result in a partial
954 // response. we must clear this flag to prevent BufferPartialContent from
955 // being called inside our OnDataAvailable (see bug 136678).
956 mCachedContentIsPartial = PR_FALSE;
958 ClearBogusContentEncodingIfNeeded();
960 // this must be called before firing OnStartRequest, since http clients,
961 // such as imagelib, expect our cache entry to already have the correct
962 // expiration time (bug 87710).
963 if (mCacheEntry) {
964 rv = InitCacheEntry();
965 if (NS_FAILED(rv))
966 CloseCacheEntry(PR_TRUE);
969 // Check that the server sent us what we were asking for
970 if (mResuming) {
971 // Create an entity id from the response
972 nsCAutoString id;
973 rv = GetEntityID(id);
974 if (NS_FAILED(rv)) {
975 // If creating an entity id is not possible -> error
976 Cancel(NS_ERROR_NOT_RESUMABLE);
978 else if (mResponseHead->Status() != 206 &&
979 mResponseHead->Status() != 200) {
980 // Probably 404 Not Found, 412 Precondition Failed or
981 // 416 Invalid Range -> error
982 LOG(("Unexpected response status while resuming, aborting [this=%p]\n",
983 this));
984 Cancel(NS_ERROR_ENTITY_CHANGED);
986 // If we were passed an entity id, verify it's equal to the server's
987 else if (!mEntityID.IsEmpty()) {
988 if (!mEntityID.Equals(id)) {
989 LOG(("Entity mismatch, expected '%s', got '%s', aborting [this=%p]",
990 mEntityID.get(), id.get(), this));
991 Cancel(NS_ERROR_ENTITY_CHANGED);
996 rv = CallOnStartRequest();
997 if (NS_FAILED(rv)) return rv;
999 // install cache listener if we still have a cache entry open
1000 if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) {
1001 rv = InstallCacheListener();
1002 if (NS_FAILED(rv)) return rv;
1004 // create offline cache entry if offline caching was requested
1005 if (mCacheForOfflineUse) {
1006 PRBool shouldCacheForOfflineUse;
1007 rv = ShouldUpdateOfflineCacheEntry(&shouldCacheForOfflineUse);
1008 if (NS_FAILED(rv)) return rv;
1010 if (shouldCacheForOfflineUse) {
1011 LOG(("writing to the offline cache"));
1012 rv = InitOfflineCacheEntry();
1013 if (NS_FAILED(rv)) return rv;
1015 if (mOfflineCacheEntry) {
1016 rv = InstallOfflineCacheListener();
1017 if (NS_FAILED(rv)) return rv;
1019 } else {
1020 LOG(("offline cache is up to date, not updating"));
1021 CloseOfflineCacheEntry();
1025 return rv;
1028 nsresult
1029 nsHttpChannel::PromptTempRedirect()
1031 nsresult rv;
1032 nsCOMPtr<nsIStringBundleService> bundleService =
1033 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
1034 if (NS_FAILED(rv)) return rv;
1036 nsCOMPtr<nsIStringBundle> stringBundle;
1037 rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
1038 if (NS_FAILED(rv)) return rv;
1040 nsXPIDLString messageString;
1041 rv = stringBundle->GetStringFromName(NS_LITERAL_STRING("RepostFormData").get(), getter_Copies(messageString));
1042 // GetStringFromName can return NS_OK and NULL messageString.
1043 if (NS_SUCCEEDED(rv) && messageString) {
1044 PRBool repost = PR_FALSE;
1046 nsCOMPtr<nsIPrompt> prompt;
1047 GetCallback(prompt);
1048 if (!prompt)
1049 return NS_ERROR_NO_INTERFACE;
1051 prompt->Confirm(nsnull, messageString, &repost);
1052 if (!repost)
1053 return NS_ERROR_FAILURE;
1056 return rv;
1059 nsresult
1060 nsHttpChannel::ProxyFailover()
1062 LOG(("nsHttpChannel::ProxyFailover [this=%x]\n", this));
1064 nsresult rv;
1066 nsCOMPtr<nsIProtocolProxyService> pps =
1067 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1068 if (NS_FAILED(rv))
1069 return rv;
1071 nsCOMPtr<nsIProxyInfo> pi;
1072 rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
1073 getter_AddRefs(pi));
1074 if (NS_FAILED(rv))
1075 return rv;
1077 // XXXbz so where does this codepath remove us from the loadgroup,
1078 // exactly?
1079 return DoReplaceWithProxy(pi);
1082 void
1083 nsHttpChannel::HandleAsyncReplaceWithProxy()
1085 NS_PRECONDITION(!mPendingAsyncCallOnResume, "How did that happen?");
1087 if (mSuspendCount) {
1088 LOG(("Waiting until resume to do async proxy replacement [this=%p]\n",
1089 this));
1090 mPendingAsyncCallOnResume =
1091 &nsHttpChannel::HandleAsyncReplaceWithProxy;
1092 return;
1095 nsresult status = mStatus;
1097 nsCOMPtr<nsIProxyInfo> pi;
1098 pi.swap(mTargetProxyInfo);
1099 if (!mCanceled) {
1100 status = DoReplaceWithProxy(pi);
1101 if (mLoadGroup && NS_SUCCEEDED(status)) {
1102 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
1106 if (NS_FAILED(status)) {
1107 AsyncAbort(status);
1111 nsresult
1112 nsHttpChannel::DoReplaceWithProxy(nsIProxyInfo* pi)
1114 nsresult rv;
1116 nsCOMPtr<nsIChannel> newChannel;
1117 rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel));
1118 if (NS_FAILED(rv))
1119 return rv;
1121 rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
1122 if (NS_FAILED(rv))
1123 return rv;
1125 // Inform consumers about this fake redirect
1126 PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
1127 rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
1128 if (NS_FAILED(rv))
1129 return rv;
1131 // open new channel
1132 rv = newChannel->AsyncOpen(mListener, mListenerContext);
1133 if (NS_FAILED(rv))
1134 return rv;
1136 mStatus = NS_BINDING_REDIRECTED;
1137 mListener = nsnull;
1138 mListenerContext = nsnull;
1139 return rv;
1142 nsresult
1143 nsHttpChannel::ResolveProxy()
1145 LOG(("nsHttpChannel::ResolveProxy [this=%x]\n", this));
1147 nsresult rv;
1149 nsCOMPtr<nsIProtocolProxyService> pps =
1150 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
1151 if (NS_FAILED(rv))
1152 return rv;
1154 return pps->AsyncResolve(mURI, 0, this, getter_AddRefs(mProxyRequest));
1157 PRBool
1158 nsHttpChannel::ResponseWouldVary()
1160 PRBool result = PR_FALSE;
1161 nsCAutoString buf, metaKey;
1162 mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
1163 if (!buf.IsEmpty()) {
1164 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
1166 // enumerate the elements of the Vary header...
1167 char *val = buf.BeginWriting(); // going to munge buf
1168 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1169 while (token) {
1171 // if "*", then assume response would vary. technically speaking,
1172 // "Vary: header, *" is not permitted, but we allow it anyways.
1174 // if the response depends on the value of the "Cookie" header, then
1175 // bail since we do not store cookies in the cache. this is done
1176 // for the following reasons:
1178 // 1- cookies can be very large in size
1180 // 2- cookies may contain sensitive information. (for parity with
1181 // out policy of not storing Set-cookie headers in the cache
1182 // meta data, we likewise do not want to store cookie headers
1183 // here.)
1185 // this implementation is obviously not fully standards compliant, but
1186 // it is perhaps most prudent given the above issues.
1188 if ((*token == '*') || (PL_strcasecmp(token, "cookie") == 0)) {
1189 result = PR_TRUE;
1190 break;
1192 else {
1193 // build cache meta data key...
1194 metaKey = prefix + nsDependentCString(token);
1196 // check the last value of the given request header to see if it has
1197 // since changed. if so, then indeed the cached response is invalid.
1198 nsXPIDLCString lastVal;
1199 mCacheEntry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
1200 if (lastVal) {
1201 nsHttpAtom atom = nsHttp::ResolveAtom(token);
1202 const char *newVal = mRequestHead.PeekHeader(atom);
1203 if (newVal && (strcmp(newVal, lastVal) != 0)) {
1204 result = PR_TRUE; // yes, response would vary
1205 break;
1209 // next token...
1210 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
1214 return result;
1217 //-----------------------------------------------------------------------------
1218 // nsHttpChannel <byte-range>
1219 //-----------------------------------------------------------------------------
1221 nsresult
1222 nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
1224 // cached content has been found to be partial, add necessary request
1225 // headers to complete cache entry.
1227 // use strongest validator available...
1228 const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
1229 if (!val)
1230 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
1231 if (!val) {
1232 // if we hit this code it means mCachedResponseHead->IsResumable() is
1233 // either broken or not being called.
1234 NS_NOTREACHED("no cache validator");
1235 return NS_ERROR_FAILURE;
1238 char buf[32];
1239 PR_snprintf(buf, sizeof(buf), "bytes=%u-", partialLen);
1241 mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
1242 mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
1244 return NS_OK;
1247 nsresult
1248 nsHttpChannel::ProcessPartialContent()
1250 // ok, we've just received a 206
1252 // we need to stream whatever data is in the cache out first, and then
1253 // pick up whatever data is on the wire, writing it into the cache.
1255 LOG(("nsHttpChannel::ProcessPartialContent [this=%x]\n", this));
1257 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
1258 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
1260 // Make sure to clear bogus content-encodings before looking at the header
1261 ClearBogusContentEncodingIfNeeded();
1263 // Check if the content-encoding we now got is different from the one we
1264 // got before
1265 if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
1266 mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
1267 != 0) {
1268 Cancel(NS_ERROR_INVALID_CONTENT_ENCODING);
1269 return CallOnStartRequest();
1273 // suspend the current transaction
1274 nsresult rv = mTransactionPump->Suspend();
1275 if (NS_FAILED(rv)) return rv;
1277 // merge any new headers with the cached response headers
1278 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
1279 if (NS_FAILED(rv)) return rv;
1281 // update the cached response head
1282 nsCAutoString head;
1283 mCachedResponseHead->Flatten(head, PR_TRUE);
1284 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
1285 if (NS_FAILED(rv)) return rv;
1287 // make the cached response be the current response
1288 delete mResponseHead;
1289 mResponseHead = mCachedResponseHead;
1290 mCachedResponseHead = 0;
1292 rv = UpdateExpirationTime();
1293 if (NS_FAILED(rv)) return rv;
1295 // notify observers interested in looking at a response that has been
1296 // merged with any cached headers (http-on-examine-merged-response).
1297 gHttpHandler->OnExamineMergedResponse(this);
1299 // the cached content is valid, although incomplete.
1300 mCachedContentIsValid = PR_TRUE;
1301 return ReadFromCache();
1304 nsresult
1305 nsHttpChannel::OnDoneReadingPartialCacheEntry(PRBool *streamDone)
1307 nsresult rv;
1309 LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%x]", this));
1311 // by default, assume we would have streamed all data or failed...
1312 *streamDone = PR_TRUE;
1314 // setup cache listener to append to cache entry
1315 PRUint32 size;
1316 rv = mCacheEntry->GetDataSize(&size);
1317 if (NS_FAILED(rv)) return rv;
1319 rv = InstallCacheListener(size);
1320 if (NS_FAILED(rv)) return rv;
1322 // need to track the logical offset of the data being sent to our listener
1323 mLogicalOffset = size;
1325 // we're now completing the cached content, so we can clear this flag.
1326 // this puts us in the state of a regular download.
1327 mCachedContentIsPartial = PR_FALSE;
1329 // resume the transaction if it exists, otherwise the pipe contained the
1330 // remaining part of the document and we've now streamed all of the data.
1331 if (mTransactionPump) {
1332 rv = mTransactionPump->Resume();
1333 if (NS_SUCCEEDED(rv))
1334 *streamDone = PR_FALSE;
1336 else
1337 NS_NOTREACHED("no transaction");
1338 return rv;
1341 //-----------------------------------------------------------------------------
1342 // nsHttpChannel <cache>
1343 //-----------------------------------------------------------------------------
1345 nsresult
1346 nsHttpChannel::ProcessNotModified()
1348 nsresult rv;
1350 LOG(("nsHttpChannel::ProcessNotModified [this=%x]\n", this));
1352 NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
1353 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
1355 // merge any new headers with the cached response headers
1356 rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
1357 if (NS_FAILED(rv)) return rv;
1359 // update the cached response head
1360 nsCAutoString head;
1361 mCachedResponseHead->Flatten(head, PR_TRUE);
1362 rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
1363 if (NS_FAILED(rv)) return rv;
1365 // make the cached response be the current response
1366 delete mResponseHead;
1367 mResponseHead = mCachedResponseHead;
1368 mCachedResponseHead = 0;
1370 rv = UpdateExpirationTime();
1371 if (NS_FAILED(rv)) return rv;
1373 // notify observers interested in looking at a reponse that has been
1374 // merged with any cached headers
1375 gHttpHandler->OnExamineMergedResponse(this);
1377 mCachedContentIsValid = PR_TRUE;
1378 rv = ReadFromCache();
1379 if (NS_FAILED(rv)) return rv;
1381 mTransactionReplaced = PR_TRUE;
1382 return NS_OK;
1385 nsresult
1386 nsHttpChannel::ProcessFallback(PRBool *fallingBack)
1388 LOG(("nsHttpChannel::ProcessFallback [this=%x]\n", this));
1389 nsresult rv;
1391 *fallingBack = PR_FALSE;
1393 // At this point a load has failed (either due to network problems
1394 // or an error returned on the server). Perform an application
1395 // cache fallback if we have a URI to fall back to.
1396 if (!mApplicationCache || mFallbackKey.IsEmpty() || mFallbackChannel) {
1397 LOG((" choosing not to fallback [%p,%s,%d]",
1398 mApplicationCache.get(), mFallbackKey.get(), mFallbackChannel));
1399 return NS_OK;
1402 // Make sure the fallback entry hasn't been marked as a foreign
1403 // entry.
1404 PRUint32 fallbackEntryType;
1405 rv = mApplicationCache->GetTypes(mFallbackKey, &fallbackEntryType);
1406 NS_ENSURE_SUCCESS(rv, rv);
1408 if (fallbackEntryType & nsIApplicationCache::ITEM_FOREIGN) {
1409 // This cache points to a fallback that refers to a different
1410 // manifest. Refuse to fall back.
1411 return NS_OK;
1414 NS_ASSERTION(fallbackEntryType & nsIApplicationCache::ITEM_FALLBACK,
1415 "Fallback entry not marked correctly!");
1417 // Kill any opportunistic cache entry, and disable opportunistic
1418 // caching for the fallback.
1419 if (mOfflineCacheEntry) {
1420 mOfflineCacheEntry->Doom();
1421 mOfflineCacheEntry = 0;
1422 mOfflineCacheAccess = 0;
1425 mCacheForOfflineUse = PR_FALSE;
1426 mCachingOpportunistically = PR_FALSE;
1427 mOfflineCacheClientID.Truncate();
1428 mOfflineCacheEntry = 0;
1429 mOfflineCacheAccess = 0;
1431 // Close the current cache entry.
1432 if (mCacheEntry)
1433 CloseCacheEntry(PR_TRUE);
1435 // Create a new channel to load the fallback entry.
1436 nsRefPtr<nsIChannel> newChannel;
1437 rv = gHttpHandler->NewChannel(mURI, getter_AddRefs(newChannel));
1438 NS_ENSURE_SUCCESS(rv, rv);
1440 rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
1441 NS_ENSURE_SUCCESS(rv, rv);
1443 // Make sure the new channel loads from the fallback key.
1444 nsCOMPtr<nsIHttpChannelInternal> httpInternal =
1445 do_QueryInterface(newChannel, &rv);
1446 NS_ENSURE_SUCCESS(rv, rv);
1448 rv = httpInternal->SetupFallbackChannel(mFallbackKey.get());
1449 NS_ENSURE_SUCCESS(rv, rv);
1451 // ... and fallbacks should only load from the cache.
1452 PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE | LOAD_ONLY_FROM_CACHE;
1453 rv = newChannel->SetLoadFlags(newLoadFlags);
1455 // Inform consumers about this fake redirect
1456 PRUint32 redirectFlags = nsIChannelEventSink::REDIRECT_INTERNAL;
1457 rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
1458 if (NS_FAILED(rv))
1459 return rv;
1461 rv = newChannel->AsyncOpen(mListener, mListenerContext);
1462 NS_ENSURE_SUCCESS(rv, rv);
1464 // close down this channel
1465 Cancel(NS_BINDING_REDIRECTED);
1467 // disconnect from our listener
1468 mListener = 0;
1469 mListenerContext = 0;
1470 // and from our callbacks
1471 mCallbacks = nsnull;
1472 mProgressSink = nsnull;
1474 *fallingBack = PR_TRUE;
1476 return NS_OK;
1479 nsresult
1480 nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
1482 nsresult rv;
1484 *delayed = PR_FALSE;
1486 LOG(("nsHttpChannel::OpenCacheEntry [this=%x]", this));
1488 // make sure we're not abusing this function
1489 NS_PRECONDITION(!mCacheEntry, "cache entry already open");
1491 nsCAutoString cacheKey;
1493 if (mRequestHead.Method() == nsHttp::Post) {
1494 // If the post id is already set then this is an attempt to replay
1495 // a post transaction via the cache. Otherwise, we need a unique
1496 // post id for this transaction.
1497 if (mPostID == 0)
1498 mPostID = gHttpHandler->GenerateUniqueID();
1500 else if ((mRequestHead.Method() != nsHttp::Get) &&
1501 (mRequestHead.Method() != nsHttp::Head)) {
1502 // don't use the cache for other types of requests
1503 return NS_OK;
1506 if (mRequestHead.PeekHeader(nsHttp::Range) || mResuming) {
1507 // we don't support caching for byte range requests initiated
1508 // by our clients or via nsIResumableChannel.
1509 // XXX perhaps we could munge their byte range into the cache
1510 // key to make caching sort'a work.
1511 return NS_OK;
1514 if (RequestIsConditional()) {
1515 // don't use the cache if our consumer is making a conditional request
1516 // (see bug 331825).
1517 return NS_OK;
1520 GenerateCacheKey(mPostID, cacheKey);
1522 // Get a cache session with appropriate storage policy
1523 nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
1525 // Set the desired cache access mode accordingly...
1526 nsCacheAccessMode accessRequested;
1527 if (mLoadFlags & (LOAD_ONLY_FROM_CACHE | INHIBIT_CACHING)) {
1528 // If we have been asked to bypass the cache and not write to the
1529 // cache, then don't use the cache at all. Unless we're actually
1530 // offline, which takes precedence over BYPASS_LOCAL_CACHE.
1531 if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
1532 return NS_ERROR_NOT_AVAILABLE;
1533 accessRequested = nsICache::ACCESS_READ;
1535 else if (BYPASS_LOCAL_CACHE(mLoadFlags))
1536 accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
1537 else
1538 accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing
1540 if (!mApplicationCache) {
1541 // Pick up an application cache from the load group if available
1542 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
1543 GetCallback(appCacheContainer);
1545 if (appCacheContainer) {
1546 appCacheContainer->GetApplicationCache(getter_AddRefs(mApplicationCache));
1549 if ((mLoadFlags & LOAD_CHECK_OFFLINE_CACHE) && !mApplicationCache) {
1550 // We're supposed to load from an application cache, but
1551 // one was not supplied by the load group. Ask the
1552 // application cache service to choose one for us.
1553 nsCOMPtr<nsIApplicationCacheService> appCacheService =
1554 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
1555 if (appCacheService) {
1556 nsresult rv = appCacheService->ChooseApplicationCache
1557 (cacheKey, getter_AddRefs(mApplicationCache));
1558 NS_ENSURE_SUCCESS(rv, rv);
1563 nsCOMPtr<nsICacheSession> session;
1565 // Will be set to true if we've found the right session, but need
1566 // to open the cache entry asynchronously.
1567 PRBool waitingForValidation = PR_FALSE;
1569 // If we have an application cache, we check it first.
1570 if (mApplicationCache) {
1571 nsCAutoString appCacheClientID;
1572 mApplicationCache->GetClientID(appCacheClientID);
1574 nsCOMPtr<nsICacheService> serv =
1575 do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
1576 NS_ENSURE_SUCCESS(rv, rv);
1578 rv = serv->CreateSession(appCacheClientID.get(),
1579 nsICache::STORE_OFFLINE,
1580 nsICache::STREAM_BASED,
1581 getter_AddRefs(session));
1582 NS_ENSURE_SUCCESS(rv, rv);
1584 // we'll try to synchronously open the cache entry... however,
1585 // it may be in use and not yet validated, in which case we'll
1586 // try asynchronously opening the cache entry.
1588 // We open with ACCESS_READ only, because we don't want to
1589 // overwrite the offline cache entry non-atomically.
1590 // ACCESS_READ will prevent us from writing to the offline
1591 // cache as a normal cache entry.
1592 rv = session->OpenCacheEntry(cacheKey,
1593 nsICache::ACCESS_READ, PR_FALSE,
1594 getter_AddRefs(mCacheEntry));
1595 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
1596 accessRequested = nsICache::ACCESS_READ;
1597 waitingForValidation = PR_TRUE;
1598 rv = NS_OK;
1601 if (NS_FAILED(rv) && !mCacheForOfflineUse && !mFallbackChannel) {
1602 // Check for namespace match.
1603 nsCOMPtr<nsIApplicationCacheNamespace> namespaceEntry;
1604 rv = mApplicationCache->GetMatchingNamespace
1605 (cacheKey, getter_AddRefs(namespaceEntry));
1606 NS_ENSURE_SUCCESS(rv, rv);
1608 PRUint32 namespaceType = 0;
1609 if (!namespaceEntry ||
1610 NS_FAILED(namespaceEntry->GetItemType(&namespaceType)) ||
1611 (namespaceType &
1612 (nsIApplicationCacheNamespace::NAMESPACE_FALLBACK |
1613 nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC |
1614 nsIApplicationCacheNamespace::NAMESPACE_BYPASS)) == 0) {
1615 // When loading from an application cache, only items
1616 // on the whitelist or matching a
1617 // fallback/opportunistic namespace should hit the
1618 // network...
1619 mLoadFlags |= LOAD_ONLY_FROM_CACHE;
1621 // ... and if there were an application cache entry,
1622 // we would have found it earlier.
1623 return NS_ERROR_CACHE_KEY_NOT_FOUND;
1626 if (namespaceType &
1627 nsIApplicationCacheNamespace::NAMESPACE_FALLBACK) {
1628 rv = namespaceEntry->GetData(mFallbackKey);
1629 NS_ENSURE_SUCCESS(rv, rv);
1632 if ((namespaceType &
1633 nsIApplicationCacheNamespace::NAMESPACE_OPPORTUNISTIC) &&
1634 mLoadFlags & LOAD_DOCUMENT_URI) {
1635 // Document loads for items in an opportunistic namespace
1636 // should be placed in the offline cache.
1637 nsCString clientID;
1638 mApplicationCache->GetClientID(clientID);
1640 mCacheForOfflineUse = !clientID.IsEmpty();
1641 SetOfflineCacheClientID(clientID);
1642 mCachingOpportunistically = PR_TRUE;
1647 if (!mCacheEntry && !waitingForValidation) {
1648 rv = gHttpHandler->GetCacheSession(storagePolicy,
1649 getter_AddRefs(session));
1650 if (NS_FAILED(rv)) return rv;
1652 rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
1653 getter_AddRefs(mCacheEntry));
1654 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
1655 waitingForValidation = PR_TRUE;
1656 rv = NS_OK;
1658 if (NS_FAILED(rv)) return rv;
1661 if (waitingForValidation) {
1662 // access to the cache entry has been denied (because the
1663 // cache entry is probably in use by another channel).
1664 if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
1665 LOG(("bypassing local cache since it is busy\n"));
1666 return NS_ERROR_NOT_AVAILABLE;
1668 rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
1669 if (NS_FAILED(rv)) return rv;
1670 // we'll have to wait for the cache entry
1671 *delayed = PR_TRUE;
1673 else if (NS_SUCCEEDED(rv)) {
1674 mCacheEntry->GetAccessGranted(&mCacheAccess);
1675 LOG(("got cache entry [access=%x]\n", mCacheAccess));
1677 return rv;
1681 nsresult
1682 nsHttpChannel::OpenOfflineCacheEntryForWriting()
1684 nsresult rv;
1686 LOG(("nsHttpChannel::OpenOfflineCacheEntryForWriting [this=%x]", this));
1688 // make sure we're not abusing this function
1689 NS_PRECONDITION(!mOfflineCacheEntry, "cache entry already open");
1691 PRBool offline = gIOService->IsOffline();
1692 if (offline) {
1693 // only put things in the offline cache while online
1694 return NS_OK;
1697 if (mRequestHead.Method() != nsHttp::Get) {
1698 // only cache complete documents offline
1699 return NS_OK;
1702 if (mRequestHead.PeekHeader(nsHttp::Range)) {
1703 // we don't support caching for byte range requests initiated
1704 // by our clients or via nsIResumableChannel.
1705 return NS_OK;
1708 if (RequestIsConditional()) {
1709 // don't use the cache if our consumer is making a conditional request
1710 // (see bug 331825).
1711 return NS_OK;
1714 nsCAutoString cacheKey;
1715 GenerateCacheKey(mPostID, cacheKey);
1717 NS_ENSURE_TRUE(!mOfflineCacheClientID.IsEmpty(),
1718 NS_ERROR_NOT_AVAILABLE);
1720 nsCOMPtr<nsICacheSession> session;
1721 nsCOMPtr<nsICacheService> serv =
1722 do_GetService(NS_CACHESERVICE_CONTRACTID, &rv);
1723 if (NS_FAILED(rv)) return rv;
1725 rv = serv->CreateSession(mOfflineCacheClientID.get(),
1726 nsICache::STORE_OFFLINE,
1727 nsICache::STREAM_BASED,
1728 getter_AddRefs(session));
1729 if (NS_FAILED(rv)) return rv;
1731 rv = session->OpenCacheEntry(cacheKey, nsICache::ACCESS_READ_WRITE,
1732 PR_FALSE, getter_AddRefs(mOfflineCacheEntry));
1734 if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
1735 // access to the cache entry has been denied (because the cache entry
1736 // is probably in use by another channel). Either the cache is being
1737 // read from (we're offline) or it's being updated elsewhere.
1738 return NS_OK;
1741 if (NS_SUCCEEDED(rv)) {
1742 mOfflineCacheEntry->GetAccessGranted(&mOfflineCacheAccess);
1743 LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess));
1746 return rv;
1749 nsresult
1750 nsHttpChannel::GenerateCacheKey(PRUint32 postID, nsACString &cacheKey)
1752 cacheKey.Truncate();
1754 if (mLoadFlags & LOAD_ANONYMOUS) {
1755 cacheKey.AssignLiteral("anon&");
1758 if (postID) {
1759 char buf[32];
1760 PR_snprintf(buf, sizeof(buf), "id=%x&", postID);
1761 cacheKey.Append(buf);
1764 if (!cacheKey.IsEmpty()) {
1765 cacheKey.AppendLiteral("uri=");
1768 // Strip any trailing #ref from the URL before using it as the key
1769 const char *spec = mFallbackChannel ? mFallbackKey.get() : mSpec.get();
1770 const char *p = strchr(spec, '#');
1771 if (p)
1772 cacheKey.Append(spec, p - spec);
1773 else
1774 cacheKey.Append(spec);
1775 return NS_OK;
1778 // UpdateExpirationTime is called when a new response comes in from the server.
1779 // It updates the stored response-time and sets the expiration time on the
1780 // cache entry.
1782 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
1784 // timeRemaining = freshnessLifetime - currentAge
1785 // expirationTime = now + timeRemaining
1787 nsresult
1788 nsHttpChannel::UpdateExpirationTime()
1790 NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
1792 nsresult rv;
1794 PRUint32 expirationTime = 0;
1795 if (!mResponseHead->MustValidate()) {
1796 PRUint32 freshnessLifetime = 0;
1798 rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
1799 if (NS_FAILED(rv)) return rv;
1801 if (freshnessLifetime > 0) {
1802 PRUint32 now = NowInSeconds(), currentAge = 0;
1804 rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
1805 if (NS_FAILED(rv)) return rv;
1807 LOG(("freshnessLifetime = %u, currentAge = %u\n",
1808 freshnessLifetime, currentAge));
1810 if (freshnessLifetime > currentAge) {
1811 PRUint32 timeRemaining = freshnessLifetime - currentAge;
1812 // be careful... now + timeRemaining may overflow
1813 if (now + timeRemaining < now)
1814 expirationTime = PRUint32(-1);
1815 else
1816 expirationTime = now + timeRemaining;
1818 else
1819 expirationTime = now;
1823 rv = mCacheEntry->SetExpirationTime(expirationTime);
1824 NS_ENSURE_SUCCESS(rv, rv);
1826 if (mOfflineCacheEntry) {
1827 rv = mOfflineCacheEntry->SetExpirationTime(expirationTime);
1828 NS_ENSURE_SUCCESS(rv, rv);
1831 return NS_OK;
1834 // CheckCache is called from Connect after a cache entry has been opened for
1835 // this URL but before going out to net. It's purpose is to set or clear the
1836 // mCachedContentIsValid flag, and to configure an If-Modified-Since request
1837 // if validation is required.
1838 nsresult
1839 nsHttpChannel::CheckCache()
1841 nsresult rv = NS_OK;
1843 LOG(("nsHTTPChannel::CheckCache [this=%x entry=%x]",
1844 this, mCacheEntry.get()));
1846 // Be pessimistic: assume the cache entry has no useful data.
1847 mCachedContentIsValid = PR_FALSE;
1849 // Don't proceed unless we have opened a cache entry for reading.
1850 if (!mCacheEntry || !(mCacheAccess & nsICache::ACCESS_READ))
1851 return NS_OK;
1853 nsXPIDLCString buf;
1855 // Get the method that was used to generate the cached response
1856 rv = mCacheEntry->GetMetaDataElement("request-method", getter_Copies(buf));
1857 NS_ENSURE_SUCCESS(rv, rv);
1859 nsHttpAtom method = nsHttp::ResolveAtom(buf);
1860 if (method == nsHttp::Head) {
1861 // The cached response does not contain an entity. We can only reuse
1862 // the response if the current request is also HEAD.
1863 if (mRequestHead.Method() != nsHttp::Head)
1864 return NS_OK;
1866 buf.Adopt(0);
1868 // We'll need this value in later computations...
1869 PRUint32 lastModifiedTime;
1870 rv = mCacheEntry->GetLastModified(&lastModifiedTime);
1871 NS_ENSURE_SUCCESS(rv, rv);
1873 // Determine if this is the first time that this cache entry
1874 // has been accessed during this session.
1875 PRBool fromPreviousSession =
1876 (gHttpHandler->SessionStartTime() > lastModifiedTime);
1878 // Get the cached HTTP response headers
1879 rv = mCacheEntry->GetMetaDataElement("response-head", getter_Copies(buf));
1880 NS_ENSURE_SUCCESS(rv, rv);
1882 // Parse the cached HTTP response headers
1883 NS_ASSERTION(!mCachedResponseHead, "memory leak detected");
1884 mCachedResponseHead = new nsHttpResponseHead();
1885 if (!mCachedResponseHead)
1886 return NS_ERROR_OUT_OF_MEMORY;
1887 rv = mCachedResponseHead->Parse((char *) buf.get());
1888 NS_ENSURE_SUCCESS(rv, rv);
1889 buf.Adopt(0);
1891 // Don't bother to validate LOAD_ONLY_FROM_CACHE items.
1892 // Don't bother to validate items that are read-only,
1893 // unless they are read-only because of INHIBIT_CACHING or because
1894 // we're updating the offline cache.
1895 // Don't bother to validate if this is a fallback entry.
1896 if (mLoadFlags & LOAD_ONLY_FROM_CACHE ||
1897 (mCacheAccess == nsICache::ACCESS_READ &&
1898 !((mLoadFlags & INHIBIT_CACHING) || mCacheForOfflineUse)) ||
1899 mFallbackChannel) {
1900 mCachedContentIsValid = PR_TRUE;
1901 return NS_OK;
1904 PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
1906 if (method != nsHttp::Head && !isCachedRedirect) {
1907 // If the cached content-length is set and it does not match the data
1908 // size of the cached content, then the cached response is partial...
1909 // either we need to issue a byte range request or we need to refetch
1910 // the entire document.
1911 nsInt64 contentLength = mCachedResponseHead->ContentLength();
1912 if (contentLength != nsInt64(-1)) {
1913 PRUint32 size;
1914 rv = mCacheEntry->GetDataSize(&size);
1915 NS_ENSURE_SUCCESS(rv, rv);
1917 if (nsInt64(size) != contentLength) {
1918 LOG(("Cached data size does not match the Content-Length header "
1919 "[content-length=%lld size=%u]\n", PRInt64(contentLength), size));
1920 if ((nsInt64(size) < contentLength) && mCachedResponseHead->IsResumable()) {
1921 // looks like a partial entry.
1922 rv = SetupByteRangeRequest(size);
1923 NS_ENSURE_SUCCESS(rv, rv);
1924 mCachedContentIsPartial = PR_TRUE;
1926 return NS_OK;
1931 PRBool doValidation = PR_FALSE;
1933 // Be optimistic: assume that we won't need to do validation
1934 mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
1935 mRequestHead.ClearHeader(nsHttp::If_None_Match);
1937 // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used.
1938 if (mLoadFlags & LOAD_FROM_CACHE) {
1939 LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
1940 doValidation = PR_FALSE;
1942 // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
1943 // it's revalidated with the server.
1944 else if (mLoadFlags & VALIDATE_ALWAYS) {
1945 LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
1946 doValidation = PR_TRUE;
1948 // Even if the VALIDATE_NEVER flag is set, there are still some cases in
1949 // which we must validate the cached response with the server.
1950 else if (mLoadFlags & VALIDATE_NEVER) {
1951 LOG(("VALIDATE_NEVER set\n"));
1952 // if no-store or if no-cache and ssl, validate cached response (see
1953 // bug 112564 for an explanation of this logic)
1954 if (mCachedResponseHead->NoStore() ||
1955 (mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
1956 LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
1957 doValidation = PR_TRUE;
1959 else {
1960 LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
1961 doValidation = PR_FALSE;
1964 // check if validation is strictly required...
1965 else if (mCachedResponseHead->MustValidate()) {
1966 LOG(("Validating based on MustValidate() returning TRUE\n"));
1967 doValidation = PR_TRUE;
1970 else if (ResponseWouldVary()) {
1971 LOG(("Validating based on Vary headers returning TRUE\n"));
1972 doValidation = PR_TRUE;
1974 // Check if the cache entry has expired...
1975 else {
1976 PRUint32 time = 0; // a temporary variable for storing time values...
1978 rv = mCacheEntry->GetExpirationTime(&time);
1979 NS_ENSURE_SUCCESS(rv, rv);
1981 if (NowInSeconds() <= time)
1982 doValidation = PR_FALSE;
1983 else if (mCachedResponseHead->MustValidateIfExpired())
1984 doValidation = PR_TRUE;
1985 else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
1986 // If the cached response does not include expiration infor-
1987 // mation, then we must validate the response, despite whether
1988 // or not this is the first access this session. This behavior
1989 // is consistent with existing browsers and is generally expected
1990 // by web authors.
1991 rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
1992 NS_ENSURE_SUCCESS(rv, rv);
1994 if (time == 0)
1995 doValidation = PR_TRUE;
1996 else
1997 doValidation = fromPreviousSession;
1999 else
2000 doValidation = PR_TRUE;
2002 LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
2005 if (!doValidation) {
2007 // Check the authorization headers used to generate the cache entry.
2008 // We must validate the cache entry if:
2010 // 1) the cache entry was generated prior to this session w/
2011 // credentials (see bug 103402).
2012 // 2) the cache entry was generated w/o credentials, but would now
2013 // require credentials (see bug 96705).
2015 // NOTE: this does not apply to proxy authentication.
2017 mCacheEntry->GetMetaDataElement("auth", getter_Copies(buf));
2018 doValidation =
2019 (fromPreviousSession && !buf.IsEmpty()) ||
2020 (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
2023 if (!doValidation) {
2024 // Sites redirect back to the original URI after setting a session/tracking
2025 // cookie. In such cases, force revalidation so that we hit the net and do not
2026 // cycle thru cached responses.
2027 if (isCachedRedirect && mRequestHead.PeekHeader(nsHttp::Cookie))
2028 doValidation = PR_TRUE;
2031 mCachedContentIsValid = !doValidation;
2033 if (doValidation) {
2035 // now, we are definitely going to issue a HTTP request to the server.
2036 // make it conditional if possible.
2038 // do not attempt to validate no-store content, since servers will not
2039 // expect it to be cached. (we only keep it in our cache for the
2040 // purposes of back/forward, etc.)
2042 // the request method MUST be either GET or HEAD (see bug 175641).
2044 if (!mCachedResponseHead->NoStore() &&
2045 (mRequestHead.Method() == nsHttp::Get ||
2046 mRequestHead.Method() == nsHttp::Head)) {
2047 const char *val;
2048 // Add If-Modified-Since header if a Last-Modified was given
2049 val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
2050 if (val)
2051 mRequestHead.SetHeader(nsHttp::If_Modified_Since,
2052 nsDependentCString(val));
2053 // Add If-None-Match header if an ETag was given in the response
2054 val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
2055 if (val)
2056 mRequestHead.SetHeader(nsHttp::If_None_Match,
2057 nsDependentCString(val));
2061 LOG(("CheckCache [this=%x doValidation=%d]\n", this, doValidation));
2062 return NS_OK;
2066 nsresult
2067 nsHttpChannel::ShouldUpdateOfflineCacheEntry(PRBool *shouldCacheForOfflineUse)
2069 *shouldCacheForOfflineUse = PR_FALSE;
2071 if (!mOfflineCacheEntry) {
2072 return NS_OK;
2075 // if we're updating the cache entry, update the offline cache entry too
2076 if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE)) {
2077 *shouldCacheForOfflineUse = PR_TRUE;
2078 return NS_OK;
2081 // if there's nothing in the offline cache, add it
2082 if (mOfflineCacheEntry && (mOfflineCacheAccess == nsICache::ACCESS_WRITE)) {
2083 *shouldCacheForOfflineUse = PR_TRUE;
2084 return NS_OK;
2087 // if the document is newer than the offline entry, update it
2088 PRUint32 docLastModifiedTime;
2089 nsresult rv = mResponseHead->GetLastModifiedValue(&docLastModifiedTime);
2090 if (NS_FAILED(rv)) {
2091 *shouldCacheForOfflineUse = PR_TRUE;
2092 return NS_OK;
2095 PRUint32 offlineLastModifiedTime;
2096 rv = mOfflineCacheEntry->GetLastModified(&offlineLastModifiedTime);
2097 NS_ENSURE_SUCCESS(rv, rv);
2099 if (docLastModifiedTime > offlineLastModifiedTime) {
2100 *shouldCacheForOfflineUse = PR_TRUE;
2101 return NS_OK;
2104 return NS_OK;
2107 // If the data in the cache hasn't expired, then there's no need to
2108 // talk with the server, not even to do an if-modified-since. This
2109 // method creates a stream from the cache, synthesizing all the various
2110 // channel-related events.
2111 nsresult
2112 nsHttpChannel::ReadFromCache()
2114 nsresult rv;
2116 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
2117 NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
2119 LOG(("nsHttpChannel::ReadFromCache [this=%x] "
2120 "Using cached copy of: %s\n", this, mSpec.get()));
2122 if (mCachedResponseHead) {
2123 NS_ASSERTION(!mResponseHead, "memory leak");
2124 mResponseHead = mCachedResponseHead;
2125 mCachedResponseHead = 0;
2128 // if we don't already have security info, try to get it from the cache
2129 // entry. there are two cases to consider here: 1) we are just reading
2130 // from the cache, or 2) this may be due to a 304 not modified response,
2131 // in which case we could have security info from a socket transport.
2132 if (!mSecurityInfo)
2133 mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
2135 if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) {
2136 // We have write access to the cache, but we don't need to go to the
2137 // server to validate at this time, so just mark the cache entry as
2138 // valid in order to allow others access to this cache entry.
2139 mCacheEntry->MarkValid();
2142 // if this is a cached redirect, we must process the redirect asynchronously
2143 // since AsyncOpen may not have returned yet. Make sure there is a Location
2144 // header, otherwise we'll have to treat this like a normal 200 response.
2145 if (mResponseHead && (mResponseHead->Status() / 100 == 3)
2146 && (mResponseHead->PeekHeader(nsHttp::Location)))
2147 return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
2149 // have we been configured to skip reading from the cache?
2150 if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
2151 // if offline caching has been requested and the offline cache needs
2152 // updating, complete the call even if the main cache entry is
2153 // up-to-date
2154 PRBool shouldUpdateOffline;
2155 if (!mCacheForOfflineUse ||
2156 NS_FAILED(ShouldUpdateOfflineCacheEntry(&shouldUpdateOffline)) ||
2157 !shouldUpdateOffline) {
2159 LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED "
2160 "load flag\n"));
2161 return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
2165 // set up the offline cache entry for writing
2166 if (mCacheForOfflineUse) {
2167 PRBool shouldUpdateOffline;
2168 rv = ShouldUpdateOfflineCacheEntry(&shouldUpdateOffline);
2169 if (NS_FAILED(rv)) return rv;
2171 if (shouldUpdateOffline) {
2172 LOG(("writing to the offline cache"));
2173 rv = InitOfflineCacheEntry();
2174 if (NS_FAILED(rv)) return rv;
2176 if (mOfflineCacheEntry) {
2177 rv = InstallOfflineCacheListener();
2178 if (NS_FAILED(rv)) return rv;
2180 } else {
2181 LOG(("offline cache is up to date, not updating"));
2182 CloseOfflineCacheEntry();
2186 // open input stream for reading...
2187 nsCOMPtr<nsIInputStream> stream;
2188 rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
2189 if (NS_FAILED(rv)) return rv;
2191 rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump),
2192 stream, nsInt64(-1), nsInt64(-1), 0, 0,
2193 PR_TRUE);
2194 if (NS_FAILED(rv)) return rv;
2196 return mCachePump->AsyncRead(this, mListenerContext);
2199 void
2200 nsHttpChannel::CloseCacheEntry(PRBool doomOnFailure)
2202 if (!mCacheEntry)
2203 return;
2205 LOG(("nsHttpChannel::CloseCacheEntry [this=%x]", this));
2207 // If we have begun to create or replace a cache entry, and that cache
2208 // entry is not complete and not resumable, then it needs to be doomed.
2209 // Otherwise, CheckCache will make the mistake of thinking that the
2210 // partial cache entry is complete.
2212 PRBool doom = PR_FALSE;
2213 if (mInitedCacheEntry) {
2214 NS_ASSERTION(mResponseHead, "oops");
2215 if (NS_FAILED(mStatus) && doomOnFailure &&
2216 (mCacheAccess & nsICache::ACCESS_WRITE) &&
2217 !mResponseHead->IsResumable())
2218 doom = PR_TRUE;
2220 else if (mCacheAccess == nsICache::ACCESS_WRITE)
2221 doom = PR_TRUE;
2223 if (doom) {
2224 LOG((" dooming cache entry!!"));
2225 mCacheEntry->Doom();
2228 if (mCachedResponseHead) {
2229 delete mCachedResponseHead;
2230 mCachedResponseHead = 0;
2233 mCachePump = 0;
2234 mCacheEntry = 0;
2235 mCacheAccess = 0;
2236 mInitedCacheEntry = PR_FALSE;
2240 void
2241 nsHttpChannel::CloseOfflineCacheEntry()
2243 if (!mOfflineCacheEntry)
2244 return;
2246 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%x]", this));
2248 if (NS_FAILED(mStatus)) {
2249 mOfflineCacheEntry->Doom();
2251 else {
2252 PRBool succeeded;
2253 if (NS_SUCCEEDED(GetRequestSucceeded(&succeeded)) && !succeeded)
2254 mOfflineCacheEntry->Doom();
2257 mOfflineCacheEntry = 0;
2258 mOfflineCacheAccess = 0;
2260 if (mCachingOpportunistically) {
2261 nsCOMPtr<nsIApplicationCacheService> appCacheService =
2262 do_GetService(NS_APPLICATIONCACHESERVICE_CONTRACTID);
2263 if (appCacheService) {
2264 nsCAutoString cacheKey;
2265 GenerateCacheKey(mPostID, cacheKey);
2266 appCacheService->CacheOpportunistically(mApplicationCache,
2267 cacheKey);
2273 // Initialize the cache entry for writing.
2274 // - finalize storage policy
2275 // - store security info
2276 // - update expiration time
2277 // - store headers and other meta data
2278 nsresult
2279 nsHttpChannel::InitCacheEntry()
2281 nsresult rv;
2283 NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
2284 // if only reading, nothing to be done here.
2285 if (mCacheAccess == nsICache::ACCESS_READ)
2286 return NS_OK;
2288 // Don't cache the response again if already cached...
2289 if (mCachedContentIsValid)
2290 return NS_OK;
2292 LOG(("nsHttpChannel::InitCacheEntry [this=%x entry=%x]\n",
2293 this, mCacheEntry.get()));
2295 // The no-store directive within the 'Cache-Control:' header indicates
2296 // that we must not store the response in a persistent cache.
2297 if (mResponseHead->NoStore())
2298 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
2300 // Only cache SSL content on disk if the server sent a
2301 // Cache-Control: public header, or if the user set the pref
2302 if (!gHttpHandler->CanCacheAllSSLContent() &&
2303 mConnectionInfo->UsingSSL() && !mResponseHead->CacheControlPublic())
2304 mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
2306 if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
2307 rv = mCacheEntry->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
2308 if (NS_FAILED(rv)) return rv;
2311 // Set the expiration time for this cache entry
2312 rv = UpdateExpirationTime();
2313 if (NS_FAILED(rv)) return rv;
2315 rv = AddCacheEntryHeaders(mCacheEntry);
2316 if (NS_FAILED(rv)) return rv;
2318 mInitedCacheEntry = PR_TRUE;
2319 return NS_OK;
2323 nsresult
2324 nsHttpChannel::InitOfflineCacheEntry()
2326 if (!mOfflineCacheEntry) {
2327 return NS_OK;
2330 if (mResponseHead->NoStore()) {
2331 CloseOfflineCacheEntry();
2333 return NS_OK;
2336 // This entry's expiration time should match the main entry's expiration
2337 // time. UpdateExpirationTime() will keep it in sync once the offline
2338 // cache entry has been created.
2339 if (mCacheEntry) {
2340 PRUint32 expirationTime;
2341 nsresult rv = mCacheEntry->GetExpirationTime(&expirationTime);
2342 NS_ENSURE_SUCCESS(rv, rv);
2344 mOfflineCacheEntry->SetExpirationTime(expirationTime);
2347 return AddCacheEntryHeaders(mOfflineCacheEntry);
2351 nsresult
2352 nsHttpChannel::AddCacheEntryHeaders(nsICacheEntryDescriptor *entry)
2354 nsresult rv;
2356 // Store secure data in memory only
2357 if (mSecurityInfo)
2358 entry->SetSecurityInfo(mSecurityInfo);
2360 // Store the HTTP request method with the cache entry so we can distinguish
2361 // for example GET and HEAD responses.
2362 rv = entry->SetMetaDataElement("request-method",
2363 mRequestHead.Method().get());
2364 if (NS_FAILED(rv)) return rv;
2366 // Store the HTTP authorization scheme used if any...
2367 rv = StoreAuthorizationMetaData(entry);
2368 if (NS_FAILED(rv)) return rv;
2370 // Iterate over the headers listed in the Vary response header, and
2371 // store the value of the corresponding request header so we can verify
2372 // that it has not varied when we try to re-use the cached response at
2373 // a later time. Take care not to store "Cookie" headers though. We
2374 // take care of "Vary: cookie" in ResponseWouldVary.
2376 // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
2377 // in the cache. we could try to avoid needlessly storing the "accept"
2378 // header in this case, but it doesn't seem worth the extra code to perform
2379 // the check.
2381 nsCAutoString buf, metaKey;
2382 mResponseHead->GetHeader(nsHttp::Vary, buf);
2383 if (!buf.IsEmpty()) {
2384 NS_NAMED_LITERAL_CSTRING(prefix, "request-");
2386 char *val = buf.BeginWriting(); // going to munge buf
2387 char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
2388 while (token) {
2389 if ((*token != '*') && (PL_strcasecmp(token, "cookie") != 0)) {
2390 nsHttpAtom atom = nsHttp::ResolveAtom(token);
2391 const char *requestVal = mRequestHead.PeekHeader(atom);
2392 if (requestVal) {
2393 // build cache meta data key and set meta data element...
2394 metaKey = prefix + nsDependentCString(token);
2395 entry->SetMetaDataElement(metaKey.get(), requestVal);
2398 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
2404 // Store the received HTTP head with the cache entry as an element of
2405 // the meta data.
2406 nsCAutoString head;
2407 mResponseHead->Flatten(head, PR_TRUE);
2408 rv = entry->SetMetaDataElement("response-head", head.get());
2410 return rv;
2413 nsresult
2414 nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntryDescriptor *entry)
2416 // Not applicable to proxy authorization...
2417 const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
2418 if (val) {
2419 // eg. [Basic realm="wally world"]
2420 nsCAutoString buf(Substring(val, strchr(val, ' ')));
2421 return entry->SetMetaDataElement("auth", buf.get());
2423 return NS_OK;
2426 // Finalize the cache entry
2427 // - may need to rewrite response headers if any headers changed
2428 // - may need to recalculate the expiration time if any headers changed
2429 // - called only for freshly written cache entries
2430 nsresult
2431 nsHttpChannel::FinalizeCacheEntry()
2433 LOG(("nsHttpChannel::FinalizeCacheEntry [this=%x]\n", this));
2435 if (mResponseHead && mResponseHeadersModified) {
2436 // Set the expiration time for this cache entry
2437 nsresult rv = UpdateExpirationTime();
2438 if (NS_FAILED(rv)) return rv;
2440 return NS_OK;
2443 // Open an output stream to the cache entry and insert a listener tee into
2444 // the chain of response listeners.
2445 nsresult
2446 nsHttpChannel::InstallCacheListener(PRUint32 offset)
2448 nsresult rv;
2450 LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
2452 NS_ASSERTION(mCacheEntry, "no cache entry");
2453 NS_ASSERTION(mListener, "no listener");
2455 nsCOMPtr<nsIOutputStream> out;
2456 rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
2457 if (NS_FAILED(rv)) return rv;
2459 // XXX disk cache does not support overlapped i/o yet
2460 #if 0
2461 // Mark entry valid inorder to allow simultaneous reading...
2462 rv = mCacheEntry->MarkValid();
2463 if (NS_FAILED(rv)) return rv;
2464 #endif
2466 nsCOMPtr<nsIStreamListenerTee> tee =
2467 do_CreateInstance(kStreamListenerTeeCID, &rv);
2468 if (NS_FAILED(rv)) return rv;
2470 rv = tee->Init(mListener, out);
2471 if (NS_FAILED(rv)) return rv;
2473 mListener = tee;
2474 return NS_OK;
2477 nsresult
2478 nsHttpChannel::InstallOfflineCacheListener()
2480 nsresult rv;
2482 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
2483 mSpec.get()));
2485 NS_ASSERTION(mOfflineCacheEntry, "no offline cache entry");
2486 NS_ASSERTION(mListener, "no listener");
2488 nsCOMPtr<nsIOutputStream> out;
2489 rv = mOfflineCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
2490 if (NS_FAILED(rv)) return rv;
2492 nsCOMPtr<nsIStreamListenerTee> tee =
2493 do_CreateInstance(kStreamListenerTeeCID, &rv);
2494 if (NS_FAILED(rv)) return rv;
2496 rv = tee->Init(mListener, out);
2497 if (NS_FAILED(rv)) return rv;
2499 mListener = tee;
2501 return NS_OK;
2504 void
2505 nsHttpChannel::ClearBogusContentEncodingIfNeeded()
2507 // For .gz files, apache sends both a Content-Type: application/x-gzip
2508 // as well as Content-Encoding: gzip, which is completely wrong. In
2509 // this case, we choose to ignore the rogue Content-Encoding header. We
2510 // must do this early on so as to prevent it from being seen up stream.
2511 // The same problem exists for Content-Encoding: compress in default
2512 // Apache installs.
2513 if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "gzip") && (
2514 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
2515 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
2516 mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
2517 // clear the Content-Encoding header
2518 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
2520 else if (mResponseHead->HasHeaderValue(nsHttp::Content_Encoding, "compress") && (
2521 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
2522 mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
2523 // clear the Content-Encoding header
2524 mResponseHead->ClearHeader(nsHttp::Content_Encoding);
2528 //-----------------------------------------------------------------------------
2529 // nsHttpChannel <redirect>
2530 //-----------------------------------------------------------------------------
2532 static PLDHashOperator
2533 CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure)
2535 nsIWritablePropertyBag* bag = static_cast<nsIWritablePropertyBag*>
2536 (aClosure);
2537 bag->SetProperty(aKey, aData);
2538 return PL_DHASH_NEXT;
2541 nsresult
2542 nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
2543 nsIChannel *newChannel,
2544 PRBool preserveMethod)
2546 PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE;
2547 // if the original channel was using SSL and this channel is not using
2548 // SSL, then no need to inhibit persistent caching. however, if the
2549 // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
2550 // set, then allow the flag to apply to the redirected channel as well.
2551 // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
2552 // we only need to check if the original channel was using SSL.
2553 if (mConnectionInfo->UsingSSL())
2554 newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
2556 newChannel->SetOriginalURI(mOriginalURI);
2557 newChannel->SetLoadGroup(mLoadGroup);
2558 newChannel->SetNotificationCallbacks(mCallbacks);
2559 newChannel->SetLoadFlags(newLoadFlags);
2561 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
2562 if (!httpChannel)
2563 return NS_OK; // no other options to set
2565 if (preserveMethod) {
2566 nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
2567 if (mUploadStream && uploadChannel) {
2568 // rewind upload stream
2569 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
2570 if (seekable)
2571 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
2573 // replicate original call to SetUploadStream...
2574 if (mUploadStreamHasHeaders)
2575 uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
2576 else {
2577 const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
2578 const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length);
2579 if (ctype && clen)
2580 uploadChannel->SetUploadStream(mUploadStream,
2581 nsDependentCString(ctype),
2582 atoi(clen));
2585 // must happen after setting upload stream since SetUploadStream
2586 // may change the request method.
2587 httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
2589 // convey the referrer if one was used for this channel to the next one
2590 if (mReferrer)
2591 httpChannel->SetReferrer(mReferrer);
2592 // convey the mAllowPipelining flag
2593 httpChannel->SetAllowPipelining(mAllowPipelining);
2594 // convey the new redirection limit
2595 httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
2597 nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
2598 if (httpInternal) {
2599 // update the DocumentURI indicator since we are being redirected.
2600 // if this was a top-level document channel, then the new channel
2601 // should have its mDocumentURI point to newURI; otherwise, we
2602 // just need to pass along our mDocumentURI to the new channel.
2603 if (newURI && (mURI == mDocumentURI))
2604 httpInternal->SetDocumentURI(newURI);
2605 else
2606 httpInternal->SetDocumentURI(mDocumentURI);
2609 // convey the mApplyConversion flag (bug 91862)
2610 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
2611 if (encodedChannel)
2612 encodedChannel->SetApplyConversion(mApplyConversion);
2614 // transfer the resume information
2615 if (mResuming) {
2616 nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
2617 if (!resumableChannel) {
2618 NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
2619 return NS_ERROR_NOT_RESUMABLE;
2621 resumableChannel->ResumeAt(mStartPos, mEntityID);
2624 // transfer application cache information
2625 if (mApplicationCache) {
2626 nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer =
2627 do_QueryInterface(newChannel);
2628 if (appCacheContainer) {
2629 appCacheContainer->SetApplicationCache(mApplicationCache);
2633 // transfer any properties
2634 nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
2635 if (bag)
2636 mPropertyHash.EnumerateRead(CopyProperties, bag.get());
2638 return NS_OK;
2641 nsresult
2642 nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
2644 LOG(("nsHttpChannel::ProcessRedirection [this=%x type=%u]\n",
2645 this, redirectType));
2647 const char *location = mResponseHead->PeekHeader(nsHttp::Location);
2649 // if a location header was not given, then we can't perform the redirect,
2650 // so just carry on as though this were a normal response.
2651 if (!location)
2652 return NS_ERROR_FAILURE;
2654 // make sure non-ASCII characters in the location header are escaped.
2655 nsCAutoString locationBuf;
2656 if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
2657 location = locationBuf.get();
2659 if (mRedirectionLimit == 0) {
2660 LOG(("redirection limit reached!\n"));
2661 // this error code is fatal, and should be conveyed to our listener.
2662 Cancel(NS_ERROR_REDIRECT_LOOP);
2663 return NS_ERROR_REDIRECT_LOOP;
2666 LOG(("redirecting to: %s [redirection-limit=%u]\n",
2667 location, PRUint32(mRedirectionLimit)));
2669 nsresult rv;
2670 nsCOMPtr<nsIChannel> newChannel;
2671 nsCOMPtr<nsIURI> newURI;
2673 // create a new URI using the location header and the current URL
2674 // as a base...
2675 nsCOMPtr<nsIIOService> ioService;
2676 rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
2677 if (NS_FAILED(rv)) return rv;
2679 // the new uri should inherit the origin charset of the current uri
2680 nsCAutoString originCharset;
2681 rv = mURI->GetOriginCharset(originCharset);
2682 if (NS_FAILED(rv))
2683 originCharset.Truncate();
2685 rv = ioService->NewURI(nsDependentCString(location), originCharset.get(), mURI,
2686 getter_AddRefs(newURI));
2687 if (NS_FAILED(rv)) return rv;
2689 // Kill the current cache entry if we are redirecting
2690 // back to ourself.
2691 PRBool redirectingBackToSameURI = PR_FALSE;
2692 if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
2693 NS_SUCCEEDED(mURI->Equals(newURI, &redirectingBackToSameURI)) &&
2694 redirectingBackToSameURI)
2695 mCacheEntry->Doom();
2697 // move the reference of the old location to the new one if the new
2698 // one has none.
2699 nsCOMPtr<nsIURL> newURL = do_QueryInterface(newURI);
2700 if (newURL) {
2701 nsCAutoString ref;
2702 rv = newURL->GetRef(ref);
2703 if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
2704 nsCOMPtr<nsIURL> baseURL(do_QueryInterface(mURI));
2705 if (baseURL) {
2706 baseURL->GetRef(ref);
2707 if (!ref.IsEmpty())
2708 newURL->SetRef(ref);
2713 // if we need to re-send POST data then be sure to ask the user first.
2714 PRBool preserveMethod = (redirectType == 307);
2715 if (preserveMethod && mUploadStream) {
2716 rv = PromptTempRedirect();
2717 if (NS_FAILED(rv)) return rv;
2720 rv = ioService->NewChannelFromURI(newURI, getter_AddRefs(newChannel));
2721 if (NS_FAILED(rv)) return rv;
2723 rv = SetupReplacementChannel(newURI, newChannel, preserveMethod);
2724 if (NS_FAILED(rv)) return rv;
2726 PRUint32 redirectFlags;
2727 if (redirectType == 301) // Moved Permanently
2728 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
2729 else
2730 redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
2732 // verify that this is a legal redirect
2733 rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
2734 if (NS_FAILED(rv))
2735 return rv;
2737 // And now, the deprecated way
2738 nsCOMPtr<nsIHttpEventSink> httpEventSink;
2739 GetCallback(httpEventSink);
2740 if (httpEventSink) {
2741 // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
2742 // versions.
2743 rv = httpEventSink->OnRedirect(this, newChannel);
2744 if (NS_FAILED(rv)) return rv;
2746 // XXX we used to talk directly with the script security manager, but that
2747 // should really be handled by the event sink implementation.
2749 // begin loading the new channel
2750 rv = newChannel->AsyncOpen(mListener, mListenerContext);
2751 if (NS_FAILED(rv)) return rv;
2753 // close down this channel
2754 Cancel(NS_BINDING_REDIRECTED);
2756 // disconnect from our listener
2757 mListener = 0;
2758 mListenerContext = 0;
2759 // and from our callbacks
2760 mCallbacks = nsnull;
2761 mProgressSink = nsnull;
2762 return NS_OK;
2765 //-----------------------------------------------------------------------------
2766 // nsHttpChannel <auth>
2767 //-----------------------------------------------------------------------------
2769 // buf contains "domain\user"
2770 static void
2771 ParseUserDomain(PRUnichar *buf,
2772 const PRUnichar **user,
2773 const PRUnichar **domain)
2775 PRUnichar *p = buf;
2776 while (*p && *p != '\\') ++p;
2777 if (!*p)
2778 return;
2779 *p = '\0';
2780 *domain = buf;
2781 *user = p + 1;
2784 // helper function for setting identity from raw user:pass
2785 static void
2786 SetIdent(nsHttpAuthIdentity &ident,
2787 PRUint32 authFlags,
2788 PRUnichar *userBuf,
2789 PRUnichar *passBuf)
2791 const PRUnichar *user = userBuf;
2792 const PRUnichar *domain = nsnull;
2794 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
2795 ParseUserDomain(userBuf, &user, &domain);
2797 ident.Set(domain, user, passBuf);
2800 // helper function for getting an auth prompt from an interface requestor
2801 static void
2802 GetAuthPrompt(nsIInterfaceRequestor *ifreq, PRBool proxyAuth,
2803 nsIAuthPrompt2 **result)
2805 if (!ifreq)
2806 return;
2808 PRUint32 promptReason;
2809 if (proxyAuth)
2810 promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
2811 else
2812 promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
2814 nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
2815 if (promptProvider)
2816 promptProvider->GetAuthPrompt(promptReason,
2817 NS_GET_IID(nsIAuthPrompt2),
2818 reinterpret_cast<void**>(result));
2819 else
2820 NS_QueryAuthPrompt2(ifreq, result);
2823 // generate credentials for the given challenge, and update the auth cache.
2824 nsresult
2825 nsHttpChannel::GenCredsAndSetEntry(nsIHttpAuthenticator *auth,
2826 PRBool proxyAuth,
2827 const char *scheme,
2828 const char *host,
2829 PRInt32 port,
2830 const char *directory,
2831 const char *realm,
2832 const char *challenge,
2833 const nsHttpAuthIdentity &ident,
2834 nsCOMPtr<nsISupports> &sessionState,
2835 char **result)
2837 nsresult rv;
2838 PRUint32 authFlags;
2840 rv = auth->GetAuthFlags(&authFlags);
2841 if (NS_FAILED(rv)) return rv;
2843 nsISupports *ss = sessionState;
2845 // set informations that depend on whether
2846 // we're authenticating against a proxy
2847 // or a webserver
2848 nsISupports **continuationState;
2850 if (proxyAuth) {
2851 continuationState = &mProxyAuthContinuationState;
2852 } else {
2853 continuationState = &mAuthContinuationState;
2856 rv = auth->GenerateCredentials(this,
2857 challenge,
2858 proxyAuth,
2859 ident.Domain(),
2860 ident.User(),
2861 ident.Password(),
2862 &ss,
2863 &*continuationState,
2864 result);
2866 sessionState.swap(ss);
2867 if (NS_FAILED(rv)) return rv;
2869 // don't log this in release build since it could contain sensitive info.
2870 #ifdef DEBUG
2871 LOG(("generated creds: %s\n", *result));
2872 #endif
2874 // find out if this authenticator allows reuse of credentials and/or
2875 // challenge.
2876 PRBool saveCreds =
2877 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS);
2878 PRBool saveChallenge =
2879 0 != (authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE);
2881 // this getter never fails
2882 nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
2884 // create a cache entry. we do this even though we don't yet know that
2885 // these credentials are valid b/c we need to avoid prompting the user
2886 // more than once in case the credentials are valid.
2888 // if the credentials are not reusable, then we don't bother sticking
2889 // them in the auth cache.
2890 rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
2891 saveCreds ? *result : nsnull,
2892 saveChallenge ? challenge : nsnull,
2893 ident, sessionState);
2894 return rv;
2897 nsresult
2898 nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
2900 LOG(("nsHttpChannel::ProcessAuthentication [this=%x code=%u]\n",
2901 this, httpStatus));
2903 if (mLoadFlags & LOAD_ANONYMOUS) {
2904 return NS_ERROR_NOT_AVAILABLE;
2907 const char *challenges;
2908 PRBool proxyAuth = (httpStatus == 407);
2910 nsresult rv = PrepareForAuthentication(proxyAuth);
2911 if (NS_FAILED(rv))
2912 return rv;
2914 if (proxyAuth) {
2915 // only allow a proxy challenge if we have a proxy server configured.
2916 // otherwise, we could inadvertantly expose the user's proxy
2917 // credentials to an origin server. We could attempt to proceed as
2918 // if we had received a 401 from the server, but why risk flirting
2919 // with trouble? IE similarly rejects 407s when a proxy server is
2920 // not configured, so there's no reason not to do the same.
2921 if (!mConnectionInfo->UsingHttpProxy()) {
2922 LOG(("rejecting 407 when proxy server not configured!\n"));
2923 return NS_ERROR_UNEXPECTED;
2925 if (mConnectionInfo->UsingSSL() && !mTransaction->SSLConnectFailed()) {
2926 // we need to verify that this challenge came from the proxy
2927 // server itself, and not some server on the other side of the
2928 // SSL tunnel.
2929 LOG(("rejecting 407 from origin server!\n"));
2930 return NS_ERROR_UNEXPECTED;
2932 challenges = mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
2934 else
2935 challenges = mResponseHead->PeekHeader(nsHttp::WWW_Authenticate);
2936 NS_ENSURE_TRUE(challenges, NS_ERROR_UNEXPECTED);
2938 nsCAutoString creds;
2939 rv = GetCredentials(challenges, proxyAuth, creds);
2940 if (NS_FAILED(rv))
2941 LOG(("unable to authenticate\n"));
2942 else {
2943 // set the authentication credentials
2944 if (proxyAuth)
2945 mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
2946 else
2947 mRequestHead.SetHeader(nsHttp::Authorization, creds);
2949 mAuthRetryPending = PR_TRUE; // see DoAuthRetry
2951 return rv;
2954 nsresult
2955 nsHttpChannel::PrepareForAuthentication(PRBool proxyAuth)
2957 LOG(("nsHttpChannel::PrepareForAuthentication [this=%x]\n", this));
2959 if (!proxyAuth) {
2960 // reset the current proxy continuation state because our last
2961 // authentication attempt was completed successfully.
2962 NS_IF_RELEASE(mProxyAuthContinuationState);
2963 LOG((" proxy continuation state has been reset"));
2966 if (!mConnectionInfo->UsingHttpProxy() || mProxyAuthType.IsEmpty())
2967 return NS_OK;
2969 // We need to remove any Proxy_Authorization header left over from a
2970 // non-request based authentication handshake (e.g., for NTLM auth).
2972 nsCAutoString contractId;
2973 contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
2974 contractId.Append(mProxyAuthType);
2976 nsresult rv;
2977 nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
2978 do_GetService(contractId.get(), &rv);
2979 if (NS_FAILED(rv))
2980 return rv;
2982 PRUint32 precedingAuthFlags;
2983 rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
2984 if (NS_FAILED(rv))
2985 return rv;
2987 if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
2988 const char *challenges =
2989 mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
2990 if (!challenges) {
2991 // delete the proxy authorization header because we weren't
2992 // asked to authenticate
2993 mRequestHead.ClearHeader(nsHttp::Proxy_Authorization);
2994 LOG((" cleared proxy authorization header"));
2998 return NS_OK;
3001 nsresult
3002 nsHttpChannel::GetCredentials(const char *challenges,
3003 PRBool proxyAuth,
3004 nsAFlatCString &creds)
3006 nsCOMPtr<nsIHttpAuthenticator> auth;
3007 nsCAutoString challenge;
3009 nsCString authType; // force heap allocation to enable string sharing since
3010 // we'll be assigning this value into mAuthType.
3012 // set informations that depend on whether we're authenticating against a
3013 // proxy or a webserver
3014 nsISupports **currentContinuationState;
3015 nsCString *currentAuthType;
3017 if (proxyAuth) {
3018 currentContinuationState = &mProxyAuthContinuationState;
3019 currentAuthType = &mProxyAuthType;
3020 } else {
3021 currentContinuationState = &mAuthContinuationState;
3022 currentAuthType = &mAuthType;
3025 nsresult rv = NS_ERROR_NOT_AVAILABLE;
3026 PRBool gotCreds = PR_FALSE;
3028 // figure out which challenge we can handle and which authenticator to use.
3029 for (const char *eol = challenges - 1; eol; ) {
3030 const char *p = eol + 1;
3032 // get the challenge string (LF separated -- see nsHttpHeaderArray)
3033 if ((eol = strchr(p, '\n')) != nsnull)
3034 challenge.Assign(p, eol - p);
3035 else
3036 challenge.Assign(p);
3038 rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
3039 if (NS_SUCCEEDED(rv)) {
3041 // if we've already selected an auth type from a previous challenge
3042 // received while processing this channel, then skip others until
3043 // we find a challenge corresponding to the previously tried auth
3044 // type.
3046 if (!currentAuthType->IsEmpty() && authType != *currentAuthType)
3047 continue;
3050 // we allow the routines to run all the way through before we
3051 // decide if they are valid.
3053 // we don't worry about the auth cache being altered because that
3054 // would have been the last step, and if the error is from updating
3055 // the authcache it wasn't really altered anyway. -CTN
3057 // at this point the code is really only useful for client side
3058 // errors (it will not automatically fail over to do a different
3059 // auth type if the server keeps rejecting what is being sent, even
3060 // if a particular auth method only knows 1 thing, like a
3061 // non-identity based authentication method)
3063 rv = GetCredentialsForChallenge(challenge.get(), authType.get(),
3064 proxyAuth, auth, creds);
3065 if (NS_SUCCEEDED(rv)) {
3066 gotCreds = PR_TRUE;
3067 *currentAuthType = authType;
3069 break;
3072 // reset the auth type and continuation state
3073 NS_IF_RELEASE(*currentContinuationState);
3074 currentAuthType->Truncate();
3078 if (!gotCreds && !currentAuthType->IsEmpty()) {
3079 // looks like we never found the auth type we were looking for.
3080 // reset the auth type and continuation state, and try again.
3081 currentAuthType->Truncate();
3082 NS_IF_RELEASE(*currentContinuationState);
3084 rv = GetCredentials(challenges, proxyAuth, creds);
3087 return rv;
3090 nsresult
3091 nsHttpChannel::GetCredentialsForChallenge(const char *challenge,
3092 const char *authType,
3093 PRBool proxyAuth,
3094 nsIHttpAuthenticator *auth,
3095 nsAFlatCString &creds)
3097 LOG(("nsHttpChannel::GetCredentialsForChallenge [this=%x proxyAuth=%d challenges=%s]\n",
3098 this, proxyAuth, challenge));
3100 // this getter never fails
3101 nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
3103 PRUint32 authFlags;
3104 nsresult rv = auth->GetAuthFlags(&authFlags);
3105 if (NS_FAILED(rv)) return rv;
3107 nsCAutoString realm;
3108 ParseRealm(challenge, realm);
3110 // if no realm, then use the auth type as the realm. ToUpperCase so the
3111 // ficticious realm stands out a bit more.
3112 // XXX this will cause some single signon misses!
3113 // XXX this will cause problems when we expose the auth cache to OJI!
3114 // XXX this was meant to be used with NTLM, which supplies no realm.
3116 if (realm.IsEmpty()) {
3117 realm = authType;
3118 ToUpperCase(realm);
3122 // set informations that depend on whether
3123 // we're authenticating against a proxy
3124 // or a webserver
3125 const char *host;
3126 PRInt32 port;
3127 nsHttpAuthIdentity *ident;
3128 nsCAutoString path, scheme;
3129 PRBool identFromURI = PR_FALSE;
3130 nsISupports **continuationState;
3132 if (proxyAuth) {
3133 NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!");
3135 host = mConnectionInfo->ProxyHost();
3136 port = mConnectionInfo->ProxyPort();
3137 ident = &mProxyIdent;
3138 scheme.AssignLiteral("http");
3140 continuationState = &mProxyAuthContinuationState;
3142 else {
3143 host = mConnectionInfo->Host();
3144 port = mConnectionInfo->Port();
3145 ident = &mIdent;
3147 rv = GetCurrentPath(path);
3148 if (NS_FAILED(rv)) return rv;
3150 rv = mURI->GetScheme(scheme);
3151 if (NS_FAILED(rv)) return rv;
3153 // if this is the first challenge, then try using the identity
3154 // specified in the URL.
3155 if (mIdent.IsEmpty()) {
3156 GetIdentityFromURI(authFlags, mIdent);
3157 identFromURI = !mIdent.IsEmpty();
3160 continuationState = &mAuthContinuationState;
3164 // if we already tried some credentials for this transaction, then
3165 // we need to possibly clear them from the cache, unless the credentials
3166 // in the cache have changed, in which case we'd want to give them a
3167 // try instead.
3169 nsHttpAuthEntry *entry = nsnull;
3170 authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry);
3172 // hold reference to the auth session state (in case we clear our
3173 // reference to the entry).
3174 nsCOMPtr<nsISupports> sessionStateGrip;
3175 if (entry)
3176 sessionStateGrip = entry->mMetaData;
3178 // for digest auth, maybe our cached nonce value simply timed out...
3179 PRBool identityInvalid;
3180 nsISupports *sessionState = sessionStateGrip;
3181 rv = auth->ChallengeReceived(this,
3182 challenge,
3183 proxyAuth,
3184 &sessionState,
3185 &*continuationState,
3186 &identityInvalid);
3187 sessionStateGrip.swap(sessionState);
3188 if (NS_FAILED(rv)) return rv;
3190 LOG((" identity invalid = %d\n", identityInvalid));
3192 if (identityInvalid) {
3193 if (entry) {
3194 if (ident->Equals(entry->Identity())) {
3195 LOG((" clearing bad auth cache entry\n"));
3196 // ok, we've already tried this user identity, so clear the
3197 // corresponding entry from the auth cache.
3198 authCache->ClearAuthEntry(scheme.get(), host, port, realm.get());
3199 entry = nsnull;
3200 ident->Clear();
3202 else if (!identFromURI || nsCRT::strcmp(ident->User(), entry->Identity().User()) == 0) {
3203 LOG((" taking identity from auth cache\n"));
3204 // the password from the auth cache is more likely to be
3205 // correct than the one in the URL. at least, we know that it
3206 // works with the given username. it is possible for a server
3207 // to distinguish logons based on the supplied password alone,
3208 // but that would be quite unusual... and i don't think we need
3209 // to worry about such unorthodox cases.
3210 ident->Set(entry->Identity());
3211 identFromURI = PR_FALSE;
3212 if (entry->Creds()[0] != '\0') {
3213 LOG((" using cached credentials!\n"));
3214 creds.Assign(entry->Creds());
3215 return entry->AddPath(path.get());
3219 else if (!identFromURI) {
3220 // hmm... identity invalid, but no auth entry! the realm probably
3221 // changed (see bug 201986).
3222 ident->Clear();
3225 if (!entry && ident->IsEmpty()) {
3226 PRUint32 level = nsIAuthPrompt2::LEVEL_NONE;
3227 if (scheme.EqualsLiteral("https"))
3228 level = nsIAuthPrompt2::LEVEL_SECURE;
3229 else if (authFlags & nsIHttpAuthenticator::IDENTITY_ENCRYPTED)
3230 level = nsIAuthPrompt2::LEVEL_PW_ENCRYPTED;
3232 // at this point we are forced to interact with the user to get
3233 // their username and password for this domain.
3234 rv = PromptForIdentity(level, proxyAuth, realm.get(),
3235 authType, authFlags, *ident);
3236 if (NS_FAILED(rv)) return rv;
3237 identFromURI = PR_FALSE;
3241 if (identFromURI) {
3242 // Warn the user before automatically using the identity from the URL
3243 // to automatically log them into a site (see bug 232567).
3244 if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), PR_FALSE)) {
3245 // calling cancel here sets our mStatus and aborts the HTTP
3246 // transaction, which prevents OnDataAvailable events.
3247 Cancel(NS_ERROR_ABORT);
3248 // this return code alone is not equivalent to Cancel, since
3249 // it only instructs our caller that authentication failed.
3250 // without an explicit call to Cancel, our caller would just
3251 // load the page that accompanies the HTTP auth challenge.
3252 return NS_ERROR_ABORT;
3257 // get credentials for the given user:pass
3259 // always store the credentials we're trying now so that they will be used
3260 // on subsequent links. This will potentially remove good credentials from
3261 // the cache. This is ok as we don't want to use cached credentials if the
3262 // user specified something on the URI or in another manner. This is so
3263 // that we don't transparently authenticate as someone they're not
3264 // expecting to authenticate as.
3266 nsXPIDLCString result;
3267 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port, path.get(),
3268 realm.get(), challenge, *ident, sessionStateGrip,
3269 getter_Copies(result));
3270 if (NS_SUCCEEDED(rv))
3271 creds = result;
3272 return rv;
3275 nsresult
3276 nsHttpChannel::GetAuthenticator(const char *challenge,
3277 nsCString &authType,
3278 nsIHttpAuthenticator **auth)
3280 LOG(("nsHttpChannel::GetAuthenticator [this=%x]\n", this));
3282 const char *p;
3284 // get the challenge type
3285 if ((p = strchr(challenge, ' ')) != nsnull)
3286 authType.Assign(challenge, p - challenge);
3287 else
3288 authType.Assign(challenge);
3290 // normalize to lowercase
3291 ToLowerCase(authType);
3293 nsCAutoString contractid;
3294 contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
3295 contractid.Append(authType);
3297 return CallGetService(contractid.get(), auth);
3300 void
3301 nsHttpChannel::GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity &ident)
3303 LOG(("nsHttpChannel::GetIdentityFromURI [this=%x]\n", this));
3305 nsAutoString userBuf;
3306 nsAutoString passBuf;
3308 // XXX i18n
3309 nsCAutoString buf;
3310 mURI->GetUsername(buf);
3311 if (!buf.IsEmpty()) {
3312 NS_UnescapeURL(buf);
3313 CopyASCIItoUTF16(buf, userBuf);
3314 mURI->GetPassword(buf);
3315 if (!buf.IsEmpty()) {
3316 NS_UnescapeURL(buf);
3317 CopyASCIItoUTF16(buf, passBuf);
3321 if (!userBuf.IsEmpty())
3322 SetIdent(ident, authFlags, (PRUnichar *) userBuf.get(), (PRUnichar *) passBuf.get());
3325 void
3326 nsHttpChannel::ParseRealm(const char *challenge, nsACString &realm)
3329 // From RFC2617 section 1.2, the realm value is defined as such:
3331 // realm = "realm" "=" realm-value
3332 // realm-value = quoted-string
3334 // but, we'll accept anything after the the "=" up to the first space, or
3335 // end-of-line, if the string is not quoted.
3337 const char *p = PL_strcasestr(challenge, "realm=");
3338 if (p) {
3339 PRBool has_quote = PR_FALSE;
3340 p += 6;
3341 if (*p == '"') {
3342 has_quote = PR_TRUE;
3343 p++;
3346 const char *end = p;
3347 while (*end && has_quote) {
3348 // Loop through all the string characters to find the closing
3349 // quote, ignoring escaped quotes.
3350 if (*end == '"' && end[-1] != '\\')
3351 break;
3352 ++end;
3355 if (!has_quote)
3356 end = strchr(p, ' ');
3357 if (end)
3358 realm.Assign(p, end - p);
3359 else
3360 realm.Assign(p);
3365 class nsHTTPAuthInformation : public nsAuthInformationHolder {
3366 public:
3367 nsHTTPAuthInformation(PRUint32 aFlags, const nsString& aRealm,
3368 const nsCString& aAuthType)
3369 : nsAuthInformationHolder(aFlags, aRealm, aAuthType) {}
3371 void SetToHttpAuthIdentity(PRUint32 authFlags, nsHttpAuthIdentity& identity);
3374 void
3375 nsHTTPAuthInformation::SetToHttpAuthIdentity(PRUint32 authFlags, nsHttpAuthIdentity& identity)
3377 identity.Set(Domain().get(), User().get(), Password().get());
3380 nsresult
3381 nsHttpChannel::PromptForIdentity(PRUint32 level,
3382 PRBool proxyAuth,
3383 const char *realm,
3384 const char *authType,
3385 PRUint32 authFlags,
3386 nsHttpAuthIdentity &ident)
3388 LOG(("nsHttpChannel::PromptForIdentity [this=%x]\n", this));
3390 nsCOMPtr<nsIAuthPrompt2> authPrompt;
3391 GetAuthPrompt(mCallbacks, proxyAuth, getter_AddRefs(authPrompt));
3392 if (!authPrompt && mLoadGroup) {
3393 nsCOMPtr<nsIInterfaceRequestor> cbs;
3394 mLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
3395 GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
3397 if (!authPrompt)
3398 return NS_ERROR_NO_INTERFACE;
3400 // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
3401 NS_ConvertASCIItoUTF16 realmU(realm);
3403 nsresult rv;
3405 // prompt the user...
3406 PRUint32 promptFlags = 0;
3407 if (proxyAuth)
3408 promptFlags |= nsIAuthInformation::AUTH_PROXY;
3409 else
3410 promptFlags |= nsIAuthInformation::AUTH_HOST;
3412 if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
3413 promptFlags |= nsIAuthInformation::NEED_DOMAIN;
3415 nsRefPtr<nsHTTPAuthInformation> holder =
3416 new nsHTTPAuthInformation(promptFlags, realmU,
3417 nsDependentCString(authType));
3418 if (!holder)
3419 return NS_ERROR_OUT_OF_MEMORY;
3420 PRBool retval = PR_FALSE;
3421 rv = authPrompt->PromptAuth(this,
3422 level,
3423 holder, &retval);
3424 if (NS_FAILED(rv))
3425 return rv;
3427 // remember that we successfully showed the user an auth dialog
3428 if (!proxyAuth)
3429 mSuppressDefensiveAuth = PR_TRUE;
3431 if (!retval)
3432 rv = NS_ERROR_ABORT;
3433 else
3434 holder->SetToHttpAuthIdentity(authFlags, ident);
3436 return rv;
3439 PRBool
3440 nsHttpChannel::ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt)
3442 // skip prompting the user if
3443 // 1) we've already prompted the user
3444 // 2) we're not a toplevel channel
3445 // 3) the userpass length is less than the "phishy" threshold
3447 if (mSuppressDefensiveAuth || !(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
3448 return PR_TRUE;
3450 nsresult rv;
3451 nsCAutoString userPass;
3452 rv = mURI->GetUserPass(userPass);
3453 if (NS_FAILED(rv) || (userPass.Length() < gHttpHandler->PhishyUserPassLength()))
3454 return PR_TRUE;
3456 // we try to confirm by prompting the user. if we cannot do so, then
3457 // assume the user said ok. this is done to keep things working in
3458 // embedded builds, where the string bundle might not be present, etc.
3460 nsCOMPtr<nsIStringBundleService> bundleService =
3461 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
3462 if (!bundleService)
3463 return PR_TRUE;
3465 nsCOMPtr<nsIStringBundle> bundle;
3466 bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
3467 if (!bundle)
3468 return PR_TRUE;
3470 nsCAutoString host;
3471 rv = mURI->GetHost(host);
3472 if (NS_FAILED(rv))
3473 return PR_TRUE;
3475 nsCAutoString user;
3476 rv = mURI->GetUsername(user);
3477 if (NS_FAILED(rv))
3478 return PR_TRUE;
3480 NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
3481 const PRUnichar *strs[2] = { ucsHost.get(), ucsUser.get() };
3483 nsXPIDLString msg;
3484 bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
3485 if (!msg)
3486 return PR_TRUE;
3488 nsCOMPtr<nsIPrompt> prompt;
3489 GetCallback(prompt);
3490 if (!prompt)
3491 return PR_TRUE;
3493 // do not prompt again
3494 mSuppressDefensiveAuth = PR_TRUE;
3496 PRBool confirmed;
3497 if (doYesNoPrompt) {
3498 PRInt32 choice;
3499 rv = prompt->ConfirmEx(nsnull, msg,
3500 nsIPrompt::BUTTON_POS_1_DEFAULT +
3501 nsIPrompt::STD_YES_NO_BUTTONS,
3502 nsnull, nsnull, nsnull, nsnull, nsnull, &choice);
3503 if (NS_FAILED(rv))
3504 return PR_TRUE;
3506 confirmed = choice == 0;
3508 else {
3509 rv = prompt->Confirm(nsnull, msg, &confirmed);
3510 if (NS_FAILED(rv))
3511 return PR_TRUE;
3514 return confirmed;
3517 void
3518 nsHttpChannel::CheckForSuperfluousAuth()
3520 // we've been called because it has been determined that this channel is
3521 // getting loaded without taking the userpass from the URL. if the URL
3522 // contained a userpass, then (provided some other conditions are true),
3523 // we'll give the user an opportunity to abort the channel as this might be
3524 // an attempt to spoof a different site (see bug 232567).
3525 if (!mAuthRetryPending) {
3526 // ask user...
3527 if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), PR_TRUE)) {
3528 // calling cancel here sets our mStatus and aborts the HTTP
3529 // transaction, which prevents OnDataAvailable events.
3530 Cancel(NS_ERROR_ABORT);
3535 void
3536 nsHttpChannel::SetAuthorizationHeader(nsHttpAuthCache *authCache,
3537 nsHttpAtom header,
3538 const char *scheme,
3539 const char *host,
3540 PRInt32 port,
3541 const char *path,
3542 nsHttpAuthIdentity &ident)
3544 nsHttpAuthEntry *entry = nsnull;
3545 nsresult rv;
3547 // set informations that depend on whether
3548 // we're authenticating against a proxy
3549 // or a webserver
3550 nsISupports **continuationState;
3552 if (header == nsHttp::Proxy_Authorization) {
3553 continuationState = &mProxyAuthContinuationState;
3554 } else {
3555 continuationState = &mAuthContinuationState;
3558 rv = authCache->GetAuthEntryForPath(scheme, host, port, path, &entry);
3559 if (NS_SUCCEEDED(rv)) {
3560 // if we are trying to add a header for origin server auth and if the
3561 // URL contains an explicit username, then try the given username first.
3562 // we only want to do this, however, if we know the URL requires auth
3563 // based on the presence of an auth cache entry for this URL (which is
3564 // true since we are here). but, if the username from the URL matches
3565 // the username from the cache, then we should prefer the password
3566 // stored in the cache since that is most likely to be valid.
3567 if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
3568 GetIdentityFromURI(0, ident);
3569 // if the usernames match, then clear the ident so we will pick
3570 // up the one from the auth cache instead.
3571 if (nsCRT::strcmp(ident.User(), entry->User()) == 0)
3572 ident.Clear();
3574 PRBool identFromURI;
3575 if (ident.IsEmpty()) {
3576 ident.Set(entry->Identity());
3577 identFromURI = PR_FALSE;
3579 else
3580 identFromURI = PR_TRUE;
3582 nsXPIDLCString temp;
3583 const char *creds = entry->Creds();
3584 const char *challenge = entry->Challenge();
3585 // we can only send a preemptive Authorization header if we have either
3586 // stored credentials or a stored challenge from which to derive
3587 // credentials. if the identity is from the URI, then we cannot use
3588 // the stored credentials.
3589 if ((!creds[0] || identFromURI) && challenge[0]) {
3590 nsCOMPtr<nsIHttpAuthenticator> auth;
3591 nsCAutoString unused;
3592 rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth));
3593 if (NS_SUCCEEDED(rv)) {
3594 PRBool proxyAuth = (header == nsHttp::Proxy_Authorization);
3595 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
3596 entry->Realm(), challenge, ident,
3597 entry->mMetaData, getter_Copies(temp));
3598 if (NS_SUCCEEDED(rv))
3599 creds = temp.get();
3601 // make sure the continuation state is null since we do not
3602 // support mixing preemptive and 'multirequest' authentication.
3603 NS_IF_RELEASE(*continuationState);
3606 if (creds[0]) {
3607 LOG((" adding \"%s\" request header\n", header.get()));
3608 mRequestHead.SetHeader(header, nsDependentCString(creds));
3610 // suppress defensive auth prompting for this channel since we know
3611 // that we already prompted at least once this session. we only do
3612 // this for non-proxy auth since the URL's userpass is not used for
3613 // proxy auth.
3614 if (header == nsHttp::Authorization)
3615 mSuppressDefensiveAuth = PR_TRUE;
3617 else
3618 ident.Clear(); // don't remember the identity
3622 void
3623 nsHttpChannel::AddAuthorizationHeaders()
3625 LOG(("nsHttpChannel::AddAuthorizationHeaders? [this=%x]\n", this));
3627 if (mLoadFlags & LOAD_ANONYMOUS) {
3628 return;
3631 // this getter never fails
3632 nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
3634 // check if proxy credentials should be sent
3635 const char *proxyHost = mConnectionInfo->ProxyHost();
3636 if (proxyHost && mConnectionInfo->UsingHttpProxy())
3637 SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
3638 "http", proxyHost, mConnectionInfo->ProxyPort(),
3639 nsnull, // proxy has no path
3640 mProxyIdent);
3642 // check if server credentials should be sent
3643 nsCAutoString path, scheme;
3644 if (NS_SUCCEEDED(GetCurrentPath(path)) &&
3645 NS_SUCCEEDED(mURI->GetScheme(scheme))) {
3646 SetAuthorizationHeader(authCache, nsHttp::Authorization,
3647 scheme.get(),
3648 mConnectionInfo->Host(),
3649 mConnectionInfo->Port(),
3650 path.get(),
3651 mIdent);
3655 nsresult
3656 nsHttpChannel::GetCurrentPath(nsACString &path)
3658 nsresult rv;
3659 nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
3660 if (url)
3661 rv = url->GetDirectory(path);
3662 else
3663 rv = mURI->GetPath(path);
3664 return rv;
3667 //-----------------------------------------------------------------------------
3668 // nsHttpChannel::nsISupports
3669 //-----------------------------------------------------------------------------
3671 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, nsHashPropertyBag)
3672 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, nsHashPropertyBag)
3674 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
3675 NS_INTERFACE_MAP_ENTRY(nsIRequest)
3676 NS_INTERFACE_MAP_ENTRY(nsIChannel)
3677 NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
3678 NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
3679 NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
3680 NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
3681 NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
3682 NS_INTERFACE_MAP_ENTRY(nsICacheListener)
3683 NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
3684 NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
3685 NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
3686 NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
3687 NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
3688 NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
3689 NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
3690 NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
3691 NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
3692 NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
3694 //-----------------------------------------------------------------------------
3695 // nsHttpChannel::nsIRequest
3696 //-----------------------------------------------------------------------------
3698 NS_IMETHODIMP
3699 nsHttpChannel::GetName(nsACString &aName)
3701 aName = mSpec;
3702 return NS_OK;
3705 NS_IMETHODIMP
3706 nsHttpChannel::IsPending(PRBool *value)
3708 NS_ENSURE_ARG_POINTER(value);
3709 *value = mIsPending;
3710 return NS_OK;
3713 NS_IMETHODIMP
3714 nsHttpChannel::GetStatus(nsresult *aStatus)
3716 NS_ENSURE_ARG_POINTER(aStatus);
3717 *aStatus = mStatus;
3718 return NS_OK;
3721 NS_IMETHODIMP
3722 nsHttpChannel::Cancel(nsresult status)
3724 LOG(("nsHttpChannel::Cancel [this=%x status=%x]\n", this, status));
3725 if (mCanceled) {
3726 LOG((" ignoring; already canceled\n"));
3727 return NS_OK;
3729 mCanceled = PR_TRUE;
3730 mStatus = status;
3731 if (mProxyRequest)
3732 mProxyRequest->Cancel(status);
3733 if (mTransaction)
3734 gHttpHandler->CancelTransaction(mTransaction, status);
3735 if (mTransactionPump)
3736 mTransactionPump->Cancel(status);
3737 if (mCachePump)
3738 mCachePump->Cancel(status);
3739 return NS_OK;
3742 NS_IMETHODIMP
3743 nsHttpChannel::Suspend()
3745 NS_ENSURE_TRUE(mIsPending, NS_ERROR_NOT_AVAILABLE);
3747 LOG(("nsHttpChannel::Suspend [this=%x]\n", this));
3749 ++mSuspendCount;
3751 if (mTransactionPump)
3752 return mTransactionPump->Suspend();
3753 if (mCachePump)
3754 return mCachePump->Suspend();
3756 return NS_OK;
3759 NS_IMETHODIMP
3760 nsHttpChannel::Resume()
3762 NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
3764 LOG(("nsHttpChannel::Resume [this=%x]\n", this));
3766 if (--mSuspendCount == 0 && mPendingAsyncCallOnResume) {
3767 nsresult rv = AsyncCall(mPendingAsyncCallOnResume);
3768 mPendingAsyncCallOnResume = nsnull;
3769 NS_ENSURE_SUCCESS(rv, rv);
3772 if (mTransactionPump)
3773 return mTransactionPump->Resume();
3774 if (mCachePump)
3775 return mCachePump->Resume();
3777 return NS_OK;
3780 NS_IMETHODIMP
3781 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
3783 NS_ENSURE_ARG_POINTER(aLoadGroup);
3784 *aLoadGroup = mLoadGroup;
3785 NS_IF_ADDREF(*aLoadGroup);
3786 return NS_OK;
3788 NS_IMETHODIMP
3789 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
3791 mLoadGroup = aLoadGroup;
3792 mProgressSink = nsnull;
3793 return NS_OK;
3796 NS_IMETHODIMP
3797 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
3799 NS_ENSURE_ARG_POINTER(aLoadFlags);
3800 *aLoadFlags = mLoadFlags;
3801 return NS_OK;
3803 NS_IMETHODIMP
3804 nsHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
3806 mLoadFlags = aLoadFlags;
3807 return NS_OK;
3810 //-----------------------------------------------------------------------------
3811 // nsHttpChannel::nsIChannel
3812 //-----------------------------------------------------------------------------
3814 NS_IMETHODIMP
3815 nsHttpChannel::GetOriginalURI(nsIURI **originalURI)
3817 NS_ENSURE_ARG_POINTER(originalURI);
3818 *originalURI = mOriginalURI;
3819 NS_ADDREF(*originalURI);
3820 return NS_OK;
3822 NS_IMETHODIMP
3823 nsHttpChannel::SetOriginalURI(nsIURI *originalURI)
3825 NS_ENSURE_ARG_POINTER(originalURI);
3826 mOriginalURI = originalURI;
3827 return NS_OK;
3830 NS_IMETHODIMP
3831 nsHttpChannel::GetURI(nsIURI **URI)
3833 NS_ENSURE_ARG_POINTER(URI);
3834 *URI = mURI;
3835 NS_IF_ADDREF(*URI);
3836 return NS_OK;
3839 NS_IMETHODIMP
3840 nsHttpChannel::GetOwner(nsISupports **owner)
3842 NS_ENSURE_ARG_POINTER(owner);
3843 *owner = mOwner;
3844 NS_IF_ADDREF(*owner);
3845 return NS_OK;
3847 NS_IMETHODIMP
3848 nsHttpChannel::SetOwner(nsISupports *owner)
3850 mOwner = owner;
3851 return NS_OK;
3854 NS_IMETHODIMP
3855 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
3857 NS_IF_ADDREF(*callbacks = mCallbacks);
3858 return NS_OK;
3860 NS_IMETHODIMP
3861 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *callbacks)
3863 mCallbacks = callbacks;
3864 mProgressSink = nsnull;
3865 return NS_OK;
3868 NS_IMETHODIMP
3869 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
3871 NS_ENSURE_ARG_POINTER(securityInfo);
3872 *securityInfo = mSecurityInfo;
3873 NS_IF_ADDREF(*securityInfo);
3874 return NS_OK;
3877 NS_IMETHODIMP
3878 nsHttpChannel::GetContentType(nsACString &value)
3880 if (!mResponseHead) {
3881 // We got no data, we got no headers, we got nothing
3882 value.Truncate();
3883 return NS_ERROR_NOT_AVAILABLE;
3886 if (!mResponseHead->ContentType().IsEmpty()) {
3887 value = mResponseHead->ContentType();
3888 return NS_OK;
3892 value.AssignLiteral(UNKNOWN_CONTENT_TYPE);
3893 return NS_OK;
3896 NS_IMETHODIMP
3897 nsHttpChannel::SetContentType(const nsACString &value)
3899 if (mListener || mWasOpened) {
3900 if (!mResponseHead)
3901 return NS_ERROR_NOT_AVAILABLE;
3903 nsCAutoString contentTypeBuf, charsetBuf;
3904 PRBool hadCharset;
3905 net_ParseContentType(value, contentTypeBuf, charsetBuf, &hadCharset);
3907 mResponseHead->SetContentType(contentTypeBuf);
3909 // take care not to stomp on an existing charset
3910 if (hadCharset)
3911 mResponseHead->SetContentCharset(charsetBuf);
3912 } else {
3913 // We are being given a content-type hint.
3914 PRBool dummy;
3915 net_ParseContentType(value, mContentTypeHint, mContentCharsetHint,
3916 &dummy);
3919 return NS_OK;
3922 NS_IMETHODIMP
3923 nsHttpChannel::GetContentCharset(nsACString &value)
3925 if (!mResponseHead)
3926 return NS_ERROR_NOT_AVAILABLE;
3928 value = mResponseHead->ContentCharset();
3929 return NS_OK;
3932 NS_IMETHODIMP
3933 nsHttpChannel::SetContentCharset(const nsACString &value)
3935 if (mListener) {
3936 if (!mResponseHead)
3937 return NS_ERROR_NOT_AVAILABLE;
3939 mResponseHead->SetContentCharset(value);
3940 } else {
3941 // Charset hint
3942 mContentCharsetHint = value;
3944 return NS_OK;
3947 NS_IMETHODIMP
3948 nsHttpChannel::GetContentLength(PRInt32 *value)
3950 NS_ENSURE_ARG_POINTER(value);
3952 if (!mResponseHead)
3953 return NS_ERROR_NOT_AVAILABLE;
3955 // XXX truncates to 32 bit
3956 LL_L2I(*value, mResponseHead->ContentLength());
3957 return NS_OK;
3960 NS_IMETHODIMP
3961 nsHttpChannel::SetContentLength(PRInt32 value)
3963 NS_NOTYETIMPLEMENTED("nsHttpChannel::SetContentLength");
3964 return NS_ERROR_NOT_IMPLEMENTED;
3967 NS_IMETHODIMP
3968 nsHttpChannel::Open(nsIInputStream **_retval)
3970 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_IN_PROGRESS);
3971 return NS_ImplementChannelOpen(this, _retval);
3974 NS_IMETHODIMP
3975 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
3977 LOG(("nsHttpChannel::AsyncOpen [this=%x]\n", this));
3979 NS_ENSURE_ARG_POINTER(listener);
3980 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
3981 NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
3983 nsresult rv;
3985 rv = NS_CheckPortSafety(mURI);
3986 if (NS_FAILED(rv))
3987 return rv;
3989 // Remember the cookie header that was set, if any
3990 const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
3991 if (cookieHeader)
3992 mUserSetCookieHeader = cookieHeader;
3994 // fetch cookies, and add them to the request header
3995 AddCookiesToRequest();
3997 // notify "http-on-modify-request" observers
3998 gHttpHandler->OnModifyRequest(this);
4000 // Adjust mCaps according to our request headers:
4001 // - If "Connection: close" is set as a request header, then do not bother
4002 // trying to establish a keep-alive connection.
4003 if (mRequestHead.HasHeaderValue(nsHttp::Connection, "close"))
4004 mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
4006 mIsPending = PR_TRUE;
4007 mWasOpened = PR_TRUE;
4009 mListener = listener;
4010 mListenerContext = context;
4012 // add ourselves to the load group. from this point forward, we'll report
4013 // all failures asynchronously.
4014 if (mLoadGroup)
4015 mLoadGroup->AddRequest(this, nsnull);
4017 // We may have been cancelled already, either by on-modify-request
4018 // listeners or by load group observers; in that case, we should
4019 // not send the request to the server
4020 if (mCanceled)
4021 rv = mStatus;
4022 else
4023 rv = Connect();
4024 if (NS_FAILED(rv)) {
4025 LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv, mCanceled));
4026 CloseCacheEntry(PR_TRUE);
4027 AsyncAbort(rv);
4029 return NS_OK;
4031 //-----------------------------------------------------------------------------
4032 // nsHttpChannel::nsIHttpChannel
4033 //-----------------------------------------------------------------------------
4035 NS_IMETHODIMP
4036 nsHttpChannel::GetRequestMethod(nsACString &method)
4038 method = mRequestHead.Method();
4039 return NS_OK;
4041 NS_IMETHODIMP
4042 nsHttpChannel::SetRequestMethod(const nsACString &method)
4044 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4046 const nsCString &flatMethod = PromiseFlatCString(method);
4048 // Method names are restricted to valid HTTP tokens.
4049 if (!nsHttp::IsValidToken(flatMethod))
4050 return NS_ERROR_INVALID_ARG;
4052 nsHttpAtom atom = nsHttp::ResolveAtom(flatMethod.get());
4053 if (!atom)
4054 return NS_ERROR_FAILURE;
4056 mRequestHead.SetMethod(atom);
4057 return NS_OK;
4060 NS_IMETHODIMP
4061 nsHttpChannel::GetReferrer(nsIURI **referrer)
4063 NS_ENSURE_ARG_POINTER(referrer);
4064 *referrer = mReferrer;
4065 NS_IF_ADDREF(*referrer);
4066 return NS_OK;
4069 NS_IMETHODIMP
4070 nsHttpChannel::SetReferrer(nsIURI *referrer)
4072 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4074 // clear existing referrer, if any
4075 mReferrer = nsnull;
4076 mRequestHead.ClearHeader(nsHttp::Referer);
4078 if (!referrer)
4079 return NS_OK;
4081 // check referrer blocking pref
4082 PRUint32 referrerLevel;
4083 if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)
4084 referrerLevel = 1; // user action
4085 else
4086 referrerLevel = 2; // inline content
4087 if (gHttpHandler->ReferrerLevel() < referrerLevel)
4088 return NS_OK;
4090 nsCOMPtr<nsIURI> referrerGrip;
4091 nsresult rv;
4092 PRBool match;
4095 // Strip off "wyciwyg://123/" from wyciwyg referrers.
4097 // XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
4098 // perhaps some sort of generic nsINestedURI could be used. then, if an URI
4099 // fails the whitelist test, then we could check for an inner URI and try
4100 // that instead. though, that might be too automatic.
4102 rv = referrer->SchemeIs("wyciwyg", &match);
4103 if (NS_FAILED(rv)) return rv;
4104 if (match) {
4105 nsCAutoString path;
4106 rv = referrer->GetPath(path);
4107 if (NS_FAILED(rv)) return rv;
4109 PRUint32 pathLength = path.Length();
4110 if (pathLength <= 2) return NS_ERROR_FAILURE;
4112 // Path is of the form "//123/http://foo/bar", with a variable number of digits.
4113 // To figure out where the "real" URL starts, search path for a '/', starting at
4114 // the third character.
4115 PRInt32 slashIndex = path.FindChar('/', 2);
4116 if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
4118 // Get the charset of the original URI so we can pass it to our fixed up URI.
4119 nsCAutoString charset;
4120 referrer->GetOriginCharset(charset);
4122 // Replace |referrer| with a URI without wyciwyg://123/.
4123 rv = NS_NewURI(getter_AddRefs(referrerGrip),
4124 Substring(path, slashIndex + 1, pathLength - slashIndex - 1),
4125 charset.get());
4126 if (NS_FAILED(rv)) return rv;
4128 referrer = referrerGrip.get();
4132 // block referrer if not on our white list...
4134 static const char *const referrerWhiteList[] = {
4135 "http",
4136 "https",
4137 "ftp",
4138 "gopher",
4139 nsnull
4141 match = PR_FALSE;
4142 const char *const *scheme = referrerWhiteList;
4143 for (; *scheme && !match; ++scheme) {
4144 rv = referrer->SchemeIs(*scheme, &match);
4145 if (NS_FAILED(rv)) return rv;
4147 if (!match)
4148 return NS_OK; // kick out....
4151 // Handle secure referrals.
4153 // Support referrals from a secure server if this is a secure site
4154 // and (optionally) if the host names are the same.
4156 rv = referrer->SchemeIs("https", &match);
4157 if (NS_FAILED(rv)) return rv;
4158 if (match) {
4159 rv = mURI->SchemeIs("https", &match);
4160 if (NS_FAILED(rv)) return rv;
4161 if (!match)
4162 return NS_OK;
4164 if (!gHttpHandler->SendSecureXSiteReferrer()) {
4165 nsCAutoString referrerHost;
4166 nsCAutoString host;
4168 rv = referrer->GetAsciiHost(referrerHost);
4169 if (NS_FAILED(rv)) return rv;
4171 rv = mURI->GetAsciiHost(host);
4172 if (NS_FAILED(rv)) return rv;
4174 // GetAsciiHost returns lowercase hostname.
4175 if (!referrerHost.Equals(host))
4176 return NS_OK;
4180 nsCOMPtr<nsIURI> clone;
4182 // we need to clone the referrer, so we can:
4183 // (1) modify it
4184 // (2) keep a reference to it after returning from this function
4186 rv = referrer->Clone(getter_AddRefs(clone));
4187 if (NS_FAILED(rv)) return rv;
4189 // strip away any userpass; we don't want to be giving out passwords ;-)
4190 clone->SetUserPass(EmptyCString());
4192 // strip away any fragment per RFC 2616 section 14.36
4193 nsCOMPtr<nsIURL> url = do_QueryInterface(clone);
4194 if (url)
4195 url->SetRef(EmptyCString());
4197 nsCAutoString spec;
4198 rv = clone->GetAsciiSpec(spec);
4199 if (NS_FAILED(rv)) return rv;
4201 // finally, remember the referrer URI and set the Referer header.
4202 mReferrer = clone;
4203 mRequestHead.SetHeader(nsHttp::Referer, spec);
4204 return NS_OK;
4207 NS_IMETHODIMP
4208 nsHttpChannel::GetRequestHeader(const nsACString &header, nsACString &value)
4210 // XXX might be better to search the header list directly instead of
4211 // hitting the http atom hash table.
4213 nsHttpAtom atom = nsHttp::ResolveAtom(header);
4214 if (!atom)
4215 return NS_ERROR_NOT_AVAILABLE;
4217 return mRequestHead.GetHeader(atom, value);
4220 NS_IMETHODIMP
4221 nsHttpChannel::SetRequestHeader(const nsACString &header,
4222 const nsACString &value,
4223 PRBool merge)
4225 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4227 const nsCString &flatHeader = PromiseFlatCString(header);
4228 const nsCString &flatValue = PromiseFlatCString(value);
4230 LOG(("nsHttpChannel::SetRequestHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
4231 this, flatHeader.get(), flatValue.get(), merge));
4233 // Header names are restricted to valid HTTP tokens.
4234 if (!nsHttp::IsValidToken(flatHeader))
4235 return NS_ERROR_INVALID_ARG;
4237 // Header values MUST NOT contain line-breaks. RFC 2616 technically
4238 // permits CTL characters, including CR and LF, in header values provided
4239 // they are quoted. However, this can lead to problems if servers do not
4240 // interpret quoted strings properly. Disallowing CR and LF here seems
4241 // reasonable and keeps things simple. We also disallow a null byte.
4242 if (flatValue.FindCharInSet("\r\n") != kNotFound ||
4243 flatValue.Length() != strlen(flatValue.get()))
4244 return NS_ERROR_INVALID_ARG;
4246 nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
4247 if (!atom) {
4248 NS_WARNING("failed to resolve atom");
4249 return NS_ERROR_NOT_AVAILABLE;
4252 return mRequestHead.SetHeader(atom, flatValue, merge);
4255 NS_IMETHODIMP
4256 nsHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor)
4258 return mRequestHead.Headers().VisitHeaders(visitor);
4261 NS_IMETHODIMP
4262 nsHttpChannel::GetUploadStream(nsIInputStream **stream)
4264 NS_ENSURE_ARG_POINTER(stream);
4265 *stream = mUploadStream;
4266 NS_IF_ADDREF(*stream);
4267 return NS_OK;
4270 NS_IMETHODIMP
4271 nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &contentType, PRInt32 contentLength)
4273 // NOTE: for backwards compatibility and for compatibility with old style
4274 // plugins, |stream| may include headers, specifically Content-Type and
4275 // Content-Length headers. in this case, |contentType| and |contentLength|
4276 // would be unspecified. this is traditionally the case of a POST request,
4277 // and so we select POST as the request method if contentType and
4278 // contentLength are unspecified.
4280 if (stream) {
4281 if (!contentType.IsEmpty()) {
4282 if (contentLength < 0) {
4283 stream->Available((PRUint32 *) &contentLength);
4284 if (contentLength < 0) {
4285 NS_ERROR("unable to determine content length");
4286 return NS_ERROR_FAILURE;
4289 mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", contentLength));
4290 mRequestHead.SetHeader(nsHttp::Content_Type, contentType);
4291 mUploadStreamHasHeaders = PR_FALSE;
4292 mRequestHead.SetMethod(nsHttp::Put); // PUT request
4294 else {
4295 mUploadStreamHasHeaders = PR_TRUE;
4296 mRequestHead.SetMethod(nsHttp::Post); // POST request
4299 else {
4300 mUploadStreamHasHeaders = PR_FALSE;
4301 mRequestHead.SetMethod(nsHttp::Get); // revert to GET request
4303 mUploadStream = stream;
4304 return NS_OK;
4307 NS_IMETHODIMP
4308 nsHttpChannel::GetResponseStatus(PRUint32 *value)
4310 NS_ENSURE_ARG_POINTER(value);
4311 if (!mResponseHead)
4312 return NS_ERROR_NOT_AVAILABLE;
4313 *value = mResponseHead->Status();
4314 return NS_OK;
4317 NS_IMETHODIMP
4318 nsHttpChannel::GetResponseStatusText(nsACString &value)
4320 if (!mResponseHead)
4321 return NS_ERROR_NOT_AVAILABLE;
4322 value = mResponseHead->StatusText();
4323 return NS_OK;
4326 NS_IMETHODIMP
4327 nsHttpChannel::GetRequestSucceeded(PRBool *value)
4329 NS_PRECONDITION(value, "Don't ever pass a null arg to this function");
4330 if (!mResponseHead)
4331 return NS_ERROR_NOT_AVAILABLE;
4332 PRUint32 status = mResponseHead->Status();
4333 *value = (status / 100 == 2);
4334 return NS_OK;
4337 NS_IMETHODIMP
4338 nsHttpChannel::GetResponseHeader(const nsACString &header, nsACString &value)
4340 if (!mResponseHead)
4341 return NS_ERROR_NOT_AVAILABLE;
4342 nsHttpAtom atom = nsHttp::ResolveAtom(header);
4343 if (!atom)
4344 return NS_ERROR_NOT_AVAILABLE;
4345 return mResponseHead->GetHeader(atom, value);
4348 NS_IMETHODIMP
4349 nsHttpChannel::SetResponseHeader(const nsACString &header,
4350 const nsACString &value,
4351 PRBool merge)
4353 LOG(("nsHttpChannel::SetResponseHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
4354 this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(), merge));
4356 if (!mResponseHead)
4357 return NS_ERROR_NOT_AVAILABLE;
4358 nsHttpAtom atom = nsHttp::ResolveAtom(header);
4359 if (!atom)
4360 return NS_ERROR_NOT_AVAILABLE;
4362 // these response headers must not be changed
4363 if (atom == nsHttp::Content_Type ||
4364 atom == nsHttp::Content_Length ||
4365 atom == nsHttp::Content_Encoding ||
4366 atom == nsHttp::Trailer ||
4367 atom == nsHttp::Transfer_Encoding)
4368 return NS_ERROR_ILLEGAL_VALUE;
4370 mResponseHeadersModified = PR_TRUE;
4372 return mResponseHead->SetHeader(atom, value, merge);
4375 NS_IMETHODIMP
4376 nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
4378 if (!mResponseHead)
4379 return NS_ERROR_NOT_AVAILABLE;
4380 return mResponseHead->Headers().VisitHeaders(visitor);
4383 NS_IMETHODIMP
4384 nsHttpChannel::IsNoStoreResponse(PRBool *value)
4386 if (!mResponseHead)
4387 return NS_ERROR_NOT_AVAILABLE;
4388 *value = mResponseHead->NoStore();
4389 return NS_OK;
4392 NS_IMETHODIMP
4393 nsHttpChannel::IsNoCacheResponse(PRBool *value)
4395 if (!mResponseHead)
4396 return NS_ERROR_NOT_AVAILABLE;
4397 *value = mResponseHead->NoCache();
4398 if (!*value)
4399 *value = mResponseHead->ExpiresInPast();
4400 return NS_OK;
4403 NS_IMETHODIMP
4404 nsHttpChannel::GetApplyConversion(PRBool *value)
4406 NS_ENSURE_ARG_POINTER(value);
4407 *value = mApplyConversion;
4408 return NS_OK;
4411 NS_IMETHODIMP
4412 nsHttpChannel::SetApplyConversion(PRBool value)
4414 LOG(("nsHttpChannel::SetApplyConversion [this=%x value=%d]\n", this, value));
4415 mApplyConversion = value;
4416 return NS_OK;
4419 NS_IMETHODIMP
4420 nsHttpChannel::GetAllowPipelining(PRBool *value)
4422 NS_ENSURE_ARG_POINTER(value);
4423 *value = mAllowPipelining;
4424 return NS_OK;
4427 NS_IMETHODIMP
4428 nsHttpChannel::SetAllowPipelining(PRBool value)
4430 if (mIsPending)
4431 return NS_ERROR_FAILURE;
4432 mAllowPipelining = value;
4433 return NS_OK;
4436 NS_IMETHODIMP
4437 nsHttpChannel::GetRedirectionLimit(PRUint32 *value)
4439 NS_ENSURE_ARG_POINTER(value);
4440 *value = PRUint32(mRedirectionLimit);
4441 return NS_OK;
4444 NS_IMETHODIMP
4445 nsHttpChannel::SetRedirectionLimit(PRUint32 value)
4447 mRedirectionLimit = PR_MIN(value, 0xff);
4448 return NS_OK;
4451 NS_IMETHODIMP
4452 nsHttpChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings)
4454 NS_PRECONDITION(aEncodings, "Null out param");
4455 if (!mResponseHead) {
4456 *aEncodings = nsnull;
4457 return NS_OK;
4460 const char *encoding = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
4461 if (!encoding) {
4462 *aEncodings = nsnull;
4463 return NS_OK;
4465 nsContentEncodings* enumerator = new nsContentEncodings(this, encoding);
4466 if (!enumerator)
4467 return NS_ERROR_OUT_OF_MEMORY;
4469 NS_ADDREF(*aEncodings = enumerator);
4470 return NS_OK;
4473 //-----------------------------------------------------------------------------
4474 // nsHttpChannel::nsIHttpChannelInternal
4475 //-----------------------------------------------------------------------------
4477 NS_IMETHODIMP
4478 nsHttpChannel::GetDocumentURI(nsIURI **aDocumentURI)
4480 NS_ENSURE_ARG_POINTER(aDocumentURI);
4481 *aDocumentURI = mDocumentURI;
4482 NS_IF_ADDREF(*aDocumentURI);
4483 return NS_OK;
4486 NS_IMETHODIMP
4487 nsHttpChannel::SetDocumentURI(nsIURI *aDocumentURI)
4489 mDocumentURI = aDocumentURI;
4490 return NS_OK;
4493 NS_IMETHODIMP
4494 nsHttpChannel::GetRequestVersion(PRUint32 *major, PRUint32 *minor)
4496 int version = mRequestHead.Version();
4498 if (major) { *major = version / 10; }
4499 if (minor) { *minor = version % 10; }
4501 return NS_OK;
4504 NS_IMETHODIMP
4505 nsHttpChannel::GetResponseVersion(PRUint32 *major, PRUint32 *minor)
4507 if (!mResponseHead)
4509 *major = *minor = 0; // we should at least be kind about it
4510 return NS_ERROR_NOT_AVAILABLE;
4513 int version = mResponseHead->Version();
4515 if (major) { *major = version / 10; }
4516 if (minor) { *minor = version % 10; }
4518 return NS_OK;
4521 NS_IMETHODIMP
4522 nsHttpChannel::SetCookie(const char *aCookieHeader)
4524 if (mLoadFlags & LOAD_ANONYMOUS) {
4525 return NS_OK;
4528 // empty header isn't an error
4529 if (!(aCookieHeader && *aCookieHeader))
4530 return NS_OK;
4532 nsICookieService *cs = gHttpHandler->GetCookieService();
4533 NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
4535 nsCOMPtr<nsIPrompt> prompt;
4536 GetCallback(prompt);
4538 return cs->SetCookieStringFromHttp(mURI,
4539 mDocumentURI ? mDocumentURI : mOriginalURI,
4540 prompt,
4541 aCookieHeader,
4542 mResponseHead->PeekHeader(nsHttp::Date),
4543 this);
4546 NS_IMETHODIMP
4547 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey)
4549 LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
4550 this, aFallbackKey));
4551 mFallbackChannel = PR_TRUE;
4552 mFallbackKey = aFallbackKey;
4554 return NS_OK;
4557 //-----------------------------------------------------------------------------
4558 // nsHttpChannel::nsISupportsPriority
4559 //-----------------------------------------------------------------------------
4561 NS_IMETHODIMP
4562 nsHttpChannel::GetPriority(PRInt32 *value)
4564 *value = mPriority;
4565 return NS_OK;
4568 NS_IMETHODIMP
4569 nsHttpChannel::SetPriority(PRInt32 value)
4571 PRInt16 newValue = CLAMP(value, PR_INT16_MIN, PR_INT16_MAX);
4572 if (mPriority == newValue)
4573 return NS_OK;
4574 mPriority = newValue;
4575 if (mTransaction)
4576 gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
4577 return NS_OK;
4580 NS_IMETHODIMP
4581 nsHttpChannel::AdjustPriority(PRInt32 delta)
4583 return SetPriority(mPriority + delta);
4586 //-----------------------------------------------------------------------------
4587 // nsHttpChannel::nsIProtocolProxyCallback
4588 //-----------------------------------------------------------------------------
4590 NS_IMETHODIMP
4591 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIURI *uri,
4592 nsIProxyInfo *pi, nsresult status)
4594 mProxyRequest = nsnull;
4596 // If status is a failure code, then it means that we failed to resolve
4597 // proxy info. That is a non-fatal error assuming it wasn't because the
4598 // request was canceled. We just failover to DIRECT when proxy resolution
4599 // fails (failure can mean that the PAC URL could not be loaded).
4601 // Need to replace this channel with a new one. It would be complex to try
4602 // to change the value of mConnectionInfo since so much of our state may
4603 // depend on its state.
4604 mTargetProxyInfo = pi;
4605 HandleAsyncReplaceWithProxy();
4606 return NS_OK;
4609 //-----------------------------------------------------------------------------
4610 // nsHttpChannel::nsIProxiedChannel
4611 //-----------------------------------------------------------------------------
4613 NS_IMETHODIMP
4614 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
4616 if (!mConnectionInfo)
4617 *result = nsnull;
4618 else {
4619 *result = mConnectionInfo->ProxyInfo();
4620 NS_IF_ADDREF(*result);
4622 return NS_OK;
4625 //-----------------------------------------------------------------------------
4626 // nsHttpChannel::nsIRequestObserver
4627 //-----------------------------------------------------------------------------
4629 NS_IMETHODIMP
4630 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
4632 if (!(mCanceled || NS_FAILED(mStatus))) {
4633 // capture the request's status, so our consumers will know ASAP of any
4634 // connection failures, etc - bug 93581
4635 request->GetStatus(&mStatus);
4638 LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x status=%x]\n",
4639 this, request, mStatus));
4641 // Make sure things are what we expect them to be...
4642 NS_ASSERTION(request == mCachePump || request == mTransactionPump,
4643 "Unexpected request");
4644 NS_ASSERTION(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
4645 "If we have both pumps, the cache content must be partial");
4647 if (!mSecurityInfo && !mCachePump && mTransaction) {
4648 // grab the security info from the connection object; the transaction
4649 // is guaranteed to own a reference to the connection.
4650 mSecurityInfo = mTransaction->SecurityInfo();
4653 // don't enter this block if we're reading from the cache...
4654 if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
4655 NS_ASSERTION(mResponseHead == nsnull, "leaking mResponseHead");
4657 // all of the response headers have been acquired, so we can take ownership
4658 // of them from the transaction.
4659 mResponseHead = mTransaction->TakeResponseHead();
4660 // the response head may be null if the transaction was cancelled. in
4661 // which case we just need to call OnStartRequest/OnStopRequest.
4662 if (mResponseHead)
4663 return ProcessResponse();
4665 NS_WARNING("No response head in OnStartRequest");
4668 // avoid crashing if mListener happens to be null...
4669 if (!mListener) {
4670 NS_NOTREACHED("mListener is null");
4671 return NS_OK;
4674 // on proxy errors, try to failover
4675 if (mConnectionInfo->ProxyInfo() &&
4676 (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
4677 mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
4678 mStatus == NS_ERROR_NET_TIMEOUT)) {
4679 if (NS_SUCCEEDED(ProxyFailover()))
4680 return NS_OK;
4683 // on other request errors, try to fall back
4684 PRBool fallingBack;
4685 if (NS_FAILED(mStatus) &&
4686 NS_SUCCEEDED(ProcessFallback(&fallingBack)) &&
4687 fallingBack) {
4689 return NS_OK;
4692 return CallOnStartRequest();
4695 NS_IMETHODIMP
4696 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
4698 LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
4699 this, request, status));
4701 // honor the cancelation status even if the underlying transaction completed.
4702 if (mCanceled || NS_FAILED(mStatus))
4703 status = mStatus;
4705 if (mCachedContentIsPartial) {
4706 if (NS_SUCCEEDED(status)) {
4707 // mTransactionPump should be suspended
4708 NS_ASSERTION(request != mTransactionPump,
4709 "byte-range transaction finished prematurely");
4711 if (request == mCachePump) {
4712 PRBool streamDone;
4713 status = OnDoneReadingPartialCacheEntry(&streamDone);
4714 if (NS_SUCCEEDED(status) && !streamDone)
4715 return status;
4716 // otherwise, fall through and fire OnStopRequest...
4718 else
4719 NS_NOTREACHED("unexpected request");
4721 // Do not to leave the transaction in a suspended state in error cases.
4722 if (NS_FAILED(status) && mTransaction)
4723 gHttpHandler->CancelTransaction(mTransaction, status);
4726 if (mTransaction) {
4727 // determine if we should call DoAuthRetry
4728 PRBool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
4731 // grab reference to connection in case we need to retry an
4732 // authentication request over it. this applies to connection based
4733 // authentication schemes only. for request based schemes, conn is not
4734 // needed, so it may be null.
4736 // this code relies on the code in nsHttpTransaction::Close, which
4737 // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
4738 // keep the connection around after the transaction is finished.
4740 nsRefPtr<nsAHttpConnection> conn;
4741 if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION))
4742 conn = mTransaction->Connection();
4744 // at this point, we're done with the transaction
4745 NS_RELEASE(mTransaction);
4746 mTransactionPump = 0;
4748 // handle auth retry...
4749 if (authRetry) {
4750 mAuthRetryPending = PR_FALSE;
4751 status = DoAuthRetry(conn);
4752 if (NS_SUCCEEDED(status))
4753 return NS_OK;
4756 // If DoAuthRetry failed, or if we have been cancelled since showing
4757 // the auth. dialog, then we need to send OnStartRequest now
4758 if (authRetry || (mAuthRetryPending && NS_FAILED(status))) {
4759 NS_ASSERTION(NS_FAILED(status), "should have a failure code here");
4760 // NOTE: since we have a failure status, we can ignore the return
4761 // value from onStartRequest.
4762 mListener->OnStartRequest(this, mListenerContext);
4765 // if this transaction has been replaced, then bail.
4766 if (mTransactionReplaced)
4767 return NS_OK;
4770 mIsPending = PR_FALSE;
4771 mStatus = status;
4773 // perform any final cache operations before we close the cache entry.
4774 if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE))
4775 FinalizeCacheEntry();
4777 if (mListener) {
4778 LOG((" calling OnStopRequest\n"));
4779 mListener->OnStopRequest(this, mListenerContext, status);
4780 mListener = 0;
4781 mListenerContext = 0;
4784 if (mCacheEntry)
4785 CloseCacheEntry(PR_TRUE);
4787 if (mOfflineCacheEntry)
4788 CloseOfflineCacheEntry();
4790 if (mLoadGroup)
4791 mLoadGroup->RemoveRequest(this, nsnull, status);
4793 mCallbacks = nsnull;
4794 mProgressSink = nsnull;
4796 return NS_OK;
4799 //-----------------------------------------------------------------------------
4800 // nsHttpChannel::nsIStreamListener
4801 //-----------------------------------------------------------------------------
4803 NS_IMETHODIMP
4804 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
4805 nsIInputStream *input,
4806 PRUint32 offset, PRUint32 count)
4808 LOG(("nsHttpChannel::OnDataAvailable [this=%x request=%x offset=%u count=%u]\n",
4809 this, request, offset, count));
4811 // don't send out OnDataAvailable notifications if we've been canceled.
4812 if (mCanceled)
4813 return mStatus;
4815 NS_ASSERTION(mResponseHead, "No response head in ODA!!");
4817 NS_ASSERTION(!(mCachedContentIsPartial && (request == mTransactionPump)),
4818 "transaction pump not suspended");
4820 if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
4821 PRUint32 n;
4822 return input->ReadSegments(NS_DiscardSegment, nsnull, count, &n);
4825 if (mListener) {
4827 // synthesize transport progress event. we do this here since we want
4828 // to delay OnProgress events until we start streaming data. this is
4829 // crucially important since it impacts the lock icon (see bug 240053).
4831 nsresult transportStatus;
4832 if (request == mCachePump)
4833 transportStatus = nsITransport::STATUS_READING;
4834 else
4835 transportStatus = nsISocketTransport::STATUS_RECEIVING_FROM;
4837 // mResponseHead may reference new or cached headers, but either way it
4838 // holds our best estimate of the total content length. Even in the case
4839 // of a byte range request, the content length stored in the cached
4840 // response headers is what we want to use here.
4842 nsUint64 progressMax(PRUint64(mResponseHead->ContentLength()));
4843 nsUint64 progress = mLogicalOffset + nsUint64(count);
4844 NS_ASSERTION(progress <= progressMax, "unexpected progress values");
4846 OnTransportStatus(nsnull, transportStatus, progress, progressMax);
4849 // we have to manually keep the logical offset of the stream up-to-date.
4850 // we cannot depend solely on the offset provided, since we may have
4851 // already streamed some data from another source (see, for example,
4852 // OnDoneReadingPartialCacheEntry).
4854 nsresult rv = mListener->OnDataAvailable(this,
4855 mListenerContext,
4856 input,
4857 mLogicalOffset,
4858 count);
4859 if (NS_SUCCEEDED(rv))
4860 mLogicalOffset = progress;
4861 return rv;
4864 return NS_ERROR_ABORT;
4867 //-----------------------------------------------------------------------------
4868 // nsHttpChannel::nsITransportEventSink
4869 //-----------------------------------------------------------------------------
4871 NS_IMETHODIMP
4872 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
4873 PRUint64 progress, PRUint64 progressMax)
4875 // cache the progress sink so we don't have to query for it each time.
4876 if (!mProgressSink)
4877 GetCallback(mProgressSink);
4879 // block socket status event after Cancel or OnStopRequest has been called.
4880 if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
4881 LOG(("sending status notification [this=%x status=%x progress=%llu/%llu]\n",
4882 this, status, progress, progressMax));
4884 nsCAutoString host;
4885 mURI->GetHost(host);
4886 mProgressSink->OnStatus(this, nsnull, status,
4887 NS_ConvertUTF8toUTF16(host).get());
4889 if (progress > 0) {
4890 NS_ASSERTION(progress <= progressMax, "unexpected progress values");
4891 mProgressSink->OnProgress(this, nsnull, progress, progressMax);
4894 #ifdef DEBUG
4895 else
4896 LOG(("skipping status notification [this=%x sink=%x pending=%u background=%x]\n",
4897 this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
4898 #endif
4900 return NS_OK;
4903 //-----------------------------------------------------------------------------
4904 // nsHttpChannel::nsICachingChannel
4905 //-----------------------------------------------------------------------------
4907 NS_IMETHODIMP
4908 nsHttpChannel::GetCacheToken(nsISupports **token)
4910 NS_ENSURE_ARG_POINTER(token);
4911 if (!mCacheEntry)
4912 return NS_ERROR_NOT_AVAILABLE;
4913 return CallQueryInterface(mCacheEntry, token);
4916 NS_IMETHODIMP
4917 nsHttpChannel::SetCacheToken(nsISupports *token)
4919 return NS_ERROR_NOT_IMPLEMENTED;
4922 NS_IMETHODIMP
4923 nsHttpChannel::GetOfflineCacheToken(nsISupports **token)
4925 NS_ENSURE_ARG_POINTER(token);
4926 if (!mOfflineCacheEntry)
4927 return NS_ERROR_NOT_AVAILABLE;
4928 return CallQueryInterface(mOfflineCacheEntry, token);
4931 NS_IMETHODIMP
4932 nsHttpChannel::SetOfflineCacheToken(nsISupports *token)
4934 return NS_ERROR_NOT_IMPLEMENTED;
4937 NS_IMETHODIMP
4938 nsHttpChannel::GetCacheKey(nsISupports **key)
4940 nsresult rv;
4941 NS_ENSURE_ARG_POINTER(key);
4943 LOG(("nsHttpChannel::GetCacheKey [this=%x]\n", this));
4945 *key = nsnull;
4947 nsCOMPtr<nsISupportsPRUint32> container =
4948 do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
4949 if (NS_FAILED(rv)) return rv;
4951 rv = container->SetData(mPostID);
4952 if (NS_FAILED(rv)) return rv;
4954 return CallQueryInterface(container, key);
4957 NS_IMETHODIMP
4958 nsHttpChannel::SetCacheKey(nsISupports *key)
4960 nsresult rv;
4962 LOG(("nsHttpChannel::SetCacheKey [this=%x key=%x]\n", this, key));
4964 // can only set the cache key if a load is not in progress
4965 NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
4967 if (!key)
4968 mPostID = 0;
4969 else {
4970 // extract the post id
4971 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
4972 if (NS_FAILED(rv)) return rv;
4974 rv = container->GetData(&mPostID);
4975 if (NS_FAILED(rv)) return rv;
4977 return NS_OK;
4980 NS_IMETHODIMP
4981 nsHttpChannel::GetCacheAsFile(PRBool *value)
4983 NS_ENSURE_ARG_POINTER(value);
4984 if (!mCacheEntry)
4985 return NS_ERROR_NOT_AVAILABLE;
4986 nsCacheStoragePolicy storagePolicy;
4987 mCacheEntry->GetStoragePolicy(&storagePolicy);
4988 *value = (storagePolicy == nsICache::STORE_ON_DISK_AS_FILE);
4989 return NS_OK;
4992 NS_IMETHODIMP
4993 nsHttpChannel::SetCacheAsFile(PRBool value)
4995 if (!mCacheEntry || mLoadFlags & INHIBIT_PERSISTENT_CACHING)
4996 return NS_ERROR_NOT_AVAILABLE;
4997 nsCacheStoragePolicy policy;
4998 if (value)
4999 policy = nsICache::STORE_ON_DISK_AS_FILE;
5000 else
5001 policy = nsICache::STORE_ANYWHERE;
5002 return mCacheEntry->SetStoragePolicy(policy);
5006 NS_IMETHODIMP
5007 nsHttpChannel::GetCacheForOfflineUse(PRBool *value)
5009 *value = mCacheForOfflineUse;
5011 return NS_OK;
5014 NS_IMETHODIMP
5015 nsHttpChannel::SetCacheForOfflineUse(PRBool value)
5017 mCacheForOfflineUse = value;
5019 return NS_OK;
5022 NS_IMETHODIMP
5023 nsHttpChannel::GetOfflineCacheClientID(nsACString &value)
5025 value = mOfflineCacheClientID;
5027 return NS_OK;
5030 NS_IMETHODIMP
5031 nsHttpChannel::SetOfflineCacheClientID(const nsACString &value)
5033 mOfflineCacheClientID = value;
5035 return NS_OK;
5038 NS_IMETHODIMP
5039 nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
5041 if (!mCacheEntry)
5042 return NS_ERROR_NOT_AVAILABLE;
5043 return mCacheEntry->GetFile(cacheFile);
5046 NS_IMETHODIMP
5047 nsHttpChannel::IsFromCache(PRBool *value)
5049 if (!mIsPending)
5050 return NS_ERROR_NOT_AVAILABLE;
5052 // return false if reading a partial cache entry; the data isn't entirely
5053 // from the cache!
5055 *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
5056 mCachedContentIsValid && !mCachedContentIsPartial;
5058 return NS_OK;
5061 //-----------------------------------------------------------------------------
5062 // nsHttpChannel::nsIResumableChannel
5063 //-----------------------------------------------------------------------------
5065 NS_IMETHODIMP
5066 nsHttpChannel::ResumeAt(PRUint64 aStartPos,
5067 const nsACString& aEntityID)
5069 LOG(("nsHttpChannel::ResumeAt [this=%p startPos=%llu id='%s']\n",
5070 this, aStartPos, PromiseFlatCString(aEntityID).get()));
5071 mEntityID = aEntityID;
5072 mStartPos = aStartPos;
5073 mResuming = PR_TRUE;
5074 return NS_OK;
5077 NS_IMETHODIMP
5078 nsHttpChannel::GetEntityID(nsACString& aEntityID)
5080 // Don't return an entity ID for Non-GET requests which require
5081 // additional data
5082 if (mRequestHead.Method() != nsHttp::Get) {
5083 return NS_ERROR_NOT_RESUMABLE;
5086 PRUint64 size = LL_MAXUINT;
5087 nsCAutoString etag, lastmod;
5088 if (mResponseHead) {
5089 size = mResponseHead->TotalEntitySize();
5090 const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
5091 if (cLastMod)
5092 lastmod = cLastMod;
5093 const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
5094 if (cEtag)
5095 etag = cEtag;
5097 nsCString entityID;
5098 NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
5099 esc_FileBaseName | esc_Forced, entityID);
5100 entityID.Append('/');
5101 entityID.AppendInt(PRInt64(size));
5102 entityID.Append('/');
5103 entityID.Append(lastmod);
5104 // NOTE: Appending lastmod as the last part avoids having to escape it
5106 aEntityID = entityID;
5108 return NS_OK;
5111 //-----------------------------------------------------------------------------
5112 // nsHttpChannel::nsICacheListener
5113 //-----------------------------------------------------------------------------
5115 NS_IMETHODIMP
5116 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
5117 nsCacheAccessMode access,
5118 nsresult status)
5120 LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%x entry=%x "
5121 "access=%x status=%x]\n", this, entry, access, status));
5123 // if the channel's already fired onStopRequest, then we should ignore
5124 // this event.
5125 if (!mIsPending)
5126 return NS_OK;
5128 // otherwise, we have to handle this event.
5129 if (NS_SUCCEEDED(status)) {
5130 mCacheEntry = entry;
5131 mCacheAccess = access;
5134 nsresult rv;
5136 if (mCanceled && NS_FAILED(mStatus)) {
5137 LOG(("channel was canceled [this=%x status=%x]\n", this, mStatus));
5138 rv = mStatus;
5140 else if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(status))
5141 // if this channel is only allowed to pull from the cache, then
5142 // we must fail if we were unable to open a cache entry.
5143 rv = NS_ERROR_DOCUMENT_NOT_CACHED;
5144 else
5145 // advance to the next state...
5146 rv = Connect(PR_FALSE);
5148 // a failure from Connect means that we have to abort the channel.
5149 if (NS_FAILED(rv)) {
5150 CloseCacheEntry(PR_TRUE);
5151 AsyncAbort(rv);
5154 return NS_OK;
5157 nsresult
5158 nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
5160 LOG(("nsHttpChannel::DoAuthRetry [this=%x]\n", this));
5162 NS_ASSERTION(!mTransaction, "should not have a transaction");
5163 nsresult rv;
5165 // toggle mIsPending to allow nsIObserver implementations to modify
5166 // the request headers (bug 95044).
5167 mIsPending = PR_FALSE;
5169 // fetch cookies, and add them to the request header.
5170 // the server response could have included cookies that must be sent with
5171 // this authentication attempt (bug 84794).
5172 AddCookiesToRequest();
5174 // notify "http-on-modify-request" observers
5175 gHttpHandler->OnModifyRequest(this);
5177 mIsPending = PR_TRUE;
5179 // get rid of the old response headers
5180 delete mResponseHead;
5181 mResponseHead = nsnull;
5183 // set sticky connection flag and disable pipelining.
5184 mCaps |= NS_HTTP_STICKY_CONNECTION;
5185 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
5187 // and create a new one...
5188 rv = SetupTransaction();
5189 if (NS_FAILED(rv)) return rv;
5191 // transfer ownership of connection to transaction
5192 if (conn)
5193 mTransaction->SetConnection(conn);
5195 // rewind the upload stream
5196 if (mUploadStream) {
5197 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
5198 if (seekable)
5199 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
5202 rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
5203 if (NS_FAILED(rv)) return rv;
5205 return mTransactionPump->AsyncRead(this, nsnull);
5208 //-----------------------------------------------------------------------------
5209 // nsHttpChannel::nsIApplicationCacheContainer
5210 //-----------------------------------------------------------------------------
5211 NS_IMETHODIMP
5212 nsHttpChannel::GetApplicationCache(nsIApplicationCache **out)
5214 NS_IF_ADDREF(*out = mApplicationCache);
5215 return NS_OK;
5218 NS_IMETHODIMP
5219 nsHttpChannel::SetApplicationCache(nsIApplicationCache *appCache)
5221 mApplicationCache = appCache;
5222 return NS_OK;
5226 //-----------------------------------------------------------------------------
5227 // nsHttpChannel::nsContentEncodings <public>
5228 //-----------------------------------------------------------------------------
5230 nsHttpChannel::nsContentEncodings::nsContentEncodings(nsIHttpChannel* aChannel,
5231 const char* aEncodingHeader) :
5232 mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(PR_FALSE)
5234 mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
5235 mCurStart = mCurEnd;
5238 nsHttpChannel::nsContentEncodings::~nsContentEncodings()
5242 //-----------------------------------------------------------------------------
5243 // nsHttpChannel::nsContentEncodings::nsISimpleEnumerator
5244 //-----------------------------------------------------------------------------
5246 NS_IMETHODIMP
5247 nsHttpChannel::nsContentEncodings::HasMore(PRBool* aMoreEncodings)
5249 if (mReady) {
5250 *aMoreEncodings = PR_TRUE;
5251 return NS_OK;
5254 nsresult rv = PrepareForNext();
5255 *aMoreEncodings = NS_SUCCEEDED(rv);
5256 return NS_OK;
5259 NS_IMETHODIMP
5260 nsHttpChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding)
5262 aNextEncoding.Truncate();
5263 if (!mReady) {
5264 nsresult rv = PrepareForNext();
5265 if (NS_FAILED(rv)) {
5266 return NS_ERROR_FAILURE;
5270 const nsACString & encoding = Substring(mCurStart, mCurEnd);
5272 nsACString::const_iterator start, end;
5273 encoding.BeginReading(start);
5274 encoding.EndReading(end);
5276 PRBool haveType = PR_FALSE;
5277 if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("gzip"),
5278 start,
5279 end)) {
5280 aNextEncoding.AssignLiteral(APPLICATION_GZIP);
5281 haveType = PR_TRUE;
5284 if (!haveType) {
5285 encoding.BeginReading(start);
5286 if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"),
5287 start,
5288 end)) {
5289 aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
5291 haveType = PR_TRUE;
5295 if (! haveType) {
5296 encoding.BeginReading(start);
5297 if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"),
5298 start,
5299 end)) {
5300 aNextEncoding.AssignLiteral(APPLICATION_ZIP);
5301 haveType = PR_TRUE;
5305 // Prepare to fetch the next encoding
5306 mCurEnd = mCurStart;
5307 mReady = PR_FALSE;
5309 if (haveType)
5310 return NS_OK;
5312 NS_WARNING("Unknown encoding type");
5313 return NS_ERROR_FAILURE;
5316 //-----------------------------------------------------------------------------
5317 // nsHttpChannel::nsContentEncodings::nsISupports
5318 //-----------------------------------------------------------------------------
5320 NS_IMPL_ISUPPORTS1(nsHttpChannel::nsContentEncodings, nsIUTF8StringEnumerator)
5322 //-----------------------------------------------------------------------------
5323 // nsHttpChannel::nsContentEncodings <private>
5324 //-----------------------------------------------------------------------------
5326 nsresult
5327 nsHttpChannel::nsContentEncodings::PrepareForNext(void)
5329 NS_PRECONDITION(mCurStart == mCurEnd, "Indeterminate state");
5331 // At this point both mCurStart and mCurEnd point to somewhere
5332 // past the end of the next thing we want to return
5334 while (mCurEnd != mEncodingHeader) {
5335 --mCurEnd;
5336 if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd))
5337 break;
5339 if (mCurEnd == mEncodingHeader)
5340 return NS_ERROR_NOT_AVAILABLE; // no more encodings
5341 ++mCurEnd;
5343 // At this point mCurEnd points to the first char _after_ the
5344 // header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
5346 mCurStart = mCurEnd - 1;
5347 while (mCurStart != mEncodingHeader &&
5348 *mCurStart != ',' && !nsCRT::IsAsciiSpace(*mCurStart))
5349 --mCurStart;
5350 if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
5351 ++mCurStart; // we stopped because of a weird char, so move up one
5353 // At this point mCurStart and mCurEnd bracket the encoding string
5354 // we want. Check that it's not "identity"
5355 if (Substring(mCurStart, mCurEnd).Equals("identity",
5356 nsCaseInsensitiveCStringComparator())) {
5357 mCurEnd = mCurStart;
5358 return PrepareForNext();
5361 mReady = PR_TRUE;
5362 return NS_OK;
5365 //-----------------------------------------------------------------------------
5366 // nsStreamListenerWrapper <private>
5367 //-----------------------------------------------------------------------------
5369 // Wrapper class to make replacement of nsHttpChannel's listener
5370 // from JavaScript possible. It is workaround for bug 433711.
5371 class nsStreamListenerWrapper : public nsIStreamListener
5373 public:
5374 nsStreamListenerWrapper(nsIStreamListener *listener);
5376 NS_DECL_ISUPPORTS
5377 NS_FORWARD_NSIREQUESTOBSERVER(mListener->)
5378 NS_FORWARD_NSISTREAMLISTENER(mListener->)
5380 private:
5381 ~nsStreamListenerWrapper() {}
5382 nsCOMPtr<nsIStreamListener> mListener;
5385 nsStreamListenerWrapper::nsStreamListenerWrapper(nsIStreamListener *listener)
5386 : mListener(listener)
5388 NS_ASSERTION(mListener, "no stream listener specified");
5391 NS_IMPL_ISUPPORTS2(nsStreamListenerWrapper,
5392 nsIStreamListener,
5393 nsIRequestObserver)
5395 //-----------------------------------------------------------------------------
5396 // nsHttpChannel::nsITraceableChannel
5397 //-----------------------------------------------------------------------------
5399 NS_IMETHODIMP
5400 nsHttpChannel::SetNewListener(nsIStreamListener *aListener, nsIStreamListener **_retval)
5402 if (!mTracingEnabled)
5403 return NS_ERROR_FAILURE;
5405 NS_ENSURE_ARG_POINTER(aListener);
5407 nsCOMPtr<nsIStreamListener> wrapper =
5408 new nsStreamListenerWrapper(mListener);
5410 if (!wrapper)
5411 return NS_ERROR_OUT_OF_MEMORY;
5413 wrapper.forget(_retval);
5414 mListener = aListener;
5415 return NS_OK;
5418 void
5419 nsHttpChannel::MaybeInvalidateCacheEntryForSubsequentGet()
5421 // See RFC 2616 section 5.1.1. These are considered valid
5422 // methods which DO NOT invalidate cache-entries for the
5423 // referred resource. POST, PUT and DELETE as well as any
5424 // other method not listed here will potentially invalidate
5425 // any cached copy of the resource
5426 if (mRequestHead.Method() == nsHttp::Options ||
5427 mRequestHead.Method() == nsHttp::Get ||
5428 mRequestHead.Method() == nsHttp::Head ||
5429 mRequestHead.Method() == nsHttp::Trace ||
5430 mRequestHead.Method() == nsHttp::Connect)
5431 return;
5433 // NOTE:
5434 // Following comments 24,32 and 33 in bug #327765, we only care about
5435 // the cache in the protocol-handler.
5436 // The logic below deviates from the original logic in OpenCacheEntry on
5437 // one point by using only READ_ONLY access-policy. I think this is safe.
5438 LOG(("MaybeInvalidateCacheEntryForSubsequentGet [this=%x]\n", this));
5440 nsCAutoString tmpCacheKey;
5441 // passing 0 in first param gives the cache-key for a GET to my resource
5442 GenerateCacheKey(0, tmpCacheKey);
5444 // Now, find the session holding the cache-entry
5445 nsCOMPtr<nsICacheSession> session;
5446 nsCacheStoragePolicy storagePolicy = DetermineStoragePolicy();
5448 nsresult rv;
5449 rv = gHttpHandler->GetCacheSession(storagePolicy,
5450 getter_AddRefs(session));
5452 if (NS_FAILED(rv)) return;
5454 // Finally, find the actual cache-entry
5455 nsCOMPtr<nsICacheEntryDescriptor> tmpCacheEntry;
5456 rv = session->OpenCacheEntry(tmpCacheKey, nsICache::ACCESS_READ,
5457 PR_FALSE,
5458 getter_AddRefs(tmpCacheEntry));
5460 // If entry was found, set its expiration-time = 0
5461 if(NS_SUCCEEDED(rv)) {
5462 tmpCacheEntry->SetExpirationTime(0);
5466 nsCacheStoragePolicy
5467 nsHttpChannel::DetermineStoragePolicy()
5469 nsCacheStoragePolicy policy = nsICache::STORE_ANYWHERE;
5470 if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
5471 policy = nsICache::STORE_IN_MEMORY;
5473 return policy;