From 2aa4a82499d4becd2284cdb482213d541b8804dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 16:29:10 +0200 Subject: Adding upstream version 86.0.1. Signed-off-by: Daniel Baumann --- netwerk/protocol/ftp/FTPChannelChild.cpp | 541 ++++++ netwerk/protocol/ftp/FTPChannelChild.h | 137 ++ netwerk/protocol/ftp/FTPChannelParent.cpp | 418 +++++ netwerk/protocol/ftp/FTPChannelParent.h | 92 + netwerk/protocol/ftp/PFTPChannel.ipdl | 54 + netwerk/protocol/ftp/doc/testdoc | 4 + netwerk/protocol/ftp/ftpCore.h | 15 + netwerk/protocol/ftp/moz.build | 50 + netwerk/protocol/ftp/nsFTPChannel.cpp | 213 +++ netwerk/protocol/ftp/nsFTPChannel.h | 115 ++ netwerk/protocol/ftp/nsFtpConnectionThread.cpp | 1960 ++++++++++++++++++++ netwerk/protocol/ftp/nsFtpConnectionThread.h | 254 +++ netwerk/protocol/ftp/nsFtpControlConnection.cpp | 169 ++ netwerk/protocol/ftp/nsFtpControlConnection.h | 83 + netwerk/protocol/ftp/nsFtpProtocolHandler.cpp | 331 ++++ netwerk/protocol/ftp/nsFtpProtocolHandler.h | 85 + netwerk/protocol/ftp/nsIFTPChannel.idl | 29 + .../protocol/ftp/nsIFTPChannelParentInternal.idl | 15 + netwerk/protocol/ftp/test/frametest/contents.html | 5 + netwerk/protocol/ftp/test/frametest/index.html | 12 + netwerk/protocol/ftp/test/frametest/menu.html | 371 ++++ 21 files changed, 4953 insertions(+) create mode 100644 netwerk/protocol/ftp/FTPChannelChild.cpp create mode 100644 netwerk/protocol/ftp/FTPChannelChild.h create mode 100644 netwerk/protocol/ftp/FTPChannelParent.cpp create mode 100644 netwerk/protocol/ftp/FTPChannelParent.h create mode 100644 netwerk/protocol/ftp/PFTPChannel.ipdl create mode 100644 netwerk/protocol/ftp/doc/testdoc create mode 100644 netwerk/protocol/ftp/ftpCore.h create mode 100644 netwerk/protocol/ftp/moz.build create mode 100644 netwerk/protocol/ftp/nsFTPChannel.cpp create mode 100644 netwerk/protocol/ftp/nsFTPChannel.h create mode 100644 netwerk/protocol/ftp/nsFtpConnectionThread.cpp create mode 100644 netwerk/protocol/ftp/nsFtpConnectionThread.h create mode 100644 netwerk/protocol/ftp/nsFtpControlConnection.cpp create mode 100644 netwerk/protocol/ftp/nsFtpControlConnection.h create mode 100644 netwerk/protocol/ftp/nsFtpProtocolHandler.cpp create mode 100644 netwerk/protocol/ftp/nsFtpProtocolHandler.h create mode 100644 netwerk/protocol/ftp/nsIFTPChannel.idl create mode 100644 netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl create mode 100644 netwerk/protocol/ftp/test/frametest/contents.html create mode 100644 netwerk/protocol/ftp/test/frametest/index.html create mode 100644 netwerk/protocol/ftp/test/frametest/menu.html (limited to 'netwerk/protocol/ftp') diff --git a/netwerk/protocol/ftp/FTPChannelChild.cpp b/netwerk/protocol/ftp/FTPChannelChild.cpp new file mode 100644 index 0000000000..63edaff4cd --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelChild.cpp @@ -0,0 +1,541 @@ +/* -*- 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/FTPChannelChild.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/BrowserChild.h" +#include "nsContentUtils.h" +#include "nsFtpProtocolHandler.h" +#include "nsIBrowserChild.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "base/compiler_specific.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "nsIURIMutator.h" +#include "nsContentSecurityManager.h" +#include "mozilla/ScopeExit.h" + +using mozilla::dom::ContentChild; +using namespace mozilla::ipc; + +#undef LOG +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +FTPChannelChild::FTPChannelChild(nsIURI* aUri) + : mIPCOpen(false), + mEventQ(new ChannelEventQueue(static_cast(this))), + mCanceled(false), + mSuspendCount(0), + mIsPending(false), + mLastModifiedTime(0), + mStartPos(0), + mSuspendSent(false) { + LOG(("Creating FTPChannelChild @%p\n", this)); + // grab a reference to the handler to ensure that it doesn't go away. + NS_ADDREF(gFtpHandler); + SetURI(aUri); + + // 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 @%p\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) + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelChild::GetLastModifiedTime(PRTime* aLastModifiedTime) { + *aLastModifiedTime = mLastModifiedTime; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::SetLastModifiedTime(PRTime aLastModifiedTime) { + 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& aEntityID) { + aEntityID = mEntityID; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) { DROP_DEAD(); } + +NS_IMETHODIMP FTPChannelChild::GetHttpProxyConnectResponseCode( + int32_t* aResponseCode) { + DROP_DEAD(); +} + +NS_IMETHODIMP +FTPChannelChild::SetUploadStream(nsIInputStream* aStream, + const nsACString& aContentType, + int64_t aContentLength) { + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + mUploadStream = aStream; + // NOTE: contentLength is intentionally ignored here. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::GetUploadStream(nsIInputStream** aStream) { + NS_ENSURE_ARG_POINTER(aStream); + *aStream = mUploadStream; + NS_IF_ADDREF(*aStream); + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::AsyncOpen(nsIStreamListener* aListener) { + nsCOMPtr listener = aListener; + nsresult rv = + nsContentSecurityManager::doContentSecurityCheck(this, listener); + NS_ENSURE_SUCCESS(rv, rv); + + LOG(("FTPChannelChild::AsyncOpen [this=%p]\n", this)); + + NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE); + NS_ENSURE_TRUE( + !static_cast(gNeckoChild->Manager())->IsShuttingDown(), + 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. + 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::BrowserChild* browserChild = nullptr; + nsCOMPtr iBrowserChild; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsIBrowserChild), + getter_AddRefs(iBrowserChild)); + GetCallback(iBrowserChild); + if (iBrowserChild) { + browserChild = + static_cast(iBrowserChild.get()); + } + + mListener = listener; + + // add ourselves to the load group. + if (mLoadGroup) mLoadGroup->AddRequest(this, nullptr); + + mozilla::ipc::AutoIPCStream autoStream; + autoStream.Serialize(mUploadStream, + static_cast(gNeckoChild->Manager())); + + uint32_t loadFlags = 0; + GetLoadFlags(&loadFlags); + + FTPChannelOpenArgs openArgs; + SerializeURI(nsBaseChannel::URI(), openArgs.uri()); + openArgs.startPos() = mStartPos; + openArgs.entityID() = mEntityID; + openArgs.uploadStream() = autoStream.TakeOptionalValue(); + openArgs.loadFlags() = loadFlags; + + nsCOMPtr loadInfo = LoadInfo(); + rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo()); + NS_ENSURE_SUCCESS(rv, rv); + + // This must happen before the constructor message is sent. + SetupNeckoTarget(); + + gNeckoChild->SendPFTPChannelConstructor( + this, browserChild, 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* aResult) { + *aResult = mIsPending; + return NS_OK; +} + +nsresult FTPChannelChild::OpenContentStream(bool aAsync, + nsIInputStream** aStream, + nsIChannel** aChannel) { + MOZ_CRASH("FTPChannel*Child* should never have OpenContentStream called!"); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::PFTPChannelChild +//----------------------------------------------------------------------------- + +mozilla::ipc::IPCResult FTPChannelChild::RecvOnStartRequest( + 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)); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr(this), aChannelStatus, + aContentLength, aContentType, aLastModified, aEntityID, aURI]() { + self->DoOnStartRequest(aChannelStatus, aContentLength, aContentType, + aLastModified, aEntityID, aURI); + })); + return IPC_OK(); +} + +void FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) { + mDuringOnStart = true; + RefPtr self = this; + auto clearDuringFlag = + mozilla::MakeScopeExit([self] { self->mDuringOnStart = false; }); + + LOG(("FTPChannelChild::DoOnStartRequest [this=%p]\n", this)); + + 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)) { + // Changes nsBaseChannel::URI() + rv = NS_MutateURI(mURI).SetSpec(spec).Finalize(mURI); + if (NS_FAILED(rv)) { + Cancel(rv); + } + } else { + Cancel(rv); + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + rv = mListener->OnStartRequest(this); + if (NS_FAILED(rv)) Cancel(rv); +} + +mozilla::ipc::IPCResult FTPChannelChild::RecvOnDataAvailable( + const nsresult& aChannelStatus, const nsCString& aData, + const uint64_t& aOffset, const uint32_t& aCount) { + LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this)); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr(this), aChannelStatus, aData, + aOffset, aCount]() { + self->DoOnDataAvailable(aChannelStatus, aData, aOffset, aCount); + })); + + return IPC_OK(); +} + +void FTPChannelChild::DoOnDataAvailable(const nsresult& aChannelStatus, + const nsCString& aData, + const uint64_t& aOffset, + const uint32_t& aCount) { + LOG(("FTPChannelChild::DoOnDataAvailable [this=%p]\n", this)); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aChannelStatus; + } + + 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), + Span(aData).To(aCount), NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) { + Cancel(rv); + return; + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + rv = mListener->OnDataAvailable(this, stringStream, aOffset, aCount); + if (NS_FAILED(rv)) { + Cancel(rv); + } + stringStream->Close(); +} + +mozilla::ipc::IPCResult FTPChannelChild::RecvOnStopRequest( + const nsresult& aChannelStatus, const nsCString& aErrorMsg, + const bool& aUseUTF8) { + LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%" PRIx32 "]\n", + this, static_cast(aChannelStatus))); + + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr(this), aChannelStatus, aErrorMsg, + aUseUTF8]() { + self->DoOnStopRequest(aChannelStatus, aErrorMsg, aUseUTF8); + })); + return IPC_OK(); +} + +void FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus, + const nsCString& aErrorMsg, + bool aUseUTF8) { + LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%" PRIx32 "]\n", this, + static_cast(aChannelStatus))); + + 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, aChannelStatus); + + mListener = 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); +} + +mozilla::ipc::IPCResult FTPChannelChild::RecvFailedAsyncOpen( + const nsresult& aStatusCode) { + LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%" PRIx32 "]\n", + this, static_cast(aStatusCode))); + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, [self = UnsafePtr(this), aStatusCode]() { + self->DoFailedAsyncOpen(aStatusCode); + })); + return IPC_OK(); +} + +void FTPChannelChild::DoFailedAsyncOpen(const nsresult& aStatusCode) { + LOG(("FTPChannelChild::DoFailedAsyncOpen [this=%p status=%" PRIx32 "]\n", + this, static_cast(aStatusCode))); + mStatus = aStatusCode; + + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, aStatusCode); + } + + if (mListener) { + mListener->OnStartRequest(this); + mIsPending = false; + mListener->OnStopRequest(this, aStatusCode); + } else { + mIsPending = false; + } + + mListener = nullptr; + + if (mIPCOpen) { + Send__delete__(this); + } +} + +mozilla::ipc::IPCResult FTPChannelChild::RecvDeleteSelf() { + mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent( + this, + [self = UnsafePtr(this)]() { self->DoDeleteSelf(); })); + return IPC_OK(); +} + +void FTPChannelChild::DoDeleteSelf() { + if (mIPCOpen) { + Send__delete__(this); + } +} + +NS_IMETHODIMP +FTPChannelChild::Cancel(nsresult aStatus) { + LOG(("FTPChannelChild::Cancel [this=%p]\n", this)); + if (mCanceled) { + return NS_OK; + } + + mCanceled = true; + mStatus = aStatus; + if (mIPCOpen) { + SendCancel(aStatus); + } + 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. + if (!mSuspendCount++) { + 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. + if (!--mSuspendCount && mSuspendSent) { + SendResume(); + } + mEventQ->Resume(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::nsIChildChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelChild::ConnectParent(uint32_t aId) { + NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE); + NS_ENSURE_TRUE( + !static_cast(gNeckoChild->Manager())->IsShuttingDown(), + NS_ERROR_FAILURE); + + LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this)); + + mozilla::dom::BrowserChild* browserChild = nullptr; + nsCOMPtr iBrowserChild; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsIBrowserChild), + getter_AddRefs(iBrowserChild)); + GetCallback(iBrowserChild); + if (iBrowserChild) { + browserChild = + static_cast(iBrowserChild.get()); + } + + // This must happen before the constructor message is sent. + SetupNeckoTarget(); + + // 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(aId); + + if (!gNeckoChild->SendPFTPChannelConstructor( + this, browserChild, IPC::SerializedLoadContext(this), connectArgs)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::CompleteRedirectSetup(nsIStreamListener* aListener) { + 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 = aListener; + + // 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; +} + +void FTPChannelChild::SetupNeckoTarget() { + if (mNeckoTarget) { + return; + } + nsCOMPtr loadInfo = LoadInfo(); + mNeckoTarget = + nsContentUtils::GetEventTargetByLoadInfo(loadInfo, TaskCategory::Network); + if (!mNeckoTarget) { + return; + } + + gNeckoChild->SetEventTargetForActor(this, mNeckoTarget); +} + +} // namespace net +} // namespace mozilla diff --git a/netwerk/protocol/ftp/FTPChannelChild.h b/netwerk/protocol/ftp/FTPChannelChild.h new file mode 100644 index 0000000000..e9c7dc9f6f --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelChild.h @@ -0,0 +1,137 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_FTPChannelChild_h +#define mozilla_net_FTPChannelChild_h + +#include "mozilla/UniquePtr.h" +#include "mozilla/net/PFTPChannelChild.h" +#include "mozilla/net/ChannelEventQueue.h" +#include "nsBaseChannel.h" +#include "nsIFTPChannel.h" +#include "nsIUploadChannel.h" +#include "nsIProxiedChannel.h" +#include "nsIResumableChannel.h" +#include "nsIChildChannel.h" +#include "nsIEventTarget.h" + +#include "nsIStreamListener.h" +#include "mozilla/net/PrivateBrowsingChannel.h" + +class nsIEventTarget; + +namespace mozilla { + +namespace net { + +// This class inherits logic from nsBaseChannel that is not needed for an +// e10s child channel, but it works. At some point we could slice up +// nsBaseChannel and have a new class that has only the common logic for +// nsFTPChannel/FTPChannelChild. + +class FTPChannelChild final : public PFTPChannelChild, + public nsBaseChannel, + public nsIFTPChannel, + public nsIUploadChannel, + public nsIResumableChannel, + public nsIProxiedChannel, + public nsIChildChannel { + public: + typedef ::nsIStreamListener nsIStreamListener; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFTPCHANNEL + NS_DECL_NSIUPLOADCHANNEL + NS_DECL_NSIRESUMABLECHANNEL + NS_DECL_NSIPROXIEDCHANNEL + NS_DECL_NSICHILDCHANNEL + + NS_IMETHOD Cancel(nsresult aStatus) override; + NS_IMETHOD Suspend() override; + NS_IMETHOD Resume() override; + + explicit FTPChannelChild(nsIURI* aUri); + + void AddIPDLReference(); + void ReleaseIPDLReference(); + + NS_IMETHOD AsyncOpen(nsIStreamListener* aListener) override; + + // Note that we handle this ourselves, overriding the nsBaseChannel + // default behavior, in order to be e10s-friendly. + NS_IMETHOD IsPending(bool* aResult) override; + + nsresult OpenContentStream(bool aAsync, nsIInputStream** aStream, + nsIChannel** aChannel) override; + + bool IsSuspended() const; + + protected: + virtual ~FTPChannelChild(); + + mozilla::ipc::IPCResult RecvOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) override; + mozilla::ipc::IPCResult RecvOnDataAvailable(const nsresult& aChannelStatus, + const nsCString& aData, + const uint64_t& aOffset, + const uint32_t& aCount) override; + mozilla::ipc::IPCResult RecvOnStopRequest(const nsresult& aChannelStatus, + const nsCString& aErrorMsg, + const bool& aUseUTF8) override; + mozilla::ipc::IPCResult RecvFailedAsyncOpen( + const nsresult& aStatusCode) override; + mozilla::ipc::IPCResult RecvDeleteSelf() override; + + void DoOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, const nsCString& aEntityID, + const URIParams& aURI); + void DoOnDataAvailable(const nsresult& aChannelStatus, const nsCString& aData, + const uint64_t& aOffset, const uint32_t& aCount); + void DoOnStopRequest(const nsresult& StatusCode, const nsCString& aErrorMsg, + bool aUseUTF8); + void DoFailedAsyncOpen(const nsresult& aStatusCode); + void DoDeleteSelf(); + + void SetupNeckoTarget() override; + + friend class NeckoTargetChannelFunctionEvent; + + private: + nsCOMPtr mUploadStream; + + bool mIPCOpen; + const RefPtr mEventQ; + + bool mCanceled; + uint32_t mSuspendCount; + bool mIsPending; + + // This will only be true while DoOnStartRequest is in progress. + // It is used to enforce that DivertToParent is only called during that time. + bool mDuringOnStart = false; + + PRTime mLastModifiedTime; + uint64_t mStartPos; + nsCString mEntityID; + + // Set if SendSuspend is called. Determines if SendResume is needed when + // diverting callbacks to parent. + bool mSuspendSent; +}; + +inline bool FTPChannelChild::IsSuspended() const { return mSuspendCount != 0; } + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_FTPChannelChild_h diff --git a/netwerk/protocol/ftp/FTPChannelParent.cpp b/netwerk/protocol/ftp/FTPChannelParent.cpp new file mode 100644 index 0000000000..3bf94fea70 --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -0,0 +1,418 @@ +/* -*- 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/FTPChannelParent.h" +#include "nsStringStream.h" +#include "mozilla/net/ChannelEventQueue.h" +#include "mozilla/dom/BrowserParent.h" +#include "nsFTPChannel.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsFtpProtocolHandler.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPromptProvider.h" +#include "nsIHttpChannelInternal.h" +#include "nsISecureBrowserUI.h" +#include "nsIForcePendingChannel.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Unused.h" +#include "SerializedLoadContext.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/dom/ContentParent.h" + +using namespace mozilla::dom; +using namespace mozilla::ipc; + +#undef LOG +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +FTPChannelParent::FTPChannelParent(dom::BrowserParent* aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus) + : mIPCClosed(false), + mLoadContext(aLoadContext), + mPBOverride(aOverrideStatus), + mStatus(NS_OK), + mBrowserParent(aIframeEmbedding), + mUseUTF8(false) { + nsIProtocolHandler* handler; + CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler); + MOZ_ASSERT(handler, "no ftp handler"); + + mEventQ = new ChannelEventQueue(static_cast(this)); +} + +FTPChannelParent::~FTPChannelParent() { gFtpHandler->Release(); } + +void FTPChannelParent::ActorDestroy(ActorDestroyReason why) { + // We may still have refcount>0 if the channel hasn't called OnStopRequest + // yet, but we must not send any more msgs to child. + mIPCClosed = true; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(FTPChannelParent, nsIStreamListener, nsIParentChannel, + nsIInterfaceRequestor, nsIRequestObserver, + nsIChannelEventSink, nsIFTPChannelParentInternal) + +//----------------------------------------------------------------------------- +// FTPChannelParent::PFTPChannelParent +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// FTPChannelParent methods +//----------------------------------------------------------------------------- + +bool FTPChannelParent::Init(const FTPChannelCreationArgs& aArgs) { + switch (aArgs.type()) { + case FTPChannelCreationArgs::TFTPChannelOpenArgs: { + const FTPChannelOpenArgs& a = aArgs.get_FTPChannelOpenArgs(); + return DoAsyncOpen(a.uri(), a.startPos(), a.entityID(), a.uploadStream(), + a.loadInfo(), a.loadFlags()); + } + case FTPChannelCreationArgs::TFTPChannelConnectArgs: { + const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs(); + return ConnectChannel(cArgs.channelId()); + } + default: + MOZ_ASSERT_UNREACHABLE("unknown open type"); + return false; + } +} + +bool FTPChannelParent::DoAsyncOpen(const URIParams& aURI, + const uint64_t& aStartPos, + const nsCString& aEntityID, + const Maybe& aUploadStream, + const Maybe& aLoadInfoArgs, + const uint32_t& aLoadFlags) { + nsresult rv; + + nsCOMPtr uri = DeserializeURI(aURI); + if (!uri) return false; + +#ifdef DEBUG + LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n", this, + uri->GetSpecOrDefault().get())); +#endif + + nsCOMPtr ios(do_GetIOService(&rv)); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr loadInfo; + rv = mozilla::ipc::LoadInfoArgsToLoadInfo(aLoadInfoArgs, + getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + OriginAttributes attrs; + rv = loadInfo->GetOriginAttributes(&attrs); + if (NS_FAILED(rv)) { + return SendFailedAsyncOpen(rv); + } + + nsCOMPtr chan; + rv = NS_NewChannelInternal(getter_AddRefs(chan), uri, loadInfo, nullptr, + nullptr, nullptr, aLoadFlags, ios); + + if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); + + mChannel = chan; + + // later on mChannel may become an HTTP channel (we'll be redirected to one + // if we're using a proxy), but for now this is safe + nsFtpChannel* ftpChan = static_cast(mChannel.get()); + + if (mPBOverride != kPBOverride_Unset) { + ftpChan->SetPrivate(mPBOverride == kPBOverride_Private ? true : false); + } + rv = ftpChan->SetNotificationCallbacks(this); + if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); + + nsCOMPtr upload = DeserializeIPCStream(aUploadStream); + if (upload) { + // contentType and contentLength are ignored + rv = ftpChan->SetUploadStream(upload, ""_ns, 0); + if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); + } + + rv = ftpChan->ResumeAt(aStartPos, aEntityID); + if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); + + rv = ftpChan->AsyncOpen(this); + + if (NS_FAILED(rv)) return SendFailedAsyncOpen(rv); + + return true; +} + +bool FTPChannelParent::ConnectChannel(const uint64_t& channelId) { + nsresult rv; + + LOG(("Looking for a registered channel [this=%p, id=%" PRIx64 "]", this, + channelId)); + + nsCOMPtr channel; + rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel)); + if (NS_SUCCEEDED(rv)) mChannel = channel; + + LOG((" found channel %p, rv=%08" PRIx32, mChannel.get(), + static_cast(rv))); + + return true; +} + +mozilla::ipc::IPCResult FTPChannelParent::RecvCancel(const nsresult& status) { + if (mChannel) mChannel->Cancel(status); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult FTPChannelParent::RecvSuspend() { + if (mChannel) { + mChannel->Suspend(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult FTPChannelParent::RecvResume() { + if (mChannel) { + mChannel->Resume(); + } + return IPC_OK(); +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIRequestObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::OnStartRequest(nsIRequest* aRequest) { + LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this)); + + nsCOMPtr chan = do_QueryInterface(aRequest); + MOZ_ASSERT(chan); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + // Send down any permissions which are relevant to this URL if we are + // performing a document load. + if (!mIPCClosed) { + PContentParent* pcp = Manager()->Manager(); + MOZ_ASSERT(pcp, "We should have a manager if our IPC isn't closed"); + DebugOnly rv = + static_cast(pcp)->AboutToLoadHttpFtpDocumentForChild( + chan); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + int64_t contentLength; + chan->GetContentLength(&contentLength); + nsCString contentType; + chan->GetContentType(contentType); + nsresult channelStatus = NS_OK; + chan->GetStatus(&channelStatus); + + nsCString entityID; + nsCOMPtr resChan = do_QueryInterface(aRequest); + MOZ_ASSERT( + resChan); // both FTP and HTTP should implement nsIResumableChannel + if (resChan) { + resChan->GetEntityID(entityID); + } + + PRTime lastModified = 0; + nsCOMPtr ftpChan = do_QueryInterface(aRequest); + if (ftpChan) { + ftpChan->GetLastModifiedTime(&lastModified); + } + nsCOMPtr httpChan = do_QueryInterface(aRequest); + if (httpChan) { + Unused << httpChan->GetLastModifiedTime(&lastModified); + } + + URIParams uriparam; + nsCOMPtr uri; + chan->GetURI(getter_AddRefs(uri)); + SerializeURI(uri, uriparam); + + if (mIPCClosed || + !SendOnStartRequest(channelStatus, contentLength, contentType, + lastModified, entityID, uriparam)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%" PRIu32 "]\n", this, + static_cast(aStatusCode))); + + if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::OnDataAvailable(nsIRequest* aRequest, + nsIInputStream* aInputStream, + uint64_t aOffset, uint32_t aCount) { + LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this)); + + nsCString data; + nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); + if (NS_FAILED(rv)) return rv; + + nsresult channelStatus = NS_OK; + mChannel->GetStatus(&channelStatus); + + if (mIPCClosed || !SendOnDataAvailable(channelStatus, data, aOffset, aCount)) + return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIParentChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::SetParentListener(ParentChannelListener* aListener) { + // Do not need ptr to ParentChannelListener. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::NotifyClassificationFlags(uint32_t aClassificationFlags, + bool aIsThirdParty) { + // One day, this should probably be filled in. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::NotifyFlashPluginStateChanged( + nsIHttpChannel::FlashPluginState aState) { + // One day, this should probably be filled in. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::SetClassifierMatchedInfo(const nsACString& aList, + const nsACString& aProvider, + const nsACString& aFullHash) { + // One day, this should probably be filled in. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::SetClassifierMatchedTrackingInfo( + const nsACString& aLists, const nsACString& aFullHashes) { + // One day, this should probably be filled in. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::Delete() { + if (mIPCClosed || !SendDeleteSelf()) return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::GetRemoteType(nsACString& aRemoteType) { + if (!CanSend()) { + return NS_ERROR_UNEXPECTED; + } + + dom::PContentParent* pcp = Manager()->Manager(); + aRemoteType = static_cast(pcp)->GetRemoteType(); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIInterfaceRequestor +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::GetInterface(const nsIID& uuid, void** result) { + if (uuid.Equals(NS_GET_IID(nsIAuthPromptProvider)) || + uuid.Equals(NS_GET_IID(nsISecureBrowserUI))) { + if (mBrowserParent) { + return mBrowserParent->QueryInterface(uuid, result); + } + } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) || + uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr provider(do_QueryObject(mBrowserParent)); + if (provider) { + nsresult rv = provider->GetAuthPrompt( + nsIAuthPromptProvider::PROMPT_NORMAL, uuid, result); + if (NS_FAILED(rv)) { + return NS_ERROR_NO_INTERFACE; + } + return NS_OK; + } + } + + // Only support nsILoadContext if child channel's callbacks did too + if (uuid.Equals(NS_GET_IID(nsILoadContext)) && mLoadContext) { + nsCOMPtr copy = mLoadContext; + copy.forget(result); + return NS_OK; + } + + return QueryInterface(uuid, result); +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIChannelEventSink +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::AsyncOnChannelRedirect( + nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t redirectFlags, + nsIAsyncVerifyRedirectCallback* callback) { + nsCOMPtr ftpChan = do_QueryInterface(newChannel); + if (!ftpChan) { + // when FTP is set to use HTTP proxying, we wind up getting redirected to an + // HTTP channel. + nsCOMPtr httpChan = do_QueryInterface(newChannel); + if (!httpChan) return NS_ERROR_UNEXPECTED; + } + mChannel = newChannel; + callback->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::SetErrorMsg(const char* aMsg, bool aUseUTF8) { + mErrorMsg = aMsg; + mUseUTF8 = aUseUTF8; + return NS_OK; +} + +//--------------------- +} // namespace net +} // namespace mozilla diff --git a/netwerk/protocol/ftp/FTPChannelParent.h b/netwerk/protocol/ftp/FTPChannelParent.h new file mode 100644 index 0000000000..21ef7590bb --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelParent.h @@ -0,0 +1,92 @@ +/* -*- 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/. */ + +#ifndef mozilla_net_FTPChannelParent_h +#define mozilla_net_FTPChannelParent_h + +#include "mozilla/net/PFTPChannelParent.h" +#include "mozilla/net/NeckoParent.h" +#include "nsIParentChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIChannelEventSink.h" +#include "nsIFTPChannelParentInternal.h" + +class nsILoadContext; + +namespace mozilla { + +namespace dom { +class BrowserParent; +} // namespace dom + +namespace net { +class ChannelEventQueue; + +class FTPChannelParent final : public PFTPChannelParent, + public nsIParentChannel, + public nsIInterfaceRequestor, + public nsIChannelEventSink, + public nsIFTPChannelParentInternal { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + FTPChannelParent(dom::BrowserParent* aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus); + + bool Init(const FTPChannelCreationArgs& aOpenArgs); + + NS_IMETHOD SetErrorMsg(const char* aMsg, bool aUseUTF8) override; + + protected: + virtual ~FTPChannelParent(); + + bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos, + const nsCString& aEntityID, + const Maybe& aUploadStream, + const Maybe& aLoadInfoArgs, + const uint32_t& aLoadFlags); + + // used to connect redirected-to channel in parent with just created + // ChildChannel. Used during HTTP->FTP redirects. + bool ConnectChannel(const uint64_t& channelId); + + virtual mozilla::ipc::IPCResult RecvCancel(const nsresult& status) override; + virtual mozilla::ipc::IPCResult RecvSuspend() override; + virtual mozilla::ipc::IPCResult RecvResume() override; + + virtual void ActorDestroy(ActorDestroyReason why) override; + + // if configured to use HTTP proxy for FTP, this can an an HTTP channel. + nsCOMPtr mChannel; + + bool mIPCClosed; + + nsCOMPtr mLoadContext; + + PBOverrideStatus mPBOverride; + + // Set to the canceled status value if the main channel was canceled. + nsresult mStatus; + + RefPtr mBrowserParent; + + RefPtr mEventQ; + + nsCString mErrorMsg; + bool mUseUTF8; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_FTPChannelParent_h diff --git a/netwerk/protocol/ftp/PFTPChannel.ipdl b/netwerk/protocol/ftp/PFTPChannel.ipdl new file mode 100644 index 0000000000..be0d8abaac --- /dev/null +++ b/netwerk/protocol/ftp/PFTPChannel.ipdl @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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 protocol PNecko; +include URIParams; + +//FIXME: bug #792908 (NeckoChannelParams already included by PNecko) +include NeckoChannelParams; + +using PRTime from "prtime.h"; + +namespace mozilla { +namespace net { + +async protocol PFTPChannel +{ + manager PNecko; + +parent: + // Note: channels are opened during construction, so no open method here: + // see PNecko.ipdl + + async __delete__(); + + async Cancel(nsresult status); + async Suspend(); + async Resume(); + +child: + async OnStartRequest(nsresult aChannelStatus, + int64_t aContentLength, + nsCString aContentType, + PRTime aLastModified, + nsCString aEntityID, + URIParams aURI); + async OnDataAvailable(nsresult channelStatus, + nsCString data, + uint64_t offset, + uint32_t count); + async OnStopRequest(nsresult channelStatus, + nsCString aErrorMsg, + bool aUseUTF8); + async FailedAsyncOpen(nsresult statusCode); + + async DeleteSelf(); +}; + +} // namespace net +} // namespace mozilla + diff --git a/netwerk/protocol/ftp/doc/testdoc b/netwerk/protocol/ftp/doc/testdoc new file mode 100644 index 0000000000..61fda16fcc --- /dev/null +++ b/netwerk/protocol/ftp/doc/testdoc @@ -0,0 +1,4 @@ +Test +here +there +everywhere diff --git a/netwerk/protocol/ftp/ftpCore.h b/netwerk/protocol/ftp/ftpCore.h new file mode 100644 index 0000000000..5c7433495b --- /dev/null +++ b/netwerk/protocol/ftp/ftpCore.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef __ftpCore_h___ +#define __ftpCore_h___ + +#include "nsError.h" + +/** + * Status nsresult codes + */ + +#endif // __ftpCore_h___ diff --git a/netwerk/protocol/ftp/moz.build b/netwerk/protocol/ftp/moz.build new file mode 100644 index 0000000000..914916bf29 --- /dev/null +++ b/netwerk/protocol/ftp/moz.build @@ -0,0 +1,50 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "Networking: FTP") + +XPIDL_SOURCES += [ + "nsIFTPChannel.idl", + "nsIFTPChannelParentInternal.idl", +] + +XPIDL_MODULE = "necko_ftp" + +EXPORTS += [ + "ftpCore.h", +] + +EXPORTS.mozilla.net += [ + "FTPChannelChild.h", + "FTPChannelParent.h", +] + +UNIFIED_SOURCES += [ + "FTPChannelChild.cpp", + "FTPChannelParent.cpp", + "nsFTPChannel.cpp", + "nsFtpConnectionThread.cpp", + "nsFtpControlConnection.cpp", + "nsFtpProtocolHandler.cpp", +] + +IPDL_SOURCES += [ + "PFTPChannel.ipdl", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +LOCAL_INCLUDES += [ + "/netwerk/base", +] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/netwerk/protocol/ftp/nsFTPChannel.cpp b/netwerk/protocol/ftp/nsFTPChannel.cpp new file mode 100644 index 0000000000..97a7be1e68 --- /dev/null +++ b/netwerk/protocol/ftp/nsFTPChannel.cpp @@ -0,0 +1,213 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sts=2 sw=2 et cin: */ +/* 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 "nsFTPChannel.h" +#include "nsFtpConnectionThread.h" // defines nsFtpState + +#include "nsThreadUtils.h" +#include "mozilla/Attributes.h" + +using namespace mozilla; +using namespace mozilla::net; +extern LazyLogModule gFTPLog; + +// There are two transport connections established for an +// ftp connection. One is used for the command channel , and +// the other for the data channel. The command channel is the first +// connection made and is used to negotiate the second, data, channel. +// The data channel is driven by the command channel and is either +// initiated by the server (PORT command) or by the client (PASV command). +// Client initiation is the most common case and is attempted first. + +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS_INHERITED(nsFtpChannel, nsBaseChannel, nsIUploadChannel, + nsIResumableChannel, nsIFTPChannel, + nsIProxiedChannel, nsIForcePendingChannel, + nsISupportsWeakReference) + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpChannel::SetUploadStream(nsIInputStream* stream, + const nsACString& contentType, + int64_t contentLength) { + NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS); + + mUploadStream = stream; + + // NOTE: contentLength is intentionally ignored here. + + return NS_OK; +} + +NS_IMETHODIMP +nsFtpChannel::GetUploadStream(nsIInputStream** aStream) { + NS_ENSURE_ARG_POINTER(aStream); + nsCOMPtr stream = mUploadStream; + stream.forget(aStream); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpChannel::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) { + NS_ENSURE_TRUE(!Pending(), NS_ERROR_IN_PROGRESS); + mEntityID = aEntityID; + mStartPos = aStartPos; + mResumeRequested = (mStartPos || !mEntityID.IsEmpty()); + return NS_OK; +} + +NS_IMETHODIMP +nsFtpChannel::GetEntityID(nsACString& entityID) { + if (mEntityID.IsEmpty()) return NS_ERROR_NOT_RESUMABLE; + + entityID = mEntityID; + return NS_OK; +} + +//----------------------------------------------------------------------------- +NS_IMETHODIMP +nsFtpChannel::GetProxyInfo(nsIProxyInfo** aProxyInfo) { + nsCOMPtr info = ProxyInfo(); + info.forget(aProxyInfo); + return NS_OK; +} + +NS_IMETHODIMP nsFtpChannel::GetHttpProxyConnectResponseCode( + int32_t* aResponseCode) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +//----------------------------------------------------------------------------- + +nsresult nsFtpChannel::OpenContentStream(bool async, nsIInputStream** result, + nsIChannel** channel) { + if (!async) return NS_ERROR_NOT_IMPLEMENTED; + + RefPtr state = new nsFtpState(); + + nsresult rv = state->Init(this); + if (NS_FAILED(rv)) { + return rv; + } + + state.forget(result); + return NS_OK; +} + +bool nsFtpChannel::GetStatusArg(nsresult status, nsString& statusArg) { + nsAutoCString host; + URI()->GetHost(host); + CopyUTF8toUTF16(host, statusArg); + return true; +} + +void nsFtpChannel::OnCallbacksChanged() { mFTPEventSink = nullptr; } + +//----------------------------------------------------------------------------- + +namespace { + +class FTPEventSinkProxy final : public nsIFTPEventSink { + ~FTPEventSinkProxy() = default; + + public: + explicit FTPEventSinkProxy(nsIFTPEventSink* aTarget) + : mTarget(aTarget), mEventTarget(GetCurrentEventTarget()) {} + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFTPEVENTSINK + + class OnFTPControlLogRunnable : public Runnable { + public: + OnFTPControlLogRunnable(nsIFTPEventSink* aTarget, bool aServer, + const char* aMessage) + : mozilla::Runnable("FTPEventSinkProxy::OnFTPControlLogRunnable"), + mTarget(aTarget), + mServer(aServer), + mMessage(aMessage) {} + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr mTarget; + bool mServer; + nsCString mMessage; + }; + + private: + nsCOMPtr mTarget; + nsCOMPtr mEventTarget; +}; + +NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink) + +NS_IMETHODIMP +FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg) { + RefPtr r = + new OnFTPControlLogRunnable(mTarget, aServer, aMsg); + return mEventTarget->Dispatch(r, NS_DISPATCH_NORMAL); +} + +NS_IMETHODIMP +FTPEventSinkProxy::OnFTPControlLogRunnable::Run() { + mTarget->OnFTPControlLog(mServer, mMessage.get()); + return NS_OK; +} + +} // namespace + +void nsFtpChannel::GetFTPEventSink(nsCOMPtr& aResult) { + if (!mFTPEventSink) { + nsCOMPtr ftpSink; + GetCallback(ftpSink); + if (ftpSink) { + mFTPEventSink = new FTPEventSinkProxy(ftpSink); + } + } + aResult = mFTPEventSink; +} + +NS_IMETHODIMP +nsFtpChannel::ForcePending(bool aForcePending) { + // Set true here so IsPending will return true. + // Required for callback diversion from child back to parent. In such cases + // OnStopRequest can be called in the parent before callbacks are diverted + // back from the child to the listener in the parent. + mForcePending = aForcePending; + + return NS_OK; +} + +NS_IMETHODIMP +nsFtpChannel::IsPending(bool* result) { + *result = Pending(); + return NS_OK; +} + +bool nsFtpChannel::Pending() const { + return nsBaseChannel::Pending() || mForcePending; +} + +NS_IMETHODIMP +nsFtpChannel::Suspend() { + LOG(("nsFtpChannel::Suspend [this=%p]\n", this)); + NS_ENSURE_TRUE(Pending(), NS_ERROR_NOT_AVAILABLE); + + ++mSuspendCount; + return nsBaseChannel::Suspend(); +} + +NS_IMETHODIMP +nsFtpChannel::Resume() { + LOG(("nsFtpChannel::Resume [this=%p]\n", this)); + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + --mSuspendCount; + return nsBaseChannel::Resume(); +} diff --git a/netwerk/protocol/ftp/nsFTPChannel.h b/netwerk/protocol/ftp/nsFTPChannel.h new file mode 100644 index 0000000000..52dc23506a --- /dev/null +++ b/netwerk/protocol/ftp/nsFTPChannel.h @@ -0,0 +1,115 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=4 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#ifndef nsFTPChannel_h___ +#define nsFTPChannel_h___ + +#include "nsBaseChannel.h" + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIFTPChannel.h" +#include "nsIForcePendingChannel.h" +#include "nsIUploadChannel.h" +#include "nsIProxyInfo.h" +#include "nsIProxiedChannel.h" +#include "nsIResumableChannel.h" +#include "nsWeakReference.h" + +class nsIURI; + +class nsFtpChannel final : public nsBaseChannel, + public nsIFTPChannel, + public nsIUploadChannel, + public nsIResumableChannel, + public nsIProxiedChannel, + public nsIForcePendingChannel, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIUPLOADCHANNEL + NS_DECL_NSIRESUMABLECHANNEL + NS_DECL_NSIPROXIEDCHANNEL + + nsFtpChannel(nsIURI* uri, nsIProxyInfo* pi) + : mProxyInfo(pi), + mStartPos(0), + mResumeRequested(false), + mLastModifiedTime(0), + mForcePending(false), + mSuspendCount(0) { + SetURI(uri); + } + + void UpdateURI(nsIURI* aURI) { + MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(), "Not thread-safe."); + mURI = aURI; + } + + nsIProxyInfo* ProxyInfo() { return mProxyInfo; } + + void SetProxyInfo(nsIProxyInfo* pi) { mProxyInfo = pi; } + + NS_IMETHOD IsPending(bool* result) override; + + // This is a short-cut to calling nsIRequest::IsPending(). + // Overrides Pending in nsBaseChannel. + bool Pending() const override; + + // Were we asked to resume a download? + bool ResumeRequested() { return mResumeRequested; } + + // Download from this byte offset + uint64_t StartPos() { return mStartPos; } + + // ID of the entity to resume downloading + const nsCString& EntityID() { return mEntityID; } + void SetEntityID(const nsACString& entityID) { mEntityID = entityID; } + + NS_IMETHOD GetLastModifiedTime(PRTime* lastModifiedTime) override { + *lastModifiedTime = mLastModifiedTime; + return NS_OK; + } + + NS_IMETHOD SetLastModifiedTime(PRTime lastModifiedTime) override { + mLastModifiedTime = lastModifiedTime; + return NS_OK; + } + + // Data stream to upload + nsIInputStream* UploadStream() { return mUploadStream; } + + // Helper function for getting the nsIFTPEventSink. + void GetFTPEventSink(nsCOMPtr& aResult); + + NS_IMETHOD Suspend() override; + NS_IMETHOD Resume() override; + + public: + NS_IMETHOD ForcePending(bool aForcePending) override; + + protected: + virtual ~nsFtpChannel() = default; + virtual nsresult OpenContentStream(bool async, nsIInputStream** result, + nsIChannel** channel) override; + virtual bool GetStatusArg(nsresult status, nsString& statusArg) override; + virtual void OnCallbacksChanged() override; + + private: + nsCOMPtr mProxyInfo; + nsCOMPtr mFTPEventSink; + nsCOMPtr mUploadStream; + uint64_t mStartPos; + nsCString mEntityID; + bool mResumeRequested; + PRTime mLastModifiedTime; + bool mForcePending; + + // Current suspension depth for this channel object + uint32_t mSuspendCount; +}; + +#endif /* nsFTPChannel_h___ */ diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp new file mode 100644 index 0000000000..c93a2c604b --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -0,0 +1,1960 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set tw=80 ts=4 sts=2 sw=2 et cin: */ +/* 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 + +#include "prprf.h" +#include "mozilla/Logging.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/TextUtils.h" +#include "prtime.h" + +#include "nsIOService.h" +#include "nsFTPChannel.h" +#include "nsFtpConnectionThread.h" +#include "nsFtpControlConnection.h" +#include "nsFtpProtocolHandler.h" +#include "netCore.h" +#include "nsCRT.h" +#include "nsEscape.h" +#include "nsMimeTypes.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIAsyncStreamCopier.h" +#include "nsThreadUtils.h" +#include "nsStreamUtils.h" +#include "nsIURL.h" +#include "nsISocketTransport.h" +#include "nsIPrefBranch.h" +#include "nsAuthInformationHolder.h" +#include "nsIProtocolProxyService.h" +#include "nsICancelable.h" +#include "nsIOutputStream.h" +#include "nsIProtocolHandler.h" +#include "nsIProxyInfo.h" +#include "nsISocketTransportService.h" +#include "nsIURI.h" +#include "nsILoadInfo.h" +#include "nsIAuthPrompt2.h" +#include "nsIFTPChannelParentInternal.h" +#include "mozilla/Telemetry.h" + +using namespace mozilla; +using namespace mozilla::net; + +extern LazyLogModule gFTPLog; +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) +#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args) + +// remove FTP parameters (starting with ";") from the path +static void removeParamsFromPath(nsCString& path) { + int32_t index = path.FindChar(';'); + if (index >= 0) { + path.SetLength(index); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsFtpState, nsBaseContentStream, + nsIInputStreamCallback, nsITransportEventSink, + nsIRequestObserver, nsIProtocolProxyCallback) + +nsFtpState::nsFtpState() + : nsBaseContentStream(true), + mState(FTP_INIT), + mNextState(FTP_S_USER), + mKeepRunning(true), + mResponseCode(0), + mReceivedControlData(false), + mTryingCachedControl(false), + mRETRFailed(false), + mFileSize(kJS_MAX_SAFE_UINTEGER), + mServerType(FTP_GENERIC_TYPE), + mAction(GET), + mAnonymous(true), + mRetryPass(false), + mStorReplyReceived(false), + mRlist1xxReceived(false), + mRretr1xxReceived(false), + mRstor1xxReceived(false), + mInternalError(NS_OK), + mReconnectAndLoginAgain(false), + mCacheConnection(true), + mPort(21), + mAddressChecked(false), + mServerIsIPv6(false), + mUseUTF8(false), + mControlStatus(NS_OK), + mDeferredCallbackPending(false) { + this->mServerAddress.raw.family = 0; + this->mServerAddress.inet = {}; + LOG_INFO(("FTP:(%p) nsFtpState created", this)); + + // make sure handler stays around + mHandler = gFtpHandler; +} + +nsFtpState::~nsFtpState() { + LOG_INFO(("FTP:(%p) nsFtpState destroyed", this)); + + if (mProxyRequest) mProxyRequest->Cancel(NS_ERROR_FAILURE); + + // release reference to handler + mHandler = nullptr; +} + +// nsIInputStreamCallback implementation +NS_IMETHODIMP +nsFtpState::OnInputStreamReady(nsIAsyncInputStream* aInStream) { + LOG(("FTP:(%p) data stream ready\n", this)); + + // We are receiving a notification from our data stream, so just forward it + // on to our stream callback. + if (HasPendingCallback()) DispatchCallbackSync(); + + return NS_OK; +} + +void nsFtpState::OnControlDataAvailable(const char* aData, uint32_t aDataLen) { + LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen)); + mControlConnection->WaitData(this); // queue up another call + + if (!mReceivedControlData) { + // parameter can be null cause the channel fills them in. + OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0); + mReceivedControlData = true; + } + + // Sometimes we can get two responses in the same packet, eg from LIST. + // So we need to parse the response line by line + + nsCString buffer = mControlReadCarryOverBuf; + + // Clear the carryover buf - if we still don't have a line, then it will + // be reappended below + mControlReadCarryOverBuf.Truncate(); + + buffer.Append(aData, aDataLen); + + const char* currLine = buffer.get(); + while (*currLine && mKeepRunning) { + int32_t eolLength = strcspn(currLine, CRLF); + int32_t currLineLength = strlen(currLine); + + // if currLine is empty or only contains CR or LF, then bail. we can + // sometimes get an ODA event with the full response line + CR without + // the trailing LF. the trailing LF might come in the next ODA event. + // because we are happy enough to process a response line ending only + // in CR, we need to take care to discard the extra LF (bug 191220). + if (eolLength == 0 && currLineLength <= 1) break; + + if (eolLength == currLineLength) { + mControlReadCarryOverBuf.Assign(currLine); + break; + } + + // Append the current segment, including the LF + nsAutoCString line; + int32_t crlfLength = 0; + + if ((currLineLength > eolLength) && (currLine[eolLength] == nsCRT::CR) && + (currLine[eolLength + 1] == nsCRT::LF)) { + crlfLength = 2; // CR +LF + } else { + crlfLength = 1; // + LF or CR + } + + line.Assign(currLine, eolLength + crlfLength); + + // Does this start with a response code? + bool startNum = (line.Length() >= 3 && IsAsciiDigit(line[0]) && + IsAsciiDigit(line[1]) && IsAsciiDigit(line[2])); + + if (mResponseMsg.IsEmpty()) { + // If we get here, then we know that we have a complete line, and + // that it is the first one + + NS_ASSERTION(line.Length() > 4 && startNum, + "Read buffer doesn't include response code"); + + mResponseCode = atoi(PromiseFlatCString(Substring(line, 0, 3)).get()); + } + + mResponseMsg.Append(line); + + // This is the last line if its 3 numbers followed by a space + if (startNum && line[3] == ' ') { + // yup. last line, let's move on. + if (mState == mNextState) { + NS_ERROR("ftp read state mixup"); + mInternalError = NS_ERROR_FAILURE; + mState = FTP_ERROR; + } else { + mState = mNextState; + } + + nsCOMPtr ftpSink; + mChannel->GetFTPEventSink(ftpSink); + if (ftpSink) ftpSink->OnFTPControlLog(true, mResponseMsg.get()); + + nsresult rv = Process(); + mResponseMsg.Truncate(); + if (NS_FAILED(rv)) { + CloseWithStatus(rv); + return; + } + } + + currLine = currLine + eolLength + crlfLength; + } +} + +void nsFtpState::OnControlError(nsresult status) { + NS_ASSERTION(NS_FAILED(status), "expecting error condition"); + + LOG(("FTP:(%p) CC(%p) error [%" PRIx32 " was-cached=%u]\n", this, + mControlConnection.get(), static_cast(status), + mTryingCachedControl)); + + mControlStatus = status; + if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) { + mReconnectAndLoginAgain = false; + mAnonymous = false; + mControlStatus = NS_OK; + Connect(); + } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) { + mTryingCachedControl = false; + Connect(); + } else { + CloseWithStatus(status); + } +} + +nsresult nsFtpState::EstablishControlConnection() { + NS_ASSERTION(!mControlConnection, "we already have a control connection"); + + nsresult rv; + + LOG(("FTP:(%p) trying cached control\n", this)); + + // Look to see if we can use a cached control connection: + RefPtr connection; + // Don't use cached control if anonymous (bug #473371) + if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) + gFtpHandler->RemoveConnection(mChannel->URI(), getter_AddRefs(connection)); + + if (connection) { + mControlConnection.swap(connection); + if (mControlConnection->IsAlive()) { + // set stream listener of the control connection to be us. + mControlConnection->WaitData(this); + + // read cached variables into us. + mServerType = mControlConnection->mServerType; + mPassword = mControlConnection->mPassword; + mPwd = mControlConnection->mPwd; + mUseUTF8 = mControlConnection->mUseUTF8; + mTryingCachedControl = true; + + // we have to set charset to connection if server supports utf-8 + if (mUseUTF8) mChannel->SetContentCharset("UTF-8"_ns); + + // we're already connected to this server, skip login. + mState = FTP_S_PASV; + mResponseCode = 530; // assume the control connection was dropped. + mControlStatus = NS_OK; + mReceivedControlData = false; // For this request, we have not. + + // if we succeed, return. Otherwise, we need to create a transport + rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); + if (NS_SUCCEEDED(rv)) return rv; + } + LOG(("FTP:(%p) cached CC(%p) is unusable\n", this, + mControlConnection.get())); + + mControlConnection->WaitData(nullptr); + mControlConnection = nullptr; + } + + LOG(("FTP:(%p) creating CC\n", this)); + + mState = FTP_READ_BUF; + mNextState = FTP_S_USER; + + nsAutoCString host; + rv = mChannel->URI()->GetAsciiHost(host); + if (NS_FAILED(rv)) return rv; + + mControlConnection = new nsFtpControlConnection(host, mPort); + if (!mControlConnection) return NS_ERROR_OUT_OF_MEMORY; + + rv = mControlConnection->Connect(mChannel->ProxyInfo(), this); + if (NS_FAILED(rv)) { + LOG(("FTP:(%p) CC(%p) failed to connect [rv=%" PRIx32 "]\n", this, + mControlConnection.get(), static_cast(rv))); + mControlConnection = nullptr; + return rv; + } + + return mControlConnection->WaitData(this); +} + +void nsFtpState::MoveToNextState(FTP_STATE nextState) { + if (NS_FAILED(mInternalError)) { + mState = FTP_ERROR; + LOG(("FTP:(%p) FAILED (%" PRIx32 ")\n", this, + static_cast(mInternalError))); + } else { + mState = FTP_READ_BUF; + mNextState = nextState; + } +} + +nsresult nsFtpState::Process() { + nsresult rv = NS_OK; + bool processingRead = true; + + while (mKeepRunning && processingRead) { + switch (mState) { + case FTP_COMMAND_CONNECT: + KillControlConnection(); + LOG(("FTP:(%p) establishing CC", this)); + mInternalError = EstablishControlConnection(); // sets mState + if (NS_FAILED(mInternalError)) { + mState = FTP_ERROR; + LOG(("FTP:(%p) FAILED\n", this)); + } else { + LOG(("FTP:(%p) SUCCEEDED\n", this)); + } + break; + + case FTP_READ_BUF: + LOG(("FTP:(%p) Waiting for CC(%p)\n", this, mControlConnection.get())); + processingRead = false; + break; + + case FTP_ERROR: // xx needs more work to handle dropped control + // connection cases + if ((mTryingCachedControl && mResponseCode == 530 && + mInternalError == NS_ERROR_FTP_PASV) || + (mResponseCode == 425 && mInternalError == NS_ERROR_FTP_PASV)) { + // The user was logged out during an pasv operation + // we want to restart this request with a new control + // channel. + mState = FTP_COMMAND_CONNECT; + } else if (mResponseCode == 421 && + mInternalError != NS_ERROR_FTP_LOGIN) { + // The command channel dropped for some reason. + // Fire it back up, unless we were trying to login + // in which case the server might just be telling us + // that the max number of users has been reached... + mState = FTP_COMMAND_CONNECT; + } else if (mAnonymous && mInternalError == NS_ERROR_FTP_LOGIN) { + // If the login was anonymous, and it failed, try again with a + // username Don't reuse old control connection, see #386167 + mAnonymous = false; + mState = FTP_COMMAND_CONNECT; + } else { + LOG(("FTP:(%p) FTP_ERROR - calling StopProcessing\n", this)); + rv = StopProcessing(); + NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); + processingRead = false; + } + break; + + case FTP_COMPLETE: + LOG(("FTP:(%p) COMPLETE\n", this)); + rv = StopProcessing(); + NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); + processingRead = false; + break; + + // USER + case FTP_S_USER: + rv = S_user(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN; + + MoveToNextState(FTP_R_USER); + break; + + case FTP_R_USER: + mState = R_user(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; + + break; + // PASS + case FTP_S_PASS: + rv = S_pass(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN; + + MoveToNextState(FTP_R_PASS); + break; + + case FTP_R_PASS: + mState = R_pass(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; + + break; + // ACCT + case FTP_S_ACCT: + rv = S_acct(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN; + + MoveToNextState(FTP_R_ACCT); + break; + + case FTP_R_ACCT: + mState = R_acct(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; + + break; + + // SYST + case FTP_S_SYST: + rv = S_syst(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_LOGIN; + + MoveToNextState(FTP_R_SYST); + break; + + case FTP_R_SYST: + mState = R_syst(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; + + break; + + // TYPE + case FTP_S_TYPE: + rv = S_type(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_TYPE); + break; + + case FTP_R_TYPE: + mState = R_type(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + // CWD + case FTP_S_CWD: + rv = S_cwd(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_CWD; + + MoveToNextState(FTP_R_CWD); + break; + + case FTP_R_CWD: + mState = R_cwd(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_CWD; + break; + + // LIST + case FTP_S_LIST: + rv = S_list(); + + if (rv == NS_ERROR_NOT_RESUMABLE) { + mInternalError = rv; + } else if (NS_FAILED(rv)) { + mInternalError = NS_ERROR_FTP_CWD; + } + + MoveToNextState(FTP_R_LIST); + break; + + case FTP_R_LIST: + mState = R_list(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + + // SIZE + case FTP_S_SIZE: + rv = S_size(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_SIZE); + break; + + case FTP_R_SIZE: + mState = R_size(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + + // REST + case FTP_S_REST: + rv = S_rest(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_REST); + break; + + case FTP_R_REST: + mState = R_rest(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + + // MDTM + case FTP_S_MDTM: + rv = S_mdtm(); + if (NS_FAILED(rv)) mInternalError = rv; + MoveToNextState(FTP_R_MDTM); + break; + + case FTP_R_MDTM: + mState = R_mdtm(); + + // Don't want to overwrite a more explicit status code + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) + mInternalError = NS_ERROR_FAILURE; + + break; + + // RETR + case FTP_S_RETR: + rv = S_retr(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_RETR); + break; + + case FTP_R_RETR: + + mState = R_retr(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + + // STOR + case FTP_S_STOR: + rv = S_stor(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_STOR); + break; + + case FTP_R_STOR: + mState = R_stor(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; + + break; + + // PASV + case FTP_S_PASV: + rv = S_pasv(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_PASV; + + MoveToNextState(FTP_R_PASV); + break; + + case FTP_R_PASV: + mState = R_pasv(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_PASV; + + break; + + // PWD + case FTP_S_PWD: + rv = S_pwd(); + + if (NS_FAILED(rv)) mInternalError = NS_ERROR_FTP_PWD; + + MoveToNextState(FTP_R_PWD); + break; + + case FTP_R_PWD: + mState = R_pwd(); + + if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_PWD; + + break; + + // FEAT for RFC2640 support + case FTP_S_FEAT: + rv = S_feat(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_FEAT); + break; + + case FTP_R_FEAT: + mState = R_feat(); + + // Don't want to overwrite a more explicit status code + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) + mInternalError = NS_ERROR_FAILURE; + break; + + // OPTS for some non-RFC2640-compliant servers support + case FTP_S_OPTS: + rv = S_opts(); + + if (NS_FAILED(rv)) mInternalError = rv; + + MoveToNextState(FTP_R_OPTS); + break; + + case FTP_R_OPTS: + mState = R_opts(); + + // Don't want to overwrite a more explicit status code + if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError)) + mInternalError = NS_ERROR_FAILURE; + break; + + default:; + } + } + + return rv; +} + +/////////////////////////////////// +// STATE METHODS +/////////////////////////////////// +nsresult nsFtpState::S_user() { + // some servers on connect send us a 421 or 521. (84525) (141784) + if ((mResponseCode == 421) || (mResponseCode == 521)) return NS_ERROR_FAILURE; + + nsresult rv; + nsAutoCString usernameStr("USER "); + + mResponseMsg = ""; + + if (mAnonymous) { + mReconnectAndLoginAgain = true; + usernameStr.AppendLiteral("anonymous"); + } else { + mReconnectAndLoginAgain = false; + if (mUsername.IsEmpty()) { + // No prompt for anonymous requests (bug #473371) + if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) + return NS_ERROR_FAILURE; + + nsCOMPtr prompter; + NS_QueryAuthPrompt2(static_cast(mChannel), + getter_AddRefs(prompter)); + if (!prompter) return NS_ERROR_NOT_INITIALIZED; + + RefPtr info = new nsAuthInformationHolder( + nsIAuthInformation::AUTH_HOST, u""_ns, ""_ns); + + bool retval; + rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, info, + &retval); + + // if the user canceled or didn't supply a username we want to fail + if (NS_FAILED(rv) || !retval || info->User().IsEmpty()) + return NS_ERROR_FAILURE; + + mUsername = info->User(); + mPassword = info->Password(); + } + // XXX Is UTF-8 the best choice? + AppendUTF16toUTF8(mUsername, usernameStr); + } + + usernameStr.AppendLiteral(CRLF); + + return SendFTPCommand(usernameStr); +} + +FTP_STATE +nsFtpState::R_user() { + mReconnectAndLoginAgain = false; + if (mResponseCode / 100 == 3) { + // send off the password + return FTP_S_PASS; + } + if (mResponseCode / 100 == 2) { + // no password required, we're already logged in + return FTP_S_SYST; + } + if (mResponseCode / 100 == 5) { + // problem logging in. typically this means the server + // has reached it's user limit. + return FTP_ERROR; + } + // LOGIN FAILED + return FTP_ERROR; +} + +nsresult nsFtpState::S_pass() { + nsresult rv; + nsAutoCString passwordStr("PASS "); + + mResponseMsg = ""; + + if (mAnonymous) { + if (!mPassword.IsEmpty()) { + // XXX Is UTF-8 the best choice? + AppendUTF16toUTF8(mPassword, passwordStr); + } else { + nsAutoCString anonPassword; + bool useRealEmail = false; + nsCOMPtr prefs = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (prefs) { + rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail); + if (NS_SUCCEEDED(rv) && useRealEmail) { + prefs->GetCharPref("network.ftp.anonymous_password", anonPassword); + } + } + if (!anonPassword.IsEmpty()) { + passwordStr.AppendASCII(anonPassword.get()); + } else { + // We need to default to a valid email address - bug 101027 + // example.com is reserved (rfc2606), so use that + passwordStr.AppendLiteral("mozilla@example.com"); + } + } + } else { + if (mPassword.IsEmpty() || mRetryPass) { + // No prompt for anonymous requests (bug #473371) + if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) + return NS_ERROR_FAILURE; + + nsCOMPtr prompter; + NS_QueryAuthPrompt2(static_cast(mChannel), + getter_AddRefs(prompter)); + if (!prompter) return NS_ERROR_NOT_INITIALIZED; + + RefPtr info = new nsAuthInformationHolder( + nsIAuthInformation::AUTH_HOST | nsIAuthInformation::ONLY_PASSWORD, + u""_ns, ""_ns); + + info->SetUserInternal(mUsername); + + bool retval; + rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE, info, + &retval); + + // we want to fail if the user canceled. Note here that if they want + // a blank password, we will pass it along. + if (NS_FAILED(rv) || !retval) return NS_ERROR_FAILURE; + + mPassword = info->Password(); + } + // XXX Is UTF-8 the best choice? + AppendUTF16toUTF8(mPassword, passwordStr); + } + + passwordStr.AppendLiteral(CRLF); + + return SendFTPCommand(passwordStr); +} + +FTP_STATE +nsFtpState::R_pass() { + if (mResponseCode / 100 == 3) { + // send account info + return FTP_S_ACCT; + } + if (mResponseCode / 100 == 2) { + // logged in + return FTP_S_SYST; + } + if (mResponseCode == 503) { + // start over w/ the user command. + // note: the password was successful, and it's stored in mPassword + mRetryPass = false; + return FTP_S_USER; + } + if (mResponseCode / 100 == 5 || mResponseCode == 421) { + // There is no difference between a too-many-users error, + // a wrong-password error, or any other sort of error + + if (!mAnonymous) mRetryPass = true; + + return FTP_ERROR; + } + // unexpected response code + return FTP_ERROR; +} + +nsresult nsFtpState::S_pwd() { + return SendFTPCommand(nsLiteralCString("PWD" CRLF)); +} + +FTP_STATE +nsFtpState::R_pwd() { + // Error response to PWD command isn't fatal, but don't cache the connection + // if CWD command is sent since correct mPwd is needed for further requests. + if (mResponseCode / 100 != 2) return FTP_S_TYPE; + + nsAutoCString respStr(mResponseMsg); + int32_t pos = respStr.FindChar('"'); + if (pos > -1) { + respStr.Cut(0, pos + 1); + pos = respStr.FindChar('"'); + if (pos > -1) { + respStr.Truncate(pos); + if (mServerType == FTP_VMS_TYPE) ConvertDirspecFromVMS(respStr); + if (respStr.IsEmpty() || respStr.Last() != '/') respStr.Append('/'); + mPwd = respStr; + } + } + return FTP_S_TYPE; +} + +nsresult nsFtpState::S_syst() { + return SendFTPCommand(nsLiteralCString("SYST" CRLF)); +} + +FTP_STATE +nsFtpState::R_syst() { + if (mResponseCode / 100 == 2) { + if ((mResponseMsg.Find("L8") > -1) || (mResponseMsg.Find("UNIX") > -1) || + (mResponseMsg.Find("BSD") > -1) || + (mResponseMsg.Find("MACOS Peter's Server") > -1) || + (mResponseMsg.Find("MACOS WebSTAR FTP") > -1) || + (mResponseMsg.Find("MVS") > -1) || (mResponseMsg.Find("OS/390") > -1) || + (mResponseMsg.Find("OS/400") > -1)) { + mServerType = FTP_UNIX_TYPE; + } else if ((mResponseMsg.Find("WIN32", true) > -1) || + (mResponseMsg.Find("windows", true) > -1)) { + mServerType = FTP_NT_TYPE; + } else if (mResponseMsg.Find("OS/2", true) > -1) { + mServerType = FTP_OS2_TYPE; + } else if (mResponseMsg.Find("VMS", true) > -1) { + mServerType = FTP_VMS_TYPE; + } else { + NS_ERROR("Server type list format unrecognized."); + + // clear mResponseMsg, which is displayed to the user. + mResponseMsg = ""; + return FTP_ERROR; + } + + return FTP_S_FEAT; + } + + if (mResponseCode / 100 == 5) { + // server didn't like the SYST command. Probably (500, 501, 502) + // No clue. We will just hope it is UNIX type server. + mServerType = FTP_UNIX_TYPE; + + return FTP_S_FEAT; + } + return FTP_ERROR; +} + +nsresult nsFtpState::S_acct() { + return SendFTPCommand(nsLiteralCString("ACCT noaccount" CRLF)); +} + +FTP_STATE +nsFtpState::R_acct() { + if (mResponseCode / 100 == 2) return FTP_S_SYST; + + return FTP_ERROR; +} + +nsresult nsFtpState::S_type() { + return SendFTPCommand(nsLiteralCString("TYPE I" CRLF)); +} + +FTP_STATE +nsFtpState::R_type() { + if (mResponseCode / 100 != 2) return FTP_ERROR; + + return FTP_S_PASV; +} + +nsresult nsFtpState::S_cwd() { + // Don't cache the connection if PWD command failed + if (mPwd.IsEmpty()) mCacheConnection = false; + + nsAutoCString cwdStr; + if (mAction != PUT) cwdStr = mPath; + if (cwdStr.IsEmpty() || cwdStr.First() != '/') cwdStr.Insert(mPwd, 0); + if (mServerType == FTP_VMS_TYPE) ConvertDirspecToVMS(cwdStr); + cwdStr.InsertLiteral("CWD ", 0); + cwdStr.AppendLiteral(CRLF); + + return SendFTPCommand(cwdStr); +} + +FTP_STATE +nsFtpState::R_cwd() { + if (mResponseCode / 100 == 2) { + if (mAction == PUT) return FTP_S_STOR; + + return FTP_S_LIST; + } + + return FTP_ERROR; +} + +nsresult nsFtpState::S_size() { + nsAutoCString sizeBuf(mPath); + if (sizeBuf.IsEmpty() || sizeBuf.First() != '/') sizeBuf.Insert(mPwd, 0); + if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(sizeBuf); + sizeBuf.InsertLiteral("SIZE ", 0); + sizeBuf.AppendLiteral(CRLF); + + return SendFTPCommand(sizeBuf); +} + +FTP_STATE +nsFtpState::R_size() { + if (mResponseCode / 100 == 2) { + PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize); + mChannel->SetContentLength(mFileSize); + } + + // We may want to be able to resume this + return FTP_S_MDTM; +} + +nsresult nsFtpState::S_mdtm() { + nsAutoCString mdtmBuf(mPath); + if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/') mdtmBuf.Insert(mPwd, 0); + if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(mdtmBuf); + mdtmBuf.InsertLiteral("MDTM ", 0); + mdtmBuf.AppendLiteral(CRLF); + + return SendFTPCommand(mdtmBuf); +} + +FTP_STATE +nsFtpState::R_mdtm() { + if (mResponseCode == 213) { + mResponseMsg.Cut(0, 4); + mResponseMsg.Trim(" \t\r\n"); + // yyyymmddhhmmss + if (mResponseMsg.Length() != 14) { + NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response"); + } else { + mModTime = mResponseMsg; + + // Save lastModified time for downloaded files. + nsAutoCString timeString; + nsresult error; + PRExplodedTime exTime; + + mResponseMsg.Mid(timeString, 0, 4); + exTime.tm_year = timeString.ToInteger(&error); + mResponseMsg.Mid(timeString, 4, 2); + exTime.tm_month = timeString.ToInteger(&error) - 1; // january = 0 + mResponseMsg.Mid(timeString, 6, 2); + exTime.tm_mday = timeString.ToInteger(&error); + mResponseMsg.Mid(timeString, 8, 2); + exTime.tm_hour = timeString.ToInteger(&error); + mResponseMsg.Mid(timeString, 10, 2); + exTime.tm_min = timeString.ToInteger(&error); + mResponseMsg.Mid(timeString, 12, 2); + exTime.tm_sec = timeString.ToInteger(&error); + exTime.tm_usec = 0; + + exTime.tm_params.tp_gmt_offset = 0; + exTime.tm_params.tp_dst_offset = 0; + + PR_NormalizeTime(&exTime, PR_GMTParameters); + exTime.tm_params = PR_LocalTimeParameters(&exTime); + + PRTime time = PR_ImplodeTime(&exTime); + (void)mChannel->SetLastModifiedTime(time); + } + } + + nsCString entityID; + entityID.Truncate(); + entityID.AppendInt(int64_t(mFileSize)); + entityID.Append('/'); + entityID.Append(mModTime); + mChannel->SetEntityID(entityID); + + // We weren't asked to resume + if (!mChannel->ResumeRequested()) return FTP_S_RETR; + + // if (our entityID == supplied one (if any)) + if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID)) + return FTP_S_REST; + + mInternalError = NS_ERROR_ENTITY_CHANGED; + mResponseMsg.Truncate(); + return FTP_ERROR; +} + +nsresult nsFtpState::SetContentType() { + // FTP directory URLs don't always end in a slash. Make sure they do. + // This check needs to be here rather than a more obvious place + // (e.g. LIST command processing) so that it ensures the terminating + // slash is appended for the new request case. + + if (!mPath.IsEmpty() && mPath.Last() != '/') { + nsCOMPtr url = (do_QueryInterface(mChannel->URI())); + nsAutoCString filePath; + if (NS_SUCCEEDED(url->GetFilePath(filePath))) { + filePath.Append('/'); + nsresult rv = NS_MutateURI(url).SetFilePath(filePath).Finalize(url); + if (NS_SUCCEEDED(rv)) { + mChannel->UpdateURI(url); + } + } + } + return mChannel->SetContentType( + nsLiteralCString(APPLICATION_HTTP_INDEX_FORMAT)); +} + +nsresult nsFtpState::S_list() { + nsresult rv = SetContentType(); + if (NS_FAILED(rv)) + // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has + // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) + return (nsresult)FTP_ERROR; + + rv = mChannel->PushStreamConverter("text/ftp-dir", + APPLICATION_HTTP_INDEX_FORMAT); + if (NS_FAILED(rv)) { + // clear mResponseMsg which is displayed to the user. + // TODO: we should probably set this to something meaningful. + mResponseMsg = ""; + return rv; + } + + // dir listings aren't resumable + NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE); + + mChannel->SetEntityID(""_ns); + + const char* listString; + if (mServerType == FTP_VMS_TYPE) { + listString = "LIST *.*;0" CRLF; + } else { + listString = "LIST" CRLF; + } + + return SendFTPCommand(nsDependentCString(listString)); +} + +FTP_STATE +nsFtpState::R_list() { + if (mResponseCode / 100 == 1) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_LISTINGS, 1); + + mRlist1xxReceived = true; + + // OK, time to start reading from the data connection. + if (mDataStream && HasPendingCallback()) + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); + return FTP_READ_BUF; + } + + if (mResponseCode / 100 == 2 && mRlist1xxReceived) { + //(DONE) + mNextState = FTP_COMPLETE; + mRlist1xxReceived = false; + return FTP_COMPLETE; + } + return FTP_ERROR; +} + +nsresult nsFtpState::S_retr() { + nsAutoCString retrStr(mPath); + if (retrStr.IsEmpty() || retrStr.First() != '/') retrStr.Insert(mPwd, 0); + if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(retrStr); + retrStr.InsertLiteral("RETR ", 0); + retrStr.AppendLiteral(CRLF); + + return SendFTPCommand(retrStr); +} + +FTP_STATE +nsFtpState::R_retr() { + if (mResponseCode / 100 == 2) { + if (!mRretr1xxReceived) return FTP_ERROR; + + //(DONE) + mNextState = FTP_COMPLETE; + mRretr1xxReceived = false; + return FTP_COMPLETE; + } + + if (mResponseCode / 100 == 1) { + mChannel->SetContentType(nsLiteralCString(APPLICATION_OCTET_STREAM)); + + Telemetry::ScalarAdd( + Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_FILES, 1); + + mRretr1xxReceived = true; + + if (mDataStream && HasPendingCallback()) + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); + return FTP_READ_BUF; + } + + // These error codes are related to problems with the connection. + // If we encounter any at this point, do not try CWD and abort. + if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426) + return FTP_ERROR; + + if (mResponseCode / 100 == 5) { + mRETRFailed = true; + return FTP_S_PASV; + } + + return FTP_S_CWD; +} + +nsresult nsFtpState::S_rest() { + nsAutoCString restString("REST "); + // The int64_t cast is needed to avoid ambiguity + restString.AppendInt(int64_t(mChannel->StartPos()), 10); + restString.AppendLiteral(CRLF); + + return SendFTPCommand(restString); +} + +FTP_STATE +nsFtpState::R_rest() { + if (mResponseCode / 100 == 4) { + // If REST fails, then we can't resume + mChannel->SetEntityID(""_ns); + + mInternalError = NS_ERROR_NOT_RESUMABLE; + mResponseMsg.Truncate(); + + return FTP_ERROR; + } + + return FTP_S_RETR; +} + +nsresult nsFtpState::S_stor() { + NS_ENSURE_STATE(mChannel->UploadStream()); + + NS_ASSERTION(mAction == PUT, "Wrong state to be here"); + + nsCOMPtr url = do_QueryInterface(mChannel->URI()); + NS_ASSERTION(url, "I thought you were a nsStandardURL"); + + nsAutoCString storStr; + url->GetFilePath(storStr); + NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path"); + + // kill the first slash since we want to be relative to CWD. + if (storStr.First() == '/') storStr.Cut(0, 1); + + if (mServerType == FTP_VMS_TYPE) ConvertFilespecToVMS(storStr); + + NS_UnescapeURL(storStr); + storStr.InsertLiteral("STOR ", 0); + storStr.AppendLiteral(CRLF); + + return SendFTPCommand(storStr); +} + +FTP_STATE +nsFtpState::R_stor() { + if (mResponseCode / 100 == 2 && mRstor1xxReceived) { + //(DONE) + mNextState = FTP_COMPLETE; + mStorReplyReceived = true; + mRstor1xxReceived = false; + + // Call Close() if it was not called in nsFtpState::OnStoprequest() + if (!mUploadRequest && !IsClosed()) Close(); + + return FTP_COMPLETE; + } + + if (mResponseCode / 100 == 1) { + Telemetry::ScalarAdd( + Telemetry::ScalarID::NETWORKING_FTP_OPENED_CHANNELS_FILES, 1); + + LOG(("FTP:(%p) writing on DT\n", this)); + mRstor1xxReceived = true; + return FTP_READ_BUF; + } + + mStorReplyReceived = true; + return FTP_ERROR; +} + +nsresult nsFtpState::S_pasv() { + if (!mAddressChecked) { + // Find socket address + mAddressChecked = true; + mServerAddress.raw.family = AF_INET; + mServerAddress.inet.ip = htonl(INADDR_ANY); + mServerAddress.inet.port = htons(0); + + nsITransport* controlSocket = mControlConnection->Transport(); + if (!controlSocket) + // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has + // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109) + return (nsresult)FTP_ERROR; + + nsCOMPtr sTrans = do_QueryInterface(controlSocket); + if (sTrans) { + nsresult rv = sTrans->GetPeerAddr(&mServerAddress); + if (NS_SUCCEEDED(rv)) { + if (!mServerAddress.IsIPAddrAny()) + mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) && + !mServerAddress.IsIPAddrV4Mapped(); + else { + /* + * In case of SOCKS5 remote DNS resolution, we do + * not know the remote IP address. Still, if it is + * an IPV6 host, then the external address of the + * socks server should also be IPv6, and this is the + * self address of the transport. + */ + NetAddr selfAddress; + rv = sTrans->GetSelfAddr(&selfAddress); + if (NS_SUCCEEDED(rv)) + mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) && + !selfAddress.IsIPAddrV4Mapped(); + } + } + } + } + + const char* string; + if (mServerIsIPv6) { + string = "EPSV" CRLF; + } else { + string = "PASV" CRLF; + } + + return SendFTPCommand(nsDependentCString(string)); +} + +FTP_STATE +nsFtpState::R_pasv() { + if (mResponseCode / 100 != 2) return FTP_ERROR; + + nsresult rv; + int32_t port; + + nsAutoCString responseCopy(mResponseMsg); + char* response = responseCopy.BeginWriting(); + + char* ptr = response; + + // Make sure to ignore the address in the PASV response (bug 370559) + + if (mServerIsIPv6) { + // The returned string is of the form + // text (|||ppp|) + // Where '|' can be any single character + char delim; + while (*ptr && *ptr != '(') ptr++; + if (*ptr++ != '(') return FTP_ERROR; + delim = *ptr++; + if (!delim || *ptr++ != delim || *ptr++ != delim || *ptr < '0' || + *ptr > '9') + return FTP_ERROR; + port = 0; + do { + port = port * 10 + *ptr++ - '0'; + } while (*ptr >= '0' && *ptr <= '9'); + if (*ptr++ != delim || *ptr != ')') return FTP_ERROR; + } else { + // The returned address string can be of the form + // (xxx,xxx,xxx,xxx,ppp,ppp) or + // xxx,xxx,xxx,xxx,ppp,ppp (without parens) + int32_t h0, h1, h2, h3, p0, p1; + + int32_t fields = 0; + // First try with parens + while (*ptr && *ptr != '(') ++ptr; + if (*ptr) { + ++ptr; + fields = PR_sscanf(ptr, "%ld,%ld,%ld,%ld,%ld,%ld", &h0, &h1, &h2, &h3, + &p0, &p1); + } + if (!*ptr || fields < 6) { + // OK, lets try w/o parens + ptr = response; + while (*ptr && *ptr != ',') ++ptr; + if (*ptr) { + // backup to the start of the digits + do { + ptr--; + } while ((ptr >= response) && (*ptr >= '0') && (*ptr <= '9')); + ptr++; // get back onto the numbers + fields = PR_sscanf(ptr, "%ld,%ld,%ld,%ld,%ld,%ld", &h0, &h1, &h2, &h3, + &p0, &p1); + } + } + + NS_ASSERTION(fields == 6, "Can't parse PASV response"); + if (fields < 6) return FTP_ERROR; + + port = ((int32_t)(p0 << 8)) + p1; + } + + bool newDataConn = true; + if (mDataTransport) { + // Reuse this connection only if its still alive, and the port + // is the same + nsCOMPtr strans = do_QueryInterface(mDataTransport); + if (strans) { + int32_t oldPort; + nsresult rv = strans->GetPort(&oldPort); + if (NS_SUCCEEDED(rv)) { + if (oldPort == port) { + bool isAlive; + if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive) + newDataConn = false; + } + } + } + + if (newDataConn) { + mDataTransport->Close(NS_ERROR_ABORT); + mDataTransport = nullptr; + if (mDataStream) { + mDataStream->CloseWithStatus(NS_ERROR_ABORT); + mDataStream = nullptr; + } + } + } + + if (newDataConn) { + // now we know where to connect our data channel + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (!sts) return FTP_ERROR; + + nsCOMPtr strans; + + nsAutoCString host; + if (!mServerAddress.IsIPAddrAny()) { + char buf[kIPv6CStrBufSize]; + mServerAddress.ToStringBuffer(buf, sizeof(buf)); + host.Assign(buf); + } else { + /* + * In case of SOCKS5 remote DNS resolving, the peer address + * fetched previously will be invalid (0.0.0.0): it is unknown + * to us. But we can pass on the original hostname to the + * connect for the data connection. + */ + rv = mChannel->URI()->GetAsciiHost(host); + if (NS_FAILED(rv)) return FTP_ERROR; + } + + rv = sts->CreateTransport(nsTArray(), host, port, + mChannel->ProxyInfo(), + getter_AddRefs(strans)); // the data socket + if (NS_FAILED(rv)) return FTP_ERROR; + mDataTransport = strans; + + strans->SetQoSBits(gFtpHandler->GetDataQoSBits()); + + LOG(("FTP:(%p) created DT (%s:%x)\n", this, host.get(), port)); + + // hook ourself up as a proxy for status notifications + rv = mDataTransport->SetEventSink(this, GetCurrentEventTarget()); + NS_ENSURE_SUCCESS(rv, FTP_ERROR); + + if (mAction == PUT) { + NS_ASSERTION(!mRETRFailed, "Failed before uploading"); + + // nsIUploadChannel requires the upload stream to support ReadSegments. + // therefore, we can open an unbuffered socket output stream. + nsCOMPtr output; + rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, + getter_AddRefs(output)); + if (NS_FAILED(rv)) return FTP_ERROR; + + // perform the data copy on the socket transport thread. we do this + // because "output" is a socket output stream, so the result is that + // all work will be done on the socket transport thread. + nsCOMPtr stEventTarget = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (!stEventTarget) return FTP_ERROR; + + nsCOMPtr copier = + do_CreateInstance(NS_ASYNCSTREAMCOPIER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = copier->Init(mChannel->UploadStream(), output, stEventTarget, true, + false /* output is NOT buffered */, 0, true, true); + } + if (NS_FAILED(rv)) return FTP_ERROR; + + rv = copier->AsyncCopy(this, nullptr); + if (NS_FAILED(rv)) return FTP_ERROR; + + // hold a reference to the copier so we can cancel it if necessary. + mUploadRequest = copier; + + // update the current working directory before sending the STOR + // command. this is needed since we might be reusing a control + // connection. + return FTP_S_CWD; + } + + // + // else, we are reading from the data connection... + // + + // open a buffered, asynchronous socket input stream + nsCOMPtr input; + rv = mDataTransport->OpenInputStream(0, nsIOService::gDefaultSegmentSize, + nsIOService::gDefaultSegmentCount, + getter_AddRefs(input)); + NS_ENSURE_SUCCESS(rv, FTP_ERROR); + mDataStream = do_QueryInterface(input); + } + + if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/') return FTP_S_CWD; + return FTP_S_SIZE; +} + +nsresult nsFtpState::S_feat() { + return SendFTPCommand(nsLiteralCString("FEAT" CRLF)); +} + +FTP_STATE +nsFtpState::R_feat() { + if (mResponseCode / 100 == 2) { + if (mResponseMsg.Find(nsLiteralCString(CRLF " UTF8" CRLF), true) > -1) { + // This FTP server supports UTF-8 encoding + mChannel->SetContentCharset("UTF-8"_ns); + mUseUTF8 = true; + return FTP_S_OPTS; + } + } + + mUseUTF8 = false; + return FTP_S_PWD; +} + +nsresult nsFtpState::S_opts() { + // This command is for compatibility of old FTP spec (IETF Draft) + return SendFTPCommand(nsLiteralCString("OPTS UTF8 ON" CRLF)); +} + +FTP_STATE +nsFtpState::R_opts() { + // Ignore error code because "OPTS UTF8 ON" is for compatibility of + // FTP server using IETF draft + return FTP_S_PWD; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsIRequest methods: + +nsresult nsFtpState::Init(nsFtpChannel* channel) { + // parameter validation + NS_ASSERTION(channel, "FTP: needs a channel"); + + mChannel = channel; // a straight ref ptr to the channel + + mKeepRunning = true; + mSuppliedEntityID = channel->EntityID(); + + if (channel->UploadStream()) mAction = PUT; + + nsresult rv; + nsCOMPtr url = do_QueryInterface(mChannel->URI()); + + nsAutoCString host; + if (url) { + rv = url->GetAsciiHost(host); + } else { + rv = mChannel->URI()->GetAsciiHost(host); + } + if (NS_FAILED(rv) || host.IsEmpty()) { + return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString path; + if (url) { + rv = url->GetFilePath(path); + } else { + rv = mChannel->URI()->GetPathQueryRef(path); + } + if (NS_FAILED(rv)) return rv; + + removeParamsFromPath(path); + + nsCOMPtr outURI; + // FTP parameters such as type=i are ignored + if (url) { + rv = NS_MutateURI(url).SetFilePath(path).Finalize(outURI); + } else { + rv = NS_MutateURI(mChannel->URI()).SetPathQueryRef(path).Finalize(outURI); + } + if (NS_SUCCEEDED(rv)) { + mChannel->UpdateURI(outURI); + } + + // Skip leading slash + char* fwdPtr = path.BeginWriting(); + if (!fwdPtr) return NS_ERROR_OUT_OF_MEMORY; + if (*fwdPtr == '/') fwdPtr++; + if (*fwdPtr != '\0') { + // now unescape it... %xx reduced inline to resulting character + int32_t len = NS_UnescapeURL(fwdPtr); + mPath.Assign(fwdPtr, len); + +#ifdef DEBUG + if (mPath.FindCharInSet(CRLF) >= 0) + NS_ERROR("NewURI() should've prevented this!!!"); +#endif + } + + // pull any username and/or password out of the uri + nsAutoCString uname; + rv = mChannel->URI()->GetUsername(uname); + if (NS_FAILED(rv)) return rv; + + if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) { + mAnonymous = false; + CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername); + + // return an error if we find a CR or LF in the username + if (uname.FindCharInSet(CRLF) >= 0) return NS_ERROR_MALFORMED_URI; + } + + nsAutoCString password; + rv = mChannel->URI()->GetPassword(password); + if (NS_FAILED(rv)) return rv; + + CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword); + + // return an error if we find a CR or LF in the password + if (mPassword.FindCharInSet(CRLF) >= 0) return NS_ERROR_MALFORMED_URI; + + int32_t port; + rv = mChannel->URI()->GetPort(&port); + if (NS_FAILED(rv)) return rv; + + if (port > 0) mPort = port; + + // Lookup Proxy information asynchronously if it isn't already set + // on the channel and if we aren't configured explicitly to go directly + nsCOMPtr pps = + do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID); + + if (pps && !mChannel->ProxyInfo()) { + pps->AsyncResolve(static_cast(mChannel), 0, this, nullptr, + getter_AddRefs(mProxyRequest)); + } + + return NS_OK; +} + +void nsFtpState::Connect() { + mState = FTP_COMMAND_CONNECT; + mNextState = FTP_S_USER; + + nsresult rv = Process(); + + // check for errors. + if (NS_FAILED(rv)) { + LOG(("FTP:Process() failed: %" PRIx32 "\n", static_cast(rv))); + mInternalError = NS_ERROR_FAILURE; + mState = FTP_ERROR; + CloseWithStatus(mInternalError); + } +} + +void nsFtpState::KillControlConnection() { + mControlReadCarryOverBuf.Truncate(0); + + mAddressChecked = false; + mServerIsIPv6 = false; + + // if everything went okay, save the connection. + // FIX: need a better way to determine if we can cache the connections. + // there are some errors which do not mean that we need to kill the + // connection e.g. fnf. + + if (!mControlConnection) return; + + // kill the reference to ourselves in the control connection. + mControlConnection->WaitData(nullptr); + + if (NS_SUCCEEDED(mInternalError) && NS_SUCCEEDED(mControlStatus) && + mControlConnection->IsAlive() && mCacheConnection) { + LOG_INFO(("FTP:(%p) caching CC(%p)", this, mControlConnection.get())); + + // Store connection persistent data + mControlConnection->mServerType = mServerType; + mControlConnection->mPassword = mPassword; + mControlConnection->mPwd = mPwd; + mControlConnection->mUseUTF8 = mUseUTF8; + + nsresult rv = NS_OK; + // Don't cache controlconnection if anonymous (bug #473371) + if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS)) + rv = gFtpHandler->InsertConnection(mChannel->URI(), mControlConnection); + // Can't cache it? Kill it then. + mControlConnection->Disconnect(rv); + } else { + mControlConnection->Disconnect(NS_BINDING_ABORTED); + } + + mControlConnection = nullptr; +} + +nsresult nsFtpState::StopProcessing() { + // Only do this function once. + if (!mKeepRunning) return NS_OK; + mKeepRunning = false; + + LOG_INFO(("FTP:(%p) nsFtpState stopping", this)); + + if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) { + // check to see if the control status is bad, forward the error message. + nsCOMPtr ftpChanP; + mChannel->GetCallback(ftpChanP); + if (ftpChanP) { + ftpChanP->SetErrorMsg(mResponseMsg.get(), mUseUTF8); + } + } + + nsresult broadcastErrorCode = mControlStatus; + if (NS_SUCCEEDED(broadcastErrorCode)) broadcastErrorCode = mInternalError; + + mInternalError = broadcastErrorCode; + + KillControlConnection(); + + // XXX This can fire before we are done loading data. Is that a problem? + OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0); + + if (NS_FAILED(broadcastErrorCode)) CloseWithStatus(broadcastErrorCode); + + return NS_OK; +} + +nsresult nsFtpState::SendFTPCommand(const nsACString& command) { + NS_ASSERTION(mControlConnection, "null control connection"); + + // we don't want to log the password: + nsAutoCString logcmd(command); + if (StringBeginsWith(command, "PASS "_ns)) logcmd = "PASS xxxxx"; + + LOG(("FTP:(%p) writing \"%s\"\n", this, logcmd.get())); + + nsCOMPtr ftpSink; + mChannel->GetFTPEventSink(ftpSink); + if (ftpSink) ftpSink->OnFTPControlLog(false, logcmd.get()); + + if (mControlConnection) return mControlConnection->Write(command); + + return NS_ERROR_FAILURE; +} + +// Convert a unix-style filespec to VMS format +// /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt +// /foo/file.txt -> foo:[000000]file.txt +void nsFtpState::ConvertFilespecToVMS(nsCString& fileString) { + int ntok = 1; + char *t, *nextToken; + nsAutoCString fileStringCopy; + + // Get a writeable copy we can strtok with. + fileStringCopy = fileString; + t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken); + if (t) + while (nsCRT::strtok(nextToken, "/", &nextToken)) + ntok++; // count number of terms (tokens) + LOG(("FTP:(%p) ConvertFilespecToVMS ntok: %d\n", this, ntok)); + LOG(("FTP:(%p) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get())); + + if (fileString.First() == '/') { + // absolute filespec + // / -> [] + // /a -> a (doesn't really make much sense) + // /a/b -> a:[000000]b + // /a/b/c -> a:[b]c + // /a/b/c/d -> a:[b.c]d + if (ntok == 1) { + if (fileString.Length() == 1) { + // Just a slash + fileString.Truncate(); + fileString.AppendLiteral("[]"); + } else { + // just copy the name part (drop the leading slash) + fileStringCopy = fileString; + fileString = Substring(fileStringCopy, 1, fileStringCopy.Length() - 1); + } + } else { + // Get another copy since the last one was written to. + fileStringCopy = fileString; + fileString.Truncate(); + fileString.Append( + nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken)); + fileString.AppendLiteral(":["); + if (ntok > 2) { + for (int i = 2; i < ntok; i++) { + if (i > 2) fileString.Append('.'); + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); + } + } else { + fileString.AppendLiteral("000000"); + } + fileString.Append(']'); + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); + } + } else { + // relative filespec + // a -> a + // a/b -> [.a]b + // a/b/c -> [.a.b]c + if (ntok == 1) { + // no slashes, just use the name as is + } else { + // Get another copy since the last one was written to. + fileStringCopy = fileString; + fileString.Truncate(); + fileString.AppendLiteral("[."); + fileString.Append( + nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken)); + if (ntok > 2) { + for (int i = 2; i < ntok; i++) { + fileString.Append('.'); + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); + } + } + fileString.Append(']'); + fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken)); + } + } + LOG(("FTP:(%p) ConvertFilespecToVMS to: \"%s\"\n", this, fileString.get())); +} + +// Convert a unix-style dirspec to VMS format +// /foo/fred/barney/rubble -> foo:[fred.barney.rubble] +// /foo/fred -> foo:[fred] +// /foo -> foo:[000000] +// (null) -> (null) +void nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec) { + LOG(("FTP:(%p) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get())); + if (!dirSpec.IsEmpty()) { + if (dirSpec.Last() != '/') dirSpec.Append('/'); + // we can use the filespec routine if we make it look like a file name + dirSpec.Append('x'); + ConvertFilespecToVMS(dirSpec); + dirSpec.Truncate(dirSpec.Length() - 1); + } + LOG(("FTP:(%p) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get())); +} + +// Convert an absolute VMS style dirspec to UNIX format +void nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec) { + LOG(("FTP:(%p) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get())); + if (dirSpec.IsEmpty()) { + dirSpec.Insert('.', 0); + } else { + dirSpec.Insert('/', 0); + dirSpec.ReplaceSubstring(":[", "/"); + dirSpec.ReplaceChar('.', '/'); + dirSpec.ReplaceChar(']', '/'); + } + LOG(("FTP:(%p) ConvertDirspecFromVMS to: \"%s\"\n", this, dirSpec.get())); +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpState::OnTransportStatus(nsITransport* transport, nsresult status, + int64_t progress, int64_t progressMax) { + // Mix signals from both the control and data connections. + + // Ignore data transfer events on the control connection. + if (mControlConnection && transport == mControlConnection->Transport()) { + switch (status) { + case NS_NET_STATUS_RESOLVING_HOST: + case NS_NET_STATUS_RESOLVED_HOST: + case NS_NET_STATUS_CONNECTING_TO: + case NS_NET_STATUS_CONNECTED_TO: + case NS_NET_STATUS_TLS_HANDSHAKE_STARTING: + case NS_NET_STATUS_TLS_HANDSHAKE_ENDED: + break; + default: + return NS_OK; + } + } + + // Ignore the progressMax value from the socket. We know the true size of + // the file based on the response from our SIZE request. Additionally, only + // report the max progress based on where we started/resumed. + mChannel->OnTransportStatus(nullptr, status, progress, + mFileSize - mChannel->StartPos()); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpState::OnStartRequest(nsIRequest* request) { + mStorReplyReceived = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpState::OnStopRequest(nsIRequest* request, nsresult status) { + mUploadRequest = nullptr; + + // Close() will be called when reply to STOR command is received + // see bug #389394 + if (!mStorReplyReceived) return NS_OK; + + // We're done uploading. Let our consumer know that we're done. + Close(); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpState::Available(uint64_t* result) { + if (mDataStream) return mDataStream->Available(result); + + return nsBaseContentStream::Available(result); +} + +NS_IMETHODIMP +nsFtpState::ReadSegments(nsWriteSegmentFun writer, void* closure, + uint32_t count, uint32_t* result) { + // Insert a thunk here so that the input stream passed to the writer is this + // input stream instead of mDataStream. + + if (mDataStream) { + nsWriteSegmentThunk thunk = {this, writer, closure}; + nsresult rv; + rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count, result); + return rv; + } + + return nsBaseContentStream::ReadSegments(writer, closure, count, result); +} + +NS_IMETHODIMP +nsFtpState::CloseWithStatus(nsresult status) { + LOG(("FTP:(%p) close [%" PRIx32 "]\n", this, static_cast(status))); + + // Shutdown the control connection processing if we are being closed with an + // error. Note: This method may be called several times. + if (!IsClosed() && NS_FAILED(status)) { + if (NS_SUCCEEDED(mInternalError)) mInternalError = status; + StopProcessing(); + } + + if (mUploadRequest) { + mUploadRequest->Cancel(NS_ERROR_ABORT); + mUploadRequest = nullptr; + } + + if (mDataTransport) { + // Shutdown the data transport. + mDataTransport->Close(NS_ERROR_ABORT); + mDataTransport = nullptr; + } + + if (mDataStream) { + mDataStream->CloseWithStatus(NS_ERROR_ABORT); + mDataStream = nullptr; + } + + return nsBaseContentStream::CloseWithStatus(status); +} + +static nsresult CreateHTTPProxiedChannel(nsIChannel* channel, nsIProxyInfo* pi, + nsIChannel** newChannel) { + nsresult rv; + nsCOMPtr ioService = do_GetIOService(&rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr handler; + rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr pph = do_QueryInterface(handler, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr uri; + channel->GetURI(getter_AddRefs(uri)); + + nsCOMPtr loadInfo = channel->LoadInfo(); + + return pph->NewProxiedChannel(uri, pi, 0, nullptr, loadInfo, newChannel); +} + +NS_IMETHODIMP +nsFtpState::OnProxyAvailable(nsICancelable* request, nsIChannel* channel, + nsIProxyInfo* pi, nsresult status) { + mProxyRequest = nullptr; + + // failed status code just implies DIRECT processing + + if (NS_SUCCEEDED(status)) { + nsAutoCString type; + if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) { + // Proxy the FTP url via HTTP + // This would have been easier to just return a HTTP channel directly + // from nsIIOService::NewChannelFromURI(), but the proxy type cannot + // be reliabliy determined synchronously without jank due to pac, etc.. + LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this)); + + nsCOMPtr newChannel; + if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi, + getter_AddRefs(newChannel))) && + NS_SUCCEEDED(mChannel->Redirect( + newChannel, nsIChannelEventSink::REDIRECT_INTERNAL, true))) { + LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this)); + return NS_OK; + } + } else if (pi) { + // Proxy using the FTP protocol routed through a socks proxy + LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this)); + mChannel->SetProxyInfo(pi); + } + } + + if (mDeferredCallbackPending) { + mDeferredCallbackPending = false; + OnCallbackPending(); + } + return NS_OK; +} + +void nsFtpState::OnCallbackPending() { + if (mState == FTP_INIT) { + if (mProxyRequest) { + mDeferredCallbackPending = true; + return; + } + Connect(); + } else if (mDataStream) { + mDataStream->AsyncWait(this, 0, 0, CallbackTarget()); + } +} diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.h b/netwerk/protocol/ftp/nsFtpConnectionThread.h new file mode 100644 index 0000000000..ec1cf999c7 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.h @@ -0,0 +1,254 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef __nsFtpConnectionThread__h_ +#define __nsFtpConnectionThread__h_ + +#include "nsBaseContentStream.h" + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsITransport.h" +#include "mozilla/net/DNS.h" +#include "nsFtpControlConnection.h" +#include "nsIProtocolProxyCallback.h" + +// ftp server types +#define FTP_GENERIC_TYPE 0 +#define FTP_UNIX_TYPE 1 +#define FTP_VMS_TYPE 8 +#define FTP_NT_TYPE 9 +#define FTP_OS2_TYPE 11 + +// ftp states +typedef enum _FTP_STATE { + /////////////////////// + //// Internal states + FTP_INIT, + FTP_COMMAND_CONNECT, + FTP_READ_BUF, + FTP_ERROR, + FTP_COMPLETE, + + /////////////////////// + //// Command channel connection setup states + FTP_S_USER, + FTP_R_USER, + FTP_S_PASS, + FTP_R_PASS, + FTP_S_SYST, + FTP_R_SYST, + FTP_S_ACCT, + FTP_R_ACCT, + FTP_S_TYPE, + FTP_R_TYPE, + FTP_S_CWD, + FTP_R_CWD, + FTP_S_SIZE, + FTP_R_SIZE, + FTP_S_MDTM, + FTP_R_MDTM, + FTP_S_REST, + FTP_R_REST, + FTP_S_RETR, + FTP_R_RETR, + FTP_S_STOR, + FTP_R_STOR, + FTP_S_LIST, + FTP_R_LIST, + FTP_S_PASV, + FTP_R_PASV, + FTP_S_PWD, + FTP_R_PWD, + FTP_S_FEAT, + FTP_R_FEAT, + FTP_S_OPTS, + FTP_R_OPTS +} FTP_STATE; + +// higher level ftp actions +typedef enum _FTP_ACTION { GET, PUT } FTP_ACTION; + +class nsFtpChannel; +class nsICancelable; +class nsIProxyInfo; +class nsIStreamListener; + +// The nsFtpState object is the content stream for the channel. It implements +// nsIInputStreamCallback, so it can read data from the control connection. It +// implements nsITransportEventSink so it can mix status events from both the +// control connection and the data connection. + +class nsFtpState final : public nsBaseContentStream, + public nsIInputStreamCallback, + public nsITransportEventSink, + public nsIRequestObserver, + public nsFtpControlConnectionListener, + public nsIProtocolProxyCallback { + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSIPROTOCOLPROXYCALLBACK + + // Override input stream methods: + NS_IMETHOD CloseWithStatus(nsresult status) override; + NS_IMETHOD Available(uint64_t* result) override; + NS_IMETHOD ReadSegments(nsWriteSegmentFun fun, void* closure, uint32_t count, + uint32_t* result) override; + + // nsFtpControlConnectionListener methods: + virtual void OnControlDataAvailable(const char* data, + uint32_t dataLen) override; + virtual void OnControlError(nsresult status) override; + + nsFtpState(); + nsresult Init(nsFtpChannel* channel); + + protected: + // Notification from nsBaseContentStream::AsyncWait + virtual void OnCallbackPending() override; + + private: + virtual ~nsFtpState(); + + /////////////////////////////////// + // BEGIN: STATE METHODS + nsresult S_user(); + FTP_STATE R_user(); + nsresult S_pass(); + FTP_STATE R_pass(); + nsresult S_syst(); + FTP_STATE R_syst(); + nsresult S_acct(); + FTP_STATE R_acct(); + + nsresult S_type(); + FTP_STATE R_type(); + nsresult S_cwd(); + FTP_STATE R_cwd(); + + nsresult S_size(); + FTP_STATE R_size(); + nsresult S_mdtm(); + FTP_STATE R_mdtm(); + nsresult S_list(); + FTP_STATE R_list(); + + nsresult S_rest(); + FTP_STATE R_rest(); + nsresult S_retr(); + FTP_STATE R_retr(); + nsresult S_stor(); + FTP_STATE R_stor(); + nsresult S_pasv(); + FTP_STATE R_pasv(); + nsresult S_pwd(); + FTP_STATE R_pwd(); + nsresult S_feat(); + FTP_STATE R_feat(); + nsresult S_opts(); + FTP_STATE R_opts(); + // END: STATE METHODS + /////////////////////////////////// + + // internal methods + void MoveToNextState(FTP_STATE nextState); + nsresult Process(); + + void KillControlConnection(); + nsresult StopProcessing(); + nsresult EstablishControlConnection(); + nsresult SendFTPCommand(const nsACString& command); + void ConvertFilespecToVMS(nsCString& fileSpec); + void ConvertDirspecToVMS(nsCString& fileSpec); + void ConvertDirspecFromVMS(nsCString& fileSpec); + nsresult BuildStreamConverter(nsIStreamListener** convertStreamListener); + nsresult SetContentType(); + + /** + * This method is called to kick-off the FTP state machine. mState is + * reset to FTP_COMMAND_CONNECT, and the FTP state machine progresses from + * there. This method is initially called (indirectly) from the channel's + * AsyncOpen implementation. + */ + void Connect(); + + /////////////////////////////////// + // Private members + + nsCOMPtr mHandler; // Ref to gFtpHandler + + // ****** state machine vars + FTP_STATE mState; // the current state + FTP_STATE mNextState; // the next state + bool mKeepRunning; // thread event loop boolean + int32_t mResponseCode; // the last command response code + nsCString mResponseMsg; // the last command response text + + // ****** channel/transport/stream vars + RefPtr + mControlConnection; // cacheable control connection (owns mCPipe) + bool mReceivedControlData; + bool mTryingCachedControl; // retrying the password + bool mRETRFailed; // Did we already try a RETR and it failed? + uint64_t mFileSize; + nsCString mModTime; + + // ****** consumer vars + RefPtr + mChannel; // our owning FTP channel we pass through our events + nsCOMPtr mProxyInfo; + + // ****** connection cache vars + int32_t mServerType; // What kind of server are we talking to + + // ****** protocol interpretation related state vars + nsString mUsername; // username + nsString mPassword; // password + FTP_ACTION mAction; // the higher level action (GET/PUT) + bool mAnonymous; // try connecting anonymous (default) + bool mRetryPass; // retrying the password + bool mStorReplyReceived; // FALSE if waiting for STOR + // completion status from server + bool mRlist1xxReceived; // TRUE if we have received a LIST 1xx + // response from the server + bool mRretr1xxReceived; // TRUE if we have received a RETR 1xx + // response from the server + bool mRstor1xxReceived; // TRUE if we have received a STOR 1xx + // response from the server + nsresult mInternalError; // represents internal state errors + bool mReconnectAndLoginAgain; + bool mCacheConnection; + + // ****** URI vars + int32_t mPort; // the port to connect to + nsString mFilename; // url filename (if any) + nsCString mPath; // the url's path + nsCString mPwd; // login Path + + // ****** other vars + nsCOMPtr mDataTransport; + nsCOMPtr mDataStream; + nsCOMPtr mUploadRequest; + bool mAddressChecked; + bool mServerIsIPv6; + bool mUseUTF8; + + mozilla::net::NetAddr mServerAddress; + + // ***** control read gvars + nsresult mControlStatus; + nsCString mControlReadCarryOverBuf; + + nsCString mSuppliedEntityID; + + nsCOMPtr mProxyRequest; + bool mDeferredCallbackPending; +}; + +#endif //__nsFtpConnectionThread__h_ diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.cpp b/netwerk/protocol/ftp/nsFtpControlConnection.cpp new file mode 100644 index 0000000000..1d54ed9de2 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpControlConnection.cpp @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsIOService.h" +#include "nsFtpControlConnection.h" +#include "nsFtpProtocolHandler.h" +#include "mozilla/Logging.h" +#include "nsIInputStream.h" +#include "nsISocketTransportService.h" +#include "nsISocketTransport.h" +#include "nsThreadUtils.h" +#include "nsNetCID.h" +#include "nsTArray.h" +#include + +using namespace mozilla; +using namespace mozilla::net; + +extern LazyLogModule gFTPLog; +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) +#define LOG_INFO(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Info, args) + +// +// nsFtpControlConnection implementation ... +// + +NS_IMPL_ISUPPORTS(nsFtpControlConnection, nsIInputStreamCallback) + +NS_IMETHODIMP +nsFtpControlConnection::OnInputStreamReady(nsIAsyncInputStream* stream) { + char data[4096]; + + // Consume data whether we have a listener or not. + uint64_t avail64; + uint32_t avail = 0; + nsresult rv = stream->Available(&avail64); + if (NS_SUCCEEDED(rv)) { + avail = (uint32_t)std::min(avail64, (uint64_t)sizeof(data)); + + uint32_t n; + rv = stream->Read(data, avail, &n); + if (NS_SUCCEEDED(rv)) avail = n; + } + + // It's important that we null out mListener before calling one of its + // methods as it may call WaitData, which would queue up another read. + + RefPtr listener; + listener.swap(mListener); + + if (!listener) return NS_OK; + + if (NS_FAILED(rv)) { + listener->OnControlError(rv); + } else { + listener->OnControlDataAvailable(data, avail); + } + + return NS_OK; +} + +nsFtpControlConnection::nsFtpControlConnection(const nsACString& host, + uint32_t port) + : mServerType(0), + mSuspendedWrite(0), + mSessionId(gFtpHandler->GetSessionId()), + mUseUTF8(false), + mHost(host), + mPort(port) { + LOG_INFO(("FTP:CC created @%p", this)); +} + +nsFtpControlConnection::~nsFtpControlConnection() { + LOG_INFO(("FTP:CC destroyed @%p", this)); +} + +bool nsFtpControlConnection::IsAlive() { + if (!mSocket) return false; + + bool isAlive = false; + mSocket->IsAlive(&isAlive); + return isAlive; +} +nsresult nsFtpControlConnection::Connect(nsIProxyInfo* proxyInfo, + nsITransportEventSink* eventSink) { + if (mSocket) return NS_OK; + + // build our own + nsresult rv; + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = sts->CreateTransport(nsTArray(), mHost, mPort, proxyInfo, + getter_AddRefs(mSocket)); // the command transport + if (NS_FAILED(rv)) return rv; + + mSocket->SetQoSBits(gFtpHandler->GetControlQoSBits()); + + // proxy transport events back to current thread + if (eventSink) mSocket->SetEventSink(eventSink, GetCurrentEventTarget()); + + // open buffered, blocking output stream to socket. so long as commands + // do not exceed 1024 bytes in length, the writing thread (the main thread) + // will not block. this should be OK. + rv = mSocket->OpenOutputStream(nsITransport::OPEN_BLOCKING, 1024, 1, + getter_AddRefs(mSocketOutput)); + if (NS_FAILED(rv)) return rv; + + // open buffered, non-blocking/asynchronous input stream to socket. + nsCOMPtr inStream; + rv = mSocket->OpenInputStream(0, nsIOService::gDefaultSegmentSize, + nsIOService::gDefaultSegmentCount, + getter_AddRefs(inStream)); + if (NS_SUCCEEDED(rv)) mSocketInput = do_QueryInterface(inStream); + + return rv; +} + +nsresult nsFtpControlConnection::WaitData( + nsFtpControlConnectionListener* listener) { + LOG(("FTP:(%p) wait data [listener=%p]\n", this, listener)); + + // If listener is null, then simply disconnect the listener. Otherwise, + // ensure that we are listening. + if (!listener) { + mListener = nullptr; + return NS_OK; + } + + NS_ENSURE_STATE(mSocketInput); + + mListener = listener; + return mSocketInput->AsyncWait(this, 0, 0, GetCurrentEventTarget()); +} + +nsresult nsFtpControlConnection::Disconnect(nsresult status) { + if (!mSocket) return NS_OK; // already disconnected + + LOG_INFO(("FTP:(%p) CC disconnecting (%" PRIx32 ")", this, + static_cast(status))); + + if (NS_FAILED(status)) { + // break cyclic reference! + mSocket->Close(status); + mSocket = nullptr; + mSocketInput->AsyncWait(nullptr, 0, 0, nullptr); // clear any observer + mSocketInput = nullptr; + mSocketOutput = nullptr; + } + + return NS_OK; +} + +nsresult nsFtpControlConnection::Write(const nsACString& command) { + NS_ENSURE_STATE(mSocketOutput); + + uint32_t len = command.Length(); + uint32_t cnt; + nsresult rv = mSocketOutput->Write(command.Data(), len, &cnt); + + if (NS_FAILED(rv)) return rv; + + if (len != cnt) return NS_ERROR_FAILURE; + + return NS_OK; +} diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.h b/netwerk/protocol/ftp/nsFtpControlConnection.h new file mode 100644 index 0000000000..c29e23eeed --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpControlConnection.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set et ts=4 sts=2 sw=2 cin: */ +/* 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/. */ + +#ifndef nsFtpControlConnection_h___ +#define nsFtpControlConnection_h___ + +#include "nsCOMPtr.h" + +#include "nsISocketTransport.h" +#include "nsIAsyncInputStream.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsIOutputStream; +class nsIProxyInfo; +class nsITransportEventSink; + +class nsFtpControlConnectionListener : public nsISupports { + public: + /** + * Called when a chunk of data arrives on the control connection. + * @param data + * The new data or null if an error occurred. + * @param dataLen + * The data length in bytes. + */ + virtual void OnControlDataAvailable(const char* data, uint32_t dataLen) = 0; + + /** + * Called when an error occurs on the control connection. + * @param status + * A failure code providing more info about the error. + */ + virtual void OnControlError(nsresult status) = 0; +}; + +class nsFtpControlConnection final : public nsIInputStreamCallback { + ~nsFtpControlConnection(); + + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAMCALLBACK + + nsFtpControlConnection(const nsACString& host, uint32_t port); + + nsresult Connect(nsIProxyInfo* proxyInfo, nsITransportEventSink* eventSink); + nsresult Disconnect(nsresult status); + nsresult Write(const nsACString& command); + + bool IsAlive(); + + nsITransport* Transport() { return mSocket; } + + /** + * Call this function to be notified asynchronously when there is data + * available for the socket. The listener passed to this method replaces + * any existing listener, and the listener can be null to disconnect the + * previous listener. + */ + nsresult WaitData(nsFtpControlConnectionListener* listener); + + uint32_t mServerType; // what kind of server is it. + nsString mPassword; + int32_t mSuspendedWrite; + nsCString mPwd; + uint32_t mSessionId; + bool mUseUTF8; + + private: + nsCString mHost; + uint32_t mPort; + + nsCOMPtr mSocket; + nsCOMPtr mSocketOutput; + nsCOMPtr mSocketInput; + + RefPtr mListener; +}; + +#endif diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp new file mode 100644 index 0000000000..99c8a19693 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/FTPChannelChild.h" +using namespace mozilla; +using namespace mozilla::net; + +#include "nsFtpProtocolHandler.h" +#include "nsFTPChannel.h" +#include "mozilla/Logging.h" +#include "nsIPrefBranch.h" +#include "nsIObserverService.h" +#include "nsEscape.h" +#include "nsAlgorithm.h" + +//----------------------------------------------------------------------------- + +// +// Log module for FTP Protocol logging... +// +// To enable logging (see prlog.h for full details): +// +// set MOZ_LOG=nsFtp:5 +// set MOZ_LOG_FILE=ftp.log +// +// This enables LogLevel::Debug level information and places all output in +// the file ftp.log. +// +LazyLogModule gFTPLog("nsFtp"); +#undef LOG +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) + +//----------------------------------------------------------------------------- + +#define IDLE_TIMEOUT_PREF "network.ftp.idleConnectionTimeout" +#define IDLE_CONNECTION_LIMIT 8 /* TODO pref me */ + +#define ENABLED_PREF "network.ftp.enabled" +#define QOS_DATA_PREF "network.ftp.data.qos" +#define QOS_CONTROL_PREF "network.ftp.control.qos" + +nsFtpProtocolHandler* gFtpHandler = nullptr; + +//----------------------------------------------------------------------------- + +nsFtpProtocolHandler::nsFtpProtocolHandler() + : mIdleTimeout(-1), + mEnabled(true), + mSessionId(0), + mControlQoSBits(0x00), + mDataQoSBits(0x00) { + LOG(("FTP:creating handler @%p\n", this)); + + gFtpHandler = this; +} + +nsFtpProtocolHandler::~nsFtpProtocolHandler() { + LOG(("FTP:destroying handler @%p\n", this)); + + NS_ASSERTION(mRootConnectionList.Length() == 0, "why wasn't Observe called?"); + + gFtpHandler = nullptr; +} + +NS_IMPL_ISUPPORTS(nsFtpProtocolHandler, nsIProtocolHandler, + nsIProxiedProtocolHandler, nsIObserver, + nsISupportsWeakReference) + +nsresult nsFtpProtocolHandler::Init() { + if (IsNeckoChild()) NeckoChild::InitNeckoChild(); + + if (mIdleTimeout == -1) { + nsresult rv; + nsCOMPtr branch = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &mIdleTimeout); + if (NS_FAILED(rv)) mIdleTimeout = 5 * 60; // 5 minute default + + rv = branch->AddObserver(IDLE_TIMEOUT_PREF, this, true); + if (NS_FAILED(rv)) return rv; + + rv = branch->GetBoolPref(ENABLED_PREF, &mEnabled); + if (NS_FAILED(rv)) mEnabled = true; + + rv = branch->AddObserver(ENABLED_PREF, this, true); + if (NS_FAILED(rv)) return rv; + + int32_t val; + rv = branch->GetIntPref(QOS_DATA_PREF, &val); + if (NS_SUCCEEDED(rv)) mDataQoSBits = (uint8_t)clamped(val, 0, 0xff); + + rv = branch->AddObserver(QOS_DATA_PREF, this, true); + if (NS_FAILED(rv)) return rv; + + rv = branch->GetIntPref(QOS_CONTROL_PREF, &val); + if (NS_SUCCEEDED(rv)) mControlQoSBits = (uint8_t)clamped(val, 0, 0xff); + + rv = branch->AddObserver(QOS_CONTROL_PREF, this, true); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + if (observerService) { + observerService->AddObserver(this, "network:offline-about-to-go-offline", + true); + + observerService->AddObserver(this, "net:clear-active-logins", true); + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIProtocolHandler methods: + +NS_IMETHODIMP +nsFtpProtocolHandler::GetScheme(nsACString& result) { + result.AssignLiteral("ftp"); + return NS_OK; +} + +NS_IMETHODIMP +nsFtpProtocolHandler::GetDefaultPort(int32_t* result) { + *result = 21; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpProtocolHandler::GetProtocolFlags(uint32_t* result) { + *result = URI_STD | ALLOWS_PROXY | ALLOWS_PROXY_HTTP | URI_LOADABLE_BY_ANYONE; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewChannel(nsIURI* url, nsILoadInfo* aLoadInfo, + nsIChannel** result) { + return NewProxiedChannel(url, nullptr, 0, nullptr, aLoadInfo, result); +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo, + uint32_t proxyResolveFlags, + nsIURI* proxyURI, + nsILoadInfo* aLoadInfo, + nsIChannel** result) { + if (!mEnabled) { + return NS_ERROR_UNKNOWN_PROTOCOL; + } + + NS_ENSURE_ARG_POINTER(uri); + RefPtr channel; + if (IsNeckoChild()) + channel = new FTPChannelChild(uri); + else + channel = new nsFtpChannel(uri, proxyInfo); + + // set the loadInfo on the new channel + nsresult rv = channel->SetLoadInfo(aLoadInfo); + NS_ENSURE_SUCCESS(rv, rv); + + channel.forget(result); + return rv; +} + +NS_IMETHODIMP +nsFtpProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + *_retval = (port == 21 || port == 22); + return NS_OK; +} + +// connection cache methods + +void nsFtpProtocolHandler::Timeout(nsITimer* aTimer, void* aClosure) { + LOG(("FTP:timeout reached for %p\n", aClosure)); + + bool found = gFtpHandler->mRootConnectionList.RemoveElement(aClosure); + if (!found) { + NS_ERROR("timerStruct not found"); + return; + } + + timerStruct* s = (timerStruct*)aClosure; + delete s; +} + +nsresult nsFtpProtocolHandler::RemoveConnection( + nsIURI* aKey, nsFtpControlConnection** _retval) { + NS_ASSERTION(_retval, "null pointer"); + NS_ASSERTION(aKey, "null pointer"); + + *_retval = nullptr; + + nsAutoCString spec; + aKey->GetPrePath(spec); + + LOG(("FTP:removing connection for %s\n", spec.get())); + + timerStruct* ts = nullptr; + uint32_t i; + bool found = false; + + for (i = 0; i < mRootConnectionList.Length(); ++i) { + ts = mRootConnectionList[i]; + if (strcmp(spec.get(), ts->key) == 0) { + found = true; + mRootConnectionList.RemoveElementAt(i); + break; + } + } + + if (!found) return NS_ERROR_FAILURE; + + // swap connection ownership + ts->conn.forget(_retval); + delete ts; + + return NS_OK; +} + +nsresult nsFtpProtocolHandler::InsertConnection(nsIURI* aKey, + nsFtpControlConnection* aConn) { + NS_ASSERTION(aConn, "null pointer"); + NS_ASSERTION(aKey, "null pointer"); + + if (aConn->mSessionId != mSessionId) return NS_ERROR_FAILURE; + + nsAutoCString spec; + aKey->GetPrePath(spec); + + LOG(("FTP:inserting connection for %s\n", spec.get())); + + timerStruct* ts = new timerStruct(); + if (!ts) return NS_ERROR_OUT_OF_MEMORY; + + nsCOMPtr timer; + nsresult rv = NS_NewTimerWithFuncCallback( + getter_AddRefs(timer), nsFtpProtocolHandler::Timeout, ts, + mIdleTimeout * 1000, nsITimer::TYPE_REPEATING_SLACK, + "nsFtpProtocolHandler::InsertConnection"); + if (NS_FAILED(rv)) { + delete ts; + return rv; + } + + ts->key = ToNewCString(spec, mozilla::fallible); + if (!ts->key) { + delete ts; + return NS_ERROR_OUT_OF_MEMORY; + } + + // ts->conn is a RefPtr + ts->conn = aConn; + ts->timer = timer; + + // + // limit number of idle connections. if limit is reached, then prune + // eldest connection with matching key. if none matching, then prune + // eldest connection. + // + if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) { + uint32_t i; + for (i = 0; i < mRootConnectionList.Length(); ++i) { + timerStruct* candidate = mRootConnectionList[i]; + if (strcmp(candidate->key, ts->key) == 0) { + mRootConnectionList.RemoveElementAt(i); + delete candidate; + break; + } + } + if (mRootConnectionList.Length() == IDLE_CONNECTION_LIMIT) { + timerStruct* eldest = mRootConnectionList[0]; + mRootConnectionList.RemoveElementAt(0); + delete eldest; + } + } + + mRootConnectionList.AppendElement(ts); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsIObserver + +NS_IMETHODIMP +nsFtpProtocolHandler::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + LOG(("FTP:observing [%s]\n", aTopic)); + + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + nsCOMPtr branch = do_QueryInterface(aSubject); + if (!branch) { + NS_ERROR("no prefbranch"); + return NS_ERROR_UNEXPECTED; + } + int32_t val; + nsresult rv = branch->GetIntPref(IDLE_TIMEOUT_PREF, &val); + if (NS_SUCCEEDED(rv)) mIdleTimeout = val; + bool enabled; + rv = branch->GetBoolPref(ENABLED_PREF, &enabled); + if (NS_SUCCEEDED(rv)) mEnabled = enabled; + + rv = branch->GetIntPref(QOS_DATA_PREF, &val); + if (NS_SUCCEEDED(rv)) mDataQoSBits = (uint8_t)clamped(val, 0, 0xff); + + rv = branch->GetIntPref(QOS_CONTROL_PREF, &val); + if (NS_SUCCEEDED(rv)) mControlQoSBits = (uint8_t)clamped(val, 0, 0xff); + } else if (!strcmp(aTopic, "network:offline-about-to-go-offline")) { + ClearAllConnections(); + } else if (!strcmp(aTopic, "net:clear-active-logins")) { + ClearAllConnections(); + mSessionId++; + } else { + MOZ_ASSERT_UNREACHABLE("unexpected topic"); + } + + return NS_OK; +} + +void nsFtpProtocolHandler::ClearAllConnections() { + uint32_t i; + for (i = 0; i < mRootConnectionList.Length(); ++i) + delete mRootConnectionList[i]; + mRootConnectionList.Clear(); +} diff --git a/netwerk/protocol/ftp/nsFtpProtocolHandler.h b/netwerk/protocol/ftp/nsFtpProtocolHandler.h new file mode 100644 index 0000000000..38ce33f8b7 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsFtpProtocolHandler_h__ +#define nsFtpProtocolHandler_h__ + +#include "nsFtpControlConnection.h" +#include "nsIProxiedProtocolHandler.h" +#include "nsTArray.h" +#include "nsITimer.h" +#include "nsIObserver.h" +#include "nsWeakReference.h" + +//----------------------------------------------------------------------------- + +class nsFtpProtocolHandler final : public nsIProxiedProtocolHandler, + public nsIObserver, + public nsSupportsWeakReference { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + NS_DECL_NSIPROXIEDPROTOCOLHANDLER + NS_DECL_NSIOBSERVER + + nsFtpProtocolHandler(); + + nsresult Init(); + + // FTP Connection list access + nsresult InsertConnection(nsIURI* aKey, nsFtpControlConnection* aConn); + nsresult RemoveConnection(nsIURI* aKey, nsFtpControlConnection** aConn); + uint32_t GetSessionId() { return mSessionId; } + + uint8_t GetDataQoSBits() { return mDataQoSBits; } + uint8_t GetControlQoSBits() { return mControlQoSBits; } + + private: + virtual ~nsFtpProtocolHandler(); + + // Stuff for the timer callback function + struct timerStruct { + nsCOMPtr timer; + RefPtr conn; + char* key; + + timerStruct() : key(nullptr) {} + + ~timerStruct() { + if (timer) timer->Cancel(); + if (key) free(key); + if (conn) { + conn->Disconnect(NS_ERROR_ABORT); + } + } + }; + + static void Timeout(nsITimer* aTimer, void* aClosure); + void ClearAllConnections(); + + nsTArray mRootConnectionList; + + int32_t mIdleTimeout; + bool mEnabled; + + // When "clear active logins" is performed, all idle connection are dropped + // and mSessionId is incremented. When nsFtpState wants to insert idle + // connection we refuse to cache if its mSessionId is different (i.e. + // control connection had been created before last "clear active logins" was + // performed. + uint32_t mSessionId; + + uint8_t mControlQoSBits; + uint8_t mDataQoSBits; +}; + +//----------------------------------------------------------------------------- + +extern nsFtpProtocolHandler* gFtpHandler; + +#include "mozilla/Logging.h" +extern mozilla::LazyLogModule gFTPLog; + +#endif // !nsFtpProtocolHandler_h__ diff --git a/netwerk/protocol/ftp/nsIFTPChannel.idl b/netwerk/protocol/ftp/nsIFTPChannel.idl new file mode 100644 index 0000000000..de82983841 --- /dev/null +++ b/netwerk/protocol/ftp/nsIFTPChannel.idl @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsISupports.idl" + +/** + * This interface may be used to determine if a channel is a FTP channel. + */ +[scriptable, uuid(07f0d5cd-1fd5-4aa3-b6fc-665bdc5dbf9f)] +interface nsIFTPChannel : nsISupports +{ + attribute PRTime lastModifiedTime; +}; + +/** + * This interface may be defined as a notification callback on the FTP + * channel. It allows a consumer to receive a log of the FTP control + * connection conversation. + */ +[scriptable, uuid(455d4234-0330-43d2-bbfb-99afbecbfeb0)] +interface nsIFTPEventSink : nsISupports +{ + /** + * XXX document this method! (see bug 328915) + */ + void OnFTPControlLog(in boolean server, in string msg); +}; diff --git a/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl new file mode 100644 index 0000000000..2642c804b1 --- /dev/null +++ b/netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsISupports.idl" + +/** + * This is an internal interface for FTP parent channel. + */ +[builtinclass, uuid(87b58410-83cb-42a7-b57b-27c07ef828d7)] +interface nsIFTPChannelParentInternal : nsISupports +{ + void setErrorMsg(in string msg, in boolean useUTF8); +}; diff --git a/netwerk/protocol/ftp/test/frametest/contents.html b/netwerk/protocol/ftp/test/frametest/contents.html new file mode 100644 index 0000000000..077b8d8f75 --- /dev/null +++ b/netwerk/protocol/ftp/test/frametest/contents.html @@ -0,0 +1,5 @@ + + +

Click a link to the left

+ + diff --git a/netwerk/protocol/ftp/test/frametest/index.html b/netwerk/protocol/ftp/test/frametest/index.html new file mode 100644 index 0000000000..6a8a736251 --- /dev/null +++ b/netwerk/protocol/ftp/test/frametest/index.html @@ -0,0 +1,12 @@ + + +FTP Frameset Test + + + + + + + + + diff --git a/netwerk/protocol/ftp/test/frametest/menu.html b/netwerk/protocol/ftp/test/frametest/menu.html new file mode 100644 index 0000000000..9f43f00ee8 --- /dev/null +++ b/netwerk/protocol/ftp/test/frametest/menu.html @@ -0,0 +1,371 @@ + + + + +


+ + -- cgit v1.2.3