/* -*- 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" #include "nsIPrompt.h" using namespace mozilla::ipc; #undef LOG #define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) namespace mozilla { namespace net { FTPChannelChild::FTPChannelChild(nsIURI* uri) : mIPCOpen(false) , mUnknownDecoderInvolved(false) , mCanceled(false) , mSuspendCount(0) , mIsPending(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(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() { MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference"); mIPCOpen = true; AddRef(); } void FTPChannelChild::ReleaseIPDLReference() { MOZ_ASSERT(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; } 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 iTabChild; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_IID(nsITabChild), getter_AddRefs(iTabChild)); GetCallback(iTabChild); if (iTabChild) { tabChild = static_cast(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 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 loadInfo; GetLoadInfo(getter_AddRefs(loadInfo)); rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo()); NS_ENSURE_SUCCESS(rv, rv); 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!"); LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this)); mEventQ->RunOrEnqueue(new FTPStartRequestEvent(this, 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::DoOnStartRequest [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 uri = DeserializeURI(aURI); nsresult rv = uri->GetSpec(spec); if (NS_SUCCEEDED(rv)) { rv = nsBaseChannel::URI()->SetSpec(spec); if (NS_FAILED(rv)) { Cancel(rv); } } else { Cancel(rv); } AutoEventEnqueuer ensureSerialDispatch(mEventQ); 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!"); LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this)); mEventQ->RunOrEnqueue(new FTPDataAvailableEvent(this, channelStatus, data, offset, count), mDivertingToParent); return true; } class MaybeDivertOnDataFTPEvent : public ChannelEvent { public: MaybeDivertOnDataFTPEvent(FTPChannelChild* child, const nsCString& data, const uint64_t& offset, const uint32_t& count) : mChild(child) , mData(data) , mOffset(offset) , mCount(count) {} void Run() { mChild->MaybeDivertOnData(mData, mOffset, mCount); } private: FTPChannelChild* mChild; nsCString mData; uint64_t mOffset; uint32_t mCount; }; void FTPChannelChild::MaybeDivertOnData(const nsCString& data, const uint64_t& offset, const uint32_t& count) { if (mDivertingToParent) { SendDivertOnDataAvailable(data, offset, count); } } void FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus, const nsCString& data, const uint64_t& offset, const uint32_t& count) { LOG(("FTPChannelChild::DoOnDataAvailable [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; if (mUnknownDecoderInvolved) { mUnknownDecoderEventQ.AppendElement( MakeUnique(this, data, offset, count)); } // 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 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, const nsCString &aErrorMsg, bool aUseUTF8) : mChild(aChild) , mChannelStatus(aChannelStatus) , mErrorMsg(aErrorMsg) , mUseUTF8(aUseUTF8) { } void Run() { mChild->DoOnStopRequest(mChannelStatus, mErrorMsg, mUseUTF8); } private: FTPChannelChild* mChild; nsresult mChannelStatus; nsCString mErrorMsg; bool mUseUTF8; }; bool FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus, const nsCString &aErrorMsg, const bool &aUseUTF8) { MOZ_RELEASE_ASSERT(!mFlushedForDiversion, "Should not be receiving any more callbacks from parent!"); LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%x]\n", this, aChannelStatus)); mEventQ->RunOrEnqueue(new FTPStopRequestEvent(this, aChannelStatus, aErrorMsg, aUseUTF8)); return true; } class MaybeDivertOnStopFTPEvent : public ChannelEvent { public: MaybeDivertOnStopFTPEvent(FTPChannelChild* child, const nsresult& aChannelStatus) : mChild(child) , mChannelStatus(aChannelStatus) {} void Run() { mChild->MaybeDivertOnStop(mChannelStatus); } private: FTPChannelChild* mChild; nsresult mChannelStatus; }; void FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus) { if (mDivertingToParent) { SendDivertOnStopRequest(aChannelStatus); } } void FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus, const nsCString &aErrorMsg, bool aUseUTF8) { LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%x]\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; if (mUnknownDecoderInvolved) { mUnknownDecoderEventQ.AppendElement( MakeUnique(this, 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); if (NS_FAILED(aChannelStatus) && !aErrorMsg.IsEmpty()) { NS_ERROR("FTP error on stop request."); } 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) { LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%x]\n", this, statusCode)); mEventQ->RunOrEnqueue(new FTPFailedAsyncOpenEvent(this, statusCode)); return true; } void FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode) { LOG(("FTPChannelChild::DoFailedAsyncOpen [this=%p status=%x]\n", this, 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() { LOG(("FTPChannelChild::RecvFlushedForDiversion [this=%p]\n", this)); MOZ_ASSERT(mDivertingToParent); mEventQ->RunOrEnqueue(new FTPFlushedForDiversionEvent(this), true); return true; } void FTPChannelChild::FlushedForDiversion() { LOG(("FTPChannelChild::FlushedForDiversion [this=%p]\n", this)); 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() { LOG(("FTPChannelChild::RecvDivertMessages [this=%p]\n", this)); 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() { mEventQ->RunOrEnqueue(new FTPDeleteSelfEvent(this)); return true; } void FTPChannelChild::DoDeleteSelf() { if (mIPCOpen) Send__delete__(this); } NS_IMETHODIMP FTPChannelChild::Cancel(nsresult status) { LOG(("FTPChannelChild::Cancel [this=%p]\n", this)); 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); LOG(("FTPChannelChild::Suspend [this=%p]\n", this)); // 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); LOG(("FTPChannelChild::Resume [this=%p]\n", this)); // 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) { LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this)); mozilla::dom::TabChild* tabChild = nullptr; nsCOMPtr iTabChild; NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, NS_GET_IID(nsITabChild), getter_AddRefs(iTabChild)); GetCallback(iTabChild); if (iTabChild) { tabChild = static_cast(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); LOG(("FTPChannelChild::DivertToParent [this=%p]\n", this)); // 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(diverter); return NS_OK; } NS_IMETHODIMP FTPChannelChild::UnknownDecoderInvolvedKeepData() { mUnknownDecoderInvolved = true; return NS_OK; } NS_IMETHODIMP FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled() { mUnknownDecoderInvolved = false; nsresult rv = NS_OK; if (mDivertingToParent) { rv = mEventQ->PrependEvents(mUnknownDecoderEventQ); } mUnknownDecoderEventQ.Clear(); return rv; } NS_IMETHODIMP FTPChannelChild::GetDivertingToParent(bool* aDiverting) { NS_ENSURE_ARG_POINTER(aDiverting); *aDiverting = mDivertingToParent; return NS_OK; } } // namespace net } // namespace mozilla