RetroZilla/netwerk/protocol/http/src/nsHttpChannel.cpp
2015-10-20 23:03:22 -04:00

4775 lines
158 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla.
*
* The Initial Developer of the Original Code is
* Netscape Communications.
* Portions created by the Initial Developer are Copyright (C) 2001
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@meer.net> (original author)
* Christian Biesinger <cbiesinger@web.de>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "nsHttpChannel.h"
#include "nsHttpTransaction.h"
#include "nsHttpConnection.h"
#include "nsHttpHandler.h"
#include "nsHttpAuthCache.h"
#include "nsHttpResponseHead.h"
#include "nsHttp.h"
#include "nsIHttpAuthenticator.h"
#include "nsIAuthPrompt.h"
#include "nsIAuthPromptProvider.h"
#include "nsIStringBundle.h"
#include "nsXPCOM.h"
#include "nsISupportsPrimitives.h"
#include "nsIURL.h"
#include "nsIScriptSecurityManager.h"
#include "nsIIDNService.h"
#include "nsIStreamListenerTee.h"
#include "nsISeekableStream.h"
#include "nsCPasswordManager.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsString.h"
#include "nsPrintfCString.h"
#include "nsReadableUtils.h"
#include "nsUnicharUtils.h"
#include "nsAutoPtr.h"
#include "plstr.h"
#include "prprf.h"
#include "nsEscape.h"
#include "nsICookieService.h"
#include "nsIResumableChannel.h"
#include "nsInt64.h"
#include "nsIVariant.h"
#include "nsChannelProperties.h"
#include "nsIOService.h"
// True if the local cache should be bypassed when processing a request.
#define BYPASS_LOCAL_CACHE(loadFlags) \
(loadFlags & (nsIRequest::LOAD_BYPASS_CACHE | \
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE))
static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
static NS_METHOD DiscardSegments(nsIInputStream *input,
void *closure,
const char *buf,
PRUint32 offset,
PRUint32 count,
PRUint32 *countRead)
{
*countRead = count;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------
nsHttpChannel::nsHttpChannel()
: mResponseHead(nsnull)
, mTransaction(nsnull)
, mConnectionInfo(nsnull)
, mLoadFlags(LOAD_NORMAL)
, mStatus(NS_OK)
, mLogicalOffset(0)
, mCaps(0)
, mPriority(PRIORITY_NORMAL)
, mCachedResponseHead(nsnull)
, mCacheAccess(0)
, mPostID(0)
, mRequestTime(0)
, mProxyAuthContinuationState(nsnull)
, mAuthContinuationState(nsnull)
, mStartPos(LL_MAXUINT)
, mRedirectionLimit(gHttpHandler->RedirectionLimit())
, mIsPending(PR_FALSE)
, mApplyConversion(PR_TRUE)
, mAllowPipelining(PR_TRUE)
, mCachedContentIsValid(PR_FALSE)
, mCachedContentIsPartial(PR_FALSE)
, mResponseHeadersModified(PR_FALSE)
, mCanceled(PR_FALSE)
, mTransactionReplaced(PR_FALSE)
, mUploadStreamHasHeaders(PR_FALSE)
, mAuthRetryPending(PR_FALSE)
, mSuppressDefensiveAuth(PR_FALSE)
, mResuming(PR_FALSE)
, mOpenedCacheForWriting(PR_FALSE)
{
LOG(("Creating nsHttpChannel @%x\n", this));
// grab a reference to the handler to ensure that it doesn't go away.
nsHttpHandler *handler = gHttpHandler;
NS_ADDREF(handler);
}
nsHttpChannel::~nsHttpChannel()
{
LOG(("Destroying nsHttpChannel @%x\n", this));
if (mResponseHead) {
delete mResponseHead;
mResponseHead = 0;
}
if (mCachedResponseHead) {
delete mCachedResponseHead;
mCachedResponseHead = 0;
}
NS_IF_RELEASE(mConnectionInfo);
NS_IF_RELEASE(mTransaction);
NS_IF_RELEASE(mProxyAuthContinuationState);
NS_IF_RELEASE(mAuthContinuationState);
// release our reference to the handler
nsHttpHandler *handler = gHttpHandler;
NS_RELEASE(handler);
}
nsresult
nsHttpChannel::Init(nsIURI *uri,
PRUint8 caps,
nsProxyInfo *proxyInfo)
{
LOG(("nsHttpChannel::Init [this=%x]\n", this));
NS_PRECONDITION(uri, "null uri");
nsresult rv = nsHashPropertyBag::Init();
if (NS_FAILED(rv))
return rv;
mURI = uri;
mOriginalURI = uri;
mDocumentURI = nsnull;
mCaps = caps;
//
// Construct connection info object
//
nsCAutoString host;
PRInt32 port = -1;
PRBool usingSSL = PR_FALSE;
rv = mURI->SchemeIs("https", &usingSSL);
if (NS_FAILED(rv)) return rv;
rv = mURI->GetAsciiHost(host);
if (NS_FAILED(rv)) return rv;
// reject the URL if it doesn't specify a host
if (host.IsEmpty())
return NS_ERROR_MALFORMED_URI;
rv = mURI->GetPort(&port);
if (NS_FAILED(rv)) return rv;
LOG(("host=%s port=%d\n", host.get(), port));
rv = mURI->GetAsciiSpec(mSpec);
if (NS_FAILED(rv)) return rv;
LOG(("uri=%s\n", mSpec.get()));
mConnectionInfo = new nsHttpConnectionInfo(host, port,
proxyInfo, usingSSL);
if (!mConnectionInfo)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mConnectionInfo);
// make sure our load flags include this bit if this is a secure channel.
if (usingSSL && !gHttpHandler->IsPersistentHttpsCachingEnabled())
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
// Set default request method
mRequestHead.SetMethod(nsHttp::Get);
//
// Set request headers
//
nsCAutoString hostLine;
if (strchr(host.get(), ':')) {
// host is an IPv6 address literal and must be encapsulated in []'s
hostLine.Assign('[');
hostLine.Append(host);
hostLine.Append(']');
}
else
hostLine.Assign(host);
if (port != -1) {
hostLine.Append(':');
hostLine.AppendInt(port);
}
rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
if (NS_FAILED(rv)) return rv;
rv = gHttpHandler->
AddStandardRequestHeaders(&mRequestHead.Headers(), caps,
!mConnectionInfo->UsingSSL() &&
mConnectionInfo->UsingHttpProxy());
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <private>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::AsyncCall(nsAsyncCallback funcPtr)
{
nsresult rv;
nsAsyncCallEvent *event = new nsAsyncCallEvent;
if (!event)
return NS_ERROR_OUT_OF_MEMORY;
event->mFuncPtr = funcPtr;
NS_ADDREF_THIS();
PL_InitEvent(event, this,
nsHttpChannel::AsyncCall_EventHandlerFunc,
nsHttpChannel::AsyncCall_EventCleanupFunc);
rv = mEventQ->PostEvent(event);
if (NS_FAILED(rv)) {
PL_DestroyEvent(event);
NS_RELEASE_THIS();
}
return rv;
}
void *PR_CALLBACK
nsHttpChannel::AsyncCall_EventHandlerFunc(PLEvent *ev)
{
nsHttpChannel *chan =
NS_STATIC_CAST(nsHttpChannel *, PL_GetEventOwner(ev));
nsAsyncCallEvent *ace = (nsAsyncCallEvent *) ev;
nsAsyncCallback funcPtr = ace->mFuncPtr;
if (chan) {
(chan->*funcPtr)();
NS_RELEASE(chan);
}
return nsnull;
}
void PR_CALLBACK
nsHttpChannel::AsyncCall_EventCleanupFunc(PLEvent *ev)
{
delete (nsAsyncCallEvent *) ev;
}
PRBool
nsHttpChannel::RequestIsConditional()
{
// Is our consumer issuing a conditional request?
return mRequestHead.PeekHeader(nsHttp::If_Modified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_None_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Unmodified_Since) ||
mRequestHead.PeekHeader(nsHttp::If_Match) ||
mRequestHead.PeekHeader(nsHttp::If_Range);
}
nsresult
nsHttpChannel::Connect(PRBool firstTime)
{
nsresult rv;
LOG(("nsHttpChannel::Connect [this=%x]\n", this));
// ensure that we are using a valid hostname
if (!net_IsValidHostName(nsDependentCString(mConnectionInfo->Host())))
return NS_ERROR_UNKNOWN_HOST;
// true when called from AsyncOpen
if (firstTime) {
PRBool delayed = PR_FALSE;
PRBool offline = PR_FALSE;
// are we offline?
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) return rv;
ioService->GetOffline(&offline);
if (offline)
mLoadFlags |= LOAD_ONLY_FROM_CACHE;
else if (PL_strcmp(mConnectionInfo->ProxyType(), "unknown") == 0)
return ResolveProxy(); // Lazily resolve proxy info
// Don't allow resuming when cache must be used
if (mResuming && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
LOG(("Resuming from cache is not supported yet"));
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
// open a cache entry for this channel...
rv = OpenCacheEntry(offline, &delayed);
if (NS_FAILED(rv)) {
LOG(("OpenCacheEntry failed [rv=%x]\n", rv));
// if this channel is only allowed to pull from the cache, then
// we must fail if we were unable to open a cache entry.
if (mLoadFlags & LOAD_ONLY_FROM_CACHE)
return NS_ERROR_DOCUMENT_NOT_CACHED;
// otherwise, let's just proceed without using the cache.
}
if (NS_SUCCEEDED(rv) && delayed)
return NS_OK;
}
// we may or may not have a cache entry at this point
if (mCacheEntry) {
// inspect the cache entry to determine whether or not we need to go
// out to net to validate it. this call sets mCachedContentIsValid
// and may set request headers as required for cache validation.
rv = CheckCache();
NS_ASSERTION(NS_SUCCEEDED(rv), "cache check failed");
// read straight from the cache if possible...
if (mCachedContentIsValid) {
return ReadFromCache();
}
else if (mLoadFlags & LOAD_ONLY_FROM_CACHE) {
// the cache contains the requested resource, but it must be
// validated before we can reuse it. since we are not allowed
// to hit the net, there's nothing more to do. the document
// is effectively not in the cache.
return NS_ERROR_DOCUMENT_NOT_CACHED;
}
}
// check to see if authorization headers should be included
AddAuthorizationHeaders();
// hit the net...
rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
if (NS_FAILED(rv)) return rv;
return mTransactionPump->AsyncRead(this, nsnull);
}
// called when Connect fails
nsresult
nsHttpChannel::AsyncAbort(nsresult status)
{
LOG(("nsHttpChannel::AsyncAbort [this=%x status=%x]\n", this, status));
mStatus = status;
mIsPending = PR_FALSE;
// create a proxy for the listener..
nsCOMPtr<nsIRequestObserver> observer;
NS_NewRequestObserverProxy(getter_AddRefs(observer), mListener, mEventQ);
if (observer) {
observer->OnStartRequest(this, mListenerContext);
observer->OnStopRequest(this, mListenerContext, mStatus);
}
else {
NS_ERROR("unable to create request observer proxy");
// XXX else, no proxy object manager... what do we do?
}
mListener = 0;
mListenerContext = 0;
// finally remove ourselves from the load group.
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, status);
return NS_OK;
}
void
nsHttpChannel::HandleAsyncRedirect()
{
nsresult rv = NS_OK;
LOG(("nsHttpChannel::HandleAsyncRedirect [this=%p]\n", this));
// since this event is handled asynchronously, it is possible that this
// channel could have been canceled, in which case there would be no point
// in processing the redirect.
if (NS_SUCCEEDED(mStatus)) {
rv = ProcessRedirection(mResponseHead->Status());
if (NS_FAILED(rv)) {
// If ProcessRedirection fails, then we have to send out the
// OnStart/OnStop notifications.
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
mStatus = rv;
if (mListener) {
mListener->OnStartRequest(this, mListenerContext);
mListener->OnStopRequest(this, mListenerContext, mStatus);
mListener = 0;
mListenerContext = 0;
}
}
}
// close the cache entry... blow it away if we couldn't process
// the redirect for some reason.
CloseCacheEntry(rv);
mIsPending = PR_FALSE;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
void
nsHttpChannel::HandleAsyncNotModified()
{
LOG(("nsHttpChannel::HandleAsyncNotModified [this=%p]\n", this));
if (mListener) {
mListener->OnStartRequest(this, mListenerContext);
mListener->OnStopRequest(this, mListenerContext, mStatus);
mListener = 0;
mListenerContext = 0;
}
CloseCacheEntry(NS_OK);
mIsPending = PR_FALSE;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
nsresult
nsHttpChannel::SetupTransaction()
{
LOG(("nsHttpChannel::SetupTransaction [this=%x]\n", this));
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsresult rv;
if (mCaps & NS_HTTP_ALLOW_PIPELINING) {
//
// disable pipelining if:
// (1) pipelining has been explicitly disabled
// (2) request corresponds to a top-level document load (link click)
// (3) request method is non-idempotent
//
// XXX does the toplevel document check really belong here? or, should
// we push it out entirely to necko consumers?
//
if (!mAllowPipelining || (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI) ||
!(mRequestHead.Method() == nsHttp::Get ||
mRequestHead.Method() == nsHttp::Head ||
mRequestHead.Method() == nsHttp::Propfind ||
mRequestHead.Method() == nsHttp::Proppatch)) {
LOG((" pipelining disallowed\n"));
mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
}
}
// use the URI path if not proxying (transparent proxying such as SSL proxy
// does not count here). also, figure out what version we should be speaking.
nsCAutoString buf, path;
nsCString* requestURI;
if (mConnectionInfo->UsingSSL() || !mConnectionInfo->UsingHttpProxy()) {
rv = mURI->GetPath(path);
if (NS_FAILED(rv)) return rv;
// path may contain UTF-8 characters, so ensure that they're escaped.
if (NS_EscapeURL(path.get(), path.Length(), esc_OnlyNonASCII, buf))
requestURI = &buf;
else
requestURI = &path;
mRequestHead.SetVersion(gHttpHandler->HttpVersion());
}
else {
rv = mURI->GetUserPass(buf);
if (NS_FAILED(rv)) return rv;
if (!buf.IsEmpty() && ((strncmp(mSpec.get(), "http:", 5) == 0) ||
strncmp(mSpec.get(), "https:", 6) == 0)) {
nsCOMPtr<nsIURI> tempURI;
rv = mURI->Clone(getter_AddRefs(tempURI));
if (NS_FAILED(rv)) return rv;
rv = tempURI->SetUserPass(EmptyCString());
if (NS_FAILED(rv)) return rv;
rv = tempURI->GetAsciiSpec(path);
if (NS_FAILED(rv)) return rv;
requestURI = &path;
}
else
requestURI = &mSpec;
mRequestHead.SetVersion(gHttpHandler->ProxyHttpVersion());
}
// trim off the #ref portion if any...
PRInt32 ref = requestURI->FindChar('#');
if (ref != kNotFound)
requestURI->SetLength(ref);
mRequestHead.SetRequestURI(*requestURI);
// set the request time for cache expiration calculations
mRequestTime = NowInSeconds();
// if doing a reload, force end-to-end
if (mLoadFlags & LOAD_BYPASS_CACHE) {
// We need to send 'Pragma:no-cache' to inhibit proxy caching even if
// no proxy is configured since we might be talking with a transparent
// proxy, i.e. one that operates at the network level. See bug #14772.
mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
// If we're configured to speak HTTP/1.1 then also send 'Cache-control:
// no-cache'
if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
}
else if ((mLoadFlags & VALIDATE_ALWAYS) && (mCacheAccess & nsICache::ACCESS_READ)) {
// We need to send 'Cache-Control: max-age=0' to force each cache along
// the path to the origin server to revalidate its own entry, if any,
// with the next cache or server. See bug #84847.
//
// If we're configured to speak HTTP/1.0 then just send 'Pragma: no-cache'
if (mRequestHead.Version() >= NS_HTTP_VERSION_1_1)
mRequestHead.SetHeader(nsHttp::Cache_Control, NS_LITERAL_CSTRING("max-age=0"), PR_TRUE);
else
mRequestHead.SetHeader(nsHttp::Pragma, NS_LITERAL_CSTRING("no-cache"), PR_TRUE);
}
if (mResuming) {
char byteRange[32];
PR_snprintf(byteRange, sizeof(byteRange), "bytes=%llu-", mStartPos);
mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(byteRange));
if (!mEntityID.IsEmpty()) {
// Also, we want an error if this resource changed in the meantime
// Format of the entity id is: escaped_etag/size/lastmod
nsCString::const_iterator start, end, slash;
mEntityID.BeginReading(start);
mEntityID.EndReading(end);
mEntityID.BeginReading(slash);
if (FindCharInReadable('/', slash, end)) {
nsCAutoString ifMatch;
mRequestHead.SetHeader(nsHttp::If_Match,
NS_UnescapeURL(Substring(start, slash), 0, ifMatch));
++slash; // Incrementing, so that searching for '/' won't find
// the same slash again
}
if (FindCharInReadable('/', slash, end)) {
mRequestHead.SetHeader(nsHttp::If_Unmodified_Since,
Substring(++slash, end));
}
}
}
// create wrapper for this channel's notification callbacks
nsCOMPtr<nsIInterfaceRequestor> callbacks;
NS_NewNotificationCallbacksAggregation(mCallbacks, mLoadGroup,
getter_AddRefs(callbacks));
if (!callbacks)
return NS_ERROR_OUT_OF_MEMORY;
// create the transaction object
mTransaction = new nsHttpTransaction();
if (!mTransaction)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mTransaction);
nsCOMPtr<nsIAsyncInputStream> responseStream;
rv = mTransaction->Init(mCaps, mConnectionInfo, &mRequestHead,
mUploadStream, mUploadStreamHasHeaders,
mEventQ, callbacks, this,
getter_AddRefs(responseStream));
if (NS_FAILED(rv)) return rv;
rv = nsInputStreamPump::Create(getter_AddRefs(mTransactionPump),
responseStream);
return rv;
}
void
nsHttpChannel::AddCookiesToRequest()
{
nsXPIDLCString cookie;
nsICookieService *cs = gHttpHandler->GetCookieService();
if (cs)
cs->GetCookieStringFromHttp(mURI,
mDocumentURI ? mDocumentURI : mOriginalURI,
this,
getter_Copies(cookie));
if (cookie.IsEmpty())
cookie = mUserSetCookieHeader;
else if (!mUserSetCookieHeader.IsEmpty())
cookie.Append(NS_LITERAL_CSTRING("; ") + mUserSetCookieHeader);
// overwrite any existing cookie headers. be sure to clear any
// existing cookies if we have no cookies to set or if the cookie
// service is unavailable.
mRequestHead.SetHeader(nsHttp::Cookie, cookie, PR_FALSE);
}
void
nsHttpChannel::ApplyContentConversions()
{
if (!mResponseHead)
return;
LOG(("nsHttpChannel::ApplyContentConversions [this=%x]\n", this));
if (!mApplyConversion) {
LOG(("not applying conversion per mApplyConversion\n"));
return;
}
const char *val = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
if (gHttpHandler->IsAcceptableEncoding(val)) {
nsCOMPtr<nsIStreamConverterService> serv;
nsresult rv = gHttpHandler->
GetStreamConverterService(getter_AddRefs(serv));
// we won't fail to load the page just because we couldn't load the
// stream converter service.. carry on..
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIStreamListener> converter;
nsCAutoString from(val);
ToLowerCase(from);
rv = serv->AsyncConvertData(from.get(),
"uncompressed",
mListener,
mListenerContext,
getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
LOG(("converter installed from \'%s\' to \'uncompressed\'\n", val));
mListener = converter;
}
}
}
}
static void
CallTypeSniffers(void *aClosure, const PRUint8 *aData, PRUint32 aCount)
{
nsIChannel *chan = NS_STATIC_CAST(nsIChannel*, aClosure);
const nsCOMArray<nsIContentSniffer_MOZILLA_1_8_BRANCH>& sniffers =
gIOService->GetContentSniffers();
PRUint32 length = sniffers.Count();
for (PRUint32 i = 0; i < length; ++i) {
nsCAutoString newType;
nsresult rv =
sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType);
if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) {
chan->SetContentType(newType);
break;
}
}
}
nsresult
nsHttpChannel::CallOnStartRequest()
{
if (mResponseHead && mResponseHead->ContentType().IsEmpty()) {
if (!mContentTypeHint.IsEmpty())
mResponseHead->SetContentType(mContentTypeHint);
else {
// Uh-oh. We had better find out what type we are!
// XXX This does not work with content-encodings... but
// neither does applying the conversion from the URILoader
nsCOMPtr<nsIStreamConverterService> serv;
nsresult rv = gHttpHandler->
GetStreamConverterService(getter_AddRefs(serv));
// If we failed, we just fall through to the "normal" case
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIStreamListener> converter;
rv = serv->AsyncConvertData(UNKNOWN_CONTENT_TYPE,
"*/*",
mListener,
mListenerContext,
getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
mListener = converter;
}
}
}
}
if (mResponseHead && mResponseHead->ContentCharset().IsEmpty())
mResponseHead->SetContentCharset(mContentCharsetHint);
if (mResponseHead)
SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH,
mResponseHead->ContentLength());
// Allow consumers to override our content type
if ((mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) &&
gIOService->GetContentSniffers().Count() != 0) {
// NOTE: We can have both a txn pump and a cache pump when the cache
// content is partial. In that case, we need to read from the cache,
// because that's the one that has the initial contents.
nsInputStreamPump* pump = mCachePump ? mCachePump : mTransactionPump;
pump->PeekStream(CallTypeSniffers, NS_STATIC_CAST(nsIChannel*, this));
}
LOG((" calling mListener->OnStartRequest\n"));
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
if (NS_FAILED(rv)) return rv;
// install stream converter if required
ApplyContentConversions();
return rv;
}
nsresult
nsHttpChannel::ProcessFailedSSLConnect(PRUint32 httpStatus)
{
// Failure to set up SSL proxy tunnel means one of the following:
// 1) Proxy wants authorization, or forbids.
// 2) DNS at proxy couldn't resolve target URL.
// 3) Proxy connection to target failed or timed out.
// 4) Eve noticed our proxy CONNECT, and is replying with malicious HTML.
//
// Our current architecture will parse response content with the
// permission of the target URL! Given #4, we must avoid rendering the
// body of the reply, and instead give the user a (hopefully helpful)
// boilerplate error page, based on just the HTTP status of the reply.
NS_ABORT_IF_FALSE(mConnectionInfo->UsingSSL(),
"SSL connect failed but not using SSL?");
nsresult rv;
switch (httpStatus)
{
case 300: case 301: case 302: case 303: case 307:
// Bad redirect: not top-level, or it's a POST, bad/missing Location,
// or ProcessRedirect() failed for some other reason. Legal
// redirects that fail because site not available, etc., are handled
// elsewhere, in the regular codepath.
rv = NS_ERROR_CONNECTION_REFUSED;
break;
case 403: // HTTP/1.1: "Forbidden"
case 407: // ProcessAuthentication() failed
case 501: // HTTP/1.1: "Not Implemented"
// user sees boilerplate Mozilla "Proxy Refused Connection" page.
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
break;
// Squid sends 404 if DNS fails (regular 404 from target is tunneled)
case 404: // HTTP/1.1: "Not Found"
// RFC 2616: "some deployed proxies are known to return 400 or 500 when
// DNS lookups time out." (Squid uses 500 if it runs out of sockets: so
// we have a conflict here).
case 400: // HTTP/1.1 "Bad Request"
case 500: // HTTP/1.1: "Internal Server Error"
/* User sees: "Address Not Found: Firefox can't find the server at
* www.foo.com."
*/
rv = NS_ERROR_UNKNOWN_HOST;
break;
case 502: // HTTP/1.1: "Bad Gateway" (invalid resp from target server)
// Squid returns 503 if target request fails for anything but DNS.
case 503: // HTTP/1.1: "Service Unavailable"
/* User sees: "Failed to Connect:
* Firefox can't establish a connection to the server at
* www.foo.com. Though the site seems valid, the browser
* was unable to establish a connection."
*/
rv = NS_ERROR_CONNECTION_REFUSED;
break;
// RFC 2616 uses 504 for both DNS and target timeout, so not clear what to
// do here: picking target timeout, as DNS covered by 400/404/500
case 504: // HTTP/1.1: "Gateway Timeout"
// user sees: "Network Timeout: The server at www.foo.com
// is taking too long to respond."
rv = NS_ERROR_NET_TIMEOUT;
break;
// Confused proxy server or malicious response
default:
rv = NS_ERROR_PROXY_CONNECTION_REFUSED;
break;
}
LOG(("Cancelling failed SSL proxy connection [this=%x httpStatus=%u]\n",
this, httpStatus));
Cancel(rv);
return rv;
}
PRBool
nsHttpChannel::ShouldSSLProxyResponseContinue(PRUint32 httpStatus)
{
// When SSL connect has failed, allow proxy reply to continue only if it's
// an auth request, or a redirect of a non-POST top-level document load.
switch (httpStatus) {
case 407:
return PR_TRUE;
case 300: case 301: case 302: case 303: case 307:
{
return ( (mLoadFlags & nsIChannel::LOAD_DOCUMENT_URI) &&
mURI == mDocumentURI &&
mRequestHead.Method() != nsHttp::Post);
}
}
return PR_FALSE;
}
nsresult
nsHttpChannel::ProcessResponse()
{
nsresult rv;
PRUint32 httpStatus = mResponseHead->Status();
LOG(("nsHttpChannel::ProcessResponse [this=%x httpStatus=%u]\n",
this, httpStatus));
if (mTransaction->SSLConnectFailed() &&
!ShouldSSLProxyResponseContinue(httpStatus))
return ProcessFailedSSLConnect(httpStatus);
// notify "http-on-examine-response" observers
gHttpHandler->OnExamineResponse(this);
// set cookies, if any exist; done after OnExamineResponse to allow those
// observers to modify the cookie response headers
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
// handle unused username and password in url (see bug 232567)
if (httpStatus != 401 && httpStatus != 407) {
CheckForSuperfluousAuth();
if (mCanceled)
return CallOnStartRequest();
if (mAuthContinuationState) {
// reset the current continuation state because our last
// authentication attempt has been completed successfully
NS_RELEASE(mAuthContinuationState);
LOG((" continuation state has been reset"));
}
}
// handle different server response categories. Note that we handle
// caching or not caching of error pages in
// nsHttpResponseHead::MustValidate; if you change this switch, update that
// one
switch (httpStatus) {
case 200:
case 203:
// Per RFC 2616, 14.35.2, "A server MAY ignore the Range header".
// So if a server does that and sends 200 instead of 206 that we
// expect, notify our caller.
if (mResuming) {
Cancel(NS_ERROR_NOT_RESUMABLE);
rv = CallOnStartRequest();
break;
}
// these can normally be cached
rv = ProcessNormal();
break;
case 206:
if (mCachedContentIsPartial) // an internal byte range request...
rv = ProcessPartialContent();
else
rv = ProcessNormal();
break;
case 300:
case 301:
case 302:
case 307:
case 303:
#if 0
case 305: // disabled as a security measure (see bug 187996).
#endif
// don't store the response body for redirects
rv = ProcessRedirection(httpStatus);
if (NS_SUCCEEDED(rv))
CloseCacheEntry(InitCacheEntry());
else {
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
if (mTransaction->SSLConnectFailed())
return ProcessFailedSSLConnect(httpStatus);
rv = ProcessNormal();
}
break;
case 304:
rv = ProcessNotModified();
if (NS_FAILED(rv)) {
LOG(("ProcessNotModified failed [rv=%x]\n", rv));
rv = ProcessNormal();
}
break;
case 401:
case 407:
rv = ProcessAuthentication(httpStatus);
if (NS_FAILED(rv)) {
LOG(("ProcessAuthentication failed [rv=%x]\n", rv));
if (mTransaction->SSLConnectFailed())
return ProcessFailedSSLConnect(httpStatus);
CheckForSuperfluousAuth();
rv = ProcessNormal();
}
break;
case 412: // Precondition failed
case 416: // Invalid range
if (mResuming) {
Cancel(NS_ERROR_ENTITY_CHANGED);
rv = CallOnStartRequest();
break;
}
// fall through
default:
rv = ProcessNormal();
break;
}
return rv;
}
nsresult
nsHttpChannel::ProcessNormal()
{
nsresult rv;
LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
// if we're here, then any byte-range requests failed to result in a partial
// response. we must clear this flag to prevent BufferPartialContent from
// being called inside our OnDataAvailable (see bug 136678).
mCachedContentIsPartial = PR_FALSE;
// For .gz files, apache sends both a Content-Type: application/x-gzip
// as well as Content-Encoding: gzip, which is completely wrong. In
// this case, we choose to ignore the rogue Content-Encoding header. We
// must do this early on so as to prevent it from being seen up stream.
// The same problem exists for Content-Encoding: compress in default
// Apache installs.
const char *encoding = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
if (encoding && PL_strcasestr(encoding, "gzip") && (
mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP) ||
mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP2) ||
mResponseHead->ContentType().EqualsLiteral(APPLICATION_GZIP3))) {
// clear the Content-Encoding header
mResponseHead->ClearHeader(nsHttp::Content_Encoding);
}
else if (encoding && PL_strcasestr(encoding, "compress") && (
mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS) ||
mResponseHead->ContentType().EqualsLiteral(APPLICATION_COMPRESS2))) {
// clear the Content-Encoding header
mResponseHead->ClearHeader(nsHttp::Content_Encoding);
}
// this must be called before firing OnStartRequest, since http clients,
// such as imagelib, expect our cache entry to already have the correct
// expiration time (bug 87710).
if (mCacheEntry) {
rv = InitCacheEntry();
if (NS_FAILED(rv))
CloseCacheEntry(NS_BINDING_ABORTED);
}
// Check that the server sent us what we were asking for
if (mResuming) {
// Create an entity id from the response
nsCAutoString id;
rv = GetEntityID(id);
if (NS_FAILED(rv)) {
// If creating an entity id is not possible -> error
Cancel(NS_ERROR_NOT_RESUMABLE);
}
// If we were passed an entity id, verify it's equal to the server's
else if (!mEntityID.IsEmpty()) {
if (!mEntityID.Equals(id))
Cancel(NS_ERROR_ENTITY_CHANGED);
}
}
rv = CallOnStartRequest();
if (NS_FAILED(rv)) return rv;
// install cache listener if we still have a cache entry open
if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE))
rv = InstallCacheListener();
return rv;
}
nsresult
nsHttpChannel::PromptTempRedirect()
{
nsresult rv;
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStringBundle> stringBundle;
rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(stringBundle));
if (NS_FAILED(rv)) return rv;
nsXPIDLString messageString;
rv = stringBundle->GetStringFromName(NS_LITERAL_STRING("RepostFormData").get(), getter_Copies(messageString));
// GetStringFromName can return NS_OK and NULL messageString.
if (NS_SUCCEEDED(rv) && messageString) {
PRBool repost = PR_FALSE;
nsCOMPtr<nsIPrompt> prompt;
GetCallback(prompt);
if (!prompt)
return NS_ERROR_NO_INTERFACE;
prompt->Confirm(nsnull, messageString, &repost);
if (!repost)
return NS_ERROR_FAILURE;
}
return rv;
}
nsresult
nsHttpChannel::ProxyFailover()
{
LOG(("nsHttpChannel::ProxyFailover [this=%x]\n", this));
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIProxyInfo> pi;
rv = pps->GetFailoverForProxy(mConnectionInfo->ProxyInfo(), mURI, mStatus,
getter_AddRefs(pi));
if (NS_FAILED(rv))
return rv;
return ReplaceWithProxy(pi);
}
nsresult
nsHttpChannel::ReplaceWithProxy(nsIProxyInfo *pi)
{
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
rv = gHttpHandler->NewProxiedChannel(mURI, pi, getter_AddRefs(newChannel));
if (NS_FAILED(rv))
return rv;
rv = SetupReplacementChannel(mURI, newChannel, PR_TRUE);
if (NS_FAILED(rv))
return rv;
// Inform consumers about this fake redirect
PRUint32 flags = nsIChannelEventSink::REDIRECT_INTERNAL;
rv = gHttpHandler->OnChannelRedirect(this, newChannel, flags);
if (NS_FAILED(rv))
return rv;
// open new channel
rv = newChannel->AsyncOpen(mListener, mListenerContext);
if (NS_FAILED(rv))
return rv;
mStatus = NS_BINDING_REDIRECTED;
mListener = nsnull;
mListenerContext = nsnull;
return rv;
}
nsresult
nsHttpChannel::ResolveProxy()
{
LOG(("nsHttpChannel::ResolveProxy [this=%x]\n", this));
nsresult rv;
nsCOMPtr<nsIProtocolProxyService> pps =
do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv))
return rv;
return pps->AsyncResolve(mURI, 0, this, getter_AddRefs(mProxyRequest));
}
PRBool
nsHttpChannel::ResponseWouldVary()
{
PRBool result = PR_FALSE;
nsCAutoString buf, metaKey;
mCachedResponseHead->GetHeader(nsHttp::Vary, buf);
if (!buf.IsEmpty()) {
NS_NAMED_LITERAL_CSTRING(prefix, "request-");
// enumerate the elements of the Vary header...
char *val = buf.BeginWriting(); // going to munge buf
char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
while (token) {
//
// if "*", then assume response would vary. technically speaking,
// "Vary: header, *" is not permitted, but we allow it anyways.
//
// if the response depends on the value of the "Cookie" header, then
// bail since we do not store cookies in the cache. this is done
// for the following reasons:
//
// 1- cookies can be very large in size
//
// 2- cookies may contain sensitive information. (for parity with
// out policy of not storing Set-cookie headers in the cache
// meta data, we likewise do not want to store cookie headers
// here.)
//
// this implementation is obviously not fully standards compliant, but
// it is perhaps most prudent given the above issues.
//
if ((*token == '*') || (PL_strcasecmp(token, "cookie") == 0)) {
result = PR_TRUE;
break;
}
else {
// build cache meta data key...
metaKey = prefix + nsDependentCString(token);
// check the last value of the given request header to see if it has
// since changed. if so, then indeed the cached response is invalid.
nsXPIDLCString lastVal;
mCacheEntry->GetMetaDataElement(metaKey.get(), getter_Copies(lastVal));
if (lastVal) {
nsHttpAtom atom = nsHttp::ResolveAtom(token);
const char *newVal = mRequestHead.PeekHeader(atom);
if (newVal && (strcmp(newVal, lastVal) != 0)) {
result = PR_TRUE; // yes, response would vary
break;
}
}
// next token...
token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
}
}
}
return result;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <byte-range>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::SetupByteRangeRequest(PRUint32 partialLen)
{
// cached content has been found to be partial, add necessary request
// headers to complete cache entry.
// use strongest validator available...
const char *val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
if (!val)
val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
if (!val) {
// if we hit this code it means mCachedResponseHead->IsResumable() is
// either broken or not being called.
NS_NOTREACHED("no cache validator");
return NS_ERROR_FAILURE;
}
char buf[32];
PR_snprintf(buf, sizeof(buf), "bytes=%u-", partialLen);
mRequestHead.SetHeader(nsHttp::Range, nsDependentCString(buf));
mRequestHead.SetHeader(nsHttp::If_Range, nsDependentCString(val));
return NS_OK;
}
nsresult
nsHttpChannel::ProcessPartialContent()
{
// ok, we've just received a 206
//
// we need to stream whatever data is in the cache out first, and then
// pick up whatever data is on the wire, writing it into the cache.
LOG(("nsHttpChannel::ProcessPartialContent [this=%x]\n", this));
NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
// Check if the content-encoding we now got is different from the one we
// got before
if (PL_strcasecmp(mResponseHead->PeekHeader(nsHttp::Content_Encoding),
mCachedResponseHead->PeekHeader(nsHttp::Content_Encoding))
!= 0) {
Cancel(NS_ERROR_UNEXPECTED); // XXX need better error code
return CallOnStartRequest();
}
// suspend the current transaction
nsresult rv = mTransactionPump->Suspend();
if (NS_FAILED(rv)) return rv;
// merge any new headers with the cached response headers
rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
if (NS_FAILED(rv)) return rv;
// update the cached response head
nsCAutoString head;
mCachedResponseHead->Flatten(head, PR_TRUE);
rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
// make the cached response be the current response
delete mResponseHead;
mResponseHead = mCachedResponseHead;
mCachedResponseHead = 0;
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
// notify observers interested in looking at a response that has been
// merged with any cached headers (http-on-examine-merged-response).
gHttpHandler->OnExamineMergedResponse(this);
// the cached content is valid, although incomplete.
mCachedContentIsValid = PR_TRUE;
return ReadFromCache();
}
nsresult
nsHttpChannel::OnDoneReadingPartialCacheEntry(PRBool *streamDone)
{
nsresult rv;
LOG(("nsHttpChannel::OnDoneReadingPartialCacheEntry [this=%x]", this));
// by default, assume we would have streamed all data or failed...
*streamDone = PR_TRUE;
// setup cache listener to append to cache entry
PRUint32 size;
rv = mCacheEntry->GetDataSize(&size);
if (NS_FAILED(rv)) return rv;
rv = InstallCacheListener(size);
if (NS_FAILED(rv)) return rv;
// need to track the logical offset of the data being sent to our listener
mLogicalOffset = size;
// we're now completing the cached content, so we can clear this flag.
// this puts us in the state of a regular download.
mCachedContentIsPartial = PR_FALSE;
// resume the transaction if it exists, otherwise the pipe contained the
// remaining part of the document and we've now streamed all of the data.
if (mTransactionPump) {
rv = mTransactionPump->Resume();
if (NS_SUCCEEDED(rv))
*streamDone = PR_FALSE;
}
else
NS_NOTREACHED("no transaction");
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <cache>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::ProcessNotModified()
{
nsresult rv;
LOG(("nsHttpChannel::ProcessNotModified [this=%x]\n", this));
NS_ENSURE_TRUE(mCachedResponseHead, NS_ERROR_NOT_INITIALIZED);
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_NOT_INITIALIZED);
// merge any new headers with the cached response headers
rv = mCachedResponseHead->UpdateHeaders(mResponseHead->Headers());
if (NS_FAILED(rv)) return rv;
// update the cached response head
nsCAutoString head;
mCachedResponseHead->Flatten(head, PR_TRUE);
rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
// make the cached response be the current response
delete mResponseHead;
mResponseHead = mCachedResponseHead;
mCachedResponseHead = 0;
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
// notify observers interested in looking at a reponse that has been
// merged with any cached headers
gHttpHandler->OnExamineMergedResponse(this);
mCachedContentIsValid = PR_TRUE;
rv = ReadFromCache();
if (NS_FAILED(rv)) return rv;
mTransactionReplaced = PR_TRUE;
return NS_OK;
}
nsresult
nsHttpChannel::OpenCacheEntry(PRBool offline, PRBool *delayed)
{
nsresult rv;
*delayed = PR_FALSE;
LOG(("nsHttpChannel::OpenCacheEntry [this=%x]", this));
// make sure we're not abusing this function
NS_PRECONDITION(!mCacheEntry, "cache entry already open");
nsCAutoString cacheKey;
if (mRequestHead.Method() == nsHttp::Post) {
// If the post id is already set then this is an attempt to replay
// a post transaction via the cache. Otherwise, we need a unique
// post id for this transaction.
if (mPostID == 0)
mPostID = gHttpHandler->GenerateUniqueID();
}
else if ((mRequestHead.Method() != nsHttp::Get) &&
(mRequestHead.Method() != nsHttp::Head)) {
// don't use the cache for other types of requests
return NS_OK;
}
if (mRequestHead.PeekHeader(nsHttp::Range)) {
// we don't support caching for byte range requests initiated
// by our clients or via nsIResumableChannel.
// XXX perhaps we could munge their byte range into the cache
// key to make caching sort'a work.
return NS_OK;
}
if (RequestIsConditional()) {
// don't use the cache if our consumer is making a conditional request
// (see bug 331825).
return NS_OK;
}
GenerateCacheKey(cacheKey);
// Get a cache session with appropriate storage policy
nsCacheStoragePolicy storagePolicy;
if (mLoadFlags & INHIBIT_PERSISTENT_CACHING)
storagePolicy = nsICache::STORE_IN_MEMORY;
else
storagePolicy = nsICache::STORE_ANYWHERE; // allow on disk
nsCOMPtr<nsICacheSession> session;
rv = gHttpHandler->GetCacheSession(storagePolicy,
getter_AddRefs(session));
if (NS_FAILED(rv)) return rv;
// Set the desired cache access mode accordingly...
nsCacheAccessMode accessRequested;
if (offline || (mLoadFlags & INHIBIT_CACHING)) {
// If we have been asked to bypass the cache and not write to the
// cache, then don't use the cache at all.
if (BYPASS_LOCAL_CACHE(mLoadFlags) && !offline)
return NS_ERROR_NOT_AVAILABLE;
accessRequested = nsICache::ACCESS_READ;
}
else if (BYPASS_LOCAL_CACHE(mLoadFlags))
accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
else
accessRequested = nsICache::ACCESS_READ_WRITE; // normal browsing
// we'll try to synchronously open the cache entry... however, it may be
// in use and not yet validated, in which case we'll try asynchronously
// opening the cache entry.
rv = session->OpenCacheEntry(cacheKey, accessRequested, PR_FALSE,
getter_AddRefs(mCacheEntry));
if (rv == NS_ERROR_CACHE_WAIT_FOR_VALIDATION) {
// access to the cache entry has been denied (because the cache entry
// is probably in use by another channel).
if (mLoadFlags & LOAD_BYPASS_LOCAL_CACHE_IF_BUSY) {
LOG(("bypassing local cache since it is busy\n"));
return NS_ERROR_NOT_AVAILABLE;
}
rv = session->AsyncOpenCacheEntry(cacheKey, accessRequested, this);
if (NS_FAILED(rv)) return rv;
// we'll have to wait for the cache entry
*delayed = PR_TRUE;
}
else if (NS_SUCCEEDED(rv)) {
mCacheEntry->GetAccessGranted(&mCacheAccess);
LOG(("got cache entry [access=%x]\n", mCacheAccess));
}
return rv;
}
nsresult
nsHttpChannel::GenerateCacheKey(nsACString &cacheKey)
{
if (mPostID) {
char buf[32];
PR_snprintf(buf, sizeof(buf), "id=%x&uri=", mPostID);
cacheKey.Assign(buf);
} else
cacheKey.Truncate();
// Strip any trailing #ref from the URL before using it as the key
const char *spec = mSpec.get();
const char *p = strchr(spec, '#');
if (p)
cacheKey.Append(spec, p - spec);
else
cacheKey.Append(spec);
return NS_OK;
}
// UpdateExpirationTime is called when a new response comes in from the server.
// It updates the stored response-time and sets the expiration time on the
// cache entry.
//
// From section 13.2.4 of RFC2616, we compute expiration time as follows:
//
// timeRemaining = freshnessLifetime - currentAge
// expirationTime = now + timeRemaining
//
nsresult
nsHttpChannel::UpdateExpirationTime()
{
NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
PRUint32 expirationTime = 0;
if (!mResponseHead->MustValidate()) {
PRUint32 freshnessLifetime = 0;
nsresult rv;
rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
if (NS_FAILED(rv)) return rv;
if (freshnessLifetime > 0) {
PRUint32 now = NowInSeconds(), currentAge = 0;
rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
if (NS_FAILED(rv)) return rv;
LOG(("freshnessLifetime = %u, currentAge = %u\n",
freshnessLifetime, currentAge));
if (freshnessLifetime > currentAge) {
PRUint32 timeRemaining = freshnessLifetime - currentAge;
// be careful... now + timeRemaining may overflow
if (now + timeRemaining < now)
expirationTime = PRUint32(-1);
else
expirationTime = now + timeRemaining;
}
else
expirationTime = now;
}
}
return mCacheEntry->SetExpirationTime(expirationTime);
}
// CheckCache is called from Connect after a cache entry has been opened for
// this URL but before going out to net. It's purpose is to set or clear the
// mCachedContentIsValid flag, and to configure an If-Modified-Since request
// if validation is required.
nsresult
nsHttpChannel::CheckCache()
{
nsresult rv = NS_OK;
LOG(("nsHTTPChannel::CheckCache [this=%x entry=%x]",
this, mCacheEntry.get()));
// Be pessimistic: assume the cache entry has no useful data.
mCachedContentIsValid = PR_FALSE;
// Don't proceed unless we have opened a cache entry for reading.
if (!mCacheEntry || !(mCacheAccess & nsICache::ACCESS_READ))
return NS_OK;
nsXPIDLCString buf;
// Get the method that was used to generate the cached response
rv = mCacheEntry->GetMetaDataElement("request-method", getter_Copies(buf));
if (NS_FAILED(rv)) return rv;
nsHttpAtom method = nsHttp::ResolveAtom(buf);
if (method == nsHttp::Head) {
// The cached response does not contain an entity. We can only reuse
// the response if the current request is also HEAD.
if (mRequestHead.Method() != nsHttp::Head)
return NS_OK;
}
buf.Adopt(0);
// We'll need this value in later computations...
PRUint32 lastModifiedTime;
rv = mCacheEntry->GetLastModified(&lastModifiedTime);
if (NS_FAILED(rv)) return rv;
// Determine if this is the first time that this cache entry
// has been accessed during this session.
PRBool fromPreviousSession =
(gHttpHandler->SessionStartTime() > lastModifiedTime);
// Get the cached HTTP response headers
rv = mCacheEntry->GetMetaDataElement("response-head", getter_Copies(buf));
if (NS_FAILED(rv)) return rv;
// Parse the cached HTTP response headers
NS_ASSERTION(!mCachedResponseHead, "memory leak detected");
mCachedResponseHead = new nsHttpResponseHead();
if (!mCachedResponseHead)
return NS_ERROR_OUT_OF_MEMORY;
rv = mCachedResponseHead->Parse((char *) buf.get());
if (NS_FAILED(rv)) return rv;
buf.Adopt(0);
// If we were only granted read access, then assume the entry is valid.
// unless it is INHBIT_CACHING
if (mCacheAccess == nsICache::ACCESS_READ &&
!(mLoadFlags & INHIBIT_CACHING)) {
mCachedContentIsValid = PR_TRUE;
return NS_OK;
}
PRUint16 isCachedRedirect = mCachedResponseHead->Status()/100 == 3;
if (method != nsHttp::Head && !isCachedRedirect) {
// If the cached content-length is set and it does not match the data
// size of the cached content, then the cached response is partial...
// either we need to issue a byte range request or we need to refetch
// the entire document.
nsInt64 contentLength = mCachedResponseHead->ContentLength();
if (contentLength != nsInt64(-1)) {
PRUint32 size;
rv = mCacheEntry->GetDataSize(&size);
if (NS_FAILED(rv)) return rv;
if (nsInt64(size) != contentLength) {
LOG(("Cached data size does not match the Content-Length header "
"[content-length=%lld size=%u]\n", PRInt64(contentLength), size));
if ((nsInt64(size) < contentLength) && mCachedResponseHead->IsResumable()) {
// looks like a partial entry.
rv = SetupByteRangeRequest(size);
if (NS_FAILED(rv)) return rv;
mCachedContentIsPartial = PR_TRUE;
}
return NS_OK;
}
}
}
PRBool doValidation = PR_FALSE;
// Be optimistic: assume that we won't need to do validation
mRequestHead.ClearHeader(nsHttp::If_Modified_Since);
mRequestHead.ClearHeader(nsHttp::If_None_Match);
// If the LOAD_FROM_CACHE flag is set, any cached data can simply be used.
if (mLoadFlags & LOAD_FROM_CACHE) {
LOG(("NOT validating based on LOAD_FROM_CACHE load flag\n"));
doValidation = PR_FALSE;
}
// If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
// it's revalidated with the server.
else if (mLoadFlags & VALIDATE_ALWAYS) {
LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
doValidation = PR_TRUE;
}
// Even if the VALIDATE_NEVER flag is set, there are still some cases in
// which we must validate the cached response with the server.
else if (mLoadFlags & VALIDATE_NEVER) {
LOG(("VALIDATE_NEVER set\n"));
// if no-store or if no-cache and ssl, validate cached response (see
// bug 112564 for an explanation of this logic)
if (mCachedResponseHead->NoStore() ||
(mCachedResponseHead->NoCache() && mConnectionInfo->UsingSSL())) {
LOG(("Validating based on (no-store || (no-cache && ssl)) logic\n"));
doValidation = PR_TRUE;
}
else {
LOG(("NOT validating based on VALIDATE_NEVER load flag\n"));
doValidation = PR_FALSE;
}
}
// check if validation is strictly required...
else if (mCachedResponseHead->MustValidate()) {
LOG(("Validating based on MustValidate() returning TRUE\n"));
doValidation = PR_TRUE;
}
else if (ResponseWouldVary()) {
LOG(("Validating based on Vary headers returning TRUE\n"));
doValidation = PR_TRUE;
}
// Check if the cache entry has expired...
else {
PRUint32 time = 0; // a temporary variable for storing time values...
rv = mCacheEntry->GetExpirationTime(&time);
if (NS_FAILED(rv)) return rv;
if (NowInSeconds() <= time)
doValidation = PR_FALSE;
else if (mCachedResponseHead->MustValidateIfExpired())
doValidation = PR_TRUE;
else if (mLoadFlags & VALIDATE_ONCE_PER_SESSION) {
// If the cached response does not include expiration infor-
// mation, then we must validate the response, despite whether
// or not this is the first access this session. This behavior
// is consistent with existing browsers and is generally expected
// by web authors.
rv = mCachedResponseHead->ComputeFreshnessLifetime(&time);
if (NS_FAILED(rv)) return rv;
if (time == 0)
doValidation = PR_TRUE;
else
doValidation = fromPreviousSession;
}
else
doValidation = PR_TRUE;
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
}
if (!doValidation) {
//
// Check the authorization headers used to generate the cache entry.
// We must validate the cache entry if:
//
// 1) the cache entry was generated prior to this session w/
// credentials (see bug 103402).
// 2) the cache entry was generated w/o credentials, but would now
// require credentials (see bug 96705).
//
// NOTE: this does not apply to proxy authentication.
//
mCacheEntry->GetMetaDataElement("auth", getter_Copies(buf));
doValidation =
(fromPreviousSession && !buf.IsEmpty()) ||
(buf.IsEmpty() && mRequestHead.PeekHeader(nsHttp::Authorization));
}
if (!doValidation) {
// Sites redirect back to the original URI after setting a session/tracking
// cookie. In such cases, force revalidation so that we hit the net and do not
// cycle thru cached responses.
if (isCachedRedirect && mRequestHead.PeekHeader(nsHttp::Cookie))
doValidation = PR_TRUE;
}
mCachedContentIsValid = !doValidation;
if (doValidation) {
//
// now, we are definitely going to issue a HTTP request to the server.
// make it conditional if possible.
//
// do not attempt to validate no-store content, since servers will not
// expect it to be cached. (we only keep it in our cache for the
// purposes of back/forward, etc.)
//
// the request method MUST be either GET or HEAD (see bug 175641).
//
if (!mCachedResponseHead->NoStore() &&
(mRequestHead.Method() == nsHttp::Get ||
mRequestHead.Method() == nsHttp::Head)) {
const char *val;
// Add If-Modified-Since header if a Last-Modified was given
val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
if (val)
mRequestHead.SetHeader(nsHttp::If_Modified_Since,
nsDependentCString(val));
// Add If-None-Match header if an ETag was given in the response
val = mCachedResponseHead->PeekHeader(nsHttp::ETag);
if (val)
mRequestHead.SetHeader(nsHttp::If_None_Match,
nsDependentCString(val));
}
}
LOG(("CheckCache [this=%x doValidation=%d]\n", this, doValidation));
return NS_OK;
}
// If the data in the cache hasn't expired, then there's no need to
// talk with the server, not even to do an if-modified-since. This
// method creates a stream from the cache, synthesizing all the various
// channel-related events.
nsresult
nsHttpChannel::ReadFromCache()
{
nsresult rv;
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_FAILURE);
NS_ENSURE_TRUE(mCachedContentIsValid, NS_ERROR_FAILURE);
LOG(("nsHttpChannel::ReadFromCache [this=%x] "
"Using cached copy of: %s\n", this, mSpec.get()));
if (mCachedResponseHead) {
NS_ASSERTION(!mResponseHead, "memory leak");
mResponseHead = mCachedResponseHead;
mCachedResponseHead = 0;
}
// if we don't already have security info, try to get it from the cache
// entry. there are two cases to consider here: 1) we are just reading
// from the cache, or 2) this may be due to a 304 not modified response,
// in which case we could have security info from a socket transport.
if (!mSecurityInfo)
mCacheEntry->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
if ((mCacheAccess & nsICache::ACCESS_WRITE) && !mCachedContentIsPartial) {
// We have write access to the cache, but we don't need to go to the
// server to validate at this time, so just mark the cache entry as
// valid in order to allow others access to this cache entry.
mCacheEntry->MarkValid();
}
// if this is a cached redirect, we must process the redirect asynchronously
// since AsyncOpen may not have returned yet. Make sure there is a Location
// header, otherwise we'll have to treat this like a normal 200 response.
if (mResponseHead && (mResponseHead->Status() / 100 == 3)
&& (mResponseHead->PeekHeader(nsHttp::Location)))
return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
// have we been configured to skip reading from the cache?
if ((mLoadFlags & LOAD_ONLY_IF_MODIFIED) && !mCachedContentIsPartial) {
LOG(("skipping read from cache based on LOAD_ONLY_IF_MODIFIED load flag\n"));
return AsyncCall(&nsHttpChannel::HandleAsyncNotModified);
}
// open input stream for reading...
nsCOMPtr<nsIInputStream> stream;
rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(stream));
if (NS_FAILED(rv)) return rv;
rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump),
stream, nsInt64(-1), nsInt64(-1), 0, 0,
PR_TRUE);
if (NS_FAILED(rv)) return rv;
return mCachePump->AsyncRead(this, mListenerContext);
}
nsresult
nsHttpChannel::CloseCacheEntry(nsresult status)
{
nsresult rv = NS_OK;
if (mCacheEntry) {
LOG(("nsHttpChannel::CloseCacheEntry [this=%x status=%x]", this, status));
// don't doom the cache entry if only reading from it...
if (NS_FAILED(status)
&& (mCacheAccess & nsICache::ACCESS_WRITE) && !mCachePump) {
LOG(("dooming cache entry!!"));
rv = mCacheEntry->Doom();
}
if (mCachedResponseHead) {
delete mCachedResponseHead;
mCachedResponseHead = 0;
}
mCachePump = 0;
mCacheEntry = 0;
mCacheAccess = 0;
mOpenedCacheForWriting = PR_FALSE;
}
return rv;
}
// Initialize the cache entry for writing.
// - finalize storage policy
// - store security info
// - update expiration time
// - store headers and other meta data
nsresult
nsHttpChannel::InitCacheEntry()
{
nsresult rv;
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
// if only reading, nothing to be done here.
if (mCacheAccess == nsICache::ACCESS_READ)
return NS_OK;
// Don't cache the response again if already cached...
if (mCachedContentIsValid)
return NS_OK;
LOG(("nsHttpChannel::InitCacheEntry [this=%x entry=%x]\n",
this, mCacheEntry.get()));
// The no-store directive within the 'Cache-Control:' header indicates
// that we must not store the response in a persistent cache.
if (mResponseHead->NoStore())
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
// For HTTPS transactions, the storage policy will already be IN_MEMORY.
// We are concerned instead about load attributes which may have changed.
if (mLoadFlags & INHIBIT_PERSISTENT_CACHING) {
rv = mCacheEntry->SetStoragePolicy(nsICache::STORE_IN_MEMORY);
if (NS_FAILED(rv)) return rv;
}
// Store secure data in memory only
if (mSecurityInfo)
mCacheEntry->SetSecurityInfo(mSecurityInfo);
// Set the expiration time for this cache entry
rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
// Store the HTTP request method with the cache entry so we can distinguish
// for example GET and HEAD responses.
rv = mCacheEntry->SetMetaDataElement("request-method", mRequestHead.Method().get());
if (NS_FAILED(rv)) return rv;
// Store the HTTP authorization scheme used if any...
rv = StoreAuthorizationMetaData();
if (NS_FAILED(rv)) return rv;
// Iterate over the headers listed in the Vary response header, and
// store the value of the corresponding request header so we can verify
// that it has not varied when we try to re-use the cached response at
// a later time. Take care not to store "Cookie" headers though. We
// take care of "Vary: cookie" in ResponseWouldVary.
//
// NOTE: if "Vary: accept, cookie", then we will store the "accept" header
// in the cache. we could try to avoid needlessly storing the "accept"
// header in this case, but it doesn't seem worth the extra code to perform
// the check.
{
nsCAutoString buf, metaKey;
mResponseHead->GetHeader(nsHttp::Vary, buf);
if (!buf.IsEmpty()) {
NS_NAMED_LITERAL_CSTRING(prefix, "request-");
char *val = buf.BeginWriting(); // going to munge buf
char *token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
while (token) {
if ((*token != '*') && (PL_strcasecmp(token, "cookie") != 0)) {
nsHttpAtom atom = nsHttp::ResolveAtom(token);
const char *requestVal = mRequestHead.PeekHeader(atom);
if (requestVal) {
// build cache meta data key and set meta data element...
metaKey = prefix + nsDependentCString(token);
mCacheEntry->SetMetaDataElement(metaKey.get(), requestVal);
}
}
token = nsCRT::strtok(val, NS_HTTP_HEADER_SEPS, &val);
}
}
}
// Store the received HTTP head with the cache entry as an element of
// the meta data.
nsCAutoString head;
mResponseHead->Flatten(head, PR_TRUE);
return mCacheEntry->SetMetaDataElement("response-head", head.get());
}
nsresult
nsHttpChannel::StoreAuthorizationMetaData()
{
// Not applicable to proxy authorization...
const char *val = mRequestHead.PeekHeader(nsHttp::Authorization);
if (val) {
// eg. [Basic realm="wally world"]
nsCAutoString buf(Substring(val, strchr(val, ' ')));
return mCacheEntry->SetMetaDataElement("auth", buf.get());
}
return NS_OK;
}
// Finalize the cache entry
// - may need to rewrite response headers if any headers changed
// - may need to recalculate the expiration time if any headers changed
// - called only for freshly written cache entries
nsresult
nsHttpChannel::FinalizeCacheEntry()
{
LOG(("nsHttpChannel::FinalizeCacheEntry [this=%x]\n", this));
if (mResponseHead && mResponseHeadersModified) {
// Set the expiration time for this cache entry
nsresult rv = UpdateExpirationTime();
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
// Open an output stream to the cache entry and insert a listener tee into
// the chain of response listeners.
nsresult
nsHttpChannel::InstallCacheListener(PRUint32 offset)
{
nsresult rv;
LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
NS_ASSERTION(mCacheEntry, "no cache entry");
NS_ASSERTION(mListener, "no listener");
nsCOMPtr<nsIOutputStream> out;
rv = mCacheEntry->OpenOutputStream(offset, getter_AddRefs(out));
if (NS_FAILED(rv)) return rv;
mOpenedCacheForWriting = PR_TRUE;
// XXX disk cache does not support overlapped i/o yet
#if 0
// Mark entry valid inorder to allow simultaneous reading...
rv = mCacheEntry->MarkValid();
if (NS_FAILED(rv)) return rv;
#endif
nsCOMPtr<nsIStreamListenerTee> tee =
do_CreateInstance(kStreamListenerTeeCID, &rv);
if (NS_FAILED(rv)) return rv;
rv = tee->Init(mListener, out);
if (NS_FAILED(rv)) return rv;
mListener = tee;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <redirect>
//-----------------------------------------------------------------------------
PR_STATIC_CALLBACK(PLDHashOperator)
CopyProperties(const nsAString& aKey, nsIVariant *aData, void *aClosure)
{
nsIWritablePropertyBag* bag = NS_STATIC_CAST(nsIWritablePropertyBag*,
aClosure);
bag->SetProperty(aKey, aData);
return PL_DHASH_NEXT;
}
nsresult
nsHttpChannel::SetupReplacementChannel(nsIURI *newURI,
nsIChannel *newChannel,
PRBool preserveMethod)
{
PRUint32 newLoadFlags = mLoadFlags | LOAD_REPLACE;
// if the original channel was using SSL and this channel is not using
// SSL, then no need to inhibit persistent caching. however, if the
// original channel was not using SSL and has INHIBIT_PERSISTENT_CACHING
// set, then allow the flag to apply to the redirected channel as well.
// since we force set INHIBIT_PERSISTENT_CACHING on all HTTPS channels,
// we only need to check if the original channel was using SSL.
if (mConnectionInfo->UsingSSL())
newLoadFlags &= ~INHIBIT_PERSISTENT_CACHING;
newChannel->SetOriginalURI(mOriginalURI);
newChannel->SetLoadGroup(mLoadGroup);
newChannel->SetNotificationCallbacks(mCallbacks);
newChannel->SetLoadFlags(newLoadFlags);
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
if (!httpChannel)
return NS_OK; // no other options to set
if (preserveMethod) {
nsCOMPtr<nsIUploadChannel> uploadChannel = do_QueryInterface(httpChannel);
if (mUploadStream && uploadChannel) {
// rewind upload stream
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
if (seekable)
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
// replicate original call to SetUploadStream...
if (mUploadStreamHasHeaders)
uploadChannel->SetUploadStream(mUploadStream, EmptyCString(), -1);
else {
const char *ctype = mRequestHead.PeekHeader(nsHttp::Content_Type);
const char *clen = mRequestHead.PeekHeader(nsHttp::Content_Length);
if (ctype && clen)
uploadChannel->SetUploadStream(mUploadStream,
nsDependentCString(ctype),
atoi(clen));
}
}
// must happen after setting upload stream since SetUploadStream
// may change the request method.
httpChannel->SetRequestMethod(nsDependentCString(mRequestHead.Method()));
}
// convey the referrer if one was used for this channel to the next one
if (mReferrer)
httpChannel->SetReferrer(mReferrer);
// convey the mAllowPipelining flag
httpChannel->SetAllowPipelining(mAllowPipelining);
// convey the new redirection limit
httpChannel->SetRedirectionLimit(mRedirectionLimit - 1);
nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(newChannel);
if (httpInternal) {
// update the DocumentURI indicator since we are being redirected.
// if this was a top-level document channel, then the new channel
// should have its mDocumentURI point to newURI; otherwise, we
// just need to pass along our mDocumentURI to the new channel.
if (newURI && (mURI == mDocumentURI))
httpInternal->SetDocumentURI(newURI);
else
httpInternal->SetDocumentURI(mDocumentURI);
}
// convey the mApplyConversion flag (bug 91862)
nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
if (encodedChannel)
encodedChannel->SetApplyConversion(mApplyConversion);
// transfer the resume information
if (mResuming) {
nsCOMPtr<nsIResumableChannel> resumableChannel(do_QueryInterface(newChannel));
if (!resumableChannel) {
NS_WARNING("Got asked to resume, but redirected to non-resumable channel!");
return NS_ERROR_NOT_RESUMABLE;
}
resumableChannel->ResumeAt(mStartPos, mEntityID);
}
// transfer any properties
nsCOMPtr<nsIWritablePropertyBag> bag(do_QueryInterface(newChannel));
if (bag)
mPropertyHash.EnumerateRead(CopyProperties, bag.get());
return NS_OK;
}
nsresult
nsHttpChannel::ProcessRedirection(PRUint32 redirectType)
{
LOG(("nsHttpChannel::ProcessRedirection [this=%x type=%u]\n",
this, redirectType));
const char *location = mResponseHead->PeekHeader(nsHttp::Location);
// if a location header was not given, then we can't perform the redirect,
// so just carry on as though this were a normal response.
if (!location)
return NS_ERROR_FAILURE;
// make sure non-ASCII characters in the location header are escaped.
nsCAutoString locationBuf;
if (NS_EscapeURL(location, -1, esc_OnlyNonASCII, locationBuf))
location = locationBuf.get();
if (mRedirectionLimit == 0) {
LOG(("redirection limit reached!\n"));
// this error code is fatal, and should be conveyed to our listener.
Cancel(NS_ERROR_REDIRECT_LOOP);
return NS_ERROR_REDIRECT_LOOP;
}
LOG(("redirecting to: %s [redirection-limit=%u]\n",
location, PRUint32(mRedirectionLimit)));
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
nsCOMPtr<nsIURI> newURI;
// create a new URI using the location header and the current URL
// as a base...
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) return rv;
// the new uri should inherit the origin charset of the current uri
nsCAutoString originCharset;
rv = mURI->GetOriginCharset(originCharset);
if (NS_FAILED(rv))
originCharset.Truncate();
rv = ioService->NewURI(nsDependentCString(location), originCharset.get(), mURI,
getter_AddRefs(newURI));
if (NS_FAILED(rv)) return rv;
// verify that this is a legal redirect
nsCOMPtr<nsIScriptSecurityManager> securityManager =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
if (securityManager) {
rv = securityManager->CheckLoadURI(mURI, newURI,
nsIScriptSecurityManager::DISALLOW_FROM_MAIL |
nsIScriptSecurityManager::DISALLOW_SCRIPT_OR_DATA);
if (NS_FAILED(rv)) return rv;
}
// Kill the current cache entry if we are redirecting
// back to ourself.
PRBool redirectingBackToSameURI = PR_FALSE;
if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
NS_SUCCEEDED(mURI->Equals(newURI, &redirectingBackToSameURI)) &&
redirectingBackToSameURI)
mCacheEntry->Doom();
// move the reference of the old location to the new one if the new
// one has none.
nsCOMPtr<nsIURL> newURL = do_QueryInterface(newURI, &rv);
if (NS_SUCCEEDED(rv)) {
nsCAutoString ref;
rv = newURL->GetRef(ref);
if (NS_SUCCEEDED(rv) && ref.IsEmpty()) {
nsCOMPtr<nsIURL> baseURL( do_QueryInterface(mURI, &rv) );
if (NS_SUCCEEDED(rv)) {
baseURL->GetRef(ref);
if (!ref.IsEmpty())
newURL->SetRef(ref);
}
}
}
// if we need to re-send POST data then be sure to ask the user first.
PRBool preserveMethod = (redirectType == 307);
if (preserveMethod && mUploadStream) {
rv = PromptTempRedirect();
if (NS_FAILED(rv)) return rv;
}
rv = ioService->NewChannelFromURI(newURI, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) return rv;
rv = SetupReplacementChannel(newURI, newChannel, preserveMethod);
if (NS_FAILED(rv)) return rv;
// call out to the event sink to notify it of this redirection.
PRUint32 redirectFlags;
if (redirectType == 301) // Moved Permanently
redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT;
else
redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY;
rv = gHttpHandler->OnChannelRedirect(this, newChannel, redirectFlags);
if (NS_FAILED(rv))
return rv;
// And now, the deprecated way
nsCOMPtr<nsIHttpEventSink> httpEventSink;
GetCallback(httpEventSink);
if (httpEventSink) {
// NOTE: nsIHttpEventSink is only used for compatibility with pre-1.8
// versions.
rv = httpEventSink->OnRedirect(this, newChannel);
if (NS_FAILED(rv)) return rv;
}
// XXX we used to talk directly with the script security manager, but that
// should really be handled by the event sink implementation.
// begin loading the new channel
rv = newChannel->AsyncOpen(mListener, mListenerContext);
if (NS_FAILED(rv)) return rv;
// close down this channel
Cancel(NS_BINDING_REDIRECTED);
// disconnect from our listener
mListener = 0;
mListenerContext = 0;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <auth>
//-----------------------------------------------------------------------------
// buf contains "domain\user"
static void
ParseUserDomain(PRUnichar *buf,
const PRUnichar **user,
const PRUnichar **domain)
{
PRUnichar *p = buf;
while (*p && *p != '\\') ++p;
if (!*p)
return;
*p = '\0';
*domain = buf;
*user = p + 1;
}
// helper function for setting identity from raw user:pass
static void
SetIdent(nsHttpAuthIdentity &ident,
PRUint32 authFlags,
PRUnichar *userBuf,
PRUnichar *passBuf)
{
const PRUnichar *user = userBuf;
const PRUnichar *domain = nsnull;
if (authFlags & nsIHttpAuthenticator::IDENTITY_INCLUDES_DOMAIN)
ParseUserDomain(userBuf, &user, &domain);
ident.Set(domain, user, passBuf);
}
// helper function for getting an auth prompt from an interface requestor
static void
GetAuthPrompt(nsIInterfaceRequestor *ifreq, PRBool proxyAuth,
nsIAuthPrompt **result)
{
if (!ifreq)
return;
PRUint32 promptReason;
if (proxyAuth)
promptReason = nsIAuthPromptProvider::PROMPT_PROXY;
else
promptReason = nsIAuthPromptProvider::PROMPT_NORMAL;
nsCOMPtr<nsIAuthPromptProvider> promptProvider = do_GetInterface(ifreq);
if (promptProvider)
promptProvider->GetAuthPrompt(promptReason, result);
else
CallGetInterface(ifreq, result);
}
// generate credentials for the given challenge, and update the auth cache.
nsresult
nsHttpChannel::GenCredsAndSetEntry(nsIHttpAuthenticator *auth,
PRBool proxyAuth,
const char *scheme,
const char *host,
PRInt32 port,
const char *directory,
const char *realm,
const char *challenge,
const nsHttpAuthIdentity &ident,
nsCOMPtr<nsISupports> &sessionState,
char **result)
{
nsresult rv;
PRUint32 authFlags;
rv = auth->GetAuthFlags(&authFlags);
if (NS_FAILED(rv)) return rv;
nsISupports *ss = sessionState;
// set informations that depend on whether
// we're authenticating against a proxy
// or a webserver
nsISupports **continuationState;
if (proxyAuth) {
continuationState = &mProxyAuthContinuationState;
} else {
continuationState = &mAuthContinuationState;
}
rv = auth->GenerateCredentials(this,
challenge,
proxyAuth,
ident.Domain(),
ident.User(),
ident.Password(),
&ss,
&*continuationState,
result);
sessionState.swap(ss);
if (NS_FAILED(rv)) return rv;
// don't log this in release build since it could contain sensitive info.
#ifdef DEBUG
LOG(("generated creds: %s\n", *result));
#endif
// find out if this authenticator allows reuse of credentials and/or
// challenge.
PRBool saveCreds =
authFlags & nsIHttpAuthenticator::REUSABLE_CREDENTIALS;
PRBool saveChallenge =
authFlags & nsIHttpAuthenticator::REUSABLE_CHALLENGE;
// this getter never fails
nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
// create a cache entry. we do this even though we don't yet know that
// these credentials are valid b/c we need to avoid prompting the user
// more than once in case the credentials are valid.
//
// if the credentials are not reusable, then we don't bother sticking
// them in the auth cache.
rv = authCache->SetAuthEntry(scheme, host, port, directory, realm,
saveCreds ? *result : nsnull,
saveChallenge ? challenge : nsnull,
ident, sessionState);
return rv;
}
nsresult
nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
{
LOG(("nsHttpChannel::ProcessAuthentication [this=%x code=%u]\n",
this, httpStatus));
const char *challenges;
PRBool proxyAuth = (httpStatus == 407);
nsresult rv = PrepareForAuthentication(proxyAuth);
if (NS_FAILED(rv))
return rv;
if (proxyAuth) {
// only allow a proxy challenge if we have a proxy server configured.
// otherwise, we could inadvertantly expose the user's proxy
// credentials to an origin server. We could attempt to proceed as
// if we had received a 401 from the server, but why risk flirting
// with trouble? IE similarly rejects 407s when a proxy server is
// not configured, so there's no reason not to do the same.
if (!mConnectionInfo->UsingHttpProxy()) {
LOG(("rejecting 407 when proxy server not configured!\n"));
return NS_ERROR_UNEXPECTED;
}
if (mConnectionInfo->UsingSSL() && !mTransaction->SSLConnectFailed()) {
// we need to verify that this challenge came from the proxy
// server itself, and not some server on the other side of the
// SSL tunnel.
LOG(("rejecting 407 from origin server!\n"));
return NS_ERROR_UNEXPECTED;
}
challenges = mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
}
else
challenges = mResponseHead->PeekHeader(nsHttp::WWW_Authenticate);
NS_ENSURE_TRUE(challenges, NS_ERROR_UNEXPECTED);
nsCAutoString creds;
rv = GetCredentials(challenges, proxyAuth, creds);
if (NS_FAILED(rv))
LOG(("unable to authenticate\n"));
else {
// set the authentication credentials
if (proxyAuth)
mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
else
mRequestHead.SetHeader(nsHttp::Authorization, creds);
mAuthRetryPending = PR_TRUE; // see DoAuthRetry
}
return rv;
}
nsresult
nsHttpChannel::PrepareForAuthentication(PRBool proxyAuth)
{
LOG(("nsHttpChannel::PrepareForAuthentication [this=%x]\n", this));
if (!proxyAuth) {
// reset the current proxy continuation state because our last
// authentication attempt was completed successfully.
NS_IF_RELEASE(mProxyAuthContinuationState);
LOG((" proxy continuation state has been reset"));
}
if (!mConnectionInfo->UsingHttpProxy() || mProxyAuthType.IsEmpty())
return NS_OK;
// We need to remove any Proxy_Authorization header left over from a
// non-request based authentication handshake (e.g., for NTLM auth).
nsCAutoString contractId;
contractId.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
contractId.Append(mProxyAuthType);
nsresult rv;
nsCOMPtr<nsIHttpAuthenticator> precedingAuth =
do_GetService(contractId.get(), &rv);
if (NS_FAILED(rv))
return rv;
PRUint32 precedingAuthFlags;
rv = precedingAuth->GetAuthFlags(&precedingAuthFlags);
if (NS_FAILED(rv))
return rv;
if (!(precedingAuthFlags & nsIHttpAuthenticator::REQUEST_BASED)) {
const char *challenges =
mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
if (!challenges) {
// delete the proxy authorization header because we weren't
// asked to authenticate
mRequestHead.ClearHeader(nsHttp::Proxy_Authorization);
LOG((" cleared proxy authorization header"));
}
}
return NS_OK;
}
nsresult
nsHttpChannel::GetCredentials(const char *challenges,
PRBool proxyAuth,
nsAFlatCString &creds)
{
nsCOMPtr<nsIHttpAuthenticator> auth;
nsCAutoString challenge;
nsCString authType; // force heap allocation to enable string sharing since
// we'll be assigning this value into mAuthType.
// set informations that depend on whether we're authenticating against a
// proxy or a webserver
nsISupports **currentContinuationState;
nsCString *currentAuthType;
if (proxyAuth) {
currentContinuationState = &mProxyAuthContinuationState;
currentAuthType = &mProxyAuthType;
} else {
currentContinuationState = &mAuthContinuationState;
currentAuthType = &mAuthType;
}
nsresult rv = NS_ERROR_NOT_AVAILABLE;
PRBool gotCreds = PR_FALSE;
// figure out which challenge we can handle and which authenticator to use.
for (const char *eol = challenges - 1; eol; ) {
const char *p = eol + 1;
// get the challenge string (LF separated -- see nsHttpHeaderArray)
if ((eol = strchr(p, '\n')) != nsnull)
challenge.Assign(p, eol - p);
else
challenge.Assign(p);
rv = GetAuthenticator(challenge.get(), authType, getter_AddRefs(auth));
if (NS_SUCCEEDED(rv)) {
//
// if we've already selected an auth type from a previous challenge
// received while processing this channel, then skip others until
// we find a challenge corresponding to the previously tried auth
// type.
//
if (!currentAuthType->IsEmpty() && authType != *currentAuthType)
continue;
//
// we allow the routines to run all the way through before we
// decide if they are valid.
//
// we don't worry about the auth cache being altered because that
// would have been the last step, and if the error is from updating
// the authcache it wasn't really altered anyway. -CTN
//
// at this point the code is really only useful for client side
// errors (it will not automatically fail over to do a different
// auth type if the server keeps rejecting what is being sent, even
// if a particular auth method only knows 1 thing, like a
// non-identity based authentication method)
//
rv = GetCredentialsForChallenge(challenge.get(), authType.get(),
proxyAuth, auth, creds);
if (NS_SUCCEEDED(rv)) {
gotCreds = PR_TRUE;
*currentAuthType = authType;
break;
}
// reset the auth type and continuation state
NS_IF_RELEASE(*currentContinuationState);
currentAuthType->Truncate();
}
}
if (!gotCreds && !currentAuthType->IsEmpty()) {
// looks like we never found the auth type we were looking for.
// reset the auth type and continuation state, and try again.
currentAuthType->Truncate();
NS_IF_RELEASE(*currentContinuationState);
rv = GetCredentials(challenges, proxyAuth, creds);
}
return rv;
}
nsresult
nsHttpChannel::GetCredentialsForChallenge(const char *challenge,
const char *authType,
PRBool proxyAuth,
nsIHttpAuthenticator *auth,
nsAFlatCString &creds)
{
LOG(("nsHttpChannel::GetCredentialsForChallenge [this=%x proxyAuth=%d challenges=%s]\n",
this, proxyAuth, challenge));
// this getter never fails
nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
PRUint32 authFlags;
nsresult rv = auth->GetAuthFlags(&authFlags);
if (NS_FAILED(rv)) return rv;
nsCAutoString realm;
ParseRealm(challenge, realm);
// if no realm, then use the auth type as the realm. ToUpperCase so the
// ficticious realm stands out a bit more.
// XXX this will cause some single signon misses!
// XXX this will cause problems when we expose the auth cache to OJI!
// XXX this was meant to be used with NTLM, which supplies no realm.
/*
if (realm.IsEmpty()) {
realm = authType;
ToUpperCase(realm);
}
*/
// set informations that depend on whether
// we're authenticating against a proxy
// or a webserver
const char *host;
PRInt32 port;
nsHttpAuthIdentity *ident;
nsCAutoString path, scheme;
PRBool identFromURI = PR_FALSE;
nsISupports **continuationState;
if (proxyAuth) {
NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!");
host = mConnectionInfo->ProxyHost();
port = mConnectionInfo->ProxyPort();
ident = &mProxyIdent;
scheme.AssignLiteral("http");
continuationState = &mProxyAuthContinuationState;
}
else {
host = mConnectionInfo->Host();
port = mConnectionInfo->Port();
ident = &mIdent;
rv = GetCurrentPath(path);
if (NS_FAILED(rv)) return rv;
rv = mURI->GetScheme(scheme);
if (NS_FAILED(rv)) return rv;
// if this is the first challenge, then try using the identity
// specified in the URL.
if (mIdent.IsEmpty()) {
GetIdentityFromURI(authFlags, mIdent);
identFromURI = !mIdent.IsEmpty();
}
continuationState = &mAuthContinuationState;
}
//
// if we already tried some credentials for this transaction, then
// we need to possibly clear them from the cache, unless the credentials
// in the cache have changed, in which case we'd want to give them a
// try instead.
//
nsHttpAuthEntry *entry = nsnull;
authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry);
// hold reference to the auth session state (in case we clear our
// reference to the entry).
nsCOMPtr<nsISupports> sessionStateGrip;
if (entry)
sessionStateGrip = entry->mMetaData;
// for digest auth, maybe our cached nonce value simply timed out...
PRBool identityInvalid;
nsISupports *sessionState = sessionStateGrip;
rv = auth->ChallengeReceived(this,
challenge,
proxyAuth,
&sessionState,
&*continuationState,
&identityInvalid);
sessionStateGrip.swap(sessionState);
if (NS_FAILED(rv)) return rv;
LOG((" identity invalid = %d\n", identityInvalid));
if (identityInvalid) {
if (entry) {
if (ident->Equals(entry->Identity())) {
LOG((" clearing bad auth cache entry\n"));
// ok, we've already tried this user identity, so clear the
// corresponding entry from the auth cache.
ClearPasswordManagerEntry(scheme.get(), host, port, realm.get(), entry->User());
authCache->ClearAuthEntry(scheme.get(), host, port, realm.get());
entry = nsnull;
ident->Clear();
}
else if (!identFromURI || nsCRT::strcmp(ident->User(), entry->Identity().User()) == 0) {
LOG((" taking identity from auth cache\n"));
// the password from the auth cache is more likely to be
// correct than the one in the URL. at least, we know that it
// works with the given username. it is possible for a server
// to distinguish logons based on the supplied password alone,
// but that would be quite unusual... and i don't think we need
// to worry about such unorthodox cases.
ident->Set(entry->Identity());
identFromURI = PR_FALSE;
if (entry->Creds()[0] != '\0') {
LOG((" using cached credentials!\n"));
creds.Assign(entry->Creds());
return entry->AddPath(path.get());
}
}
}
else if (!identFromURI) {
// hmm... identity invalid, but no auth entry! the realm probably
// changed (see bug 201986).
ident->Clear();
}
if (!entry && ident->IsEmpty()) {
// at this point we are forced to interact with the user to get
// their username and password for this domain.
rv = PromptForIdentity(scheme.get(), host, port, proxyAuth, realm.get(),
authType, authFlags, *ident);
if (NS_FAILED(rv)) return rv;
identFromURI = PR_FALSE;
}
}
if (identFromURI) {
// Warn the user before automatically using the identity from the URL
// to automatically log them into a site (see bug 232567).
if (!ConfirmAuth(NS_LITERAL_STRING("AutomaticAuth"), PR_FALSE)) {
// calling cancel here sets our mStatus and aborts the HTTP
// transaction, which prevents OnDataAvailable events.
Cancel(NS_ERROR_ABORT);
// this return code alone is not equivalent to Cancel, since
// it only instructs our caller that authentication failed.
// without an explicit call to Cancel, our caller would just
// load the page that accompanies the HTTP auth challenge.
return NS_ERROR_ABORT;
}
}
//
// get credentials for the given user:pass
//
// always store the credentials we're trying now so that they will be used
// on subsequent links. This will potentially remove good credentials from
// the cache. This is ok as we don't want to use cached credentials if the
// user specified something on the URI or in another manner. This is so
// that we don't transparently authenticate as someone they're not
// expecting to authenticate as.
//
nsXPIDLCString result;
rv = GenCredsAndSetEntry(auth, proxyAuth, scheme.get(), host, port, path.get(),
realm.get(), challenge, *ident, sessionStateGrip,
getter_Copies(result));
if (NS_SUCCEEDED(rv))
creds = result;
return rv;
}
nsresult
nsHttpChannel::GetAuthenticator(const char *challenge,
nsCString &authType,
nsIHttpAuthenticator **auth)
{
LOG(("nsHttpChannel::GetAuthenticator [this=%x]\n", this));
const char *p;
// get the challenge type
if ((p = strchr(challenge, ' ')) != nsnull)
authType.Assign(challenge, p - challenge);
else
authType.Assign(challenge);
// normalize to lowercase
ToLowerCase(authType);
nsCAutoString contractid;
contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
contractid.Append(authType);
return CallGetService(contractid.get(), auth);
}
void
nsHttpChannel::GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity &ident)
{
LOG(("nsHttpChannel::GetIdentityFromURI [this=%x]\n", this));
nsAutoString userBuf;
nsAutoString passBuf;
// XXX i18n
nsCAutoString buf;
mURI->GetUsername(buf);
if (!buf.IsEmpty()) {
NS_UnescapeURL(buf);
CopyASCIItoUCS2(buf, userBuf);
mURI->GetPassword(buf);
if (!buf.IsEmpty()) {
NS_UnescapeURL(buf);
CopyASCIItoUCS2(buf, passBuf);
}
}
if (!userBuf.IsEmpty())
SetIdent(ident, authFlags, (PRUnichar *) userBuf.get(), (PRUnichar *) passBuf.get());
}
void
nsHttpChannel::ParseRealm(const char *challenge, nsACString &realm)
{
//
// From RFC2617 section 1.2, the realm value is defined as such:
//
// realm = "realm" "=" realm-value
// realm-value = quoted-string
//
// but, we'll accept anything after the the "=" up to the first space, or
// end-of-line, if the string is not quoted.
//
const char *p = PL_strcasestr(challenge, "realm=");
if (p) {
p += 6;
if (*p == '"')
p++;
const char *end = PL_strchr(p, '"');
if (!end)
end = PL_strchr(p, ' ');
if (end)
realm.Assign(p, end - p);
else
realm.Assign(p);
}
}
nsresult
nsHttpChannel::PromptForIdentity(const char *scheme,
const char *host,
PRInt32 port,
PRBool proxyAuth,
const char *realm,
const char *authType,
PRUint32 authFlags,
nsHttpAuthIdentity &ident)
{
LOG(("nsHttpChannel::PromptForIdentity [this=%x]\n", this));
// XXX authType should be included in the prompt
// XXX i18n: IDN not supported.
nsCOMPtr<nsIAuthPrompt> authPrompt;
GetAuthPrompt(mCallbacks, proxyAuth, getter_AddRefs(authPrompt));
if (!authPrompt && mLoadGroup) {
nsCOMPtr<nsIInterfaceRequestor> cbs;
mLoadGroup->GetNotificationCallbacks(getter_AddRefs(cbs));
GetAuthPrompt(cbs, proxyAuth, getter_AddRefs(authPrompt));
}
if (!authPrompt)
return NS_ERROR_NO_INTERFACE;
// XXX i18n: need to support non-ASCII realm strings (see bug 41489)
NS_ConvertASCIItoUTF16 realmU(realm);
//
// construct the single signon key
//
// we always add the port to domain since it is used as the key for storing
// in password maanger. THE FORMAT OF THIS KEY IS SACROSANCT!! do not
// even think about changing the format of this key.
//
// XXX we need to prefix this with "scheme://" at some point. however, that
// has to be done very carefully and probably with some cooperation from the
// password manager to ensure that passwords remembered under the old key
// format are not lost.
//
nsAutoString key;
CopyASCIItoUTF16(host, key); // XXX IDN?
key.Append(PRUnichar(':'));
key.AppendInt(port);
key.AppendLiteral(" (");
key.Append(realmU);
key.Append(PRUnichar(')'));
nsresult rv;
// construct the message string
nsCOMPtr<nsIStringBundleService> bundleSvc =
do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIStringBundle> bundle;
rv = bundleSvc->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
if (NS_FAILED(rv)) return rv;
// figure out what message to display...
nsAutoString displayHost;
CopyASCIItoUTF16(host, displayHost); // XXX IDN?
// If not proxy auth then add port only if it was originally specified
// in the URI.
PRInt32 uriPort = -1;
if (proxyAuth || (NS_SUCCEEDED(mURI->GetPort(&uriPort)) && uriPort != -1)) {
displayHost.Append(PRUnichar(':'));
displayHost.AppendInt(port);
}
nsXPIDLString message;
{
NS_NAMED_LITERAL_STRING(proxyText, "EnterUserPasswordForProxy");
NS_NAMED_LITERAL_STRING(originText, "EnterUserPasswordForRealm");
const PRUnichar *text;
if (proxyAuth) {
text = proxyText.get();
} else {
text = originText.get();
// prepend "scheme://"
nsAutoString schemeU;
CopyASCIItoUTF16(scheme, schemeU);
schemeU.AppendLiteral("://");
displayHost.Insert(schemeU, 0);
}
const PRUnichar *strings[] = { realmU.get(), displayHost.get() };
rv = bundle->FormatStringFromName(text, strings, 2,
getter_Copies(message));
}
if (NS_FAILED(rv)) return rv;
// prompt the user...
PRBool retval = PR_FALSE;
PRUnichar *user = nsnull, *pass = nsnull;
rv = authPrompt->PromptUsernameAndPassword(nsnull, message.get(),
key.get(),
nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
&user, &pass, &retval);
if (NS_FAILED(rv)) return rv;
// remember that we successfully showed the user an auth dialog
if (!proxyAuth)
mSuppressDefensiveAuth = PR_TRUE;
if (!retval || !user || !pass)
rv = NS_ERROR_ABORT;
else
SetIdent(ident, authFlags, user, pass);
if (user) nsMemory::Free(user);
if (pass) nsMemory::Free(pass);
return rv;
}
PRBool
nsHttpChannel::ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt)
{
// skip prompting the user if
// 1) we've already prompted the user
// 2) we're not a toplevel channel
// 3) the userpass length is less than the "phishy" threshold
if (mSuppressDefensiveAuth || !(mLoadFlags & LOAD_INITIAL_DOCUMENT_URI))
return PR_TRUE;
nsresult rv;
nsCAutoString userPass;
rv = mURI->GetUserPass(userPass);
if (NS_FAILED(rv) || (userPass.Length() < gHttpHandler->PhishyUserPassLength()))
return PR_TRUE;
// we try to confirm by prompting the user. if we cannot do so, then
// assume the user said ok. this is done to keep things working in
// embedded builds, where the string bundle might not be present, etc.
nsCOMPtr<nsIStringBundleService> bundleService =
do_GetService(NS_STRINGBUNDLE_CONTRACTID);
if (!bundleService)
return PR_TRUE;
nsCOMPtr<nsIStringBundle> bundle;
bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
if (!bundle)
return PR_TRUE;
nsCAutoString host;
rv = mURI->GetHost(host);
if (NS_FAILED(rv))
return PR_TRUE;
nsCAutoString user;
rv = mURI->GetUsername(user);
if (NS_FAILED(rv))
return PR_TRUE;
NS_ConvertUTF8toUTF16 ucsHost(host), ucsUser(user);
const PRUnichar *strs[2] = { ucsHost.get(), ucsUser.get() };
nsXPIDLString msg;
bundle->FormatStringFromName(bundleKey.get(), strs, 2, getter_Copies(msg));
if (!msg)
return PR_TRUE;
nsCOMPtr<nsIPrompt> prompt;
GetCallback(prompt);
if (!prompt)
return PR_TRUE;
// do not prompt again
mSuppressDefensiveAuth = PR_TRUE;
PRBool confirmed;
if (doYesNoPrompt) {
PRInt32 choice;
rv = prompt->ConfirmEx(nsnull, msg,
nsIPrompt::BUTTON_POS_1_DEFAULT +
nsIPrompt::BUTTON_TITLE_YES * nsIPrompt::BUTTON_POS_0 +
nsIPrompt::BUTTON_TITLE_NO * nsIPrompt::BUTTON_POS_1,
nsnull, nsnull, nsnull, nsnull, nsnull, &choice);
if (NS_FAILED(rv))
return PR_TRUE;
confirmed = choice == 0;
}
else {
rv = prompt->Confirm(nsnull, msg, &confirmed);
if (NS_FAILED(rv))
return PR_TRUE;
}
return confirmed;
}
void
nsHttpChannel::CheckForSuperfluousAuth()
{
// we've been called because it has been determined that this channel is
// getting loaded without taking the userpass from the URL. if the URL
// contained a userpass, then (provided some other conditions are true),
// we'll give the user an opportunity to abort the channel as this might be
// an attempt to spoof a different site (see bug 232567).
if (!mAuthRetryPending) {
// ask user...
if (!ConfirmAuth(NS_LITERAL_STRING("SuperfluousAuth"), PR_TRUE)) {
// calling cancel here sets our mStatus and aborts the HTTP
// transaction, which prevents OnDataAvailable events.
Cancel(NS_ERROR_ABORT);
}
}
}
void
nsHttpChannel::SetAuthorizationHeader(nsHttpAuthCache *authCache,
nsHttpAtom header,
const char *scheme,
const char *host,
PRInt32 port,
const char *path,
nsHttpAuthIdentity &ident)
{
nsHttpAuthEntry *entry = nsnull;
nsresult rv;
// set informations that depend on whether
// we're authenticating against a proxy
// or a webserver
nsISupports **continuationState;
if (header == nsHttp::Proxy_Authorization) {
continuationState = &mProxyAuthContinuationState;
} else {
continuationState = &mAuthContinuationState;
}
rv = authCache->GetAuthEntryForPath(scheme, host, port, path, &entry);
if (NS_SUCCEEDED(rv)) {
// if we are trying to add a header for origin server auth and if the
// URL contains an explicit username, then try the given username first.
// we only want to do this, however, if we know the URL requires auth
// based on the presence of an auth cache entry for this URL (which is
// true since we are here). but, if the username from the URL matches
// the username from the cache, then we should prefer the password
// stored in the cache since that is most likely to be valid.
if (header == nsHttp::Authorization && entry->Domain()[0] == '\0') {
GetIdentityFromURI(0, ident);
// if the usernames match, then clear the ident so we will pick
// up the one from the auth cache instead.
if (nsCRT::strcmp(ident.User(), entry->User()) == 0)
ident.Clear();
}
PRBool identFromURI;
if (ident.IsEmpty()) {
ident.Set(entry->Identity());
identFromURI = PR_FALSE;
}
else
identFromURI = PR_TRUE;
nsXPIDLCString temp;
const char *creds = entry->Creds();
const char *challenge = entry->Challenge();
// we can only send a preemptive Authorization header if we have either
// stored credentials or a stored challenge from which to derive
// credentials. if the identity is from the URI, then we cannot use
// the stored credentials.
if ((!creds[0] || identFromURI) && challenge[0]) {
nsCOMPtr<nsIHttpAuthenticator> auth;
nsCAutoString unused;
rv = GetAuthenticator(challenge, unused, getter_AddRefs(auth));
if (NS_SUCCEEDED(rv)) {
PRBool proxyAuth = (header == nsHttp::Proxy_Authorization);
rv = GenCredsAndSetEntry(auth, proxyAuth, scheme, host, port, path,
entry->Realm(), challenge, ident,
entry->mMetaData, getter_Copies(temp));
if (NS_SUCCEEDED(rv))
creds = temp.get();
// make sure the continuation state is null since we do not
// support mixing preemptive and 'multirequest' authentication.
NS_IF_RELEASE(*continuationState);
}
}
if (creds[0]) {
LOG((" adding \"%s\" request header\n", header.get()));
mRequestHead.SetHeader(header, nsDependentCString(creds));
// suppress defensive auth prompting for this channel since we know
// that we already prompted at least once this session. we only do
// this for non-proxy auth since the URL's userpass is not used for
// proxy auth.
if (header == nsHttp::Authorization)
mSuppressDefensiveAuth = PR_TRUE;
}
else
ident.Clear(); // don't remember the identity
}
}
void
nsHttpChannel::AddAuthorizationHeaders()
{
LOG(("nsHttpChannel::AddAuthorizationHeaders? [this=%x]\n", this));
// this getter never fails
nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
// check if proxy credentials should be sent
const char *proxyHost = mConnectionInfo->ProxyHost();
if (proxyHost && mConnectionInfo->UsingHttpProxy())
SetAuthorizationHeader(authCache, nsHttp::Proxy_Authorization,
"http", proxyHost, mConnectionInfo->ProxyPort(),
nsnull, // proxy has no path
mProxyIdent);
// check if server credentials should be sent
nsCAutoString path, scheme;
if (NS_SUCCEEDED(GetCurrentPath(path)) &&
NS_SUCCEEDED(mURI->GetScheme(scheme))) {
SetAuthorizationHeader(authCache, nsHttp::Authorization,
scheme.get(),
mConnectionInfo->Host(),
mConnectionInfo->Port(),
path.get(),
mIdent);
}
}
nsresult
nsHttpChannel::GetCurrentPath(nsACString &path)
{
nsresult rv;
nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
if (url)
rv = url->GetDirectory(path);
else
rv = mURI->GetPath(path);
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ADDREF_INHERITED(nsHttpChannel, nsHashPropertyBag)
NS_IMPL_RELEASE_INHERITED(nsHttpChannel, nsHashPropertyBag)
NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannel)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsICachingChannel)
NS_INTERFACE_MAP_ENTRY(nsIUploadChannel)
NS_INTERFACE_MAP_ENTRY(nsICacheListener)
NS_INTERFACE_MAP_ENTRY(nsIEncodedChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
NS_INTERFACE_MAP_ENTRY(nsITransportEventSink)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY(nsIProtocolProxyCallback)
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetName(nsACString &aName)
{
aName = mSpec;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::IsPending(PRBool *value)
{
NS_ENSURE_ARG_POINTER(value);
*value = mIsPending;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetStatus(nsresult *aStatus)
{
NS_ENSURE_ARG_POINTER(aStatus);
*aStatus = mStatus;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::Cancel(nsresult status)
{
LOG(("nsHttpChannel::Cancel [this=%x status=%x]\n", this, status));
mCanceled = PR_TRUE;
mStatus = status;
if (mProxyRequest)
mProxyRequest->Cancel(status);
if (mTransaction)
gHttpHandler->CancelTransaction(mTransaction, status);
if (mTransactionPump)
mTransactionPump->Cancel(status);
if (mCachePump)
mCachePump->Cancel(status);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::Suspend()
{
LOG(("nsHttpChannel::Suspend [this=%x]\n", this));
if (mTransactionPump)
return mTransactionPump->Suspend();
if (mCachePump)
return mCachePump->Suspend();
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsHttpChannel::Resume()
{
LOG(("nsHttpChannel::Resume [this=%x]\n", this));
if (mTransactionPump)
return mTransactionPump->Resume();
if (mCachePump)
return mCachePump->Resume();
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsHttpChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
{
NS_ENSURE_ARG_POINTER(aLoadGroup);
*aLoadGroup = mLoadGroup;
NS_IF_ADDREF(*aLoadGroup);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
{
mLoadGroup = aLoadGroup;
mProgressSink = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
{
NS_ENSURE_ARG_POINTER(aLoadFlags);
*aLoadFlags = mLoadFlags;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
{
mLoadFlags = aLoadFlags;
// don't let anyone overwrite this bit if we're using a secure channel.
if (mConnectionInfo && mConnectionInfo->UsingSSL()
&& !gHttpHandler->IsPersistentHttpsCachingEnabled())
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetOriginalURI(nsIURI **originalURI)
{
NS_ENSURE_ARG_POINTER(originalURI);
*originalURI = mOriginalURI;
NS_IF_ADDREF(*originalURI);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetOriginalURI(nsIURI *originalURI)
{
mOriginalURI = originalURI;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetURI(nsIURI **URI)
{
NS_ENSURE_ARG_POINTER(URI);
*URI = mURI;
NS_IF_ADDREF(*URI);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetOwner(nsISupports **owner)
{
NS_ENSURE_ARG_POINTER(owner);
*owner = mOwner;
NS_IF_ADDREF(*owner);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetOwner(nsISupports *owner)
{
mOwner = owner;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetNotificationCallbacks(nsIInterfaceRequestor **callbacks)
{
NS_IF_ADDREF(*callbacks = mCallbacks);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *callbacks)
{
mCallbacks = callbacks;
mProgressSink = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetSecurityInfo(nsISupports **securityInfo)
{
NS_ENSURE_ARG_POINTER(securityInfo);
*securityInfo = mSecurityInfo;
NS_IF_ADDREF(*securityInfo);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetContentType(nsACString &value)
{
if (!mResponseHead) {
// We got no data, we got no headers, we got nothing
value.Truncate();
return NS_ERROR_NOT_AVAILABLE;
}
if (!mResponseHead->ContentType().IsEmpty()) {
value = mResponseHead->ContentType();
return NS_OK;
}
value.AssignLiteral(UNKNOWN_CONTENT_TYPE);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetContentType(const nsACString &value)
{
if (mListener) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsCAutoString contentTypeBuf, charsetBuf;
PRBool hadCharset;
net_ParseContentType(value, contentTypeBuf, charsetBuf, &hadCharset);
mResponseHead->SetContentType(contentTypeBuf);
// take care not to stomp on an existing charset
if (hadCharset)
mResponseHead->SetContentCharset(charsetBuf);
} else {
// We are being given a content-type hint.
PRBool dummy;
net_ParseContentType(value, mContentTypeHint, mContentCharsetHint,
&dummy);
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetContentCharset(nsACString &value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
value = mResponseHead->ContentCharset();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetContentCharset(const nsACString &value)
{
if (mListener) {
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
mResponseHead->SetContentCharset(value);
} else {
// Charset hint
mContentCharsetHint = value;
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetContentLength(PRInt32 *value)
{
NS_ENSURE_ARG_POINTER(value);
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
// XXX truncates to 32 bit
LL_L2I(*value, mResponseHead->ContentLength());
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetContentLength(PRInt32 value)
{
NS_NOTYETIMPLEMENTED("nsHttpChannel::SetContentLength");
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsHttpChannel::Open(nsIInputStream **_retval)
{
return NS_ImplementChannelOpen(this, _retval);
}
NS_IMETHODIMP
nsHttpChannel::AsyncOpen(nsIStreamListener *listener, nsISupports *context)
{
LOG(("nsHttpChannel::AsyncOpen [this=%x]\n", this));
NS_ENSURE_ARG_POINTER(listener);
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
nsresult rv;
// we want to grab a reference to the calling thread's event queue at
// this point. we will proxy all events back to the current thread via
// this event queue.
if (!mEventQ) {
rv = gHttpHandler->GetCurrentEventQ(getter_AddRefs(mEventQ));
if (NS_FAILED(rv)) return rv;
}
PRInt32 port;
rv = mURI->GetPort(&port);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) return rv;
rv = NS_CheckPortSafety(port, "http", ioService); // this works for https
if (NS_FAILED(rv))
return rv;
// Remember the cookie header that was set, if any
const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
if (cookieHeader)
mUserSetCookieHeader = cookieHeader;
// fetch cookies, and add them to the request header
AddCookiesToRequest();
// notify "http-on-modify-request" observers
gHttpHandler->OnModifyRequest(this);
// Adjust mCaps according to our request headers:
// - If "Connection: close" is set as a request header, then do not bother
// trying to establish a keep-alive connection.
const char *connHeader = mRequestHead.PeekHeader(nsHttp::Connection);
if (PL_strcasestr(connHeader, "close"))
mCaps &= ~(NS_HTTP_ALLOW_KEEPALIVE | NS_HTTP_ALLOW_PIPELINING);
mIsPending = PR_TRUE;
mListener = listener;
mListenerContext = context;
// add ourselves to the load group. from this point forward, we'll report
// all failures asynchronously.
if (mLoadGroup)
mLoadGroup->AddRequest(this, nsnull);
rv = Connect();
if (NS_FAILED(rv)) {
LOG(("Connect failed [rv=%x]\n", rv));
CloseCacheEntry(rv);
AsyncAbort(rv);
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIHttpChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetRequestMethod(nsACString &method)
{
method = mRequestHead.Method();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetRequestMethod(const nsACString &method)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
const nsCString &flatMethod = PromiseFlatCString(method);
// Method names are restricted to valid HTTP tokens.
if (!nsHttp::IsValidToken(flatMethod))
return NS_ERROR_INVALID_ARG;
nsHttpAtom atom = nsHttp::ResolveAtom(flatMethod.get());
if (!atom)
return NS_ERROR_FAILURE;
mRequestHead.SetMethod(atom);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetReferrer(nsIURI **referrer)
{
NS_ENSURE_ARG_POINTER(referrer);
*referrer = mReferrer;
NS_IF_ADDREF(*referrer);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetReferrer(nsIURI *referrer)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
// clear existing referrer, if any
mReferrer = nsnull;
mRequestHead.ClearHeader(nsHttp::Referer);
if (!referrer)
return NS_OK;
// check referrer blocking pref
PRUint32 referrerLevel;
if (mLoadFlags & LOAD_INITIAL_DOCUMENT_URI)
referrerLevel = 1; // user action
else
referrerLevel = 2; // inline content
if (gHttpHandler->ReferrerLevel() < referrerLevel)
return NS_OK;
nsCOMPtr<nsIURI> referrerGrip;
nsresult rv;
PRBool match;
//
// Strip off "wyciwyg://123/" from wyciwyg referrers.
//
// XXX this really belongs elsewhere since wyciwyg URLs aren't part of necko.
// perhaps some sort of generic nsINestedURI could be used. then, if an URI
// fails the whitelist test, then we could check for an inner URI and try
// that instead. though, that might be too automatic.
//
rv = referrer->SchemeIs("wyciwyg", &match);
if (NS_FAILED(rv)) return rv;
if (match) {
nsCAutoString path;
rv = referrer->GetPath(path);
if (NS_FAILED(rv)) return rv;
PRUint32 pathLength = path.Length();
if (pathLength <= 2) return NS_ERROR_FAILURE;
// Path is of the form "//123/http://foo/bar", with a variable number of digits.
// To figure out where the "real" URL starts, search path for a '/', starting at
// the third character.
PRInt32 slashIndex = path.FindChar('/', 2);
if (slashIndex == kNotFound) return NS_ERROR_FAILURE;
// Get the charset of the original URI so we can pass it to our fixed up URI.
nsCAutoString charset;
referrer->GetOriginCharset(charset);
// Replace |referrer| with a URI without wyciwyg://123/.
rv = NS_NewURI(getter_AddRefs(referrerGrip),
Substring(path, slashIndex + 1, pathLength - slashIndex - 1),
charset.get());
if (NS_FAILED(rv)) return rv;
referrer = referrerGrip.get();
}
//
// block referrer if not on our white list...
//
static const char *const referrerWhiteList[] = {
"http",
"https",
"ftp",
"gopher",
nsnull
};
match = PR_FALSE;
const char *const *scheme = referrerWhiteList;
for (; *scheme && !match; ++scheme) {
rv = referrer->SchemeIs(*scheme, &match);
if (NS_FAILED(rv)) return rv;
}
if (!match)
return NS_OK; // kick out....
//
// Handle secure referrals.
//
// Support referrals from a secure server if this is a secure site
// and (optionally) if the host names are the same.
//
rv = referrer->SchemeIs("https", &match);
if (NS_FAILED(rv)) return rv;
if (match) {
rv = mURI->SchemeIs("https", &match);
if (NS_FAILED(rv)) return rv;
if (!match)
return NS_OK;
if (!gHttpHandler->SendSecureXSiteReferrer()) {
nsCAutoString referrerHost;
nsCAutoString host;
rv = referrer->GetAsciiHost(referrerHost);
if (NS_FAILED(rv)) return rv;
rv = mURI->GetAsciiHost(host);
if (NS_FAILED(rv)) return rv;
// GetAsciiHost returns lowercase hostname.
if (!referrerHost.Equals(host))
return NS_OK;
}
}
nsCOMPtr<nsIURI> clone;
//
// we need to clone the referrer, so we can:
// (1) modify it
// (2) keep a reference to it after returning from this function
//
rv = referrer->Clone(getter_AddRefs(clone));
if (NS_FAILED(rv)) return rv;
// strip away any userpass; we don't want to be giving out passwords ;-)
clone->SetUserPass(EmptyCString());
// strip away any fragment per RFC 2616 section 14.36
nsCOMPtr<nsIURL> url = do_QueryInterface(clone);
if (url)
url->SetRef(EmptyCString());
nsCAutoString spec;
rv = clone->GetAsciiSpec(spec);
if (NS_FAILED(rv)) return rv;
// finally, remember the referrer URI and set the Referer header.
mReferrer = clone;
mRequestHead.SetHeader(nsHttp::Referer, spec);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetRequestHeader(const nsACString &header, nsACString &value)
{
// XXX might be better to search the header list directly instead of
// hitting the http atom hash table.
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom)
return NS_ERROR_NOT_AVAILABLE;
return mRequestHead.GetHeader(atom, value);
}
NS_IMETHODIMP
nsHttpChannel::SetRequestHeader(const nsACString &header,
const nsACString &value,
PRBool merge)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
const nsCString &flatHeader = PromiseFlatCString(header);
const nsCString &flatValue = PromiseFlatCString(value);
LOG(("nsHttpChannel::SetRequestHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
this, flatHeader.get(), flatValue.get(), merge));
// Header names are restricted to valid HTTP tokens.
if (!nsHttp::IsValidToken(flatHeader))
return NS_ERROR_INVALID_ARG;
// Header values MUST NOT contain line-breaks. RFC 2616 technically
// permits CTL characters, including CR and LF, in header values provided
// they are quoted. However, this can lead to problems if servers do not
// interpret quoted strings properly. Disallowing CR and LF here seems
// reasonable and keeps things simple. We also disallow a null byte.
if (flatValue.FindCharInSet("\r\n") != kNotFound ||
flatValue.Length() != strlen(flatValue.get()))
return NS_ERROR_INVALID_ARG;
nsHttpAtom atom = nsHttp::ResolveAtom(flatHeader.get());
if (!atom) {
NS_WARNING("failed to resolve atom");
return NS_ERROR_NOT_AVAILABLE;
}
return mRequestHead.SetHeader(atom, flatValue, merge);
}
NS_IMETHODIMP
nsHttpChannel::VisitRequestHeaders(nsIHttpHeaderVisitor *visitor)
{
return mRequestHead.Headers().VisitHeaders(visitor);
}
NS_IMETHODIMP
nsHttpChannel::GetUploadStream(nsIInputStream **stream)
{
NS_ENSURE_ARG_POINTER(stream);
*stream = mUploadStream;
NS_IF_ADDREF(*stream);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetUploadStream(nsIInputStream *stream, const nsACString &contentType, PRInt32 contentLength)
{
// NOTE: for backwards compatibility and for compatibility with old style
// plugins, |stream| may include headers, specifically Content-Type and
// Content-Length headers. in this case, |contentType| and |contentLength|
// would be unspecified. this is traditionally the case of a POST request,
// and so we select POST as the request method if contentType and
// contentLength are unspecified.
if (stream) {
if (!contentType.IsEmpty()) {
if (contentLength < 0) {
stream->Available((PRUint32 *) &contentLength);
if (contentLength < 0) {
NS_ERROR("unable to determine content length");
return NS_ERROR_FAILURE;
}
}
mRequestHead.SetHeader(nsHttp::Content_Length, nsPrintfCString("%d", contentLength));
mRequestHead.SetHeader(nsHttp::Content_Type, contentType);
mUploadStreamHasHeaders = PR_FALSE;
mRequestHead.SetMethod(nsHttp::Put); // PUT request
}
else {
mUploadStreamHasHeaders = PR_TRUE;
mRequestHead.SetMethod(nsHttp::Post); // POST request
}
}
else {
mUploadStreamHasHeaders = PR_FALSE;
mRequestHead.SetMethod(nsHttp::Get); // revert to GET request
}
mUploadStream = stream;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetResponseStatus(PRUint32 *value)
{
NS_ENSURE_ARG_POINTER(value);
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->Status();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetResponseStatusText(nsACString &value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
value = mResponseHead->StatusText();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetRequestSucceeded(PRBool *value)
{
NS_PRECONDITION(value, "Don't ever pass a null arg to this function");
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
PRUint32 status = mResponseHead->Status();
*value = (status / 100 == 2);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetResponseHeader(const nsACString &header, nsACString &value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom)
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->GetHeader(atom, value);
}
NS_IMETHODIMP
nsHttpChannel::SetResponseHeader(const nsACString &header,
const nsACString &value,
PRBool merge)
{
LOG(("nsHttpChannel::SetResponseHeader [this=%x header=\"%s\" value=\"%s\" merge=%u]\n",
this, PromiseFlatCString(header).get(), PromiseFlatCString(value).get(), merge));
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom)
return NS_ERROR_NOT_AVAILABLE;
// these response headers must not be changed
if (atom == nsHttp::Content_Type ||
atom == nsHttp::Content_Length ||
atom == nsHttp::Content_Encoding ||
atom == nsHttp::Trailer ||
atom == nsHttp::Transfer_Encoding)
return NS_ERROR_ILLEGAL_VALUE;
mResponseHeadersModified = PR_TRUE;
return mResponseHead->SetHeader(atom, value, merge);
}
NS_IMETHODIMP
nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->Headers().VisitHeaders(visitor);
}
NS_IMETHODIMP
nsHttpChannel::IsNoStoreResponse(PRBool *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->NoStore();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::IsNoCacheResponse(PRBool *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->NoCache();
if (!*value)
*value = mResponseHead->ExpiresInPast();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetApplyConversion(PRBool *value)
{
NS_ENSURE_ARG_POINTER(value);
*value = mApplyConversion;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetApplyConversion(PRBool value)
{
LOG(("nsHttpChannel::SetApplyConversion [this=%x value=%d]\n", this, value));
mApplyConversion = value;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetAllowPipelining(PRBool *value)
{
NS_ENSURE_ARG_POINTER(value);
*value = mAllowPipelining;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetAllowPipelining(PRBool value)
{
if (mIsPending)
return NS_ERROR_FAILURE;
mAllowPipelining = value;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetRedirectionLimit(PRUint32 *value)
{
NS_ENSURE_ARG_POINTER(value);
*value = PRUint32(mRedirectionLimit);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetRedirectionLimit(PRUint32 value)
{
mRedirectionLimit = PR_MIN(value, 0xff);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetContentEncodings(nsIUTF8StringEnumerator** aEncodings)
{
NS_PRECONDITION(aEncodings, "Null out param");
if (!mResponseHead) {
*aEncodings = nsnull;
return NS_OK;
}
const char *encoding = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
if (!encoding) {
*aEncodings = nsnull;
return NS_OK;
}
nsContentEncodings* enumerator = new nsContentEncodings(this, encoding);
if (!enumerator)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aEncodings = enumerator);
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIHttpChannelInternal
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetDocumentURI(nsIURI **aDocumentURI)
{
NS_ENSURE_ARG_POINTER(aDocumentURI);
*aDocumentURI = mDocumentURI;
NS_IF_ADDREF(*aDocumentURI);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetDocumentURI(nsIURI *aDocumentURI)
{
mDocumentURI = aDocumentURI;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetRequestVersion(PRUint32 *major, PRUint32 *minor)
{
int version = mRequestHead.Version();
if (major) { *major = version / 10; }
if (minor) { *minor = version % 10; }
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetResponseVersion(PRUint32 *major, PRUint32 *minor)
{
if (!mResponseHead)
{
*major = *minor = 0; // we should at least be kind about it
return NS_ERROR_NOT_AVAILABLE;
}
int version = mResponseHead->Version();
if (major) { *major = version / 10; }
if (minor) { *minor = version % 10; }
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetCookie(const char *aCookieHeader)
{
// empty header isn't an error
if (!(aCookieHeader && *aCookieHeader))
return NS_OK;
nsICookieService *cs = gHttpHandler->GetCookieService();
NS_ENSURE_TRUE(cs, NS_ERROR_FAILURE);
nsCOMPtr<nsIPrompt> prompt;
GetCallback(prompt);
return cs->SetCookieStringFromHttp(mURI,
mDocumentURI ? mDocumentURI : mOriginalURI,
prompt,
aCookieHeader,
mResponseHead->PeekHeader(nsHttp::Date),
this);
}
NS_IMETHODIMP
nsHttpChannel::GetProxyInfo(nsIProxyInfo **result)
{
if (!mConnectionInfo)
*result = nsnull;
else {
*result = mConnectionInfo->ProxyInfo();
NS_IF_ADDREF(*result);
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsISupportsPriority
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetPriority(PRInt32 *value)
{
*value = mPriority;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetPriority(PRInt32 value)
{
PRInt16 newValue = CLAMP(value, PR_INT16_MIN, PR_INT16_MAX);
if (mPriority == newValue)
return NS_OK;
mPriority = newValue;
if (mTransaction)
gHttpHandler->RescheduleTransaction(mTransaction, mPriority);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::AdjustPriority(PRInt32 delta)
{
return SetPriority(mPriority + delta);
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIProtocolProxyCallback
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnProxyAvailable(nsICancelable *request, nsIURI *uri,
nsIProxyInfo *pi, nsresult status)
{
mProxyRequest = nsnull;
// If status is a failure code, then it means that we failed to resolve
// proxy info. That is a non-fatal error assuming it wasn't because the
// request was canceled. We just failover to DIRECT when proxy resolution
// fails (failure can mean that the PAC URL could not be loaded).
// Need to replace this channel with a new one. It would be complex to try
// to change the value of mConnectionInfo since so much of our state may
// depend on its state.
if (!mCanceled) {
status = ReplaceWithProxy(pi);
// XXX(darin): It'd be nice if removing ourselves from the loadgroup
// could be factored into ReplaceWithProxy somehow.
if (mLoadGroup && NS_SUCCEEDED(status))
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
if (NS_FAILED(status))
AsyncAbort(status);
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequestObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
if (!(mCanceled || NS_FAILED(mStatus))) {
// capture the request's status, so our consumers will know ASAP of any
// connection failures, etc - bug 93581
request->GetStatus(&mStatus);
}
LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x status=%x]\n",
this, request, mStatus));
// Make sure things are what we expect them to be...
NS_ASSERTION(request == mCachePump || request == mTransactionPump,
"Unexpected request");
NS_ASSERTION(!(mTransactionPump && mCachePump) || mCachedContentIsPartial,
"If we have both pumps, the cache content must be partial");
// don't enter this block if we're reading from the cache...
if (NS_SUCCEEDED(mStatus) && !mCachePump && mTransaction) {
// grab the security info from the connection object; the transaction
// is guaranteed to own a reference to the connection.
mSecurityInfo = mTransaction->SecurityInfo();
NS_ASSERTION(mResponseHead == nsnull, "leaking mResponseHead");
// all of the response headers have been acquired, so we can take ownership
// of them from the transaction.
mResponseHead = mTransaction->TakeResponseHead();
// the response head may be null if the transaction was cancelled. in
// which case we just need to call OnStartRequest/OnStopRequest.
if (mResponseHead)
return ProcessResponse();
NS_WARNING("No response head in OnStartRequest");
}
// avoid crashing if mListener happens to be null...
if (!mListener) {
NS_NOTREACHED("mListener is null");
return NS_OK;
}
// on proxy errors, try to failover
if (mConnectionInfo->ProxyInfo() &&
(mStatus == NS_ERROR_PROXY_CONNECTION_REFUSED ||
mStatus == NS_ERROR_UNKNOWN_PROXY_HOST ||
mStatus == NS_ERROR_NET_TIMEOUT)) {
if (NS_SUCCEEDED(ProxyFailover()))
return NS_OK;
}
return CallOnStartRequest();
}
NS_IMETHODIMP
nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
{
LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
this, request, status));
// honor the cancelation status even if the underlying transaction completed.
if (mCanceled || NS_FAILED(mStatus))
status = mStatus;
if (mCachedContentIsPartial) {
if (NS_SUCCEEDED(status)) {
// mTransactionPump should be suspended
NS_ASSERTION(request != mTransactionPump,
"byte-range transaction finished prematurely");
if (request == mCachePump) {
PRBool streamDone;
status = OnDoneReadingPartialCacheEntry(&streamDone);
if (NS_SUCCEEDED(status) && !streamDone)
return status;
// otherwise, fall through and fire OnStopRequest...
}
else
NS_NOTREACHED("unexpected request");
}
// Do not to leave the transaction in a suspended state in error cases.
if (NS_FAILED(status) && mTransaction)
gHttpHandler->CancelTransaction(mTransaction, status);
}
PRBool isPartial = PR_FALSE;
if (mTransaction) {
// find out if the transaction ran to completion...
if (mCacheEntry)
isPartial = !mTransaction->ResponseIsComplete();
// determine if we should call DoAuthRetry
PRBool authRetry = mAuthRetryPending && NS_SUCCEEDED(status);
//
// grab reference to connection in case we need to retry an
// authentication request over it. this applies to connection based
// authentication schemes only. for request based schemes, conn is not
// needed, so it may be null.
//
// this code relies on the code in nsHttpTransaction::Close, which
// tests for NS_HTTP_STICKY_CONNECTION to determine whether or not to
// keep the connection around after the transaction is finished.
//
nsRefPtr<nsAHttpConnection> conn;
if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION))
conn = mTransaction->Connection();
// at this point, we're done with the transaction
NS_RELEASE(mTransaction);
mTransactionPump = 0;
// handle auth retry...
if (authRetry) {
mAuthRetryPending = PR_FALSE;
status = DoAuthRetry(conn);
if (NS_SUCCEEDED(status))
return NS_OK;
}
// if this transaction has been replaced, then bail.
if (mTransactionReplaced)
return NS_OK;
}
mIsPending = PR_FALSE;
mStatus = status;
// perform any final cache operations before we close the cache entry.
if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE))
FinalizeCacheEntry();
if (mListener) {
LOG((" calling OnStopRequest\n"));
mListener->OnStopRequest(this, mListenerContext, status);
mListener = 0;
mListenerContext = 0;
}
if (mCacheEntry) {
nsresult closeStatus = status;
// we don't want to discard the cache entry if we're only reading from
// the cache. If the cache entry was newly created, but we haven't
// started streaming data to it, then we don't want to keep it around
// if the load failed.
if ((!mOpenedCacheForWriting && mCacheAccess != nsICache::ACCESS_WRITE) ||
request == mCachePump)
closeStatus = NS_OK;
// we also don't want to discard the cache entry if the server supports
// byte range requests, because we could always complete the download
// at a later time.
else if (isPartial && mResponseHead && mResponseHead->IsResumable()) {
LOG(("keeping partial response that is resumable!\n"));
closeStatus = NS_OK;
}
CloseCacheEntry(closeStatus);
}
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, status);
mCallbacks = nsnull;
mProgressSink = nsnull;
mEventQ = nsnull;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIStreamListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
nsIInputStream *input,
PRUint32 offset, PRUint32 count)
{
LOG(("nsHttpChannel::OnDataAvailable [this=%x request=%x offset=%u count=%u]\n",
this, request, offset, count));
// don't send out OnDataAvailable notifications if we've been canceled.
if (mCanceled)
return mStatus;
NS_ASSERTION(mResponseHead, "No response head in ODA!!");
NS_ASSERTION(!(mCachedContentIsPartial && (request == mTransactionPump)),
"transaction pump not suspended");
if (mAuthRetryPending || (request == mTransactionPump && mTransactionReplaced)) {
PRUint32 n;
return input->ReadSegments(DiscardSegments, nsnull, count, &n);
}
if (mListener) {
//
// synthesize transport progress event. we do this here since we want
// to delay OnProgress events until we start streaming data. this is
// crucially important since it impacts the lock icon (see bug 240053).
//
nsresult transportStatus;
if (request == mCachePump)
transportStatus = nsITransport::STATUS_READING;
else
transportStatus = nsISocketTransport::STATUS_RECEIVING_FROM;
// mResponseHead may reference new or cached headers, but either way it
// holds our best estimate of the total content length. Even in the case
// of a byte range request, the content length stored in the cached
// response headers is what we want to use here.
nsUint64 progressMax(PRUint64(mResponseHead->ContentLength()));
nsUint64 progress = mLogicalOffset + nsUint64(count);
NS_ASSERTION(progress <= progressMax, "unexpected progress values");
OnTransportStatus(nsnull, transportStatus, progress, progressMax);
//
// we have to manually keep the logical offset of the stream up-to-date.
// we cannot depend soley on the offset provided, since we may have
// already streamed some data from another source (see, for example,
// OnDoneReadingPartialCacheEntry).
//
nsresult rv = mListener->OnDataAvailable(this,
mListenerContext,
input,
mLogicalOffset,
count);
if (NS_SUCCEEDED(rv))
mLogicalOffset = progress;
return rv;
}
return NS_ERROR_ABORT;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsITransportEventSink
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnTransportStatus(nsITransport *trans, nsresult status,
PRUint64 progress, PRUint64 progressMax)
{
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
// block socket status event after Cancel or OnStopRequest has been called.
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending && !(mLoadFlags & LOAD_BACKGROUND)) {
LOG(("sending status notification [this=%x status=%x progress=%llu/%llu]\n",
this, status, progress, progressMax));
nsCAutoString host;
mURI->GetHost(host);
mProgressSink->OnStatus(this, nsnull, status,
NS_ConvertUTF8toUTF16(host).get());
if (progress > 0)
mProgressSink->OnProgress(this, nsnull, progress, progressMax);
}
#ifdef DEBUG
else
LOG(("skipping status notification [this=%x sink=%x pending=%u background=%x]\n",
this, mProgressSink.get(), mIsPending, (mLoadFlags & LOAD_BACKGROUND)));
#endif
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsICachingChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetCacheToken(nsISupports **token)
{
NS_ENSURE_ARG_POINTER(token);
if (!mCacheEntry)
return NS_ERROR_NOT_AVAILABLE;
return CallQueryInterface(mCacheEntry, token);
}
NS_IMETHODIMP
nsHttpChannel::SetCacheToken(nsISupports *token)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsHttpChannel::GetCacheKey(nsISupports **key)
{
nsresult rv;
NS_ENSURE_ARG_POINTER(key);
LOG(("nsHttpChannel::GetCacheKey [this=%x]\n", this));
*key = nsnull;
nsCOMPtr<nsISupportsPRUint32> container =
do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
if (NS_FAILED(rv)) return rv;
rv = container->SetData(mPostID);
if (NS_FAILED(rv)) return rv;
return CallQueryInterface(container, key);
}
NS_IMETHODIMP
nsHttpChannel::SetCacheKey(nsISupports *key)
{
nsresult rv;
LOG(("nsHttpChannel::SetCacheKey [this=%x key=%x]\n", this, key));
// can only set the cache key if a load is not in progress
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
if (!key)
mPostID = 0;
else {
// extract the post id
nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(key, &rv);
if (NS_FAILED(rv)) return rv;
rv = container->GetData(&mPostID);
if (NS_FAILED(rv)) return rv;
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetCacheAsFile(PRBool *value)
{
NS_ENSURE_ARG_POINTER(value);
if (!mCacheEntry)
return NS_ERROR_NOT_AVAILABLE;
nsCacheStoragePolicy storagePolicy;
mCacheEntry->GetStoragePolicy(&storagePolicy);
*value = (storagePolicy == nsICache::STORE_ON_DISK_AS_FILE);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetCacheAsFile(PRBool value)
{
if (!mCacheEntry || mLoadFlags & INHIBIT_PERSISTENT_CACHING)
return NS_ERROR_NOT_AVAILABLE;
nsCacheStoragePolicy policy;
if (value)
policy = nsICache::STORE_ON_DISK_AS_FILE;
else
policy = nsICache::STORE_ANYWHERE;
return mCacheEntry->SetStoragePolicy(policy);
}
NS_IMETHODIMP
nsHttpChannel::GetCacheFile(nsIFile **cacheFile)
{
if (!mCacheEntry)
return NS_ERROR_NOT_AVAILABLE;
return mCacheEntry->GetFile(cacheFile);
}
NS_IMETHODIMP
nsHttpChannel::IsFromCache(PRBool *value)
{
if (!mIsPending)
return NS_ERROR_NOT_AVAILABLE;
// return false if reading a partial cache entry; the data isn't entirely
// from the cache!
*value = (mCachePump || (mLoadFlags & LOAD_ONLY_IF_MODIFIED)) &&
mCachedContentIsValid && !mCachedContentIsPartial;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIResumableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::ResumeAt(PRUint64 aStartPos,
const nsACString& aEntityID)
{
mEntityID = aEntityID;
mStartPos = aStartPos;
mResuming = PR_TRUE;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetEntityID(nsACString& aEntityID)
{
// Don't return an entity ID for HTTP/1.0 servers
if (mResponseHead && (mResponseHead->Version() < NS_HTTP_VERSION_1_1)) {
return NS_ERROR_NOT_RESUMABLE;
}
// Neither return one for Non-GET requests which require additional data
if (mRequestHead.Method() != nsHttp::Get) {
return NS_ERROR_NOT_RESUMABLE;
}
PRUint64 size = LL_MAXUINT;
nsCAutoString etag, lastmod;
if (mResponseHead) {
size = mResponseHead->TotalEntitySize();
const char* cLastMod = mResponseHead->PeekHeader(nsHttp::Last_Modified);
if (cLastMod)
lastmod = cLastMod;
const char* cEtag = mResponseHead->PeekHeader(nsHttp::ETag);
if (cEtag)
etag = cEtag;
}
nsCString entityID;
NS_EscapeURL(etag.BeginReading(), etag.Length(), esc_AlwaysCopy |
esc_FileBaseName | esc_Forced, entityID);
entityID.Append('/');
entityID.AppendInt(PRInt64(size));
entityID.Append('/');
entityID.Append(lastmod);
// NOTE: Appending lastmod as the last part avoids having to escape it
aEntityID = entityID;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsICacheListener
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
nsCacheAccessMode access,
nsresult status)
{
LOG(("nsHttpChannel::OnCacheEntryAvailable [this=%x entry=%x "
"access=%x status=%x]\n", this, entry, access, status));
// if the channel's already fired onStopRequest, then we should ignore
// this event.
if (!mIsPending)
return NS_OK;
// otherwise, we have to handle this event.
if (NS_SUCCEEDED(status)) {
mCacheEntry = entry;
mCacheAccess = access;
}
nsresult rv;
if (mCanceled && NS_FAILED(mStatus)) {
LOG(("channel was canceled [this=%x status=%x]\n", this, mStatus));
rv = mStatus;
}
else if ((mLoadFlags & LOAD_ONLY_FROM_CACHE) && NS_FAILED(status))
// if this channel is only allowed to pull from the cache, then
// we must fail if we were unable to open a cache entry.
rv = NS_ERROR_DOCUMENT_NOT_CACHED;
else
// advance to the next state...
rv = Connect(PR_FALSE);
// a failure from Connect means that we have to abort the channel.
if (NS_FAILED(rv)) {
CloseCacheEntry(rv);
AsyncAbort(rv);
}
return NS_OK;
}
void
nsHttpChannel::ClearPasswordManagerEntry(const char *scheme,
const char *host,
PRInt32 port,
const char *realm,
const PRUnichar *user)
{
// XXX scheme is currently unused. see comments in PromptForIdentity
nsresult rv;
nsCOMPtr<nsIPasswordManager> passWordManager = do_GetService(NS_PASSWORDMANAGER_CONTRACTID, &rv);
if (passWordManager) {
nsCAutoString domain;
domain.Assign(host);
domain.Append(':');
domain.AppendInt(port);
domain.AppendLiteral(" (");
domain.Append(realm);
domain.Append(')');
passWordManager->RemoveUser(domain, nsDependentString(user));
}
}
nsresult
nsHttpChannel::DoAuthRetry(nsAHttpConnection *conn)
{
LOG(("nsHttpChannel::DoAuthRetry [this=%x]\n", this));
NS_ASSERTION(!mTransaction, "should not have a transaction");
nsresult rv;
// toggle mIsPending to allow nsIObserver implementations to modify
// the request headers (bug 95044).
mIsPending = PR_FALSE;
// fetch cookies, and add them to the request header.
// the server response could have included cookies that must be sent with
// this authentication attempt (bug 84794).
AddCookiesToRequest();
// notify "http-on-modify-request" observers
gHttpHandler->OnModifyRequest(this);
mIsPending = PR_TRUE;
// get rid of the old response headers
delete mResponseHead;
mResponseHead = nsnull;
// set sticky connection flag and disable pipelining.
mCaps |= NS_HTTP_STICKY_CONNECTION;
mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
// and create a new one...
rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
// transfer ownership of connection to transaction
if (conn)
mTransaction->SetConnection(conn);
// rewind the upload stream
if (mUploadStream) {
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
if (seekable)
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
}
rv = gHttpHandler->InitiateTransaction(mTransaction, mPriority);
if (NS_FAILED(rv)) return rv;
return mTransactionPump->AsyncRead(this, nsnull);
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsContentEncodings <public>
//-----------------------------------------------------------------------------
nsHttpChannel::nsContentEncodings::nsContentEncodings(nsIHttpChannel* aChannel,
const char* aEncodingHeader) :
mEncodingHeader(aEncodingHeader), mChannel(aChannel), mReady(PR_FALSE)
{
mCurEnd = aEncodingHeader + strlen(aEncodingHeader);
mCurStart = mCurEnd;
}
nsHttpChannel::nsContentEncodings::~nsContentEncodings()
{
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsContentEncodings::nsISimpleEnumerator
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::nsContentEncodings::HasMore(PRBool* aMoreEncodings)
{
if (mReady) {
*aMoreEncodings = PR_TRUE;
return NS_OK;
}
nsresult rv = PrepareForNext();
*aMoreEncodings = NS_SUCCEEDED(rv);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::nsContentEncodings::GetNext(nsACString& aNextEncoding)
{
aNextEncoding.Truncate();
if (!mReady) {
nsresult rv = PrepareForNext();
if (NS_FAILED(rv)) {
return NS_ERROR_FAILURE;
}
}
const nsACString & encoding = Substring(mCurStart, mCurEnd);
nsACString::const_iterator start, end;
encoding.BeginReading(start);
encoding.EndReading(end);
PRBool haveType = PR_FALSE;
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("gzip"),
start,
end)) {
aNextEncoding.AssignLiteral(APPLICATION_GZIP);
haveType = PR_TRUE;
}
if (!haveType) {
encoding.BeginReading(start);
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("compress"),
start,
end)) {
aNextEncoding.AssignLiteral(APPLICATION_COMPRESS);
haveType = PR_TRUE;
}
}
if (! haveType) {
encoding.BeginReading(start);
if (CaseInsensitiveFindInReadable(NS_LITERAL_CSTRING("deflate"),
start,
end)) {
aNextEncoding.AssignLiteral(APPLICATION_ZIP);
haveType = PR_TRUE;
}
}
// Prepare to fetch the next encoding
mCurEnd = mCurStart;
mReady = PR_FALSE;
if (haveType)
return NS_OK;
NS_WARNING("Unknown encoding type");
return NS_ERROR_FAILURE;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsContentEncodings::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS1(nsHttpChannel::nsContentEncodings, nsIUTF8StringEnumerator)
//-----------------------------------------------------------------------------
// nsHttpChannel::nsContentEncodings <private>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::nsContentEncodings::PrepareForNext(void)
{
NS_PRECONDITION(mCurStart == mCurEnd, "Indeterminate state");
// At this point both mCurStart and mCurEnd point to somewhere
// past the end of the next thing we want to return
while (mCurEnd != mEncodingHeader) {
--mCurEnd;
if (*mCurEnd != ',' && !nsCRT::IsAsciiSpace(*mCurEnd))
break;
}
if (mCurEnd == mEncodingHeader)
return NS_ERROR_NOT_AVAILABLE; // no more encodings
++mCurEnd;
// At this point mCurEnd points to the first char _after_ the
// header we want. Furthermore, mCurEnd - 1 != mEncodingHeader
mCurStart = mCurEnd - 1;
while (mCurStart != mEncodingHeader &&
*mCurStart != ',' && !nsCRT::IsAsciiSpace(*mCurStart))
--mCurStart;
if (*mCurStart == ',' || nsCRT::IsAsciiSpace(*mCurStart))
++mCurStart; // we stopped because of a weird char, so move up one
// At this point mCurStart and mCurEnd bracket the encoding string
// we want. Check that it's not "identity"
if (Substring(mCurStart, mCurEnd).Equals("identity",
nsCaseInsensitiveCStringComparator())) {
mCurEnd = mCurStart;
return PrepareForNext();
}
mReady = PR_TRUE;
return NS_OK;
}