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
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.
24 * Darin Fisher <darin@meer.net> (original author)
25 * Christian Biesinger <cbiesinger@web.de>
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"
53 #include "nsIHttpAuthenticator.h"
54 #include "nsIApplicationCacheService.h"
55 #include "nsIAuthInformation.h"
56 #include "nsIAuthPrompt2.h"
57 #include "nsIAuthPromptProvider.h"
58 #include "nsIStringBundle.h"
60 #include "nsISupportsPrimitives.h"
62 #include "nsIIDNService.h"
63 #include "nsIStreamListenerTee.h"
64 #include "nsISeekableStream.h"
65 #include "nsMimeTypes.h"
66 #include "nsNetUtil.h"
68 #include "nsPrintfCString.h"
69 #include "nsReadableUtils.h"
70 #include "nsUnicharUtils.h"
71 #include "nsAutoPtr.h"
75 #include "nsICookieService.h"
76 #include "nsIResumableChannel.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
)
104 , mPriority(PRIORITY_NORMAL
)
105 , mCachedResponseHead(nsnull
)
109 , mProxyAuthContinuationState(nsnull
)
110 , mAuthContinuationState(nsnull
)
111 , mStartPos(LL_MAXUINT
)
112 , mPendingAsyncCallOnResume(nsnull
)
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
;
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
;
160 nsHttpChannel::Init(nsIURI
*uri
,
162 nsProxyInfo
*proxyInfo
)
164 LOG(("nsHttpChannel::Init [this=%x]\n", this));
166 NS_PRECONDITION(uri
, "null uri");
168 nsresult rv
= nsHashPropertyBag::Init();
174 mDocumentURI
= nsnull
;
178 // Construct connection info object
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
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(']');
224 hostLine
.Assign(host
);
226 hostLine
.Append(':');
227 hostLine
.AppendInt(port
);
230 rv
= mRequestHead
.SetHeader(nsHttp::Host
, hostLine
);
231 if (NS_FAILED(rv
)) return rv
;
234 AddStandardRequestHeaders(&mRequestHead
.Headers(), caps
,
235 !mConnectionInfo
->UsingSSL() &&
236 mConnectionInfo
->UsingHttpProxy());
241 //-----------------------------------------------------------------------------
242 // nsHttpChannel <private>
243 //-----------------------------------------------------------------------------
246 nsHttpChannel::AsyncCall(nsAsyncCallback funcPtr
)
248 nsCOMPtr
<nsIRunnable
> event
=
249 new nsRunnableMethod
<nsHttpChannel
>(this, funcPtr
);
250 return NS_DispatchToCurrentThread(event
);
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
);
265 nsHttpChannel::Connect(PRBool firstTime
)
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
277 PRBool delayed
= PR_FALSE
;
280 PRBool offline
= gIOService
->IsOffline();
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
);
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
312 if (mCacheForOfflineUse
) {
313 rv
= OpenOfflineCacheEntryForWriting();
314 if (NS_FAILED(rv
)) return rv
;
317 if (NS_SUCCEEDED(rv
) && delayed
)
321 // we may or may not have a cache entry at this point
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.
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
;
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
362 nsHttpChannel::AsyncAbort(nsresult status
)
364 LOG(("nsHttpChannel::AsyncAbort [this=%x status=%x]\n", this, 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.
374 mLoadGroup
->RemoveRequest(this, nsnull
, status
);
380 nsHttpChannel::HandleAsyncNotifyListener()
382 NS_PRECONDITION(!mPendingAsyncCallOnResume
, "How did that happen?");
385 LOG(("Waiting until resume to do async notification [this=%p]\n",
387 mPendingAsyncCallOnResume
= &nsHttpChannel::HandleAsyncNotifyListener
;
395 nsHttpChannel::DoNotifyListener()
398 mListener
->OnStartRequest(this, mListenerContext
);
399 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
401 mListenerContext
= 0;
403 // We have to make sure to drop the reference to the callbacks too
405 mProgressSink
= nsnull
;
409 nsHttpChannel::HandleAsyncRedirect()
411 NS_PRECONDITION(!mPendingAsyncCallOnResume
, "How did that happen?");
414 LOG(("Waiting until resume to do async redirect [this=%p]\n", this));
415 mPendingAsyncCallOnResume
= &nsHttpChannel::HandleAsyncRedirect
;
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());
429 // If ProcessRedirection fails, then we have to send out the
430 // OnStart/OnStop notifications.
431 LOG(("ProcessRedirection failed [rv=%x]\n", rv
));
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).
442 CloseCacheEntry(PR_FALSE
);
445 mIsPending
= PR_FALSE
;
448 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
452 nsHttpChannel::HandleAsyncNotModified()
454 NS_PRECONDITION(!mPendingAsyncCallOnResume
, "How did that happen?");
457 LOG(("Waiting until resume to do async not-modified [this=%p]\n",
459 mPendingAsyncCallOnResume
= &nsHttpChannel::HandleAsyncNotModified
;
463 LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
467 CloseCacheEntry(PR_TRUE
);
469 mIsPending
= PR_FALSE
;
472 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
476 nsHttpChannel::HandleAsyncFallback()
478 NS_PRECONDITION(!mPendingAsyncCallOnResume
, "How did that happen?");
481 LOG(("Waiting until resume to do async fallback [this=%p]\n", this));
482 mPendingAsyncCallOnResume
= &nsHttpChannel::HandleAsyncFallback
;
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.
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
;
505 mIsPending
= PR_FALSE
;
508 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
512 nsHttpChannel::SetupTransaction()
514 LOG(("nsHttpChannel::SetupTransaction [this=%x]\n", this));
516 NS_ENSURE_TRUE(!mTransaction
, NS_ERROR_ALREADY_INITIALIZED
);
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
))
552 mRequestHead
.SetVersion(gHttpHandler
->HttpVersion());
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
;
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:
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
);
603 mRequestHead
.SetHeader(nsHttp::Pragma
, NS_LITERAL_CSTRING("no-cache"), PR_TRUE
);
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
));
640 return NS_ERROR_OUT_OF_MEMORY
;
642 // create the transaction object
643 mTransaction
= new nsHttpTransaction();
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
));
654 NS_RELEASE(mTransaction
);
658 rv
= nsInputStreamPump::Create(getter_AddRefs(mTransactionPump
),
664 nsHttpChannel::AddCookiesToRequest()
666 if (mLoadFlags
& LOAD_ANONYMOUS
) {
670 nsXPIDLCString cookie
;
672 nsICookieService
*cs
= gHttpHandler
->GetCookieService();
674 cs
->GetCookieStringFromHttp(mURI
,
675 mDocumentURI
? mDocumentURI
: mOriginalURI
,
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
);
690 nsHttpChannel::ApplyContentConversions()
695 LOG(("nsHttpChannel::ApplyContentConversions [this=%x]\n", this));
697 if (!mApplyConversion
) {
698 LOG(("not applying conversion per mApplyConversion\n"));
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
);
713 rv
= serv
->AsyncConvertData(from
.get(),
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
));
730 // NOTE: This function duplicates code from nsBaseChannel. This will go away
731 // once HTTP uses nsBaseChannel (part of bug 312760)
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
;
743 sniffers
[i
]->GetMIMETypeFromContent(chan
, aData
, aCount
, newType
);
744 if (NS_SUCCEEDED(rv
) && !newType
.IsEmpty()) {
745 chan
->SetContentType(newType
);
752 nsHttpChannel::CallOnStartRequest()
754 mTracingEnabled
= PR_FALSE
;
756 if (mResponseHead
&& mResponseHead
->ContentType().IsEmpty()) {
757 if (!mContentTypeHint
.IsEmpty())
758 mResponseHead
->SetContentType(mContentTypeHint
);
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
,
775 getter_AddRefs(converter
));
776 if (NS_SUCCEEDED(rv
)) {
777 mListener
= converter
;
783 if (mResponseHead
&& mResponseHead
->ContentCharset().IsEmpty())
784 mResponseHead
->SetContentCharset(mContentCharsetHint
);
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
;
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();
822 nsHttpChannel::ProcessResponse()
825 PRUint32 httpStatus
= mResponseHead
->Status();
827 LOG(("nsHttpChannel::ProcessResponse [this=%x httpStatus=%u]\n",
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();
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
855 switch (httpStatus
) {
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();
868 // these can normally be cached
869 rv
= ProcessNormal();
870 MaybeInvalidateCacheEntryForSubsequentGet();
873 if (mCachedContentIsPartial
) // an internal byte range request...
874 rv
= ProcessPartialContent();
876 rv
= ProcessNormal();
884 case 305: // disabled as a security measure (see bug 187996).
886 // don't store the response body for redirects
887 MaybeInvalidateCacheEntryForSubsequentGet();
888 rv
= ProcessRedirection(httpStatus
);
889 if (NS_SUCCEEDED(rv
)) {
891 CloseCacheEntry(PR_FALSE
);
893 if (mCacheForOfflineUse
) {
894 // Store response in the offline cache
895 InitOfflineCacheEntry();
896 CloseOfflineCacheEntry();
900 LOG(("ProcessRedirection failed [rv=%x]\n", rv
));
901 rv
= ProcessNormal();
905 rv
= ProcessNotModified();
907 LOG(("ProcessNotModified failed [rv=%x]\n", rv
));
908 rv
= ProcessNormal();
913 rv
= ProcessAuthentication(httpStatus
);
915 LOG(("ProcessAuthentication failed [rv=%x]\n", rv
));
916 CheckForSuperfluousAuth();
917 rv
= ProcessNormal();
921 rv
= ProcessNormal();
922 MaybeInvalidateCacheEntryForSubsequentGet();
930 nsHttpChannel::ProcessNormal()
934 LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
937 rv
= GetRequestSucceeded(&succeeded
);
938 if (NS_SUCCEEDED(rv
) && !succeeded
) {
940 rv
= ProcessFallback(&fallingBack
);
947 // Do not continue with normal processing, fallback is in
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).
964 rv
= InitCacheEntry();
966 CloseCacheEntry(PR_TRUE
);
969 // Check that the server sent us what we were asking for
971 // Create an entity id from the response
973 rv
= GetEntityID(id
);
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",
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
;
1020 LOG(("offline cache is up to date, not updating"));
1021 CloseOfflineCacheEntry();
1029 nsHttpChannel::PromptTempRedirect()
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
);
1049 return NS_ERROR_NO_INTERFACE
;
1051 prompt
->Confirm(nsnull
, messageString
, &repost
);
1053 return NS_ERROR_FAILURE
;
1060 nsHttpChannel::ProxyFailover()
1062 LOG(("nsHttpChannel::ProxyFailover [this=%x]\n", this));
1066 nsCOMPtr
<nsIProtocolProxyService
> pps
=
1067 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID
, &rv
);
1071 nsCOMPtr
<nsIProxyInfo
> pi
;
1072 rv
= pps
->GetFailoverForProxy(mConnectionInfo
->ProxyInfo(), mURI
, mStatus
,
1073 getter_AddRefs(pi
));
1077 // XXXbz so where does this codepath remove us from the loadgroup,
1079 return DoReplaceWithProxy(pi
);
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",
1090 mPendingAsyncCallOnResume
=
1091 &nsHttpChannel::HandleAsyncReplaceWithProxy
;
1095 nsresult status
= mStatus
;
1097 nsCOMPtr
<nsIProxyInfo
> pi
;
1098 pi
.swap(mTargetProxyInfo
);
1100 status
= DoReplaceWithProxy(pi
);
1101 if (mLoadGroup
&& NS_SUCCEEDED(status
)) {
1102 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
1106 if (NS_FAILED(status
)) {
1112 nsHttpChannel::DoReplaceWithProxy(nsIProxyInfo
* pi
)
1116 nsCOMPtr
<nsIChannel
> newChannel
;
1117 rv
= gHttpHandler
->NewProxiedChannel(mURI
, pi
, getter_AddRefs(newChannel
));
1121 rv
= SetupReplacementChannel(mURI
, newChannel
, PR_TRUE
);
1125 // Inform consumers about this fake redirect
1126 PRUint32 flags
= nsIChannelEventSink::REDIRECT_INTERNAL
;
1127 rv
= gHttpHandler
->OnChannelRedirect(this, newChannel
, flags
);
1132 rv
= newChannel
->AsyncOpen(mListener
, mListenerContext
);
1136 mStatus
= NS_BINDING_REDIRECTED
;
1138 mListenerContext
= nsnull
;
1143 nsHttpChannel::ResolveProxy()
1145 LOG(("nsHttpChannel::ResolveProxy [this=%x]\n", this));
1149 nsCOMPtr
<nsIProtocolProxyService
> pps
=
1150 do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID
, &rv
);
1154 return pps
->AsyncResolve(mURI
, 0, this, getter_AddRefs(mProxyRequest
));
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
);
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
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)) {
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
));
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
1210 token
= nsCRT::strtok(val
, NS_HTTP_HEADER_SEPS
, &val
);
1217 //-----------------------------------------------------------------------------
1218 // nsHttpChannel <byte-range>
1219 //-----------------------------------------------------------------------------
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
);
1230 val
= mCachedResponseHead
->PeekHeader(nsHttp::Last_Modified
);
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
;
1239 PR_snprintf(buf
, sizeof(buf
), "bytes=%u-", partialLen
);
1241 mRequestHead
.SetHeader(nsHttp::Range
, nsDependentCString(buf
));
1242 mRequestHead
.SetHeader(nsHttp::If_Range
, nsDependentCString(val
));
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
1265 if (PL_strcasecmp(mResponseHead
->PeekHeader(nsHttp::Content_Encoding
),
1266 mCachedResponseHead
->PeekHeader(nsHttp::Content_Encoding
))
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
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();
1305 nsHttpChannel::OnDoneReadingPartialCacheEntry(PRBool
*streamDone
)
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
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
;
1337 NS_NOTREACHED("no transaction");
1341 //-----------------------------------------------------------------------------
1342 // nsHttpChannel <cache>
1343 //-----------------------------------------------------------------------------
1346 nsHttpChannel::ProcessNotModified()
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
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
;
1386 nsHttpChannel::ProcessFallback(PRBool
*fallingBack
)
1388 LOG(("nsHttpChannel::ProcessFallback [this=%x]\n", this));
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
));
1402 // Make sure the fallback entry hasn't been marked as a foreign
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.
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.
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
);
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
1469 mListenerContext
= 0;
1470 // and from our callbacks
1471 mCallbacks
= nsnull
;
1472 mProgressSink
= nsnull
;
1474 *fallingBack
= PR_TRUE
;
1480 nsHttpChannel::OpenCacheEntry(PRBool offline
, PRBool
*delayed
)
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.
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
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.
1514 if (RequestIsConditional()) {
1515 // don't use the cache if our consumer is making a conditional request
1516 // (see bug 331825).
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
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
;
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
)) ||
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
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
;
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.
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
;
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
1673 else if (NS_SUCCEEDED(rv
)) {
1674 mCacheEntry
->GetAccessGranted(&mCacheAccess
);
1675 LOG(("got cache entry [access=%x]\n", mCacheAccess
));
1682 nsHttpChannel::OpenOfflineCacheEntryForWriting()
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();
1693 // only put things in the offline cache while online
1697 if (mRequestHead
.Method() != nsHttp::Get
) {
1698 // only cache complete documents offline
1702 if (mRequestHead
.PeekHeader(nsHttp::Range
)) {
1703 // we don't support caching for byte range requests initiated
1704 // by our clients or via nsIResumableChannel.
1708 if (RequestIsConditional()) {
1709 // don't use the cache if our consumer is making a conditional request
1710 // (see bug 331825).
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.
1741 if (NS_SUCCEEDED(rv
)) {
1742 mOfflineCacheEntry
->GetAccessGranted(&mOfflineCacheAccess
);
1743 LOG(("got offline cache entry [access=%x]\n", mOfflineCacheAccess
));
1750 nsHttpChannel::GenerateCacheKey(PRUint32 postID
, nsACString
&cacheKey
)
1752 cacheKey
.Truncate();
1754 if (mLoadFlags
& LOAD_ANONYMOUS
) {
1755 cacheKey
.AssignLiteral("anon&");
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
, '#');
1772 cacheKey
.Append(spec
, p
- spec
);
1774 cacheKey
.Append(spec
);
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
1782 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
1784 // timeRemaining = freshnessLifetime - currentAge
1785 // expirationTime = now + timeRemaining
1788 nsHttpChannel::UpdateExpirationTime()
1790 NS_ENSURE_TRUE(mResponseHead
, NS_ERROR_FAILURE
);
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
, ¤tAge
);
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);
1816 expirationTime
= now
+ timeRemaining
;
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
);
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.
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
))
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
)
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
);
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
)) ||
1900 mCachedContentIsValid
= PR_TRUE
;
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)) {
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
;
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
;
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...
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
1991 rv
= mCachedResponseHead
->ComputeFreshnessLifetime(&time
);
1992 NS_ENSURE_SUCCESS(rv
, rv
);
1995 doValidation
= PR_TRUE
;
1997 doValidation
= fromPreviousSession
;
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
));
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
;
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
)) {
2048 // Add If-Modified-Since header if a Last-Modified was given
2049 val
= mCachedResponseHead
->PeekHeader(nsHttp::Last_Modified
);
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
);
2056 mRequestHead
.SetHeader(nsHttp::If_None_Match
,
2057 nsDependentCString(val
));
2061 LOG(("CheckCache [this=%x doValidation=%d]\n", this, doValidation
));
2067 nsHttpChannel::ShouldUpdateOfflineCacheEntry(PRBool
*shouldCacheForOfflineUse
)
2069 *shouldCacheForOfflineUse
= PR_FALSE
;
2071 if (!mOfflineCacheEntry
) {
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
;
2081 // if there's nothing in the offline cache, add it
2082 if (mOfflineCacheEntry
&& (mOfflineCacheAccess
== nsICache::ACCESS_WRITE
)) {
2083 *shouldCacheForOfflineUse
= PR_TRUE
;
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
;
2095 PRUint32 offlineLastModifiedTime
;
2096 rv
= mOfflineCacheEntry
->GetLastModified(&offlineLastModifiedTime
);
2097 NS_ENSURE_SUCCESS(rv
, rv
);
2099 if (docLastModifiedTime
> offlineLastModifiedTime
) {
2100 *shouldCacheForOfflineUse
= PR_TRUE
;
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.
2112 nsHttpChannel::ReadFromCache()
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.
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
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 "
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
;
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,
2194 if (NS_FAILED(rv
)) return rv
;
2196 return mCachePump
->AsyncRead(this, mListenerContext
);
2200 nsHttpChannel::CloseCacheEntry(PRBool doomOnFailure
)
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())
2220 else if (mCacheAccess
== nsICache::ACCESS_WRITE
)
2224 LOG((" dooming cache entry!!"));
2225 mCacheEntry
->Doom();
2228 if (mCachedResponseHead
) {
2229 delete mCachedResponseHead
;
2230 mCachedResponseHead
= 0;
2236 mInitedCacheEntry
= PR_FALSE
;
2241 nsHttpChannel::CloseOfflineCacheEntry()
2243 if (!mOfflineCacheEntry
)
2246 LOG(("nsHttpChannel::CloseOfflineCacheEntry [this=%x]", this));
2248 if (NS_FAILED(mStatus
)) {
2249 mOfflineCacheEntry
->Doom();
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
,
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
2279 nsHttpChannel::InitCacheEntry()
2283 NS_ENSURE_TRUE(mCacheEntry
, NS_ERROR_UNEXPECTED
);
2284 // if only reading, nothing to be done here.
2285 if (mCacheAccess
== nsICache::ACCESS_READ
)
2288 // Don't cache the response again if already cached...
2289 if (mCachedContentIsValid
)
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
;
2324 nsHttpChannel::InitOfflineCacheEntry()
2326 if (!mOfflineCacheEntry
) {
2330 if (mResponseHead
->NoStore()) {
2331 CloseOfflineCacheEntry();
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.
2340 PRUint32 expirationTime
;
2341 nsresult rv
= mCacheEntry
->GetExpirationTime(&expirationTime
);
2342 NS_ENSURE_SUCCESS(rv
, rv
);
2344 mOfflineCacheEntry
->SetExpirationTime(expirationTime
);
2347 return AddCacheEntryHeaders(mOfflineCacheEntry
);
2352 nsHttpChannel::AddCacheEntryHeaders(nsICacheEntryDescriptor
*entry
)
2356 // Store secure data in memory only
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
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
);
2389 if ((*token
!= '*') && (PL_strcasecmp(token
, "cookie") != 0)) {
2390 nsHttpAtom atom
= nsHttp::ResolveAtom(token
);
2391 const char *requestVal
= mRequestHead
.PeekHeader(atom
);
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
2407 mResponseHead
->Flatten(head
, PR_TRUE
);
2408 rv
= entry
->SetMetaDataElement("response-head", head
.get());
2414 nsHttpChannel::StoreAuthorizationMetaData(nsICacheEntryDescriptor
*entry
)
2416 // Not applicable to proxy authorization...
2417 const char *val
= mRequestHead
.PeekHeader(nsHttp::Authorization
);
2419 // eg. [Basic realm="wally world"]
2420 nsCAutoString
buf(Substring(val
, strchr(val
, ' ')));
2421 return entry
->SetMetaDataElement("auth", buf
.get());
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
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
;
2443 // Open an output stream to the cache entry and insert a listener tee into
2444 // the chain of response listeners.
2446 nsHttpChannel::InstallCacheListener(PRUint32 offset
)
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
2461 // Mark entry valid inorder to allow simultaneous reading...
2462 rv
= mCacheEntry
->MarkValid();
2463 if (NS_FAILED(rv
)) return rv
;
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
;
2478 nsHttpChannel::InstallOfflineCacheListener()
2482 LOG(("Preparing to write data into the offline cache [uri=%s]\n",
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
;
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
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
*>
2537 bag
->SetProperty(aKey
, aData
);
2538 return PL_DHASH_NEXT
;
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
);
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
);
2571 seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
2573 // replicate original call to SetUploadStream...
2574 if (mUploadStreamHasHeaders
)
2575 uploadChannel
->SetUploadStream(mUploadStream
, EmptyCString(), -1);
2577 const char *ctype
= mRequestHead
.PeekHeader(nsHttp::Content_Type
);
2578 const char *clen
= mRequestHead
.PeekHeader(nsHttp::Content_Length
);
2580 uploadChannel
->SetUploadStream(mUploadStream
,
2581 nsDependentCString(ctype
),
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
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
);
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
);
2606 httpInternal
->SetDocumentURI(mDocumentURI
);
2609 // convey the mApplyConversion flag (bug 91862)
2610 nsCOMPtr
<nsIEncodedChannel
> encodedChannel
= do_QueryInterface(httpChannel
);
2612 encodedChannel
->SetApplyConversion(mApplyConversion
);
2614 // transfer the resume information
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
));
2636 mPropertyHash
.EnumerateRead(CopyProperties
, bag
.get());
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.
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
)));
2670 nsCOMPtr
<nsIChannel
> newChannel
;
2671 nsCOMPtr
<nsIURI
> newURI
;
2673 // create a new URI using the location header and the current URL
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
);
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
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
2699 nsCOMPtr
<nsIURL
> newURL
= do_QueryInterface(newURI
);
2702 rv
= newURL
->GetRef(ref
);
2703 if (NS_SUCCEEDED(rv
) && ref
.IsEmpty()) {
2704 nsCOMPtr
<nsIURL
> baseURL(do_QueryInterface(mURI
));
2706 baseURL
->GetRef(ref
);
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
;
2730 redirectFlags
= nsIChannelEventSink::REDIRECT_TEMPORARY
;
2732 // verify that this is a legal redirect
2733 rv
= gHttpHandler
->OnChannelRedirect(this, newChannel
, redirectFlags
);
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
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
2758 mListenerContext
= 0;
2759 // and from our callbacks
2760 mCallbacks
= nsnull
;
2761 mProgressSink
= nsnull
;
2765 //-----------------------------------------------------------------------------
2766 // nsHttpChannel <auth>
2767 //-----------------------------------------------------------------------------
2769 // buf contains "domain\user"
2771 ParseUserDomain(PRUnichar
*buf
,
2772 const PRUnichar
**user
,
2773 const PRUnichar
**domain
)
2776 while (*p
&& *p
!= '\\') ++p
;
2784 // helper function for setting identity from raw user:pass
2786 SetIdent(nsHttpAuthIdentity
&ident
,
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
2802 GetAuthPrompt(nsIInterfaceRequestor
*ifreq
, PRBool proxyAuth
,
2803 nsIAuthPrompt2
**result
)
2808 PRUint32 promptReason
;
2810 promptReason
= nsIAuthPromptProvider::PROMPT_PROXY
;
2812 promptReason
= nsIAuthPromptProvider::PROMPT_NORMAL
;
2814 nsCOMPtr
<nsIAuthPromptProvider
> promptProvider
= do_GetInterface(ifreq
);
2816 promptProvider
->GetAuthPrompt(promptReason
,
2817 NS_GET_IID(nsIAuthPrompt2
),
2818 reinterpret_cast<void**>(result
));
2820 NS_QueryAuthPrompt2(ifreq
, result
);
2823 // generate credentials for the given challenge, and update the auth cache.
2825 nsHttpChannel::GenCredsAndSetEntry(nsIHttpAuthenticator
*auth
,
2830 const char *directory
,
2832 const char *challenge
,
2833 const nsHttpAuthIdentity
&ident
,
2834 nsCOMPtr
<nsISupports
> &sessionState
,
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
2848 nsISupports
**continuationState
;
2851 continuationState
= &mProxyAuthContinuationState
;
2853 continuationState
= &mAuthContinuationState
;
2856 rv
= auth
->GenerateCredentials(this,
2863 &*continuationState
,
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.
2871 LOG(("generated creds: %s\n", *result
));
2874 // find out if this authenticator allows reuse of credentials and/or
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
);
2898 nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus
)
2900 LOG(("nsHttpChannel::ProcessAuthentication [this=%x code=%u]\n",
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
);
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
2929 LOG(("rejecting 407 from origin server!\n"));
2930 return NS_ERROR_UNEXPECTED
;
2932 challenges
= mResponseHead
->PeekHeader(nsHttp::Proxy_Authenticate
);
2935 challenges
= mResponseHead
->PeekHeader(nsHttp::WWW_Authenticate
);
2936 NS_ENSURE_TRUE(challenges
, NS_ERROR_UNEXPECTED
);
2938 nsCAutoString creds
;
2939 rv
= GetCredentials(challenges
, proxyAuth
, creds
);
2941 LOG(("unable to authenticate\n"));
2943 // set the authentication credentials
2945 mRequestHead
.SetHeader(nsHttp::Proxy_Authorization
, creds
);
2947 mRequestHead
.SetHeader(nsHttp::Authorization
, creds
);
2949 mAuthRetryPending
= PR_TRUE
; // see DoAuthRetry
2955 nsHttpChannel::PrepareForAuthentication(PRBool proxyAuth
)
2957 LOG(("nsHttpChannel::PrepareForAuthentication [this=%x]\n", this));
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())
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
);
2977 nsCOMPtr
<nsIHttpAuthenticator
> precedingAuth
=
2978 do_GetService(contractId
.get(), &rv
);
2982 PRUint32 precedingAuthFlags
;
2983 rv
= precedingAuth
->GetAuthFlags(&precedingAuthFlags
);
2987 if (!(precedingAuthFlags
& nsIHttpAuthenticator::REQUEST_BASED
)) {
2988 const char *challenges
=
2989 mResponseHead
->PeekHeader(nsHttp::Proxy_Authenticate
);
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"));
3002 nsHttpChannel::GetCredentials(const char *challenges
,
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
;
3018 currentContinuationState
= &mProxyAuthContinuationState
;
3019 currentAuthType
= &mProxyAuthType
;
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
);
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
3046 if (!currentAuthType
->IsEmpty() && authType
!= *currentAuthType
)
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
)) {
3067 *currentAuthType
= authType
;
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
);
3091 nsHttpChannel::GetCredentialsForChallenge(const char *challenge
,
3092 const char *authType
,
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();
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()) {
3122 // set informations that depend on whether
3123 // we're authenticating against a proxy
3127 nsHttpAuthIdentity
*ident
;
3128 nsCAutoString path
, scheme
;
3129 PRBool identFromURI
= PR_FALSE
;
3130 nsISupports
**continuationState
;
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
;
3143 host
= mConnectionInfo
->Host();
3144 port
= mConnectionInfo
->Port();
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
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
;
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,
3185 &*continuationState
,
3187 sessionStateGrip
.swap(sessionState
);
3188 if (NS_FAILED(rv
)) return rv
;
3190 LOG((" identity invalid = %d\n", identityInvalid
));
3192 if (identityInvalid
) {
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());
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).
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
;
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
))
3276 nsHttpChannel::GetAuthenticator(const char *challenge
,
3277 nsCString
&authType
,
3278 nsIHttpAuthenticator
**auth
)
3280 LOG(("nsHttpChannel::GetAuthenticator [this=%x]\n", this));
3284 // get the challenge type
3285 if ((p
= strchr(challenge
, ' ')) != nsnull
)
3286 authType
.Assign(challenge
, p
- challenge
);
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
);
3301 nsHttpChannel::GetIdentityFromURI(PRUint32 authFlags
, nsHttpAuthIdentity
&ident
)
3303 LOG(("nsHttpChannel::GetIdentityFromURI [this=%x]\n", this));
3305 nsAutoString userBuf
;
3306 nsAutoString passBuf
;
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());
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=");
3339 PRBool has_quote
= PR_FALSE
;
3342 has_quote
= PR_TRUE
;
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] != '\\')
3356 end
= strchr(p
, ' ');
3358 realm
.Assign(p
, end
- p
);
3365 class nsHTTPAuthInformation
: public nsAuthInformationHolder
{
3367 nsHTTPAuthInformation(PRUint32 aFlags
, const nsString
& aRealm
,
3368 const nsCString
& aAuthType
)
3369 : nsAuthInformationHolder(aFlags
, aRealm
, aAuthType
) {}
3371 void SetToHttpAuthIdentity(PRUint32 authFlags
, nsHttpAuthIdentity
& identity
);
3375 nsHTTPAuthInformation::SetToHttpAuthIdentity(PRUint32 authFlags
, nsHttpAuthIdentity
& identity
)
3377 identity
.Set(Domain().get(), User().get(), Password().get());
3381 nsHttpChannel::PromptForIdentity(PRUint32 level
,
3384 const char *authType
,
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
));
3398 return NS_ERROR_NO_INTERFACE
;
3400 // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
3401 NS_ConvertASCIItoUTF16
realmU(realm
);
3405 // prompt the user...
3406 PRUint32 promptFlags
= 0;
3408 promptFlags
|= nsIAuthInformation::AUTH_PROXY
;
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
));
3419 return NS_ERROR_OUT_OF_MEMORY
;
3420 PRBool retval
= PR_FALSE
;
3421 rv
= authPrompt
->PromptAuth(this,
3427 // remember that we successfully showed the user an auth dialog
3429 mSuppressDefensiveAuth
= PR_TRUE
;
3432 rv
= NS_ERROR_ABORT
;
3434 holder
->SetToHttpAuthIdentity(authFlags
, ident
);
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
))
3451 nsCAutoString userPass
;
3452 rv
= mURI
->GetUserPass(userPass
);
3453 if (NS_FAILED(rv
) || (userPass
.Length() < gHttpHandler
->PhishyUserPassLength()))
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
);
3465 nsCOMPtr
<nsIStringBundle
> bundle
;
3466 bundleService
->CreateBundle(NECKO_MSGS_URL
, getter_AddRefs(bundle
));
3471 rv
= mURI
->GetHost(host
);
3476 rv
= mURI
->GetUsername(user
);
3480 NS_ConvertUTF8toUTF16
ucsHost(host
), ucsUser(user
);
3481 const PRUnichar
*strs
[2] = { ucsHost
.get(), ucsUser
.get() };
3484 bundle
->FormatStringFromName(bundleKey
.get(), strs
, 2, getter_Copies(msg
));
3488 nsCOMPtr
<nsIPrompt
> prompt
;
3489 GetCallback(prompt
);
3493 // do not prompt again
3494 mSuppressDefensiveAuth
= PR_TRUE
;
3497 if (doYesNoPrompt
) {
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
);
3506 confirmed
= choice
== 0;
3509 rv
= prompt
->Confirm(nsnull
, msg
, &confirmed
);
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
) {
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
);
3536 nsHttpChannel::SetAuthorizationHeader(nsHttpAuthCache
*authCache
,
3542 nsHttpAuthIdentity
&ident
)
3544 nsHttpAuthEntry
*entry
= nsnull
;
3547 // set informations that depend on whether
3548 // we're authenticating against a proxy
3550 nsISupports
**continuationState
;
3552 if (header
== nsHttp::Proxy_Authorization
) {
3553 continuationState
= &mProxyAuthContinuationState
;
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)
3574 PRBool identFromURI
;
3575 if (ident
.IsEmpty()) {
3576 ident
.Set(entry
->Identity());
3577 identFromURI
= PR_FALSE
;
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
))
3601 // make sure the continuation state is null since we do not
3602 // support mixing preemptive and 'multirequest' authentication.
3603 NS_IF_RELEASE(*continuationState
);
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
3614 if (header
== nsHttp::Authorization
)
3615 mSuppressDefensiveAuth
= PR_TRUE
;
3618 ident
.Clear(); // don't remember the identity
3623 nsHttpChannel::AddAuthorizationHeaders()
3625 LOG(("nsHttpChannel::AddAuthorizationHeaders? [this=%x]\n", this));
3627 if (mLoadFlags
& LOAD_ANONYMOUS
) {
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
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
,
3648 mConnectionInfo
->Host(),
3649 mConnectionInfo
->Port(),
3656 nsHttpChannel::GetCurrentPath(nsACString
&path
)
3659 nsCOMPtr
<nsIURL
> url
= do_QueryInterface(mURI
);
3661 rv
= url
->GetDirectory(path
);
3663 rv
= mURI
->GetPath(path
);
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 //-----------------------------------------------------------------------------
3699 nsHttpChannel::GetName(nsACString
&aName
)
3706 nsHttpChannel::IsPending(PRBool
*value
)
3708 NS_ENSURE_ARG_POINTER(value
);
3709 *value
= mIsPending
;
3714 nsHttpChannel::GetStatus(nsresult
*aStatus
)
3716 NS_ENSURE_ARG_POINTER(aStatus
);
3722 nsHttpChannel::Cancel(nsresult status
)
3724 LOG(("nsHttpChannel::Cancel [this=%x status=%x]\n", this, status
));
3726 LOG((" ignoring; already canceled\n"));
3729 mCanceled
= PR_TRUE
;
3732 mProxyRequest
->Cancel(status
);
3734 gHttpHandler
->CancelTransaction(mTransaction
, status
);
3735 if (mTransactionPump
)
3736 mTransactionPump
->Cancel(status
);
3738 mCachePump
->Cancel(status
);
3743 nsHttpChannel::Suspend()
3745 NS_ENSURE_TRUE(mIsPending
, NS_ERROR_NOT_AVAILABLE
);
3747 LOG(("nsHttpChannel::Suspend [this=%x]\n", this));
3751 if (mTransactionPump
)
3752 return mTransactionPump
->Suspend();
3754 return mCachePump
->Suspend();
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();
3775 return mCachePump
->Resume();
3781 nsHttpChannel::GetLoadGroup(nsILoadGroup
**aLoadGroup
)
3783 NS_ENSURE_ARG_POINTER(aLoadGroup
);
3784 *aLoadGroup
= mLoadGroup
;
3785 NS_IF_ADDREF(*aLoadGroup
);
3789 nsHttpChannel::SetLoadGroup(nsILoadGroup
*aLoadGroup
)
3791 mLoadGroup
= aLoadGroup
;
3792 mProgressSink
= nsnull
;
3797 nsHttpChannel::GetLoadFlags(nsLoadFlags
*aLoadFlags
)
3799 NS_ENSURE_ARG_POINTER(aLoadFlags
);
3800 *aLoadFlags
= mLoadFlags
;
3804 nsHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags
)
3806 mLoadFlags
= aLoadFlags
;
3810 //-----------------------------------------------------------------------------
3811 // nsHttpChannel::nsIChannel
3812 //-----------------------------------------------------------------------------
3815 nsHttpChannel::GetOriginalURI(nsIURI
**originalURI
)
3817 NS_ENSURE_ARG_POINTER(originalURI
);
3818 *originalURI
= mOriginalURI
;
3819 NS_ADDREF(*originalURI
);
3823 nsHttpChannel::SetOriginalURI(nsIURI
*originalURI
)
3825 NS_ENSURE_ARG_POINTER(originalURI
);
3826 mOriginalURI
= originalURI
;
3831 nsHttpChannel::GetURI(nsIURI
**URI
)
3833 NS_ENSURE_ARG_POINTER(URI
);
3840 nsHttpChannel::GetOwner(nsISupports
**owner
)
3842 NS_ENSURE_ARG_POINTER(owner
);
3844 NS_IF_ADDREF(*owner
);
3848 nsHttpChannel::SetOwner(nsISupports
*owner
)
3855 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor
**callbacks
)
3857 NS_IF_ADDREF(*callbacks
= mCallbacks
);
3861 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor
*callbacks
)
3863 mCallbacks
= callbacks
;
3864 mProgressSink
= nsnull
;
3869 nsHttpChannel::GetSecurityInfo(nsISupports
**securityInfo
)
3871 NS_ENSURE_ARG_POINTER(securityInfo
);
3872 *securityInfo
= mSecurityInfo
;
3873 NS_IF_ADDREF(*securityInfo
);
3878 nsHttpChannel::GetContentType(nsACString
&value
)
3880 if (!mResponseHead
) {
3881 // We got no data, we got no headers, we got nothing
3883 return NS_ERROR_NOT_AVAILABLE
;
3886 if (!mResponseHead
->ContentType().IsEmpty()) {
3887 value
= mResponseHead
->ContentType();
3892 value
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
3897 nsHttpChannel::SetContentType(const nsACString
&value
)
3899 if (mListener
|| mWasOpened
) {
3901 return NS_ERROR_NOT_AVAILABLE
;
3903 nsCAutoString contentTypeBuf
, charsetBuf
;
3905 net_ParseContentType(value
, contentTypeBuf
, charsetBuf
, &hadCharset
);
3907 mResponseHead
->SetContentType(contentTypeBuf
);
3909 // take care not to stomp on an existing charset
3911 mResponseHead
->SetContentCharset(charsetBuf
);
3913 // We are being given a content-type hint.
3915 net_ParseContentType(value
, mContentTypeHint
, mContentCharsetHint
,
3923 nsHttpChannel::GetContentCharset(nsACString
&value
)
3926 return NS_ERROR_NOT_AVAILABLE
;
3928 value
= mResponseHead
->ContentCharset();
3933 nsHttpChannel::SetContentCharset(const nsACString
&value
)
3937 return NS_ERROR_NOT_AVAILABLE
;
3939 mResponseHead
->SetContentCharset(value
);
3942 mContentCharsetHint
= value
;
3948 nsHttpChannel::GetContentLength(PRInt32
*value
)
3950 NS_ENSURE_ARG_POINTER(value
);
3953 return NS_ERROR_NOT_AVAILABLE
;
3955 // XXX truncates to 32 bit
3956 LL_L2I(*value
, mResponseHead
->ContentLength());
3961 nsHttpChannel::SetContentLength(PRInt32 value
)
3963 NS_NOTYETIMPLEMENTED("nsHttpChannel::SetContentLength");
3964 return NS_ERROR_NOT_IMPLEMENTED
;
3968 nsHttpChannel::Open(nsIInputStream
**_retval
)
3970 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_IN_PROGRESS
);
3971 return NS_ImplementChannelOpen(this, _retval
);
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
);
3985 rv
= NS_CheckPortSafety(mURI
);
3989 // Remember the cookie header that was set, if any
3990 const char *cookieHeader
= mRequestHead
.PeekHeader(nsHttp::Cookie
);
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.
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
4024 if (NS_FAILED(rv
)) {
4025 LOG(("Calling AsyncAbort [rv=%x mCanceled=%i]\n", rv
, mCanceled
));
4026 CloseCacheEntry(PR_TRUE
);
4031 //-----------------------------------------------------------------------------
4032 // nsHttpChannel::nsIHttpChannel
4033 //-----------------------------------------------------------------------------
4036 nsHttpChannel::GetRequestMethod(nsACString
&method
)
4038 method
= mRequestHead
.Method();
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());
4054 return NS_ERROR_FAILURE
;
4056 mRequestHead
.SetMethod(atom
);
4061 nsHttpChannel::GetReferrer(nsIURI
**referrer
)
4063 NS_ENSURE_ARG_POINTER(referrer
);
4064 *referrer
= mReferrer
;
4065 NS_IF_ADDREF(*referrer
);
4070 nsHttpChannel::SetReferrer(nsIURI
*referrer
)
4072 NS_ENSURE_TRUE(!mIsPending
, NS_ERROR_IN_PROGRESS
);
4074 // clear existing referrer, if any
4076 mRequestHead
.ClearHeader(nsHttp::Referer
);
4081 // check referrer blocking pref
4082 PRUint32 referrerLevel
;
4083 if (mLoadFlags
& LOAD_INITIAL_DOCUMENT_URI
)
4084 referrerLevel
= 1; // user action
4086 referrerLevel
= 2; // inline content
4087 if (gHttpHandler
->ReferrerLevel() < referrerLevel
)
4090 nsCOMPtr
<nsIURI
> referrerGrip
;
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
;
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),
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
[] = {
4142 const char *const *scheme
= referrerWhiteList
;
4143 for (; *scheme
&& !match
; ++scheme
) {
4144 rv
= referrer
->SchemeIs(*scheme
, &match
);
4145 if (NS_FAILED(rv
)) return rv
;
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
;
4159 rv
= mURI
->SchemeIs("https", &match
);
4160 if (NS_FAILED(rv
)) return rv
;
4164 if (!gHttpHandler
->SendSecureXSiteReferrer()) {
4165 nsCAutoString referrerHost
;
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
))
4180 nsCOMPtr
<nsIURI
> clone
;
4182 // we need to clone the referrer, so we can:
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
);
4195 url
->SetRef(EmptyCString());
4198 rv
= clone
->GetAsciiSpec(spec
);
4199 if (NS_FAILED(rv
)) return rv
;
4201 // finally, remember the referrer URI and set the Referer header.
4203 mRequestHead
.SetHeader(nsHttp::Referer
, spec
);
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
);
4215 return NS_ERROR_NOT_AVAILABLE
;
4217 return mRequestHead
.GetHeader(atom
, value
);
4221 nsHttpChannel::SetRequestHeader(const nsACString
&header
,
4222 const nsACString
&value
,
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());
4248 NS_WARNING("failed to resolve atom");
4249 return NS_ERROR_NOT_AVAILABLE
;
4252 return mRequestHead
.SetHeader(atom
, flatValue
, merge
);
4256 nsHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor
*visitor
)
4258 return mRequestHead
.Headers().VisitHeaders(visitor
);
4262 nsHttpChannel::GetUploadStream(nsIInputStream
**stream
)
4264 NS_ENSURE_ARG_POINTER(stream
);
4265 *stream
= mUploadStream
;
4266 NS_IF_ADDREF(*stream
);
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.
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
4295 mUploadStreamHasHeaders
= PR_TRUE
;
4296 mRequestHead
.SetMethod(nsHttp::Post
); // POST request
4300 mUploadStreamHasHeaders
= PR_FALSE
;
4301 mRequestHead
.SetMethod(nsHttp::Get
); // revert to GET request
4303 mUploadStream
= stream
;
4308 nsHttpChannel::GetResponseStatus(PRUint32
*value
)
4310 NS_ENSURE_ARG_POINTER(value
);
4312 return NS_ERROR_NOT_AVAILABLE
;
4313 *value
= mResponseHead
->Status();
4318 nsHttpChannel::GetResponseStatusText(nsACString
&value
)
4321 return NS_ERROR_NOT_AVAILABLE
;
4322 value
= mResponseHead
->StatusText();
4327 nsHttpChannel::GetRequestSucceeded(PRBool
*value
)
4329 NS_PRECONDITION(value
, "Don't ever pass a null arg to this function");
4331 return NS_ERROR_NOT_AVAILABLE
;
4332 PRUint32 status
= mResponseHead
->Status();
4333 *value
= (status
/ 100 == 2);
4338 nsHttpChannel::GetResponseHeader(const nsACString
&header
, nsACString
&value
)
4341 return NS_ERROR_NOT_AVAILABLE
;
4342 nsHttpAtom atom
= nsHttp::ResolveAtom(header
);
4344 return NS_ERROR_NOT_AVAILABLE
;
4345 return mResponseHead
->GetHeader(atom
, value
);
4349 nsHttpChannel::SetResponseHeader(const nsACString
&header
,
4350 const nsACString
&value
,
4353 LOG(("nsHttpChannel::SetResponseHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
4354 this, PromiseFlatCString(header
).get(), PromiseFlatCString(value
).get(), merge
));
4357 return NS_ERROR_NOT_AVAILABLE
;
4358 nsHttpAtom atom
= nsHttp::ResolveAtom(header
);
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
);
4376 nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor
*visitor
)
4379 return NS_ERROR_NOT_AVAILABLE
;
4380 return mResponseHead
->Headers().VisitHeaders(visitor
);
4384 nsHttpChannel::IsNoStoreResponse(PRBool
*value
)
4387 return NS_ERROR_NOT_AVAILABLE
;
4388 *value
= mResponseHead
->NoStore();
4393 nsHttpChannel::IsNoCacheResponse(PRBool
*value
)
4396 return NS_ERROR_NOT_AVAILABLE
;
4397 *value
= mResponseHead
->NoCache();
4399 *value
= mResponseHead
->ExpiresInPast();
4404 nsHttpChannel::GetApplyConversion(PRBool
*value
)
4406 NS_ENSURE_ARG_POINTER(value
);
4407 *value
= mApplyConversion
;
4412 nsHttpChannel::SetApplyConversion(PRBool value
)
4414 LOG(("nsHttpChannel::SetApplyConversion [this=%x value=%d]\n", this, value
));
4415 mApplyConversion
= value
;
4420 nsHttpChannel::GetAllowPipelining(PRBool
*value
)
4422 NS_ENSURE_ARG_POINTER(value
);
4423 *value
= mAllowPipelining
;
4428 nsHttpChannel::SetAllowPipelining(PRBool value
)
4431 return NS_ERROR_FAILURE
;
4432 mAllowPipelining
= value
;
4437 nsHttpChannel::GetRedirectionLimit(PRUint32
*value
)
4439 NS_ENSURE_ARG_POINTER(value
);
4440 *value
= PRUint32(mRedirectionLimit
);
4445 nsHttpChannel::SetRedirectionLimit(PRUint32 value
)
4447 mRedirectionLimit
= PR_MIN(value
, 0xff);
4452 nsHttpChannel::GetContentEncodings(nsIUTF8StringEnumerator
** aEncodings
)
4454 NS_PRECONDITION(aEncodings
, "Null out param");
4455 if (!mResponseHead
) {
4456 *aEncodings
= nsnull
;
4460 const char *encoding
= mResponseHead
->PeekHeader(nsHttp::Content_Encoding
);
4462 *aEncodings
= nsnull
;
4465 nsContentEncodings
* enumerator
= new nsContentEncodings(this, encoding
);
4467 return NS_ERROR_OUT_OF_MEMORY
;
4469 NS_ADDREF(*aEncodings
= enumerator
);
4473 //-----------------------------------------------------------------------------
4474 // nsHttpChannel::nsIHttpChannelInternal
4475 //-----------------------------------------------------------------------------
4478 nsHttpChannel::GetDocumentURI(nsIURI
**aDocumentURI
)
4480 NS_ENSURE_ARG_POINTER(aDocumentURI
);
4481 *aDocumentURI
= mDocumentURI
;
4482 NS_IF_ADDREF(*aDocumentURI
);
4487 nsHttpChannel::SetDocumentURI(nsIURI
*aDocumentURI
)
4489 mDocumentURI
= aDocumentURI
;
4494 nsHttpChannel::GetRequestVersion(PRUint32
*major
, PRUint32
*minor
)
4496 int version
= mRequestHead
.Version();
4498 if (major
) { *major
= version
/ 10; }
4499 if (minor
) { *minor
= version
% 10; }
4505 nsHttpChannel::GetResponseVersion(PRUint32
*major
, PRUint32
*minor
)
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; }
4522 nsHttpChannel::SetCookie(const char *aCookieHeader
)
4524 if (mLoadFlags
& LOAD_ANONYMOUS
) {
4528 // empty header isn't an error
4529 if (!(aCookieHeader
&& *aCookieHeader
))
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
,
4542 mResponseHead
->PeekHeader(nsHttp::Date
),
4547 nsHttpChannel::SetupFallbackChannel(const char *aFallbackKey
)
4549 LOG(("nsHttpChannel::SetupFallbackChannel [this=%x, key=%s]",
4550 this, aFallbackKey
));
4551 mFallbackChannel
= PR_TRUE
;
4552 mFallbackKey
= aFallbackKey
;
4557 //-----------------------------------------------------------------------------
4558 // nsHttpChannel::nsISupportsPriority
4559 //-----------------------------------------------------------------------------
4562 nsHttpChannel::GetPriority(PRInt32
*value
)
4569 nsHttpChannel::SetPriority(PRInt32 value
)
4571 PRInt16 newValue
= CLAMP(value
, PR_INT16_MIN
, PR_INT16_MAX
);
4572 if (mPriority
== newValue
)
4574 mPriority
= newValue
;
4576 gHttpHandler
->RescheduleTransaction(mTransaction
, mPriority
);
4581 nsHttpChannel::AdjustPriority(PRInt32 delta
)
4583 return SetPriority(mPriority
+ delta
);
4586 //-----------------------------------------------------------------------------
4587 // nsHttpChannel::nsIProtocolProxyCallback
4588 //-----------------------------------------------------------------------------
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();
4609 //-----------------------------------------------------------------------------
4610 // nsHttpChannel::nsIProxiedChannel
4611 //-----------------------------------------------------------------------------
4614 nsHttpChannel::GetProxyInfo(nsIProxyInfo
**result
)
4616 if (!mConnectionInfo
)
4619 *result
= mConnectionInfo
->ProxyInfo();
4620 NS_IF_ADDREF(*result
);
4625 //-----------------------------------------------------------------------------
4626 // nsHttpChannel::nsIRequestObserver
4627 //-----------------------------------------------------------------------------
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.
4663 return ProcessResponse();
4665 NS_WARNING("No response head in OnStartRequest");
4668 // avoid crashing if mListener happens to be null...
4670 NS_NOTREACHED("mListener is null");
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()))
4683 // on other request errors, try to fall back
4685 if (NS_FAILED(mStatus
) &&
4686 NS_SUCCEEDED(ProcessFallback(&fallingBack
)) &&
4692 return CallOnStartRequest();
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
))
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
) {
4713 status
= OnDoneReadingPartialCacheEntry(&streamDone
);
4714 if (NS_SUCCEEDED(status
) && !streamDone
)
4716 // otherwise, fall through and fire OnStopRequest...
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
);
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...
4750 mAuthRetryPending
= PR_FALSE
;
4751 status
= DoAuthRetry(conn
);
4752 if (NS_SUCCEEDED(status
))
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
)
4770 mIsPending
= PR_FALSE
;
4773 // perform any final cache operations before we close the cache entry.
4774 if (mCacheEntry
&& (mCacheAccess
& nsICache::ACCESS_WRITE
))
4775 FinalizeCacheEntry();
4778 LOG((" calling OnStopRequest\n"));
4779 mListener
->OnStopRequest(this, mListenerContext
, status
);
4781 mListenerContext
= 0;
4785 CloseCacheEntry(PR_TRUE
);
4787 if (mOfflineCacheEntry
)
4788 CloseOfflineCacheEntry();
4791 mLoadGroup
->RemoveRequest(this, nsnull
, status
);
4793 mCallbacks
= nsnull
;
4794 mProgressSink
= nsnull
;
4799 //-----------------------------------------------------------------------------
4800 // nsHttpChannel::nsIStreamListener
4801 //-----------------------------------------------------------------------------
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.
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
)) {
4822 return input
->ReadSegments(NS_DiscardSegment
, nsnull
, count
, &n
);
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
;
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,
4859 if (NS_SUCCEEDED(rv
))
4860 mLogicalOffset
= progress
;
4864 return NS_ERROR_ABORT
;
4867 //-----------------------------------------------------------------------------
4868 // nsHttpChannel::nsITransportEventSink
4869 //-----------------------------------------------------------------------------
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.
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
));
4885 mURI
->GetHost(host
);
4886 mProgressSink
->OnStatus(this, nsnull
, status
,
4887 NS_ConvertUTF8toUTF16(host
).get());
4890 NS_ASSERTION(progress
<= progressMax
, "unexpected progress values");
4891 mProgressSink
->OnProgress(this, nsnull
, progress
, progressMax
);
4896 LOG(("skipping status notification [this=%x sink=%x pending=%u background=%x]\n",
4897 this, mProgressSink
.get(), mIsPending
, (mLoadFlags
& LOAD_BACKGROUND
)));
4903 //-----------------------------------------------------------------------------
4904 // nsHttpChannel::nsICachingChannel
4905 //-----------------------------------------------------------------------------
4908 nsHttpChannel::GetCacheToken(nsISupports
**token
)
4910 NS_ENSURE_ARG_POINTER(token
);
4912 return NS_ERROR_NOT_AVAILABLE
;
4913 return CallQueryInterface(mCacheEntry
, token
);
4917 nsHttpChannel::SetCacheToken(nsISupports
*token
)
4919 return NS_ERROR_NOT_IMPLEMENTED
;
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
);
4932 nsHttpChannel::SetOfflineCacheToken(nsISupports
*token
)
4934 return NS_ERROR_NOT_IMPLEMENTED
;
4938 nsHttpChannel::GetCacheKey(nsISupports
**key
)
4941 NS_ENSURE_ARG_POINTER(key
);
4943 LOG(("nsHttpChannel::GetCacheKey [this=%x]\n", this));
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
);
4958 nsHttpChannel::SetCacheKey(nsISupports
*key
)
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
);
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
;
4981 nsHttpChannel::GetCacheAsFile(PRBool
*value
)
4983 NS_ENSURE_ARG_POINTER(value
);
4985 return NS_ERROR_NOT_AVAILABLE
;
4986 nsCacheStoragePolicy storagePolicy
;
4987 mCacheEntry
->GetStoragePolicy(&storagePolicy
);
4988 *value
= (storagePolicy
== nsICache::STORE_ON_DISK_AS_FILE
);
4993 nsHttpChannel::SetCacheAsFile(PRBool value
)
4995 if (!mCacheEntry
|| mLoadFlags
& INHIBIT_PERSISTENT_CACHING
)
4996 return NS_ERROR_NOT_AVAILABLE
;
4997 nsCacheStoragePolicy policy
;
4999 policy
= nsICache::STORE_ON_DISK_AS_FILE
;
5001 policy
= nsICache::STORE_ANYWHERE
;
5002 return mCacheEntry
->SetStoragePolicy(policy
);
5007 nsHttpChannel::GetCacheForOfflineUse(PRBool
*value
)
5009 *value
= mCacheForOfflineUse
;
5015 nsHttpChannel::SetCacheForOfflineUse(PRBool value
)
5017 mCacheForOfflineUse
= value
;
5023 nsHttpChannel::GetOfflineCacheClientID(nsACString
&value
)
5025 value
= mOfflineCacheClientID
;
5031 nsHttpChannel::SetOfflineCacheClientID(const nsACString
&value
)
5033 mOfflineCacheClientID
= value
;
5039 nsHttpChannel::GetCacheFile(nsIFile
**cacheFile
)
5042 return NS_ERROR_NOT_AVAILABLE
;
5043 return mCacheEntry
->GetFile(cacheFile
);
5047 nsHttpChannel::IsFromCache(PRBool
*value
)
5050 return NS_ERROR_NOT_AVAILABLE
;
5052 // return false if reading a partial cache entry; the data isn't entirely
5055 *value
= (mCachePump
|| (mLoadFlags
& LOAD_ONLY_IF_MODIFIED
)) &&
5056 mCachedContentIsValid
&& !mCachedContentIsPartial
;
5061 //-----------------------------------------------------------------------------
5062 // nsHttpChannel::nsIResumableChannel
5063 //-----------------------------------------------------------------------------
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
;
5078 nsHttpChannel::GetEntityID(nsACString
& aEntityID
)
5080 // Don't return an entity ID for Non-GET requests which require
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
);
5093 const char* cEtag
= mResponseHead
->PeekHeader(nsHttp::ETag
);
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
;
5111 //-----------------------------------------------------------------------------
5112 // nsHttpChannel::nsICacheListener
5113 //-----------------------------------------------------------------------------
5116 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor
*entry
,
5117 nsCacheAccessMode access
,
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
5128 // otherwise, we have to handle this event.
5129 if (NS_SUCCEEDED(status
)) {
5130 mCacheEntry
= entry
;
5131 mCacheAccess
= access
;
5136 if (mCanceled
&& NS_FAILED(mStatus
)) {
5137 LOG(("channel was canceled [this=%x status=%x]\n", this, 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
;
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
);
5158 nsHttpChannel::DoAuthRetry(nsAHttpConnection
*conn
)
5160 LOG(("nsHttpChannel::DoAuthRetry [this=%x]\n", this));
5162 NS_ASSERTION(!mTransaction
, "should not have a transaction");
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
5193 mTransaction
->SetConnection(conn
);
5195 // rewind the upload stream
5196 if (mUploadStream
) {
5197 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(mUploadStream
);
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 //-----------------------------------------------------------------------------
5212 nsHttpChannel::GetApplicationCache(nsIApplicationCache
**out
)
5214 NS_IF_ADDREF(*out
= mApplicationCache
);
5219 nsHttpChannel::SetApplicationCache(nsIApplicationCache
*appCache
)
5221 mApplicationCache
= appCache
;
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 //-----------------------------------------------------------------------------
5247 nsHttpChannel::nsContentEncodings::HasMore(PRBool
* aMoreEncodings
)
5250 *aMoreEncodings
= PR_TRUE
;
5254 nsresult rv
= PrepareForNext();
5255 *aMoreEncodings
= NS_SUCCEEDED(rv
);
5260 nsHttpChannel::nsContentEncodings::GetNext(nsACString
& aNextEncoding
)
5262 aNextEncoding
.Truncate();
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"),
5280 aNextEncoding
.AssignLiteral(APPLICATION_GZIP
);
5285 encoding
.BeginReading(start
);
5286 if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"),
5289 aNextEncoding
.AssignLiteral(APPLICATION_COMPRESS
);
5296 encoding
.BeginReading(start
);
5297 if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"),
5300 aNextEncoding
.AssignLiteral(APPLICATION_ZIP
);
5305 // Prepare to fetch the next encoding
5306 mCurEnd
= mCurStart
;
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 //-----------------------------------------------------------------------------
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
) {
5336 if (*mCurEnd
!= ',' && !nsCRT::IsAsciiSpace(*mCurEnd
))
5339 if (mCurEnd
== mEncodingHeader
)
5340 return NS_ERROR_NOT_AVAILABLE
; // no more encodings
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
))
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();
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
5374 nsStreamListenerWrapper(nsIStreamListener
*listener
);
5377 NS_FORWARD_NSIREQUESTOBSERVER(mListener
->)
5378 NS_FORWARD_NSISTREAMLISTENER(mListener
->)
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
,
5395 //-----------------------------------------------------------------------------
5396 // nsHttpChannel::nsITraceableChannel
5397 //-----------------------------------------------------------------------------
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
);
5411 return NS_ERROR_OUT_OF_MEMORY
;
5413 wrapper
.forget(_retval
);
5414 mListener
= aListener
;
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
)
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();
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
,
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
;