gecko/netwerk/protocol/ftp/FTPChannelChild.cpp
2014-11-14 08:57:09 -08:00

804 lines
22 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et tw=80 : */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/net/NeckoChild.h"
#include "mozilla/net/ChannelDiverterChild.h"
#include "mozilla/net/FTPChannelChild.h"
#include "mozilla/dom/TabChild.h"
#include "nsFtpProtocolHandler.h"
#include "nsITabChild.h"
#include "nsStringStream.h"
#include "nsNetUtil.h"
#include "base/compiler_specific.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "mozilla/ipc/URIUtils.h"
#include "SerializedLoadContext.h"
#include "mozilla/ipc/BackgroundUtils.h"
using namespace mozilla::ipc;
#undef LOG
#define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
namespace mozilla {
namespace net {
FTPChannelChild::FTPChannelChild(nsIURI* uri)
: mIPCOpen(false)
, mCanceled(false)
, mSuspendCount(0)
, mIsPending(false)
, mWasOpened(false)
, mLastModifiedTime(0)
, mStartPos(0)
, mDivertingToParent(false)
, mFlushedForDiversion(false)
, mSuspendSent(false)
{
LOG(("Creating FTPChannelChild @%x\n", this));
// grab a reference to the handler to ensure that it doesn't go away.
NS_ADDREF(gFtpHandler);
SetURI(uri);
mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this));
// We could support thread retargeting, but as long as we're being driven by
// IPDL on the main thread it doesn't buy us anything.
DisallowThreadRetargeting();
}
FTPChannelChild::~FTPChannelChild()
{
LOG(("Destroying FTPChannelChild @%x\n", this));
gFtpHandler->Release();
}
void
FTPChannelChild::AddIPDLReference()
{
NS_ABORT_IF_FALSE(!mIPCOpen, "Attempt to retain more than one IPDL reference");
mIPCOpen = true;
AddRef();
}
void
FTPChannelChild::ReleaseIPDLReference()
{
NS_ABORT_IF_FALSE(mIPCOpen, "Attempt to release nonexistent IPDL reference");
mIPCOpen = false;
Release();
}
//-----------------------------------------------------------------------------
// FTPChannelChild::nsISupports
//-----------------------------------------------------------------------------
NS_IMPL_ISUPPORTS_INHERITED(FTPChannelChild,
nsBaseChannel,
nsIFTPChannel,
nsIUploadChannel,
nsIResumableChannel,
nsIProxiedChannel,
nsIChildChannel,
nsIDivertableChannel)
//-----------------------------------------------------------------------------
NS_IMETHODIMP
FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime)
{
*lastModifiedTime = mLastModifiedTime;
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::SetLastModifiedTime(PRTime lastModifiedTime)
{
return NS_ERROR_NOT_AVAILABLE;
}
NS_IMETHODIMP
FTPChannelChild::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
mStartPos = aStartPos;
mEntityID = aEntityID;
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::GetEntityID(nsACString& entityID)
{
entityID = mEntityID;
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo)
{
DROP_DEAD();
}
NS_IMETHODIMP
FTPChannelChild::SetUploadStream(nsIInputStream* stream,
const nsACString& contentType,
int64_t contentLength)
{
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
mUploadStream = stream;
// NOTE: contentLength is intentionally ignored here.
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::GetUploadStream(nsIInputStream** stream)
{
NS_ENSURE_ARG_POINTER(stream);
*stream = mUploadStream;
NS_IF_ADDREF(*stream);
return NS_OK;
}
//-----------------------------------------------------------------------------
// helper function to assign loadInfo to openArgs
void
propagateLoadInfo(nsILoadInfo *aLoadInfo,
FTPChannelOpenArgs& openArgs)
{
mozilla::ipc::PrincipalInfo requestingPrincipalInfo;
mozilla::ipc::PrincipalInfo triggeringPrincipalInfo;
if (aLoadInfo) {
mozilla::ipc::PrincipalToPrincipalInfo(aLoadInfo->LoadingPrincipal(),
&requestingPrincipalInfo);
openArgs.requestingPrincipalInfo() = requestingPrincipalInfo;
mozilla::ipc::PrincipalToPrincipalInfo(aLoadInfo->TriggeringPrincipal(),
&triggeringPrincipalInfo);
openArgs.triggeringPrincipalInfo() = triggeringPrincipalInfo;
openArgs.securityFlags() = aLoadInfo->GetSecurityFlags();
openArgs.contentPolicyType() = aLoadInfo->GetContentPolicyType();
return;
}
// use default values if no loadInfo is provided
mozilla::ipc::PrincipalToPrincipalInfo(nsContentUtils::GetSystemPrincipal(),
&requestingPrincipalInfo);
openArgs.requestingPrincipalInfo() = requestingPrincipalInfo;
openArgs.triggeringPrincipalInfo() = requestingPrincipalInfo;
openArgs.securityFlags() = nsILoadInfo::SEC_NORMAL;
openArgs.contentPolicyType() = nsIContentPolicy::TYPE_OTHER;
}
NS_IMETHODIMP
FTPChannelChild::AsyncOpen(::nsIStreamListener* listener, nsISupports* aContext)
{
LOG(("FTPChannelChild::AsyncOpen [this=%p]\n", this));
NS_ENSURE_TRUE((gNeckoChild), 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, as we've done since before e10s.
nsresult rv;
rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate,
// because in the child ipdl,
// a typedef URI is defined...
if (NS_FAILED(rv))
return rv;
mozilla::dom::TabChild* tabChild = nullptr;
nsCOMPtr<nsITabChild> iTabChild;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsITabChild),
getter_AddRefs(iTabChild));
GetCallback(iTabChild);
if (iTabChild) {
tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get());
}
if (MissingRequiredTabChild(tabChild, "ftp")) {
return NS_ERROR_ILLEGAL_VALUE;
}
mListener = listener;
mListenerContext = aContext;
// add ourselves to the load group.
if (mLoadGroup)
mLoadGroup->AddRequest(this, nullptr);
OptionalInputStreamParams uploadStream;
nsTArray<mozilla::ipc::FileDescriptor> fds;
SerializeInputStream(mUploadStream, uploadStream, fds);
MOZ_ASSERT(fds.IsEmpty());
FTPChannelOpenArgs openArgs;
SerializeURI(nsBaseChannel::URI(), openArgs.uri());
openArgs.startPos() = mStartPos;
openArgs.entityID() = mEntityID;
openArgs.uploadStream() = uploadStream;
nsCOMPtr<nsILoadInfo> loadInfo;
GetLoadInfo(getter_AddRefs(loadInfo));
propagateLoadInfo(loadInfo, openArgs);
gNeckoChild->
SendPFTPChannelConstructor(this, tabChild, IPC::SerializedLoadContext(this),
openArgs);
// The socket transport layer in the chrome process now has a logical ref to
// us until OnStopRequest is called.
AddIPDLReference();
mIsPending = true;
mWasOpened = true;
return rv;
}
NS_IMETHODIMP
FTPChannelChild::IsPending(bool* result)
{
*result = mIsPending;
return NS_OK;
}
nsresult
FTPChannelChild::OpenContentStream(bool async,
nsIInputStream** stream,
nsIChannel** channel)
{
NS_RUNTIMEABORT("FTPChannel*Child* should never have OpenContentStream called!");
return NS_OK;
}
//-----------------------------------------------------------------------------
// FTPChannelChild::PFTPChannelChild
//-----------------------------------------------------------------------------
class FTPStartRequestEvent : public ChannelEvent
{
public:
FTPStartRequestEvent(FTPChannelChild* aChild,
const nsresult& aChannelStatus,
const int64_t& aContentLength,
const nsCString& aContentType,
const PRTime& aLastModified,
const nsCString& aEntityID,
const URIParams& aURI)
: mChild(aChild)
, mChannelStatus(aChannelStatus)
, mContentLength(aContentLength)
, mContentType(aContentType)
, mLastModified(aLastModified)
, mEntityID(aEntityID)
, mURI(aURI)
{
}
void Run()
{
mChild->DoOnStartRequest(mChannelStatus, mContentLength, mContentType,
mLastModified, mEntityID, mURI);
}
private:
FTPChannelChild* mChild;
nsresult mChannelStatus;
int64_t mContentLength;
nsCString mContentType;
PRTime mLastModified;
nsCString mEntityID;
URIParams mURI;
};
bool
FTPChannelChild::RecvOnStartRequest(const nsresult& aChannelStatus,
const int64_t& aContentLength,
const nsCString& aContentType,
const PRTime& aLastModified,
const nsCString& aEntityID,
const URIParams& aURI)
{
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPStartRequestEvent(this, aChannelStatus,
aContentLength, aContentType,
aLastModified, aEntityID, aURI));
} else {
DoOnStartRequest(aChannelStatus, aContentLength, aContentType,
aLastModified, aEntityID, aURI);
}
return true;
}
void
FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus,
const int64_t& aContentLength,
const nsCString& aContentType,
const PRTime& aLastModified,
const nsCString& aEntityID,
const URIParams& aURI)
{
LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this));
// mFlushedForDiversion and mDivertingToParent should NEVER be set at this
// stage, as they are set in the listener's OnStartRequest.
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"mFlushedForDiversion should be unset before OnStartRequest!");
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"mDivertingToParent should be unset before OnStartRequest!");
if (!mCanceled && NS_SUCCEEDED(mStatus)) {
mStatus = aChannelStatus;
}
mContentLength = aContentLength;
SetContentType(aContentType);
mLastModifiedTime = aLastModified;
mEntityID = aEntityID;
nsCString spec;
nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
uri->GetSpec(spec);
nsBaseChannel::URI()->SetSpec(spec);
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
nsresult rv = mListener->OnStartRequest(this, mListenerContext);
if (NS_FAILED(rv))
Cancel(rv);
if (mDivertingToParent) {
mListener = nullptr;
mListenerContext = nullptr;
if (mLoadGroup) {
mLoadGroup->RemoveRequest(this, nullptr, mStatus);
}
}
}
class FTPDataAvailableEvent : public ChannelEvent
{
public:
FTPDataAvailableEvent(FTPChannelChild* aChild,
const nsresult& aChannelStatus,
const nsCString& aData,
const uint64_t& aOffset,
const uint32_t& aCount)
: mChild(aChild)
, mChannelStatus(aChannelStatus)
, mData(aData)
, mOffset(aOffset)
, mCount(aCount)
{
}
void Run()
{
mChild->DoOnDataAvailable(mChannelStatus, mData, mOffset, mCount);
}
private:
FTPChannelChild* mChild;
nsresult mChannelStatus;
nsCString mData;
uint64_t mOffset;
uint32_t mCount;
};
bool
FTPChannelChild::RecvOnDataAvailable(const nsresult& channelStatus,
const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(
new FTPDataAvailableEvent(this, channelStatus, data, offset, count));
} else {
MOZ_RELEASE_ASSERT(!mDivertingToParent,
"ShouldEnqueue when diverting to parent!");
DoOnDataAvailable(channelStatus, data, offset, count);
}
return true;
}
void
FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus,
const nsCString& data,
const uint64_t& offset,
const uint32_t& count)
{
LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this));
if (!mCanceled && NS_SUCCEEDED(mStatus)) {
mStatus = channelStatus;
}
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnDataAvailable(data, offset, count);
return;
}
if (mCanceled)
return;
// 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;
}
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
rv = mListener->OnDataAvailable(this, mListenerContext,
stringStream, offset, count);
if (NS_FAILED(rv))
Cancel(rv);
stringStream->Close();
}
class FTPStopRequestEvent : public ChannelEvent
{
public:
FTPStopRequestEvent(FTPChannelChild* aChild,
const nsresult& aChannelStatus)
: mChild(aChild)
, mChannelStatus(aChannelStatus)
{
}
void Run()
{
mChild->DoOnStopRequest(mChannelStatus);
}
private:
FTPChannelChild* mChild;
nsresult mChannelStatus;
};
bool
FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus)
{
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be receiving any more callbacks from parent!");
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPStopRequestEvent(this, aChannelStatus));
} else {
DoOnStopRequest(aChannelStatus);
}
return true;
}
void
FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus)
{
LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%u]\n",
this, aChannelStatus));
if (mDivertingToParent) {
MOZ_RELEASE_ASSERT(!mFlushedForDiversion,
"Should not be processing any more callbacks from parent!");
SendDivertOnStopRequest(aChannelStatus);
return;
}
if (!mCanceled)
mStatus = aChannelStatus;
{ // Ensure that all queued ipdl events are dispatched before
// we initiate protocol deletion below.
mIsPending = false;
AutoEventEnqueuer ensureSerialDispatch(mEventQ);
(void)mListener->OnStopRequest(this, mListenerContext, aChannelStatus);
mListener = nullptr;
mListenerContext = nullptr;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus);
}
// This calls NeckoChild::DeallocPFTPChannelChild(), which deletes |this| if IPDL
// holds the last reference. Don't rely on |this| existing after here!
Send__delete__(this);
}
class FTPFailedAsyncOpenEvent : public ChannelEvent
{
public:
FTPFailedAsyncOpenEvent(FTPChannelChild* aChild, nsresult aStatus)
: mChild(aChild), mStatus(aStatus) {}
void Run() { mChild->DoFailedAsyncOpen(mStatus); }
private:
FTPChannelChild* mChild;
nsresult mStatus;
};
bool
FTPChannelChild::RecvFailedAsyncOpen(const nsresult& statusCode)
{
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPFailedAsyncOpenEvent(this, statusCode));
} else {
DoFailedAsyncOpen(statusCode);
}
return true;
}
void
FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode)
{
mStatus = statusCode;
if (mLoadGroup)
mLoadGroup->RemoveRequest(this, nullptr, statusCode);
if (mListener) {
mListener->OnStartRequest(this, mListenerContext);
mIsPending = false;
mListener->OnStopRequest(this, mListenerContext, statusCode);
} else {
mIsPending = false;
}
mListener = nullptr;
mListenerContext = nullptr;
if (mIPCOpen)
Send__delete__(this);
}
class FTPFlushedForDiversionEvent : public ChannelEvent
{
public:
explicit FTPFlushedForDiversionEvent(FTPChannelChild* aChild)
: mChild(aChild)
{
MOZ_RELEASE_ASSERT(aChild);
}
void Run()
{
mChild->FlushedForDiversion();
}
private:
FTPChannelChild* mChild;
};
bool
FTPChannelChild::RecvFlushedForDiversion()
{
MOZ_ASSERT(mDivertingToParent);
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPFlushedForDiversionEvent(this));
} else {
MOZ_CRASH();
}
return true;
}
void
FTPChannelChild::FlushedForDiversion()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
// Once this is set, it should not be unset before FTPChannelChild is taken
// down. After it is set, no OnStart/OnData/OnStop callbacks should be
// received from the parent channel, nor dequeued from the ChannelEventQueue.
mFlushedForDiversion = true;
SendDivertComplete();
}
bool
FTPChannelChild::RecvDivertMessages()
{
MOZ_RELEASE_ASSERT(mDivertingToParent);
MOZ_RELEASE_ASSERT(mSuspendCount > 0);
// DivertTo() has been called on parent, so we can now start sending queued
// IPDL messages back to parent listener.
if (NS_WARN_IF(NS_FAILED(Resume()))) {
return false;
}
return true;
}
class FTPDeleteSelfEvent : public ChannelEvent
{
public:
explicit FTPDeleteSelfEvent(FTPChannelChild* aChild)
: mChild(aChild) {}
void Run() { mChild->DoDeleteSelf(); }
private:
FTPChannelChild* mChild;
};
bool
FTPChannelChild::RecvDeleteSelf()
{
if (mEventQ->ShouldEnqueue()) {
mEventQ->Enqueue(new FTPDeleteSelfEvent(this));
} else {
DoDeleteSelf();
}
return true;
}
void
FTPChannelChild::DoDeleteSelf()
{
if (mIPCOpen)
Send__delete__(this);
}
NS_IMETHODIMP
FTPChannelChild::Cancel(nsresult status)
{
if (mCanceled)
return NS_OK;
mCanceled = true;
mStatus = status;
if (mIPCOpen)
SendCancel(status);
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::Suspend()
{
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
// SendSuspend only once, when suspend goes from 0 to 1.
// Don't SendSuspend at all if we're diverting callbacks to the parent;
// suspend will be called at the correct time in the parent itself.
if (!mSuspendCount++ && !mDivertingToParent) {
SendSuspend();
mSuspendSent = true;
}
mEventQ->Suspend();
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::Resume()
{
NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE);
// SendResume only once, when suspend count drops to 0.
// Don't SendResume at all if we're diverting callbacks to the parent (unless
// suspend was sent earlier); otherwise, resume will be called at the correct
// time in the parent itself.
if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) {
SendResume();
}
mEventQ->Resume();
return NS_OK;
}
//-----------------------------------------------------------------------------
// FTPChannelChild::nsIChildChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
FTPChannelChild::ConnectParent(uint32_t id)
{
mozilla::dom::TabChild* tabChild = nullptr;
nsCOMPtr<nsITabChild> iTabChild;
NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
NS_GET_IID(nsITabChild),
getter_AddRefs(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();
FTPChannelConnectArgs connectArgs(id);
if (!gNeckoChild->SendPFTPChannelConstructor(this, tabChild,
IPC::SerializedLoadContext(this),
connectArgs)) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
NS_IMETHODIMP
FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener,
nsISupports *aContext)
{
LOG(("FTPChannelChild::CompleteRedirectSetup [this=%p]\n", this));
NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS);
NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED);
mIsPending = true;
mWasOpened = true;
mListener = listener;
mListenerContext = aContext;
// add ourselves to the load group.
if (mLoadGroup)
mLoadGroup->AddRequest(this, nullptr);
// 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;
}
//-----------------------------------------------------------------------------
// FTPChannelChild::nsIDivertableChannel
//-----------------------------------------------------------------------------
NS_IMETHODIMP
FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild)
{
MOZ_RELEASE_ASSERT(aChild);
MOZ_RELEASE_ASSERT(gNeckoChild);
MOZ_RELEASE_ASSERT(!mDivertingToParent);
// We must fail DivertToParent() if there's no parent end of the channel (and
// won't be!) due to early failure.
if (NS_FAILED(mStatus) && !mIPCOpen) {
return mStatus;
}
nsresult rv = Suspend();
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
// Once this is set, it should not be unset before the child is taken down.
mDivertingToParent = true;
PChannelDiverterChild* diverter =
gNeckoChild->SendPChannelDiverterConstructor(this);
MOZ_RELEASE_ASSERT(diverter);
*aChild = static_cast<ChannelDiverterChild*>(diverter);
return NS_OK;
}
} // namespace net
} // namespace mozilla