/* -*- 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) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Alon Zakai * Josh Matthews * * 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 "mozilla/net/NeckoChild.h" #include "mozilla/net/FTPChannelChild.h" #include "nsFtpProtocolHandler.h" #include "nsStringStream.h" #include "nsMimeTypes.h" #include "nsNetUtil.h" #include "nsIURIFixup.h" #include "nsCDefaultURIFixup.h" #undef LOG #define LOG(args) PR_LOG(gFTPLog, PR_LOG_DEBUG, args) namespace mozilla { namespace net { FTPChannelChild::FTPChannelChild(nsIURI* uri) : mIPCOpen(false) , mEventQ(this) , mCanceled(false) , mSuspendCount(0) , mIsPending(PR_FALSE) , mWasOpened(PR_FALSE) , mLastModifiedTime(0) , mStartPos(0) { 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); } 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_INHERITED5(FTPChannelChild, nsBaseChannel, nsIFTPChannel, nsIUploadChannel, nsIResumableChannel, nsIProxiedChannel, nsIChildChannel) //----------------------------------------------------------------------------- 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(PRUint64 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, PRInt32 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=%x]\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; // FIXME: like bug 558623, merge constructor+SendAsyncOpen into 1 IPC msg gNeckoChild->SendPFTPChannelConstructor(this); mListener = listener; mListenerContext = aContext; // add ourselves to the load group. if (mLoadGroup) mLoadGroup->AddRequest(this, nsnull); SendAsyncOpen(nsBaseChannel::URI(), mStartPos, mEntityID, IPC::InputStream(mUploadStream)); // The socket transport layer in the chrome process now has a logical ref to // us until OnStopRequest is called. AddIPDLReference(); mIsPending = PR_TRUE; mWasOpened = PR_TRUE; return rv; } NS_IMETHODIMP FTPChannelChild::IsPending(PRBool* result) { *result = mIsPending; return NS_OK; } nsresult FTPChannelChild::OpenContentStream(PRBool 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 PRInt32& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const IPC::URI& aURI) : mChild(aChild), mContentLength(aContentLength), mContentType(aContentType), mLastModified(aLastModified), mEntityID(aEntityID), mURI(aURI) {} void Run() { mChild->DoOnStartRequest(mContentLength, mContentType, mLastModified, mEntityID, mURI); } private: FTPChannelChild* mChild; PRInt32 mContentLength; nsCString mContentType; PRTime mLastModified; nsCString mEntityID; IPC::URI mURI; }; bool FTPChannelChild::RecvOnStartRequest(const PRInt32& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const IPC::URI& aURI) { if (mEventQ.ShouldEnqueue()) { mEventQ.Enqueue(new FTPStartRequestEvent(this, aContentLength, aContentType, aLastModified, aEntityID, aURI)); } else { DoOnStartRequest(aContentLength, aContentType, aLastModified, aEntityID, aURI); } return true; } void FTPChannelChild::DoOnStartRequest(const PRInt32& aContentLength, const nsCString& aContentType, const PRTime& aLastModified, const nsCString& aEntityID, const IPC::URI& aURI) { LOG(("FTPChannelChild::RecvOnStartRequest [this=%x]\n", this)); SetContentLength(aContentLength); SetContentType(aContentType); mLastModifiedTime = aLastModified; mEntityID = aEntityID; nsCString spec; nsCOMPtr uri(aURI); uri->GetSpec(spec); nsBaseChannel::URI()->SetSpec(spec); AutoEventEnqueuer ensureSerialDispatch(mEventQ); nsresult rv = mListener->OnStartRequest(this, mListenerContext); if (NS_FAILED(rv)) Cancel(rv); } class FTPDataAvailableEvent : public ChannelEvent { public: FTPDataAvailableEvent(FTPChannelChild* aChild, const nsCString& aData, const PRUint32& aOffset, const PRUint32& aCount) : mChild(aChild), mData(aData), mOffset(aOffset), mCount(aCount) {} void Run() { mChild->DoOnDataAvailable(mData, mOffset, mCount); } private: FTPChannelChild* mChild; nsCString mData; PRUint32 mOffset, mCount; }; bool FTPChannelChild::RecvOnDataAvailable(const nsCString& data, const PRUint32& offset, const PRUint32& count) { if (mEventQ.ShouldEnqueue()) { mEventQ.Enqueue(new FTPDataAvailableEvent(this, data, offset, count)); } else { DoOnDataAvailable(data, offset, count); } return true; } void FTPChannelChild::DoOnDataAvailable(const nsCString& data, const PRUint32& offset, const PRUint32& count) { LOG(("FTPChannelChild::RecvOnDataAvailable [this=%x]\n", this)); 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 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& aStatusCode) : mChild(aChild), mStatusCode(aStatusCode) {} void Run() { mChild->DoOnStopRequest(mStatusCode); } private: FTPChannelChild* mChild; nsresult mStatusCode; }; bool FTPChannelChild::RecvOnStopRequest(const nsresult& statusCode) { if (mEventQ.ShouldEnqueue()) { mEventQ.Enqueue(new FTPStopRequestEvent(this, statusCode)); } else { DoOnStopRequest(statusCode); } return true; } void FTPChannelChild::DoOnStopRequest(const nsresult& statusCode) { LOG(("FTPChannelChild::RecvOnStopRequest [this=%x status=%u]\n", this, statusCode)); if (!mCanceled) mStatus = statusCode; { // Ensure that all queued ipdl events are dispatched before // we initiate protocol deletion below. mIsPending = PR_FALSE; AutoEventEnqueuer ensureSerialDispatch(mEventQ); (void)mListener->OnStopRequest(this, mListenerContext, statusCode); mListener = nsnull; mListenerContext = nsnull; if (mLoadGroup) mLoadGroup->RemoveRequest(this, nsnull, statusCode); } // This calls NeckoChild::DeallocPFTPChannel(), which deletes |this| if IPDL // holds the last reference. Don't rely on |this| existing after here! Send__delete__(this); } class FTPCancelEarlyEvent : public ChannelEvent { public: FTPCancelEarlyEvent(FTPChannelChild* aChild, nsresult aStatus) : mChild(aChild), mStatus(aStatus) {} void Run() { mChild->DoCancelEarly(mStatus); } private: FTPChannelChild* mChild; nsresult mStatus; }; bool FTPChannelChild::RecvCancelEarly(const nsresult& statusCode) { if (mEventQ.ShouldEnqueue()) { mEventQ.Enqueue(new FTPCancelEarlyEvent(this, statusCode)); } else { DoCancelEarly(statusCode); } return true; } void FTPChannelChild::DoCancelEarly(const nsresult& statusCode) { if (mCanceled) return; mCanceled = true; mStatus = statusCode; mIsPending = PR_FALSE; if (mLoadGroup) mLoadGroup->RemoveRequest(this, nsnull, statusCode); if (mListener) { mListener->OnStartRequest(this, mListenerContext); mListener->OnStopRequest(this, mListenerContext, statusCode); } mListener = nsnull; mListenerContext = nsnull; if (mIPCOpen) Send__delete__(this); } class FTPDeleteSelfEvent : public ChannelEvent { public: 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); if (!mSuspendCount++) { SendSuspend(); mEventQ.Suspend(); } return NS_OK; } NS_IMETHODIMP FTPChannelChild::Resume() { NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE); if (!--mSuspendCount) { SendResume(); mEventQ.Resume(); // TODO: make this async: see HttpChannelChild::Resume } return NS_OK; } //----------------------------------------------------------------------------- // FTPChannelChild::nsIChildChannel //----------------------------------------------------------------------------- NS_IMETHODIMP FTPChannelChild::ConnectParent(PRUint32 id) { // 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->SendPFTPChannelConstructor(this)) return NS_ERROR_FAILURE; if (!SendConnectChannel(id)) return NS_ERROR_FAILURE; return NS_OK; } NS_IMETHODIMP FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener, nsISupports *aContext) { LOG(("FTPChannelChild::CompleteRedirectSetup [this=%x]\n", this)); NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); mIsPending = PR_TRUE; mWasOpened = PR_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; } } // namespace net } // namespace mozilla