Mozilla/mozilla/netwerk/protocol/http/src/nsHttpChannel.cpp
darin%netscape.com b45f9d0bff Fixes bug 81125 "shift reload doesn't work" r=gagan sr=dougt
git-svn-id: svn://10.0.0.236/trunk@95741 18797224-902f-48f8-a5cc-f745e15eee43
2001-05-23 00:11:43 +00:00

2273 lines
70 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* 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 Netscape Communications are
* Copyright (C) 2001 by Netscape Communications. All
* Rights Reserved.
*
* Contributor(s):
* Darin Fisher <darin@netscape.com> (original author)
*/
#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 "nsIStringBundle.h"
#include "nsISupportsPrimitives.h"
#include "nsIFileStream.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsString2.h"
#include "nsReadableUtils.h"
#include "plstr.h"
#include "prprf.h"
static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
//-----------------------------------------------------------------------------
// nsHttpChannel <public>
//-----------------------------------------------------------------------------
nsHttpChannel::nsHttpChannel()
: mResponseHead(nsnull)
, mTransaction(nsnull)
, mPrevTransaction(nsnull)
, mConnectionInfo(nsnull)
, mLoadFlags(LOAD_NORMAL)
, mCapabilities(0)
, mStatus(NS_OK)
, mReferrerType(REFERRER_NONE)
, mCachedResponseHead(nsnull)
, mCacheAccess(0)
, mPostID(0)
, mRequestTime(0)
, mIsPending(PR_FALSE)
, mApplyConversion(PR_TRUE)
, mTriedCredentialsFromPrehost(PR_FALSE)
, mFromCacheOnly(PR_FALSE)
, mCachedContentIsValid(PR_FALSE)
{
LOG(("Creating nsHttpChannel @%x\n", this));
NS_INIT_ISUPPORTS();
// grab a reference to the handler to ensure that it doesn't go away.
nsHttpHandler *handler = nsHttpHandler::get();
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(mPrevTransaction);
// release our reference to the handler
nsHttpHandler *handler = nsHttpHandler::get();
NS_RELEASE(handler);
}
nsresult
nsHttpChannel::Init(nsIURI *uri,
PRUint32 caps,
const char *proxyHost,
PRInt32 proxyPort,
const char *proxyType)
{
nsresult rv;
LOG(("nsHttpChannel::Init [this=%x]\n"));
NS_PRECONDITION(uri, "null uri");
mURI = uri;
mOriginalURI = uri;
mCapabilities = caps;
rv = mURI->GetSpec(getter_Copies(mSpec));
if (NS_FAILED(rv)) return rv;
LOG(("uri=%s\n", mSpec.get()));
//
// Construct connection info object
//
nsXPIDLCString host;
PRInt32 port = -1;
PRBool usingSSL = PR_FALSE;
rv = mURI->SchemeIs("https", &usingSSL);
if (NS_FAILED(rv)) return rv;
rv = mURI->GetHost(getter_Copies(host));
if (NS_FAILED(rv)) return rv;
rv = mURI->GetPort(&port);
if (NS_FAILED(rv)) return rv;
LOG(("host=%s port=%d\n", host.get(), port));
mConnectionInfo = new nsHttpConnectionInfo(host, port,
proxyHost, proxyPort,
proxyType, 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)
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
// Set default request method
mRequestHead.SetMethod(nsHttp::Get);
//
// Set request headers
//
nsCString hostLine;
if (port == -1)
hostLine.Assign(host.get());
else if (PL_strchr(host.get(), ':')) {
hostLine.Assign('[');
hostLine.Append(host.get());
hostLine.Append(']');
} else {
hostLine.Assign(host.get());
hostLine.Append(':');
hostLine.AppendInt(port);
}
rv = mRequestHead.SetHeader(nsHttp::Host, hostLine);
if (NS_FAILED(rv)) return rv;
rv = nsHttpHandler::get()->AddStandardRequestHeaders(&mRequestHead.Headers(), caps);
if (NS_FAILED(rv)) return rv;
// check to see if authorization headers should be included
rv = AddAuthorizationHeaders();
if (NS_FAILED(rv)) return rv;
// Notify nsIHttpNotify implementations
rv = nsHttpHandler::get()->OnModifyRequest(this);
NS_ASSERTION(NS_SUCCEEDED(rv), "OnModifyRequest failed");
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <private>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::Connect(PRBool firstTime)
{
nsresult rv;
LOG(("nsHttpChannel::Connect [this=%x]\n", this));
// true when called from AsyncOpen
if (firstTime) {
PRBool delayed = PR_FALSE;
// open a cache entry for this channel...
rv = OpenCacheEntry(&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 (mFromCacheOnly)
return mPostID ? NS_ERROR_DOCUMENT_NOT_CACHED : rv;
// 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 (mFromCacheOnly) {
// The cache no longer contains the requested resource, and we
// are not allowed to refetch it, so there's nothing more to do.
// If this was a refetch of a POST transaction's resposne, then
// this failure indicates that the response is no longer cached.
return mPostID ? NS_ERROR_DOCUMENT_NOT_CACHED : NS_BINDING_FAILED;
}
}
// hit the net...
rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
return nsHttpHandler::get()->InitiateTransaction(mTransaction, mConnectionInfo);
}
// 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 an async proxy for the listener..
nsCOMPtr<nsIProxyObjectManager> mgr;
nsHttpHandler::get()->GetProxyObjectManager(getter_AddRefs(mgr));
if (mgr) {
nsCOMPtr<nsIRequestObserver> observer;
mgr->GetProxyForObject(NS_CURRENT_EVENTQ,
NS_GET_IID(nsIRequestObserver),
mListener,
PROXY_ASYNC | PROXY_ALWAYS,
getter_AddRefs(observer));
if (observer) {
observer->OnStartRequest(this, mListenerContext);
observer->OnStopRequest(this, mListenerContext, mStatus);
}
}
// XXX else, no proxy object manager... what do we do?
// finally remove ourselves from the load group.
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, status);
return NS_OK;
}
nsresult
nsHttpChannel::SetupTransaction()
{
NS_ENSURE_TRUE(!mTransaction, NS_ERROR_ALREADY_INITIALIZED);
nsCOMPtr<nsIStreamListener> listenerProxy;
nsresult rv = NS_NewStreamListenerProxy(getter_AddRefs(listenerProxy),
this, nsnull,
NS_HTTP_SEGMENT_SIZE,
NS_HTTP_BUFFER_SIZE);
if (NS_FAILED(rv)) return rv;
// create the transaction object
mTransaction = new nsHttpTransaction(listenerProxy, this);
if (!mTransaction)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(mTransaction);
// use the URI path if not proxying (transparent proxying such as SSL proxy
// does not count here).
nsXPIDLCString requestURI;
if ((mConnectionInfo->ProxyHost() == nsnull) || mConnectionInfo->UsingSSL()) {
rv = mURI->GetPath(getter_Copies(requestURI));
if (NS_FAILED(rv)) return rv;
}
else
requestURI = mSpec.get();
// trim off the #ref portion if any...
char *p = PL_strrchr(requestURI.get(), '#');
if (p) *p = 0;
mRequestHead.SetVersion(nsHttpHandler::get()->DefaultVersion());
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, "no-cache");
mRequestHead.SetHeader(nsHttp::Cache_Control, "max-age=0");
}
return mTransaction->SetupRequest(&mRequestHead, mUploadStream);
}
nsresult
nsHttpChannel::ApplyContentConversions()
{
LOG(("nsHttpChannel::ApplyContentConversions [this=%x]\n", this));
if (!mApplyConversion) {
LOG(("not applying conversion per mApplyConversion\n"));
return NS_OK;
}
const char *val = mResponseHead->PeekHeader(nsHttp::Content_Encoding);
if (val) {
nsCOMPtr<nsIStreamConverterService> serv;
nsresult rv = nsHttpHandler::get()->
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;
nsAutoString from = NS_ConvertASCIItoUCS2(val);
rv = serv->AsyncConvertData(from.get(),
NS_LITERAL_STRING("uncompressed").get(),
mListener,
mListenerContext,
getter_AddRefs(converter));
if (NS_SUCCEEDED(rv)) {
LOG(("converter installed from \'%s\' to \'uncompressed\'\n", val));
mListener = converter;
}
}
}
return NS_OK;
}
nsresult
nsHttpChannel::ProcessResponse()
{
nsresult rv = NS_OK;
PRUint32 httpStatus = mResponseHead->Status();
LOG(("nsHttpChannel::ProcessResponse [this=%x httpStatus=%u]\n",
this, httpStatus));
// handle different server response categories
switch (httpStatus) {
case 200:
case 203:
case 206:
rv = ProcessNormal();
break;
case 300:
case 301:
// XXX this is actually a cacheable response
CloseCacheEntry(NS_ERROR_ABORT);
rv = ProcessRedirection(httpStatus);
if (NS_FAILED(rv)) {
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
rv = ProcessNormal();
}
break;
case 302:
case 303:
case 305:
case 307:
CloseCacheEntry(NS_ERROR_ABORT);
rv = ProcessRedirection(httpStatus);
if (NS_FAILED(rv)) {
LOG(("ProcessRedirection failed [rv=%x]\n", rv));
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));
CloseCacheEntry(NS_ERROR_ABORT);
rv = ProcessNormal();
}
break;
default:
CloseCacheEntry(NS_ERROR_ABORT);
rv = ProcessNormal();
break;
}
return rv;
}
nsresult
nsHttpChannel::ProcessNormal()
{
nsresult rv;
LOG(("nsHttpChannel::ProcessNormal [this=%x]\n", this));
// install stream converter if required
ApplyContentConversions();
// install cache listener if we still have a cache entry open
if (mCacheEntry) {
rv = CacheReceivedResponse();
if (NS_FAILED(rv)) return rv;
}
// notify nsIHttpNotify implementations
rv = nsHttpHandler::get()->OnExamineResponse(this);
NS_ASSERTION(NS_SUCCEEDED(rv), "OnExamineResponse failed");
return mListener->OnStartRequest(this, mListenerContext);
}
//-----------------------------------------------------------------------------
// 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;
// drop our reference to the current transaction... ie. let it finish
// in the background, since we can most likely reuse the connection.
mPrevTransaction = mTransaction;
mTransaction = nsnull;
// notify nsIHttpNotify implementations as response headers may have changed
nsHttpHandler::get()->OnExamineResponse(this);
mCachedContentIsValid = PR_TRUE;
return ReadFromCache();
}
nsresult
nsHttpChannel::OpenCacheEntry(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 = nsHttpHandler::get()->GenerateUniqueID();
}
else if ((mRequestHead.Method() != nsHttp::Get) &&
(mRequestHead.Method() != nsHttp::Head)) {
// don't use the cache for other types of requests
return NS_OK;
}
else if (mRequestHead.PeekHeader(nsHttp::Range)) {
// we don't support caching for byte range requests
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 = nsHttpHandler::get()->GetCacheSession(storagePolicy,
getter_AddRefs(session));
if (NS_FAILED(rv)) return rv;
// Are we offline?
PRBool offline = PR_FALSE;
nsCOMPtr<nsIIOService> ioService = do_GetIOService();
if (ioService)
ioService->GetOffline(&offline);
// Set the desired cache access mode accordingly...
nsCacheAccessMode accessRequested;
if (offline) {
// Since we are offline, we can only read from the cache.
accessRequested = nsICache::ACCESS_READ;
mFromCacheOnly = PR_TRUE;
}
else if (mLoadFlags & LOAD_BYPASS_CACHE)
accessRequested = nsICache::ACCESS_WRITE; // replace cache entry
else if (mFromCacheOnly)
accessRequested = nsICache::ACCESS_READ; // read from cache
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
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 (rv == NS_OK) {
mCacheEntry->GetAccessGranted(&mCacheAccess);
LOG(("got cache entry [access=%x]\n", mCacheAccess));
}
return rv;
}
nsresult
nsHttpChannel::GenerateCacheKey(nsACString &cacheKey)
{
cacheKey.SetLength(0);
if (mPostID) {
char buf[32];
PR_snprintf(buf, sizeof(buf), "%x", mPostID);
cacheKey.Append("id=");
cacheKey.Append(buf);
cacheKey.Append("&uri=");
}
// Strip any trailing #ref from the URL before using it as the key
const char *p = PL_strchr(mSpec, '#');
if (p)
cacheKey.Append(mSpec, p - mSpec);
else
cacheKey.Append(mSpec);
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()
{
nsresult rv;
PRUint32 now = NowInSeconds();
PRUint32 freshnessLifetime, currentAge, timeRemaining = 0;
NS_ENSURE_TRUE(mResponseHead, NS_ERROR_FAILURE);
rv = mResponseHead->ComputeCurrentAge(now, mRequestTime, &currentAge);
if (NS_FAILED(rv)) return rv;
rv = mResponseHead->ComputeFreshnessLifetime(&freshnessLifetime);
if (NS_FAILED(rv)) return rv;
LOG(("freshnessLifetime = %u, currentAge = %u\n",
freshnessLifetime, currentAge));
if (freshnessLifetime > currentAge)
timeRemaining = freshnessLifetime - currentAge;
return mCacheEntry->SetExpirationTime(now + timeRemaining);
}
// 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 = 0;
// 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 = 0;
// If we were only granted read access, then assume the entry is valid.
if (mCacheAccess == nsICache::ACCESS_READ) {
mCachedContentIsValid = PR_TRUE;
return NS_OK;
}
// If the cached content-length is set and it does not match the data size
// of the cached content, then refetch.
PRInt32 contentLength = mCachedResponseHead->ContentLength();
if (contentLength != -1) {
PRUint32 size;
rv = mCacheEntry->GetDataSize(&size);
if (NS_FAILED(rv)) return rv;
if (size != (PRUint32) contentLength) {
LOG(("Cached data size does not match the Content-Length header "
"[content-length=%u size=%u]\n", contentLength, size));
// looks like a partial entry.
// XXX must re-fetch until we learn how to do byte range requests.
return NS_OK;
}
}
PRBool doValidation = PR_FALSE;
const char *val;
// Be optimistic: assume that we won't need to do validation
mRequestHead.SetHeader(nsHttp::If_Modified_Since, nsnull);
mRequestHead.SetHeader(nsHttp::If_None_Match, nsnull);
// 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;
goto end;
}
// If the VALIDATE_ALWAYS flag is set, any cached data won't be used until
// it's revalidated with the server.
if (mLoadFlags & VALIDATE_ALWAYS) {
LOG(("Validating based on VALIDATE_ALWAYS load flag\n"));
doValidation = PR_TRUE;
goto end;
}
// If the must-revalidate directive is present in the cached response, data
// must always be revalidated with the server, even if the user has
// configured validation to be turned off (See RFC 2616, section 14.9.4).
val = mCachedResponseHead->PeekHeader(nsHttp::Cache_Control);
if (val && PL_strcasestr(val, "must-revalidate")) {
LOG(("Validating based on \"%s\" header\n", val));
doValidation = PR_TRUE;
goto end;
}
// The no-cache directive within the 'Cache-Control:' header indicates
// that we must validate this cached response before reusing.
if (val && PL_strcasestr(val, "no-cache")) {
LOG(("Validating based on \"%s\" header\n", val));
doValidation = PR_TRUE;
goto end;
}
// XXX we are not quite handling no-cache correctly in this case. We really
// should check for field-names and only force validation if they match
// existing response headers. See RFC2616 section 14.9.1 for details.
// Although 'Pragma:no-cache' is not a standard HTTP response header (it's
// a request header), caching is inhibited when this header is present so
// as to match existing Navigator behavior.
val = mCachedResponseHead->PeekHeader(nsHttp::Pragma);
if (val && PL_strcasestr(val, "no-cache")) {
LOG(("Validating based on \"%s\" header\n", val));
doValidation = PR_TRUE;
goto end;
}
// Check the Vary header. Per comments on bug 37609, most of the request
// headers that we generate do not vary with the exception of Accept-Charset
// and Accept-Language, so we force validation only if these headers or "*"
// are listed with the Vary response header.
//
// XXX this may not be sufficient if embedders start tweaking or adding HTTP
// request headers.
//
// XXX will need to add the Accept header to this list if we start sending
// a full Accept header, since the addition of plugins could change this
// header (see bug 58040).
val = mCachedResponseHead->PeekHeader(nsHttp::Vary);
if (val && (PL_strstr(val, "*") ||
PL_strcasestr(val, "accept-charset") ||
PL_strcasestr(val, "accept-language"))) {
LOG(("Validating based on \"%s\" header\n", val));
doValidation = PR_TRUE;
goto end;
}
// delay checking this flag until we've verified that the above flags are
// not set.
if (mLoadFlags & VALIDATE_NEVER) {
LOG(("Not validating based on VALIDATE_NEVER flag\n"));
doValidation = PR_FALSE;
goto end;
}
// Check if the cache entry has expired...
{
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 (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 {
rv = mCacheEntry->GetLastModified(&time);
if (NS_FAILED(rv)) return rv;
// Determine if this is the first time that this cache entry
// has been accessed in this session, and validate if so.
doValidation = (nsHttpHandler::get()->SessionStartTime() > time);
}
}
else
doValidation = PR_TRUE;
LOG(("%salidating based on expiration time\n", doValidation ? "V" : "Not v"));
}
end:
mCachedContentIsValid = !doValidation;
if (doValidation) {
// Add If-Modified-Since header if a Last-Modified was given
val = mCachedResponseHead->PeekHeader(nsHttp::Last_Modified);
if (!val) {
LOG(("No Last-Lodified header sent, using the Date header instead...\n"));
val = mCachedResponseHead->PeekHeader(nsHttp::Date);
}
if (val)
mRequestHead.SetHeader(nsHttp::If_Modified_Since, 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, 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()
{
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;
}
// install stream converter if required
ApplyContentConversions();
// 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) {
// 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();
}
// Get a transport to the cached data...
nsresult rv = mCacheEntry->GetTransport(getter_AddRefs(mCacheTransport));
if (NS_FAILED(rv)) return rv;
// Pump the cache data downstream
return mCacheTransport->AsyncRead(this, mListenerContext,
0, PRUint32(-1), 0,
getter_AddRefs(mCacheReadRequest));
}
nsresult
nsHttpChannel::CloseCacheEntry(nsresult status)
{
nsresult rv = NS_OK;
if (mCacheEntry) {
LOG(("nsHttpChannel::CloseCacheEntry [this=%x status=%x]", this, status));
if (NS_FAILED(status) && (mCacheAccess & nsICache::ACCESS_WRITE)) {
LOG(("dooming cache entry!!"));
rv = mCacheEntry->Doom();
}
if (mCachedResponseHead) {
delete mCachedResponseHead;
mCachedResponseHead = 0;
}
mCacheReadRequest = 0;
mCacheTransport = 0;
mCacheEntry = 0;
mCacheAccess = 0;
}
return rv;
}
// Cache the network response from the server, including both the content and
// the HTTP headers.
nsresult
nsHttpChannel::CacheReceivedResponse()
{
const char *val;
nsresult rv;
NS_ENSURE_TRUE(mCacheEntry, NS_ERROR_UNEXPECTED);
NS_ENSURE_TRUE(mCacheAccess & nsICache::ACCESS_WRITE, NS_ERROR_UNEXPECTED);
// Don't cache the response again if already cached...
if (mCachedContentIsValid)
return NS_OK;
LOG(("nsHttpChannel::CacheReceivedResponse [this=%x entry=%x]\n",
this, mCacheEntry.get()));
// The no-store directive within the 'Cache-Control:' header indicates
// that we should not store the response in a persistent cache
val = mResponseHead->PeekHeader(nsHttp::Cache_Control);
if (val && PL_strcasestr(val, "no-store")) {
mLoadFlags |= INHIBIT_PERSISTENT_CACHING;
LOG(("Inhibiting persistent caching because of \"%s\"\n", val));
}
// Store secure data in memory only
if (mSecurityInfo)
mCacheEntry->SetSecurityInfo(mSecurityInfo);
// 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;
}
// 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 received HTTP head with the cache entry as an element of
// the meta data.
nsCAutoString head;
mResponseHead->Flatten(head, PR_TRUE);
rv = mCacheEntry->SetMetaDataElement("response-head", head.get());
if (NS_FAILED(rv)) return rv;
// Open an output stream to the cache entry and insert a listener tee into
// the chain of response listeners.
LOG(("Preparing to write data into the cache [uri=%s]\n", mSpec.get()));
rv = mCacheEntry->GetTransport(getter_AddRefs(mCacheTransport));
if (NS_FAILED(rv)) return rv;
nsCOMPtr<nsIOutputStream> out;
rv = mCacheTransport->OpenOutputStream(0, PRUint32(-1), 0, getter_AddRefs(out));
if (NS_FAILED(rv)) return rv;
// 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 = do_QueryInterface(tee, &rv);
return rv;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <redirect>
//-----------------------------------------------------------------------------
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;
// notify nsIHttpNotify implementations before this channel goes away
nsHttpHandler::get()->OnExamineResponse(this);
LOG(("redirecting to: %s\n", location));
nsresult rv;
nsCOMPtr<nsIChannel> newChannel;
if (redirectType == 305) {
// we must repeat the request via the proxy specified by location
PRInt32 proxyPort;
// location is of the form "host:port"
char *p = PL_strchr(location, ':');
if (p) {
*p = 0;
proxyPort = atoi(p+1);
}
else
proxyPort = 80;
// talk to the http handler directly for this case
rv = nsHttpHandler::get()->
NewProxyChannel(mURI, location, proxyPort, "http",
getter_AddRefs(newChannel));
if (NS_FAILED(rv)) return rv;
}
else {
//
// this redirect could be to ANY uri, so we need to talk to the
// IO service to create the new channel.
//
nsCOMPtr<nsIIOService> serv = do_GetIOService(&rv);
if (NS_FAILED(rv)) return rv;
// create a new URI using the location header and the current URL
// as a base...
nsCOMPtr<nsIURI> newURI;
rv = serv->NewURI(location, mURI, getter_AddRefs(newURI));
if (NS_FAILED(rv)) return rv;
// 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)) {
nsXPIDLCString ref;
rv = newURL->GetRef(getter_Copies(ref));
if (NS_SUCCEEDED(rv) && !ref) {
nsCOMPtr<nsIURL> baseURL = do_QueryInterface(mURI, &rv);
if (NS_SUCCEEDED(rv)) {
baseURL->GetRef(getter_Copies(ref));
if (ref)
newURL->SetRef(ref);
}
}
}
// build the new channel
rv = NS_OpenURI(getter_AddRefs(newChannel), newURI, serv, mLoadGroup,
mCallbacks, mLoadFlags | LOAD_REPLACE);
if (NS_FAILED(rv)) return rv;
}
// convey the original uri
rv = newChannel->SetOriginalURI(mOriginalURI);
if (NS_FAILED(rv)) return rv;
// convey the referrer if one was used for this channel to the next one
if (mReferrer) {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(newChannel);
if (httpChannel)
httpChannel->SetReferrer(mReferrer, mReferrerType);
}
// call out to the event sink to notify it of this redirection.
if (mHttpEventSink) {
rv = mHttpEventSink->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
mTransaction->Cancel(NS_BINDING_REDIRECTED);
// disconnect from our listener
mListener = 0;
mListenerContext = 0;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel <auth>
//-----------------------------------------------------------------------------
nsresult
nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
{
LOG(("nsHttpChannel::ProcessAuthentication [this=%x code=%u]\n",
this, httpStatus));
const char *challenge;
PRBool proxyAuth = (httpStatus == 407);
if (proxyAuth)
challenge = mResponseHead->PeekHeader(nsHttp::Proxy_Authenticate);
else
challenge = mResponseHead->PeekHeader(nsHttp::WWW_Authenticate);
if (!challenge) {
LOG(("null challenge!\n"));
return NS_ERROR_UNEXPECTED;
}
LOG(("challenge=%s\n", challenge));
nsCAutoString creds;
nsresult rv = GetCredentials(challenge, proxyAuth, creds);
if (NS_FAILED(rv)) return rv;
// set the authentication credentials
if (proxyAuth)
mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
else
mRequestHead.SetHeader(nsHttp::Authorization, creds);
// kill off the current transaction
mTransaction->Cancel(NS_BINDING_REDIRECTED);
mPrevTransaction = mTransaction;
mTransaction = nsnull;
// and create a new one...
rv = SetupTransaction();
if (NS_FAILED(rv)) return rv;
// rewind the upload stream
if (mUploadStream) {
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mUploadStream);
if (seekable)
seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
else {
// try nsIRandomAccessStore
nsCOMPtr<nsIRandomAccessStore> ras = do_QueryInterface(mUploadStream);
if (ras)
ras->Seek(PR_SEEK_SET, 0);
}
}
rv = nsHttpHandler::get()->InitiateTransaction(mTransaction, mConnectionInfo);
if (NS_FAILED(rv)) return rv;
return NS_OK;
}
nsresult
nsHttpChannel::GetCredentials(const char *challenges,
PRBool proxyAuth,
nsAFlatCString &creds)
{
nsAutoString user, pass;
nsresult rv;
LOG(("nsHttpChannel::GetCredentials [this=%x proxyAuth=%d challenges=%s]\n",
this, proxyAuth, challenges));
nsHttpAuthCache *authCache = nsHttpHandler::get()->AuthCache();
if (!authCache)
return NS_ERROR_NOT_INITIALIZED;
// proxy auth's never in prehost
if (!mTriedCredentialsFromPrehost && !proxyAuth) {
rv = GetUserPassFromURI(user, pass);
if (NS_FAILED(rv)) return rv;
mTriedCredentialsFromPrehost = PR_TRUE;
}
// figure out which challenge we can handle and which authenticator to use.
nsCAutoString challenge;
nsCOMPtr<nsIHttpAuthenticator> auth;
rv = SelectChallenge(challenges, challenge, getter_AddRefs(auth));
if (!auth) {
LOG(("authentication type not supported\n"));
return NS_ERROR_FAILURE;
}
nsCAutoString realm;
rv = ParseRealm(challenge.get(), realm);
if (NS_FAILED(rv)) return rv;
const char *triedCreds = nsnull;
const char *host;
nsXPIDLCString path;
PRInt32 port;
if (proxyAuth) {
host = mConnectionInfo->ProxyHost();
port = mConnectionInfo->ProxyPort();
triedCreds = mRequestHead.PeekHeader(nsHttp::Proxy_Authorization);
}
else {
host = mConnectionInfo->Host();
port = mConnectionInfo->Port();
triedCreds = mRequestHead.PeekHeader(nsHttp::Authorization);
rv = GetCurrentPath(getter_Copies(path));
if (NS_FAILED(rv)) return rv;
}
//
// 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.
//
authCache->GetCredentialsForDomain(host, port, realm.get(), creds);
if (triedCreds && !PL_strcmp(triedCreds, creds.get())) {
// ok.. clear the credentials from the cache
authCache->SetCredentials(host, port, nsnull, realm.get(), nsnull);
creds.Truncate(0);
}
// otherwise, let's try the credentials we got from the cache
if (!creds.IsEmpty()) {
LOG(("using cached credentials!\n"));
return NS_OK;
}
if (user.IsEmpty()) {
// at this point we are forced to interact with the user to get their
// username and password for this domain.
rv = PromptForUserPass(host, port, proxyAuth, realm.get(), user, pass);
if (NS_FAILED(rv)) return rv;
}
// talk to the authenticator to get credentials for this user/pass combo.
nsXPIDLCString result;
rv = auth->GenerateCredentials(this,
challenge.get(),
user.get(),
pass.get(),
getter_Copies(result));
if (NS_FAILED(rv)) return rv;
LOG(("generated creds: %s\n", result.get()));
creds.Assign(result);
// store these credentials in the cache. 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.
return authCache->SetCredentials(host, port, path, realm.get(), creds.get());
}
nsresult
nsHttpChannel::SelectChallenge(const char *challenges,
nsAFlatCString &challenge,
nsIHttpAuthenticator **auth)
{
nsCAutoString scheme;
LOG(("nsHttpChannel::SelectChallenge [this=%x]\n", this));
// loop over the various challenges (LF separated)...
for (const char *eol = challenges - 1; eol; ) {
const char *p = eol + 1;
// get the challenge string
if ((eol = PL_strchr(p, '\n')) != nsnull)
challenge.Assign(p, eol - p);
else
challenge.Assign(p);
// get the challenge type
if ((p = PL_strchr(challenge.get(), ' ')) != nsnull)
scheme.Assign(challenge.get(), p - challenge.get());
else
scheme.Assign(challenge);
// normalize to lowercase
ToLowerCase(scheme);
if (NS_SUCCEEDED(GetAuthenticator(scheme.get(), auth)))
return NS_OK;
}
return NS_ERROR_FAILURE;
}
nsresult
nsHttpChannel::GetAuthenticator(const char *scheme, nsIHttpAuthenticator **auth)
{
LOG(("nsHttpChannel::GetAuthenticator [this=%x scheme=%s]\n", this, scheme));
nsCAutoString contractid;
contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
contractid.Append(scheme);
nsresult rv;
nsCOMPtr<nsIHttpAuthenticator> serv = do_GetService(contractid, &rv);
if (NS_FAILED(rv)) return rv;
*auth = serv;
NS_ADDREF(*auth);
return NS_OK;
}
nsresult
nsHttpChannel::GetUserPassFromURI(nsAString &user,
nsAString &pass)
{
LOG(("nsHttpChannel::GetUserPassFromURI [this=%x]\n", this));
// XXX should be a necko utility function
nsXPIDLCString prehost;
mURI->GetPreHost(getter_Copies(prehost));
if (prehost) {
nsresult rv;
nsCOMPtr<nsIIOService> serv = do_GetIOService(&rv);
if (NS_FAILED(rv)) return rv;
nsXPIDLCString buf;
rv = serv->Unescape(prehost, getter_Copies(buf));
if (NS_FAILED(rv)) return rv;
char *p = PL_strchr(buf, ':');
if (p) {
// user:pass
*p = 0;
user = NS_ConvertASCIItoUCS2(buf);
pass = NS_ConvertASCIItoUCS2(p+1);
}
else {
// user
user = NS_ConvertASCIItoUCS2(buf);
}
}
return NS_OK;
}
nsresult
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);
}
return NS_OK;
}
nsresult
nsHttpChannel::PromptForUserPass(const char *host,
PRInt32 port,
PRBool proxyAuth,
const char *realm,
nsAString &user,
nsAString &pass)
{
LOG(("nsHttpChannel::PromptForUserPass [this=%x realm=%s]\n", this, realm));
nsresult rv;
nsCOMPtr<nsIAuthPrompt> authPrompt = do_GetInterface(mCallbacks, &rv);
if (NS_FAILED(rv)) {
NS_WARNING("notification callbacks should provide nsIAuthPrompt");
return rv;
}
// construct the domain string
nsCAutoString domain;
domain.Assign(host);
domain.Append(':');
domain.AppendInt(port);
nsAutoString hostU = NS_ConvertASCIItoUCS2(domain);
domain.Append(" (");
domain.Append(realm);
domain.Append(')');
// 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...
nsXPIDLString message;
if (proxyAuth) {
const PRUnichar *strings[] = { hostU.GetUnicode() };
rv = bundle->FormatStringFromName(
NS_LITERAL_STRING("EnterUserPasswordForProxy").get(),
strings, 1,
getter_Copies(message));
}
else {
nsAutoString realmU;
realmU.Assign(NS_LITERAL_STRING("\""));
realmU.AppendWithConversion(realm);
realmU.Append(NS_LITERAL_STRING("\""));
const PRUnichar *strings[] = { realmU.GetUnicode(), hostU.GetUnicode() };
rv = bundle->FormatStringFromName(
NS_LITERAL_STRING("EnterUserPasswordForRealm").get(),
strings, 2,
getter_Copies(message));
}
if (NS_FAILED(rv)) return rv;
// prompt the user...
nsXPIDLString userBuf, passBuf;
PRBool retval = PR_FALSE;
rv = authPrompt->PromptUsernameAndPassword(nsnull,
message.get(),
NS_ConvertASCIItoUCS2(domain).get(),
nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
getter_Copies(userBuf),
getter_Copies(passBuf),
&retval);
if (NS_FAILED(rv))
return rv;
if (!retval)
return NS_ERROR_ABORT;
user.Assign(userBuf);
pass.Assign(passBuf);
return NS_OK;
}
nsresult
nsHttpChannel::AddAuthorizationHeaders()
{
LOG(("nsHttpChannel::AddAuthorizationHeaders [this=%x]\n", this));
nsHttpAuthCache *authCache = nsHttpHandler::get()->AuthCache();
if (authCache) {
nsCAutoString creds;
nsCAutoString realm;
nsresult rv;
// check if proxy credentials should be sent
const char *proxyHost = mConnectionInfo->ProxyHost();
const char *proxyType = mConnectionInfo->ProxyType();
if (proxyHost && !PL_strcmp(proxyType, "http")) {
rv = authCache->GetCredentialsForPath(proxyHost,
mConnectionInfo->ProxyPort(),
nsnull, realm, creds);
if (NS_SUCCEEDED(rv)) {
LOG(("adding Proxy_Authorization [creds=%s]\n", creds.get()));
mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds.get());
}
}
// check if server credentials should be sent
nsXPIDLCString path;
rv = GetCurrentPath(getter_Copies(path));
if (NS_FAILED(rv)) return rv;
rv = authCache->GetCredentialsForPath(mConnectionInfo->Host(),
mConnectionInfo->Port(),
path.get(),
realm,
creds);
if (NS_SUCCEEDED(rv)) {
LOG(("adding Authorization [creds=%s]\n", creds.get()));
mRequestHead.SetHeader(nsHttp::Authorization, creds.get());
}
}
return NS_OK;
}
nsresult
nsHttpChannel::GetCurrentPath(char **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_THREADSAFE_ISUPPORTS9(nsHttpChannel,
nsIRequest,
nsIChannel,
nsIRequestObserver,
nsIStreamListener,
nsIHttpChannel,
nsIInterfaceRequestor,
nsIProgressEventSink,
nsICachingChannel,
nsICacheListener)
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetName(PRUnichar **aName)
{
NS_ENSURE_ARG_POINTER(aName);
*aName = ToNewUnicode(NS_ConvertASCIItoUCS2(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));
if (mTransaction)
mTransaction->Cancel(status);
else if (mCacheReadRequest)
mCacheReadRequest->Cancel(status);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::Suspend()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsHttpChannel::Resume()
{
return NS_ERROR_NOT_IMPLEMENTED;
}
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;
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())
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_ENSURE_ARG_POINTER(callbacks);
*callbacks = mCallbacks;
NS_IF_ADDREF(*callbacks);
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetNotificationCallbacks(nsIInterfaceRequestor *callbacks)
{
mCallbacks = callbacks;
mHttpEventSink = do_GetInterface(mCallbacks);
mProgressSink = do_GetInterface(mCallbacks);
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(char **value)
{
nsresult rv;
NS_ENSURE_ARG_POINTER(value);
*value = nsnull;
if (mResponseHead && mResponseHead->ContentType())
return DupString(mResponseHead->ContentType(), value);
// else if the there isn't a response yet or if the response does not
// contain a content-type header, try to determine the content type
// from the file extension of the URI...
// We had to do this same hack in 4.x. Sometimes, we run an http url that
// ends in special extensions like .dll, .exe, etc and the server doesn't
// provide a specific content type for the document. In actuality the
// document is really text/html (sometimes). For these cases, we don't want
// to ask the mime service for the content type because it will make
// incorrect conclusions based on the file extension. Instead, set the
// content type to unknown and allow our unknown content type decoder a
// chance to sniff the data stream and conclude a content type.
PRBool doMimeLookup = PR_TRUE;
nsCOMPtr<nsIURL> url = do_QueryInterface(mURI);
if (url) {
nsXPIDLCString ext;
url->GetFileExtension(getter_Copies(ext));
if (ext && (!PL_strcasecmp(ext, "dll") || !PL_strcasecmp(ext, "exe")))
doMimeLookup = PR_FALSE;
}
if (doMimeLookup) {
nsCOMPtr<nsIMIMEService> mime;
nsHttpHandler::get()->GetMimeService(getter_AddRefs(mime));
if (mime) {
rv = mime->GetTypeFromURI(mURI, value);
if (NS_SUCCEEDED(rv)) {
// cache this result if possible
if (mResponseHead)
mResponseHead->SetContentType(*value);
return rv;
}
}
}
// the content-type can only be set to application/x-unknown-content-type
// if there is data from which to infer a content-type.
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = PL_strdup(UNKNOWN_CONTENT_TYPE);
return *value ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
}
NS_IMETHODIMP
nsHttpChannel::SetContentType(const char *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->SetContentType(value);
}
NS_IMETHODIMP
nsHttpChannel::GetContentLength(PRInt32 *value)
{
NS_ENSURE_ARG_POINTER(value);
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
*value = mResponseHead->ContentLength();
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetContentLength(PRInt32 value)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsHttpChannel::Open(nsIInputStream **_retval)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
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);
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);
nsresult rv = Connect();
if (NS_FAILED(rv)) {
LOG(("Connect failed [rv=%x]\n", rv));
// make sure cache entry
CloseCacheEntry(rv);
AsyncAbort(rv);
mListener = 0;
mListenerContext = 0;
}
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIHttpChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetRequestMethod(char **method)
{
return DupString(mRequestHead.Method().get(), method);
}
NS_IMETHODIMP
nsHttpChannel::SetRequestMethod(const char *method)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
nsHttpAtom atom = nsHttp::ResolveAtom(method);
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, PRUint32 referrerType)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
if (nsHttpHandler::get()->ReferrerLevel() < referrerType)
return NS_OK;
// save a copy of the referrer so we can return it if requested
mReferrer = referrer;
// save a copy of the referrer type for redirects
mReferrerType = referrerType;
// clear the old referer first
mRequestHead.SetHeader(nsHttp::Referer, nsnull);
if (referrer) {
nsXPIDLCString spec;
referrer->GetSpec(getter_Copies(spec));
if (spec) {
nsCAutoString ref(spec.get());
// strip away any prehost; we don't want to be giving out passwords ;-)
nsXPIDLCString prehost;
referrer->GetPreHost(getter_Copies(prehost));
if (prehost && *prehost) {
PRUint32 prehostLoc = PRUint32(ref.Find(prehost, PR_TRUE));
ref.Cut(prehostLoc, nsCharTraits<char>::length(prehost) + 1); // + 1 for @
}
mRequestHead.SetHeader(nsHttp::Referer, ref);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::GetRequestHeader(const char *header, char **value)
{
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom)
return NS_ERROR_NOT_AVAILABLE;
return mRequestHead.GetHeader(atom, value);
}
NS_IMETHODIMP
nsHttpChannel::SetRequestHeader(const char *header, const char *value)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
LOG(("nsHttpChannel::SetRequestHeader [this=%x header=%s value=%s]\n",
this, header, value));
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom) {
NS_WARNING("failed to resolve atom");
return NS_ERROR_NOT_AVAILABLE;
}
return mRequestHead.SetHeader(atom, value);
}
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)
{
mUploadStream = stream;
if (mUploadStream)
mRequestHead.SetMethod(nsHttp::Post);
else
mRequestHead.SetMethod(nsHttp::Get);
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(char **value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
return DupString(mResponseHead->StatusText(), value);
}
NS_IMETHODIMP
nsHttpChannel::GetResponseHeader(const char *header, char **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 char *header, const char *value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
nsHttpAtom atom = nsHttp::ResolveAtom(header);
if (!atom)
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->SetHeader(atom, value);
}
NS_IMETHODIMP
nsHttpChannel::VisitResponseHeaders(nsIHttpHeaderVisitor *visitor)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
return mResponseHead->Headers().VisitHeaders(visitor);
}
NS_IMETHODIMP
nsHttpChannel::GetCharset(char **value)
{
if (!mResponseHead)
return NS_ERROR_NOT_AVAILABLE;
return DupString(mResponseHead->ContentCharset(), value);
}
NS_IMETHODIMP
nsHttpChannel::GetApplyConversion(PRBool *value)
{
NS_ENSURE_ARG_POINTER(value);
*value = mApplyConversion;
return NS_OK;
}
NS_IMETHODIMP
nsHttpChannel::SetApplyConversion(PRBool value)
{
mApplyConversion = value;
return NS_OK;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIRequestObserver
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
{
LOG(("nsHttpChannel::OnStartRequest [this=%x request=%x]\n", this, request));
if (mTransaction) {
// grab the security info from the connection object; the transaction
// is guaranteed to own a reference to the connection.
mTransaction->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
// 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();
}
// there won't be a response head if we've been cancelled
return mListener->OnStartRequest(this, mListenerContext);
}
NS_IMETHODIMP
nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult status)
{
LOG(("nsHttpChannel::OnStopRequest [this=%x request=%x status=%x]\n",
this, request, status));
// if the request is a previous transaction, then simply release it.
if (request == mPrevTransaction) {
NS_RELEASE(mPrevTransaction);
mPrevTransaction = nsnull;
}
// if the request is for something we no longer reference, then simply
// drop this event.
if ((request != mTransaction) && (request != mCacheReadRequest))
return NS_OK;
mIsPending = PR_FALSE;
mStatus = status;
// at this point, we're done with the transaction
if (mTransaction) {
NS_RELEASE(mTransaction);
mTransaction = nsnull;
}
if (mCacheEntry && NS_SUCCEEDED(status))
mCacheEntry->MarkValid();
if (mListener) {
mListener->OnStopRequest(this, mListenerContext, status);
mListener = 0;
mListenerContext = 0;
}
if (mCacheEntry)
CloseCacheEntry(status);
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, status);
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));
// if the request is for something we no longer reference, then simply
// drop this event.
if ((request != mTransaction) && (request != mCacheReadRequest)) {
NS_WARNING("got stale request... why wasn't it cancelled?");
return NS_BASE_STREAM_CLOSED;
}
if (mListener)
return mListener->OnDataAvailable(this, mListenerContext, input, offset, count);
return NS_BASE_STREAM_CLOSED;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIInterfaceRequestor
//-----------------------------------------------------------------------------
NS_IMETHODIMP
nsHttpChannel::GetInterface(const nsIID &iid, void **result)
{
if (iid.Equals(NS_GET_IID(nsIProgressEventSink))) {
//
// we return ourselves as the progress event sink so we can intercept
// notifications and set the correct request and context parameters.
// but, if we don't have a progress sink to forward those messages
// to, then there's no point in handing out a reference to ourselves.
//
if (!mProgressSink)
return NS_ERROR_NO_INTERFACE;
return QueryInterface(iid, result);
}
if (mCallbacks)
return mCallbacks->GetInterface(iid, result);
return NS_ERROR_NO_INTERFACE;
}
//-----------------------------------------------------------------------------
// nsHttpChannel::nsIProgressEventSink
//-----------------------------------------------------------------------------
// called on the socket thread
NS_IMETHODIMP
nsHttpChannel::OnStatus(nsIRequest *req, nsISupports *ctx, nsresult status,
const PRUnichar *statusText)
{
if (mProgressSink)
mProgressSink->OnStatus(this, mListenerContext, status, statusText);
return NS_OK;
}
// called on the socket thread
NS_IMETHODIMP
nsHttpChannel::OnProgress(nsIRequest *req, nsISupports *ctx,
PRUint32 progress, PRUint32 progressMax)
{
if (mProgressSink)
mProgressSink->OnProgress(this, mListenerContext, progress, progressMax);
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, PRBool fromCacheOnly)
{
nsresult rv;
LOG(("nsHttpChannel::SetCacheKey [this=%x key=%x fromCacheOnly=%d]\n",
this, key, fromCacheOnly));
// 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;
}
mFromCacheOnly = fromCacheOnly;
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)
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);
}
//-----------------------------------------------------------------------------
// 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;
}
// advance to the next state...
nsresult 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;
}