michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set sw=2 ts=8 et tw=80 : */ michael@0: michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/net/FTPChannelParent.h" michael@0: #include "nsFTPChannel.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsFtpProtocolHandler.h" michael@0: #include "mozilla/ipc/InputStreamUtils.h" michael@0: #include "mozilla/ipc/URIUtils.h" michael@0: #include "mozilla/unused.h" michael@0: #include "SerializedLoadContext.h" michael@0: michael@0: using namespace mozilla::ipc; michael@0: michael@0: #undef LOG michael@0: #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: FTPChannelParent::FTPChannelParent(nsILoadContext* aLoadContext, PBOverrideStatus aOverrideStatus) michael@0: : mIPCClosed(false) michael@0: , mLoadContext(aLoadContext) michael@0: , mPBOverride(aOverrideStatus) michael@0: , mStatus(NS_OK) michael@0: , mDivertingFromChild(false) michael@0: , mDivertedOnStartRequest(false) michael@0: , mSuspendedForDiversion(false) michael@0: { michael@0: nsIProtocolHandler* handler; michael@0: CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler); michael@0: NS_ASSERTION(handler, "no ftp handler"); michael@0: } michael@0: michael@0: FTPChannelParent::~FTPChannelParent() michael@0: { michael@0: gFtpHandler->Release(); michael@0: } michael@0: michael@0: void michael@0: FTPChannelParent::ActorDestroy(ActorDestroyReason why) michael@0: { michael@0: // We may still have refcount>0 if the channel hasn't called OnStopRequest michael@0: // yet, but we must not send any more msgs to child. michael@0: mIPCClosed = true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ISUPPORTS(FTPChannelParent, michael@0: nsIStreamListener, michael@0: nsIParentChannel, michael@0: nsIInterfaceRequestor, michael@0: nsIRequestObserver) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::PFTPChannelParent michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent methods michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: bool michael@0: FTPChannelParent::Init(const FTPChannelCreationArgs& aArgs) michael@0: { michael@0: switch (aArgs.type()) { michael@0: case FTPChannelCreationArgs::TFTPChannelOpenArgs: michael@0: { michael@0: const FTPChannelOpenArgs& a = aArgs.get_FTPChannelOpenArgs(); michael@0: return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream()); michael@0: } michael@0: case FTPChannelCreationArgs::TFTPChannelConnectArgs: michael@0: { michael@0: const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs(); michael@0: return ConnectChannel(cArgs.channelId()); michael@0: } michael@0: default: michael@0: NS_NOTREACHED("unknown open type"); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::DoAsyncOpen(const URIParams& aURI, michael@0: const uint64_t& aStartPos, michael@0: const nsCString& aEntityID, michael@0: const OptionalInputStreamParams& aUploadStream) michael@0: { michael@0: nsCOMPtr uri = DeserializeURI(aURI); michael@0: if (!uri) michael@0: return false; michael@0: michael@0: #ifdef DEBUG michael@0: nsCString uriSpec; michael@0: uri->GetSpec(uriSpec); michael@0: LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n", michael@0: this, uriSpec.get())); michael@0: #endif michael@0: michael@0: nsresult rv; michael@0: nsCOMPtr ios(do_GetIOService(&rv)); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: michael@0: nsCOMPtr chan; michael@0: rv = NS_NewChannel(getter_AddRefs(chan), uri, ios); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: michael@0: mChannel = static_cast(chan.get()); michael@0: michael@0: if (mPBOverride != kPBOverride_Unset) { michael@0: mChannel->SetPrivate(mPBOverride == kPBOverride_Private ? true : false); michael@0: } michael@0: michael@0: rv = mChannel->SetNotificationCallbacks(this); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: michael@0: nsTArray fds; michael@0: nsCOMPtr upload = DeserializeInputStream(aUploadStream, fds); michael@0: if (upload) { michael@0: // contentType and contentLength are ignored michael@0: rv = mChannel->SetUploadStream(upload, EmptyCString(), 0); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: } michael@0: michael@0: rv = mChannel->ResumeAt(aStartPos, aEntityID); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: michael@0: rv = mChannel->AsyncOpen(this, nullptr); michael@0: if (NS_FAILED(rv)) michael@0: return SendFailedAsyncOpen(rv); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::ConnectChannel(const uint32_t& channelId) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId)); michael@0: michael@0: nsCOMPtr channel; michael@0: rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel)); michael@0: if (NS_SUCCEEDED(rv)) michael@0: mChannel = static_cast(channel.get()); michael@0: michael@0: LOG((" found channel %p, rv=%08x", mChannel.get(), rv)); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvCancel(const nsresult& status) michael@0: { michael@0: if (mChannel) michael@0: mChannel->Cancel(status); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvSuspend() michael@0: { michael@0: if (mChannel) michael@0: mChannel->Suspend(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvResume() michael@0: { michael@0: if (mChannel) michael@0: mChannel->Resume(); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data, michael@0: const uint64_t& offset, michael@0: const uint32_t& count) michael@0: { michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot RecvDivertOnDataAvailable if diverting is not set!"); michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return false; michael@0: } michael@0: michael@0: // Drop OnDataAvailables if the parent was canceled already. michael@0: if (NS_FAILED(mStatus)) { michael@0: return true; michael@0: } michael@0: michael@0: nsCOMPtr stringStream; michael@0: nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(), michael@0: count, NS_ASSIGNMENT_DEPEND); michael@0: if (NS_FAILED(rv)) { michael@0: if (mChannel) { michael@0: mChannel->Cancel(rv); michael@0: } michael@0: mStatus = rv; michael@0: return true; michael@0: } michael@0: michael@0: rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count); michael@0: michael@0: stringStream->Close(); michael@0: if (NS_FAILED(rv)) { michael@0: if (mChannel) { michael@0: mChannel->Cancel(rv); michael@0: } michael@0: mStatus = rv; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode) michael@0: { michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot RecvDivertOnStopRequest if diverting is not set!"); michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return false; michael@0: } michael@0: michael@0: // Honor the channel's status even if the underlying transaction completed. michael@0: nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode; michael@0: michael@0: // Reset fake pending status in case OnStopRequest has already been called. michael@0: if (mChannel) { michael@0: mChannel->ForcePending(false); michael@0: } michael@0: michael@0: OnStopRequest(mChannel, nullptr, status); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: FTPChannelParent::RecvDivertComplete() michael@0: { michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot RecvDivertComplete if diverting is not set!"); michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return false; michael@0: } michael@0: michael@0: nsresult rv = ResumeForDiversion(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::nsIRequestObserver michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) michael@0: { michael@0: LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this)); michael@0: michael@0: if (mDivertingFromChild) { michael@0: MOZ_RELEASE_ASSERT(mDivertToListener, michael@0: "Cannot divert if listener is unset!"); michael@0: return mDivertToListener->OnStartRequest(aRequest, aContext); michael@0: } michael@0: michael@0: nsCOMPtr chan = do_QueryInterface(aRequest); michael@0: MOZ_ASSERT(chan); michael@0: NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); michael@0: michael@0: int64_t contentLength; michael@0: chan->GetContentLength(&contentLength); michael@0: nsCString contentType; michael@0: chan->GetContentType(contentType); michael@0: michael@0: nsCString entityID; michael@0: nsCOMPtr resChan = do_QueryInterface(aRequest); michael@0: MOZ_ASSERT(resChan); // both FTP and HTTP should implement nsIResumableChannel michael@0: if (resChan) { michael@0: resChan->GetEntityID(entityID); michael@0: } michael@0: michael@0: nsCOMPtr ftpChan = do_QueryInterface(aRequest); michael@0: PRTime lastModified = 0; michael@0: if (ftpChan) { michael@0: ftpChan->GetLastModifiedTime(&lastModified); michael@0: } else { michael@0: // Temporary hack: if we were redirected to use an HTTP channel (ie FTP is michael@0: // using an HTTP proxy), cancel, as we don't support those redirects yet. michael@0: aRequest->Cancel(NS_ERROR_NOT_IMPLEMENTED); michael@0: } michael@0: michael@0: URIParams uriparam; michael@0: nsCOMPtr uri; michael@0: chan->GetURI(getter_AddRefs(uri)); michael@0: SerializeURI(uri, uriparam); michael@0: michael@0: if (mIPCClosed || !SendOnStartRequest(mStatus, contentLength, contentType, michael@0: lastModified, entityID, uriparam)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::OnStopRequest(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsresult aStatusCode) michael@0: { michael@0: LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n", michael@0: this, aStatusCode)); michael@0: michael@0: if (mDivertingFromChild) { michael@0: MOZ_RELEASE_ASSERT(mDivertToListener, michael@0: "Cannot divert if listener is unset!"); michael@0: return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode); michael@0: } michael@0: michael@0: if (mIPCClosed || !SendOnStopRequest(aStatusCode)) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::nsIStreamListener michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::OnDataAvailable(nsIRequest* aRequest, michael@0: nsISupports* aContext, michael@0: nsIInputStream* aInputStream, michael@0: uint64_t aOffset, michael@0: uint32_t aCount) michael@0: { michael@0: LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this)); michael@0: michael@0: if (mDivertingFromChild) { michael@0: MOZ_RELEASE_ASSERT(mDivertToListener, michael@0: "Cannot divert if listener is unset!"); michael@0: return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream, michael@0: aOffset, aCount); michael@0: } michael@0: michael@0: nsCString data; michael@0: nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mIPCClosed || !SendOnDataAvailable(mStatus, data, aOffset, aCount)) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::nsIParentChannel michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::SetParentListener(HttpChannelParentListener* aListener) michael@0: { michael@0: // Do not need ptr to HttpChannelParentListener. michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::Delete() michael@0: { michael@0: if (mIPCClosed || !SendDeleteSelf()) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::nsIInterfaceRequestor michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMETHODIMP michael@0: FTPChannelParent::GetInterface(const nsIID& uuid, void** result) michael@0: { michael@0: // Only support nsILoadContext if child channel's callbacks did too michael@0: if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) { michael@0: NS_ADDREF(mLoadContext); michael@0: *result = static_cast(mLoadContext); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return QueryInterface(uuid, result); michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // FTPChannelParent::ADivertableParentChannel michael@0: //----------------------------------------------------------------------------- michael@0: nsresult michael@0: FTPChannelParent::SuspendForDiversion() michael@0: { michael@0: MOZ_ASSERT(mChannel); michael@0: if (NS_WARN_IF(mDivertingFromChild)) { michael@0: MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Try suspending the channel. Allow it to fail, since OnStopRequest may have michael@0: // been called and thus the channel may not be pending. michael@0: nsresult rv = mChannel->Suspend(); michael@0: MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE); michael@0: mSuspendedForDiversion = NS_SUCCEEDED(rv); michael@0: michael@0: // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent michael@0: // to the child. michael@0: mDivertingFromChild = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* private, supporting function for ADivertableParentChannel */ michael@0: nsresult michael@0: FTPChannelParent::ResumeForDiversion() michael@0: { michael@0: MOZ_ASSERT(mChannel); michael@0: MOZ_ASSERT(mDivertToListener); michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot ResumeForDiversion if not diverting!"); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mSuspendedForDiversion) { michael@0: nsresult rv = mChannel->Resume(); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: FailDiversion(NS_ERROR_UNEXPECTED, true); michael@0: return rv; michael@0: } michael@0: mSuspendedForDiversion = false; michael@0: } michael@0: michael@0: // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will michael@0: // keep us alive if there's more data to be delivered to listener. michael@0: if (NS_WARN_IF(NS_FAILED(Delete()))) { michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: FTPChannelParent::DivertTo(nsIStreamListener *aListener) michael@0: { michael@0: MOZ_ASSERT(aListener); michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot DivertTo new listener if diverting is not set!"); michael@0: return; michael@0: } michael@0: michael@0: if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) { michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: michael@0: mDivertToListener = aListener; michael@0: michael@0: // Call OnStartRequest and SendDivertMessages asynchronously to avoid michael@0: // reentering client context. michael@0: NS_DispatchToCurrentThread( michael@0: NS_NewRunnableMethod(this, &FTPChannelParent::StartDiversion)); michael@0: return; michael@0: } michael@0: michael@0: void michael@0: FTPChannelParent::StartDiversion() michael@0: { michael@0: if (NS_WARN_IF(!mDivertingFromChild)) { michael@0: MOZ_ASSERT(mDivertingFromChild, michael@0: "Cannot StartDiversion if diverting is not set!"); michael@0: return; michael@0: } michael@0: michael@0: // Fake pending status in case OnStopRequest has already been called. michael@0: if (mChannel) { michael@0: mChannel->ForcePending(true); michael@0: } michael@0: michael@0: // Call OnStartRequest for the "DivertTo" listener. michael@0: nsresult rv = OnStartRequest(mChannel, nullptr); michael@0: if (NS_FAILED(rv)) { michael@0: if (mChannel) { michael@0: mChannel->Cancel(rv); michael@0: } michael@0: mStatus = rv; michael@0: return; michael@0: } michael@0: michael@0: // After OnStartRequest has been called, tell FTPChannelChild to divert the michael@0: // OnDataAvailables and OnStopRequest to this FTPChannelParent. michael@0: if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) { michael@0: FailDiversion(NS_ERROR_UNEXPECTED); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: class FTPFailDiversionEvent : public nsRunnable michael@0: { michael@0: public: michael@0: FTPFailDiversionEvent(FTPChannelParent *aChannelParent, michael@0: nsresult aErrorCode, michael@0: bool aSkipResume) michael@0: : mChannelParent(aChannelParent) michael@0: , mErrorCode(aErrorCode) michael@0: , mSkipResume(aSkipResume) michael@0: { michael@0: MOZ_RELEASE_ASSERT(aChannelParent); michael@0: MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); michael@0: } michael@0: NS_IMETHOD Run() michael@0: { michael@0: mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume); michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsRefPtr mChannelParent; michael@0: nsresult mErrorCode; michael@0: bool mSkipResume; michael@0: }; michael@0: michael@0: void michael@0: FTPChannelParent::FailDiversion(nsresult aErrorCode, michael@0: bool aSkipResume) michael@0: { michael@0: MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); michael@0: MOZ_RELEASE_ASSERT(mDivertingFromChild); michael@0: MOZ_RELEASE_ASSERT(mDivertToListener); michael@0: MOZ_RELEASE_ASSERT(mChannel); michael@0: michael@0: NS_DispatchToCurrentThread( michael@0: new FTPFailDiversionEvent(this, aErrorCode, aSkipResume)); michael@0: } michael@0: michael@0: void michael@0: FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode, michael@0: bool aSkipResume) michael@0: { michael@0: MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); michael@0: MOZ_RELEASE_ASSERT(mDivertingFromChild); michael@0: MOZ_RELEASE_ASSERT(mDivertToListener); michael@0: MOZ_RELEASE_ASSERT(mChannel); michael@0: michael@0: mChannel->Cancel(aErrorCode); michael@0: michael@0: mChannel->ForcePending(false); michael@0: michael@0: bool isPending = false; michael@0: nsresult rv = mChannel->IsPending(&isPending); michael@0: MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); michael@0: michael@0: // Resume only we suspended earlier. michael@0: if (mSuspendedForDiversion) { michael@0: mChannel->Resume(); michael@0: } michael@0: // Channel has already sent OnStartRequest to the child, so ensure that we michael@0: // call it here if it hasn't already been called. michael@0: if (!mDivertedOnStartRequest) { michael@0: mChannel->ForcePending(true); michael@0: mDivertToListener->OnStartRequest(mChannel, nullptr); michael@0: mChannel->ForcePending(false); michael@0: } michael@0: // If the channel is pending, it will call OnStopRequest itself; otherwise, do michael@0: // it here. michael@0: if (!isPending) { michael@0: mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode); michael@0: } michael@0: mDivertToListener = nullptr; michael@0: mChannel = nullptr; michael@0: michael@0: if (!mIPCClosed) { michael@0: unused << SendDeleteSelf(); michael@0: } michael@0: } michael@0: michael@0: //--------------------- michael@0: } // namespace net michael@0: } // namespace mozilla michael@0: