gecko/netwerk/protocol/http/HttpChannelChild.cpp
Ed Morley 160bed2536 Backout SPDY to keep us under the MSVC virtual address space limit during win PGO builds (bug 709193)
Backs out 952d14a9e508 (bug 707173), c170c678c9ac (bug 708305), 0a5f66d5d8e4 (bug 707662), 3204b70435fe (bug 706236) and the main landing range 48807fde0339:0bd45ead1676 (bug 528288).
2011-12-10 22:36:26 +00:00

1454 lines
43 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* ***** 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.org code.
*
* The Initial Developer of the Original Code is
* The Mozilla Foundation
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jason Duell <jduell.mcbugs@gmail.com>
* Daniel Witte <dwitte@mozilla.com>
* Honza Bambas <honzab@firemni.cz>
*
* 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 "nsHttp.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/HttpChannelChild.h"
#include "nsStringStream.h"
#include "nsHttpHandler.h"
#include "nsMimeTypes.h"
#include "nsNetUtil.h"
#include "nsSerializationHelper.h"
namespace mozilla {
namespace net {
//-----------------------------------------------------------------------------
// HttpChannelChild
//-----------------------------------------------------------------------------
HttpChannelChild::HttpChannelChild()
: HttpAsyncAborter<HttpChannelChild>(this)
, mIsFromCache(false)
, mCacheEntryAvailable(false)
, mCacheExpirationTime(nsICache::NO_EXPIRATION_TIME)
, mSendResumeAt(false)
, mIPCOpen(false)
, mKeptAlive(false)
, mEventQ(static_cast<nsIHttpChannel*>(this))
{
LOG(("Creating HttpChannelChild @%x\n", this));
}
HttpChannelChild::~HttpChannelChild()
{
LOG(("Destroying HttpChannelChild @%x\n", this));
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsISupports
//-----------------------------------------------------------------------------
// Override nsHashPropertyBag's AddRef: we don't need thread-safe refcnt
NS_IMPL_ADDREF(HttpChannelChild)
NS_IMETHODIMP_(nsrefcnt) HttpChannelChild::Release()
{
NS_PRECONDITION(0 != mRefCnt, "dup release");
NS_ASSERT_OWNINGTHREAD(HttpChannelChild);
--mRefCnt;
NS_LOG_RELEASE(this, mRefCnt, "HttpChannelChild");
// Normally we Send_delete in OnStopRequest, but when we need to retain the
// remote channel for security info IPDL itself holds 1 reference, so we
// Send_delete when refCnt==1.
if (mKeptAlive && mRefCnt == 1) {
NS_ASSERTION(mIPCOpen, "mIPCOpen false!");
mKeptAlive = false;
// Send_delete calls NeckoChild::DeallocPHttpChannel, which will release
// again to refcount==0
PHttpChannelChild::Send__delete__(this);
return 0;
}
if (mRefCnt == 0) {
mRefCnt = 1; /* stabilize */
delete this;
return 0;
}
return mRefCnt;
}
NS_INTERFACE_MAP_BEGIN(HttpChannelChild)
NS_INTERFACE_MAP_ENTRY(nsIRequest)
NS_INTERFACE_MAP_ENTRY(nsIChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelInternal)
NS_INTERFACE_MAP_ENTRY(nsICacheInfoChannel)
NS_INTERFACE_MAP_ENTRY(nsIResumableChannel)
NS_INTERFACE_MAP_ENTRY(nsISupportsPriority)
NS_INTERFACE_MAP_ENTRY(nsIProxiedChannel)
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
NS_INTERFACE_MAP_ENTRY(nsIAsyncVerifyRedirectCallback)
NS_INTERFACE_MAP_ENTRY(nsIChildChannel)
NS_INTERFACE_MAP_ENTRY(nsIHttpChannelChild)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAssociatedContentSecurity, GetAssociatedContentSecurity())
NS_INTERFACE_MAP_END_INHERITING(HttpBaseChannel)
//-----------------------------------------------------------------------------
// HttpChannelChild::PHttpChannelChild
//-----------------------------------------------------------------------------
void
HttpChannelChild::AddIPDLReference()
{
NS_ABORT_IF_FALSE(!mIPCOpen, "Attempt to retain more than one IPDL reference");
mIPCOpen = true;
AddRef();
}
void
HttpChannelChild::ReleaseIPDLReference()
{
NS_ABORT_IF_FALSE(mIPCOpen, "Attempt to release nonexistent IPDL reference");
mIPCOpen = false;
Release();
}
class AssociateApplicationCacheEvent : public ChannelEvent
{
public:
AssociateApplicationCacheEvent(HttpChannelChild* child,
const nsCString &groupID,
const nsCString &clientID)
: mChild(child)
, groupID(groupID)
, clientID(clientID) {}
void Run() { mChild->AssociateApplicationCache(groupID, clientID); }
private:
HttpChannelChild* mChild;
nsCString groupID;
nsCString clientID;
};
bool
HttpChannelChild::RecvAssociateApplicationCache(const nsCString &groupID,
const nsCString &clientID)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new AssociateApplicationCacheEvent(this, groupID, clientID));
} else {
AssociateApplicationCache(groupID, clientID);
}
return true;
}
void
HttpChannelChild::AssociateApplicationCache(const nsCString &groupID,
const nsCString &clientID)
{
nsresult rv;
mApplicationCache = do_CreateInstance(NS_APPLICATIONCACHE_CONTRACTID, &rv);
if (NS_FAILED(rv))
return;
mLoadedFromApplicationCache = true;
mApplicationCache->InitAsHandle(groupID, clientID);
}
class StartRequestEvent : public ChannelEvent
{
public:
StartRequestEvent(HttpChannelChild* child,
const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const RequestHeaderTuples& requestHeaders,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const PRUint32& cacheExpirationTime,
const nsCString& cachedCharset,
const nsCString& securityInfoSerialization,
const PRNetAddr& selfAddr,
const PRNetAddr& peerAddr)
: mChild(child)
, mResponseHead(responseHead)
, mRequestHeaders(requestHeaders)
, mUseResponseHead(useResponseHead)
, mIsFromCache(isFromCache)
, mCacheEntryAvailable(cacheEntryAvailable)
, mCacheExpirationTime(cacheExpirationTime)
, mCachedCharset(cachedCharset)
, mSecurityInfoSerialization(securityInfoSerialization)
, mSelfAddr(selfAddr)
, mPeerAddr(peerAddr)
{}
void Run()
{
mChild->OnStartRequest(mResponseHead, mUseResponseHead, mRequestHeaders,
mIsFromCache, mCacheEntryAvailable,
mCacheExpirationTime, mCachedCharset,
mSecurityInfoSerialization, mSelfAddr, mPeerAddr);
}
private:
HttpChannelChild* mChild;
nsHttpResponseHead mResponseHead;
RequestHeaderTuples mRequestHeaders;
bool mUseResponseHead;
bool mIsFromCache;
bool mCacheEntryAvailable;
PRUint32 mCacheExpirationTime;
nsCString mCachedCharset;
nsCString mSecurityInfoSerialization;
PRNetAddr mSelfAddr;
PRNetAddr mPeerAddr;
};
bool
HttpChannelChild::RecvOnStartRequest(const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const RequestHeaderTuples& requestHeaders,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const PRUint32& cacheExpirationTime,
const nsCString& cachedCharset,
const nsCString& securityInfoSerialization,
const PRNetAddr& selfAddr,
const PRNetAddr& peerAddr)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new StartRequestEvent(this, responseHead, useResponseHead,
requestHeaders, isFromCache,
cacheEntryAvailable,
cacheExpirationTime, cachedCharset,
securityInfoSerialization, selfAddr,
peerAddr));
} else {
OnStartRequest(responseHead, useResponseHead, requestHeaders, isFromCache,
cacheEntryAvailable, cacheExpirationTime, cachedCharset,
securityInfoSerialization, selfAddr, peerAddr);
}
return true;
}
void
HttpChannelChild::OnStartRequest(const nsHttpResponseHead& responseHead,
const bool& useResponseHead,
const RequestHeaderTuples& requestHeaders,
const bool& isFromCache,
const bool& cacheEntryAvailable,
const PRUint32& cacheExpirationTime,
const nsCString& cachedCharset,
const nsCString& securityInfoSerialization,
const PRNetAddr& selfAddr,
const PRNetAddr& peerAddr)
{
LOG(("HttpChannelChild::RecvOnStartRequest [this=%x]\n", this));
if (useResponseHead && !mCanceled)
mResponseHead = new nsHttpResponseHead(responseHead);
if (!securityInfoSerialization.IsEmpty()) {
NS_DeserializeObject(securityInfoSerialization,
getter_AddRefs(mSecurityInfo));
}
mIsFromCache = isFromCache;
mCacheEntryAvailable = cacheEntryAvailable;
mCacheExpirationTime = cacheExpirationTime;
mCachedCharset = cachedCharset;
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
// replace our request headers with what actually got sent in the parent
mRequestHead.ClearHeaders();
for (PRUint32 i = 0; i < requestHeaders.Length(); i++) {
mRequestHead.Headers().SetHeader(nsHttp::ResolveAtom(requestHeaders[i].mHeader),
requestHeaders[i].mValue);
}
// notify "http-on-examine-response" observers
gHttpHandler->OnExamineResponse(this);
mTracingEnabled = false;
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
if (NS_FAILED(rv)) {
Cancel(rv);
return;
}
if (mResponseHead)
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
rv = ApplyContentConversions();
if (NS_FAILED(rv))
Cancel(rv);
mSelfAddr = selfAddr;
mPeerAddr = peerAddr;
}
class TransportAndDataEvent : public ChannelEvent
{
public:
TransportAndDataEvent(HttpChannelChild* child,
const nsresult& status,
const PRUint64& progress,
const PRUint64& progressMax,
const nsCString& data,
const PRUint32& offset,
const PRUint32& count)
: mChild(child)
, mStatus(status)
, mProgress(progress)
, mProgressMax(progressMax)
, mData(data)
, mOffset(offset)
, mCount(count) {}
void Run() { mChild->OnTransportAndData(mStatus, mProgress, mProgressMax,
mData, mOffset, mCount); }
private:
HttpChannelChild* mChild;
nsresult mStatus;
PRUint64 mProgress;
PRUint64 mProgressMax;
nsCString mData;
PRUint32 mOffset;
PRUint32 mCount;
};
bool
HttpChannelChild::RecvOnTransportAndData(const nsresult& status,
const PRUint64& progress,
const PRUint64& progressMax,
const nsCString& data,
const PRUint32& offset,
const PRUint32& count)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new TransportAndDataEvent(this, status, progress,
progressMax, data, offset,
count));
} else {
OnTransportAndData(status, progress, progressMax, data, offset, count);
}
return true;
}
void
HttpChannelChild::OnTransportAndData(const nsresult& status,
const PRUint64 progress,
const PRUint64& progressMax,
const nsCString& data,
const PRUint32& offset,
const PRUint32& count)
{
LOG(("HttpChannelChild::OnTransportAndData [this=%x]\n", this));
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
// Hold queue lock throughout all three calls, else we might process a later
// necko msg in between them.
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
// block status/progress after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set.
// - JDUELL: may not need mStatus/mIsPending checks, given this is always called
// during OnDataAvailable, and we've already checked mCanceled. Code
// dupe'd from nsHttpChannel
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
// OnStatus
//
NS_ASSERTION(status == nsISocketTransport::STATUS_RECEIVING_FROM ||
status == nsITransport::STATUS_READING,
"unexpected status code");
nsCAutoString host;
mURI->GetHost(host);
mProgressSink->OnStatus(this, nsnull, status,
NS_ConvertUTF8toUTF16(host).get());
// OnProgress
//
if (progress > 0) {
NS_ASSERTION(progress <= progressMax, "unexpected progress values");
mProgressSink->OnProgress(this, nsnull, progress, progressMax);
}
}
// OnDataAvailable
//
// NOTE: the OnDataAvailable contract requires the client to read all the data
// in the inputstream. This code relies on that ('data' will go away after
// this function). Apparently the previous, non-e10s behavior was to actually
// support only reading part of the data, allowing later calls to read the
// rest.
nsCOMPtr<nsIInputStream> stringStream;
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(),
count, NS_ASSIGNMENT_DEPEND);
if (NS_FAILED(rv)) {
Cancel(rv);
return;
}
rv = mListener->OnDataAvailable(this, mListenerContext,
stringStream, offset, count);
stringStream->Close();
if (NS_FAILED(rv)) {
Cancel(rv);
}
}
class StopRequestEvent : public ChannelEvent
{
public:
StopRequestEvent(HttpChannelChild* child,
const nsresult& statusCode)
: mChild(child)
, mStatusCode(statusCode) {}
void Run() { mChild->OnStopRequest(mStatusCode); }
private:
HttpChannelChild* mChild;
nsresult mStatusCode;
};
bool
HttpChannelChild::RecvOnStopRequest(const nsresult& statusCode)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new StopRequestEvent(this, statusCode));
} else {
OnStopRequest(statusCode);
}
return true;
}
void
HttpChannelChild::OnStopRequest(const nsresult& statusCode)
{
LOG(("HttpChannelChild::OnStopRequest [this=%x status=%u]\n",
this, statusCode));
mIsPending = false;
if (!mCanceled && NS_SUCCEEDED(mStatus))
mStatus = statusCode;
{ // We must flush the queue before we Send__delete__
// (although we really shouldn't receive any msgs after OnStop),
// so make sure this goes out of scope before then.
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
mListener->OnStopRequest(this, mListenerContext, mStatus);
mListener = 0;
mListenerContext = 0;
mCacheEntryAvailable = false;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, mStatus);
}
if (mLoadFlags & LOAD_DOCUMENT_URI) {
// Keep IPDL channel open, but only for updating security info.
mKeptAlive = true;
SendDocumentChannelCleanup();
} else {
// This calls NeckoChild::DeallocPHttpChannel(), which deletes |this| if IPDL
// holds the last reference. Don't rely on |this| existing after here.
PHttpChannelChild::Send__delete__(this);
}
}
class ProgressEvent : public ChannelEvent
{
public:
ProgressEvent(HttpChannelChild* child,
const PRUint64& progress,
const PRUint64& progressMax)
: mChild(child)
, mProgress(progress)
, mProgressMax(progressMax) {}
void Run() { mChild->OnProgress(mProgress, mProgressMax); }
private:
HttpChannelChild* mChild;
PRUint64 mProgress, mProgressMax;
};
bool
HttpChannelChild::RecvOnProgress(const PRUint64& progress,
const PRUint64& progressMax)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new ProgressEvent(this, progress, progressMax));
} else {
OnProgress(progress, progressMax);
}
return true;
}
void
HttpChannelChild::OnProgress(const PRUint64& progress,
const PRUint64& progressMax)
{
LOG(("HttpChannelChild::OnProgress [this=%p progress=%llu/%llu]\n",
this, progress, progressMax));
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
// block socket status event after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
if (progress > 0) {
NS_ASSERTION(progress <= progressMax, "unexpected progress values");
mProgressSink->OnProgress(this, nsnull, progress, progressMax);
}
}
}
class StatusEvent : public ChannelEvent
{
public:
StatusEvent(HttpChannelChild* child,
const nsresult& status)
: mChild(child)
, mStatus(status) {}
void Run() { mChild->OnStatus(mStatus); }
private:
HttpChannelChild* mChild;
nsresult mStatus;
};
bool
HttpChannelChild::RecvOnStatus(const nsresult& status)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new StatusEvent(this, status));
} else {
OnStatus(status);
}
return true;
}
void
HttpChannelChild::OnStatus(const nsresult& status)
{
LOG(("HttpChannelChild::OnStatus [this=%p status=%x]\n", this, status));
if (mCanceled)
return;
// cache the progress sink so we don't have to query for it each time.
if (!mProgressSink)
GetCallback(mProgressSink);
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
// block socket status event after Cancel or OnStopRequest has been called,
// or if channel has LOAD_BACKGROUND set
if (mProgressSink && NS_SUCCEEDED(mStatus) && mIsPending &&
!(mLoadFlags & LOAD_BACKGROUND))
{
nsCAutoString host;
mURI->GetHost(host);
mProgressSink->OnStatus(this, nsnull, status,
NS_ConvertUTF8toUTF16(host).get());
}
}
class FailedAsyncOpenEvent : public ChannelEvent
{
public:
FailedAsyncOpenEvent(HttpChannelChild* child, const nsresult& status)
: mChild(child)
, mStatus(status) {}
void Run() { mChild->FailedAsyncOpen(mStatus); }
private:
HttpChannelChild* mChild;
nsresult mStatus;
};
bool
HttpChannelChild::RecvFailedAsyncOpen(const nsresult& status)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new FailedAsyncOpenEvent(this, status));
} else {
FailedAsyncOpen(status);
}
return true;
}
// We need to have an implementation of this function just so that we can keep
// all references to mCallOnResume of type HttpChannelChild: it's not OK in C++
// to set a member function ptr to a base class function.
void
HttpChannelChild::HandleAsyncAbort()
{
HttpAsyncAborter<HttpChannelChild>::HandleAsyncAbort();
}
void
HttpChannelChild::FailedAsyncOpen(const nsresult& status)
{
LOG(("HttpChannelChild::FailedAsyncOpen [this=%p status=%x]\n", this, status));
mStatus = status;
mIsPending = false;
// We're already being called from IPDL, therefore already "async"
HandleAsyncAbort();
}
void
HttpChannelChild::DoNotifyListenerCleanup()
{
if (mIPCOpen)
PHttpChannelChild::Send__delete__(this);
}
class DeleteSelfEvent : public ChannelEvent
{
public:
DeleteSelfEvent(HttpChannelChild* child) : mChild(child) {}
void Run() { mChild->DeleteSelf(); }
private:
HttpChannelChild* mChild;
};
bool
HttpChannelChild::RecvDeleteSelf()
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new DeleteSelfEvent(this));
} else {
DeleteSelf();
}
return true;
}
void
HttpChannelChild::DeleteSelf()
{
Send__delete__(this);
}
class Redirect1Event : public ChannelEvent
{
public:
Redirect1Event(HttpChannelChild* child,
const PRUint32& newChannelId,
const IPC::URI& newURI,
const PRUint32& redirectFlags,
const nsHttpResponseHead& responseHead)
: mChild(child)
, mNewChannelId(newChannelId)
, mNewURI(newURI)
, mRedirectFlags(redirectFlags)
, mResponseHead(responseHead) {}
void Run()
{
mChild->Redirect1Begin(mNewChannelId, mNewURI, mRedirectFlags,
mResponseHead);
}
private:
HttpChannelChild* mChild;
PRUint32 mNewChannelId;
IPC::URI mNewURI;
PRUint32 mRedirectFlags;
nsHttpResponseHead mResponseHead;
};
bool
HttpChannelChild::RecvRedirect1Begin(const PRUint32& newChannelId,
const URI& newUri,
const PRUint32& redirectFlags,
const nsHttpResponseHead& responseHead)
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new Redirect1Event(this, newChannelId, newUri,
redirectFlags, responseHead));
} else {
Redirect1Begin(newChannelId, newUri, redirectFlags, responseHead);
}
return true;
}
void
HttpChannelChild::Redirect1Begin(const PRUint32& newChannelId,
const IPC::URI& newURI,
const PRUint32& redirectFlags,
const nsHttpResponseHead& responseHead)
{
nsresult rv;
nsCOMPtr<nsIIOService> ioService;
rv = gHttpHandler->GetIOService(getter_AddRefs(ioService));
if (NS_FAILED(rv)) {
// Veto redirect. nsHttpChannel decides to cancel or continue.
OnRedirectVerifyCallback(rv);
return;
}
nsCOMPtr<nsIURI> uri(newURI);
nsCOMPtr<nsIChannel> newChannel;
rv = ioService->NewChannelFromURI(uri, getter_AddRefs(newChannel));
if (NS_FAILED(rv)) {
// Veto redirect. nsHttpChannel decides to cancel or continue.
OnRedirectVerifyCallback(rv);
return;
}
// We won't get OnStartRequest, set cookies here.
mResponseHead = new nsHttpResponseHead(responseHead);
SetCookie(mResponseHead->PeekHeader(nsHttp::Set_Cookie));
bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(),
mRequestHead.Method());
rv = SetupReplacementChannel(uri, newChannel, !rewriteToGET);
if (NS_FAILED(rv)) {
// Veto redirect. nsHttpChannel decides to cancel or continue.
OnRedirectVerifyCallback(rv);
return;
}
mRedirectChannelChild = do_QueryInterface(newChannel);
if (mRedirectChannelChild) {
mRedirectChannelChild->ConnectParent(newChannelId);
} else {
NS_ERROR("Redirecting to a protocol that doesn't support universal protocol redirect");
}
rv = gHttpHandler->AsyncOnChannelRedirect(this,
newChannel,
redirectFlags);
if (NS_FAILED(rv))
OnRedirectVerifyCallback(rv);
}
class Redirect3Event : public ChannelEvent
{
public:
Redirect3Event(HttpChannelChild* child) : mChild(child) {}
void Run() { mChild->Redirect3Complete(); }
private:
HttpChannelChild* mChild;
};
bool
HttpChannelChild::RecvRedirect3Complete()
{
if (mEventQ.ShouldEnqueue()) {
mEventQ.Enqueue(new Redirect3Event(this));
} else {
Redirect3Complete();
}
return true;
}
void
HttpChannelChild::Redirect3Complete()
{
nsresult rv = NS_OK;
// Chrome channel has been AsyncOpen'd. Reflect this in child.
if (mRedirectChannelChild)
rv = mRedirectChannelChild->CompleteRedirectSetup(mListener,
mListenerContext);
// Redirecting to new channel: shut this down and init new channel
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nsnull, NS_BINDING_ABORTED);
if (NS_FAILED(rv))
NS_WARNING("CompleteRedirectSetup failed, HttpChannelChild already open?");
// Release ref to new channel.
mRedirectChannelChild = nsnull;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIChildChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::ConnectParent(PRUint32 id)
{
mozilla::dom::TabChild* tabChild = nsnull;
nsCOMPtr<nsITabChild> iTabChild;
GetCallback(iTabChild);
if (iTabChild) {
tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
}
// The socket transport in the chrome process now holds a logical ref to us
// until OnStopRequest, or we do a redirect, or we hit an IPDL error.
AddIPDLReference();
if (!gNeckoChild->SendPHttpChannelConstructor(this, tabChild))
return NS_ERROR_FAILURE;
if (!SendConnectChannel(id))
return NS_ERROR_FAILURE;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
nsISupports *aContext)
{
LOG(("HttpChannelChild::FinishRedirectSetup [this=%x]\n", this));
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
/*
* No need to check for cancel: we don't get here if nsHttpChannel canceled
* before AsyncOpen(); if it's canceled after that, OnStart/Stop will just
* get called with error code as usual. So just setup mListener and make the
* channel reflect AsyncOpen'ed state.
*/
mIsPending = true;
mWasOpened = true;
mListener = listener;
mListenerContext = aContext;
// add ourselves to the load group.
if (mLoadGroup)
mLoadGroup->AddRequest(this, nsnull);
// We already have an open IPDL connection to the parent. If on-modify-request
// listeners or load group observers canceled us, let the parent handle it
// and send it back to us naturally.
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIAsyncVerifyRedirectCallback
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::OnRedirectVerifyCallback(nsresult result)
{
nsCOMPtr<nsIHttpChannel> newHttpChannel =
do_QueryInterface(mRedirectChannelChild);
if (newHttpChannel) {
// Must not be called until after redirect observers called.
newHttpChannel->SetOriginalURI(mOriginalURI);
}
RequestHeaderTuples emptyHeaders;
RequestHeaderTuples* headerTuples = &emptyHeaders;
nsCOMPtr<nsIHttpChannelChild> newHttpChannelChild =
do_QueryInterface(mRedirectChannelChild);
if (newHttpChannelChild && NS_SUCCEEDED(result)) {
newHttpChannelChild->AddCookiesToRequest();
newHttpChannelChild->GetHeaderTuples(&headerTuples);
}
// After we verify redirect, nsHttpChannel may hit the network: must give
// "http-on-modify-request" observers the chance to cancel before that.
if (NS_SUCCEEDED(result))
gHttpHandler->OnModifyRequest(newHttpChannel);
if (mIPCOpen)
SendRedirect2Verify(result, *headerTuples);
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIRequest
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::Cancel(nsresult status)
{
if (!mCanceled) {
// If this cancel occurs before nsHttpChannel has been set up, AsyncOpen
// is responsible for cleaning up.
mCanceled = true;
mStatus = status;
if (RemoteChannelExists())
SendCancel(status);
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::Suspend()
{
NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
if (!mSuspendCount++) {
SendSuspend();
mEventQ.Suspend();
}
return NS_OK;
}
void
HttpChannelChild::CompleteResume()
{
if (mCallOnResume) {
(this->*mCallOnResume)();
mCallOnResume = 0;
}
// Don't resume event queue until now, else queued events could get
// flushed/called before mCallOnResume, which needs to run first.
mEventQ.Resume();
}
NS_IMETHODIMP
HttpChannelChild::Resume()
{
NS_ENSURE_TRUE(RemoteChannelExists(), NS_ERROR_NOT_AVAILABLE);
NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED);
nsresult rv = NS_OK;
if (!--mSuspendCount) {
SendResume();
rv = AsyncCall(&HttpChannelChild::CompleteResume);
}
return rv;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::GetSecurityInfo(nsISupports **aSecurityInfo)
{
NS_ENSURE_ARG_POINTER(aSecurityInfo);
NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::AsyncOpen(nsIStreamListener *listener, nsISupports *aContext)
{
LOG(("HttpChannelChild::AsyncOpen [this=%x uri=%s]\n", this, mSpec.get()));
if (mCanceled)
return mStatus;
NS_ENSURE_TRUE(gNeckoChild != nsnull, NS_ERROR_FAILURE);
NS_ENSURE_ARG_POINTER(listener);
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
// Port checked in parent, but duplicate here so we can return with error
// immediately
nsresult rv;
rv = NS_CheckPortSafety(mURI);
if (NS_FAILED(rv))
return rv;
const char *cookieHeader = mRequestHead.PeekHeader(nsHttp::Cookie);
if (cookieHeader) {
mUserSetCookieHeader = cookieHeader;
}
AddCookiesToRequest();
//
// NOTE: From now on we must return NS_OK; all errors must be handled via
// OnStart/OnStopRequest
//
// notify "http-on-modify-request" observers
gHttpHandler->OnModifyRequest(this);
mIsPending = true;
mWasOpened = true;
mListener = listener;
mListenerContext = aContext;
// add ourselves to the load group.
if (mLoadGroup)
mLoadGroup->AddRequest(this, nsnull);
if (mCanceled) {
// We may have been canceled already, either by on-modify-request
// listeners or by load group observers; in that case, don't create IPDL
// connection. See nsHttpChannel::AsyncOpen().
AsyncAbort(mStatus);
return NS_OK;
}
nsCString appCacheClientId;
if (mInheritApplicationCache) {
// Pick up an application cache from the notification
// callbacks if available
nsCOMPtr<nsIApplicationCacheContainer> appCacheContainer;
GetCallback(appCacheContainer);
if (appCacheContainer) {
nsCOMPtr<nsIApplicationCache> appCache;
rv = appCacheContainer->GetApplicationCache(getter_AddRefs(appCache));
if (NS_SUCCEEDED(rv) && appCache) {
appCache->GetClientID(appCacheClientId);
}
}
}
//
// Send request to the chrome process...
//
// FIXME: bug 558623: Combine constructor and SendAsyncOpen into one IPC msg
mozilla::dom::TabChild* tabChild = nsnull;
nsCOMPtr<nsITabChild> iTabChild;
GetCallback(iTabChild);
if (iTabChild) {
tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
}
// The socket transport in the chrome process now holds a logical ref to us
// until OnStopRequest, or we do a redirect, or we hit an IPDL error.
AddIPDLReference();
gNeckoChild->SendPHttpChannelConstructor(this, tabChild);
SendAsyncOpen(IPC::URI(mURI), IPC::URI(mOriginalURI),
IPC::URI(mDocumentURI), IPC::URI(mReferrer), mLoadFlags,
mRequestHeaders, mRequestHead.Method(),
IPC::InputStream(mUploadStream), mUploadStreamHasHeaders,
mPriority, mRedirectionLimit, mAllowPipelining,
mForceAllowThirdPartyCookie, mSendResumeAt,
mStartPos, mEntityID, mChooseApplicationCache,
appCacheClientId);
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIHttpChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::SetRequestHeader(const nsACString& aHeader,
const nsACString& aValue,
bool aMerge)
{
nsresult rv = HttpBaseChannel::SetRequestHeader(aHeader, aValue, aMerge);
if (NS_FAILED(rv))
return rv;
RequestHeaderTuple* tuple = mRequestHeaders.AppendElement();
if (!tuple)
return NS_ERROR_OUT_OF_MEMORY;
tuple->mHeader = aHeader;
tuple->mValue = aValue;
tuple->mMerge = aMerge;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIHttpChannelInternal
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::SetupFallbackChannel(const char *aFallbackKey)
{
DROP_DEAD();
}
// The next four _should_ be implemented, but we need to figure out how
// to transfer the data from the chrome process first.
NS_IMETHODIMP
HttpChannelChild::GetRemoteAddress(nsACString & _result)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HttpChannelChild::GetRemotePort(PRInt32 * _result)
{
NS_ENSURE_ARG_POINTER(_result);
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HttpChannelChild::GetLocalAddress(nsACString & _result)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
HttpChannelChild::GetLocalPort(PRInt32 * _result)
{
NS_ENSURE_ARG_POINTER(_result);
return NS_ERROR_NOT_AVAILABLE;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsICacheInfoChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::GetCacheTokenExpirationTime(PRUint32 *_retval)
{
NS_ENSURE_ARG_POINTER(_retval);
if (!mCacheEntryAvailable)
return NS_ERROR_NOT_AVAILABLE;
*_retval = mCacheExpirationTime;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::GetCacheTokenCachedCharset(nsACString &_retval)
{
if (!mCacheEntryAvailable)
return NS_ERROR_NOT_AVAILABLE;
_retval = mCachedCharset;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::SetCacheTokenCachedCharset(const nsACString &aCharset)
{
if (!mCacheEntryAvailable || !RemoteChannelExists())
return NS_ERROR_NOT_AVAILABLE;
mCachedCharset = aCharset;
if (!SendSetCacheTokenCachedCharset(PromiseFlatCString(aCharset))) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::IsFromCache(bool *value)
{
if (!mIsPending)
return NS_ERROR_NOT_AVAILABLE;
*value = mIsFromCache;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIResumableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::ResumeAt(PRUint64 startPos, const nsACString& entityID)
{
ENSURE_CALLED_BEFORE_ASYNC_OPEN();
mStartPos = startPos;
mEntityID = entityID;
mSendResumeAt = true;
return NS_OK;
}
// GetEntityID is shared in HttpBaseChannel
//-----------------------------------------------------------------------------
// HttpChannelChild::nsISupportsPriority
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::SetPriority(PRInt32 aPriority)
{
PRInt16 newValue = CLAMP(aPriority, PR_INT16_MIN, PR_INT16_MAX);
if (mPriority == newValue)
return NS_OK;
mPriority = newValue;
if (RemoteChannelExists())
SendSetPriority(mPriority);
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIProxiedChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::GetProxyInfo(nsIProxyInfo **aProxyInfo)
{
DROP_DEAD();
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIApplicationCacheContainer
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::GetApplicationCache(nsIApplicationCache **aApplicationCache)
{
NS_IF_ADDREF(*aApplicationCache = mApplicationCache);
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::SetApplicationCache(nsIApplicationCache *aApplicationCache)
{
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
mApplicationCache = aApplicationCache;
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIApplicationCacheChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
HttpChannelChild::GetLoadedFromApplicationCache(bool *aLoadedFromApplicationCache)
{
*aLoadedFromApplicationCache = mLoadedFromApplicationCache;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::GetInheritApplicationCache(bool *aInherit)
{
*aInherit = mInheritApplicationCache;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::SetInheritApplicationCache(bool aInherit)
{
mInheritApplicationCache = aInherit;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::GetChooseApplicationCache(bool *aChoose)
{
*aChoose = mChooseApplicationCache;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::SetChooseApplicationCache(bool aChoose)
{
mChooseApplicationCache = aChoose;
return NS_OK;
}
NS_IMETHODIMP
HttpChannelChild::MarkOfflineCacheEntryAsForeign()
{
SendMarkOfflineCacheEntryAsForeign();
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIAssociatedContentSecurity
//-----------------------------------------------------------------------------
bool
HttpChannelChild::GetAssociatedContentSecurity(
nsIAssociatedContentSecurity** _result)
{
if (!mSecurityInfo)
return false;
nsCOMPtr<nsIAssociatedContentSecurity> assoc =
do_QueryInterface(mSecurityInfo);
if (!assoc)
return false;
if (_result)
assoc.forget(_result);
return true;
}
/* attribute unsigned long countSubRequestsHighSecurity; */
NS_IMETHODIMP
HttpChannelChild::GetCountSubRequestsHighSecurity(
PRInt32 *aSubRequestsHighSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->GetCountSubRequestsHighSecurity(aSubRequestsHighSecurity);
}
NS_IMETHODIMP
HttpChannelChild::SetCountSubRequestsHighSecurity(
PRInt32 aSubRequestsHighSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->SetCountSubRequestsHighSecurity(aSubRequestsHighSecurity);
}
/* attribute unsigned long countSubRequestsLowSecurity; */
NS_IMETHODIMP
HttpChannelChild::GetCountSubRequestsLowSecurity(
PRInt32 *aSubRequestsLowSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->GetCountSubRequestsLowSecurity(aSubRequestsLowSecurity);
}
NS_IMETHODIMP
HttpChannelChild::SetCountSubRequestsLowSecurity(
PRInt32 aSubRequestsLowSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->SetCountSubRequestsLowSecurity(aSubRequestsLowSecurity);
}
/* attribute unsigned long countSubRequestsBrokenSecurity; */
NS_IMETHODIMP
HttpChannelChild::GetCountSubRequestsBrokenSecurity(
PRInt32 *aSubRequestsBrokenSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->GetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
}
NS_IMETHODIMP
HttpChannelChild::SetCountSubRequestsBrokenSecurity(
PRInt32 aSubRequestsBrokenSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->SetCountSubRequestsBrokenSecurity(aSubRequestsBrokenSecurity);
}
/* attribute unsigned long countSubRequestsNoSecurity; */
NS_IMETHODIMP
HttpChannelChild::GetCountSubRequestsNoSecurity(PRInt32 *aSubRequestsNoSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->GetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
}
NS_IMETHODIMP
HttpChannelChild::SetCountSubRequestsNoSecurity(PRInt32 aSubRequestsNoSecurity)
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
return assoc->SetCountSubRequestsNoSecurity(aSubRequestsNoSecurity);
}
NS_IMETHODIMP
HttpChannelChild::Flush()
{
nsCOMPtr<nsIAssociatedContentSecurity> assoc;
if (!GetAssociatedContentSecurity(getter_AddRefs(assoc)))
return NS_OK;
nsresult rv;
PRInt32 hi, low, broken, no;
rv = assoc->GetCountSubRequestsHighSecurity(&hi);
NS_ENSURE_SUCCESS(rv, rv);
rv = assoc->GetCountSubRequestsLowSecurity(&low);
NS_ENSURE_SUCCESS(rv, rv);
rv = assoc->GetCountSubRequestsBrokenSecurity(&broken);
NS_ENSURE_SUCCESS(rv, rv);
rv = assoc->GetCountSubRequestsNoSecurity(&no);
NS_ENSURE_SUCCESS(rv, rv);
if (mIPCOpen)
SendUpdateAssociatedContentSecurity(hi, low, broken, no);
return NS_OK;
}
//-----------------------------------------------------------------------------
// HttpChannelChild::nsIHttpChannelChild
//-----------------------------------------------------------------------------
NS_IMETHODIMP HttpChannelChild::AddCookiesToRequest()
{
HttpBaseChannel::AddCookiesToRequest();
return NS_OK;
}
NS_IMETHODIMP HttpChannelChild::GetHeaderTuples(RequestHeaderTuples **aHeaderTuples)
{
*aHeaderTuples = &mRequestHeaders;
return NS_OK;
}
//------------------------------------------------------------------------------
}} // mozilla::net