summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/ftp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/ftp')
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.cpp541
-rw-r--r--netwerk/protocol/ftp/FTPChannelChild.h137
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.cpp418
-rw-r--r--netwerk/protocol/ftp/FTPChannelParent.h92
-rw-r--r--netwerk/protocol/ftp/PFTPChannel.ipdl54
-rw-r--r--netwerk/protocol/ftp/doc/testdoc4
-rw-r--r--netwerk/protocol/ftp/ftpCore.h15
-rw-r--r--netwerk/protocol/ftp/moz.build50
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.cpp213
-rw-r--r--netwerk/protocol/ftp/nsFTPChannel.h115
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.cpp1960
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.h254
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.cpp169
-rw-r--r--netwerk/protocol/ftp/nsFtpControlConnection.h83
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.cpp331
-rw-r--r--netwerk/protocol/ftp/nsFtpProtocolHandler.h85
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannel.idl29
-rw-r--r--netwerk/protocol/ftp/nsIFTPChannelParentInternal.idl15
-rw-r--r--netwerk/protocol/ftp/test/frametest/contents.html5
-rw-r--r--netwerk/protocol/ftp/test/frametest/index.html12
-rw-r--r--netwerk/protocol/ftp/test/frametest/menu.html371
21 files changed, 4953 insertions, 0 deletions
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<nsIFTPChannel*>(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<nsIStreamListener> 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<ContentChild*>(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<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(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<ContentChild*>(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<nsILoadInfo> 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<FTPChannelChild>(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<FTPChannelChild> 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<nsIURI> 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<FTPChannelChild>(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<nsIInputStream> 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<uint32_t>(aChannelStatus)));
+
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(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<uint32_t>(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<uint32_t>(aStatusCode)));
+ mEventQ->RunOrEnqueue(new NeckoTargetChannelFunctionEvent(
+ this, [self = UnsafePtr<FTPChannelChild>(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<uint32_t>(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<FTPChannelChild>(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<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown(),
+ NS_ERROR_FAILURE);
+
+ LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this));
+
+ mozilla::dom::BrowserChild* browserChild = nullptr;
+ nsCOMPtr<nsIBrowserChild> iBrowserChild;
+ NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup,
+ NS_GET_IID(nsIBrowserChild),
+ getter_AddRefs(iBrowserChild));
+ GetCallback(iBrowserChild);
+ if (iBrowserChild) {
+ browserChild =
+ static_cast<mozilla::dom::BrowserChild*>(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<nsILoadInfo> 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<nsIInputStream> mUploadStream;
+
+ bool mIPCOpen;
+ const RefPtr<ChannelEventQueue> 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<nsIParentChannel*>(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<IPCStream>& aUploadStream,
+ const Maybe<LoadInfoArgs>& aLoadInfoArgs,
+ const uint32_t& aLoadFlags) {
+ nsresult rv;
+
+ nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+ if (!uri) return false;
+
+#ifdef DEBUG
+ LOG(("FTPChannelParent DoAsyncOpen [this=%p uri=%s]\n", this,
+ uri->GetSpecOrDefault().get()));
+#endif
+
+ nsCOMPtr<nsIIOService> ios(do_GetIOService(&rv));
+ if (NS_FAILED(rv)) {
+ return SendFailedAsyncOpen(rv);
+ }
+
+ nsCOMPtr<nsILoadInfo> 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<nsIChannel> 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<nsFtpChannel*>(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<nsIInputStream> 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<nsIChannel> 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<uint32_t>(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<nsIChannel> 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<nsresult> rv =
+ static_cast<ContentParent*>(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<nsIResumableChannel> resChan = do_QueryInterface(aRequest);
+ MOZ_ASSERT(
+ resChan); // both FTP and HTTP should implement nsIResumableChannel
+ if (resChan) {
+ resChan->GetEntityID(entityID);
+ }
+
+ PRTime lastModified = 0;
+ nsCOMPtr<nsIFTPChannel> ftpChan = do_QueryInterface(aRequest);
+ if (ftpChan) {
+ ftpChan->GetLastModifiedTime(&lastModified);
+ }
+ nsCOMPtr<nsIHttpChannelInternal> httpChan = do_QueryInterface(aRequest);
+ if (httpChan) {
+ Unused << httpChan->GetLastModifiedTime(&lastModified);
+ }
+
+ URIParams uriparam;
+ nsCOMPtr<nsIURI> 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<uint32_t>(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<dom::ContentParent*>(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<nsIAuthPromptProvider> 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<nsILoadContext> 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<nsIFTPChannel> ftpChan = do_QueryInterface(newChannel);
+ if (!ftpChan) {
+ // when FTP is set to use HTTP proxying, we wind up getting redirected to an
+ // HTTP channel.
+ nsCOMPtr<nsIHttpChannel> 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<IPCStream>& aUploadStream,
+ const Maybe<LoadInfoArgs>& 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<nsIChannel> mChannel;
+
+ bool mIPCClosed;
+
+ nsCOMPtr<nsILoadContext> mLoadContext;
+
+ PBOverrideStatus mPBOverride;
+
+ // Set to the canceled status value if the main channel was canceled.
+ nsresult mStatus;
+
+ RefPtr<mozilla::dom::BrowserParent> mBrowserParent;
+
+ RefPtr<ChannelEventQueue> 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<nsIInputStream> 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<nsIProxyInfo> 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<nsFtpState> 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<nsIFTPEventSink> mTarget;
+ bool mServer;
+ nsCString mMessage;
+ };
+
+ private:
+ nsCOMPtr<nsIFTPEventSink> mTarget;
+ nsCOMPtr<nsIEventTarget> mEventTarget;
+};
+
+NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink)
+
+NS_IMETHODIMP
+FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg) {
+ RefPtr<OnFTPControlLogRunnable> 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<nsIFTPEventSink>& aResult) {
+ if (!mFTPEventSink) {
+ nsCOMPtr<nsIFTPEventSink> 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<nsIFTPEventSink>& 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<nsIProxyInfo> mProxyInfo;
+ nsCOMPtr<nsIFTPEventSink> mFTPEventSink;
+ nsCOMPtr<nsIInputStream> 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 <ctype.h>
+
+#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<nsIFTPEventSink> 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<uint32_t>(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<nsFtpControlConnection> 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<uint32_t>(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<uint32_t>(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<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> 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<nsIPrefBranch> 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<nsIAuthPrompt2> prompter;
+ NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
+ getter_AddRefs(prompter));
+ if (!prompter) return NS_ERROR_NOT_INITIALIZED;
+
+ RefPtr<nsAuthInformationHolder> 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<nsIURL> 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<nsIURL> 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<nsISocketTransport> 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<nsISocketTransport> 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<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!sts) return FTP_ERROR;
+
+ nsCOMPtr<nsISocketTransport> 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<nsCString>(), 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<nsIOutputStream> 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<nsIEventTarget> stEventTarget =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
+ if (!stEventTarget) return FTP_ERROR;
+
+ nsCOMPtr<nsIAsyncStreamCopier> 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<nsIInputStream> 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<nsIURL> 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<nsIURI> 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<nsIProtocolProxyService> pps =
+ do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
+
+ if (pps && !mChannel->ProxyInfo()) {
+ pps->AsyncResolve(static_cast<nsIChannel*>(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<uint32_t>(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<nsIFTPChannelParentInternal> 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<nsIFTPEventSink> 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<uint32_t>(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<nsIIOService> ioService = do_GetIOService(&rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProtocolHandler> handler;
+ rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIURI> uri;
+ channel->GetURI(getter_AddRefs(uri));
+
+ nsCOMPtr<nsILoadInfo> 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<nsIChannel> 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<nsIProxiedProtocolHandler> 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<nsFtpControlConnection>
+ 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<nsFtpChannel>
+ mChannel; // our owning FTP channel we pass through our events
+ nsCOMPtr<nsIProxyInfo> 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<nsITransport> mDataTransport;
+ nsCOMPtr<nsIAsyncInputStream> mDataStream;
+ nsCOMPtr<nsIRequest> mUploadRequest;
+ bool mAddressChecked;
+ bool mServerIsIPv6;
+ bool mUseUTF8;
+
+ mozilla::net::NetAddr mServerAddress;
+
+ // ***** control read gvars
+ nsresult mControlStatus;
+ nsCString mControlReadCarryOverBuf;
+
+ nsCString mSuppliedEntityID;
+
+ nsCOMPtr<nsICancelable> 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 <algorithm>
+
+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<nsFtpControlConnectionListener> 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<nsISocketTransportService> sts =
+ do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = sts->CreateTransport(nsTArray<nsCString>(), 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<nsIInputStream> 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<uint32_t>(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<nsISocketTransport> mSocket;
+ nsCOMPtr<nsIOutputStream> mSocketOutput;
+ nsCOMPtr<nsIAsyncInputStream> mSocketInput;
+
+ RefPtr<nsFtpControlConnectionListener> 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<nsIPrefBranch> 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<nsIObserverService> 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<nsBaseChannel> 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<nsITimer> 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<nsIPrefBranch> 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<nsITimer> timer;
+ RefPtr<nsFtpControlConnection> 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<timerStruct*> 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 @@
+<html>
+<body>
+<h2>Click a link to the left</h2>
+</body>
+</html>
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 @@
+<html>
+<head>
+<title>FTP Frameset Test</title>
+
+</head>
+
+<frameset cols="30%,70%" name="ftp_main_frame">
+ <frame src="menu.html" name="ftp_menu" scrolling="yes" marginwidth="0" marginheight="0" noresize>
+ <frame src="contents.html" name="ftp_content" scrolling="YES" marginwidth="0" marginheight="0" noresize>
+</frameset>
+
+</html>
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 @@
+<html>
+
+<script language="javascript">
+/* eslint-disable no-unsanitized/method */
+<!--
+
+function add_location(url) {
+ // faster to do this in one assignment, but who really cares.
+
+ var string = '<LI> <a href=\"javascript: ftp_open(\'';
+ string += url;
+ string += '\')\"';
+ string += 'onmouseover=\"window.status=\'ftp://';
+ string += url;
+ string += '\'; return true; \">';
+ string += url;
+ string += "</a>";
+ document.writeln(string);
+}
+
+function ftp_open(url) {
+ url = "ftp://" + url;
+ parent.ftp_content.location = url;
+}
+
+// I like this format.
+document.writeln("<pre>");
+
+document.writeln("<br><OL><b>Company sites</b>");
+ add_location("ftp.mozilla.org");
+ add_location("ftp.sun.com");
+ add_location("ftp.iplanet.com");
+ add_location("ftp.netscape.com");
+ add_location("ftp.apple.com");
+ add_location("ftp.microsoft.com");
+ add_location("ftp.netmanage.com");
+
+document.writeln("</OL><br><OL><b>Misc sites</b>");
+ add_location("cal044202.student.utwente.nl");
+ add_location("download.intel.com");
+ add_location("fddisunsite.oit.unc.edu");
+ add_location("ftp.abas.de");
+ add_location("ftp.acm.org");
+ add_location("ftp.acomp.hu");
+ add_location("ftp.acri.fr");
+ add_location("ftp.alaska.edu");
+ add_location("ftp.altera.com");
+ add_location("ftp.amsat.org");
+ add_location("ftp.amtp.cam.ac.uk");
+ add_location("ftp.ar.freebsd.org");
+ add_location("ftp.ari.net");
+ add_location("ftp.arl.mil");
+ add_location("ftp.astro.ulg.ac.be");
+ add_location("ftp.avery-zweckform.com");
+ add_location("ftp.awi-bremerhaven.de");
+ add_location("ftp.axime-is.fr");
+ add_location("ftp.ba.cnr.it");
+ add_location("ftp.bath.ac.uk");
+ add_location("ftp.bic.mni.mcgill.ca");
+ add_location("ftp.biomed.ruhr-uni-bochum.de");
+ add_location("ftp.boerde.de");
+ add_location("ftp.bond.edu.au");
+ add_location("ftp.boulder.ibm.com");
+ add_location("ftp.brics.dk");
+ add_location("ftp.bris.ac.uk");
+ add_location("ftp.cablelabs.com");
+ add_location("ftp.cac.psu.edu");
+ add_location("ftp.cadpoint.se");
+ add_location("ftp.cas.cz");
+ add_location("ftp.cciw.ca");
+ add_location("ftp.ccs.queensu.ca");
+ add_location("ftp.ccsi.com");
+ add_location("ftp.cdrom.com");
+ add_location("ftp.cea.fr");
+ add_location("ftp.celestial.com");
+ add_location("ftp.cert.fr");
+ add_location("ftp.cgd.ucar.edu");
+ add_location("ftp.chiba-u.ac.jp");
+ add_location("ftp.cis.ksu.edu");
+ add_location("ftp.citi2.fr");
+ add_location("ftp.cityline.net");
+ add_location("ftp.cnam.fr");
+ add_location("ftp.cohesive.com");
+ add_location("ftp.contrib.net");
+ add_location("ftp.create.ucsb.edu");
+ add_location("ftp.cronyx.ru");
+ add_location("ftp.cs.arizona.edu");
+ add_location("ftp.cs.colorado.edu");
+ add_location("ftp.cs.concordia.ca");
+ add_location("ftp.cs.helsinki.fi");
+ add_location("ftp.cs.jhu.edu");
+ add_location("ftp.cs.monash.edu.au");
+ add_location("ftp.cs.ohiou.edu");
+ add_location("ftp.cs.rug.nl");
+ add_location("ftp.cs.toronto.edu");
+ add_location("ftp.cs.umanitoba.ca");
+ add_location("ftp.cs.uni-dortmund.de");
+ add_location("ftp.cs.vu.nl");
+ add_location("ftp.cse.cuhk.edu.hk");
+ add_location("ftp.cse.unsw.edu.au");
+ add_location("ftp.csse.monash.edu.au");
+ add_location("ftp.csus.edu");
+ add_location("ftp.cullasaja.com");
+ add_location("ftp.daimi.au.dk");
+ add_location("ftp.dcs.qmw.ac.uk");
+ add_location("ftp.delorie.com");
+ add_location("ftp.dementia.org");
+ add_location("ftp.dfki.uni-kl.de");
+ add_location("ftp.dgs.monash.edu.au");
+ add_location("ftp.dis.strath.ac.uk");
+ add_location("ftp.dosis.uni-dortmund.de");
+ add_location("ftp.duke.edu");
+ add_location("ftp.duplexx.com");
+ add_location("ftp.ece.ucdavis.edu");
+ add_location("ftp.ee.lbl.gov");
+ add_location("ftp.ee.rochester.edu");
+ add_location("ftp.ee.uts.edu.au");
+ add_location("ftp.efrei.fr");
+ add_location("ftp.elet.polimi.it");
+ add_location("ftp.elite.net");
+ add_location("ftp.embl-hamburg.de");
+ add_location("ftp.eng.buffalo.edu");
+ add_location("ftp.engr.uark.edu");
+ add_location("ftp.eni.co.jp");
+ add_location("ftp.enst-bretagne.fr");
+ add_location("ftp.epix.net");
+ add_location("ftp.eskimo.com");
+ add_location("ftp.essential.org");
+ add_location("ftp.eunet.fi");
+ add_location("ftp.eurexpo.com");
+ add_location("ftp.ex.ac.uk");
+ add_location("ftp.faximum.com");
+ add_location("ftp.fernuni-hagen.de");
+ add_location("ftp.fh-dortmund.de");
+ add_location("ftp.fit.qut.edu.au");
+ add_location("ftp.forum.swarthmore.edu");
+ add_location("ftp.fsu.edu");
+ add_location("ftp.ftp.epson.com");
+ add_location("ftp.fu-berlin.de");
+ add_location("ftp.fujixerox.co.jp");
+ add_location("ftp.game.org");
+ add_location("ftp.ge.ucl.ac.uk");
+ add_location("ftp.genetics.wisc.edu");
+ add_location("ftp.geo.uu.nl");
+ add_location("ftp.geom.umn.edu");
+ add_location("ftp.gfdl.gov");
+ add_location("ftp.gigo.com");
+ add_location("ftp.giss.nasa.gov");
+ add_location("ftp.globalnet.co.uk");
+ add_location("ftp.gnu.org");
+ add_location("ftp.gnu.vbs.at");
+ add_location("ftp.gps.caltech.edu");
+ add_location("ftp.grau-wzs.de");
+ add_location("ftp.gsoc.dlr.de");
+ add_location("ftp.gutenberg.org");
+ add_location("ftp.hawaii.edu");
+ add_location("ftp.hep.net");
+ add_location("ftp.hgc.edu");
+ add_location("ftp.hgmp.mrc.ac.uk");
+ add_location("ftp.hugin.dk");
+ add_location("ftp.ic.tsu.ru");
+ add_location("ftp.icce.rug.nl");
+ add_location("ftp.icon-stl.net");
+ add_location("ftp.icor.fr");
+ add_location("ftp.ics.uci.edu");
+ add_location("ftp.idsia.ch");
+ add_location("ftp.ifm.liu.se");
+ add_location("ftp.ifm.uni-kiel.de");
+ add_location("ftp.iglou.com");
+ add_location("ftp.ign.fr");
+ add_location("ftp.imag.fr");
+ add_location("ftp.inel.gov");
+ add_location("ftp.inf.ethz.ch");
+ add_location("ftp.inf.puc-rio.br");
+ add_location("ftp.infoflex.se");
+ add_location("ftp.informatik.rwth-aachen.de");
+ add_location("ftp.informatik.uni-bremen.de");
+ add_location("ftp.informatik.uni-hannover.de");
+ add_location("ftp.infoscandic.se");
+ add_location("ftp.intel.com");
+ add_location("ftp.intergraph.com");
+ add_location("ftp.ionet.net");
+ add_location("ftp.ipc.chiba-u.ac.jp");
+ add_location("ftp.ips.cs.tu-bs.de");
+ add_location("ftp.iram.rwth-aachen.de");
+ add_location("ftp.is.co.za");
+ add_location("ftp.isoc.org");
+ add_location("ftp.iteso.mx");
+ add_location("ftp.ivd.uni-stuttgart.de");
+ add_location("ftp.iway.fr");
+ add_location("ftp.jcu.edu.au");
+ add_location("ftp.jhuapl.edu");
+ add_location("ftp.jpix.ad.jp");
+ add_location("ftp.karlin.mff.cuni.cz");
+ add_location("ftp.kfu.com");
+ add_location("ftp.kfunigraz.ac.at");
+ add_location("ftp.khm.de");
+ add_location("ftp.ki.se");
+ add_location("ftp.komkon.org");
+ add_location("ftp.laas.fr");
+ add_location("ftp.lanl.gov");
+ add_location("ftp.lantronix.com");
+ add_location("ftp.lava.net");
+ add_location("ftp.lcs.mit.edu");
+ add_location("ftp.legend.co.uk");
+ add_location("ftp.leidenuniv.nl");
+ add_location("ftp.let.rug.nl");
+ add_location("ftp.linux.co.uk");
+ add_location("ftp.linux.unife.it");
+ add_location("ftp.liv.ac.uk");
+ add_location("ftp.livingston.com");
+ add_location("ftp.lnt.e-technik.tu-muenchen.de");
+ add_location("ftp.lsu.edu");
+ add_location("ftp.lth.se");
+ add_location("ftp.lysator.liu.se");
+ add_location("ftp.mailbase.ac.uk");
+ add_location("ftp.mainstream.net");
+ add_location("ftp.maricopa.edu");
+ add_location("ftp.math.fu-berlin.de");
+ add_location("ftp.math.hr");
+ add_location("ftp.math.utah.edu");
+ add_location("ftp.mathematik.uni-marburg.de");
+ add_location("ftp.maths.tcd.ie");
+ add_location("ftp.maths.usyd.edu.au");
+ add_location("ftp.mathworks.com");
+ add_location("ftp.mbb.ki.se");
+ add_location("ftp.mbt.ru");
+ add_location("ftp.mcs.net");
+ add_location("ftp.mcs.vuw.ac.nz");
+ add_location("ftp.media.mit.edu");
+ add_location("ftp.meme.com");
+ add_location("ftp.merl.com");
+ add_location("ftp.microport.com");
+ add_location("ftp.mms.de");
+ add_location("ftp.mpce.mq.edu.au");
+ add_location("ftp.mpgn.com");
+ add_location("ftp.mpipf-muenchen.mpg.de");
+ add_location("ftp.mscf.uky.edu");
+ add_location("ftp.natinst.com");
+ add_location("ftp.ncsa.uiuc.edu");
+ add_location("ftp.net-tel.co.uk");
+ add_location("ftp.net.cmu.edu");
+ add_location("ftp.netsw.org");
+ add_location("ftp.new-york.net");
+ add_location("ftp.nis.net");
+ add_location("ftp.nlm.nih.gov");
+ add_location("ftp.nmt.edu");
+ add_location("ftp.noao.edu");
+ add_location("ftp.ntnu.no");
+ add_location("ftp.nwu.edu");
+ add_location("ftp.nysaes.cornell.edu");
+ add_location("ftp.observ.u-bordeaux.fr");
+ add_location("ftp.oit.unc.edu");
+ add_location("ftp.oldenbourg.de");
+ add_location("ftp.omg.unb.ca");
+ add_location("ftp.onecall.net");
+ add_location("ftp.ornl.gov");
+ add_location("ftp.ozone.fmi.fi");
+ add_location("ftp.pacific.net.hk");
+ add_location("ftp.panix.com");
+ add_location("ftp.pcnet.com");
+ add_location("ftp.phred.org");
+ add_location("ftp.pnl.gov");
+ add_location("ftp.prairienet.org");
+ add_location("ftp.proxad.net");
+ add_location("ftp.proximity.com.au");
+ add_location("ftp.psg.com");
+ add_location("ftp.psy.uq.edu.au");
+ add_location("ftp.psychologie.uni-freiburg.de");
+ add_location("ftp.pwr.wroc.pl");
+ add_location("ftp.python.org");
+ add_location("ftp.quantum.de");
+ add_location("ftp.ra.phy.cam.ac.uk");
+ add_location("ftp.rasip.fer.hr");
+ add_location("ftp.rbgkew.org.uk");
+ add_location("ftp.rcsb.org");
+ add_location("ftp.realtime.net");
+ add_location("ftp.red-bean.com");
+ add_location("ftp.redac.co.uk");
+ add_location("ftp.redac.fr");
+ add_location("ftp.rediris.es");
+ add_location("ftp.rgn.it");
+ add_location("ftp.rice.edu");
+ add_location("ftp.rkk.hu");
+ add_location("ftp.robelle.com");
+ add_location("ftp.rose.hp.com");
+ add_location("ftp.rt66.com");
+ add_location("ftp.ruhr-uni-bochum.de");
+ add_location("ftp.rz.uni-frankfurt.de");
+ add_location("ftp.sat.dundee.ac.uk");
+ add_location("ftp.saugus.net");
+ add_location("ftp.schrodinger.com");
+ add_location("ftp.science-computing.de");
+ add_location("ftp.science.unitn.it");
+ add_location("ftp.sco.com");
+ add_location("ftp.scs.leeds.ac.uk");
+ add_location("ftp.scsr.nevada.edu");
+ add_location("ftp.sd.monash.edu.au");
+ add_location("ftp.sdv.fr");
+ add_location("ftp.selapo.vwl.uni-muenchen.de");
+ add_location("ftp.serv.net");
+ add_location("ftp.sgi.leeds.ac.uk");
+ add_location("ftp.shore.net");
+ add_location("ftp.socsci.auc.dk");
+ add_location("ftp.space.net");
+ add_location("ftp.spec.org");
+ add_location("ftp.stallion.com");
+ add_location("ftp.starnet.de");
+ add_location("ftp.stat.math.ethz.ch");
+ add_location("ftp.stat.umn.edu");
+ add_location("ftp.std.com");
+ add_location("ftp.structchem.uni-essen.de");
+ add_location("ftp.sunsite.org.uk");
+ add_location("ftp.syd.dms.csiro.au");
+ add_location("ftp.tapr.org");
+ add_location("ftp.teco.uni-karlsruhe.de");
+ add_location("ftp.tenon.com");
+ add_location("ftp.tierzucht.uni-kiel.de");
+ add_location("ftp.tnt.uni-hannover.de");
+ add_location("ftp.tu-clausthal.de");
+ add_location("ftp.uci.edu");
+ add_location("ftp.ucsd.edu");
+ add_location("ftp.udel.edu");
+ add_location("ftp.uec.ac.jp");
+ add_location("ftp.uibk.ac.at");
+ add_location("ftp.uit.co.uk");
+ add_location("ftp.uji.es");
+ add_location("ftp.uke.uni-hamburg.de");
+ add_location("ftp.ulcc.ac.uk");
+ add_location("ftp.um.es");
+ add_location("ftp.umi.cs.tu-bs.de");
+ add_location("ftp.uni-augsburg.de");
+ add_location("ftp.uni-dortmund.de");
+ add_location("ftp.uni-hannover.de");
+ add_location("ftp.uni-magdeburg.de");
+ add_location("ftp.unidata.ucar.edu");
+ add_location("ftp.unige.ch");
+ add_location("ftp.univ-aix.fr");
+ add_location("ftp.upc.es");
+ add_location("ftp.uradio.ku.dk");
+ add_location("ftp.uralexpress.ru");
+ add_location("ftp.urc.ac.ru");
+ add_location("ftp.ut.ee");
+ add_location("ftp.uunet.ca");
+ add_location("ftp.uwo.ca");
+ add_location("ftp.vaxxine.com");
+ add_location("ftp.visi.com");
+ add_location("ftp.vub.ac.be");
+ add_location("ftp.wfu.edu");
+ add_location("ftp.win.tue.nl");
+ add_location("ftp.wolfe.net");
+ add_location("sunsite.cnlab-switch.ch");
+ add_location("sunsite.sut.ac.jp");
+ add_location("ftp.cuhk.edu.hk");
+ add_location("ftp.cetis.hvu.nl");
+ add_location("ftp.clinet.fi");
+ add_location("ftp.gamma.ru");
+ add_location("ftp.itv.se");
+ add_location("ftp.cs.rpi.edu");
+ add_location("ftp.carrier.kiev.ua");
+ add_location("ftp.rosnet.ru");
+ add_location("ftp.nsk.su");
+ add_location("ftp.southcom.com.au");
+
+// -->
+
+</script>
+<body>
+<br><br><br>
+</body>
+</html>