Back to index

lightning-sunbird  0.9+nobinonly
nsHttpChannel.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set expandtab ts=4 sw=4 sts=4 cin: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is Mozilla.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications.
00020  * Portions created by the Initial Developer are Copyright (C) 2001
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Darin Fisher <darin@meer.net> (original author)
00025  *   Christian Biesinger <cbiesinger@web.de>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either the GNU General Public License Version 2 or later (the "GPL"), or
00029  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nsHttpChannel.h"
00042 #include "nsHttpTransaction.h"
00043 #include "nsHttpConnection.h"
00044 #include "nsHttpHandler.h"
00045 #include "nsHttpAuthCache.h"
00046 #include "nsHttpResponseHead.h"
00047 #include "nsHttp.h"
00048 #include "nsIHttpAuthenticator.h"
00049 #include "nsIAuthPrompt.h"
00050 #include "nsIAuthPromptProvider.h"
00051 #include "nsIStringBundle.h"
00052 #include "nsXPCOM.h"
00053 #include "nsISupportsPrimitives.h"
00054 #include "nsIURL.h"
00055 #include "nsIScriptSecurityManager.h"
00056 #include "nsIIDNService.h"
00057 #include "nsIStreamListenerTee.h"
00058 #include "nsISeekableStream.h"
00059 #include "nsCPasswordManager.h"
00060 #include "nsMimeTypes.h"
00061 #include "nsNetUtil.h"
00062 #include "nsString.h"
00063 #include "nsPrintfCString.h"
00064 #include "nsReadableUtils.h"
00065 #include "nsUnicharUtils.h"
00066 #include "nsAutoPtr.h"
00067 #include "plstr.h"
00068 #include "prprf.h"
00069 #include "nsEscape.h"
00070 #include "nsICookieService.h"
00071 #include "nsIResumableChannel.h"
00072 #include "nsInt64.h"
00073 #include "nsIVariant.h"
00074 #include "nsChannelProperties.h"
00075 #include "nsIOService.h"
00076 
00077 // True if the local cache should be bypassed when processing a request.
00078 #define BYPASS_LOCAL_CACHE(loadFlags) \
00079         (loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
00080                       nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
00081 
00082 static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
00083 
00084 static NS_METHOD DiscardSegments(nsIInputStream *input,
00085                                  void *closure,
00086                                  const char *buf,
00087                                  PRUint32 offset,
00088                                  PRUint32 count,
00089                                  PRUint32 *countRead)
00090 {
00091     *countRead = count;
00092     return NS_OK;
00093 }
00094 
00095 //-----------------------------------------------------------------------------
00096 // nsHttpChannel <public>
00097 //-----------------------------------------------------------------------------
00098 
00099 nsHttpChannel::nsHttpChannel()
00100     : mResponseHead(nsnull)
00101     , mTransaction(nsnull)
00102     , mConnectionInfo(nsnull)
00103     , mLoadFlags(LOAD_NORMAL)
00104     , mStatus(NS_OK)
00105     , mLogicalOffset(0)
00106     , mCaps(0)
00107     , mPriority(PRIORITY_NORMAL)
00108     , mCachedResponseHead(nsnull)
00109     , mCacheAccess(0)
00110     , mPostID(0)
00111     , mRequestTime(0)
00112     , mProxyAuthContinuationState(nsnull)
00113     , mAuthContinuationState(nsnull)
00114     , mStartPos(LL_MAXUINT)
00115     , mRedirectionLimit(gHttpHandler->RedirectionLimit())
00116     , mIsPending(PR_FALSE)
00117     , mApplyConversion(PR_TRUE)
00118     , mAllowPipelining(PR_TRUE)
00119     , mCachedContentIsValid(PR_FALSE)
00120     , mCachedContentIsPartial(PR_FALSE)
00121     , mResponseHeadersModified(PR_FALSE)
00122     , mCanceled(PR_FALSE)
00123     , mTransactionReplaced(PR_FALSE)
00124     , mUploadStreamHasHeaders(PR_FALSE)
00125     , mAuthRetryPending(PR_FALSE)
00126     , mSuppressDefensiveAuth(PR_FALSE)
00127     , mResuming(PR_FALSE)
00128     , mOpenedCacheForWriting(PR_FALSE)
00129 {
00130     LOG(("Creating nsHttpChannel @%x\n", this));
00131 
00132     // grab a reference to the handler to ensure that it doesn't go away.
00133     nsHttpHandler *handler = gHttpHandler;
00134     NS_ADDREF(handler);
00135 }
00136 
00137 nsHttpChannel::~nsHttpChannel()
00138 {
00139     LOG(("Destroying nsHttpChannel @%x\n", this));
00140 
00141     if (mResponseHead) {
00142         delete mResponseHead;
00143         mResponseHead = 0;
00144     }
00145     if (mCachedResponseHead) {
00146         delete mCachedResponseHead;
00147         mCachedResponseHead = 0;
00148     }
00149 
00150     NS_IF_RELEASE(mConnectionInfo);
00151     NS_IF_RELEASE(mTransaction);
00152 
00153     NS_IF_RELEASE(mProxyAuthContinuationState);
00154     NS_IF_RELEASE(mAuthContinuationState);
00155 
00156     // release our reference to the handler
00157     nsHttpHandler *handler = gHttpHandler;
00158     NS_RELEASE(handler);
00159 }
00160 
00161 nsresult
00162 nsHttpChannel::Init(nsIURI *uri,
00163                     PRUint8 caps,
00164                     nsProxyInfo *proxyInfo)
00165 {
00166     LOG(("nsHttpChannel::Init [this=%x]\n", this));
00167 
00168     NS_PRECONDITION(uri, "null uri");
00169 
00170     nsresult rv = nsHashPropertyBag::Init();
00171     if (NS_FAILED(rv))
00172         return rv;
00173 
00174     mURI = uri;
00175     mOriginalURI = uri;
00176     mDocumentURI = nsnull;
00177     mCaps = caps;
00178 
00179     //
00180     // Construct connection info object
00181     //
00182     nsCAutoString host;
00183     PRInt32 port = -1;
00184     PRBool usingSSL = PR_FALSE;
00185     
00186     rv = mURI->SchemeIs("https", &usingSSL);
00187     if (NS_FAILED(rv)) return rv;
00188 
00189     rv = mURI->GetAsciiHost(host);
00190     if (NS_FAILED(rv)) return rv;
00191 
00192     // reject the URL if it doesn't specify a host
00193     if (host.IsEmpty())
00194         return NS_ERROR_MALFORMED_URI;
00195 
00196     rv = mURI->GetPort(&port);
00197     if (NS_FAILED(rv)) return rv;
00198 
00199     LOG(("host=%s port=%d\n", host.get(), port));
00200 
00201     rv = mURI->GetAsciiSpec(mSpec);
00202     if (NS_FAILED(rv)) return rv;
00203 
00204     LOG(("uri=%s\n", mSpec.get()));
00205 
00206     mConnectionInfo = new nsHttpConnectionInfo(host, port,
00207                                                proxyInfo, usingSSL);
00208     if (!mConnectionInfo)
00209         return NS_ERROR_OUT_OF_MEMORY;
00210     NS_ADDREF(mConnectionInfo);
00211 
00212     // make sure our load flags include this bit if this is a secure channel.
00213     if (usingSSL && !gHttpHandler->IsPersistentHttpsCachingEnabled()) 
00214         mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
00215 
00216     // Set default request method
00217     mRequestHead.SetMethod(nsHttp::Get);
00218 
00219     //
00220     // Set request headers
00221     //
00222     nsCAutoString hostLine;
00223     if (strchr(host.get(), ':')) {
00224         // host is an IPv6 address literal and must be encapsulated in []'s
00225         hostLine.Assign('[');
00226         hostLine.Append(host);
00227         hostLine.Append(']');
00228     }
00229     else
00230         hostLine.Assign(host);
00231     if (port != -1) {
00232         hostLine.Append(':');
00233         hostLine.AppendInt(port);
00234     }
00235 
00236     rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
00237     if (NS_FAILED(rv)) return rv;
00238 
00239     rv = gHttpHandler->
00240         AddStandardRequestHeaders(&mRequestHead.Headers(), caps,
00241                                   !mConnectionInfo->UsingSSL() &&
00242                                   mConnectionInfo->UsingHttpProxy());
00243 
00244     return rv;
00245 }
00246 
00247 //-----------------------------------------------------------------------------
00248 // nsHttpChannel <private>
00249 //-----------------------------------------------------------------------------
00250 
00251 nsresult
00252 nsHttpChannel::AsyncCall(nsAsyncCallback funcPtr)
00253 {
00254     nsresult rv;
00255 
00256     nsAsyncCallEvent *event = new nsAsyncCallEvent;
00257     if (!event)
00258         return NS_ERROR_OUT_OF_MEMORY;
00259 
00260     event->mFuncPtr = funcPtr;
00261 
00262     NS_ADDREF_THIS();
00263 
00264     PL_InitEvent(event, this,
00265                  nsHttpChannel::AsyncCall_EventHandlerFunc,
00266                  nsHttpChannel::AsyncCall_EventCleanupFunc);
00267 
00268     rv = mEventQ->PostEvent(event);
00269     if (NS_FAILED(rv)) {
00270         PL_DestroyEvent(event);
00271         NS_RELEASE_THIS();
00272     }
00273     return rv;
00274 }
00275 
00276 void *PR_CALLBACK
00277 nsHttpChannel::AsyncCall_EventHandlerFunc(PLEvent *ev)
00278 {
00279     nsHttpChannel *chan =
00280         NS_STATIC_CAST(nsHttpChannel *, PL_GetEventOwner(ev));
00281 
00282     nsAsyncCallEvent *ace = (nsAsyncCallEvent *) ev;
00283     nsAsyncCallback funcPtr = ace->mFuncPtr;
00284 
00285     if (chan) {
00286         (chan->*funcPtr)();
00287         NS_RELEASE(chan);
00288     }
00289     return nsnull;
00290 }
00291 
00292 void PR_CALLBACK
00293 nsHttpChannel::AsyncCall_EventCleanupFunc(PLEvent *ev)
00294 {
00295     delete (nsAsyncCallEvent *) ev;
00296 }
00297 
00298 PRBool
00299 nsHttpChannel::RequestIsConditional()
00300 {
00301     // Is our consumer issuing a conditional request?
00302     return mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
00303            mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
00304            mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
00305            mRequestHead.PeekHeader(nsHttp::If_Match) ||
00306            mRequestHead.PeekHeader(nsHttp::If_Range);
00307 }
00308 
00309 nsresult
00310 nsHttpChannel::Connect(PRBool firstTime)
00311 {
00312     nsresult rv;
00313 
00314     LOG(("nsHttpChannel::Connect [this=%x]\n", this));
00315 
00316     // ensure that we are using a valid hostname
00317     if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
00318         return NS_ERROR_UNKNOWN_HOST;
00319 
00320     // true when called from AsyncOpen
00321     if (firstTime) {
00322         PRBool delayed = PR_FALSE;
00323         PRBool offline = PR_FALSE;
00324     
00325         // are we offline?
00326         nsCOMPtr<nsIIOService> ioService;
00327         rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
00328         if (NS_FAILED(rv)) return rv;
00329 
00330         ioService->GetOffline(&offline);
00331         if (offline)
00332             mLoadFlags |= LOAD_ONLY_FROM_CACHE;
00333         else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
00334             return ResolveProxy();  // Lazily resolve proxy info
00335 
00336         // Don't allow resuming when cache must be used
00337         if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
00338             LOG(("Resuming from cache is not supported yet"));
00339             return NS_ERROR_DOCUMENT_NOT_CACHED;
00340         }
00341 
00342         // open a cache entry for this channel...
00343         rv = OpenCacheEntry(offline, &delayed);
00344 
00345         if (NS_FAILED(rv)) {
00346             LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
00347             // if this channel is only allowed to pull from the cache, then
00348             // we must fail if we were unable to open a cache entry.
00349             if (mLoadFlags & LOAD_ONLY_FROM_CACHE)
00350                 return NS_ERROR_DOCUMENT_NOT_CACHED;
00351             // otherwise, let's just proceed without using the cache.
00352         }
00353  
00354         if (NS_SUCCEEDED(rv) && delayed)
00355             return NS_OK;
00356     }
00357 
00358     // we may or may not have a cache entry at this point
00359     if (mCacheEntry) {
00360         // inspect the cache entry to determine whether or not we need to go
00361         // out to net to validate it.  this call sets mCachedContentIsValid
00362         // and may set request headers as required for cache validation.
00363         rv = CheckCache();
00364         NS_ASSERTION(NS_SUCCEEDED(rv), "cache check failed");
00365 
00366         // read straight from the cache if possible...
00367         if (mCachedContentIsValid) {
00368             return ReadFromCache();
00369         }
00370         else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
00371             // the cache contains the requested resource, but it must be 
00372             // validated before we can reuse it.  since we are not allowed
00373             // to hit the net, there's nothing more to do.  the document
00374             // is effectively not in the cache.
00375             return NS_ERROR_DOCUMENT_NOT_CACHED;
00376         }
00377     }
00378 
00379     // check to see if authorization headers should be included
00380     AddAuthorizationHeaders();
00381 
00382     // hit the net...
00383     rv = SetupTransaction();
00384     if (NS_FAILED(rv)) return rv;
00385 
00386     rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
00387     if (NS_FAILED(rv)) return rv;
00388 
00389     return mTransactionPump->AsyncRead(this, nsnull);
00390 }
00391 
00392 // called when Connect fails
00393 nsresult
00394 nsHttpChannel::AsyncAbort(nsresult status)
00395 {
00396     LOG(("nsHttpChannel::AsyncAbort [this=%x status=%x]\n", this, status));
00397 
00398     mStatus = status;
00399     mIsPending = PR_FALSE;
00400 
00401     // create a proxy for the listener..
00402     nsCOMPtr<nsIRequestObserver> observer;
00403     NS_NewRequestObserverProxy(getter_AddRefs(observer), mListener, mEventQ);
00404     if (observer) {
00405         observer->OnStartRequest(this, mListenerContext);
00406         observer->OnStopRequest(this, mListenerContext, mStatus);
00407     }
00408     else {
00409         NS_ERROR("unable to create request observer proxy");
00410         // XXX else, no proxy object manager... what do we do?
00411     }
00412     mListener = 0;
00413     mListenerContext = 0;
00414 
00415     // finally remove ourselves from the load group.
00416     if (mLoadGroup)
00417         mLoadGroup->RemoveRequest(this, nsnull, status);
00418 
00419     return NS_OK;
00420 }
00421 
00422 void
00423 nsHttpChannel::HandleAsyncRedirect()
00424 {
00425     nsresult rv = NS_OK;
00426 
00427     LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
00428 
00429     // since this event is handled asynchronously, it is possible that this
00430     // channel could have been canceled, in which case there would be no point
00431     // in processing the redirect.
00432     if (NS_SUCCEEDED(mStatus)) {
00433         rv = ProcessRedirection(mResponseHead->Status());
00434         if (NS_FAILED(rv)) {
00435             // If ProcessRedirection fails, then we have to send out the
00436             // OnStart/OnStop notifications.
00437             LOG(("ProcessRedirection failed [rv=%x]\n", rv));
00438             mStatus = rv;
00439             if (mListener) {
00440                 mListener->OnStartRequest(this, mListenerContext);
00441                 mListener->OnStopRequest(this, mListenerContext, mStatus);
00442                 mListener = 0;
00443                 mListenerContext = 0;
00444             }
00445         }
00446     }
00447 
00448     // close the cache entry... blow it away if we couldn't process
00449     // the redirect for some reason.
00450     CloseCacheEntry(rv);
00451 
00452     mIsPending = PR_FALSE;
00453 
00454     if (mLoadGroup)
00455         mLoadGroup->RemoveRequest(this, nsnull, mStatus);
00456 }
00457 
00458 void
00459 nsHttpChannel::HandleAsyncNotModified()
00460 {
00461     LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
00462 
00463     if (mListener) {
00464         mListener->OnStartRequest(this, mListenerContext);
00465         mListener->OnStopRequest(this, mListenerContext, mStatus);
00466         mListener = 0;
00467         mListenerContext = 0;
00468     }
00469 
00470     CloseCacheEntry(NS_OK);
00471 
00472     mIsPending = PR_FALSE;
00473 
00474     if (mLoadGroup)
00475         mLoadGroup->RemoveRequest(this, nsnull, mStatus);
00476 }
00477 
00478 nsresult
00479 nsHttpChannel::SetupTransaction()
00480 {
00481     LOG(("nsHttpChannel::SetupTransaction [this=%x]\n", this));
00482 
00483     NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
00484 
00485     nsresult rv;
00486 
00487     if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
00488         //
00489         // disable pipelining if:
00490         //   (1) pipelining has been explicitly disabled
00491         //   (2) request corresponds to a top-level document load (link click)
00492         //   (3) request method is non-idempotent
00493         //
00494         // XXX does the toplevel document check really belong here?  or, should
00495         //     we push it out entirely to necko consumers?
00496         //
00497         if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) ||
00498             !(mRequestHead.Method() == nsHttp::Get ||
00499               mRequestHead.Method() == nsHttp::Head ||
00500               mRequestHead.Method() == nsHttp::Propfind ||
00501               mRequestHead.Method() == nsHttp::Proppatch)) {
00502             LOG(("  pipelining disallowed\n"));
00503             mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
00504         }
00505     }
00506 
00507     // use the URI path if not proxying (transparent proxying such as SSL proxy
00508     // does not count here). also, figure out what version we should be speaking.
00509     nsCAutoString buf, path;
00510     nsCString* requestURI;
00511     if (mConnectionInfo->UsingSSL() || !mConnectionInfo->UsingHttpProxy()) {
00512         rv = mURI->GetPath(path);
00513         if (NS_FAILED(rv)) return rv;
00514         // path may contain UTF-8 characters, so ensure that they're escaped.
00515         if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
00516             requestURI = &buf;
00517         else
00518             requestURI = &path;
00519         mRequestHead.SetVersion(gHttpHandler->HttpVersion());
00520     }
00521     else {
00522         rv = mURI->GetUserPass(buf);
00523         if (NS_FAILED(rv)) return rv;
00524         if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
00525                                 strncmp(mSpec.get(), "https:", 6) == 0)) {
00526             nsCOMPtr<nsIURI> tempURI;
00527             rv = mURI->Clone(getter_AddRefs(tempURI));
00528             if (NS_FAILED(rv)) return rv;
00529             rv = tempURI->SetUserPass(EmptyCString());
00530             if (NS_FAILED(rv)) return rv;
00531             rv = tempURI->GetAsciiSpec(path);
00532             if (NS_FAILED(rv)) return rv;
00533             requestURI = &path;
00534         }
00535         else
00536             requestURI = &mSpec;
00537         mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
00538     }
00539 
00540     // trim off the #ref portion if any...
00541     PRInt32 ref = requestURI->FindChar('#');
00542     if (ref != kNotFound)
00543         requestURI->SetLength(ref);
00544 
00545     mRequestHead.SetRequestURI(*requestURI);
00546 
00547     // set the request time for cache expiration calculations
00548     mRequestTime = NowInSeconds();
00549 
00550     // if doing a reload, force end-to-end
00551     if (mLoadFlags & LOAD_BYPASS_CACHE) {
00552         // We need to send 'Pragma:no-cache' to inhibit proxy caching even if
00553         // no proxy is configured since we might be talking with a transparent
00554         // proxy, i.e. one that operates at the network level.  See bug #14772.
00555         mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
00556         // If we're configured to speak HTTP/1.1 then also send 'Cache-control:
00557         // no-cache'
00558         if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
00559             mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
00560     }
00561     else if ((mLoadFlags & VALIDATE_ALWAYS) && (mCacheAccess & nsICache::ACCESS_READ)) {
00562         // We need to send 'Cache-Control: max-age=0' to force each cache along
00563         // the path to the origin server to revalidate its own entry, if any,
00564         // with the next cache or server.  See bug #84847.
00565         //
00566         // If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
00567         if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
00568             mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("max-age=0"), PR_TRUE);
00569         else
00570             mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
00571     }
00572 
00573     if (mResuming) {
00574         char byteRange[32];
00575         PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
00576         mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
00577 
00578         if (!mEntityID.IsEmpty()) {
00579             // Also, we want an error if this resource changed in the meantime
00580             // Format of the entity id is: escaped_etag/size/lastmod
00581             nsCString::const_iterator start, end, slash;
00582             mEntityID.BeginReading(start);
00583             mEntityID.EndReading(end);
00584             mEntityID.BeginReading(slash);
00585 
00586             if (FindCharInReadable('/', slash, end)) {
00587                 nsCAutoString ifMatch;
00588                 mRequestHead.SetHeader(nsHttp::If_Match,
00589                         NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
00590 
00591                 ++slash; // Incrementing, so that searching for '/' won't find
00592                          // the same slash again
00593             }
00594 
00595             if (FindCharInReadable('/', slash, end)) {
00596                 mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
00597                         Substring(++slash, end));
00598             }
00599         }
00600     }
00601 
00602     // create wrapper for this channel's notification callbacks
00603     nsCOMPtr<nsIInterfaceRequestor> callbacks;
00604     NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
00605                                            getter_AddRefs(callbacks));
00606     if (!callbacks)
00607         return NS_ERROR_OUT_OF_MEMORY;
00608 
00609     // create the transaction object
00610     mTransaction = new nsHttpTransaction();
00611     if (!mTransaction)
00612         return NS_ERROR_OUT_OF_MEMORY;
00613     NS_ADDREF(mTransaction);
00614 
00615     nsCOMPtr<nsIAsyncInputStream> responseStream;
00616     rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
00617                             mUploadStream, mUploadStreamHasHeaders,
00618                             mEventQ, callbacks, this,
00619                             getter_AddRefs(responseStream));
00620     if (NS_FAILED(rv)) return rv;
00621 
00622     rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
00623                                    responseStream);
00624     return rv;
00625 }
00626 
00627 void
00628 nsHttpChannel::AddCookiesToRequest()
00629 {
00630     nsXPIDLCString cookie;
00631 
00632     nsICookieService *cs = gHttpHandler->GetCookieService();
00633     if (cs)
00634         cs->GetCookieStringFromHttp(mURI,
00635                                     mDocumentURI ? mDocumentURI : mOriginalURI,
00636                                     this,
00637                                     getter_Copies(cookie));
00638     if (cookie.IsEmpty())
00639         cookie = mUserSetCookieHeader;
00640     else if (!mUserSetCookieHeader.IsEmpty())
00641         cookie.Append(NS_LITERAL_CSTRING("; ") + mUserSetCookieHeader);
00642 
00643     // overwrite any existing cookie headers.  be sure to clear any
00644     // existing cookies if we have no cookies to set or if the cookie
00645     // service is unavailable.
00646     mRequestHead.SetHeader(nsHttp::Cookie, cookie, PR_FALSE);
00647 }
00648 
00649 void
00650 nsHttpChannel::ApplyContentConversions()
00651 {
00652     if (!mResponseHead)
00653         return;
00654 
00655     LOG(("nsHttpChannel::ApplyContentConversions [this=%x]\n", this));
00656 
00657     if (!mApplyConversion) {
00658         LOG(("not applying conversion per mApplyConversion\n"));
00659         return;
00660     }
00661 
00662     const char *val = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
00663     if (gHttpHandler->IsAcceptableEncoding(val)) {
00664         nsCOMPtr<nsIStreamConverterService> serv;
00665         nsresult rv = gHttpHandler->
00666                 GetStreamConverterService(getter_AddRefs(serv));
00667         // we won't fail to load the page just because we couldn't load the
00668         // stream converter service.. carry on..
00669         if (NS_SUCCEEDED(rv)) {
00670             nsCOMPtr<nsIStreamListener> converter;
00671             nsCAutoString from(val);
00672             ToLowerCase(from);
00673             rv = serv->AsyncConvertData(from.get(),
00674                                         "uncompressed",
00675                                         mListener,
00676                                         mListenerContext,
00677                                         getter_AddRefs(converter));
00678             if (NS_SUCCEEDED(rv)) {
00679                 LOG(("converter installed from \'%s\' to \'uncompressed\'\n", val));
00680                 mListener = converter;
00681             }
00682         }
00683     }
00684 }
00685 
00686 static void
00687 CallTypeSniffers(void *aClosure, const PRUint8 *aData, PRUint32 aCount)
00688 {
00689   nsIChannel *chan = NS_STATIC_CAST(nsIChannel*, aClosure);
00690 
00691   const nsCOMArray<nsIContentSniffer_MOZILLA_1_8_BRANCH>& sniffers =
00692     gIOService->GetContentSniffers();
00693   PRUint32 length = sniffers.Count();
00694   for (PRUint32 i = 0; i < length; ++i) {
00695     nsCAutoString newType;
00696     nsresult rv =
00697       sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType);
00698     if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) {
00699       chan->SetContentType(newType);
00700       break;
00701     }
00702   }
00703 }
00704 
00705 nsresult
00706 nsHttpChannel::CallOnStartRequest()
00707 {
00708     if (mResponseHead && mResponseHead->ContentType().IsEmpty()) {
00709         if (!mContentTypeHint.IsEmpty())
00710             mResponseHead->SetContentType(mContentTypeHint);
00711         else {
00712             // Uh-oh.  We had better find out what type we are!
00713 
00714             // XXX This does not work with content-encodings...  but
00715             // neither does applying the conversion from the URILoader
00716 
00717             nsCOMPtr<nsIStreamConverterService> serv;
00718             nsresult rv = gHttpHandler->
00719                 GetStreamConverterService(getter_AddRefs(serv));
00720             // If we failed, we just fall through to the "normal" case
00721             if (NS_SUCCEEDED(rv)) {
00722                 nsCOMPtr<nsIStreamListener> converter;
00723                 rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
00724                                             "*/*",
00725                                             mListener,
00726                                             mListenerContext,
00727                                             getter_AddRefs(converter));
00728                 if (NS_SUCCEEDED(rv)) {
00729                     mListener = converter;
00730                 }
00731             }
00732         }
00733     }
00734 
00735     if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
00736         mResponseHead->SetContentCharset(mContentCharsetHint);
00737 
00738     if (mResponseHead)
00739         SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
00740                            mResponseHead->ContentLength());
00741 
00742     // Allow consumers to override our content type
00743     if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) &&
00744         gIOService->GetContentSniffers().Count() != 0) {
00745         // NOTE: We can have both a txn pump and a cache pump when the cache
00746         // content is partial. In that case, we need to read from the cache,
00747         // because that's the one that has the initial contents.
00748         nsInputStreamPump* pump = mCachePump ? mCachePump : mTransactionPump;
00749         pump->PeekStream(CallTypeSniffers, NS_STATIC_CAST(nsIChannel*, this));
00750     }
00751 
00752     LOG(("  calling mListener->OnStartRequest\n"));
00753     nsresult rv = mListener->OnStartRequest(this, mListenerContext);
00754     if (NS_FAILED(rv)) return rv;
00755 
00756     // install stream converter if required
00757     ApplyContentConversions();
00758 
00759     return rv;
00760 }
00761 
00762 nsresult
00763 nsHttpChannel::ProcessResponse()
00764 {
00765     nsresult rv;
00766     PRUint32 httpStatus = mResponseHead->Status();
00767 
00768     LOG(("nsHttpChannel::ProcessResponse [this=%x httpStatus=%u]\n",
00769         this, httpStatus));
00770 
00771     // notify "http-on-examine-response" observers
00772     gHttpHandler->OnExamineResponse(this);
00773 
00774     // set cookies, if any exist; done after OnExamineResponse to allow those
00775     // observers to modify the cookie response headers
00776     SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
00777 
00778     // handle unused username and password in url (see bug 232567)
00779     if (httpStatus != 401 && httpStatus != 407) {
00780         CheckForSuperfluousAuth();
00781         if (mCanceled)
00782             return CallOnStartRequest();
00783 
00784         if (mAuthContinuationState) {
00785             // reset the current continuation state because our last
00786             // authentication attempt has been completed successfully
00787             NS_RELEASE(mAuthContinuationState);
00788             LOG(("  continuation state has been reset"));
00789         }
00790     }
00791 
00792     // handle different server response categories.  Note that we handle
00793     // caching or not caching of error pages in
00794     // nsHttpResponseHead::MustValidate; if you change this switch, update that
00795     // one
00796     switch (httpStatus) {
00797     case 200:
00798     case 203:
00799         // Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
00800         // So if a server does that and sends 200 instead of 206 that we
00801         // expect, notify our caller.
00802         if (mResuming) {
00803             Cancel(NS_ERROR_NOT_RESUMABLE);
00804             rv = CallOnStartRequest();
00805             break;
00806         }
00807         // these can normally be cached
00808         rv = ProcessNormal();
00809         break;
00810     case 206:
00811         if (mCachedContentIsPartial) // an internal byte range request...
00812             rv = ProcessPartialContent();
00813         else
00814             rv = ProcessNormal();
00815         break;
00816     case 300:
00817     case 301:
00818     case 302:
00819     case 307:
00820     case 303:
00821 #if 0
00822     case 305: // disabled as a security measure (see bug 187996).
00823 #endif
00824         // don't store the response body for redirects
00825         rv = ProcessRedirection(httpStatus);
00826         if (NS_SUCCEEDED(rv))
00827             CloseCacheEntry(InitCacheEntry());
00828         else {
00829             LOG(("ProcessRedirection failed [rv=%x]\n", rv));
00830             rv = ProcessNormal();
00831         }
00832         break;
00833     case 304:
00834         rv = ProcessNotModified();
00835         if (NS_FAILED(rv)) {
00836             LOG(("ProcessNotModified failed [rv=%x]\n", rv));
00837             rv = ProcessNormal();
00838         }
00839         break;
00840     case 401:
00841     case 407:
00842         rv = ProcessAuthentication(httpStatus);
00843         if (NS_FAILED(rv)) {
00844             LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
00845             CheckForSuperfluousAuth();
00846             rv = ProcessNormal();
00847         }
00848         break;
00849     case 412: // Precondition failed
00850     case 416: // Invalid range
00851         if (mResuming) {
00852             Cancel(NS_ERROR_ENTITY_CHANGED);
00853             rv = CallOnStartRequest();
00854             break;
00855         }
00856         // fall through
00857     default:
00858         rv = ProcessNormal();
00859         break;
00860     }
00861 
00862     return rv;
00863 }
00864 
00865 nsresult
00866 nsHttpChannel::ProcessNormal()
00867 {
00868     nsresult rv;
00869 
00870     LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
00871 
00872     // if we're here, then any byte-range requests failed to result in a partial
00873     // response.  we must clear this flag to prevent BufferPartialContent from
00874     // being called inside our OnDataAvailable (see bug 136678).
00875     mCachedContentIsPartial = PR_FALSE;
00876 
00877     // For .gz files, apache sends both a Content-Type: application/x-gzip
00878     // as well as Content-Encoding: gzip, which is completely wrong.  In
00879     // this case, we choose to ignore the rogue Content-Encoding header. We
00880     // must do this early on so as to prevent it from being seen up stream.
00881     // The same problem exists for Content-Encoding: compress in default
00882     // Apache installs.
00883     const char *encoding = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
00884     if (encoding && PL_strcasestr(encoding, "gzip") && (
00885         mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
00886         mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
00887         mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
00888         // clear the Content-Encoding header
00889         mResponseHead->ClearHeader(nsHttp::Content_Encoding);
00890     }
00891     else if (encoding && PL_strcasestr(encoding, "compress") && (
00892              mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
00893              mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
00894         // clear the Content-Encoding header
00895         mResponseHead->ClearHeader(nsHttp::Content_Encoding);
00896     }
00897 
00898     // this must be called before firing OnStartRequest, since http clients,
00899     // such as imagelib, expect our cache entry to already have the correct
00900     // expiration time (bug 87710).
00901     if (mCacheEntry) {
00902         rv = InitCacheEntry();
00903         if (NS_FAILED(rv))
00904             CloseCacheEntry(NS_BINDING_ABORTED);
00905     }
00906 
00907     // Check that the server sent us what we were asking for
00908     if (mResuming) {
00909         // Create an entity id from the response
00910         nsCAutoString id;
00911         rv = GetEntityID(id);
00912         if (NS_FAILED(rv)) {
00913             // If creating an entity id is not possible -> error
00914             Cancel(NS_ERROR_NOT_RESUMABLE);
00915         }
00916         // If we were passed an entity id, verify it's equal to the server's
00917         else if (!mEntityID.IsEmpty()) {
00918             if (!mEntityID.Equals(id))
00919                 Cancel(NS_ERROR_ENTITY_CHANGED);
00920         }
00921     }
00922 
00923     rv = CallOnStartRequest();
00924     if (NS_FAILED(rv)) return rv;
00925 
00926     // install cache listener if we still have a cache entry open
00927     if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE))
00928         rv = InstallCacheListener();
00929 
00930     return rv;
00931 }
00932 
00933 nsresult
00934 nsHttpChannel::PromptTempRedirect()
00935 {
00936     nsresult rv;
00937     nsCOMPtr<nsIStringBundleService> bundleService =
00938             do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
00939     if (NS_FAILED(rv)) return rv;
00940 
00941     nsCOMPtr<nsIStringBundle> stringBundle;
00942     rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
00943     if (NS_FAILED(rv)) return rv;
00944 
00945     nsXPIDLString messageString;
00946     rv = stringBundle->GetStringFromName(NS_LITERAL_STRING("RepostFormData").get(), getter_Copies(messageString));
00947     // GetStringFromName can return NS_OK and NULL messageString.
00948     if (NS_SUCCEEDED(rv) && messageString) {
00949         PRBool repost = PR_FALSE;
00950 
00951         nsCOMPtr<nsIPrompt> prompt;
00952         GetCallback(prompt);
00953         if (!prompt)
00954             return NS_ERROR_NO_INTERFACE;
00955 
00956         prompt->Confirm(nsnull, messageString, &repost);
00957         if (!repost)
00958             return NS_ERROR_FAILURE;
00959     }
00960 
00961     return rv;
00962 }
00963 
00964 nsresult
00965 nsHttpChannel::ProxyFailover()
00966 {
00967     LOG(("nsHttpChannel::ProxyFailover [this=%x]\n", this));
00968 
00969     nsresult rv;
00970 
00971     nsCOMPtr<nsIProtocolProxyService> pps =
00972             do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
00973     if (NS_FAILED(rv))
00974         return rv;
00975 
00976     nsCOMPtr<nsIProxyInfo> pi;
00977     rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
00978                                   getter_AddRefs(pi));
00979     if (NS_FAILED(rv))
00980         return rv;
00981 
00982     return ReplaceWithProxy(pi);
00983 }
00984 
00985 nsresult
00986 nsHttpChannel::ReplaceWithProxy(nsIProxyInfo *pi)
00987 {
00988     nsresult rv;
00989 
00990     nsCOMPtr<nsIChannel> newChannel;
00991     rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel));
00992     if (NS_FAILED(rv))
00993         return rv;
00994 
00995     rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
00996     if (NS_FAILED(rv))
00997         return rv;
00998 
00999     // Inform consumers about this fake redirect
01000     PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
01001     rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
01002     if (NS_FAILED(rv))
01003         return rv;
01004 
01005     // open new channel
01006     rv = newChannel->AsyncOpen(mListener, mListenerContext);
01007     if (NS_FAILED(rv))
01008         return rv;
01009 
01010     mStatus = NS_BINDING_REDIRECTED;
01011     mListener = nsnull;
01012     mListenerContext = nsnull;
01013     return rv;
01014 }
01015 
01016 nsresult
01017 nsHttpChannel::ResolveProxy()
01018 {
01019     LOG(("nsHttpChannel::ResolveProxy [this=%x]\n", this));
01020 
01021     nsresult rv;
01022 
01023     nsCOMPtr<nsIProtocolProxyService> pps =
01024             do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
01025     if (NS_FAILED(rv))
01026         return rv;
01027 
01028     return pps->AsyncResolve(mURI, 0, this, getter_AddRefs(mProxyRequest));
01029 }
01030 
01031 PRBool
01032 nsHttpChannel::ResponseWouldVary()
01033 {
01034     PRBool result = PR_FALSE;
01035     nsCAutoString buf, metaKey;
01036     mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
01037     if (!buf.IsEmpty()) {
01038         NS_NAMED_LITERAL_CSTRING(prefix, "request-");
01039 
01040         // enumerate the elements of the Vary header...
01041         char *val = buf.BeginWriting(); // going to munge buf
01042         char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
01043         while (token) {
01044             //
01045             // if "*", then assume response would vary.  technically speaking,
01046             // "Vary: header, *" is not permitted, but we allow it anyways.
01047             //
01048             // if the response depends on the value of the "Cookie" header, then
01049             // bail since we do not store cookies in the cache.  this is done
01050             // for the following reasons:
01051             //
01052             //   1- cookies can be very large in size
01053             //
01054             //   2- cookies may contain sensitive information.  (for parity with
01055             //      out policy of not storing Set-cookie headers in the cache
01056             //      meta data, we likewise do not want to store cookie headers
01057             //      here.)
01058             //
01059             // this implementation is obviously not fully standards compliant, but
01060             // it is perhaps most prudent given the above issues.
01061             //
01062             if ((*token == '*') || (PL_strcasecmp(token, "cookie") == 0)) {
01063                 result = PR_TRUE;
01064                 break;
01065             }
01066             else {
01067                 // build cache meta data key...
01068                 metaKey = prefix + nsDependentCString(token);
01069 
01070                 // check the last value of the given request header to see if it has
01071                 // since changed.  if so, then indeed the cached response is invalid.
01072                 nsXPIDLCString lastVal;
01073                 mCacheEntry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
01074                 if (lastVal) {
01075                     nsHttpAtom atom = nsHttp::ResolveAtom(token);
01076                     const char *newVal = mRequestHead.PeekHeader(atom);
01077                     if (newVal && (strcmp(newVal, lastVal) != 0)) {
01078                         result = PR_TRUE; // yes, response would vary
01079                         break;
01080                     }
01081                 }
01082                 
01083                 // next token...
01084                 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
01085             }
01086         }
01087     }
01088     return result;
01089 }
01090 
01091 //-----------------------------------------------------------------------------
01092 // nsHttpChannel <byte-range>
01093 //-----------------------------------------------------------------------------
01094 
01095 nsresult
01096 nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
01097 {
01098     // cached content has been found to be partial, add necessary request
01099     // headers to complete cache entry.
01100 
01101     // use strongest validator available...
01102     const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
01103     if (!val)
01104         val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
01105     if (!val) {
01106         // if we hit this code it means mCachedResponseHead->IsResumable() is
01107         // either broken or not being called.
01108         NS_NOTREACHED("no cache validator");
01109         return NS_ERROR_FAILURE;
01110     }
01111 
01112     char buf[32];
01113     PR_snprintf(buf, sizeof(buf), "bytes=%u-", partialLen);
01114 
01115     mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
01116     mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
01117 
01118     return NS_OK;
01119 }
01120 
01121 nsresult
01122 nsHttpChannel::ProcessPartialContent()
01123 {
01124     // ok, we've just received a 206
01125     //
01126     // we need to stream whatever data is in the cache out first, and then
01127     // pick up whatever data is on the wire, writing it into the cache.
01128 
01129     LOG(("nsHttpChannel::ProcessPartialContent [this=%x]\n", this)); 
01130 
01131     NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
01132     NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
01133 
01134     // Check if the content-encoding we now got is different from the one we
01135     // got before
01136     if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
01137                       mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
01138                       != 0) {
01139         Cancel(NS_ERROR_UNEXPECTED); // XXX need better error code
01140         return CallOnStartRequest();
01141     }
01142 
01143 
01144     // suspend the current transaction
01145     nsresult rv = mTransactionPump->Suspend();
01146     if (NS_FAILED(rv)) return rv;
01147 
01148     // merge any new headers with the cached response headers
01149     rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
01150     if (NS_FAILED(rv)) return rv;
01151 
01152     // update the cached response head
01153     nsCAutoString head;
01154     mCachedResponseHead->Flatten(head, PR_TRUE);
01155     rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
01156     if (NS_FAILED(rv)) return rv;
01157 
01158     // make the cached response be the current response
01159     delete mResponseHead;
01160     mResponseHead = mCachedResponseHead;
01161     mCachedResponseHead = 0;
01162 
01163     rv = UpdateExpirationTime();
01164     if (NS_FAILED(rv)) return rv;
01165 
01166     // notify observers interested in looking at a response that has been
01167     // merged with any cached headers (http-on-examine-merged-response).
01168     gHttpHandler->OnExamineMergedResponse(this);
01169 
01170     // the cached content is valid, although incomplete.
01171     mCachedContentIsValid = PR_TRUE;
01172     return ReadFromCache();
01173 }
01174 
01175 nsresult
01176 nsHttpChannel::OnDoneReadingPartialCacheEntry(PRBool *streamDone)
01177 {
01178     nsresult rv;
01179 
01180     LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%x]", this));
01181 
01182     // by default, assume we would have streamed all data or failed...
01183     *streamDone = PR_TRUE;
01184 
01185     // setup cache listener to append to cache entry
01186     PRUint32 size;
01187     rv = mCacheEntry->GetDataSize(&size);
01188     if (NS_FAILED(rv)) return rv;
01189 
01190     rv = InstallCacheListener(size);
01191     if (NS_FAILED(rv)) return rv;
01192 
01193     // need to track the logical offset of the data being sent to our listener
01194     mLogicalOffset = size;
01195 
01196     // we're now completing the cached content, so we can clear this flag.
01197     // this puts us in the state of a regular download.
01198     mCachedContentIsPartial = PR_FALSE;
01199 
01200     // resume the transaction if it exists, otherwise the pipe contained the
01201     // remaining part of the document and we've now streamed all of the data.
01202     if (mTransactionPump) {
01203         rv = mTransactionPump->Resume();
01204         if (NS_SUCCEEDED(rv))
01205             *streamDone = PR_FALSE;
01206     }
01207     else
01208         NS_NOTREACHED("no transaction");
01209     return rv;
01210 }
01211 
01212 //-----------------------------------------------------------------------------
01213 // nsHttpChannel <cache>
01214 //-----------------------------------------------------------------------------
01215 
01216 nsresult
01217 nsHttpChannel::ProcessNotModified()
01218 {
01219     nsresult rv;
01220 
01221     LOG(("nsHttpChannel::ProcessNotModified [this=%x]\n", this)); 
01222 
01223     NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
01224     NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
01225 
01226     // merge any new headers with the cached response headers
01227     rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
01228     if (NS_FAILED(rv)) return rv;
01229 
01230     // update the cached response head
01231     nsCAutoString head;
01232     mCachedResponseHead->Flatten(head, PR_TRUE);
01233     rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
01234     if (NS_FAILED(rv)) return rv;
01235 
01236     // make the cached response be the current response
01237     delete mResponseHead;
01238     mResponseHead = mCachedResponseHead;
01239     mCachedResponseHead = 0;
01240 
01241     rv = UpdateExpirationTime();
01242     if (NS_FAILED(rv)) return rv;
01243 
01244     // notify observers interested in looking at a reponse that has been
01245     // merged with any cached headers
01246     gHttpHandler->OnExamineMergedResponse(this);
01247 
01248     mCachedContentIsValid = PR_TRUE;
01249     rv = ReadFromCache();
01250     if (NS_FAILED(rv)) return rv;
01251 
01252     mTransactionReplaced = PR_TRUE;
01253     return NS_OK;
01254 }
01255 
01256 nsresult
01257 nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
01258 {
01259     nsresult rv;
01260 
01261     *delayed = PR_FALSE;
01262 
01263     LOG(("nsHttpChannel::OpenCacheEntry [this=%x]", this));
01264 
01265     // make sure we're not abusing this function
01266     NS_PRECONDITION(!mCacheEntry, "cache entry already open");
01267 
01268     nsCAutoString cacheKey;
01269 
01270     if (mRequestHead.Method() == nsHttp::Post) {
01271         // If the post id is already set then this is an attempt to replay
01272         // a post transaction via the cache.  Otherwise, we need a unique
01273         // post id for this transaction.
01274         if (mPostID == 0)
01275             mPostID = gHttpHandler->GenerateUniqueID();
01276     }
01277     else if ((mRequestHead.Method() != nsHttp::Get) &&
01278              (mRequestHead.Method() != nsHttp::Head)) {
01279         // don't use the cache for other types of requests
01280         return NS_OK;
01281     }
01282 
01283     if (mRequestHead.PeekHeader(nsHttp::Range)) {
01284         // we don't support caching for byte range requests initiated
01285         // by our clients or via nsIResumableChannel.
01286         // XXX perhaps we could munge their byte range into the cache
01287         // key to make caching sort'a work.
01288         return NS_OK;
01289     }
01290 
01291     if (RequestIsConditional()) {
01292         // don't use the cache if our consumer is making a conditional request
01293         // (see bug 331825).
01294         return NS_OK;
01295     }
01296 
01297     GenerateCacheKey(cacheKey);
01298 
01299     // Get a cache session with appropriate storage policy
01300     nsCacheStoragePolicy storagePolicy;
01301     if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
01302         storagePolicy = nsICache::STORE_IN_MEMORY;
01303     else
01304         storagePolicy = nsICache::STORE_ANYWHERE; // allow on disk
01305     nsCOMPtr<nsICacheSession> session;
01306     rv = gHttpHandler->GetCacheSession(storagePolicy,
01307                                        getter_AddRefs(session));
01308     if (NS_FAILED(rv)) return rv;
01309 
01310     // Set the desired cache access mode accordingly...
01311     nsCacheAccessMode accessRequested;
01312     if (offline || (mLoadFlags & INHIBIT_CACHING)) {
01313         // If we have been asked to bypass the cache and not write to the
01314         // cache, then don't use the cache at all.
01315         if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
01316             return NS_ERROR_NOT_AVAILABLE;
01317         accessRequested = nsICache::ACCESS_READ;
01318     }
01319     else if (BYPASS_LOCAL_CACHE(mLoadFlags))
01320         accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
01321     else
01322         accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing
01323 
01324     // we'll try to synchronously open the cache entry... however, it may be
01325     // in use and not yet validated, in which case we'll try asynchronously
01326     // opening the cache entry.
01327     rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
01328                                  getter_AddRefs(mCacheEntry));
01329     if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
01330         // access to the cache entry has been denied (because the cache entry
01331         // is probably in use by another channel).
01332         if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
01333             LOG(("bypassing local cache since it is busy\n"));
01334             return NS_ERROR_NOT_AVAILABLE;
01335         }
01336         rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
01337         if (NS_FAILED(rv)) return rv;
01338         // we'll have to wait for the cache entry
01339         *delayed = PR_TRUE;
01340     }
01341     else if (NS_SUCCEEDED(rv)) {
01342         mCacheEntry->GetAccessGranted(&mCacheAccess);
01343         LOG(("got cache entry [access=%x]\n", mCacheAccess));
01344     }
01345     return rv;
01346 }
01347 
01348 nsresult
01349 nsHttpChannel::GenerateCacheKey(nsACString &cacheKey)
01350 {
01351     if (mPostID) {
01352         char buf[32];
01353         PR_snprintf(buf, sizeof(buf), "id=%x&uri=", mPostID);
01354         cacheKey.Assign(buf);
01355     } else
01356         cacheKey.Truncate();
01357     
01358     // Strip any trailing #ref from the URL before using it as the key
01359     const char *spec = mSpec.get();
01360     const char *p = strchr(spec, '#');
01361     if (p)
01362         cacheKey.Append(spec, p - spec);
01363     else
01364         cacheKey.Append(spec);
01365     return NS_OK;
01366 }
01367 
01368 // UpdateExpirationTime is called when a new response comes in from the server.
01369 // It updates the stored response-time and sets the expiration time on the
01370 // cache entry.  
01371 //
01372 // From section 13.2.4 of RFC2616, we compute expiration time as follows:
01373 //
01374 //    timeRemaining = freshnessLifetime - currentAge
01375 //    expirationTime = now + timeRemaining
01376 // 
01377 nsresult
01378 nsHttpChannel::UpdateExpirationTime()
01379 {
01380     NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
01381 
01382     PRUint32 expirationTime = 0;
01383     if (!mResponseHead->MustValidate()) {
01384         PRUint32 freshnessLifetime = 0;
01385         nsresult rv;
01386 
01387         rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
01388         if (NS_FAILED(rv)) return rv;
01389 
01390         if (freshnessLifetime > 0) {
01391             PRUint32 now = NowInSeconds(), currentAge = 0;
01392 
01393             rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge); 
01394             if (NS_FAILED(rv)) return rv;
01395 
01396             LOG(("freshnessLifetime = %u, currentAge = %u\n",
01397                 freshnessLifetime, currentAge));
01398 
01399             if (freshnessLifetime > currentAge) {
01400                 PRUint32 timeRemaining = freshnessLifetime - currentAge;
01401                 // be careful... now + timeRemaining may overflow
01402                 if (now + timeRemaining < now)
01403                     expirationTime = PRUint32(-1);
01404                 else
01405                     expirationTime = now + timeRemaining;
01406             }
01407             else
01408                 expirationTime = now;
01409         }
01410     }
01411     return mCacheEntry->SetExpirationTime(expirationTime);
01412 }
01413 
01414 // CheckCache is called from Connect after a cache entry has been opened for
01415 // this URL but before going out to net.  It's purpose is to set or clear the 
01416 // mCachedContentIsValid flag, and to configure an If-Modified-Since request
01417 // if validation is required.
01418 nsresult
01419 nsHttpChannel::CheckCache()
01420 {
01421     nsresult rv = NS_OK;
01422 
01423     LOG(("nsHTTPChannel::CheckCache [this=%x entry=%x]",
01424         this, mCacheEntry.get()));
01425     
01426     // Be pessimistic: assume the cache entry has no useful data.
01427     mCachedContentIsValid = PR_FALSE;
01428 
01429     // Don't proceed unless we have opened a cache entry for reading.
01430     if (!mCacheEntry || !(mCacheAccess & nsICache::ACCESS_READ))
01431         return NS_OK;
01432 
01433     nsXPIDLCString buf;
01434 
01435     // Get the method that was used to generate the cached response
01436     rv = mCacheEntry->GetMetaDataElement("request-method", getter_Copies(buf));
01437     if (NS_FAILED(rv)) return rv;
01438 
01439     nsHttpAtom method = nsHttp::ResolveAtom(buf);
01440     if (method == nsHttp::Head) {
01441         // The cached response does not contain an entity.  We can only reuse
01442         // the response if the current request is also HEAD.
01443         if (mRequestHead.Method() != nsHttp::Head)
01444             return NS_OK;
01445     }
01446     buf.Adopt(0);
01447 
01448     // We'll need this value in later computations...
01449     PRUint32 lastModifiedTime;
01450     rv = mCacheEntry->GetLastModified(&lastModifiedTime);
01451     if (NS_FAILED(rv)) return rv;
01452 
01453     // Determine if this is the first time that this cache entry
01454     // has been accessed during this session.
01455     PRBool fromPreviousSession =
01456             (gHttpHandler->SessionStartTime() > lastModifiedTime);
01457 
01458     // Get the cached HTTP response headers
01459     rv = mCacheEntry->GetMetaDataElement("response-head", getter_Copies(buf));
01460     if (NS_FAILED(rv)) return rv;
01461 
01462     // Parse the cached HTTP response headers
01463     NS_ASSERTION(!mCachedResponseHead, "memory leak detected");
01464     mCachedResponseHead = new nsHttpResponseHead();
01465     if (!mCachedResponseHead)
01466         return NS_ERROR_OUT_OF_MEMORY;
01467     rv = mCachedResponseHead->Parse((char *) buf.get());
01468     if (NS_FAILED(rv)) return rv;
01469     buf.Adopt(0);
01470 
01471     // If we were only granted read access, then assume the entry is valid.
01472     // unless it is INHBIT_CACHING
01473     if (mCacheAccess == nsICache::ACCESS_READ &&
01474         !(mLoadFlags & INHIBIT_CACHING)) {
01475         mCachedContentIsValid = PR_TRUE;
01476         return NS_OK;
01477     }
01478 
01479     PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
01480 
01481     if (method != nsHttp::Head && !isCachedRedirect) {
01482         // If the cached content-length is set and it does not match the data
01483         // size of the cached content, then the cached response is partial...
01484         // either we need to issue a byte range request or we need to refetch
01485         // the entire document.
01486         nsInt64 contentLength = mCachedResponseHead->ContentLength();
01487         if (contentLength != nsInt64(-1)) {
01488             PRUint32 size;
01489             rv = mCacheEntry->GetDataSize(&size);
01490             if (NS_FAILED(rv)) return rv;
01491 
01492             if (nsInt64(size) != contentLength) {
01493                 LOG(("Cached data size does not match the Content-Length header "
01494                      "[content-length=%lld size=%u]\n", PRInt64(contentLength), size));
01495                 if ((nsInt64(size) < contentLength) && mCachedResponseHead->IsResumable()) {
01496                     // looks like a partial entry.
01497                     rv = SetupByteRangeRequest(size);
01498                     if (NS_FAILED(rv)) return rv;
01499                     mCachedContentIsPartial = PR_TRUE;
01500                 }
01501                 return NS_OK;
01502             }
01503         }
01504     }
01505 
01506     PRBool doValidation = PR_FALSE;
01507 
01508     // Be optimistic: assume that we won't need to do validation
01509     mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
01510     mRequestHead.ClearHeader(nsHttp::If_None_Match);
01511 
01512     // If the LOAD_FROM_CACHE flag is set, any cached data can simply be used.
01513     if (mLoadFlags & LOAD_FROM_CACHE) {
01514         LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
01515         doValidation = PR_FALSE;
01516     }
01517     // If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
01518     // it's revalidated with the server.
01519     else if (mLoadFlags & VALIDATE_ALWAYS) {
01520         LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
01521         doValidation = PR_TRUE;
01522     }
01523     // Even if the VALIDATE_NEVER flag is set, there are still some cases in
01524     // which we must validate the cached response with the server.
01525     else if (mLoadFlags & VALIDATE_NEVER) {
01526         LOG(("VALIDATE_NEVER set\n"));
01527         // if no-store or if no-cache and ssl, validate cached response (see
01528         // bug 112564 for an explanation of this logic)
01529         if (mCachedResponseHead->NoStore() ||
01530            (mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
01531             LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
01532             doValidation = PR_TRUE;
01533         }
01534         else {
01535             LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
01536             doValidation = PR_FALSE;
01537         }
01538     }
01539     // check if validation is strictly required...
01540     else if (mCachedResponseHead->MustValidate()) {
01541         LOG(("Validating based on MustValidate() returning TRUE\n"));
01542         doValidation = PR_TRUE;
01543     }
01544 
01545     else if (ResponseWouldVary()) {
01546         LOG(("Validating based on Vary headers returning TRUE\n"));
01547         doValidation = PR_TRUE;
01548     }
01549     // Check if the cache entry has expired...
01550     else {
01551         PRUint32 time = 0; // a temporary variable for storing time values...
01552 
01553         rv = mCacheEntry->GetExpirationTime(&time);
01554         if (NS_FAILED(rv)) return rv;
01555 
01556         if (NowInSeconds() <= time)
01557             doValidation = PR_FALSE;
01558         else if (mCachedResponseHead->MustValidateIfExpired())
01559             doValidation = PR_TRUE;
01560         else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
01561             // If the cached response does not include expiration infor-
01562             // mation, then we must validate the response, despite whether
01563             // or not this is the first access this session.  This behavior
01564             // is consistent with existing browsers and is generally expected
01565             // by web authors.
01566             rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
01567             if (NS_FAILED(rv)) return rv;
01568 
01569             if (time == 0)
01570                 doValidation = PR_TRUE;
01571             else
01572                 doValidation = fromPreviousSession;
01573         }
01574         else
01575             doValidation = PR_TRUE;
01576 
01577         LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
01578     }
01579 
01580     if (!doValidation) {
01581         //
01582         // Check the authorization headers used to generate the cache entry.
01583         // We must validate the cache entry if:
01584         //
01585         // 1) the cache entry was generated prior to this session w/
01586         //    credentials (see bug 103402).
01587         // 2) the cache entry was generated w/o credentials, but would now
01588         //    require credentials (see bug 96705).
01589         //
01590         // NOTE: this does not apply to proxy authentication.
01591         //
01592         mCacheEntry->GetMetaDataElement("auth", getter_Copies(buf));
01593         doValidation =
01594             (fromPreviousSession && !buf.IsEmpty()) ||
01595             (buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
01596     }
01597 
01598     if (!doValidation) {
01599         // Sites redirect back to the original URI after setting a session/tracking
01600         // cookie. In such cases, force revalidation so that we hit the net and do not
01601         // cycle thru cached responses.
01602         if (isCachedRedirect && mRequestHead.PeekHeader(nsHttp::Cookie))
01603             doValidation = PR_TRUE;
01604     }
01605 
01606     mCachedContentIsValid = !doValidation;
01607 
01608     if (doValidation) {
01609         //
01610         // now, we are definitely going to issue a HTTP request to the server.
01611         // make it conditional if possible.
01612         //
01613         // do not attempt to validate no-store content, since servers will not
01614         // expect it to be cached.  (we only keep it in our cache for the
01615         // purposes of back/forward, etc.)
01616         //
01617         // the request method MUST be either GET or HEAD (see bug 175641).
01618         //
01619         if (!mCachedResponseHead->NoStore() &&
01620             (mRequestHead.Method() == nsHttp::Get ||
01621              mRequestHead.Method() == nsHttp::Head)) {
01622             const char *val;
01623             // Add If-Modified-Since header if a Last-Modified was given
01624             val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
01625             if (val)
01626                 mRequestHead.SetHeader(nsHttp::If_Modified_Since,
01627                                        nsDependentCString(val));
01628             // Add If-None-Match header if an ETag was given in the response
01629             val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
01630             if (val)
01631                 mRequestHead.SetHeader(nsHttp::If_None_Match,
01632                                        nsDependentCString(val));
01633         }
01634     }
01635 
01636     LOG(("CheckCache [this=%x doValidation=%d]\n", this, doValidation));
01637     return NS_OK;
01638 }
01639 
01640 // If the data in the cache hasn't expired, then there's no need to
01641 // talk with the server, not even to do an if-modified-since.  This
01642 // method creates a stream from the cache, synthesizing all the various
01643 // channel-related events.
01644 nsresult
01645 nsHttpChannel::ReadFromCache()
01646 {
01647     nsresult rv;
01648 
01649     NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
01650     NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
01651 
01652     LOG(("nsHttpChannel::ReadFromCache [this=%x] "
01653          "Using cached copy of: %s\n", this, mSpec.get()));
01654 
01655     if (mCachedResponseHead) {
01656         NS_ASSERTION(!mResponseHead, "memory leak");
01657         mResponseHead = mCachedResponseHead;
01658         mCachedResponseHead = 0;
01659     }
01660 
01661     // if we don't already have security info, try to get it from the cache 
01662     // entry. there are two cases to consider here: 1) we are just reading
01663     // from the cache, or 2) this may be due to a 304 not modified response,
01664     // in which case we could have security info from a socket transport.
01665     if (!mSecurityInfo)
01666         mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
01667 
01668     if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) {
01669         // We have write access to the cache, but we don't need to go to the
01670         // server to validate at this time, so just mark the cache entry as
01671         // valid in order to allow others access to this cache entry.
01672         mCacheEntry->MarkValid();
01673     }
01674 
01675     // if this is a cached redirect, we must process the redirect asynchronously
01676     // since AsyncOpen may not have returned yet.  Make sure there is a Location
01677     // header, otherwise we'll have to treat this like a normal 200 response.
01678     if (mResponseHead && (mResponseHead->Status() / 100 == 3) 
01679                       && (mResponseHead->PeekHeader(nsHttp::Location)))
01680         return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
01681 
01682     // have we been configured to skip reading from the cache?
01683     if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
01684         LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED load flag\n"));
01685         return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
01686     }
01687 
01688     // open input stream for reading...
01689     nsCOMPtr<nsIInputStream> stream;
01690     rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
01691     if (NS_FAILED(rv)) return rv;
01692 
01693     rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump),
01694                                    stream, nsInt64(-1), nsInt64(-1), 0, 0,
01695                                    PR_TRUE);
01696     if (NS_FAILED(rv)) return rv;
01697 
01698     return mCachePump->AsyncRead(this, mListenerContext);
01699 }
01700 
01701 nsresult
01702 nsHttpChannel::CloseCacheEntry(nsresult status)
01703 {
01704     nsresult rv = NS_OK;
01705     if (mCacheEntry) {
01706         LOG(("nsHttpChannel::CloseCacheEntry [this=%x status=%x]", this, status));
01707 
01708         // don't doom the cache entry if only reading from it...
01709         if (NS_FAILED(status)
01710                 && (mCacheAccess & nsICache::ACCESS_WRITE) && !mCachePump) {
01711             LOG(("dooming cache entry!!"));
01712             rv = mCacheEntry->Doom();
01713         }
01714 
01715         if (mCachedResponseHead) {
01716             delete mCachedResponseHead;
01717             mCachedResponseHead = 0;
01718         }
01719 
01720         mCachePump = 0;
01721         mCacheEntry = 0;
01722         mCacheAccess = 0;
01723         mOpenedCacheForWriting = PR_FALSE;
01724     }
01725     return rv;
01726 }
01727 
01728 // Initialize the cache entry for writing.
01729 //  - finalize storage policy
01730 //  - store security info
01731 //  - update expiration time
01732 //  - store headers and other meta data
01733 nsresult
01734 nsHttpChannel::InitCacheEntry()
01735 {
01736     nsresult rv;
01737 
01738     NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
01739     // if only reading, nothing to be done here.
01740     if (mCacheAccess == nsICache::ACCESS_READ)
01741         return NS_OK;
01742 
01743     // Don't cache the response again if already cached...
01744     if (mCachedContentIsValid)
01745         return NS_OK;
01746 
01747     LOG(("nsHttpChannel::InitCacheEntry [this=%x entry=%x]\n",
01748         this, mCacheEntry.get()));
01749 
01750     // The no-store directive within the 'Cache-Control:' header indicates
01751     // that we must not store the response in a persistent cache.
01752     if (mResponseHead->NoStore())
01753         mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
01754 
01755     // For HTTPS transactions, the storage policy will already be IN_MEMORY.
01756     // We are concerned instead about load attributes which may have changed.
01757     if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
01758         rv = mCacheEntry->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
01759         if (NS_FAILED(rv)) return rv;
01760     }
01761 
01762     // Store secure data in memory only
01763     if (mSecurityInfo)
01764         mCacheEntry->SetSecurityInfo(mSecurityInfo);
01765 
01766     // Set the expiration time for this cache entry
01767     rv = UpdateExpirationTime();
01768     if (NS_FAILED(rv)) return rv;
01769 
01770     // Store the HTTP request method with the cache entry so we can distinguish
01771     // for example GET and HEAD responses.
01772     rv = mCacheEntry->SetMetaDataElement("request-method", mRequestHead.Method().get());
01773     if (NS_FAILED(rv)) return rv;
01774 
01775     // Store the HTTP authorization scheme used if any...
01776     rv = StoreAuthorizationMetaData();
01777     if (NS_FAILED(rv)) return rv;
01778 
01779     // Iterate over the headers listed in the Vary response header, and
01780     // store the value of the corresponding request header so we can verify
01781     // that it has not varied when we try to re-use the cached response at
01782     // a later time.  Take care not to store "Cookie" headers though.  We
01783     // take care of "Vary: cookie" in ResponseWouldVary.
01784     //
01785     // NOTE: if "Vary: accept, cookie", then we will store the "accept" header
01786     // in the cache.  we could try to avoid needlessly storing the "accept"
01787     // header in this case, but it doesn't seem worth the extra code to perform
01788     // the check.
01789     {
01790         nsCAutoString buf, metaKey;
01791         mResponseHead->GetHeader(nsHttp::Vary, buf);
01792         if (!buf.IsEmpty()) {
01793             NS_NAMED_LITERAL_CSTRING(prefix, "request-");
01794            
01795             char *val = buf.BeginWriting(); // going to munge buf
01796             char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
01797             while (token) {
01798                 if ((*token != '*') && (PL_strcasecmp(token, "cookie") != 0)) {
01799                     nsHttpAtom atom = nsHttp::ResolveAtom(token);
01800                     const char *requestVal = mRequestHead.PeekHeader(atom);
01801                     if (requestVal) {
01802                         // build cache meta data key and set meta data element...
01803                         metaKey = prefix + nsDependentCString(token);
01804                         mCacheEntry->SetMetaDataElement(metaKey.get(), requestVal);
01805                     }
01806                 }
01807                 token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
01808             }
01809         }
01810     }
01811 
01812 
01813     // Store the received HTTP head with the cache entry as an element of
01814     // the meta data.
01815     nsCAutoString head;
01816     mResponseHead->Flatten(head, PR_TRUE);
01817     return mCacheEntry->SetMetaDataElement("response-head", head.get());
01818 }
01819 
01820 nsresult
01821 nsHttpChannel::StoreAuthorizationMetaData()
01822 {
01823     // Not applicable to proxy authorization...
01824     const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
01825     if (val) {
01826         // eg. [Basic realm="wally world"]
01827         nsCAutoString buf(Substring(val, strchr(val, ' ')));
01828         return mCacheEntry->SetMetaDataElement("auth", buf.get());
01829     }
01830     return NS_OK;
01831 }
01832 
01833 // Finalize the cache entry
01834 //  - may need to rewrite response headers if any headers changed
01835 //  - may need to recalculate the expiration time if any headers changed
01836 //  - called only for freshly written cache entries
01837 nsresult
01838 nsHttpChannel::FinalizeCacheEntry()
01839 {
01840     LOG(("nsHttpChannel::FinalizeCacheEntry [this=%x]\n", this));
01841 
01842     if (mResponseHead && mResponseHeadersModified) {
01843         // Set the expiration time for this cache entry
01844         nsresult rv = UpdateExpirationTime();
01845         if (NS_FAILED(rv)) return rv;
01846     }
01847     return NS_OK;
01848 }
01849 
01850 // Open an output stream to the cache entry and insert a listener tee into
01851 // the chain of response listeners.
01852 nsresult
01853 nsHttpChannel::InstallCacheListener(PRUint32 offset)
01854 {
01855     nsresult rv;
01856 
01857     LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
01858 
01859     NS_ASSERTION(mCacheEntry, "no cache entry");
01860     NS_ASSERTION(mListener, "no listener");
01861 
01862     nsCOMPtr<nsIOutputStream> out;
01863     rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
01864     if (NS_FAILED(rv)) return rv;
01865 
01866     mOpenedCacheForWriting = PR_TRUE;
01867 
01868     // XXX disk cache does not support overlapped i/o yet
01869 #if 0
01870     // Mark entry valid inorder to allow simultaneous reading...
01871     rv = mCacheEntry->MarkValid();
01872     if (NS_FAILED(rv)) return rv;
01873 #endif
01874 
01875     nsCOMPtr<nsIStreamListenerTee> tee =
01876         do_CreateInstance(kStreamListenerTeeCID, &rv);
01877     if (NS_FAILED(rv)) return rv;
01878 
01879     rv = tee->Init(mListener, out);
01880     if (NS_FAILED(rv)) return rv;
01881 
01882     mListener = tee;
01883     return NS_OK;
01884 }
01885 
01886 //-----------------------------------------------------------------------------
01887 // nsHttpChannel <redirect>
01888 //-----------------------------------------------------------------------------
01889 
01890 PR_STATIC_CALLBACK(PLDHashOperator)
01891 CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure)
01892 {
01893     nsIWritablePropertyBag* bag = NS_STATIC_CAST(nsIWritablePropertyBag*,
01894                                                  aClosure);
01895     bag->SetProperty(aKey, aData);
01896     return PL_DHASH_NEXT;
01897 }
01898 
01899 nsresult
01900 nsHttpChannel::SetupReplacementChannel(nsIURI       *newURI, 
01901                                        nsIChannel   *newChannel,
01902                                        PRBool        preserveMethod)
01903 {
01904     PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE;
01905     // if the original channel was using SSL and this channel is not using
01906     // SSL, then no need to inhibit persistent caching.  however, if the
01907     // original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
01908     // set, then allow the flag to apply to the redirected channel as well.
01909     // since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
01910     // we only need to check if the original channel was using SSL.
01911     if (mConnectionInfo->UsingSSL())
01912         newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
01913 
01914     newChannel->SetOriginalURI(mOriginalURI);
01915     newChannel->SetLoadGroup(mLoadGroup); 
01916     newChannel->SetNotificationCallbacks(mCallbacks);
01917     newChannel->SetLoadFlags(newLoadFlags);
01918 
01919     nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
01920     if (!httpChannel)
01921         return NS_OK; // no other options to set
01922 
01923     if (preserveMethod) {
01924         nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
01925         if (mUploadStream && uploadChannel) {
01926             // rewind upload stream
01927             nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
01928             if (seekable)
01929                 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
01930 
01931             // replicate original call to SetUploadStream...
01932             if (mUploadStreamHasHeaders)
01933                 uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
01934             else {
01935                 const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
01936                 const char *clen  = mRequestHead.PeekHeader(nsHttp::Content_Length);
01937                 if (ctype && clen)
01938                     uploadChannel->SetUploadStream(mUploadStream,
01939                                                    nsDependentCString(ctype),
01940                                                    atoi(clen));
01941             }
01942         }
01943         // must happen after setting upload stream since SetUploadStream
01944         // may change the request method.
01945         httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
01946     }
01947     // convey the referrer if one was used for this channel to the next one
01948     if (mReferrer)
01949         httpChannel->SetReferrer(mReferrer);
01950     // convey the mAllowPipelining flag
01951     httpChannel->SetAllowPipelining(mAllowPipelining);
01952     // convey the new redirection limit
01953     httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
01954 
01955     nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
01956     if (httpInternal) {
01957         // update the DocumentURI indicator since we are being redirected.
01958         // if this was a top-level document channel, then the new channel
01959         // should have its mDocumentURI point to newURI; otherwise, we
01960         // just need to pass along our mDocumentURI to the new channel.
01961         if (newURI && (mURI == mDocumentURI))
01962             httpInternal->SetDocumentURI(newURI);
01963         else
01964             httpInternal->SetDocumentURI(mDocumentURI);
01965     } 
01966     
01967     // convey the mApplyConversion flag (bug 91862)
01968     nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
01969     if (encodedChannel)
01970         encodedChannel->SetApplyConversion(mApplyConversion);
01971 
01972     // transfer the resume information
01973     if (mResuming) {
01974         nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
01975         if (!resumableChannel) {
01976             NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
01977             return NS_ERROR_NOT_RESUMABLE;
01978         }
01979         resumableChannel->ResumeAt(mStartPos, mEntityID);
01980     }
01981 
01982     // transfer any properties
01983     nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
01984     if (bag)
01985         mPropertyHash.EnumerateRead(CopyProperties, bag.get());
01986 
01987     return NS_OK;
01988 }
01989 
01990 nsresult
01991 nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
01992 {
01993     LOG(("nsHttpChannel::ProcessRedirection [this=%x type=%u]\n",
01994         this, redirectType));
01995 
01996     const char *location = mResponseHead->PeekHeader(nsHttp::Location);
01997 
01998     // if a location header was not given, then we can't perform the redirect,
01999     // so just carry on as though this were a normal response.
02000     if (!location)
02001         return NS_ERROR_FAILURE;
02002 
02003     // make sure non-ASCII characters in the location header are escaped.
02004     nsCAutoString locationBuf;
02005     if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
02006         location = locationBuf.get();
02007 
02008     if (mRedirectionLimit == 0) {
02009         LOG(("redirection limit reached!\n"));
02010         // this error code is fatal, and should be conveyed to our listener.
02011         Cancel(NS_ERROR_REDIRECT_LOOP);
02012         return NS_ERROR_REDIRECT_LOOP;
02013     }
02014 
02015     LOG(("redirecting to: %s [redirection-limit=%u]\n",
02016         location, PRUint32(mRedirectionLimit)));
02017 
02018     nsresult rv;
02019     nsCOMPtr<nsIChannel> newChannel;
02020     nsCOMPtr<nsIURI> newURI;
02021 
02022     // create a new URI using the location header and the current URL
02023     // as a base...
02024     nsCOMPtr<nsIIOService> ioService;
02025     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
02026     if (NS_FAILED(rv)) return rv;
02027 
02028     // the new uri should inherit the origin charset of the current uri
02029     nsCAutoString originCharset;
02030     rv = mURI->GetOriginCharset(originCharset);
02031     if (NS_FAILED(rv))
02032         originCharset.Truncate();
02033 
02034     rv = ioService->NewURI(nsDependentCString(location), originCharset.get(), mURI,
02035                            getter_AddRefs(newURI));
02036     if (NS_FAILED(rv)) return rv;
02037 
02038     // verify that this is a legal redirect
02039     nsCOMPtr<nsIScriptSecurityManager> securityManager = 
02040              do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
02041     if (securityManager) {
02042         rv = securityManager->CheckLoadURI(mURI, newURI,
02043                                            nsIScriptSecurityManager::DISALLOW_FROM_MAIL |
02044                                            nsIScriptSecurityManager::DISALLOW_SCRIPT_OR_DATA);
02045         if (NS_FAILED(rv)) return rv;
02046     }
02047 
02048     // Kill the current cache entry if we are redirecting
02049     // back to ourself.
02050     PRBool redirectingBackToSameURI = PR_FALSE;
02051     if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
02052         NS_SUCCEEDED(mURI->Equals(newURI, &redirectingBackToSameURI)) &&
02053         redirectingBackToSameURI)
02054             mCacheEntry->Doom();
02055 
02056     // move the reference of the old location to the new one if the new
02057     // one has none.
02058     nsCOMPtr<nsIURL> newURL = do_QueryInterface(newURI, &rv);
02059     if (NS_SUCCEEDED(rv)) {
02060         nsCAutoString ref;
02061         rv = newURL->GetRef(ref);
02062         if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
02063             nsCOMPtr<nsIURL> baseURL( do_QueryInterface(mURI, &rv) );
02064             if (NS_SUCCEEDED(rv)) {
02065                 baseURL->GetRef(ref);
02066                 if (!ref.IsEmpty())
02067                     newURL->SetRef(ref);
02068             }
02069         }
02070     }
02071 
02072     // if we need to re-send POST data then be sure to ask the user first.
02073     PRBool preserveMethod = (redirectType == 307);
02074     if (preserveMethod && mUploadStream) {
02075         rv = PromptTempRedirect();
02076         if (NS_FAILED(rv)) return rv;
02077     }
02078 
02079     rv = ioService->NewChannelFromURI(newURI, getter_AddRefs(newChannel));
02080     if (NS_FAILED(rv)) return rv;
02081 
02082     rv = SetupReplacementChannel(newURI, newChannel, preserveMethod);
02083     if (NS_FAILED(rv)) return rv;
02084 
02085     // call out to the event sink to notify it of this redirection.
02086     PRUint32 redirectFlags;
02087     if (redirectType == 301) // Moved Permanently
02088         redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
02089     else
02090         redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
02091     rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
02092     if (NS_FAILED(rv))
02093         return rv;
02094 
02095     // And now, the deprecated way
02096     nsCOMPtr<nsIHttpEventSink> httpEventSink;
02097     GetCallback(httpEventSink);
02098     if (httpEventSink) {
02099         // NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
02100         // versions.
02101         rv = httpEventSink->OnRedirect(this, newChannel);
02102         if (NS_FAILED(rv)) return rv;
02103     }
02104     // XXX we used to talk directly with the script security manager, but that
02105     // should really be handled by the event sink implementation.
02106 
02107     // begin loading the new channel
02108     rv = newChannel->AsyncOpen(mListener, mListenerContext);
02109     if (NS_FAILED(rv)) return rv;
02110 
02111     // close down this channel
02112     Cancel(NS_BINDING_REDIRECTED);
02113     
02114     // disconnect from our listener
02115     mListener = 0;
02116     mListenerContext = 0;
02117     return NS_OK;
02118 }
02119 
02120 //-----------------------------------------------------------------------------
02121 // nsHttpChannel <auth>
02122 //-----------------------------------------------------------------------------
02123 
02124 // buf contains "domain\user"
02125 static void
02126 ParseUserDomain(PRUnichar *buf,
02127                 const PRUnichar **user,
02128                 const PRUnichar **domain)
02129 {
02130     PRUnichar *p = buf;
02131     while (*p && *p != '\\') ++p;
02132     if (!*p)
02133         return;
02134     *p = '\0';
02135     *domain = buf;
02136     *user = p + 1;
02137 }
02138 
02139 // helper function for setting identity from raw user:pass
02140 static void
02141 SetIdent(nsHttpAuthIdentity &ident,
02142          PRUint32 authFlags,
02143          PRUnichar *userBuf,
02144          PRUnichar *passBuf)
02145 {
02146     const PRUnichar *user = userBuf;
02147     const PRUnichar *domain = nsnull;
02148 
02149     if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
02150         ParseUserDomain(userBuf, &user, &domain);
02151 
02152     ident.Set(domain, user, passBuf);
02153 }
02154 
02155 // helper function for getting an auth prompt from an interface requestor
02156 static void
02157 GetAuthPrompt(nsIInterfaceRequestor *ifreq, PRBool proxyAuth,
02158               nsIAuthPrompt **result)
02159 {
02160     if (!ifreq)
02161         return;
02162 
02163     PRUint32 promptReason;
02164     if (proxyAuth)
02165         promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
02166     else 
02167         promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
02168 
02169     nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
02170     if (promptProvider)
02171         promptProvider->GetAuthPrompt(promptReason, result);
02172     else
02173         CallGetInterface(ifreq, result);
02174 }
02175 
02176 // generate credentials for the given challenge, and update the auth cache.
02177 nsresult
02178 nsHttpChannel::GenCredsAndSetEntry(nsIHttpAuthenticator *auth,
02179                                    PRBool proxyAuth,
02180                                    const char *scheme,
02181                                    const char *host,
02182                                    PRInt32 port,
02183                                    const char *directory,
02184                                    const char *realm,
02185                                    const char *challenge,
02186                                    const nsHttpAuthIdentity &ident,
02187                                    nsCOMPtr<nsISupports> &sessionState,
02188                                    char **result)
02189 {
02190     nsresult rv;
02191     PRUint32 authFlags;
02192 
02193     rv = auth->GetAuthFlags(&authFlags);
02194     if (NS_FAILED(rv)) return rv;
02195 
02196     nsISupports *ss = sessionState;
02197 
02198     // set informations that depend on whether
02199     // we're authenticating against a proxy
02200     // or a webserver
02201     nsISupports **continuationState;
02202 
02203     if (proxyAuth) {
02204         continuationState = &mProxyAuthContinuationState;
02205     } else {
02206         continuationState = &mAuthContinuationState;
02207     }
02208 
02209     rv = auth->GenerateCredentials(this,
02210                                    challenge,
02211                                    proxyAuth,
02212                                    ident.Domain(),
02213                                    ident.User(),
02214                                    ident.Password(),
02215                                    &ss,
02216                                    &*continuationState,
02217                                    result);
02218 
02219     sessionState.swap(ss);
02220     if (NS_FAILED(rv)) return rv;
02221 
02222     // don't log this in release build since it could contain sensitive info.
02223 #ifdef DEBUG 
02224     LOG(("generated creds: %s\n", *result));
02225 #endif
02226 
02227     // find out if this authenticator allows reuse of credentials and/or
02228     // challenge.
02229     PRBool saveCreds =
02230         authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS;
02231     PRBool saveChallenge =
02232         authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE;
02233 
02234     // this getter never fails
02235     nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
02236 
02237     // create a cache entry.  we do this even though we don't yet know that
02238     // these credentials are valid b/c we need to avoid prompting the user
02239     // more than once in case the credentials are valid.
02240     //
02241     // if the credentials are not reusable, then we don't bother sticking
02242     // them in the auth cache.
02243     rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
02244                                  saveCreds ? *result : nsnull,
02245                                  saveChallenge ? challenge : nsnull,
02246                                  ident, sessionState);
02247     return rv;
02248 }
02249 
02250 nsresult
02251 nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
02252 {
02253     LOG(("nsHttpChannel::ProcessAuthentication [this=%x code=%u]\n",
02254         this, httpStatus));
02255 
02256     const char *challenges;
02257     PRBool proxyAuth = (httpStatus == 407);
02258 
02259     nsresult rv = PrepareForAuthentication(proxyAuth);
02260     if (NS_FAILED(rv))
02261         return rv;
02262 
02263     if (proxyAuth) {
02264         // only allow a proxy challenge if we have a proxy server configured.
02265         // otherwise, we could inadvertantly expose the user's proxy
02266         // credentials to an origin server.  We could attempt to proceed as
02267         // if we had received a 401 from the server, but why risk flirting
02268         // with trouble?  IE similarly rejects 407s when a proxy server is
02269         // not configured, so there's no reason not to do the same.
02270         if (!mConnectionInfo->UsingHttpProxy()) {
02271             LOG(("rejecting 407 when proxy server not configured!\n"));
02272             return NS_ERROR_UNEXPECTED;
02273         }
02274         if (mConnectionInfo->UsingSSL() && !mTransaction->SSLConnectFailed()) {
02275             // we need to verify that this challenge came from the proxy
02276             // server itself, and not some server on the other side of the
02277             // SSL tunnel.
02278             LOG(("rejecting 407 from origin server!\n"));
02279             return NS_ERROR_UNEXPECTED;
02280         }
02281         challenges = mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
02282     }
02283     else
02284         challenges = mResponseHead->PeekHeader(nsHttp::WWW_Authenticate);
02285     NS_ENSURE_TRUE(challenges, NS_ERROR_UNEXPECTED);
02286 
02287     nsCAutoString creds;
02288     rv = GetCredentials(challenges, proxyAuth, creds);
02289     if (NS_FAILED(rv))
02290         LOG(("unable to authenticate\n"));
02291     else {
02292         // set the authentication credentials
02293         if (proxyAuth)
02294             mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
02295         else
02296             mRequestHead.SetHeader(nsHttp::Authorization, creds);
02297 
02298         mAuthRetryPending = PR_TRUE; // see DoAuthRetry
02299     }
02300     return rv;
02301 }
02302 
02303 nsresult
02304 nsHttpChannel::PrepareForAuthentication(PRBool proxyAuth)
02305 {
02306     LOG(("nsHttpChannel::PrepareForAuthentication [this=%x]\n", this));
02307 
02308     if (!proxyAuth) {
02309         // reset the current proxy continuation state because our last
02310         // authentication attempt was completed successfully.
02311         NS_IF_RELEASE(mProxyAuthContinuationState);
02312         LOG(("  proxy continuation state has been reset"));
02313     }
02314 
02315     if (!mConnectionInfo->UsingHttpProxy() || mProxyAuthType.IsEmpty())
02316         return NS_OK;
02317 
02318     // We need to remove any Proxy_Authorization header left over from a
02319     // non-request based authentication handshake (e.g., for NTLM auth).
02320 
02321     nsCAutoString contractId;
02322     contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
02323     contractId.Append(mProxyAuthType);
02324 
02325     nsresult rv;
02326     nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
02327         do_GetService(contractId.get(), &rv);
02328     if (NS_FAILED(rv))
02329         return rv;
02330 
02331     PRUint32 precedingAuthFlags;
02332     rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
02333     if (NS_FAILED(rv))
02334         return rv;
02335 
02336     if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
02337         const char *challenges =
02338                 mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
02339         if (!challenges) {
02340             // delete the proxy authorization header because we weren't
02341             // asked to authenticate
02342             mRequestHead.ClearHeader(nsHttp::Proxy_Authorization);
02343             LOG(("  cleared proxy authorization header"));
02344         }
02345     }
02346 
02347     return NS_OK;
02348 }
02349 
02350 nsresult
02351 nsHttpChannel::GetCredentials(const char *challenges,
02352                               PRBool proxyAuth,
02353                               nsAFlatCString &creds)
02354 {
02355     nsCOMPtr<nsIHttpAuthenticator> auth;
02356     nsCAutoString challenge;
02357 
02358     nsCString authType; // force heap allocation to enable string sharing since
02359                         // we'll be assigning this value into mAuthType.
02360 
02361     // set informations that depend on whether we're authenticating against a
02362     // proxy or a webserver
02363     nsISupports **currentContinuationState;
02364     nsCString *currentAuthType;
02365 
02366     if (proxyAuth) {
02367         currentContinuationState = &mProxyAuthContinuationState;
02368         currentAuthType = &mProxyAuthType;
02369     } else {
02370         currentContinuationState = &mAuthContinuationState;
02371         currentAuthType = &mAuthType;
02372     }
02373 
02374     nsresult rv = NS_ERROR_NOT_AVAILABLE;
02375     PRBool gotCreds = PR_FALSE;
02376     
02377     // figure out which challenge we can handle and which authenticator to use.
02378     for (const char *eol = challenges - 1; eol; ) {
02379         const char *p = eol + 1;
02380 
02381         // get the challenge string (LF separated -- see nsHttpHeaderArray)
02382         if ((eol = strchr(p, '\n')) != nsnull)
02383             challenge.Assign(p, eol - p);
02384         else
02385             challenge.Assign(p);
02386 
02387         rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
02388         if (NS_SUCCEEDED(rv)) {
02389             //
02390             // if we've already selected an auth type from a previous challenge
02391             // received while processing this channel, then skip others until
02392             // we find a challenge corresponding to the previously tried auth
02393             // type.
02394             //
02395             if (!currentAuthType->IsEmpty() && authType != *currentAuthType)
02396                 continue;
02397 
02398             //
02399             // we allow the routines to run all the way through before we
02400             // decide if they are valid.
02401             //
02402             // we don't worry about the auth cache being altered because that
02403             // would have been the last step, and if the error is from updating
02404             // the authcache it wasn't really altered anyway. -CTN 
02405             //
02406             // at this point the code is really only useful for client side
02407             // errors (it will not automatically fail over to do a different
02408             // auth type if the server keeps rejecting what is being sent, even
02409             // if a particular auth method only knows 1 thing, like a
02410             // non-identity based authentication method)
02411             //
02412             rv = GetCredentialsForChallenge(challenge.get(), authType.get(),
02413                                             proxyAuth, auth, creds);
02414             if (NS_SUCCEEDED(rv)) {
02415                 gotCreds = PR_TRUE;
02416                 *currentAuthType = authType;
02417 
02418                 break;
02419             }
02420 
02421             // reset the auth type and continuation state
02422             NS_IF_RELEASE(*currentContinuationState);
02423             currentAuthType->Truncate();
02424         }
02425     }
02426 
02427     if (!gotCreds && !currentAuthType->IsEmpty()) {
02428         // looks like we never found the auth type we were looking for.
02429         // reset the auth type and continuation state, and try again.
02430         currentAuthType->Truncate();
02431         NS_IF_RELEASE(*currentContinuationState);
02432 
02433         rv = GetCredentials(challenges, proxyAuth, creds);
02434     }
02435 
02436     return rv;
02437 }
02438 
02439 nsresult
02440 nsHttpChannel::GetCredentialsForChallenge(const char *challenge,
02441                                           const char *authType,
02442                                           PRBool proxyAuth,
02443                                           nsIHttpAuthenticator *auth,
02444                                           nsAFlatCString &creds)
02445 {
02446     LOG(("nsHttpChannel::GetCredentialsForChallenge [this=%x proxyAuth=%d challenges=%s]\n",
02447         this, proxyAuth, challenge));
02448 
02449     // this getter never fails
02450     nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
02451 
02452     PRUint32 authFlags;
02453     nsresult rv = auth->GetAuthFlags(&authFlags);
02454     if (NS_FAILED(rv)) return rv;
02455 
02456     nsCAutoString realm;
02457     ParseRealm(challenge, realm);
02458 
02459     // if no realm, then use the auth type as the realm.  ToUpperCase so the
02460     // ficticious realm stands out a bit more.
02461     // XXX this will cause some single signon misses!
02462     // XXX this will cause problems when we expose the auth cache to OJI!
02463     // XXX this was meant to be used with NTLM, which supplies no realm.
02464     /*
02465     if (realm.IsEmpty()) {
02466         realm = authType;
02467         ToUpperCase(realm);
02468     }
02469     */
02470 
02471     // set informations that depend on whether
02472     // we're authenticating against a proxy
02473     // or a webserver
02474     const char *host;
02475     PRInt32 port;
02476     nsHttpAuthIdentity *ident;
02477     nsCAutoString path, scheme;
02478     PRBool identFromURI = PR_FALSE;
02479     nsISupports **continuationState;
02480 
02481     if (proxyAuth) {
02482         NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!");
02483 
02484         host = mConnectionInfo->ProxyHost();
02485         port = mConnectionInfo->ProxyPort();
02486         ident = &mProxyIdent;
02487         scheme.AssignLiteral("http");
02488 
02489         continuationState = &mProxyAuthContinuationState;
02490     }
02491     else {
02492         host = mConnectionInfo->Host();
02493         port = mConnectionInfo->Port();
02494         ident = &mIdent;
02495 
02496         rv = GetCurrentPath(path);
02497         if (NS_FAILED(rv)) return rv;
02498 
02499         rv = mURI->GetScheme(scheme);
02500         if (NS_FAILED(rv)) return rv;
02501 
02502         // if this is the first challenge, then try using the identity
02503         // specified in the URL.
02504         if (mIdent.IsEmpty()) {
02505             GetIdentityFromURI(authFlags, mIdent);
02506             identFromURI = !mIdent.IsEmpty();
02507         }
02508 
02509         continuationState = &mAuthContinuationState;
02510     }
02511 
02512     //
02513     // if we already tried some credentials for this transaction, then
02514     // we need to possibly clear them from the cache, unless the credentials
02515     // in the cache have changed, in which case we'd want to give them a
02516     // try instead.
02517     //
02518     nsHttpAuthEntry *entry = nsnull;
02519     authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry);
02520 
02521     // hold reference to the auth session state (in case we clear our
02522     // reference to the entry).
02523     nsCOMPtr<nsISupports> sessionStateGrip;
02524     if (entry)
02525         sessionStateGrip = entry->mMetaData;
02526 
02527     // for digest auth, maybe our cached nonce value simply timed out...
02528     PRBool identityInvalid;
02529     nsISupports *sessionState = sessionStateGrip;
02530     rv = auth->ChallengeReceived(this,
02531                                  challenge,
02532                                  proxyAuth,
02533                                  &sessionState,
02534                                  &*continuationState,
02535                                  &identityInvalid);
02536     sessionStateGrip.swap(sessionState);
02537     if (NS_FAILED(rv)) return rv;
02538 
02539     LOG(("  identity invalid = %d\n", identityInvalid));
02540 
02541     if (identityInvalid) {
02542         if (entry) {
02543             if (ident->Equals(entry->Identity())) {
02544                 LOG(("  clearing bad auth cache entry\n"));
02545                 // ok, we've already tried this user identity, so clear the
02546                 // corresponding entry from the auth cache.
02547                 ClearPasswordManagerEntry(scheme.get(), host, port, realm.get(), entry->User());
02548                 authCache->ClearAuthEntry(scheme.get(), host, port, realm.get());
02549                 entry = nsnull;
02550                 ident->Clear();
02551             }
02552             else if (!identFromURI || nsCRT::strcmp(ident->User(), entry->Identity().User()) == 0) {
02553                 LOG(("  taking identity from auth cache\n"));
02554                 // the password from the auth cache is more likely to be
02555                 // correct than the one in the URL.  at least, we know that it
02556                 // works with the given username.  it is possible for a server
02557                 // to distinguish logons based on the supplied password alone,
02558                 // but that would be quite unusual... and i don't think we need
02559                 // to worry about such unorthodox cases.
02560                 ident->Set(entry->Identity());
02561                 identFromURI = PR_FALSE;
02562                 if (entry->Creds()[0] != '\0') {
02563                     LOG(("    using cached credentials!\n"));
02564                     creds.Assign(entry->Creds());
02565                     return entry->AddPath(path.get());
02566                 }
02567             }
02568         }
02569         else if (!identFromURI) {
02570             // hmm... identity invalid, but no auth entry!  the realm probably
02571             // changed (see bug 201986).
02572             ident->Clear();
02573         }
02574 
02575         if (!entry && ident->IsEmpty()) {
02576             // at this point we are forced to interact with the user to get
02577             // their username and password for this domain.
02578             rv = PromptForIdentity(scheme.get(), host, port, proxyAuth, realm.get(), 
02579                                    authType, authFlags, *ident);
02580             if (NS_FAILED(rv)) return rv;
02581             identFromURI = PR_FALSE;
02582         }
02583     }
02584 
02585     if (identFromURI) {
02586         // Warn the user before automatically using the identity from the URL
02587         // to automatically log them into a site (see bug 232567).
02588         if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), PR_FALSE)) {
02589             // calling cancel here sets our mStatus and aborts the HTTP
02590             // transaction, which prevents OnDataAvailable events.
02591             Cancel(NS_ERROR_ABORT);
02592             // this return code alone is not equivalent to Cancel, since
02593             // it only instructs our caller that authentication failed.
02594             // without an explicit call to Cancel, our caller would just
02595             // load the page that accompanies the HTTP auth challenge.
02596             return NS_ERROR_ABORT;
02597         }
02598     }
02599 
02600     //
02601     // get credentials for the given user:pass
02602     //
02603     // always store the credentials we're trying now so that they will be used
02604     // on subsequent links.  This will potentially remove good credentials from
02605     // the cache.  This is ok as we don't want to use cached credentials if the
02606     // user specified something on the URI or in another manner.  This is so
02607     // that we don't transparently authenticate as someone they're not
02608     // expecting to authenticate as.
02609     //
02610     nsXPIDLCString result;
02611     rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port, path.get(),
02612                              realm.get(), challenge, *ident, sessionStateGrip,
02613                              getter_Copies(result));
02614     if (NS_SUCCEEDED(rv))
02615         creds = result;
02616     return rv;
02617 }
02618 
02619 nsresult
02620 nsHttpChannel::GetAuthenticator(const char *challenge,
02621                                 nsCString &authType,
02622                                 nsIHttpAuthenticator **auth)
02623 {
02624     LOG(("nsHttpChannel::GetAuthenticator [this=%x]\n", this));
02625 
02626     const char *p;
02627   
02628     // get the challenge type
02629     if ((p = strchr(challenge, ' ')) != nsnull)
02630         authType.Assign(challenge, p - challenge);
02631     else
02632         authType.Assign(challenge);
02633   
02634     // normalize to lowercase
02635     ToLowerCase(authType);
02636 
02637     nsCAutoString contractid;
02638     contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
02639     contractid.Append(authType);
02640 
02641     return CallGetService(contractid.get(), auth);
02642 }
02643 
02644 void
02645 nsHttpChannel::GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity &ident)
02646 {
02647     LOG(("nsHttpChannel::GetIdentityFromURI [this=%x]\n", this));
02648 
02649     nsAutoString userBuf;
02650     nsAutoString passBuf;
02651 
02652     // XXX i18n
02653     nsCAutoString buf;
02654     mURI->GetUsername(buf);
02655     if (!buf.IsEmpty()) {
02656         NS_UnescapeURL(buf);
02657         CopyASCIItoUCS2(buf, userBuf);
02658         mURI->GetPassword(buf);
02659         if (!buf.IsEmpty()) {
02660             NS_UnescapeURL(buf);
02661             CopyASCIItoUCS2(buf, passBuf);
02662         }
02663     }
02664 
02665     if (!userBuf.IsEmpty())
02666         SetIdent(ident, authFlags, (PRUnichar *) userBuf.get(), (PRUnichar *) passBuf.get());
02667 }
02668 
02669 void
02670 nsHttpChannel::ParseRealm(const char *challenge, nsACString &realm)
02671 {
02672     //
02673     // From RFC2617 section 1.2, the realm value is defined as such:
02674     //
02675     //    realm       = "realm" "=" realm-value
02676     //    realm-value = quoted-string
02677     //
02678     // but, we'll accept anything after the the "=" up to the first space, or
02679     // end-of-line, if the string is not quoted.
02680     //
02681     const char *p = PL_strcasestr(challenge, "realm=");
02682     if (p) {
02683         p += 6;
02684         if (*p == '"')
02685             p++;
02686         const char *end = PL_strchr(p, '"');
02687         if (!end)
02688             end = PL_strchr(p, ' ');
02689         if (end)
02690             realm.Assign(p, end - p);
02691         else
02692             realm.Assign(p);
02693     }
02694 }
02695 
02696 nsresult
02697 nsHttpChannel::PromptForIdentity(const char *scheme,
02698                                  const char *host,
02699                                  PRInt32     port,
02700                                  PRBool      proxyAuth,
02701                                  const char *realm,
02702                                  const char *authType,
02703                                  PRUint32 authFlags,
02704                                  nsHttpAuthIdentity &ident)
02705 {
02706     LOG(("nsHttpChannel::PromptForIdentity [this=%x]\n", this));
02707 
02708     // XXX authType should be included in the prompt
02709 
02710     // XXX i18n: IDN not supported.
02711 
02712     nsCOMPtr<nsIAuthPrompt> authPrompt;
02713     GetAuthPrompt(mCallbacks, proxyAuth, getter_AddRefs(authPrompt));
02714     if (!authPrompt && mLoadGroup) {
02715         nsCOMPtr<nsIInterfaceRequestor> cbs;
02716         mLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
02717         GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
02718     }
02719     if (!authPrompt)
02720         return NS_ERROR_NO_INTERFACE;
02721 
02722     // XXX i18n: need to support non-ASCII realm strings (see bug 41489)
02723     NS_ConvertASCIItoUTF16 realmU(realm);
02724 
02725     //
02726     // construct the single signon key
02727     //
02728     // we always add the port to domain since it is used as the key for storing
02729     // in password maanger.  THE FORMAT OF THIS KEY IS SACROSANCT!!  do not
02730     // even think about changing the format of this key.
02731     //
02732     // XXX we need to prefix this with "scheme://" at some point.  however, that
02733     // has to be done very carefully and probably with some cooperation from the
02734     // password manager to ensure that passwords remembered under the old key
02735     // format are not lost.
02736     //
02737     nsAutoString key;
02738     CopyASCIItoUTF16(host, key); // XXX IDN?
02739     key.Append(PRUnichar(':'));
02740     key.AppendInt(port);
02741     key.AppendLiteral(" (");
02742     key.Append(realmU);
02743     key.Append(PRUnichar(')'));
02744 
02745     nsresult rv;
02746 
02747     // construct the message string
02748     nsCOMPtr<nsIStringBundleService> bundleSvc =
02749             do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
02750     if (NS_FAILED(rv)) return rv;
02751 
02752     nsCOMPtr<nsIStringBundle> bundle;
02753     rv = bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
02754     if (NS_FAILED(rv)) return rv;
02755 
02756     // figure out what message to display...
02757     nsAutoString displayHost;
02758     CopyASCIItoUTF16(host, displayHost); // XXX IDN?
02759     // If not proxy auth then add port only if it was originally specified
02760     // in the URI.
02761     PRInt32 uriPort = -1;
02762     if (proxyAuth || (NS_SUCCEEDED(mURI->GetPort(&uriPort)) && uriPort != -1)) {
02763         displayHost.Append(PRUnichar(':'));
02764         displayHost.AppendInt(port);
02765     }
02766 
02767     nsXPIDLString message;
02768     {
02769         NS_NAMED_LITERAL_STRING(proxyText, "EnterUserPasswordForProxy");
02770         NS_NAMED_LITERAL_STRING(originText, "EnterUserPasswordForRealm");
02771 
02772         const PRUnichar *text;
02773         if (proxyAuth) {
02774             text = proxyText.get();
02775         } else {
02776             text = originText.get();
02777 
02778             // prepend "scheme://"
02779             nsAutoString schemeU; 
02780             CopyASCIItoUTF16(scheme, schemeU);
02781             schemeU.AppendLiteral("://");
02782             displayHost.Insert(schemeU, 0);
02783         }
02784 
02785         const PRUnichar *strings[] = { realmU.get(), displayHost.get() };
02786 
02787         rv = bundle->FormatStringFromName(text, strings, 2,
02788                                           getter_Copies(message));
02789     }
02790     if (NS_FAILED(rv)) return rv;
02791 
02792     // prompt the user...
02793     PRBool retval = PR_FALSE;
02794     PRUnichar *user = nsnull, *pass = nsnull;
02795     rv = authPrompt->PromptUsernameAndPassword(nsnull, message.get(),
02796                                                key.get(),
02797                                                nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
02798                                                &user, &pass, &retval);
02799     if (NS_FAILED(rv)) return rv;
02800 
02801     // remember that we successfully showed the user an auth dialog
02802     if (!proxyAuth)
02803         mSuppressDefensiveAuth = PR_TRUE;
02804 
02805     if (!retval || !user || !pass)
02806         rv = NS_ERROR_ABORT;
02807     else
02808         SetIdent(ident, authFlags, user, pass);
02809   
02810     if (user) nsMemory::Free(user);
02811     if (pass) nsMemory::Free(pass);
02812     return rv;
02813 }
02814 
02815 PRBool
02816 nsHttpChannel::ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt)
02817 {
02818     // skip prompting the user if
02819     //   1) we've already prompted the user
02820     //   2) we're not a toplevel channel
02821     //   3) the userpass length is less than the "phishy" threshold
02822 
02823     if (mSuppressDefensiveAuth || !(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
02824         return PR_TRUE;
02825 
02826     nsresult rv;
02827     nsCAutoString userPass;
02828     rv = mURI->GetUserPass(userPass);
02829     if (NS_FAILED(rv) || (userPass.Length() < gHttpHandler->PhishyUserPassLength()))
02830         return PR_TRUE;
02831 
02832     // we try to confirm by prompting the user.  if we cannot do so, then
02833     // assume the user said ok.  this is done to keep things working in
02834     // embedded builds, where the string bundle might not be present, etc.
02835 
02836     nsCOMPtr<nsIStringBundleService> bundleService =
02837             do_GetService(NS_STRINGBUNDLE_CONTRACTID);
02838     if (!bundleService)
02839         return PR_TRUE;
02840 
02841     nsCOMPtr<nsIStringBundle> bundle;
02842     bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
02843     if (!bundle)
02844         return PR_TRUE;
02845 
02846     nsCAutoString host;
02847     rv = mURI->GetHost(host);
02848     if (NS_FAILED(rv))
02849         return PR_TRUE;
02850 
02851     nsCAutoString user;
02852     rv = mURI->GetUsername(user);
02853     if (NS_FAILED(rv))
02854         return PR_TRUE;
02855 
02856     NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
02857     const PRUnichar *strs[2] = { ucsHost.get(), ucsUser.get() };
02858 
02859     nsXPIDLString msg;
02860     bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
02861     if (!msg)
02862         return PR_TRUE;
02863     
02864     nsCOMPtr<nsIPrompt> prompt;
02865     GetCallback(prompt);
02866     if (!prompt)
02867         return PR_TRUE;
02868 
02869     // do not prompt again
02870     mSuppressDefensiveAuth = PR_TRUE;
02871 
02872     PRBool confirmed;
02873     if (doYesNoPrompt) {
02874         PRInt32 choice;
02875         rv = prompt->ConfirmEx(nsnull, msg,
02876                                nsIPrompt::BUTTON_POS_1_DEFAULT +
02877                                nsIPrompt::BUTTON_TITLE_YES * nsIPrompt::BUTTON_POS_0 +
02878                                nsIPrompt::BUTTON_TITLE_NO  * nsIPrompt::BUTTON_POS_1,
02879                                nsnull, nsnull, nsnull, nsnull, nsnull, &choice);
02880         if (NS_FAILED(rv))
02881             return PR_TRUE;
02882 
02883         confirmed = choice == 0;
02884     }
02885     else {
02886         rv = prompt->Confirm(nsnull, msg, &confirmed);
02887         if (NS_FAILED(rv))
02888             return PR_TRUE;
02889     }
02890 
02891     return confirmed;
02892 }
02893 
02894 void
02895 nsHttpChannel::CheckForSuperfluousAuth()
02896 {
02897     // we've been called because it has been determined that this channel is
02898     // getting loaded without taking the userpass from the URL.  if the URL
02899     // contained a userpass, then (provided some other conditions are true),
02900     // we'll give the user an opportunity to abort the channel as this might be
02901     // an attempt to spoof a different site (see bug 232567).
02902     if (!mAuthRetryPending) {
02903         // ask user...
02904         if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), PR_TRUE)) {
02905             // calling cancel here sets our mStatus and aborts the HTTP
02906             // transaction, which prevents OnDataAvailable events.
02907             Cancel(NS_ERROR_ABORT);
02908         }
02909     }
02910 }
02911 
02912 void
02913 nsHttpChannel::SetAuthorizationHeader(nsHttpAuthCache *authCache,
02914                                       nsHttpAtom header,
02915                                       const char *scheme,
02916                                       const char *host,
02917                                       PRInt32 port,
02918                                       const char *path,
02919                                       nsHttpAuthIdentity &ident)
02920 {
02921     nsHttpAuthEntry *entry = nsnull;
02922     nsresult rv;
02923 
02924     // set informations that depend on whether
02925     // we're authenticating against a proxy
02926     // or a webserver
02927     nsISupports **continuationState;
02928 
02929     if (header == nsHttp::Proxy_Authorization) {
02930         continuationState = &mProxyAuthContinuationState;
02931     } else {
02932         continuationState = &mAuthContinuationState;
02933     }
02934 
02935     rv = authCache->GetAuthEntryForPath(scheme, host, port, path, &entry);
02936     if (NS_SUCCEEDED(rv)) {
02937         // if we are trying to add a header for origin server auth and if the
02938         // URL contains an explicit username, then try the given username first.
02939         // we only want to do this, however, if we know the URL requires auth
02940         // based on the presence of an auth cache entry for this URL (which is
02941         // true since we are here).  but, if the username from the URL matches
02942         // the username from the cache, then we should prefer the password
02943         // stored in the cache since that is most likely to be valid.
02944         if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
02945             GetIdentityFromURI(0, ident);
02946             // if the usernames match, then clear the ident so we will pick
02947             // up the one from the auth cache instead.
02948             if (nsCRT::strcmp(ident.User(), entry->User()) == 0)
02949                 ident.Clear();
02950         }
02951         PRBool identFromURI;
02952         if (ident.IsEmpty()) {
02953             ident.Set(entry->Identity());
02954             identFromURI = PR_FALSE;
02955         }
02956         else
02957             identFromURI = PR_TRUE;
02958 
02959         nsXPIDLCString temp;
02960         const char *creds     = entry->Creds();
02961         const char *challenge = entry->Challenge();
02962         // we can only send a preemptive Authorization header if we have either
02963         // stored credentials or a stored challenge from which to derive
02964         // credentials.  if the identity is from the URI, then we cannot use
02965         // the stored credentials.
02966         if ((!creds[0] || identFromURI) && challenge[0]) {
02967             nsCOMPtr<nsIHttpAuthenticator> auth;
02968             nsCAutoString unused;
02969             rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth));
02970             if (NS_SUCCEEDED(rv)) {
02971                 PRBool proxyAuth = (header == nsHttp::Proxy_Authorization);
02972                 rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
02973                                          entry->Realm(), challenge, ident,
02974                                          entry->mMetaData, getter_Copies(temp));
02975                 if (NS_SUCCEEDED(rv))
02976                     creds = temp.get();
02977 
02978                 // make sure the continuation state is null since we do not
02979                 // support mixing preemptive and 'multirequest' authentication.
02980                 NS_IF_RELEASE(*continuationState);
02981             }
02982         }
02983         if (creds[0]) {
02984             LOG(("   adding \"%s\" request header\n", header.get()));
02985             mRequestHead.SetHeader(header, nsDependentCString(creds));
02986 
02987             // suppress defensive auth prompting for this channel since we know
02988             // that we already prompted at least once this session.  we only do
02989             // this for non-proxy auth since the URL's userpass is not used for
02990             // proxy auth.
02991             if (header == nsHttp::Authorization)
02992                 mSuppressDefensiveAuth = PR_TRUE;
02993         }
02994         else
02995             ident.Clear(); // don't remember the identity
02996     }
02997 }
02998 
02999 void
03000 nsHttpChannel::AddAuthorizationHeaders()
03001 {
03002     LOG(("nsHttpChannel::AddAuthorizationHeaders? [this=%x]\n", this));
03003 
03004     // this getter never fails
03005     nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
03006 
03007     // check if proxy credentials should be sent
03008     const char *proxyHost = mConnectionInfo->ProxyHost();
03009     if (proxyHost && mConnectionInfo->UsingHttpProxy())
03010         SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
03011                                "http", proxyHost, mConnectionInfo->ProxyPort(),
03012                                nsnull, // proxy has no path
03013                                mProxyIdent);
03014 
03015     // check if server credentials should be sent
03016     nsCAutoString path, scheme;
03017     if (NS_SUCCEEDED(GetCurrentPath(path)) &&
03018         NS_SUCCEEDED(mURI->GetScheme(scheme))) {
03019         SetAuthorizationHeader(authCache, nsHttp::Authorization,
03020                                scheme.get(),
03021                                mConnectionInfo->Host(),
03022                                mConnectionInfo->Port(),
03023                                path.get(),
03024                                mIdent);
03025     }
03026 }
03027 
03028 nsresult
03029 nsHttpChannel::GetCurrentPath(nsACString &path)
03030 {
03031     nsresult rv;
03032     nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
03033     if (url)
03034         rv = url->GetDirectory(path);
03035     else
03036         rv = mURI->GetPath(path);
03037     return rv;
03038 }
03039 
03040 //-----------------------------------------------------------------------------
03041 // nsHttpChannel::nsISupports
03042 //-----------------------------------------------------------------------------
03043 
03044 NS_IMPL_ADDREF_INHERITED(nsHttpChannel, nsHashPropertyBag)
03045 NS_IMPL_RELEASE_INHERITED(nsHttpChannel, nsHashPropertyBag)
03046 
03047 NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
03048     NS_INTERFACE_MAP_ENTRY(nsIRequest)
03049     NS_INTERFACE_MAP_ENTRY(nsIChannel)
03050     NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
03051     NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
03052     NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
03053     NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
03054     NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
03055     NS_INTERFACE_MAP_ENTRY(nsICacheListener)
03056     NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
03057     NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
03058     NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
03059     NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
03060     NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
03061     NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
03062 NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
03063 
03064 //-----------------------------------------------------------------------------
03065 // nsHttpChannel::nsIRequest
03066 //-----------------------------------------------------------------------------
03067 
03068 NS_IMETHODIMP
03069 nsHttpChannel::GetName(nsACString &aName)
03070 {
03071     aName = mSpec;
03072     return NS_OK;
03073 }
03074 
03075 NS_IMETHODIMP
03076 nsHttpChannel::IsPending(PRBool *value)
03077 {
03078     NS_ENSURE_ARG_POINTER(value);
03079     *value = mIsPending;
03080     return NS_OK;
03081 }
03082 
03083 NS_IMETHODIMP
03084 nsHttpChannel::GetStatus(nsresult *aStatus)
03085 {
03086     NS_ENSURE_ARG_POINTER(aStatus);
03087     *aStatus = mStatus;
03088     return NS_OK;
03089 }
03090 
03091 NS_IMETHODIMP
03092 nsHttpChannel::Cancel(nsresult status)
03093 {
03094     LOG(("nsHttpChannel::Cancel [this=%x status=%x]\n", this, status));
03095     mCanceled = PR_TRUE;
03096     mStatus = status;
03097     if (mProxyRequest)
03098         mProxyRequest->Cancel(status);
03099     if (mTransaction)
03100         gHttpHandler->CancelTransaction(mTransaction, status);
03101     if (mTransactionPump)
03102         mTransactionPump->Cancel(status);
03103     if (mCachePump)
03104         mCachePump->Cancel(status);
03105     return NS_OK;
03106 }
03107 
03108 NS_IMETHODIMP
03109 nsHttpChannel::Suspend()
03110 {
03111     LOG(("nsHttpChannel::Suspend [this=%x]\n", this));
03112     if (mTransactionPump)
03113         return mTransactionPump->Suspend();
03114     if (mCachePump)
03115         return mCachePump->Suspend();
03116 
03117     return NS_ERROR_UNEXPECTED;
03118 }
03119 
03120 NS_IMETHODIMP
03121 nsHttpChannel::Resume()
03122 {
03123     LOG(("nsHttpChannel::Resume [this=%x]\n", this));
03124     if (mTransactionPump)
03125         return mTransactionPump->Resume();
03126     if (mCachePump)
03127         return mCachePump->Resume();
03128 
03129     return NS_ERROR_UNEXPECTED;
03130 }
03131 
03132 NS_IMETHODIMP
03133 nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
03134 {
03135     NS_ENSURE_ARG_POINTER(aLoadGroup);
03136     *aLoadGroup = mLoadGroup;
03137     NS_IF_ADDREF(*aLoadGroup);
03138     return NS_OK;
03139 }
03140 NS_IMETHODIMP
03141 nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
03142 {
03143     mLoadGroup = aLoadGroup;
03144     mProgressSink = nsnull;
03145     return NS_OK;
03146 }
03147 
03148 NS_IMETHODIMP
03149 nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
03150 {
03151     NS_ENSURE_ARG_POINTER(aLoadFlags);
03152     *aLoadFlags = mLoadFlags;
03153     return NS_OK;
03154 }
03155 NS_IMETHODIMP
03156 nsHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
03157 {
03158     mLoadFlags = aLoadFlags;
03159 
03160     // don't let anyone overwrite this bit if we're using a secure channel.
03161     if (mConnectionInfo && mConnectionInfo->UsingSSL() 
03162         && !gHttpHandler->IsPersistentHttpsCachingEnabled())
03163         mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
03164 
03165     return NS_OK;
03166 }
03167 
03168 //-----------------------------------------------------------------------------
03169 // nsHttpChannel::nsIChannel
03170 //-----------------------------------------------------------------------------
03171 
03172 NS_IMETHODIMP
03173 nsHttpChannel::GetOriginalURI(nsIURI **originalURI)
03174 {
03175     NS_ENSURE_ARG_POINTER(originalURI);
03176     *originalURI = mOriginalURI;
03177     NS_IF_ADDREF(*originalURI);
03178     return NS_OK;
03179 }
03180 NS_IMETHODIMP
03181 nsHttpChannel::SetOriginalURI(nsIURI *originalURI)
03182 {
03183     mOriginalURI = originalURI;
03184     return NS_OK;
03185 }
03186 
03187 NS_IMETHODIMP
03188 nsHttpChannel::GetURI(nsIURI **URI)
03189 {
03190     NS_ENSURE_ARG_POINTER(URI);
03191     *URI = mURI;
03192     NS_IF_ADDREF(*URI);
03193     return NS_OK;
03194 }
03195 
03196 NS_IMETHODIMP
03197 nsHttpChannel::GetOwner(nsISupports **owner)
03198 {
03199     NS_ENSURE_ARG_POINTER(owner);
03200     *owner = mOwner;
03201     NS_IF_ADDREF(*owner);
03202     return NS_OK;
03203 }
03204 NS_IMETHODIMP
03205 nsHttpChannel::SetOwner(nsISupports *owner)
03206 {
03207     mOwner = owner;
03208     return NS_OK;
03209 }
03210 
03211 NS_IMETHODIMP
03212 nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
03213 {
03214     NS_IF_ADDREF(*callbacks = mCallbacks);
03215     return NS_OK;
03216 }
03217 NS_IMETHODIMP
03218 nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *callbacks)
03219 {
03220     mCallbacks = callbacks;
03221     mProgressSink = nsnull;
03222     return NS_OK;
03223 }
03224 
03225 NS_IMETHODIMP
03226 nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
03227 {
03228     NS_ENSURE_ARG_POINTER(securityInfo);
03229     *securityInfo = mSecurityInfo;
03230     NS_IF_ADDREF(*securityInfo);
03231     return NS_OK;
03232 }
03233 
03234 NS_IMETHODIMP
03235 nsHttpChannel::GetContentType(nsACString &value)
03236 {
03237     if (!mResponseHead) {
03238         // We got no data, we got no headers, we got nothing
03239         value.Truncate();
03240         return NS_ERROR_NOT_AVAILABLE;
03241     }
03242 
03243     if (!mResponseHead->ContentType().IsEmpty()) {
03244         value = mResponseHead->ContentType();
03245         return NS_OK;
03246     }
03247 
03248     
03249     value.AssignLiteral(UNKNOWN_CONTENT_TYPE);
03250     return NS_OK;
03251 }
03252 
03253 NS_IMETHODIMP
03254 nsHttpChannel::SetContentType(const nsACString &value)
03255 {
03256     if (mListener) {
03257         if (!mResponseHead)
03258             return NS_ERROR_NOT_AVAILABLE;
03259 
03260         nsCAutoString contentTypeBuf, charsetBuf;
03261         PRBool hadCharset;
03262         net_ParseContentType(value, contentTypeBuf, charsetBuf, &hadCharset);
03263 
03264         mResponseHead->SetContentType(contentTypeBuf);
03265 
03266         // take care not to stomp on an existing charset
03267         if (hadCharset)
03268             mResponseHead->SetContentCharset(charsetBuf);
03269     } else {
03270         // We are being given a content-type hint.
03271         PRBool dummy;
03272         net_ParseContentType(value, mContentTypeHint, mContentCharsetHint,
03273                              &dummy);
03274     }
03275     
03276     return NS_OK;
03277 }
03278 
03279 NS_IMETHODIMP
03280 nsHttpChannel::GetContentCharset(nsACString &value)
03281 {
03282     if (!mResponseHead)
03283         return NS_ERROR_NOT_AVAILABLE;
03284 
03285     value = mResponseHead->ContentCharset();
03286     return NS_OK;
03287 }
03288 
03289 NS_IMETHODIMP
03290 nsHttpChannel::SetContentCharset(const nsACString &value)
03291 {
03292     if (mListener) {
03293         if (!mResponseHead)
03294             return NS_ERROR_NOT_AVAILABLE;
03295 
03296         mResponseHead->SetContentCharset(value);
03297     } else {
03298         // Charset hint
03299         mContentCharsetHint = value;
03300     }
03301     return NS_OK;
03302 }
03303 
03304 NS_IMETHODIMP
03305 nsHttpChannel::GetContentLength(PRInt32 *value)
03306 {
03307     NS_ENSURE_ARG_POINTER(value);
03308 
03309     if (!mResponseHead)
03310         return NS_ERROR_NOT_AVAILABLE;
03311 
03312     // XXX truncates to 32 bit
03313     LL_L2I(*value, mResponseHead->ContentLength());
03314     return NS_OK;
03315 }
03316 
03317 NS_IMETHODIMP
03318 nsHttpChannel::SetContentLength(PRInt32 value)
03319 {
03320     NS_NOTYETIMPLEMENTED("nsHttpChannel::SetContentLength");
03321     return NS_ERROR_NOT_IMPLEMENTED;
03322 }
03323 
03324 NS_IMETHODIMP
03325 nsHttpChannel::Open(nsIInputStream **_retval)
03326 {
03327     return NS_ImplementChannelOpen(this, _retval);
03328 }
03329 
03330 NS_IMETHODIMP
03331 nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
03332 {
03333     LOG(("nsHttpChannel::AsyncOpen [this=%x]\n", this));
03334 
03335     NS_ENSURE_ARG_POINTER(listener);
03336     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
03337 
03338     nsresult rv;
03339 
03340     // we want to grab a reference to the calling thread's event queue at
03341     // this point.  we will proxy all events back to the current thread via
03342     // this event queue.
03343     if (!mEventQ) {
03344         rv = gHttpHandler->GetCurrentEventQ(getter_AddRefs(mEventQ));
03345         if (NS_FAILED(rv)) return rv;
03346     }
03347 
03348     PRInt32 port;
03349     rv = mURI->GetPort(&port);
03350     if (NS_FAILED(rv))
03351         return rv;
03352  
03353     nsCOMPtr<nsIIOService> ioService;
03354     rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
03355     if (NS_FAILED(rv)) return rv;
03356 
03357     rv = NS_CheckPortSafety(port, "http", ioService); // this works for https
03358     if (NS_FAILED(rv))
03359         return rv;
03360 
03361     // Remember the cookie header that was set, if any
03362     const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
03363     if (cookieHeader)
03364         mUserSetCookieHeader = cookieHeader;
03365 
03366     // fetch cookies, and add them to the request header
03367     AddCookiesToRequest();
03368 
03369     // notify "http-on-modify-request" observers
03370     gHttpHandler->OnModifyRequest(this);
03371 
03372     // Adjust mCaps according to our request headers:
03373     //  - If "Connection: close" is set as a request header, then do not bother
03374     //    trying to establish a keep-alive connection.
03375     const char *connHeader = mRequestHead.PeekHeader(nsHttp::Connection);
03376     if (PL_strcasestr(connHeader, "close"))
03377         mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
03378     
03379     mIsPending = PR_TRUE;
03380 
03381     mListener = listener;
03382     mListenerContext = context;
03383 
03384     // add ourselves to the load group.  from this point forward, we'll report
03385     // all failures asynchronously.
03386     if (mLoadGroup)
03387         mLoadGroup->AddRequest(this, nsnull);
03388 
03389     rv = Connect();
03390     if (NS_FAILED(rv)) {
03391         LOG(("Connect failed [rv=%x]\n", rv));
03392         CloseCacheEntry(rv);
03393         AsyncAbort(rv);
03394     }
03395     return NS_OK;
03396 }
03397 //-----------------------------------------------------------------------------
03398 // nsHttpChannel::nsIHttpChannel
03399 //-----------------------------------------------------------------------------
03400 
03401 NS_IMETHODIMP
03402 nsHttpChannel::GetRequestMethod(nsACString &method)
03403 {
03404     method = mRequestHead.Method();
03405     return NS_OK;
03406 }
03407 NS_IMETHODIMP
03408 nsHttpChannel::SetRequestMethod(const nsACString &method)
03409 {
03410     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
03411 
03412     const nsCString &flatMethod = PromiseFlatCString(method);
03413 
03414     // Method names are restricted to valid HTTP tokens.
03415     if (!nsHttp::IsValidToken(flatMethod))
03416         return NS_ERROR_INVALID_ARG;
03417 
03418     nsHttpAtom atom = nsHttp::ResolveAtom(flatMethod.get());
03419     if (!atom)
03420         return NS_ERROR_FAILURE;
03421 
03422     mRequestHead.SetMethod(atom);
03423     return NS_OK;
03424 }
03425 
03426 NS_IMETHODIMP
03427 nsHttpChannel::GetReferrer(nsIURI **referrer)
03428 {
03429     NS_ENSURE_ARG_POINTER(referrer);
03430     *referrer = mReferrer;
03431     NS_IF_ADDREF(*referrer);
03432     return NS_OK;
03433 }
03434 
03435 NS_IMETHODIMP
03436 nsHttpChannel::SetReferrer(nsIURI *referrer)
03437 {
03438     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
03439 
03440     // clear existing referrer, if any
03441     mReferrer = nsnull;
03442     mRequestHead.ClearHeader(nsHttp::Referer);
03443 
03444     if (!referrer)
03445         return NS_OK;
03446 
03447     // check referrer blocking pref
03448     PRUint32 referrerLevel;
03449     if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)
03450         referrerLevel = 1; // user action
03451     else
03452         referrerLevel = 2; // inline content
03453     if (gHttpHandler->ReferrerLevel() < referrerLevel)
03454         return NS_OK;
03455 
03456     nsCOMPtr<nsIURI> referrerGrip;
03457     nsresult rv;
03458     PRBool match;
03459 
03460     //
03461     // Strip off "wyciwyg://123/" from wyciwyg referrers.
03462     //
03463     // XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
03464     //     perhaps some sort of generic nsINestedURI could be used.  then, if an URI
03465     //     fails the whitelist test, then we could check for an inner URI and try
03466     //     that instead.  though, that might be too automatic.
03467     // 
03468     rv = referrer->SchemeIs("wyciwyg", &match);
03469     if (NS_FAILED(rv)) return rv;
03470     if (match) {
03471         nsCAutoString path;
03472         rv = referrer->GetPath(path);
03473         if (NS_FAILED(rv)) return rv;
03474 
03475         PRUint32 pathLength = path.Length();
03476         if (pathLength <= 2) return NS_ERROR_FAILURE;
03477 
03478         // Path is of the form "//123/http://foo/bar", with a variable number of digits.
03479         // To figure out where the "real" URL starts, search path for a '/', starting at 
03480         // the third character.
03481         PRInt32 slashIndex = path.FindChar('/', 2);
03482         if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
03483 
03484         // Get the charset of the original URI so we can pass it to our fixed up URI.
03485         nsCAutoString charset;
03486         referrer->GetOriginCharset(charset);
03487 
03488         // Replace |referrer| with a URI without wyciwyg://123/.
03489         rv = NS_NewURI(getter_AddRefs(referrerGrip),
03490                        Substring(path, slashIndex + 1, pathLength - slashIndex - 1),
03491                        charset.get());
03492         if (NS_FAILED(rv)) return rv;
03493 
03494         referrer = referrerGrip.get();
03495     }
03496 
03497     //
03498     // block referrer if not on our white list...
03499     //
03500     static const char *const referrerWhiteList[] = {
03501         "http",
03502         "https",
03503         "ftp",
03504         "gopher",
03505         nsnull
03506     };
03507     match = PR_FALSE;
03508     const char *const *scheme = referrerWhiteList;
03509     for (; *scheme && !match; ++scheme) {
03510         rv = referrer->SchemeIs(*scheme, &match);
03511         if (NS_FAILED(rv)) return rv;
03512     }
03513     if (!match)
03514         return NS_OK; // kick out....
03515 
03516     //
03517     // Handle secure referrals.
03518     //
03519     // Support referrals from a secure server if this is a secure site
03520     // and (optionally) if the host names are the same.
03521     //
03522     rv = referrer->SchemeIs("https", &match);
03523     if (NS_FAILED(rv)) return rv;
03524     if (match) {
03525         rv = mURI->SchemeIs("https", &match);
03526         if (NS_FAILED(rv)) return rv;
03527         if (!match)
03528             return NS_OK;
03529 
03530         if (!gHttpHandler->SendSecureXSiteReferrer()) {
03531             nsCAutoString referrerHost;
03532             nsCAutoString host;
03533 
03534             rv = referrer->GetAsciiHost(referrerHost);
03535             if (NS_FAILED(rv)) return rv;
03536 
03537             rv = mURI->GetAsciiHost(host);
03538             if (NS_FAILED(rv)) return rv;
03539 
03540             // GetAsciiHost returns lowercase hostname.
03541             if (!referrerHost.Equals(host))
03542                 return NS_OK;
03543         }
03544     }
03545 
03546     nsCOMPtr<nsIURI> clone;
03547     //
03548     // we need to clone the referrer, so we can:
03549     //  (1) modify it
03550     //  (2) keep a reference to it after returning from this function
03551     //
03552     rv = referrer->Clone(getter_AddRefs(clone));
03553     if (NS_FAILED(rv)) return rv;
03554 
03555     // strip away any userpass; we don't want to be giving out passwords ;-)
03556     clone->SetUserPass(EmptyCString());
03557 
03558     // strip away any fragment per RFC 2616 section 14.36
03559     nsCOMPtr<nsIURL> url = do_QueryInterface(clone);
03560     if (url)
03561         url->SetRef(EmptyCString());
03562 
03563     nsCAutoString spec;
03564     rv = clone->GetAsciiSpec(spec);
03565     if (NS_FAILED(rv)) return rv;
03566 
03567     // finally, remember the referrer URI and set the Referer header.
03568     mReferrer = clone;
03569     mRequestHead.SetHeader(nsHttp::Referer, spec);
03570     return NS_OK;
03571 }
03572 
03573 NS_IMETHODIMP
03574 nsHttpChannel::GetRequestHeader(const nsACString &header, nsACString &value)
03575 {
03576     // XXX might be better to search the header list directly instead of
03577     // hitting the http atom hash table.
03578 
03579     nsHttpAtom atom = nsHttp::ResolveAtom(header);
03580     if (!atom)
03581         return NS_ERROR_NOT_AVAILABLE;
03582 
03583     return mRequestHead.GetHeader(atom, value);
03584 }
03585 
03586 NS_IMETHODIMP
03587 nsHttpChannel::SetRequestHeader(const nsACString &header,
03588                                 const nsACString &value,
03589                                 PRBool merge)
03590 {
03591     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
03592 
03593     const nsCString &flatHeader = PromiseFlatCString(header);
03594     const nsCString &flatValue  = PromiseFlatCString(value);
03595 
03596     LOG(("nsHttpChannel::SetRequestHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
03597         this, flatHeader.get(), flatValue.get(), merge));
03598 
03599     // Header names are restricted to valid HTTP tokens.
03600     if (!nsHttp::IsValidToken(flatHeader))
03601         return NS_ERROR_INVALID_ARG;
03602     
03603     // Header values MUST NOT contain line-breaks.  RFC 2616 technically
03604     // permits CTL characters, including CR and LF, in header values provided
03605     // they are quoted.  However, this can lead to problems if servers do not
03606     // interpret quoted strings properly.  Disallowing CR and LF here seems
03607     // reasonable and keeps things simple.  We also disallow a null byte.
03608     if (flatValue.FindCharInSet("\r\n") != kNotFound ||
03609         flatValue.Length() != strlen(flatValue.get()))
03610         return NS_ERROR_INVALID_ARG;
03611 
03612     nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
03613     if (!atom) {
03614         NS_WARNING("failed to resolve atom");
03615         return NS_ERROR_NOT_AVAILABLE;
03616     }
03617 
03618     return mRequestHead.SetHeader(atom, flatValue, merge);
03619 }
03620 
03621 NS_IMETHODIMP
03622 nsHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor)
03623 {
03624     return mRequestHead.Headers().VisitHeaders(visitor);
03625 }
03626 
03627 NS_IMETHODIMP
03628 nsHttpChannel::GetUploadStream(nsIInputStream **stream)
03629 {
03630     NS_ENSURE_ARG_POINTER(stream);
03631     *stream = mUploadStream;
03632     NS_IF_ADDREF(*stream);
03633     return NS_OK;
03634 }
03635 
03636 NS_IMETHODIMP
03637 nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &contentType, PRInt32 contentLength)
03638 {
03639     // NOTE: for backwards compatibility and for compatibility with old style
03640     // plugins, |stream| may include headers, specifically Content-Type and
03641     // Content-Length headers.  in this case, |contentType| and |contentLength|
03642     // would be unspecified.  this is traditionally the case of a POST request,
03643     // and so we select POST as the request method if contentType and
03644     // contentLength are unspecified.
03645     
03646     if (stream) {
03647         if (!contentType.IsEmpty()) {
03648             if (contentLength < 0) {
03649                 stream->Available((PRUint32 *) &contentLength);
03650                 if (contentLength < 0) {
03651                     NS_ERROR("unable to determine content length");
03652                     return NS_ERROR_FAILURE;
03653                 }
03654             }
03655             mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", contentLength));
03656             mRequestHead.SetHeader(nsHttp::Content_Type, contentType);
03657             mUploadStreamHasHeaders = PR_FALSE;
03658             mRequestHead.SetMethod(nsHttp::Put); // PUT request
03659         }
03660         else {
03661             mUploadStreamHasHeaders = PR_TRUE;
03662             mRequestHead.SetMethod(nsHttp::Post); // POST request
03663         }
03664     }
03665     else {
03666         mUploadStreamHasHeaders = PR_FALSE;
03667         mRequestHead.SetMethod(nsHttp::Get); // revert to GET request
03668     }
03669     mUploadStream = stream;    
03670     return NS_OK;
03671 }
03672 
03673 NS_IMETHODIMP
03674 nsHttpChannel::GetResponseStatus(PRUint32 *value)
03675 {
03676     NS_ENSURE_ARG_POINTER(value);
03677     if (!mResponseHead)
03678         return NS_ERROR_NOT_AVAILABLE;
03679     *value = mResponseHead->Status();
03680     return NS_OK;
03681 }
03682 
03683 NS_IMETHODIMP
03684 nsHttpChannel::GetResponseStatusText(nsACString &value)
03685 {
03686     if (!mResponseHead)
03687         return NS_ERROR_NOT_AVAILABLE;
03688     value = mResponseHead->StatusText();
03689     return NS_OK;
03690 }
03691 
03692 NS_IMETHODIMP
03693 nsHttpChannel::GetRequestSucceeded(PRBool *value)
03694 {
03695     NS_PRECONDITION(value, "Don't ever pass a null arg to this function");
03696     if (!mResponseHead)
03697         return NS_ERROR_NOT_AVAILABLE;
03698     PRUint32 status = mResponseHead->Status();
03699     *value = (status / 100 == 2);
03700     return NS_OK;
03701 }
03702 
03703 NS_IMETHODIMP
03704 nsHttpChannel::GetResponseHeader(const nsACString &header, nsACString &value)
03705 {
03706     if (!mResponseHead)
03707         return NS_ERROR_NOT_AVAILABLE;
03708     nsHttpAtom atom = nsHttp::ResolveAtom(header);
03709     if (!atom)
03710         return NS_ERROR_NOT_AVAILABLE;
03711     return mResponseHead->GetHeader(atom, value);
03712 }
03713 
03714 NS_IMETHODIMP
03715 nsHttpChannel::SetResponseHeader(const nsACString &header,
03716                                  const nsACString &value,
03717                                  PRBool merge)
03718 {
03719     LOG(("nsHttpChannel::SetResponseHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
03720         this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(), merge));
03721 
03722     if (!mResponseHead)
03723         return NS_ERROR_NOT_AVAILABLE;
03724     nsHttpAtom atom = nsHttp::ResolveAtom(header);
03725     if (!atom)
03726         return NS_ERROR_NOT_AVAILABLE;
03727 
03728     // these response headers must not be changed 
03729     if (atom == nsHttp::Content_Type ||
03730         atom == nsHttp::Content_Length ||
03731         atom == nsHttp::Content_Encoding ||
03732         atom == nsHttp::Trailer ||
03733         atom == nsHttp::Transfer_Encoding)
03734         return NS_ERROR_ILLEGAL_VALUE;
03735 
03736     mResponseHeadersModified = PR_TRUE;
03737 
03738     return mResponseHead->SetHeader(atom, value, merge);
03739 }
03740 
03741 NS_IMETHODIMP
03742 nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
03743 {
03744     if (!mResponseHead)
03745         return NS_ERROR_NOT_AVAILABLE;
03746     return mResponseHead->Headers().VisitHeaders(visitor);
03747 }
03748 
03749 NS_IMETHODIMP
03750 nsHttpChannel::IsNoStoreResponse(PRBool *value)
03751 {
03752     if (!mResponseHead)
03753         return NS_ERROR_NOT_AVAILABLE;
03754     *value = mResponseHead->NoStore();
03755     return NS_OK;
03756 }
03757 
03758 NS_IMETHODIMP
03759 nsHttpChannel::IsNoCacheResponse(PRBool *value)
03760 {
03761     if (!mResponseHead)
03762         return NS_ERROR_NOT_AVAILABLE;
03763     *value = mResponseHead->NoCache();
03764     if (!*value)
03765         *value = mResponseHead->ExpiresInPast();
03766     return NS_OK;
03767 }
03768 
03769 NS_IMETHODIMP
03770 nsHttpChannel::GetApplyConversion(PRBool *value)
03771 {
03772     NS_ENSURE_ARG_POINTER(value);
03773     *value = mApplyConversion;
03774     return NS_OK;
03775 }
03776 
03777 NS_IMETHODIMP
03778 nsHttpChannel::SetApplyConversion(PRBool value)
03779 {
03780     LOG(("nsHttpChannel::SetApplyConversion [this=%x value=%d]\n", this, value));
03781     mApplyConversion = value;
03782     return NS_OK;
03783 }
03784 
03785 NS_IMETHODIMP
03786 nsHttpChannel::GetAllowPipelining(PRBool *value)
03787 {
03788     NS_ENSURE_ARG_POINTER(value);
03789     *value = mAllowPipelining;
03790     return NS_OK;
03791 }
03792 
03793 NS_IMETHODIMP
03794 nsHttpChannel::SetAllowPipelining(PRBool value)
03795 {
03796     if (mIsPending)
03797         return NS_ERROR_FAILURE;
03798     mAllowPipelining = value;
03799     return NS_OK;
03800 }
03801 
03802 NS_IMETHODIMP
03803 nsHttpChannel::GetRedirectionLimit(PRUint32 *value)
03804 {
03805     NS_ENSURE_ARG_POINTER(value);
03806     *value = PRUint32(mRedirectionLimit);
03807     return NS_OK;
03808 }
03809 
03810 NS_IMETHODIMP
03811 nsHttpChannel::SetRedirectionLimit(PRUint32 value)
03812 {
03813     mRedirectionLimit = PR_MIN(value, 0xff);
03814     return NS_OK;
03815 }
03816 
03817 NS_IMETHODIMP
03818 nsHttpChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings)
03819 {
03820     NS_PRECONDITION(aEncodings, "Null out param");
03821     if (!mResponseHead) {
03822         *aEncodings = nsnull;
03823         return NS_OK;
03824     }
03825     
03826     const char *encoding = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
03827     if (!encoding) {
03828         *aEncodings = nsnull;
03829         return NS_OK;
03830     }
03831     nsContentEncodings* enumerator = new nsContentEncodings(this, encoding);
03832     if (!enumerator)
03833         return NS_ERROR_OUT_OF_MEMORY;
03834 
03835     NS_ADDREF(*aEncodings = enumerator);
03836     return NS_OK;
03837 }
03838 
03839 //-----------------------------------------------------------------------------
03840 // nsHttpChannel::nsIHttpChannelInternal
03841 //-----------------------------------------------------------------------------
03842 
03843 NS_IMETHODIMP
03844 nsHttpChannel::GetDocumentURI(nsIURI **aDocumentURI)
03845 {
03846     NS_ENSURE_ARG_POINTER(aDocumentURI);
03847     *aDocumentURI = mDocumentURI;
03848     NS_IF_ADDREF(*aDocumentURI);
03849     return NS_OK;
03850 }
03851 
03852 NS_IMETHODIMP
03853 nsHttpChannel::SetDocumentURI(nsIURI *aDocumentURI)
03854 {
03855     mDocumentURI = aDocumentURI;
03856     return NS_OK;
03857 }
03858 
03859 NS_IMETHODIMP
03860 nsHttpChannel::GetRequestVersion(PRUint32 *major, PRUint32 *minor)
03861 {
03862   int version = mRequestHead.Version();
03863 
03864   if (major) { *major = version / 10; }
03865   if (minor) { *minor = version % 10; }
03866 
03867   return NS_OK;
03868 }
03869 
03870 NS_IMETHODIMP
03871 nsHttpChannel::GetResponseVersion(PRUint32 *major, PRUint32 *minor)
03872 {
03873   if (!mResponseHead)
03874   {
03875     *major = *minor = 0;                   // we should at least be kind about it
03876     return NS_ERROR_NOT_AVAILABLE;
03877   }
03878 
03879   int version = mResponseHead->Version();
03880 
03881   if (major) { *major = version / 10; }
03882   if (minor) { *minor = version % 10; }
03883 
03884   return NS_OK;
03885 }
03886 
03887 NS_IMETHODIMP
03888 nsHttpChannel::SetCookie(const char *aCookieHeader)
03889 {
03890     // empty header isn't an error
03891     if (!(aCookieHeader && *aCookieHeader))
03892         return NS_OK;
03893 
03894     nsICookieService *cs = gHttpHandler->GetCookieService();
03895     NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
03896 
03897     nsCOMPtr<nsIPrompt> prompt;
03898     GetCallback(prompt);
03899 
03900     return cs->SetCookieStringFromHttp(mURI,
03901                                        mDocumentURI ? mDocumentURI : mOriginalURI,
03902                                        prompt,
03903                                        aCookieHeader,
03904                                        mResponseHead->PeekHeader(nsHttp::Date),
03905                                        this);
03906 }
03907 
03908 NS_IMETHODIMP
03909 nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
03910 {
03911     if (!mConnectionInfo)
03912         *result = nsnull;
03913     else {
03914         *result = mConnectionInfo->ProxyInfo();
03915         NS_IF_ADDREF(*result);
03916     }
03917     return NS_OK;
03918 }
03919 
03920 //-----------------------------------------------------------------------------
03921 // nsHttpChannel::nsISupportsPriority
03922 //-----------------------------------------------------------------------------
03923 
03924 NS_IMETHODIMP
03925 nsHttpChannel::GetPriority(PRInt32 *value)
03926 {
03927     *value = mPriority;
03928     return NS_OK;
03929 }
03930 
03931 NS_IMETHODIMP
03932 nsHttpChannel::SetPriority(PRInt32 value)
03933 {
03934     PRInt16 newValue = CLAMP(value, PR_INT16_MIN, PR_INT16_MAX);
03935     if (mPriority == newValue)
03936         return NS_OK;
03937     mPriority = newValue;
03938     if (mTransaction)
03939         gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
03940     return NS_OK;
03941 }
03942 
03943 NS_IMETHODIMP
03944 nsHttpChannel::AdjustPriority(PRInt32 delta)
03945 {
03946     return SetPriority(mPriority + delta);
03947 }
03948 
03949 //-----------------------------------------------------------------------------
03950 // nsHttpChannel::nsIProtocolProxyCallback
03951 //-----------------------------------------------------------------------------
03952 
03953 NS_IMETHODIMP
03954 nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIURI *uri,
03955                                 nsIProxyInfo *pi, nsresult status)
03956 {
03957     mProxyRequest = nsnull;
03958 
03959     // If status is a failure code, then it means that we failed to resolve
03960     // proxy info.  That is a non-fatal error assuming it wasn't because the
03961     // request was canceled.  We just failover to DIRECT when proxy resolution
03962     // fails (failure can mean that the PAC URL could not be loaded).
03963     
03964     // Need to replace this channel with a new one.  It would be complex to try
03965     // to change the value of mConnectionInfo since so much of our state may
03966     // depend on its state.
03967     if (!mCanceled) {
03968         status = ReplaceWithProxy(pi);
03969 
03970         // XXX(darin): It'd be nice if removing ourselves from the loadgroup
03971         // could be factored into ReplaceWithProxy somehow.
03972         if (mLoadGroup && NS_SUCCEEDED(status))
03973             mLoadGroup->RemoveRequest(this, nsnull, mStatus);
03974     }
03975 
03976     if (NS_FAILED(status))
03977         AsyncAbort(status);
03978     return NS_OK;
03979 }
03980 
03981 //-----------------------------------------------------------------------------
03982 // nsHttpChannel::nsIRequestObserver
03983 //-----------------------------------------------------------------------------
03984 
03985 NS_IMETHODIMP
03986 nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
03987 {
03988     if (!(mCanceled || NS_FAILED(mStatus))) {
03989         // capture the request's status, so our consumers will know ASAP of any
03990         // connection failures, etc - bug 93581
03991         request->GetStatus(&mStatus);
03992     }
03993 
03994     LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x status=%x]\n",
03995         this, request, mStatus));
03996 
03997     // Make sure things are what we expect them to be...
03998     NS_ASSERTION(request == mCachePump || request == mTransactionPump,
03999                  "Unexpected request");
04000     NS_ASSERTION(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
04001                  "If we have both pumps, the cache content must be partial");
04002 
04003     // don't enter this block if we're reading from the cache...
04004     if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
04005         // grab the security info from the connection object; the transaction
04006         // is guaranteed to own a reference to the connection.
04007         mSecurityInfo = mTransaction->SecurityInfo();
04008 
04009         NS_ASSERTION(mResponseHead == nsnull, "leaking mResponseHead");
04010 
04011         // all of the response headers have been acquired, so we can take ownership
04012         // of them from the transaction.
04013         mResponseHead = mTransaction->TakeResponseHead();
04014         // the response head may be null if the transaction was cancelled.  in
04015         // which case we just need to call OnStartRequest/OnStopRequest.
04016         if (mResponseHead)
04017             return ProcessResponse();
04018 
04019         NS_WARNING("No response head in OnStartRequest");
04020     }
04021 
04022     // avoid crashing if mListener happens to be null...
04023     if (!mListener) {
04024         NS_NOTREACHED("mListener is null");
04025         return NS_OK;
04026     }
04027 
04028     // on proxy errors, try to failover
04029     if (mConnectionInfo->ProxyInfo() &&
04030            (mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
04031             mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
04032             mStatus == NS_ERROR_NET_TIMEOUT)) {
04033         if (NS_SUCCEEDED(ProxyFailover()))
04034             return NS_OK;
04035     }
04036 
04037     return CallOnStartRequest();
04038 }
04039 
04040 NS_IMETHODIMP
04041 nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
04042 {
04043     LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
04044         this, request, status));
04045 
04046     // honor the cancelation status even if the underlying transaction completed.
04047     if (mCanceled || NS_FAILED(mStatus))
04048         status = mStatus;
04049 
04050     if (mCachedContentIsPartial) {
04051         if (NS_SUCCEEDED(status)) {
04052             // mTransactionPump should be suspended
04053             NS_ASSERTION(request != mTransactionPump,
04054                 "byte-range transaction finished prematurely");
04055 
04056             if (request == mCachePump) {
04057                 PRBool streamDone;
04058                 status = OnDoneReadingPartialCacheEntry(&streamDone);
04059                 if (NS_SUCCEEDED(status) && !streamDone)
04060                     return status;
04061                 // otherwise, fall through and fire OnStopRequest...
04062             }
04063             else
04064                 NS_NOTREACHED("unexpected request");
04065         }
04066         // Do not to leave the transaction in a suspended state in error cases.
04067         if (NS_FAILED(status) && mTransaction)
04068             gHttpHandler->CancelTransaction(mTransaction, status); 
04069     }
04070 
04071     PRBool isPartial = PR_FALSE;
04072     if (mTransaction) {
04073         // find out if the transaction ran to completion...
04074         if (mCacheEntry)
04075             isPartial = !mTransaction->ResponseIsComplete();
04076 
04077         // determine if we should call DoAuthRetry
04078         PRBool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
04079 
04080         //
04081         // grab reference to connection in case we need to retry an
04082         // authentication request over it.  this applies to connection based
04083         // authentication schemes only.  for request based schemes, conn is not
04084         // needed, so it may be null.
04085         // 
04086         // this code relies on the code in nsHttpTransaction::Close, which
04087         // tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
04088         // keep the connection around after the transaction is finished.
04089         //
04090         nsRefPtr<nsAHttpConnection> conn;
04091         if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION))
04092             conn = mTransaction->Connection();
04093 
04094         // at this point, we're done with the transaction
04095         NS_RELEASE(mTransaction);
04096         mTransactionPump = 0;
04097 
04098         // handle auth retry...
04099         if (authRetry) {
04100             mAuthRetryPending = PR_FALSE;
04101             status = DoAuthRetry(conn);
04102             if (NS_SUCCEEDED(status))
04103                 return NS_OK;
04104         }
04105 
04106         // if this transaction has been replaced, then bail.
04107         if (mTransactionReplaced)
04108             return NS_OK;
04109     }
04110 
04111     mIsPending = PR_FALSE;
04112     mStatus = status;
04113 
04114     // perform any final cache operations before we close the cache entry.
04115     if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE))
04116         FinalizeCacheEntry();
04117     
04118     if (mListener) {
04119         LOG(("  calling OnStopRequest\n"));
04120         mListener->OnStopRequest(this, mListenerContext, status);
04121         mListener = 0;
04122         mListenerContext = 0;
04123     }
04124 
04125     if (mCacheEntry) {
04126         nsresult closeStatus = status;
04127         // we don't want to discard the cache entry if we're only reading from
04128         // the cache.  If the cache entry was newly created, but we haven't
04129         // started streaming data to it, then we don't want to keep it around
04130         // if the load failed.
04131         if ((!mOpenedCacheForWriting && mCacheAccess != nsICache::ACCESS_WRITE) ||
04132                 request == mCachePump)
04133             closeStatus = NS_OK;
04134         // we also don't want to discard the cache entry if the server supports
04135         // byte range requests, because we could always complete the download
04136         // at a later time.
04137         else if (isPartial && mResponseHead && mResponseHead->IsResumable()) {
04138             LOG(("keeping partial response that is resumable!\n"));
04139             closeStatus = NS_OK; 
04140         }
04141         CloseCacheEntry(closeStatus);
04142     }
04143 
04144     if (mLoadGroup)
04145         mLoadGroup->RemoveRequest(this, nsnull, status);
04146 
04147     mCallbacks = nsnull;
04148     mProgressSink = nsnull;
04149     mEventQ = nsnull;
04150     
04151     return NS_OK;
04152 }
04153 
04154 //-----------------------------------------------------------------------------
04155 // nsHttpChannel::nsIStreamListener
04156 //-----------------------------------------------------------------------------
04157 
04158 NS_IMETHODIMP
04159 nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
04160                                nsIInputStream *input,
04161                                PRUint32 offset, PRUint32 count)
04162 {
04163     LOG(("nsHttpChannel::OnDataAvailable [this=%x request=%x offset=%u count=%u]\n",
04164         this, request, offset, count));
04165 
04166     // don't send out OnDataAvailable notifications if we've been canceled.
04167     if (mCanceled)
04168         return mStatus;
04169 
04170     NS_ASSERTION(mResponseHead, "No response head in ODA!!");
04171 
04172     NS_ASSERTION(!(mCachedContentIsPartial && (request == mTransactionPump)),
04173             "transaction pump not suspended");
04174 
04175     if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
04176         PRUint32 n;
04177         return input->ReadSegments(DiscardSegments, nsnull, count, &n);
04178     }
04179 
04180     if (mListener) {
04181         //
04182         // synthesize transport progress event.  we do this here since we want
04183         // to delay OnProgress events until we start streaming data.  this is
04184         // crucially important since it impacts the lock icon (see bug 240053).
04185         //
04186         nsresult transportStatus;
04187         if (request == mCachePump)
04188             transportStatus = nsITransport::STATUS_READING;
04189         else
04190             transportStatus = nsISocketTransport::STATUS_RECEIVING_FROM;
04191 
04192         // mResponseHead may reference new or cached headers, but either way it
04193         // holds our best estimate of the total content length.  Even in the case
04194         // of a byte range request, the content length stored in the cached
04195         // response headers is what we want to use here.
04196 
04197         nsUint64 progressMax(PRUint64(mResponseHead->ContentLength()));
04198         nsUint64 progress = mLogicalOffset + nsUint64(count);
04199         NS_ASSERTION(progress <= progressMax, "unexpected progress values");
04200 
04201         OnTransportStatus(nsnull, transportStatus, progress, progressMax);
04202 
04203         //
04204         // we have to manually keep the logical offset of the stream up-to-date.
04205         // we cannot depend soley on the offset provided, since we may have 
04206         // already streamed some data from another source (see, for example,
04207         // OnDoneReadingPartialCacheEntry).
04208         //
04209         nsresult rv =  mListener->OnDataAvailable(this,
04210                                                   mListenerContext,
04211                                                   input,
04212                                                   mLogicalOffset,
04213                                                   count);
04214         if (NS_SUCCEEDED(rv))
04215             mLogicalOffset = progress;
04216         return rv;
04217     }
04218 
04219     return NS_ERROR_ABORT;
04220 }
04221 
04222 //-----------------------------------------------------------------------------
04223 // nsHttpChannel::nsITransportEventSink
04224 //-----------------------------------------------------------------------------
04225 
04226 NS_IMETHODIMP
04227 nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
04228                                  PRUint64 progress, PRUint64 progressMax)
04229 {
04230     // cache the progress sink so we don't have to query for it each time.
04231     if (!mProgressSink)
04232         GetCallback(mProgressSink);
04233 
04234     // block socket status event after Cancel or OnStopRequest has been called.
04235     if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
04236         LOG(("sending status notification [this=%x status=%x progress=%llu/%llu]\n",
04237             this, status, progress, progressMax));
04238 
04239         nsCAutoString host;
04240         mURI->GetHost(host);
04241         mProgressSink->OnStatus(this, nsnull, status,
04242                                 NS_ConvertUTF8toUTF16(host).get());
04243 
04244         if (progress > 0)
04245             mProgressSink->OnProgress(this, nsnull, progress, progressMax);
04246     }
04247 #ifdef DEBUG
04248     else
04249         LOG(("skipping status notification [this=%x sink=%x pending=%u background=%x]\n",
04250             this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
04251 #endif
04252 
04253     return NS_OK;
04254 } 
04255 
04256 //-----------------------------------------------------------------------------
04257 // nsHttpChannel::nsICachingChannel
04258 //-----------------------------------------------------------------------------
04259 
04260 NS_IMETHODIMP
04261 nsHttpChannel::GetCacheToken(nsISupports **token)
04262 {
04263     NS_ENSURE_ARG_POINTER(token);
04264     if (!mCacheEntry)
04265         return NS_ERROR_NOT_AVAILABLE;
04266     return CallQueryInterface(mCacheEntry, token);
04267 }
04268 
04269 NS_IMETHODIMP
04270 nsHttpChannel::SetCacheToken(nsISupports *token)
04271 {
04272     return NS_ERROR_NOT_IMPLEMENTED;
04273 }
04274 
04275 NS_IMETHODIMP
04276 nsHttpChannel::GetCacheKey(nsISupports **key)
04277 {
04278     nsresult rv;
04279     NS_ENSURE_ARG_POINTER(key);
04280 
04281     LOG(("nsHttpChannel::GetCacheKey [this=%x]\n", this));
04282 
04283     *key = nsnull;
04284 
04285     nsCOMPtr<nsISupportsPRUint32> container =
04286         do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
04287     if (NS_FAILED(rv)) return rv;
04288 
04289     rv = container->SetData(mPostID);
04290     if (NS_FAILED(rv)) return rv;
04291 
04292     return CallQueryInterface(container, key);
04293 }
04294 
04295 NS_IMETHODIMP
04296 nsHttpChannel::SetCacheKey(nsISupports *key)
04297 {
04298     nsresult rv;
04299 
04300     LOG(("nsHttpChannel::SetCacheKey [this=%x key=%x]\n", this, key));
04301 
04302     // can only set the cache key if a load is not in progress
04303     NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
04304 
04305     if (!key)
04306         mPostID = 0;
04307     else {
04308         // extract the post id
04309         nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
04310         if (NS_FAILED(rv)) return rv;
04311 
04312         rv = container->GetData(&mPostID);
04313         if (NS_FAILED(rv)) return rv;
04314     }
04315     return NS_OK;
04316 }
04317 
04318 NS_IMETHODIMP
04319 nsHttpChannel::GetCacheAsFile(PRBool *value)
04320 {
04321     NS_ENSURE_ARG_POINTER(value);
04322     if (!mCacheEntry)
04323         return NS_ERROR_NOT_AVAILABLE;
04324     nsCacheStoragePolicy storagePolicy;
04325     mCacheEntry->GetStoragePolicy(&storagePolicy);
04326     *value = (storagePolicy == nsICache::STORE_ON_DISK_AS_FILE);
04327     return NS_OK;
04328 }
04329 
04330 NS_IMETHODIMP
04331 nsHttpChannel::SetCacheAsFile(PRBool value)
04332 {
04333     if (!mCacheEntry || mLoadFlags & INHIBIT_PERSISTENT_CACHING)
04334         return NS_ERROR_NOT_AVAILABLE;
04335     nsCacheStoragePolicy policy;
04336     if (value)
04337         policy = nsICache::STORE_ON_DISK_AS_FILE;
04338     else
04339         policy = nsICache::STORE_ANYWHERE;
04340     return mCacheEntry->SetStoragePolicy(policy);
04341 }
04342 
04343 NS_IMETHODIMP
04344 nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
04345 {
04346     if (!mCacheEntry)
04347         return NS_ERROR_NOT_AVAILABLE;
04348     return mCacheEntry->GetFile(cacheFile);
04349 }
04350 
04351 NS_IMETHODIMP
04352 nsHttpChannel::IsFromCache(PRBool *value)
04353 {
04354     if (!mIsPending)
04355         return NS_ERROR_NOT_AVAILABLE;
04356 
04357     // return false if reading a partial cache entry; the data isn't entirely
04358     // from the cache!
04359 
04360     *value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
04361               mCachedContentIsValid && !mCachedContentIsPartial;
04362 
04363     return NS_OK;
04364 }
04365 
04366 //-----------------------------------------------------------------------------
04367 // nsHttpChannel::nsIResumableChannel
04368 //-----------------------------------------------------------------------------
04369 
04370 NS_IMETHODIMP
04371 nsHttpChannel::ResumeAt(PRUint64 aStartPos,
04372                         const nsACString& aEntityID)
04373 {
04374     mEntityID = aEntityID;
04375     mStartPos = aStartPos;
04376     mResuming = PR_TRUE;
04377     return NS_OK;
04378 }
04379 
04380 NS_IMETHODIMP
04381 nsHttpChannel::GetEntityID(nsACString& aEntityID)
04382 {
04383     // Don't return an entity ID for HTTP/1.0 servers
04384     if (mResponseHead && (mResponseHead->Version() < NS_HTTP_VERSION_1_1)) {
04385         return NS_ERROR_NOT_RESUMABLE;
04386     }
04387     // Neither return one for Non-GET requests which require additional data
04388     if (mRequestHead.Method() != nsHttp::Get) {
04389         return NS_ERROR_NOT_RESUMABLE;
04390     }
04391 
04392     PRUint64 size = LL_MAXUINT;
04393     nsCAutoString etag, lastmod;
04394     if (mResponseHead) {
04395         size = mResponseHead->TotalEntitySize();
04396         const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
04397         if (cLastMod)
04398             lastmod = cLastMod;
04399         const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
04400         if (cEtag)
04401             etag = cEtag;
04402     }
04403     nsCString entityID;
04404     NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
04405             esc_FileBaseName | esc_Forced, entityID);
04406     entityID.Append('/');
04407     entityID.AppendInt(PRInt64(size));
04408     entityID.Append('/');
04409     entityID.Append(lastmod);
04410     // NOTE: Appending lastmod as the last part avoids having to escape it
04411 
04412     aEntityID = entityID;
04413 
04414     return NS_OK;
04415 }
04416 
04417 //-----------------------------------------------------------------------------
04418 // nsHttpChannel::nsICacheListener
04419 //-----------------------------------------------------------------------------
04420 
04421 NS_IMETHODIMP
04422 nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
04423                                      nsCacheAccessMode access,
04424                                      nsresult status)
04425 {
04426     LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%x entry=%x "
04427          "access=%x status=%x]\n", this, entry, access, status));
04428 
04429     // if the channel's already fired onStopRequest, then we should ignore
04430     // this event.
04431     if (!mIsPending)
04432         return NS_OK;
04433 
04434     // otherwise, we have to handle this event.
04435     if (NS_SUCCEEDED(status)) {
04436         mCacheEntry = entry;
04437         mCacheAccess = access;
04438     }
04439 
04440     nsresult rv;
04441 
04442     if (mCanceled && NS_FAILED(mStatus)) {
04443         LOG(("channel was canceled [this=%x status=%x]\n", this, mStatus));
04444         rv = mStatus;
04445     }
04446     else if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(status))
04447         // if this channel is only allowed to pull from the cache, then
04448         // we must fail if we were unable to open a cache entry.
04449         rv = NS_ERROR_DOCUMENT_NOT_CACHED;
04450     else
04451         // advance to the next state...
04452         rv = Connect(PR_FALSE);
04453 
04454     // a failure from Connect means that we have to abort the channel.
04455     if (NS_FAILED(rv)) {
04456         CloseCacheEntry(rv);
04457         AsyncAbort(rv);
04458     }
04459 
04460     return NS_OK;
04461 }
04462 
04463 void
04464 nsHttpChannel::ClearPasswordManagerEntry(const char      *scheme,
04465                                          const char      *host,
04466                                          PRInt32          port,
04467                                          const char      *realm,
04468                                          const PRUnichar *user)
04469 {
04470     // XXX scheme is currently unused.  see comments in PromptForIdentity
04471 
04472     nsresult rv;
04473     nsCOMPtr<nsIPasswordManager> passWordManager = do_GetService(NS_PASSWORDMANAGER_CONTRACTID, &rv);
04474     if (passWordManager) {
04475         nsCAutoString domain;
04476         domain.Assign(host);
04477         domain.Append(':');
04478         domain.AppendInt(port);
04479 
04480         domain.AppendLiteral(" (");
04481         domain.Append(realm);
04482         domain.Append(')');
04483 
04484         passWordManager->RemoveUser(domain, nsDependentString(user));
04485     }
04486 } 
04487 
04488 nsresult
04489 nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
04490 {
04491     LOG(("nsHttpChannel::DoAuthRetry [this=%x]\n", this));
04492 
04493     NS_ASSERTION(!mTransaction, "should not have a transaction");
04494     nsresult rv;
04495 
04496     // toggle mIsPending to allow nsIObserver implementations to modify
04497     // the request headers (bug 95044).
04498     mIsPending = PR_FALSE;
04499 
04500     // fetch cookies, and add them to the request header.
04501     // the server response could have included cookies that must be sent with
04502     // this authentication attempt (bug 84794).
04503     AddCookiesToRequest();
04504 
04505     // notify "http-on-modify-request" observers
04506     gHttpHandler->OnModifyRequest(this);
04507 
04508     mIsPending = PR_TRUE;
04509 
04510     // get rid of the old response headers
04511     delete mResponseHead;
04512     mResponseHead = nsnull;
04513 
04514     // set sticky connection flag and disable pipelining.
04515     mCaps |=  NS_HTTP_STICKY_CONNECTION;
04516     mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
04517    
04518     // and create a new one...
04519     rv = SetupTransaction();
04520     if (NS_FAILED(rv)) return rv;
04521 
04522     // transfer ownership of connection to transaction
04523     if (conn)
04524         mTransaction->SetConnection(conn);
04525 
04526     // rewind the upload stream
04527     if (mUploadStream) {
04528         nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
04529         if (seekable)
04530             seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
04531     }
04532 
04533     rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
04534     if (NS_FAILED(rv)) return rv;
04535 
04536     return mTransactionPump->AsyncRead(this, nsnull);
04537 }
04538 
04539 //-----------------------------------------------------------------------------
04540 // nsHttpChannel::nsContentEncodings <public>
04541 //-----------------------------------------------------------------------------
04542 
04543 nsHttpChannel::nsContentEncodings::nsContentEncodings(nsIHttpChannel* aChannel,
04544                                                           const char* aEncodingHeader) :
04545     mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(PR_FALSE)
04546 {
04547     mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
04548     mCurStart = mCurEnd;
04549 }
04550     
04551 nsHttpChannel::nsContentEncodings::~nsContentEncodings()
04552 {
04553 }
04554 
04555 //-----------------------------------------------------------------------------
04556 // nsHttpChannel::nsContentEncodings::nsISimpleEnumerator
04557 //-----------------------------------------------------------------------------
04558 
04559 NS_IMETHODIMP
04560 nsHttpChannel::nsContentEncodings::HasMore(PRBool* aMoreEncodings)
04561 {
04562     if (mReady) {
04563         *aMoreEncodings = PR_TRUE;
04564         return NS_OK;
04565     }
04566     
04567     nsresult rv = PrepareForNext();
04568     *aMoreEncodings = NS_SUCCEEDED(rv);
04569     return NS_OK;
04570 }
04571 
04572 NS_IMETHODIMP
04573 nsHttpChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding)
04574 {
04575     aNextEncoding.Truncate();
04576     if (!mReady) {
04577         nsresult rv = PrepareForNext();
04578         if (NS_FAILED(rv)) {
04579             return NS_ERROR_FAILURE;
04580         }
04581     }
04582 
04583     const nsACString & encoding = Substring(mCurStart, mCurEnd);
04584 
04585     nsACString::const_iterator start, end;
04586     encoding.BeginReading(start);
04587     encoding.EndReading(end);
04588 
04589     PRBool haveType = PR_FALSE;
04590     if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("gzip"),
04591                                       start,
04592                                       end)) {
04593         aNextEncoding.AssignLiteral(APPLICATION_GZIP);
04594         haveType = PR_TRUE;
04595     }
04596 
04597     if (!haveType) {
04598         encoding.BeginReading(start);
04599         if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"),
04600                                           start,
04601                                           end)) {
04602             aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
04603                                            
04604             haveType = PR_TRUE;
04605         }
04606     }
04607     
04608     if (! haveType) {
04609         encoding.BeginReading(start);
04610         if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"),
04611                                           start,
04612                                           end)) {
04613             aNextEncoding.AssignLiteral(APPLICATION_ZIP);
04614             haveType = PR_TRUE;
04615         }
04616     }
04617 
04618     // Prepare to fetch the next encoding
04619     mCurEnd = mCurStart;
04620     mReady = PR_FALSE;
04621     
04622     if (haveType)
04623         return NS_OK;
04624 
04625     NS_WARNING("Unknown encoding type");
04626     return NS_ERROR_FAILURE;
04627 }
04628 
04629 //-----------------------------------------------------------------------------
04630 // nsHttpChannel::nsContentEncodings::nsISupports
04631 //-----------------------------------------------------------------------------
04632 
04633 NS_IMPL_ISUPPORTS1(nsHttpChannel::nsContentEncodings, nsIUTF8StringEnumerator)
04634 
04635 //-----------------------------------------------------------------------------
04636 // nsHttpChannel::nsContentEncodings <private>
04637 //-----------------------------------------------------------------------------
04638 
04639 nsresult
04640 nsHttpChannel::nsContentEncodings::PrepareForNext(void)
04641 {
04642     NS_PRECONDITION(mCurStart == mCurEnd, "Indeterminate state");
04643     
04644     // At this point both mCurStart and mCurEnd point to somewhere
04645     // past the end of the next thing we want to return
04646     
04647     while (mCurEnd != mEncodingHeader) {
04648         --mCurEnd;
04649         if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd))
04650             break;
04651     }
04652     if (mCurEnd == mEncodingHeader)
04653         return NS_ERROR_NOT_AVAILABLE; // no more encodings
04654     ++mCurEnd;
04655         
04656     // At this point mCurEnd points to the first char _after_ the
04657     // header we want.  Furthermore, mCurEnd - 1 != mEncodingHeader
04658     
04659     mCurStart = mCurEnd - 1;
04660     while (mCurStart != mEncodingHeader &&
04661            *mCurStart != ',' && !nsCRT::IsAsciiSpace(*mCurStart))
04662         --mCurStart;
04663     if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
04664         ++mCurStart; // we stopped because of a weird char, so move up one
04665         
04666     // At this point mCurStart and mCurEnd bracket the encoding string
04667     // we want.  Check that it's not "identity"
04668     if (Substring(mCurStart, mCurEnd).Equals("identity",
04669                                              nsCaseInsensitiveCStringComparator())) {
04670         mCurEnd = mCurStart;
04671         return PrepareForNext();
04672     }
04673         
04674     mReady = PR_TRUE;
04675     return NS_OK;
04676 }