diff options
Diffstat (limited to 'netwerk/protocol/ftp')
22 files changed, 10334 insertions, 0 deletions
diff --git a/netwerk/protocol/ftp/FTPChannelChild.cpp b/netwerk/protocol/ftp/FTPChannelChild.cpp new file mode 100644 index 000000000..f8284aae3 --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelChild.cpp @@ -0,0 +1,932 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/ChannelDiverterChild.h" +#include "mozilla/net/FTPChannelChild.h" +#include "mozilla/dom/TabChild.h" +#include "nsFtpProtocolHandler.h" +#include "nsITabChild.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "base/compiler_specific.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "SerializedLoadContext.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "nsIPrompt.h" + +using namespace mozilla::ipc; + +#undef LOG +#define LOG(args) MOZ_LOG(gFTPLog, mozilla::LogLevel::Debug, args) + +namespace mozilla { +namespace net { + +FTPChannelChild::FTPChannelChild(nsIURI* uri) +: mIPCOpen(false) +, mUnknownDecoderInvolved(false) +, mCanceled(false) +, mSuspendCount(0) +, mIsPending(false) +, mLastModifiedTime(0) +, mStartPos(0) +, mDivertingToParent(false) +, mFlushedForDiversion(false) +, mSuspendSent(false) +{ + LOG(("Creating FTPChannelChild @%x\n", this)); + // grab a reference to the handler to ensure that it doesn't go away. + NS_ADDREF(gFtpHandler); + SetURI(uri); + mEventQ = new ChannelEventQueue(static_cast<nsIFTPChannel*>(this)); + + // We could support thread retargeting, but as long as we're being driven by + // IPDL on the main thread it doesn't buy us anything. + DisallowThreadRetargeting(); +} + +FTPChannelChild::~FTPChannelChild() +{ + LOG(("Destroying FTPChannelChild @%x\n", this)); + gFtpHandler->Release(); +} + +void +FTPChannelChild::AddIPDLReference() +{ + MOZ_ASSERT(!mIPCOpen, "Attempt to retain more than one IPDL reference"); + mIPCOpen = true; + AddRef(); +} + +void +FTPChannelChild::ReleaseIPDLReference() +{ + MOZ_ASSERT(mIPCOpen, "Attempt to release nonexistent IPDL reference"); + mIPCOpen = false; + Release(); +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS_INHERITED(FTPChannelChild, + nsBaseChannel, + nsIFTPChannel, + nsIUploadChannel, + nsIResumableChannel, + nsIProxiedChannel, + nsIChildChannel, + nsIDivertableChannel) + +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelChild::GetLastModifiedTime(PRTime* lastModifiedTime) +{ + *lastModifiedTime = mLastModifiedTime; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::SetLastModifiedTime(PRTime lastModifiedTime) +{ + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +FTPChannelChild::ResumeAt(uint64_t aStartPos, const nsACString& aEntityID) +{ + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + mStartPos = aStartPos; + mEntityID = aEntityID; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::GetEntityID(nsACString& entityID) +{ + entityID = mEntityID; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::GetProxyInfo(nsIProxyInfo** aProxyInfo) +{ + DROP_DEAD(); +} + +NS_IMETHODIMP +FTPChannelChild::SetUploadStream(nsIInputStream* stream, + const nsACString& contentType, + int64_t contentLength) +{ + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + mUploadStream = stream; + // NOTE: contentLength is intentionally ignored here. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::GetUploadStream(nsIInputStream** stream) +{ + NS_ENSURE_ARG_POINTER(stream); + *stream = mUploadStream; + NS_IF_ADDREF(*stream); + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::AsyncOpen(::nsIStreamListener* listener, nsISupports* aContext) +{ + LOG(("FTPChannelChild::AsyncOpen [this=%p]\n", this)); + + NS_ENSURE_TRUE((gNeckoChild), NS_ERROR_FAILURE); + NS_ENSURE_ARG_POINTER(listener); + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + + // Port checked in parent, but duplicate here so we can return with error + // immediately, as we've done since before e10s. + nsresult rv; + rv = NS_CheckPortSafety(nsBaseChannel::URI()); // Need to disambiguate, + // because in the child ipdl, + // a typedef URI is defined... + if (NS_FAILED(rv)) + return rv; + + mozilla::dom::TabChild* tabChild = nullptr; + nsCOMPtr<nsITabChild> iTabChild; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsITabChild), + getter_AddRefs(iTabChild)); + GetCallback(iTabChild); + if (iTabChild) { + tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get()); + } + if (MissingRequiredTabChild(tabChild, "ftp")) { + return NS_ERROR_ILLEGAL_VALUE; + } + + mListener = listener; + mListenerContext = aContext; + + // add ourselves to the load group. + if (mLoadGroup) + mLoadGroup->AddRequest(this, nullptr); + + OptionalInputStreamParams uploadStream; + nsTArray<mozilla::ipc::FileDescriptor> fds; + SerializeInputStream(mUploadStream, uploadStream, fds); + + MOZ_ASSERT(fds.IsEmpty()); + + FTPChannelOpenArgs openArgs; + SerializeURI(nsBaseChannel::URI(), openArgs.uri()); + openArgs.startPos() = mStartPos; + openArgs.entityID() = mEntityID; + openArgs.uploadStream() = uploadStream; + + nsCOMPtr<nsILoadInfo> loadInfo; + GetLoadInfo(getter_AddRefs(loadInfo)); + rv = mozilla::ipc::LoadInfoToLoadInfoArgs(loadInfo, &openArgs.loadInfo()); + NS_ENSURE_SUCCESS(rv, rv); + + gNeckoChild-> + SendPFTPChannelConstructor(this, tabChild, IPC::SerializedLoadContext(this), + openArgs); + + // The socket transport layer in the chrome process now has a logical ref to + // us until OnStopRequest is called. + AddIPDLReference(); + + mIsPending = true; + mWasOpened = true; + + return rv; +} + +NS_IMETHODIMP +FTPChannelChild::IsPending(bool* result) +{ + *result = mIsPending; + return NS_OK; +} + +nsresult +FTPChannelChild::OpenContentStream(bool async, + nsIInputStream** stream, + nsIChannel** channel) +{ + NS_RUNTIMEABORT("FTPChannel*Child* should never have OpenContentStream called!"); + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::PFTPChannelChild +//----------------------------------------------------------------------------- + +class FTPStartRequestEvent : public ChannelEvent +{ +public: + FTPStartRequestEvent(FTPChannelChild* aChild, + const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) + : mChild(aChild) + , mChannelStatus(aChannelStatus) + , mContentLength(aContentLength) + , mContentType(aContentType) + , mLastModified(aLastModified) + , mEntityID(aEntityID) + , mURI(aURI) + { + } + void Run() + { + mChild->DoOnStartRequest(mChannelStatus, mContentLength, mContentType, + mLastModified, mEntityID, mURI); + } + +private: + FTPChannelChild* mChild; + nsresult mChannelStatus; + int64_t mContentLength; + nsCString mContentType; + PRTime mLastModified; + nsCString mEntityID; + URIParams mURI; +}; + +bool +FTPChannelChild::RecvOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) +{ + // mFlushedForDiversion and mDivertingToParent should NEVER be set at this + // stage, as they are set in the listener's OnStartRequest. + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "mFlushedForDiversion should be unset before OnStartRequest!"); + MOZ_RELEASE_ASSERT(!mDivertingToParent, + "mDivertingToParent should be unset before OnStartRequest!"); + + LOG(("FTPChannelChild::RecvOnStartRequest [this=%p]\n", this)); + + mEventQ->RunOrEnqueue(new FTPStartRequestEvent(this, aChannelStatus, + aContentLength, aContentType, + aLastModified, aEntityID, + aURI)); + return true; +} + +void +FTPChannelChild::DoOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) +{ + LOG(("FTPChannelChild::DoOnStartRequest [this=%p]\n", this)); + + // mFlushedForDiversion and mDivertingToParent should NEVER be set at this + // stage, as they are set in the listener's OnStartRequest. + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "mFlushedForDiversion should be unset before OnStartRequest!"); + MOZ_RELEASE_ASSERT(!mDivertingToParent, + "mDivertingToParent should be unset before OnStartRequest!"); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = aChannelStatus; + } + + mContentLength = aContentLength; + SetContentType(aContentType); + mLastModifiedTime = aLastModified; + mEntityID = aEntityID; + + nsCString spec; + nsCOMPtr<nsIURI> uri = DeserializeURI(aURI); + nsresult rv = uri->GetSpec(spec); + if (NS_SUCCEEDED(rv)) { + rv = nsBaseChannel::URI()->SetSpec(spec); + if (NS_FAILED(rv)) { + Cancel(rv); + } + } else { + Cancel(rv); + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + rv = mListener->OnStartRequest(this, mListenerContext); + if (NS_FAILED(rv)) + Cancel(rv); + + if (mDivertingToParent) { + mListener = nullptr; + mListenerContext = nullptr; + if (mLoadGroup) { + mLoadGroup->RemoveRequest(this, nullptr, mStatus); + } + } +} + +class FTPDataAvailableEvent : public ChannelEvent +{ +public: + FTPDataAvailableEvent(FTPChannelChild* aChild, + const nsresult& aChannelStatus, + const nsCString& aData, + const uint64_t& aOffset, + const uint32_t& aCount) + : mChild(aChild) + , mChannelStatus(aChannelStatus) + , mData(aData) + , mOffset(aOffset) + , mCount(aCount) + { + } + void Run() + { + mChild->DoOnDataAvailable(mChannelStatus, mData, mOffset, mCount); + } + +private: + FTPChannelChild* mChild; + nsresult mChannelStatus; + nsCString mData; + uint64_t mOffset; + uint32_t mCount; +}; + +bool +FTPChannelChild::RecvOnDataAvailable(const nsresult& channelStatus, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count) +{ + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "Should not be receiving any more callbacks from parent!"); + + LOG(("FTPChannelChild::RecvOnDataAvailable [this=%p]\n", this)); + + mEventQ->RunOrEnqueue(new FTPDataAvailableEvent(this, channelStatus, data, + offset, count), + mDivertingToParent); + + return true; +} + +class MaybeDivertOnDataFTPEvent : public ChannelEvent +{ + public: + MaybeDivertOnDataFTPEvent(FTPChannelChild* child, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count) + : mChild(child) + , mData(data) + , mOffset(offset) + , mCount(count) {} + + void Run() + { + mChild->MaybeDivertOnData(mData, mOffset, mCount); + } + + private: + FTPChannelChild* mChild; + nsCString mData; + uint64_t mOffset; + uint32_t mCount; +}; + +void +FTPChannelChild::MaybeDivertOnData(const nsCString& data, + const uint64_t& offset, + const uint32_t& count) +{ + if (mDivertingToParent) { + SendDivertOnDataAvailable(data, offset, count); + } +} + +void +FTPChannelChild::DoOnDataAvailable(const nsresult& channelStatus, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count) +{ + LOG(("FTPChannelChild::DoOnDataAvailable [this=%p]\n", this)); + + if (!mCanceled && NS_SUCCEEDED(mStatus)) { + mStatus = channelStatus; + } + + if (mDivertingToParent) { + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "Should not be processing any more callbacks from parent!"); + + SendDivertOnDataAvailable(data, offset, count); + return; + } + + if (mCanceled) + return; + + if (mUnknownDecoderInvolved) { + mUnknownDecoderEventQ.AppendElement( + MakeUnique<MaybeDivertOnDataFTPEvent>(this, data, offset, count)); + } + + // NOTE: the OnDataAvailable contract requires the client to read all the data + // in the inputstream. This code relies on that ('data' will go away after + // this function). Apparently the previous, non-e10s behavior was to actually + // support only reading part of the data, allowing later calls to read the + // rest. + nsCOMPtr<nsIInputStream> stringStream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), + data.get(), + count, + NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) { + Cancel(rv); + return; + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + rv = mListener->OnDataAvailable(this, mListenerContext, + stringStream, offset, count); + if (NS_FAILED(rv)) + Cancel(rv); + stringStream->Close(); +} + +class FTPStopRequestEvent : public ChannelEvent +{ +public: + FTPStopRequestEvent(FTPChannelChild* aChild, + const nsresult& aChannelStatus, + const nsCString &aErrorMsg, + bool aUseUTF8) + : mChild(aChild) + , mChannelStatus(aChannelStatus) + , mErrorMsg(aErrorMsg) + , mUseUTF8(aUseUTF8) + { + } + void Run() + { + mChild->DoOnStopRequest(mChannelStatus, mErrorMsg, mUseUTF8); + } + +private: + FTPChannelChild* mChild; + nsresult mChannelStatus; + nsCString mErrorMsg; + bool mUseUTF8; +}; + +bool +FTPChannelChild::RecvOnStopRequest(const nsresult& aChannelStatus, + const nsCString &aErrorMsg, + const bool &aUseUTF8) +{ + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "Should not be receiving any more callbacks from parent!"); + + LOG(("FTPChannelChild::RecvOnStopRequest [this=%p status=%x]\n", + this, aChannelStatus)); + + mEventQ->RunOrEnqueue(new FTPStopRequestEvent(this, aChannelStatus, aErrorMsg, + aUseUTF8)); + return true; +} + +class nsFtpChildAsyncAlert : public Runnable +{ +public: + nsFtpChildAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg) + : mPrompter(aPrompter) + , mResponseMsg(aResponseMsg) + { + MOZ_COUNT_CTOR(nsFtpChildAsyncAlert); + } +protected: + virtual ~nsFtpChildAsyncAlert() + { + MOZ_COUNT_DTOR(nsFtpChildAsyncAlert); + } +public: + NS_IMETHOD Run() override + { + if (mPrompter) { + mPrompter->Alert(nullptr, mResponseMsg.get()); + } + return NS_OK; + } +private: + nsCOMPtr<nsIPrompt> mPrompter; + nsString mResponseMsg; +}; + +class MaybeDivertOnStopFTPEvent : public ChannelEvent +{ + public: + MaybeDivertOnStopFTPEvent(FTPChannelChild* child, + const nsresult& aChannelStatus) + : mChild(child) + , mChannelStatus(aChannelStatus) {} + + void Run() + { + mChild->MaybeDivertOnStop(mChannelStatus); + } + + private: + FTPChannelChild* mChild; + nsresult mChannelStatus; +}; + +void +FTPChannelChild::MaybeDivertOnStop(const nsresult& aChannelStatus) +{ + if (mDivertingToParent) { + SendDivertOnStopRequest(aChannelStatus); + } +} + +void +FTPChannelChild::DoOnStopRequest(const nsresult& aChannelStatus, + const nsCString &aErrorMsg, + bool aUseUTF8) +{ + LOG(("FTPChannelChild::DoOnStopRequest [this=%p status=%x]\n", + this, aChannelStatus)); + + if (mDivertingToParent) { + MOZ_RELEASE_ASSERT(!mFlushedForDiversion, + "Should not be processing any more callbacks from parent!"); + + SendDivertOnStopRequest(aChannelStatus); + return; + } + + if (!mCanceled) + mStatus = aChannelStatus; + + if (mUnknownDecoderInvolved) { + mUnknownDecoderEventQ.AppendElement( + MakeUnique<MaybeDivertOnStopFTPEvent>(this, aChannelStatus)); + } + + { // Ensure that all queued ipdl events are dispatched before + // we initiate protocol deletion below. + mIsPending = false; + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + (void)mListener->OnStopRequest(this, mListenerContext, aChannelStatus); + + if (NS_FAILED(aChannelStatus) && !aErrorMsg.IsEmpty()) { + nsCOMPtr<nsIPrompt> prompter; + GetCallback(prompter); + if (prompter) { + nsCOMPtr<nsIRunnable> alertEvent; + if (aUseUTF8) { + alertEvent = new nsFtpChildAsyncAlert(prompter, + NS_ConvertUTF8toUTF16(aErrorMsg)); + } else { + alertEvent = new nsFtpChildAsyncAlert(prompter, + NS_ConvertASCIItoUTF16(aErrorMsg)); + } + NS_DispatchToMainThread(alertEvent); + } + } + + mListener = nullptr; + mListenerContext = nullptr; + + if (mLoadGroup) + mLoadGroup->RemoveRequest(this, nullptr, aChannelStatus); + } + + // This calls NeckoChild::DeallocPFTPChannelChild(), which deletes |this| if IPDL + // holds the last reference. Don't rely on |this| existing after here! + Send__delete__(this); +} + +class FTPFailedAsyncOpenEvent : public ChannelEvent +{ + public: + FTPFailedAsyncOpenEvent(FTPChannelChild* aChild, nsresult aStatus) + : mChild(aChild), mStatus(aStatus) {} + void Run() { mChild->DoFailedAsyncOpen(mStatus); } + private: + FTPChannelChild* mChild; + nsresult mStatus; +}; + +bool +FTPChannelChild::RecvFailedAsyncOpen(const nsresult& statusCode) +{ + LOG(("FTPChannelChild::RecvFailedAsyncOpen [this=%p status=%x]\n", + this, statusCode)); + mEventQ->RunOrEnqueue(new FTPFailedAsyncOpenEvent(this, statusCode)); + return true; +} + +void +FTPChannelChild::DoFailedAsyncOpen(const nsresult& statusCode) +{ + LOG(("FTPChannelChild::DoFailedAsyncOpen [this=%p status=%x]\n", + this, statusCode)); + mStatus = statusCode; + + if (mLoadGroup) + mLoadGroup->RemoveRequest(this, nullptr, statusCode); + + if (mListener) { + mListener->OnStartRequest(this, mListenerContext); + mIsPending = false; + mListener->OnStopRequest(this, mListenerContext, statusCode); + } else { + mIsPending = false; + } + + mListener = nullptr; + mListenerContext = nullptr; + + if (mIPCOpen) + Send__delete__(this); +} + +class FTPFlushedForDiversionEvent : public ChannelEvent +{ + public: + explicit FTPFlushedForDiversionEvent(FTPChannelChild* aChild) + : mChild(aChild) + { + MOZ_RELEASE_ASSERT(aChild); + } + + void Run() + { + mChild->FlushedForDiversion(); + } + private: + FTPChannelChild* mChild; +}; + +bool +FTPChannelChild::RecvFlushedForDiversion() +{ + LOG(("FTPChannelChild::RecvFlushedForDiversion [this=%p]\n", this)); + MOZ_ASSERT(mDivertingToParent); + + mEventQ->RunOrEnqueue(new FTPFlushedForDiversionEvent(this), true); + return true; +} + +void +FTPChannelChild::FlushedForDiversion() +{ + LOG(("FTPChannelChild::FlushedForDiversion [this=%p]\n", this)); + MOZ_RELEASE_ASSERT(mDivertingToParent); + + // Once this is set, it should not be unset before FTPChannelChild is taken + // down. After it is set, no OnStart/OnData/OnStop callbacks should be + // received from the parent channel, nor dequeued from the ChannelEventQueue. + mFlushedForDiversion = true; + + SendDivertComplete(); +} + +bool +FTPChannelChild::RecvDivertMessages() +{ + LOG(("FTPChannelChild::RecvDivertMessages [this=%p]\n", this)); + MOZ_RELEASE_ASSERT(mDivertingToParent); + MOZ_RELEASE_ASSERT(mSuspendCount > 0); + + // DivertTo() has been called on parent, so we can now start sending queued + // IPDL messages back to parent listener. + if (NS_WARN_IF(NS_FAILED(Resume()))) { + return false; + } + return true; +} + +class FTPDeleteSelfEvent : public ChannelEvent +{ + public: + explicit FTPDeleteSelfEvent(FTPChannelChild* aChild) + : mChild(aChild) {} + void Run() { mChild->DoDeleteSelf(); } + private: + FTPChannelChild* mChild; +}; + +bool +FTPChannelChild::RecvDeleteSelf() +{ + mEventQ->RunOrEnqueue(new FTPDeleteSelfEvent(this)); + return true; +} + +void +FTPChannelChild::DoDeleteSelf() +{ + if (mIPCOpen) + Send__delete__(this); +} + +NS_IMETHODIMP +FTPChannelChild::Cancel(nsresult status) +{ + LOG(("FTPChannelChild::Cancel [this=%p]\n", this)); + if (mCanceled) + return NS_OK; + + mCanceled = true; + mStatus = status; + if (mIPCOpen) + SendCancel(status); + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::Suspend() +{ + NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE); + + LOG(("FTPChannelChild::Suspend [this=%p]\n", this)); + + // SendSuspend only once, when suspend goes from 0 to 1. + // Don't SendSuspend at all if we're diverting callbacks to the parent; + // suspend will be called at the correct time in the parent itself. + if (!mSuspendCount++ && !mDivertingToParent) { + SendSuspend(); + mSuspendSent = true; + } + mEventQ->Suspend(); + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::Resume() +{ + NS_ENSURE_TRUE(mIPCOpen, NS_ERROR_NOT_AVAILABLE); + + LOG(("FTPChannelChild::Resume [this=%p]\n", this)); + + // SendResume only once, when suspend count drops to 0. + // Don't SendResume at all if we're diverting callbacks to the parent (unless + // suspend was sent earlier); otherwise, resume will be called at the correct + // time in the parent itself. + if (!--mSuspendCount && (!mDivertingToParent || mSuspendSent)) { + SendResume(); + } + mEventQ->Resume(); + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::nsIChildChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelChild::ConnectParent(uint32_t id) +{ + LOG(("FTPChannelChild::ConnectParent [this=%p]\n", this)); + + mozilla::dom::TabChild* tabChild = nullptr; + nsCOMPtr<nsITabChild> iTabChild; + NS_QueryNotificationCallbacks(mCallbacks, mLoadGroup, + NS_GET_IID(nsITabChild), + getter_AddRefs(iTabChild)); + GetCallback(iTabChild); + if (iTabChild) { + tabChild = static_cast<mozilla::dom::TabChild*>(iTabChild.get()); + } + + // The socket transport in the chrome process now holds a logical ref to us + // until OnStopRequest, or we do a redirect, or we hit an IPDL error. + AddIPDLReference(); + + FTPChannelConnectArgs connectArgs(id); + + if (!gNeckoChild->SendPFTPChannelConstructor(this, tabChild, + IPC::SerializedLoadContext(this), + connectArgs)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::CompleteRedirectSetup(nsIStreamListener *listener, + nsISupports *aContext) +{ + LOG(("FTPChannelChild::CompleteRedirectSetup [this=%p]\n", this)); + + NS_ENSURE_TRUE(!mIsPending, NS_ERROR_IN_PROGRESS); + NS_ENSURE_TRUE(!mWasOpened, NS_ERROR_ALREADY_OPENED); + + mIsPending = true; + mWasOpened = true; + mListener = listener; + mListenerContext = aContext; + + // add ourselves to the load group. + if (mLoadGroup) + mLoadGroup->AddRequest(this, nullptr); + + // We already have an open IPDL connection to the parent. If on-modify-request + // listeners or load group observers canceled us, let the parent handle it + // and send it back to us naturally. + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelChild::nsIDivertableChannel +//----------------------------------------------------------------------------- +NS_IMETHODIMP +FTPChannelChild::DivertToParent(ChannelDiverterChild **aChild) +{ + MOZ_RELEASE_ASSERT(aChild); + MOZ_RELEASE_ASSERT(gNeckoChild); + MOZ_RELEASE_ASSERT(!mDivertingToParent); + + LOG(("FTPChannelChild::DivertToParent [this=%p]\n", this)); + + // We must fail DivertToParent() if there's no parent end of the channel (and + // won't be!) due to early failure. + if (NS_FAILED(mStatus) && !mIPCOpen) { + return mStatus; + } + + nsresult rv = Suspend(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Once this is set, it should not be unset before the child is taken down. + mDivertingToParent = true; + + PChannelDiverterChild* diverter = + gNeckoChild->SendPChannelDiverterConstructor(this); + MOZ_RELEASE_ASSERT(diverter); + + *aChild = static_cast<ChannelDiverterChild*>(diverter); + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::UnknownDecoderInvolvedKeepData() +{ + mUnknownDecoderInvolved = true; + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelChild::UnknownDecoderInvolvedOnStartRequestCalled() +{ + mUnknownDecoderInvolved = false; + + nsresult rv = NS_OK; + + if (mDivertingToParent) { + rv = mEventQ->PrependEvents(mUnknownDecoderEventQ); + } + mUnknownDecoderEventQ.Clear(); + + return rv; +} + +NS_IMETHODIMP +FTPChannelChild::GetDivertingToParent(bool* aDiverting) +{ + NS_ENSURE_ARG_POINTER(aDiverting); + *aDiverting = mDivertingToParent; + return NS_OK; +} + +} // namespace net +} // namespace mozilla + diff --git a/netwerk/protocol/ftp/FTPChannelChild.h b/netwerk/protocol/ftp/FTPChannelChild.h new file mode 100644 index 000000000..f68b85a72 --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelChild.h @@ -0,0 +1,164 @@ +/* -*- 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 "nsIDivertableChannel.h" + +#include "nsIStreamListener.h" +#include "PrivateBrowsingChannel.h" + +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 nsIDivertableChannel +{ +public: + typedef ::nsIStreamListener nsIStreamListener; + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIFTPCHANNEL + NS_DECL_NSIUPLOADCHANNEL + NS_DECL_NSIRESUMABLECHANNEL + NS_DECL_NSIPROXIEDCHANNEL + NS_DECL_NSICHILDCHANNEL + NS_DECL_NSIDIVERTABLECHANNEL + + NS_IMETHOD Cancel(nsresult status) override; + NS_IMETHOD Suspend() override; + NS_IMETHOD Resume() override; + + explicit FTPChannelChild(nsIURI* uri); + + void AddIPDLReference(); + void ReleaseIPDLReference(); + + NS_IMETHOD AsyncOpen(nsIStreamListener* listener, nsISupports* aContext) override; + + // Note that we handle this ourselves, overriding the nsBaseChannel + // default behavior, in order to be e10s-friendly. + NS_IMETHOD IsPending(bool* result) override; + + nsresult OpenContentStream(bool async, + nsIInputStream** stream, + nsIChannel** channel) override; + + bool IsSuspended(); + + void FlushedForDiversion(); + +protected: + virtual ~FTPChannelChild(); + + bool RecvOnStartRequest(const nsresult& aChannelStatus, + const int64_t& aContentLength, + const nsCString& aContentType, + const PRTime& aLastModified, + const nsCString& aEntityID, + const URIParams& aURI) override; + bool RecvOnDataAvailable(const nsresult& channelStatus, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count) override; + bool RecvOnStopRequest(const nsresult& channelStatus, + const nsCString &aErrorMsg, + const bool &aUseUTF8) override; + bool RecvFailedAsyncOpen(const nsresult& statusCode) override; + bool RecvFlushedForDiversion() override; + bool RecvDivertMessages() override; + bool 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& channelStatus, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count); + void MaybeDivertOnData(const nsCString& data, + const uint64_t& offset, + const uint32_t& count); + void MaybeDivertOnStop(const nsresult& statusCode); + void DoOnStopRequest(const nsresult& statusCode, + const nsCString &aErrorMsg, + bool aUseUTF8); + void DoFailedAsyncOpen(const nsresult& statusCode); + void DoDeleteSelf(); + + friend class FTPStartRequestEvent; + friend class FTPDataAvailableEvent; + friend class MaybeDivertOnDataFTPEvent; + friend class FTPStopRequestEvent; + friend class MaybeDivertOnStopFTPEvent; + friend class FTPFailedAsyncOpenEvent; + friend class FTPDeleteSelfEvent; + +private: + nsCOMPtr<nsIInputStream> mUploadStream; + + bool mIPCOpen; + RefPtr<ChannelEventQueue> mEventQ; + + // If nsUnknownDecoder is involved we queue onDataAvailable (and possibly + // OnStopRequest) so that we can divert them if needed when the listener's + // OnStartRequest is finally called + nsTArray<UniquePtr<ChannelEvent>> mUnknownDecoderEventQ; + bool mUnknownDecoderInvolved; + + bool mCanceled; + uint32_t mSuspendCount; + bool mIsPending; + + PRTime mLastModifiedTime; + uint64_t mStartPos; + nsCString mEntityID; + + // Once set, OnData and possibly OnStop will be diverted to the parent. + bool mDivertingToParent; + // Once set, no OnStart/OnData/OnStop callbacks should be received from the + // parent channel, nor dequeued from the ChannelEventQueue. + bool mFlushedForDiversion; + // Set if SendSuspend is called. Determines if SendResume is needed when + // diverting callbacks to parent. + bool mSuspendSent; +}; + +inline bool +FTPChannelChild::IsSuspended() +{ + 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 000000000..cdd334971 --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelParent.cpp @@ -0,0 +1,896 @@ +/* -*- 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/TabParent.h" +#include "nsFTPChannel.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsQueryObject.h" +#include "nsFtpProtocolHandler.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPromptProvider.h" +#include "nsIEncodedChannel.h" +#include "nsIHttpChannelInternal.h" +#include "nsIForcePendingChannel.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "mozilla/Unused.h" +#include "SerializedLoadContext.h" +#include "nsIContentPolicy.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/LoadInfo.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(const PBrowserOrId& aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus) + : mIPCClosed(false) + , mLoadContext(aLoadContext) + , mPBOverride(aOverrideStatus) + , mStatus(NS_OK) + , mDivertingFromChild(false) + , mDivertedOnStartRequest(false) + , mSuspendedForDiversion(false) + , mUseUTF8(false) +{ + nsIProtocolHandler* handler; + CallGetService(NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ftp", &handler); + MOZ_ASSERT(handler, "no ftp handler"); + + if (aIframeEmbedding.type() == PBrowserOrId::TPBrowserParent) { + mTabParent = static_cast<dom::TabParent*>(aIframeEmbedding.get_PBrowserParent()); + } + + 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()); + } + case FTPChannelCreationArgs::TFTPChannelConnectArgs: + { + const FTPChannelConnectArgs& cArgs = aArgs.get_FTPChannelConnectArgs(); + return ConnectChannel(cArgs.channelId()); + } + default: + NS_NOTREACHED("unknown open type"); + return false; + } +} + +bool +FTPChannelParent::DoAsyncOpen(const URIParams& aURI, + const uint64_t& aStartPos, + const nsCString& aEntityID, + const OptionalInputStreamParams& aUploadStream, + const OptionalLoadInfoArgs& aLoadInfoArgs) +{ + 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); + } + + NeckoOriginAttributes 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, + nsIRequest::LOAD_NORMAL, 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); + + nsTArray<mozilla::ipc::FileDescriptor> fds; + nsCOMPtr<nsIInputStream> upload = DeserializeInputStream(aUploadStream, fds); + if (upload) { + // contentType and contentLength are ignored + rv = ftpChan->SetUploadStream(upload, EmptyCString(), 0); + if (NS_FAILED(rv)) + return SendFailedAsyncOpen(rv); + } + + rv = ftpChan->ResumeAt(aStartPos, aEntityID); + if (NS_FAILED(rv)) + return SendFailedAsyncOpen(rv); + + if (loadInfo && loadInfo->GetEnforceSecurity()) { + rv = ftpChan->AsyncOpen2(this); + } + else { + rv = ftpChan->AsyncOpen(this, nullptr); + } + + if (NS_FAILED(rv)) + return SendFailedAsyncOpen(rv); + + return true; +} + +bool +FTPChannelParent::ConnectChannel(const uint32_t& channelId) +{ + nsresult rv; + + LOG(("Looking for a registered channel [this=%p, id=%d]", this, channelId)); + + nsCOMPtr<nsIChannel> channel; + rv = NS_LinkRedirectChannels(channelId, this, getter_AddRefs(channel)); + if (NS_SUCCEEDED(rv)) + mChannel = channel; + + LOG((" found channel %p, rv=%08x", mChannel.get(), rv)); + + return true; +} + +bool +FTPChannelParent::RecvCancel(const nsresult& status) +{ + if (mChannel) + mChannel->Cancel(status); + + return true; +} + +bool +FTPChannelParent::RecvSuspend() +{ + if (mChannel) { + SuspendChannel(); + } + return true; +} + +bool +FTPChannelParent::RecvResume() +{ + if (mChannel) { + ResumeChannel(); + } + return true; +} + +class FTPDivertDataAvailableEvent : public ChannelEvent +{ +public: + FTPDivertDataAvailableEvent(FTPChannelParent* aParent, + const nsCString& data, + const uint64_t& offset, + const uint32_t& count) + : mParent(aParent) + , mData(data) + , mOffset(offset) + , mCount(count) + { + } + + void Run() + { + mParent->DivertOnDataAvailable(mData, mOffset, mCount); + } + +private: + FTPChannelParent* mParent; + nsCString mData; + uint64_t mOffset; + uint32_t mCount; +}; + +bool +FTPChannelParent::RecvDivertOnDataAvailable(const nsCString& data, + const uint64_t& offset, + const uint32_t& count) +{ + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot RecvDivertOnDataAvailable if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return false; + } + + // Drop OnDataAvailables if the parent was canceled already. + if (NS_FAILED(mStatus)) { + return true; + } + + mEventQ->RunOrEnqueue(new FTPDivertDataAvailableEvent(this, data, offset, + count)); + return true; +} + +void +FTPChannelParent::DivertOnDataAvailable(const nsCString& data, + const uint64_t& offset, + const uint32_t& count) +{ + LOG(("FTPChannelParent::DivertOnDataAvailable [this=%p]\n", this)); + + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot DivertOnDataAvailable if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } + + // Drop OnDataAvailables if the parent was canceled already. + if (NS_FAILED(mStatus)) { + return; + } + + nsCOMPtr<nsIInputStream> stringStream; + nsresult rv = NS_NewByteInputStream(getter_AddRefs(stringStream), data.get(), + count, NS_ASSIGNMENT_DEPEND); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + return; + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + + rv = OnDataAvailable(mChannel, nullptr, stringStream, offset, count); + + stringStream->Close(); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + } +} + +class FTPDivertStopRequestEvent : public ChannelEvent +{ +public: + FTPDivertStopRequestEvent(FTPChannelParent* aParent, + const nsresult& statusCode) + : mParent(aParent) + , mStatusCode(statusCode) + { + } + + void Run() { + mParent->DivertOnStopRequest(mStatusCode); + } + +private: + FTPChannelParent* mParent; + nsresult mStatusCode; +}; + +bool +FTPChannelParent::RecvDivertOnStopRequest(const nsresult& statusCode) +{ + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot RecvDivertOnStopRequest if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return false; + } + + mEventQ->RunOrEnqueue(new FTPDivertStopRequestEvent(this, statusCode)); + return true; +} + +void +FTPChannelParent::DivertOnStopRequest(const nsresult& statusCode) +{ + LOG(("FTPChannelParent::DivertOnStopRequest [this=%p]\n", this)); + + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot DivertOnStopRequest if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } + + // Honor the channel's status even if the underlying transaction completed. + nsresult status = NS_FAILED(mStatus) ? mStatus : statusCode; + + // Reset fake pending status in case OnStopRequest has already been called. + if (mChannel) { + nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel); + if (forcePendingIChan) { + forcePendingIChan->ForcePending(false); + } + } + + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + OnStopRequest(mChannel, nullptr, status); +} + +class FTPDivertCompleteEvent : public ChannelEvent +{ +public: + explicit FTPDivertCompleteEvent(FTPChannelParent* aParent) + : mParent(aParent) + { + } + + void Run() { + mParent->DivertComplete(); + } + +private: + FTPChannelParent* mParent; +}; + +bool +FTPChannelParent::RecvDivertComplete() +{ + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot RecvDivertComplete if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return false; + } + + mEventQ->RunOrEnqueue(new FTPDivertCompleteEvent(this)); + return true; +} + +void +FTPChannelParent::DivertComplete() +{ + LOG(("FTPChannelParent::DivertComplete [this=%p]\n", this)); + + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot DivertComplete if diverting is not set!"); + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } + + nsresult rv = ResumeForDiversion(); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailDiversion(NS_ERROR_UNEXPECTED); + } +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIRequestObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + LOG(("FTPChannelParent::OnStartRequest [this=%p]\n", this)); + + if (mDivertingFromChild) { + MOZ_RELEASE_ASSERT(mDivertToListener, + "Cannot divert if listener is unset!"); + return mDivertToListener->OnStartRequest(aRequest, aContext); + } + + nsCOMPtr<nsIChannel> chan = do_QueryInterface(aRequest); + MOZ_ASSERT(chan); + NS_ENSURE_TRUE(chan, NS_ERROR_UNEXPECTED); + + int64_t contentLength; + chan->GetContentLength(&contentLength); + nsCString contentType; + chan->GetContentType(contentType); + + 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) { + httpChan->GetLastModifiedTime(&lastModified); + } + + URIParams uriparam; + nsCOMPtr<nsIURI> uri; + chan->GetURI(getter_AddRefs(uri)); + SerializeURI(uri, uriparam); + + if (mIPCClosed || !SendOnStartRequest(mStatus, contentLength, contentType, + lastModified, entityID, uriparam)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatusCode) +{ + LOG(("FTPChannelParent::OnStopRequest: [this=%p status=%ul]\n", + this, aStatusCode)); + + if (mDivertingFromChild) { + MOZ_RELEASE_ASSERT(mDivertToListener, + "Cannot divert if listener is unset!"); + return mDivertToListener->OnStopRequest(aRequest, aContext, aStatusCode); + } + + if (mIPCClosed || !SendOnStopRequest(aStatusCode, mErrorMsg, mUseUTF8)) { + return NS_ERROR_UNEXPECTED; + } + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIStreamListener +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aInputStream, + uint64_t aOffset, + uint32_t aCount) +{ + LOG(("FTPChannelParent::OnDataAvailable [this=%p]\n", this)); + + if (mDivertingFromChild) { + MOZ_RELEASE_ASSERT(mDivertToListener, + "Cannot divert if listener is unset!"); + return mDivertToListener->OnDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount); + } + + nsCString data; + nsresult rv = NS_ReadInputStreamToString(aInputStream, data, aCount); + if (NS_FAILED(rv)) + return rv; + + if (mIPCClosed || !SendOnDataAvailable(mStatus, data, aOffset, aCount)) + return NS_ERROR_UNEXPECTED; + + return NS_OK; +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::nsIParentChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +FTPChannelParent::SetParentListener(HttpChannelParentListener* aListener) +{ + // Do not need ptr to HttpChannelParentListener. + return NS_OK; +} + +NS_IMETHODIMP +FTPChannelParent::NotifyTrackingProtectionDisabled() +{ + // 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; +} + +//----------------------------------------------------------------------------- +// 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 (mTabParent) { + return mTabParent->QueryInterface(uuid, result); + } + } else if (uuid.Equals(NS_GET_IID(nsIAuthPrompt)) || + uuid.Equals(NS_GET_IID(nsIAuthPrompt2))) { + nsCOMPtr<nsIAuthPromptProvider> provider(do_QueryObject(mTabParent)); + if (provider) { + return provider->GetAuthPrompt(nsIAuthPromptProvider::PROMPT_NORMAL, + uuid, + result); + } + } + + // 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); +} + +nsresult +FTPChannelParent::SuspendChannel() +{ + nsCOMPtr<nsIChannelWithDivertableParentListener> chan = + do_QueryInterface(mChannel); + if (chan) { + return chan->SuspendInternal(); + } else { + return mChannel->Suspend(); + } +} + +nsresult +FTPChannelParent::ResumeChannel() +{ + nsCOMPtr<nsIChannelWithDivertableParentListener> chan = + do_QueryInterface(mChannel); + if (chan) { + return chan->ResumeInternal(); + } else { + return mChannel->Resume(); + } +} + +//----------------------------------------------------------------------------- +// FTPChannelParent::ADivertableParentChannel +//----------------------------------------------------------------------------- +nsresult +FTPChannelParent::SuspendForDiversion() +{ + MOZ_ASSERT(mChannel); + if (NS_WARN_IF(mDivertingFromChild)) { + MOZ_ASSERT(!mDivertingFromChild, "Already suspended for diversion!"); + return NS_ERROR_UNEXPECTED; + } + + // Try suspending the channel. Allow it to fail, since OnStopRequest may have + // been called and thus the channel may not be pending. + nsresult rv = SuspendChannel(); + MOZ_ASSERT(NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_AVAILABLE); + mSuspendedForDiversion = NS_SUCCEEDED(rv); + + // Once this is set, no more OnStart/OnData/OnStop callbacks should be sent + // to the child. + mDivertingFromChild = true; + + nsCOMPtr<nsIChannelWithDivertableParentListener> chan = + do_QueryInterface(mChannel); + if (chan) { + chan->MessageDiversionStarted(this); + } + + return NS_OK; +} + +/* private, supporting function for ADivertableParentChannel */ +nsresult +FTPChannelParent::ResumeForDiversion() +{ + MOZ_ASSERT(mChannel); + MOZ_ASSERT(mDivertToListener); + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot ResumeForDiversion if not diverting!"); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIChannelWithDivertableParentListener> chan = + do_QueryInterface(mChannel); + if (chan) { + chan->MessageDiversionStop(); + } + + if (mSuspendedForDiversion) { + nsresult rv = ResumeChannel(); + if (NS_WARN_IF(NS_FAILED(rv))) { + FailDiversion(NS_ERROR_UNEXPECTED, true); + return rv; + } + mSuspendedForDiversion = false; + } + + // Delete() will tear down IPDL, but ref from underlying nsFTPChannel will + // keep us alive if there's more data to be delivered to listener. + if (NS_WARN_IF(NS_FAILED(Delete()))) { + FailDiversion(NS_ERROR_UNEXPECTED); + return NS_ERROR_UNEXPECTED; + } + return NS_OK; +} + +nsresult +FTPChannelParent::SuspendMessageDiversion() +{ + // This only need to suspend message queue. + mEventQ->Suspend(); + return NS_OK; +} + +nsresult +FTPChannelParent::ResumeMessageDiversion() +{ + // This only need to resumes message queue. + mEventQ->Resume(); + return NS_OK; +} + +void +FTPChannelParent::DivertTo(nsIStreamListener *aListener) +{ + MOZ_ASSERT(aListener); + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot DivertTo new listener if diverting is not set!"); + return; + } + + if (NS_WARN_IF(mIPCClosed || !SendFlushedForDiversion())) { + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } + + mDivertToListener = aListener; + + // Call OnStartRequest and SendDivertMessages asynchronously to avoid + // reentering client context. + NS_DispatchToCurrentThread( + NewRunnableMethod(this, &FTPChannelParent::StartDiversion)); + return; +} + +void +FTPChannelParent::StartDiversion() +{ + if (NS_WARN_IF(!mDivertingFromChild)) { + MOZ_ASSERT(mDivertingFromChild, + "Cannot StartDiversion if diverting is not set!"); + return; + } + + // Fake pending status in case OnStopRequest has already been called. + if (mChannel) { + nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel); + if (forcePendingIChan) { + forcePendingIChan->ForcePending(true); + } + } + + { + AutoEventEnqueuer ensureSerialDispatch(mEventQ); + // Call OnStartRequest for the "DivertTo" listener. + nsresult rv = OnStartRequest(mChannel, nullptr); + if (NS_FAILED(rv)) { + if (mChannel) { + mChannel->Cancel(rv); + } + mStatus = rv; + return; + } + } + + // After OnStartRequest has been called, tell FTPChannelChild to divert the + // OnDataAvailables and OnStopRequest to this FTPChannelParent. + if (NS_WARN_IF(mIPCClosed || !SendDivertMessages())) { + FailDiversion(NS_ERROR_UNEXPECTED); + return; + } +} + +class FTPFailDiversionEvent : public Runnable +{ +public: + FTPFailDiversionEvent(FTPChannelParent *aChannelParent, + nsresult aErrorCode, + bool aSkipResume) + : mChannelParent(aChannelParent) + , mErrorCode(aErrorCode) + , mSkipResume(aSkipResume) + { + MOZ_RELEASE_ASSERT(aChannelParent); + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + } + NS_IMETHOD Run() override + { + mChannelParent->NotifyDiversionFailed(mErrorCode, mSkipResume); + return NS_OK; + } +private: + RefPtr<FTPChannelParent> mChannelParent; + nsresult mErrorCode; + bool mSkipResume; +}; + +void +FTPChannelParent::FailDiversion(nsresult aErrorCode, + bool aSkipResume) +{ + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + MOZ_RELEASE_ASSERT(mDivertingFromChild); + MOZ_RELEASE_ASSERT(mDivertToListener); + MOZ_RELEASE_ASSERT(mChannel); + + NS_DispatchToCurrentThread( + new FTPFailDiversionEvent(this, aErrorCode, aSkipResume)); +} + +void +FTPChannelParent::NotifyDiversionFailed(nsresult aErrorCode, + bool aSkipResume) +{ + MOZ_RELEASE_ASSERT(NS_FAILED(aErrorCode)); + MOZ_RELEASE_ASSERT(mDivertingFromChild); + MOZ_RELEASE_ASSERT(mDivertToListener); + MOZ_RELEASE_ASSERT(mChannel); + + mChannel->Cancel(aErrorCode); + nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel); + if (forcePendingIChan) { + forcePendingIChan->ForcePending(false); + } + + bool isPending = false; + nsresult rv = mChannel->IsPending(&isPending); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); + + // Resume only we suspended earlier. + if (mSuspendedForDiversion) { + ResumeChannel(); + } + // Channel has already sent OnStartRequest to the child, so ensure that we + // call it here if it hasn't already been called. + if (!mDivertedOnStartRequest) { + nsCOMPtr<nsIForcePendingChannel> forcePendingIChan = do_QueryInterface(mChannel); + if (forcePendingIChan) { + forcePendingIChan->ForcePending(true); + } + mDivertToListener->OnStartRequest(mChannel, nullptr); + + if (forcePendingIChan) { + forcePendingIChan->ForcePending(false); + } + } + // If the channel is pending, it will call OnStopRequest itself; otherwise, do + // it here. + if (!isPending) { + mDivertToListener->OnStopRequest(mChannel, nullptr, aErrorCode); + } + mDivertToListener = nullptr; + mChannel = nullptr; + + if (!mIPCClosed) { + Unused << SendDeleteSelf(); + } +} + +//----------------------------------------------------------------------------- +// 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 000000000..210c99618 --- /dev/null +++ b/netwerk/protocol/ftp/FTPChannelParent.h @@ -0,0 +1,146 @@ +/* -*- 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 "ADivertableParentChannel.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 TabParent; +class PBrowserOrId; +} // namespace dom + +namespace net { +class ChannelEventQueue; + +class FTPChannelParent final : public PFTPChannelParent + , public nsIParentChannel + , public nsIInterfaceRequestor + , public ADivertableParentChannel + , public nsIChannelEventSink + , public nsIFTPChannelParentInternal +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + NS_DECL_NSIPARENTCHANNEL + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSICHANNELEVENTSINK + + FTPChannelParent(const dom::PBrowserOrId& aIframeEmbedding, + nsILoadContext* aLoadContext, + PBOverrideStatus aOverrideStatus); + + bool Init(const FTPChannelCreationArgs& aOpenArgs); + + // ADivertableParentChannel functions. + void DivertTo(nsIStreamListener *aListener) override; + nsresult SuspendForDiversion() override; + nsresult SuspendMessageDiversion() override; + nsresult ResumeMessageDiversion() override; + + // Calls OnStartRequest for "DivertTo" listener, then notifies child channel + // that it should divert OnDataAvailable and OnStopRequest calls to this + // parent channel. + void StartDiversion(); + + // Handles calling OnStart/Stop if there are errors during diversion. + // Called asynchronously from FailDiversion. + void NotifyDiversionFailed(nsresult aErrorCode, bool aSkipResume = true); + + NS_IMETHOD SetErrorMsg(const char *aMsg, bool aUseUTF8) override; + +protected: + virtual ~FTPChannelParent(); + + // private, supporting function for ADivertableParentChannel. + nsresult ResumeForDiversion(); + + // Asynchronously calls NotifyDiversionFailed. + void FailDiversion(nsresult aErrorCode, bool aSkipResume = true); + + bool DoAsyncOpen(const URIParams& aURI, const uint64_t& aStartPos, + const nsCString& aEntityID, + const OptionalInputStreamParams& aUploadStream, + const OptionalLoadInfoArgs& aLoadInfoArgs); + + // used to connect redirected-to channel in parent with just created + // ChildChannel. Used during HTTP->FTP redirects. + bool ConnectChannel(const uint32_t& channelId); + + void DivertOnDataAvailable(const nsCString& data, + const uint64_t& offset, + const uint32_t& count); + void DivertOnStopRequest(const nsresult& statusCode); + void DivertComplete(); + + friend class FTPDivertDataAvailableEvent; + friend class FTPDivertStopRequestEvent; + friend class FTPDivertCompleteEvent; + + virtual bool RecvCancel(const nsresult& status) override; + virtual bool RecvSuspend() override; + virtual bool RecvResume() override; + virtual bool RecvDivertOnDataAvailable(const nsCString& data, + const uint64_t& offset, + const uint32_t& count) override; + virtual bool RecvDivertOnStopRequest(const nsresult& statusCode) override; + virtual bool RecvDivertComplete() override; + + nsresult SuspendChannel(); + nsresult ResumeChannel(); + + 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; + + // If OnStart/OnData/OnStop have been diverted from the child, forward them to + // this listener. + nsCOMPtr<nsIStreamListener> mDivertToListener; + // Set to the canceled status value if the main channel was canceled. + nsresult mStatus; + // Once set, no OnStart/OnData/OnStop calls should be accepted; conversely, it + // must be set when RecvDivertOnData/~DivertOnStop/~DivertComplete are + // received from the child channel. + bool mDivertingFromChild; + // Set if OnStart|StopRequest was called during a diversion from the child. + bool mDivertedOnStartRequest; + + // Set if we successfully suspended the nsHttpChannel for diversion. Unset + // when we call ResumeForDiversion. + bool mSuspendedForDiversion; + RefPtr<mozilla::dom::TabParent> mTabParent; + + 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 000000000..55721bdbe --- /dev/null +++ b/netwerk/protocol/ftp/PFTPChannel.ipdl @@ -0,0 +1,74 @@ +/* -*- 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 InputStreamParams; +include URIParams; + +//FIXME: bug #792908 (NeckoChannelParams already included by PNecko) +include NeckoChannelParams; +include protocol PBlob; //FIXME: bug #792908 + +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(); + + // Divert OnDataAvailable to the parent. + async DivertOnDataAvailable(nsCString data, + uint64_t offset, + uint32_t count); + + // Divert OnStopRequest to the parent. + async DivertOnStopRequest(nsresult statusCode); + + // Child has no more events/messages to divert to the parent. + async DivertComplete(); + +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); + + // Parent has been suspended for diversion; no more events to be enqueued. + async FlushedForDiversion(); + + // Child should resume processing the ChannelEventQueue, i.e. diverting any + // OnDataAvailable and OnStopRequest messages in the queue back to the parent. + async DivertMessages(); + + async DeleteSelf(); +}; + +} // namespace net +} // namespace mozilla + diff --git a/netwerk/protocol/ftp/doc/rfc959.txt b/netwerk/protocol/ftp/doc/rfc959.txt new file mode 100644 index 000000000..5c9f11af5 --- /dev/null +++ b/netwerk/protocol/ftp/doc/rfc959.txt @@ -0,0 +1,3933 @@ + + +Network Working Group J. Postel +Request for Comments: 959 J. Reynolds + ISI +Obsoletes RFC: 765 (IEN 149) October 1985 + + FILE TRANSFER PROTOCOL (FTP) + + +Status of this Memo + + This memo is the official specification of the File Transfer + Protocol (FTP). Distribution of this memo is unlimited. + + The following new optional commands are included in this edition of + the specification: + + CDUP (Change to Parent Directory), SMNT (Structure Mount), STOU + (Store Unique), RMD (Remove Directory), MKD (Make Directory), PWD + (Print Directory), and SYST (System). + + Note that this specification is compatible with the previous edition. + +1. INTRODUCTION + + The objectives of FTP are 1) to promote sharing of files (computer + programs and/or data), 2) to encourage indirect or implicit (via + programs) use of remote computers, 3) to shield a user from + variations in file storage systems among hosts, and 4) to transfer + data reliably and efficiently. FTP, though usable directly by a user + at a terminal, is designed mainly for use by programs. + + The attempt in this specification is to satisfy the diverse needs of + users of maxi-hosts, mini-hosts, personal workstations, and TACs, + with a simple, and easily implemented protocol design. + + This paper assumes knowledge of the Transmission Control Protocol + (TCP) [2] and the Telnet Protocol [3]. These documents are contained + in the ARPA-Internet protocol handbook [1]. + +2. OVERVIEW + + In this section, the history, the terminology, and the FTP model are + discussed. The terms defined in this section are only those that + have special significance in FTP. Some of the terminology is very + specific to the FTP model; some readers may wish to turn to the + section on the FTP model while reviewing the terminology. + + + + + + + +Postel & Reynolds [Page 1] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 2.1. HISTORY + + FTP has had a long evolution over the years. Appendix III is a + chronological compilation of Request for Comments documents + relating to FTP. These include the first proposed file transfer + mechanisms in 1971 that were developed for implementation on hosts + at M.I.T. (RFC 114), plus comments and discussion in RFC 141. + + RFC 172 provided a user-level oriented protocol for file transfer + between host computers (including terminal IMPs). A revision of + this as RFC 265, restated FTP for additional review, while RFC 281 + suggested further changes. The use of a "Set Data Type" + transaction was proposed in RFC 294 in January 1982. + + RFC 354 obsoleted RFCs 264 and 265. The File Transfer Protocol + was now defined as a protocol for file transfer between HOSTs on + the ARPANET, with the primary function of FTP defined as + transfering files efficiently and reliably among hosts and + allowing the convenient use of remote file storage capabilities. + RFC 385 further commented on errors, emphasis points, and + additions to the protocol, while RFC 414 provided a status report + on the working server and user FTPs. RFC 430, issued in 1973, + (among other RFCs too numerous to mention) presented further + comments on FTP. Finally, an "official" FTP document was + published as RFC 454. + + By July 1973, considerable changes from the last versions of FTP + were made, but the general structure remained the same. RFC 542 + was published as a new "official" specification to reflect these + changes. However, many implementations based on the older + specification were not updated. + + In 1974, RFCs 607 and 614 continued comments on FTP. RFC 624 + proposed further design changes and minor modifications. In 1975, + RFC 686 entitled, "Leaving Well Enough Alone", discussed the + differences between all of the early and later versions of FTP. + RFC 691 presented a minor revision of RFC 686, regarding the + subject of print files. + + Motivated by the transition from the NCP to the TCP as the + underlying protocol, a phoenix was born out of all of the above + efforts in RFC 765 as the specification of FTP for use on TCP. + + This current edition of the FTP specification is intended to + correct some minor documentation errors, to improve the + explanation of some protocol features, and to add some new + optional commands. + + +Postel & Reynolds [Page 2] + + + +RFC 959 October 1985 +File Transfer Protocol + + + In particular, the following new optional commands are included in + this edition of the specification: + + CDUP - Change to Parent Directory + + SMNT - Structure Mount + + STOU - Store Unique + + RMD - Remove Directory + + MKD - Make Directory + + PWD - Print Directory + + SYST - System + + This specification is compatible with the previous edition. A + program implemented in conformance to the previous specification + should automatically be in conformance to this specification. + + 2.2. TERMINOLOGY + + ASCII + + The ASCII character set is as defined in the ARPA-Internet + Protocol Handbook. In FTP, ASCII characters are defined to be + the lower half of an eight-bit code set (i.e., the most + significant bit is zero). + + access controls + + Access controls define users' access privileges to the use of a + system, and to the files in that system. Access controls are + necessary to prevent unauthorized or accidental use of files. + It is the prerogative of a server-FTP process to invoke access + controls. + + byte size + + There are two byte sizes of interest in FTP: the logical byte + size of the file, and the transfer byte size used for the + transmission of the data. The transfer byte size is always 8 + bits. The transfer byte size is not necessarily the byte size + in which data is to be stored in a system, nor the logical byte + size for interpretation of the structure of the data. + + + +Postel & Reynolds [Page 3] + + + +RFC 959 October 1985 +File Transfer Protocol + + + control connection + + The communication path between the USER-PI and SERVER-PI for + the exchange of commands and replies. This connection follows + the Telnet Protocol. + + data connection + + A full duplex connection over which data is transferred, in a + specified mode and type. The data transferred may be a part of + a file, an entire file or a number of files. The path may be + between a server-DTP and a user-DTP, or between two + server-DTPs. + + data port + + The passive data transfer process "listens" on the data port + for a connection from the active transfer process in order to + open the data connection. + + DTP + + The data transfer process establishes and manages the data + connection. The DTP can be passive or active. + + End-of-Line + + The end-of-line sequence defines the separation of printing + lines. The sequence is Carriage Return, followed by Line Feed. + + EOF + + The end-of-file condition that defines the end of a file being + transferred. + + EOR + + The end-of-record condition that defines the end of a record + being transferred. + + error recovery + + A procedure that allows a user to recover from certain errors + such as failure of either host system or transfer process. In + FTP, error recovery may involve restarting a file transfer at a + given checkpoint. + + + +Postel & Reynolds [Page 4] + + + +RFC 959 October 1985 +File Transfer Protocol + + + FTP commands + + A set of commands that comprise the control information flowing + from the user-FTP to the server-FTP process. + + file + + An ordered set of computer data (including programs), of + arbitrary length, uniquely identified by a pathname. + + mode + + The mode in which data is to be transferred via the data + connection. The mode defines the data format during transfer + including EOR and EOF. The transfer modes defined in FTP are + described in the Section on Transmission Modes. + + NVT + + The Network Virtual Terminal as defined in the Telnet Protocol. + + NVFS + + The Network Virtual File System. A concept which defines a + standard network file system with standard commands and + pathname conventions. + + page + + A file may be structured as a set of independent parts called + pages. FTP supports the transmission of discontinuous files as + independent indexed pages. + + pathname + + Pathname is defined to be the character string which must be + input to a file system by a user in order to identify a file. + Pathname normally contains device and/or directory names, and + file name specification. FTP does not yet specify a standard + pathname convention. Each user must follow the file naming + conventions of the file systems involved in the transfer. + + PI + + The protocol interpreter. The user and server sides of the + protocol have distinct roles implemented in a user-PI and a + server-PI. + + +Postel & Reynolds [Page 5] + + + +RFC 959 October 1985 +File Transfer Protocol + + + record + + A sequential file may be structured as a number of contiguous + parts called records. Record structures are supported by FTP + but a file need not have record structure. + + reply + + A reply is an acknowledgment (positive or negative) sent from + server to user via the control connection in response to FTP + commands. The general form of a reply is a completion code + (including error codes) followed by a text string. The codes + are for use by programs and the text is usually intended for + human users. + + server-DTP + + The data transfer process, in its normal "active" state, + establishes the data connection with the "listening" data port. + It sets up parameters for transfer and storage, and transfers + data on command from its PI. The DTP can be placed in a + "passive" state to listen for, rather than initiate a + connection on the data port. + + server-FTP process + + A process or set of processes which perform the function of + file transfer in cooperation with a user-FTP process and, + possibly, another server. The functions consist of a protocol + interpreter (PI) and a data transfer process (DTP). + + server-PI + + The server protocol interpreter "listens" on Port L for a + connection from a user-PI and establishes a control + communication connection. It receives standard FTP commands + from the user-PI, sends replies, and governs the server-DTP. + + type + + The data representation type used for data transfer and + storage. Type implies certain transformations between the time + of data storage and data transfer. The representation types + defined in FTP are described in the Section on Establishing + Data Connections. + + + + +Postel & Reynolds [Page 6] + + + +RFC 959 October 1985 +File Transfer Protocol + + + user + + A person or a process on behalf of a person wishing to obtain + file transfer service. The human user may interact directly + with a server-FTP process, but use of a user-FTP process is + preferred since the protocol design is weighted towards + automata. + + user-DTP + + The data transfer process "listens" on the data port for a + connection from a server-FTP process. If two servers are + transferring data between them, the user-DTP is inactive. + + user-FTP process + + A set of functions including a protocol interpreter, a data + transfer process and a user interface which together perform + the function of file transfer in cooperation with one or more + server-FTP processes. The user interface allows a local + language to be used in the command-reply dialogue with the + user. + + user-PI + + The user protocol interpreter initiates the control connection + from its port U to the server-FTP process, initiates FTP + commands, and governs the user-DTP if that process is part of + the file transfer. + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 7] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 2.3. THE FTP MODEL + + With the above definitions in mind, the following model (shown in + Figure 1) may be diagrammed for an FTP service. + + ------------- + |/---------\| + || User || -------- + ||Interface|<--->| User | + |\----^----/| -------- + ---------- | | | + |/------\| FTP Commands |/----V----\| + ||Server|<---------------->| User || + || PI || FTP Replies || PI || + |\--^---/| |\----^----/| + | | | | | | + -------- |/--V---\| Data |/----V----\| -------- + | File |<--->|Server|<---------------->| User |<--->| File | + |System| || DTP || Connection || DTP || |System| + -------- |\------/| |\---------/| -------- + ---------- ------------- + + Server-FTP USER-FTP + + NOTES: 1. The data connection may be used in either direction. + 2. The data connection need not exist all of the time. + + Figure 1 Model for FTP Use + + In the model described in Figure 1, the user-protocol interpreter + initiates the control connection. The control connection follows + the Telnet protocol. At the initiation of the user, standard FTP + commands are generated by the user-PI and transmitted to the + server process via the control connection. (The user may + establish a direct control connection to the server-FTP, from a + TAC terminal for example, and generate standard FTP commands + independently, bypassing the user-FTP process.) Standard replies + are sent from the server-PI to the user-PI over the control + connection in response to the commands. + + The FTP commands specify the parameters for the data connection + (data port, transfer mode, representation type, and structure) and + the nature of file system operation (store, retrieve, append, + delete, etc.). The user-DTP or its designate should "listen" on + the specified data port, and the server initiate the data + connection and data transfer in accordance with the specified + parameters. It should be noted that the data port need not be in + + +Postel & Reynolds [Page 8] + + + +RFC 959 October 1985 +File Transfer Protocol + + + the same host that initiates the FTP commands via the control + connection, but the user or the user-FTP process must ensure a + "listen" on the specified data port. It ought to also be noted + that the data connection may be used for simultaneous sending and + receiving. + + In another situation a user might wish to transfer files between + two hosts, neither of which is a local host. The user sets up + control connections to the two servers and then arranges for a + data connection between them. In this manner, control information + is passed to the user-PI but data is transferred between the + server data transfer processes. Following is a model of this + server-server interaction. + + + Control ------------ Control + ---------->| User-FTP |<----------- + | | User-PI | | + | | "C" | | + V ------------ V + -------------- -------------- + | Server-FTP | Data Connection | Server-FTP | + | "A" |<---------------------->| "B" | + -------------- Port (A) Port (B) -------------- + + + Figure 2 + + The protocol requires that the control connections be open while + data transfer is in progress. It is the responsibility of the + user to request the closing of the control connections when + finished using the FTP service, while it is the server who takes + the action. The server may abort data transfer if the control + connections are closed without command. + + The Relationship between FTP and Telnet: + + The FTP uses the Telnet protocol on the control connection. + This can be achieved in two ways: first, the user-PI or the + server-PI may implement the rules of the Telnet Protocol + directly in their own procedures; or, second, the user-PI or + the server-PI may make use of the existing Telnet module in the + system. + + Ease of implementaion, sharing code, and modular programming + argue for the second approach. Efficiency and independence + + + +Postel & Reynolds [Page 9] + + + +RFC 959 October 1985 +File Transfer Protocol + + + argue for the first approach. In practice, FTP relies on very + little of the Telnet Protocol, so the first approach does not + necessarily involve a large amount of code. + +3. DATA TRANSFER FUNCTIONS + + Files are transferred only via the data connection. The control + connection is used for the transfer of commands, which describe the + functions to be performed, and the replies to these commands (see the + Section on FTP Replies). Several commands are concerned with the + transfer of data between hosts. These data transfer commands include + the MODE command which specify how the bits of the data are to be + transmitted, and the STRUcture and TYPE commands, which are used to + define the way in which the data are to be represented. The + transmission and representation are basically independent but the + "Stream" transmission mode is dependent on the file structure + attribute and if "Compressed" transmission mode is used, the nature + of the filler byte depends on the representation type. + + 3.1. DATA REPRESENTATION AND STORAGE + + Data is transferred from a storage device in the sending host to a + storage device in the receiving host. Often it is necessary to + perform certain transformations on the data because data storage + representations in the two systems are different. For example, + NVT-ASCII has different data storage representations in different + systems. DEC TOPS-20s's generally store NVT-ASCII as five 7-bit + ASCII characters, left-justified in a 36-bit word. IBM Mainframe's + store NVT-ASCII as 8-bit EBCDIC codes. Multics stores NVT-ASCII + as four 9-bit characters in a 36-bit word. It is desirable to + convert characters into the standard NVT-ASCII representation when + transmitting text between dissimilar systems. The sending and + receiving sites would have to perform the necessary + transformations between the standard representation and their + internal representations. + + A different problem in representation arises when transmitting + binary data (not character codes) between host systems with + different word lengths. It is not always clear how the sender + should send data, and the receiver store it. For example, when + transmitting 32-bit bytes from a 32-bit word-length system to a + 36-bit word-length system, it may be desirable (for reasons of + efficiency and usefulness) to store the 32-bit bytes + right-justified in a 36-bit word in the latter system. In any + case, the user should have the option of specifying data + representation and transformation functions. It should be noted + + + +Postel & Reynolds [Page 10] + + + +RFC 959 October 1985 +File Transfer Protocol + + + that FTP provides for very limited data type representations. + Transformations desired beyond this limited capability should be + performed by the user directly. + + 3.1.1. DATA TYPES + + Data representations are handled in FTP by a user specifying a + representation type. This type may implicitly (as in ASCII or + EBCDIC) or explicitly (as in Local byte) define a byte size for + interpretation which is referred to as the "logical byte size." + Note that this has nothing to do with the byte size used for + transmission over the data connection, called the "transfer + byte size", and the two should not be confused. For example, + NVT-ASCII has a logical byte size of 8 bits. If the type is + Local byte, then the TYPE command has an obligatory second + parameter specifying the logical byte size. The transfer byte + size is always 8 bits. + + 3.1.1.1. ASCII TYPE + + This is the default type and must be accepted by all FTP + implementations. It is intended primarily for the transfer + of text files, except when both hosts would find the EBCDIC + type more convenient. + + The sender converts the data from an internal character + representation to the standard 8-bit NVT-ASCII + representation (see the Telnet specification). The receiver + will convert the data from the standard form to his own + internal form. + + In accordance with the NVT standard, the <CRLF> sequence + should be used where necessary to denote the end of a line + of text. (See the discussion of file structure at the end + of the Section on Data Representation and Storage.) + + Using the standard NVT-ASCII representation means that data + must be interpreted as 8-bit bytes. + + The Format parameter for ASCII and EBCDIC types is discussed + below. + + + + + + + + +Postel & Reynolds [Page 11] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.1.1.2. EBCDIC TYPE + + This type is intended for efficient transfer between hosts + which use EBCDIC for their internal character + representation. + + For transmission, the data are represented as 8-bit EBCDIC + characters. The character code is the only difference + between the functional specifications of EBCDIC and ASCII + types. + + End-of-line (as opposed to end-of-record--see the discussion + of structure) will probably be rarely used with EBCDIC type + for purposes of denoting structure, but where it is + necessary the <NL> character should be used. + + 3.1.1.3. IMAGE TYPE + + The data are sent as contiguous bits which, for transfer, + are packed into the 8-bit transfer bytes. The receiving + site must store the data as contiguous bits. The structure + of the storage system might necessitate the padding of the + file (or of each record, for a record-structured file) to + some convenient boundary (byte, word or block). This + padding, which must be all zeros, may occur only at the end + of the file (or at the end of each record) and there must be + a way of identifying the padding bits so that they may be + stripped off if the file is retrieved. The padding + transformation should be well publicized to enable a user to + process a file at the storage site. + + Image type is intended for the efficient storage and + retrieval of files and for the transfer of binary data. It + is recommended that this type be accepted by all FTP + implementations. + + 3.1.1.4. LOCAL TYPE + + The data is transferred in logical bytes of the size + specified by the obligatory second parameter, Byte size. + The value of Byte size must be a decimal integer; there is + no default value. The logical byte size is not necessarily + the same as the transfer byte size. If there is a + difference in byte sizes, then the logical bytes should be + packed contiguously, disregarding transfer byte boundaries + and with any necessary padding at the end. + + + +Postel & Reynolds [Page 12] + + + +RFC 959 October 1985 +File Transfer Protocol + + + When the data reaches the receiving host, it will be + transformed in a manner dependent on the logical byte size + and the particular host. This transformation must be + invertible (i.e., an identical file can be retrieved if the + same parameters are used) and should be well publicized by + the FTP implementors. + + For example, a user sending 36-bit floating-point numbers to + a host with a 32-bit word could send that data as Local byte + with a logical byte size of 36. The receiving host would + then be expected to store the logical bytes so that they + could be easily manipulated; in this example putting the + 36-bit logical bytes into 64-bit double words should + suffice. + + In another example, a pair of hosts with a 36-bit word size + may send data to one another in words by using TYPE L 36. + The data would be sent in the 8-bit transmission bytes + packed so that 9 transmission bytes carried two host words. + + 3.1.1.5. FORMAT CONTROL + + The types ASCII and EBCDIC also take a second (optional) + parameter; this is to indicate what kind of vertical format + control, if any, is associated with a file. The following + data representation types are defined in FTP: + + A character file may be transferred to a host for one of + three purposes: for printing, for storage and later + retrieval, or for processing. If a file is sent for + printing, the receiving host must know how the vertical + format control is represented. In the second case, it must + be possible to store a file at a host and then retrieve it + later in exactly the same form. Finally, it should be + possible to move a file from one host to another and process + the file at the second host without undue trouble. A single + ASCII or EBCDIC format does not satisfy all these + conditions. Therefore, these types have a second parameter + specifying one of the following three formats: + + 3.1.1.5.1. NON PRINT + + This is the default format to be used if the second + (format) parameter is omitted. Non-print format must be + accepted by all FTP implementations. + + + + +Postel & Reynolds [Page 13] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The file need contain no vertical format information. If + it is passed to a printer process, this process may + assume standard values for spacing and margins. + + Normally, this format will be used with files destined + for processing or just storage. + + 3.1.1.5.2. TELNET FORMAT CONTROLS + + The file contains ASCII/EBCDIC vertical format controls + (i.e., <CR>, <LF>, <NL>, <VT>, <FF>) which the printer + process will interpret appropriately. <CRLF>, in exactly + this sequence, also denotes end-of-line. + + 3.1.1.5.2. CARRIAGE CONTROL (ASA) + + The file contains ASA (FORTRAN) vertical format control + characters. (See RFC 740 Appendix C; and Communications + of the ACM, Vol. 7, No. 10, p. 606, October 1964.) In a + line or a record formatted according to the ASA Standard, + the first character is not to be printed. Instead, it + should be used to determine the vertical movement of the + paper which should take place before the rest of the + record is printed. + + The ASA Standard specifies the following control + characters: + + Character Vertical Spacing + + blank Move paper up one line + 0 Move paper up two lines + 1 Move paper to top of next page + + No movement, i.e., overprint + + Clearly there must be some way for a printer process to + distinguish the end of the structural entity. If a file + has record structure (see below) this is no problem; + records will be explicitly marked during transfer and + storage. If the file has no record structure, the <CRLF> + end-of-line sequence is used to separate printing lines, + but these format effectors are overridden by the ASA + controls. + + + + + + +Postel & Reynolds [Page 14] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.1.2. DATA STRUCTURES + + In addition to different representation types, FTP allows the + structure of a file to be specified. Three file structures are + defined in FTP: + + file-structure, where there is no internal structure and + the file is considered to be a + continuous sequence of data bytes, + + record-structure, where the file is made up of sequential + records, + + and page-structure, where the file is made up of independent + indexed pages. + + File-structure is the default to be assumed if the STRUcture + command has not been used but both file and record structures + must be accepted for "text" files (i.e., files with TYPE ASCII + or EBCDIC) by all FTP implementations. The structure of a file + will affect both the transfer mode of a file (see the Section + on Transmission Modes) and the interpretation and storage of + the file. + + The "natural" structure of a file will depend on which host + stores the file. A source-code file will usually be stored on + an IBM Mainframe in fixed length records but on a DEC TOPS-20 + as a stream of characters partitioned into lines, for example + by <CRLF>. If the transfer of files between such disparate + sites is to be useful, there must be some way for one site to + recognize the other's assumptions about the file. + + With some sites being naturally file-oriented and others + naturally record-oriented there may be problems if a file with + one structure is sent to a host oriented to the other. If a + text file is sent with record-structure to a host which is file + oriented, then that host should apply an internal + transformation to the file based on the record structure. + Obviously, this transformation should be useful, but it must + also be invertible so that an identical file may be retrieved + using record structure. + + In the case of a file being sent with file-structure to a + record-oriented host, there exists the question of what + criteria the host should use to divide the file into records + which can be processed locally. If this division is necessary, + the FTP implementation should use the end-of-line sequence, + + +Postel & Reynolds [Page 15] + + + +RFC 959 October 1985 +File Transfer Protocol + + + <CRLF> for ASCII, or <NL> for EBCDIC text files, as the + delimiter. If an FTP implementation adopts this technique, it + must be prepared to reverse the transformation if the file is + retrieved with file-structure. + + 3.1.2.1. FILE STRUCTURE + + File structure is the default to be assumed if the STRUcture + command has not been used. + + In file-structure there is no internal structure and the + file is considered to be a continuous sequence of data + bytes. + + 3.1.2.2. RECORD STRUCTURE + + Record structures must be accepted for "text" files (i.e., + files with TYPE ASCII or EBCDIC) by all FTP implementations. + + In record-structure the file is made up of sequential + records. + + 3.1.2.3. PAGE STRUCTURE + + To transmit files that are discontinuous, FTP defines a page + structure. Files of this type are sometimes known as + "random access files" or even as "holey files". In these + files there is sometimes other information associated with + the file as a whole (e.g., a file descriptor), or with a + section of the file (e.g., page access controls), or both. + In FTP, the sections of the file are called pages. + + To provide for various page sizes and associated + information, each page is sent with a page header. The page + header has the following defined fields: + + Header Length + + The number of logical bytes in the page header + including this byte. The minimum header length is 4. + + Page Index + + The logical page number of this section of the file. + This is not the transmission sequence number of this + page, but the index used to identify this page of the + file. + + +Postel & Reynolds [Page 16] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Data Length + + The number of logical bytes in the page data. The + minimum data length is 0. + + Page Type + + The type of page this is. The following page types + are defined: + + 0 = Last Page + + This is used to indicate the end of a paged + structured transmission. The header length must + be 4, and the data length must be 0. + + 1 = Simple Page + + This is the normal type for simple paged files + with no page level associated control + information. The header length must be 4. + + 2 = Descriptor Page + + This type is used to transmit the descriptive + information for the file as a whole. + + 3 = Access Controlled Page + + This type includes an additional header field + for paged files with page level access control + information. The header length must be 5. + + Optional Fields + + Further header fields may be used to supply per page + control information, for example, per page access + control. + + All fields are one logical byte in length. The logical byte + size is specified by the TYPE command. See Appendix I for + further details and a specific case at the page structure. + + A note of caution about parameters: a file must be stored and + retrieved with the same parameters if the retrieved version is to + + + + +Postel & Reynolds [Page 17] + + + +RFC 959 October 1985 +File Transfer Protocol + + + be identical to the version originally transmitted. Conversely, + FTP implementations must return a file identical to the original + if the parameters used to store and retrieve a file are the same. + + 3.2. ESTABLISHING DATA CONNECTIONS + + The mechanics of transferring data consists of setting up the data + connection to the appropriate ports and choosing the parameters + for transfer. Both the user and the server-DTPs have a default + data port. The user-process default data port is the same as the + control connection port (i.e., U). The server-process default + data port is the port adjacent to the control connection port + (i.e., L-1). + + The transfer byte size is 8-bit bytes. This byte size is relevant + only for the actual transfer of the data; it has no bearing on + representation of the data within a host's file system. + + The passive data transfer process (this may be a user-DTP or a + second server-DTP) shall "listen" on the data port prior to + sending a transfer request command. The FTP request command + determines the direction of the data transfer. The server, upon + receiving the transfer request, will initiate the data connection + to the port. When the connection is established, the data + transfer begins between DTP's, and the server-PI sends a + confirming reply to the user-PI. + + Every FTP implementation must support the use of the default data + ports, and only the USER-PI can initiate a change to non-default + ports. + + It is possible for the user to specify an alternate data port by + use of the PORT command. The user may want a file dumped on a TAC + line printer or retrieved from a third party host. In the latter + case, the user-PI sets up control connections with both + server-PI's. One server is then told (by an FTP command) to + "listen" for a connection which the other will initiate. The + user-PI sends one server-PI a PORT command indicating the data + port of the other. Finally, both are sent the appropriate + transfer commands. The exact sequence of commands and replies + sent between the user-controller and the servers is defined in the + Section on FTP Replies. + + In general, it is the server's responsibility to maintain the data + connection--to initiate it and to close it. The exception to this + + + + +Postel & Reynolds [Page 18] + + + +RFC 959 October 1985 +File Transfer Protocol + + + is when the user-DTP is sending the data in a transfer mode that + requires the connection to be closed to indicate EOF. The server + MUST close the data connection under the following conditions: + + 1. The server has completed sending data in a transfer mode + that requires a close to indicate EOF. + + 2. The server receives an ABORT command from the user. + + 3. The port specification is changed by a command from the + user. + + 4. The control connection is closed legally or otherwise. + + 5. An irrecoverable error condition occurs. + + Otherwise the close is a server option, the exercise of which the + server must indicate to the user-process by either a 250 or 226 + reply only. + + 3.3. DATA CONNECTION MANAGEMENT + + Default Data Connection Ports: All FTP implementations must + support use of the default data connection ports, and only the + User-PI may initiate the use of non-default ports. + + Negotiating Non-Default Data Ports: The User-PI may specify a + non-default user side data port with the PORT command. The + User-PI may request the server side to identify a non-default + server side data port with the PASV command. Since a connection + is defined by the pair of addresses, either of these actions is + enough to get a different data connection, still it is permitted + to do both commands to use new ports on both ends of the data + connection. + + Reuse of the Data Connection: When using the stream mode of data + transfer the end of the file must be indicated by closing the + connection. This causes a problem if multiple files are to be + transfered in the session, due to need for TCP to hold the + connection record for a time out period to guarantee the reliable + communication. Thus the connection can not be reopened at once. + + There are two solutions to this problem. The first is to + negotiate a non-default port. The second is to use another + transfer mode. + + A comment on transfer modes. The stream transfer mode is + + +Postel & Reynolds [Page 19] + + + +RFC 959 October 1985 +File Transfer Protocol + + + inherently unreliable, since one can not determine if the + connection closed prematurely or not. The other transfer modes + (Block, Compressed) do not close the connection to indicate the + end of file. They have enough FTP encoding that the data + connection can be parsed to determine the end of the file. + Thus using these modes one can leave the data connection open + for multiple file transfers. + + 3.4. TRANSMISSION MODES + + The next consideration in transferring data is choosing the + appropriate transmission mode. There are three modes: one which + formats the data and allows for restart procedures; one which also + compresses the data for efficient transfer; and one which passes + the data with little or no processing. In this last case the mode + interacts with the structure attribute to determine the type of + processing. In the compressed mode, the representation type + determines the filler byte. + + All data transfers must be completed with an end-of-file (EOF) + which may be explicitly stated or implied by the closing of the + data connection. For files with record structure, all the + end-of-record markers (EOR) are explicit, including the final one. + For files transmitted in page structure a "last-page" page type is + used. + + NOTE: In the rest of this section, byte means "transfer byte" + except where explicitly stated otherwise. + + For the purpose of standardized transfer, the sending host will + translate its internal end of line or end of record denotation + into the representation prescribed by the transfer mode and file + structure, and the receiving host will perform the inverse + translation to its internal denotation. An IBM Mainframe record + count field may not be recognized at another host, so the + end-of-record information may be transferred as a two byte control + code in Stream mode or as a flagged bit in a Block or Compressed + mode descriptor. End-of-line in an ASCII or EBCDIC file with no + record structure should be indicated by <CRLF> or <NL>, + respectively. Since these transformations imply extra work for + some systems, identical systems transferring non-record structured + text files might wish to use a binary representation and stream + mode for the transfer. + + + + + + +Postel & Reynolds [Page 20] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The following transmission modes are defined in FTP: + + 3.4.1. STREAM MODE + + The data is transmitted as a stream of bytes. There is no + restriction on the representation type used; record structures + are allowed. + + In a record structured file EOR and EOF will each be indicated + by a two-byte control code. The first byte of the control code + will be all ones, the escape character. The second byte will + have the low order bit on and zeros elsewhere for EOR and the + second low order bit on for EOF; that is, the byte will have + value 1 for EOR and value 2 for EOF. EOR and EOF may be + indicated together on the last byte transmitted by turning both + low order bits on (i.e., the value 3). If a byte of all ones + was intended to be sent as data, it should be repeated in the + second byte of the control code. + + If the structure is a file structure, the EOF is indicated by + the sending host closing the data connection and all bytes are + data bytes. + + 3.4.2. BLOCK MODE + + The file is transmitted as a series of data blocks preceded by + one or more header bytes. The header bytes contain a count + field, and descriptor code. The count field indicates the + total length of the data block in bytes, thus marking the + beginning of the next data block (there are no filler bits). + The descriptor code defines: last block in the file (EOF) last + block in the record (EOR), restart marker (see the Section on + Error Recovery and Restart) or suspect data (i.e., the data + being transferred is suspected of errors and is not reliable). + This last code is NOT intended for error control within FTP. + It is motivated by the desire of sites exchanging certain types + of data (e.g., seismic or weather data) to send and receive all + the data despite local errors (such as "magnetic tape read + errors"), but to indicate in the transmission that certain + portions are suspect). Record structures are allowed in this + mode, and any representation type may be used. + + The header consists of the three bytes. Of the 24 bits of + header information, the 16 low order bits shall represent byte + count, and the 8 high order bits shall represent descriptor + codes as shown below. + + + +Postel & Reynolds [Page 21] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Block Header + + +----------------+----------------+----------------+ + | Descriptor | Byte Count | + | 8 bits | 16 bits | + +----------------+----------------+----------------+ + + + The descriptor codes are indicated by bit flags in the + descriptor byte. Four codes have been assigned, where each + code number is the decimal value of the corresponding bit in + the byte. + + Code Meaning + + 128 End of data block is EOR + 64 End of data block is EOF + 32 Suspected errors in data block + 16 Data block is a restart marker + + With this encoding, more than one descriptor coded condition + may exist for a particular block. As many bits as necessary + may be flagged. + + The restart marker is embedded in the data stream as an + integral number of 8-bit bytes representing printable + characters in the language being used over the control + connection (e.g., default--NVT-ASCII). <SP> (Space, in the + appropriate language) must not be used WITHIN a restart marker. + + For example, to transmit a six-character marker, the following + would be sent: + + +--------+--------+--------+ + |Descrptr| Byte count | + |code= 16| = 6 | + +--------+--------+--------+ + + +--------+--------+--------+ + | Marker | Marker | Marker | + | 8 bits | 8 bits | 8 bits | + +--------+--------+--------+ + + +--------+--------+--------+ + | Marker | Marker | Marker | + | 8 bits | 8 bits | 8 bits | + +--------+--------+--------+ + + +Postel & Reynolds [Page 22] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 3.4.3. COMPRESSED MODE + + There are three kinds of information to be sent: regular data, + sent in a byte string; compressed data, consisting of + replications or filler; and control information, sent in a + two-byte escape sequence. If n>0 bytes (up to 127) of regular + data are sent, these n bytes are preceded by a byte with the + left-most bit set to 0 and the right-most 7 bits containing the + number n. + + Byte string: + + 1 7 8 8 + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + |0| n | | d(1) | ... | d(n) | + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + ^ ^ + |---n bytes---| + of data + + String of n data bytes d(1),..., d(n) + Count n must be positive. + + To compress a string of n replications of the data byte d, the + following 2 bytes are sent: + + Replicated Byte: + + 2 6 8 + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + |1 0| n | | d | + +-+-+-+-+-+-+-+-+ +-+-+-+-+-+-+-+-+ + + A string of n filler bytes can be compressed into a single + byte, where the filler byte varies with the representation + type. If the type is ASCII or EBCDIC the filler byte is <SP> + (Space, ASCII code 32, EBCDIC code 64). If the type is Image + or Local byte the filler is a zero byte. + + Filler String: + + 2 6 + +-+-+-+-+-+-+-+-+ + |1 1| n | + +-+-+-+-+-+-+-+-+ + + The escape sequence is a double byte, the first of which is the + + +Postel & Reynolds [Page 23] + + + +RFC 959 October 1985 +File Transfer Protocol + + + escape byte (all zeros) and the second of which contains + descriptor codes as defined in Block mode. The descriptor + codes have the same meaning as in Block mode and apply to the + succeeding string of bytes. + + Compressed mode is useful for obtaining increased bandwidth on + very large network transmissions at a little extra CPU cost. + It can be most effectively used to reduce the size of printer + files such as those generated by RJE hosts. + + 3.5. ERROR RECOVERY AND RESTART + + There is no provision for detecting bits lost or scrambled in data + transfer; this level of error control is handled by the TCP. + However, a restart procedure is provided to protect users from + gross system failures (including failures of a host, an + FTP-process, or the underlying network). + + The restart procedure is defined only for the block and compressed + modes of data transfer. It requires the sender of data to insert + a special marker code in the data stream with some marker + information. The marker information has meaning only to the + sender, but must consist of printable characters in the default or + negotiated language of the control connection (ASCII or EBCDIC). + The marker could represent a bit-count, a record-count, or any + other information by which a system may identify a data + checkpoint. The receiver of data, if it implements the restart + procedure, would then mark the corresponding position of this + marker in the receiving system, and return this information to the + user. + + In the event of a system failure, the user can restart the data + transfer by identifying the marker point with the FTP restart + procedure. The following example illustrates the use of the + restart procedure. + + The sender of the data inserts an appropriate marker block in the + data stream at a convenient point. The receiving host marks the + corresponding data point in its file system and conveys the last + known sender and receiver marker information to the user, either + directly or over the control connection in a 110 reply (depending + on who is the sender). In the event of a system failure, the user + or controller process restarts the server at the last server + marker by sending a restart command with server's marker code as + its argument. The restart command is transmitted over the control + + + + +Postel & Reynolds [Page 24] + + + +RFC 959 October 1985 +File Transfer Protocol + + + connection and is immediately followed by the command (such as + RETR, STOR or LIST) which was being executed when the system + failure occurred. + +4. FILE TRANSFER FUNCTIONS + + The communication channel from the user-PI to the server-PI is + established as a TCP connection from the user to the standard server + port. The user protocol interpreter is responsible for sending FTP + commands and interpreting the replies received; the server-PI + interprets commands, sends replies and directs its DTP to set up the + data connection and transfer the data. If the second party to the + data transfer (the passive transfer process) is the user-DTP, then it + is governed through the internal protocol of the user-FTP host; if it + is a second server-DTP, then it is governed by its PI on command from + the user-PI. The FTP replies are discussed in the next section. In + the description of a few of the commands in this section, it is + helpful to be explicit about the possible replies. + + 4.1. FTP COMMANDS + + 4.1.1. ACCESS CONTROL COMMANDS + + The following commands specify access control identifiers + (command codes are shown in parentheses). + + USER NAME (USER) + + The argument field is a Telnet string identifying the user. + The user identification is that which is required by the + server for access to its file system. This command will + normally be the first command transmitted by the user after + the control connections are made (some servers may require + this). Additional identification information in the form of + a password and/or an account command may also be required by + some servers. Servers may allow a new USER command to be + entered at any point in order to change the access control + and/or accounting information. This has the effect of + flushing any user, password, and account information already + supplied and beginning the login sequence again. All + transfer parameters are unchanged and any file transfer in + progress is completed under the old access control + parameters. + + + + + + +Postel & Reynolds [Page 25] + + + +RFC 959 October 1985 +File Transfer Protocol + + + PASSWORD (PASS) + + The argument field is a Telnet string specifying the user's + password. This command must be immediately preceded by the + user name command, and, for some sites, completes the user's + identification for access control. Since password + information is quite sensitive, it is desirable in general + to "mask" it or suppress typeout. It appears that the + server has no foolproof way to achieve this. It is + therefore the responsibility of the user-FTP process to hide + the sensitive password information. + + ACCOUNT (ACCT) + + The argument field is a Telnet string identifying the user's + account. The command is not necessarily related to the USER + command, as some sites may require an account for login and + others only for specific access, such as storing files. In + the latter case the command may arrive at any time. + + There are reply codes to differentiate these cases for the + automation: when account information is required for login, + the response to a successful PASSword command is reply code + 332. On the other hand, if account information is NOT + required for login, the reply to a successful PASSword + command is 230; and if the account information is needed for + a command issued later in the dialogue, the server should + return a 332 or 532 reply depending on whether it stores + (pending receipt of the ACCounT command) or discards the + command, respectively. + + CHANGE WORKING DIRECTORY (CWD) + + This command allows the user to work with a different + directory or dataset for file storage or retrieval without + altering his login or accounting information. Transfer + parameters are similarly unchanged. The argument is a + pathname specifying a directory or other system dependent + file group designator. + + CHANGE TO PARENT DIRECTORY (CDUP) + + This command is a special case of CWD, and is included to + simplify the implementation of programs for transferring + directory trees between operating systems having different + + + + +Postel & Reynolds [Page 26] + + + +RFC 959 October 1985 +File Transfer Protocol + + + syntaxes for naming the parent directory. The reply codes + shall be identical to the reply codes of CWD. See + Appendix II for further details. + + STRUCTURE MOUNT (SMNT) + + This command allows the user to mount a different file + system data structure without altering his login or + accounting information. Transfer parameters are similarly + unchanged. The argument is a pathname specifying a + directory or other system dependent file group designator. + + REINITIALIZE (REIN) + + This command terminates a USER, flushing all I/O and account + information, except to allow any transfer in progress to be + completed. All parameters are reset to the default settings + and the control connection is left open. This is identical + to the state in which a user finds himself immediately after + the control connection is opened. A USER command may be + expected to follow. + + LOGOUT (QUIT) + + This command terminates a USER and if file transfer is not + in progress, the server closes the control connection. If + file transfer is in progress, the connection will remain + open for result response and the server will then close it. + If the user-process is transferring files for several USERs + but does not wish to close and then reopen connections for + each, then the REIN command should be used instead of QUIT. + + An unexpected close on the control connection will cause the + server to take the effective action of an abort (ABOR) and a + logout (QUIT). + + 4.1.2. TRANSFER PARAMETER COMMANDS + + All data transfer parameters have default values, and the + commands specifying data transfer parameters are required only + if the default parameter values are to be changed. The default + value is the last specified value, or if no value has been + specified, the standard default value is as stated here. This + implies that the server must "remember" the applicable default + values. The commands may be in any order except that they must + precede the FTP service request. The following commands + specify data transfer parameters: + + +Postel & Reynolds [Page 27] + + + +RFC 959 October 1985 +File Transfer Protocol + + + DATA PORT (PORT) + + The argument is a HOST-PORT specification for the data port + to be used in data connection. There are defaults for both + the user and server data ports, and under normal + circumstances this command and its reply are not needed. If + this command is used, the argument is the concatenation of a + 32-bit internet host address and a 16-bit TCP port address. + This address information is broken into 8-bit fields and the + value of each field is transmitted as a decimal number (in + character string representation). The fields are separated + by commas. A port command would be: + + PORT h1,h2,h3,h4,p1,p2 + + where h1 is the high order 8 bits of the internet host + address. + + PASSIVE (PASV) + + This command requests the server-DTP to "listen" on a data + port (which is not its default data port) and to wait for a + connection rather than initiate one upon receipt of a + transfer command. The response to this command includes the + host and port address this server is listening on. + + REPRESENTATION TYPE (TYPE) + + The argument specifies the representation type as described + in the Section on Data Representation and Storage. Several + types take a second parameter. The first parameter is + denoted by a single Telnet character, as is the second + Format parameter for ASCII and EBCDIC; the second parameter + for local byte is a decimal integer to indicate Bytesize. + The parameters are separated by a <SP> (Space, ASCII code + 32). + + The following codes are assigned for type: + + \ / + A - ASCII | | N - Non-print + |-><-| T - Telnet format effectors + E - EBCDIC| | C - Carriage Control (ASA) + / \ + I - Image + + L <byte size> - Local byte Byte size + + +Postel & Reynolds [Page 28] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The default representation type is ASCII Non-print. If the + Format parameter is changed, and later just the first + argument is changed, Format then returns to the Non-print + default. + + FILE STRUCTURE (STRU) + + The argument is a single Telnet character code specifying + file structure described in the Section on Data + Representation and Storage. + + The following codes are assigned for structure: + + F - File (no record structure) + R - Record structure + P - Page structure + + The default structure is File. + + TRANSFER MODE (MODE) + + The argument is a single Telnet character code specifying + the data transfer modes described in the Section on + Transmission Modes. + + The following codes are assigned for transfer modes: + + S - Stream + B - Block + C - Compressed + + The default transfer mode is Stream. + + 4.1.3. FTP SERVICE COMMANDS + + The FTP service commands define the file transfer or the file + system function requested by the user. The argument of an FTP + service command will normally be a pathname. The syntax of + pathnames must conform to server site conventions (with + standard defaults applicable), and the language conventions of + the control connection. The suggested default handling is to + use the last specified device, directory or file name, or the + standard default defined for local users. The commands may be + in any order except that a "rename from" command must be + followed by a "rename to" command and the restart command must + be followed by the interrupted service command (e.g., STOR or + RETR). The data, when transferred in response to FTP service + + +Postel & Reynolds [Page 29] + + + +RFC 959 October 1985 +File Transfer Protocol + + + commands, shall always be sent over the data connection, except + for certain informative replies. The following commands + specify FTP service requests: + + RETRIEVE (RETR) + + This command causes the server-DTP to transfer a copy of the + file, specified in the pathname, to the server- or user-DTP + at the other end of the data connection. The status and + contents of the file at the server site shall be unaffected. + + STORE (STOR) + + This command causes the server-DTP to accept the data + transferred via the data connection and to store the data as + a file at the server site. If the file specified in the + pathname exists at the server site, then its contents shall + be replaced by the data being transferred. A new file is + created at the server site if the file specified in the + pathname does not already exist. + + STORE UNIQUE (STOU) + + This command behaves like STOR except that the resultant + file is to be created in the current directory under a name + unique to that directory. The 250 Transfer Started response + must include the name generated. + + APPEND (with create) (APPE) + + This command causes the server-DTP to accept the data + transferred via the data connection and to store the data in + a file at the server site. If the file specified in the + pathname exists at the server site, then the data shall be + appended to that file; otherwise the file specified in the + pathname shall be created at the server site. + + ALLOCATE (ALLO) + + This command may be required by some servers to reserve + sufficient storage to accommodate the new file to be + transferred. The argument shall be a decimal integer + representing the number of bytes (using the logical byte + size) of storage to be reserved for the file. For files + sent with record or page structure a maximum record or page + size (in logical bytes) might also be necessary; this is + indicated by a decimal integer in a second argument field of + + +Postel & Reynolds [Page 30] + + + +RFC 959 October 1985 +File Transfer Protocol + + + the command. This second argument is optional, but when + present should be separated from the first by the three + Telnet characters <SP> R <SP>. This command shall be + followed by a STORe or APPEnd command. The ALLO command + should be treated as a NOOP (no operation) by those servers + which do not require that the maximum size of the file be + declared beforehand, and those servers interested in only + the maximum record or page size should accept a dummy value + in the first argument and ignore it. + + RESTART (REST) + + The argument field represents the server marker at which + file transfer is to be restarted. This command does not + cause file transfer but skips over the file to the specified + data checkpoint. This command shall be immediately followed + by the appropriate FTP service command which shall cause + file transfer to resume. + + RENAME FROM (RNFR) + + This command specifies the old pathname of the file which is + to be renamed. This command must be immediately followed by + a "rename to" command specifying the new file pathname. + + RENAME TO (RNTO) + + This command specifies the new pathname of the file + specified in the immediately preceding "rename from" + command. Together the two commands cause a file to be + renamed. + + ABORT (ABOR) + + This command tells the server to abort the previous FTP + service command and any associated transfer of data. The + abort command may require "special action", as discussed in + the Section on FTP Commands, to force recognition by the + server. No action is to be taken if the previous command + has been completed (including data transfer). The control + connection is not to be closed by the server, but the data + connection must be closed. + + There are two cases for the server upon receipt of this + command: (1) the FTP service command was already completed, + or (2) the FTP service command is still in progress. + + + +Postel & Reynolds [Page 31] + + + +RFC 959 October 1985 +File Transfer Protocol + + + In the first case, the server closes the data connection + (if it is open) and responds with a 226 reply, indicating + that the abort command was successfully processed. + + In the second case, the server aborts the FTP service in + progress and closes the data connection, returning a 426 + reply to indicate that the service request terminated + abnormally. The server then sends a 226 reply, + indicating that the abort command was successfully + processed. + + DELETE (DELE) + + This command causes the file specified in the pathname to be + deleted at the server site. If an extra level of protection + is desired (such as the query, "Do you really wish to + delete?"), it should be provided by the user-FTP process. + + REMOVE DIRECTORY (RMD) + + This command causes the directory specified in the pathname + to be removed as a directory (if the pathname is absolute) + or as a subdirectory of the current working directory (if + the pathname is relative). See Appendix II. + + MAKE DIRECTORY (MKD) + + This command causes the directory specified in the pathname + to be created as a directory (if the pathname is absolute) + or as a subdirectory of the current working directory (if + the pathname is relative). See Appendix II. + + PRINT WORKING DIRECTORY (PWD) + + This command causes the name of the current working + directory to be returned in the reply. See Appendix II. + + LIST (LIST) + + This command causes a list to be sent from the server to the + passive DTP. If the pathname specifies a directory or other + group of files, the server should transfer a list of files + in the specified directory. If the pathname specifies a + file then the server should send current information on the + file. A null argument implies the user's current working or + default directory. The data transfer is over the data + connection in type ASCII or type EBCDIC. (The user must + + +Postel & Reynolds [Page 32] + + + +RFC 959 October 1985 +File Transfer Protocol + + + ensure that the TYPE is appropriately ASCII or EBCDIC). + Since the information on a file may vary widely from system + to system, this information may be hard to use automatically + in a program, but may be quite useful to a human user. + + NAME LIST (NLST) + + This command causes a directory listing to be sent from + server to user site. The pathname should specify a + directory or other system-specific file group descriptor; a + null argument implies the current directory. The server + will return a stream of names of files and no other + information. The data will be transferred in ASCII or + EBCDIC type over the data connection as valid pathname + strings separated by <CRLF> or <NL>. (Again the user must + ensure that the TYPE is correct.) This command is intended + to return information that can be used by a program to + further process the files automatically. For example, in + the implementation of a "multiple get" function. + + SITE PARAMETERS (SITE) + + This command is used by the server to provide services + specific to his system that are essential to file transfer + but not sufficiently universal to be included as commands in + the protocol. The nature of these services and the + specification of their syntax can be stated in a reply to + the HELP SITE command. + + SYSTEM (SYST) + + This command is used to find out the type of operating + system at the server. The reply shall have as its first + word one of the system names listed in the current version + of the Assigned Numbers document [4]. + + STATUS (STAT) + + This command shall cause a status response to be sent over + the control connection in the form of a reply. The command + may be sent during a file transfer (along with the Telnet IP + and Synch signals--see the Section on FTP Commands) in which + case the server will respond with the status of the + operation in progress, or it may be sent between file + transfers. In the latter case, the command may have an + argument field. If the argument is a pathname, the command + is analogous to the "list" command except that data shall be + + +Postel & Reynolds [Page 33] + + + +RFC 959 October 1985 +File Transfer Protocol + + + transferred over the control connection. If a partial + pathname is given, the server may respond with a list of + file names or attributes associated with that specification. + If no argument is given, the server should return general + status information about the server FTP process. This + should include current values of all transfer parameters and + the status of connections. + + HELP (HELP) + + This command shall cause the server to send helpful + information regarding its implementation status over the + control connection to the user. The command may take an + argument (e.g., any command name) and return more specific + information as a response. The reply is type 211 or 214. + It is suggested that HELP be allowed before entering a USER + command. The server may use this reply to specify + site-dependent parameters, e.g., in response to HELP SITE. + + NOOP (NOOP) + + This command does not affect any parameters or previously + entered commands. It specifies no action other than that the + server send an OK reply. + + The File Transfer Protocol follows the specifications of the Telnet + protocol for all communications over the control connection. Since + the language used for Telnet communication may be a negotiated + option, all references in the next two sections will be to the + "Telnet language" and the corresponding "Telnet end-of-line code". + Currently, one may take these to mean NVT-ASCII and <CRLF>. No other + specifications of the Telnet protocol will be cited. + + FTP commands are "Telnet strings" terminated by the "Telnet end of + line code". The command codes themselves are alphabetic characters + terminated by the character <SP> (Space) if parameters follow and + Telnet-EOL otherwise. The command codes and the semantics of + commands are described in this section; the detailed syntax of + commands is specified in the Section on Commands, the reply sequences + are discussed in the Section on Sequencing of Commands and Replies, + and scenarios illustrating the use of commands are provided in the + Section on Typical FTP Scenarios. + + FTP commands may be partitioned as those specifying access-control + identifiers, data transfer parameters, or FTP service requests. + Certain commands (such as ABOR, STAT, QUIT) may be sent over the + control connection while a data transfer is in progress. Some + + +Postel & Reynolds [Page 34] + + + +RFC 959 October 1985 +File Transfer Protocol + + + servers may not be able to monitor the control and data connections + simultaneously, in which case some special action will be necessary + to get the server's attention. The following ordered format is + tentatively recommended: + + 1. User system inserts the Telnet "Interrupt Process" (IP) signal + in the Telnet stream. + + 2. User system sends the Telnet "Synch" signal. + + 3. User system inserts the command (e.g., ABOR) in the Telnet + stream. + + 4. Server PI, after receiving "IP", scans the Telnet stream for + EXACTLY ONE FTP command. + + (For other servers this may not be necessary but the actions listed + above should have no unusual effect.) + + 4.2. FTP REPLIES + + Replies to File Transfer Protocol commands are devised to ensure + the synchronization of requests and actions in the process of file + transfer, and to guarantee that the user process always knows the + state of the Server. Every command must generate at least one + reply, although there may be more than one; in the latter case, + the multiple replies must be easily distinguished. In addition, + some commands occur in sequential groups, such as USER, PASS and + ACCT, or RNFR and RNTO. The replies show the existence of an + intermediate state if all preceding commands have been successful. + A failure at any point in the sequence necessitates the repetition + of the entire sequence from the beginning. + + The details of the command-reply sequence are made explicit in + a set of state diagrams below. + + An FTP reply consists of a three digit number (transmitted as + three alphanumeric characters) followed by some text. The number + is intended for use by automata to determine what state to enter + next; the text is intended for the human user. It is intended + that the three digits contain enough encoded information that the + user-process (the User-PI) will not need to examine the text and + may either discard it or pass it on to the user, as appropriate. + In particular, the text may be server-dependent, so there are + likely to be varying texts for each reply code. + + A reply is defined to contain the 3-digit code, followed by Space + + +Postel & Reynolds [Page 35] + + + +RFC 959 October 1985 +File Transfer Protocol + + + <SP>, followed by one line of text (where some maximum line length + has been specified), and terminated by the Telnet end-of-line + code. There will be cases however, where the text is longer than + a single line. In these cases the complete text must be bracketed + so the User-process knows when it may stop reading the reply (i.e. + stop processing input on the control connection) and go do other + things. This requires a special format on the first line to + indicate that more than one line is coming, and another on the + last line to designate it as the last. At least one of these must + contain the appropriate reply code to indicate the state of the + transaction. To satisfy all factions, it was decided that both + the first and last line codes should be the same. + + Thus the format for multi-line replies is that the first line + will begin with the exact required reply code, followed + immediately by a Hyphen, "-" (also known as Minus), followed by + text. The last line will begin with the same code, followed + immediately by Space <SP>, optionally some text, and the Telnet + end-of-line code. + + For example: + 123-First line + Second line + 234 A line beginning with numbers + 123 The last line + + The user-process then simply needs to search for the second + occurrence of the same reply code, followed by <SP> (Space), at + the beginning of a line, and ignore all intermediary lines. If + an intermediary line begins with a 3-digit number, the Server + must pad the front to avoid confusion. + + This scheme allows standard system routines to be used for + reply information (such as for the STAT reply), with + "artificial" first and last lines tacked on. In rare cases + where these routines are able to generate three digits and a + Space at the beginning of any line, the beginning of each + text line should be offset by some neutral text, like Space. + + This scheme assumes that multi-line replies may not be nested. + + The three digits of the reply each have a special significance. + This is intended to allow a range of very simple to very + sophisticated responses by the user-process. The first digit + denotes whether the response is good, bad or incomplete. + (Referring to the state diagram), an unsophisticated user-process + will be able to determine its next action (proceed as planned, + + +Postel & Reynolds [Page 36] + + + +RFC 959 October 1985 +File Transfer Protocol + + + redo, retrench, etc.) by simply examining this first digit. A + user-process that wants to know approximately what kind of error + occurred (e.g. file system error, command syntax error) may + examine the second digit, reserving the third digit for the finest + gradation of information (e.g., RNTO command without a preceding + RNFR). + + There are five values for the first digit of the reply code: + + 1yz Positive Preliminary reply + + The requested action is being initiated; expect another + reply before proceeding with a new command. (The + user-process sending another command before the + completion reply would be in violation of protocol; but + server-FTP processes should queue any commands that + arrive while a preceding command is in progress.) This + type of reply can be used to indicate that the command + was accepted and the user-process may now pay attention + to the data connections, for implementations where + simultaneous monitoring is difficult. The server-FTP + process may send at most, one 1yz reply per command. + + 2yz Positive Completion reply + + The requested action has been successfully completed. A + new request may be initiated. + + 3yz Positive Intermediate reply + + The command has been accepted, but the requested action + is being held in abeyance, pending receipt of further + information. The user should send another command + specifying this information. This reply is used in + command sequence groups. + + 4yz Transient Negative Completion reply + + The command was not accepted and the requested action did + not take place, but the error condition is temporary and + the action may be requested again. The user should + return to the beginning of the command sequence, if any. + It is difficult to assign a meaning to "transient", + particularly when two distinct sites (Server- and + User-processes) have to agree on the interpretation. + Each reply in the 4yz category might have a slightly + different time value, but the intent is that the + + +Postel & Reynolds [Page 37] + + + +RFC 959 October 1985 +File Transfer Protocol + + + user-process is encouraged to try again. A rule of thumb + in determining if a reply fits into the 4yz or the 5yz + (Permanent Negative) category is that replies are 4yz if + the commands can be repeated without any change in + command form or in properties of the User or Server + (e.g., the command is spelled the same with the same + arguments used; the user does not change his file access + or user name; the server does not put up a new + implementation.) + + 5yz Permanent Negative Completion reply + + The command was not accepted and the requested action did + not take place. The User-process is discouraged from + repeating the exact request (in the same sequence). Even + some "permanent" error conditions can be corrected, so + the human user may want to direct his User-process to + reinitiate the command sequence by direct action at some + point in the future (e.g., after the spelling has been + changed, or the user has altered his directory status.) + + The following function groupings are encoded in the second + digit: + + x0z Syntax - These replies refer to syntax errors, + syntactically correct commands that don't fit any + functional category, unimplemented or superfluous + commands. + + x1z Information - These are replies to requests for + information, such as status or help. + + x2z Connections - Replies referring to the control and + data connections. + + x3z Authentication and accounting - Replies for the login + process and accounting procedures. + + x4z Unspecified as yet. + + x5z File system - These replies indicate the status of the + Server file system vis-a-vis the requested transfer or + other file system action. + + The third digit gives a finer gradation of meaning in each of + the function categories, specified by the second digit. The + list of replies below will illustrate this. Note that the text + + +Postel & Reynolds [Page 38] + + + +RFC 959 October 1985 +File Transfer Protocol + + + associated with each reply is recommended, rather than + mandatory, and may even change according to the command with + which it is associated. The reply codes, on the other hand, + must strictly follow the specifications in the last section; + that is, Server implementations should not invent new codes for + situations that are only slightly different from the ones + described here, but rather should adapt codes already defined. + + A command such as TYPE or ALLO whose successful execution + does not offer the user-process any new information will + cause a 200 reply to be returned. If the command is not + implemented by a particular Server-FTP process because it + has no relevance to that computer system, for example ALLO + at a TOPS20 site, a Positive Completion reply is still + desired so that the simple User-process knows it can proceed + with its course of action. A 202 reply is used in this case + with, for example, the reply text: "No storage allocation + necessary." If, on the other hand, the command requests a + non-site-specific action and is unimplemented, the response + is 502. A refinement of that is the 504 reply for a command + that is implemented, but that requests an unimplemented + parameter. + + 4.2.1 Reply Codes by Function Groups + + 200 Command okay. + 500 Syntax error, command unrecognized. + This may include errors such as command line too long. + 501 Syntax error in parameters or arguments. + 202 Command not implemented, superfluous at this site. + 502 Command not implemented. + 503 Bad sequence of commands. + 504 Command not implemented for that parameter. + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 39] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 110 Restart marker reply. + In this case, the text is exact and not left to the + particular implementation; it must read: + MARK yyyy = mmmm + Where yyyy is User-process data stream marker, and mmmm + server's equivalent marker (note the spaces between markers + and "="). + 211 System status, or system help reply. + 212 Directory status. + 213 File status. + 214 Help message. + On how to use the server or the meaning of a particular + non-standard command. This reply is useful only to the + human user. + 215 NAME system type. + Where NAME is an official system name from the list in the + Assigned Numbers document. + + 120 Service ready in nnn minutes. + 220 Service ready for new user. + 221 Service closing control connection. + Logged out if appropriate. + 421 Service not available, closing control connection. + This may be a reply to any command if the service knows it + must shut down. + 125 Data connection already open; transfer starting. + 225 Data connection open; no transfer in progress. + 425 Can't open data connection. + 226 Closing data connection. + Requested file action successful (for example, file + transfer or file abort). + 426 Connection closed; transfer aborted. + 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + + 230 User logged in, proceed. + 530 Not logged in. + 331 User name okay, need password. + 332 Need account for login. + 532 Need account for storing files. + + + + + + + + + + +Postel & Reynolds [Page 40] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 150 File status okay; about to open data connection. + 250 Requested file action okay, completed. + 257 "PATHNAME" created. + 350 Requested file action pending further information. + 450 Requested file action not taken. + File unavailable (e.g., file busy). + 550 Requested action not taken. + File unavailable (e.g., file not found, no access). + 451 Requested action aborted. Local error in processing. + 551 Requested action aborted. Page type unknown. + 452 Requested action not taken. + Insufficient storage space in system. + 552 Requested file action aborted. + Exceeded storage allocation (for current directory or + dataset). + 553 Requested action not taken. + File name not allowed. + + + 4.2.2 Numeric Order List of Reply Codes + + 110 Restart marker reply. + In this case, the text is exact and not left to the + particular implementation; it must read: + MARK yyyy = mmmm + Where yyyy is User-process data stream marker, and mmmm + server's equivalent marker (note the spaces between markers + and "="). + 120 Service ready in nnn minutes. + 125 Data connection already open; transfer starting. + 150 File status okay; about to open data connection. + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 41] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 200 Command okay. + 202 Command not implemented, superfluous at this site. + 211 System status, or system help reply. + 212 Directory status. + 213 File status. + 214 Help message. + On how to use the server or the meaning of a particular + non-standard command. This reply is useful only to the + human user. + 215 NAME system type. + Where NAME is an official system name from the list in the + Assigned Numbers document. + 220 Service ready for new user. + 221 Service closing control connection. + Logged out if appropriate. + 225 Data connection open; no transfer in progress. + 226 Closing data connection. + Requested file action successful (for example, file + transfer or file abort). + 227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). + 230 User logged in, proceed. + 250 Requested file action okay, completed. + 257 "PATHNAME" created. + + 331 User name okay, need password. + 332 Need account for login. + 350 Requested file action pending further information. + + 421 Service not available, closing control connection. + This may be a reply to any command if the service knows it + must shut down. + 425 Can't open data connection. + 426 Connection closed; transfer aborted. + 450 Requested file action not taken. + File unavailable (e.g., file busy). + 451 Requested action aborted: local error in processing. + 452 Requested action not taken. + Insufficient storage space in system. + + + + + + + + + + + +Postel & Reynolds [Page 42] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 500 Syntax error, command unrecognized. + This may include errors such as command line too long. + 501 Syntax error in parameters or arguments. + 502 Command not implemented. + 503 Bad sequence of commands. + 504 Command not implemented for that parameter. + 530 Not logged in. + 532 Need account for storing files. + 550 Requested action not taken. + File unavailable (e.g., file not found, no access). + 551 Requested action aborted: page type unknown. + 552 Requested file action aborted. + Exceeded storage allocation (for current directory or + dataset). + 553 Requested action not taken. + File name not allowed. + + +5. DECLARATIVE SPECIFICATIONS + + 5.1. MINIMUM IMPLEMENTATION + + In order to make FTP workable without needless error messages, the + following minimum implementation is required for all servers: + + TYPE - ASCII Non-print + MODE - Stream + STRUCTURE - File, Record + COMMANDS - USER, QUIT, PORT, + TYPE, MODE, STRU, + for the default values + RETR, STOR, + NOOP. + + The default values for transfer parameters are: + + TYPE - ASCII Non-print + MODE - Stream + STRU - File + + All hosts must accept the above as the standard defaults. + + + + + + + + +Postel & Reynolds [Page 43] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.2. CONNECTIONS + + The server protocol interpreter shall "listen" on Port L. The + user or user protocol interpreter shall initiate the full-duplex + control connection. Server- and user- processes should follow the + conventions of the Telnet protocol as specified in the + ARPA-Internet Protocol Handbook [1]. Servers are under no + obligation to provide for editing of command lines and may require + that it be done in the user host. The control connection shall be + closed by the server at the user's request after all transfers and + replies are completed. + + The user-DTP must "listen" on the specified data port; this may be + the default user port (U) or a port specified in the PORT command. + The server shall initiate the data connection from his own default + data port (L-1) using the specified user data port. The direction + of the transfer and the port used will be determined by the FTP + service command. + + Note that all FTP implementation must support data transfer using + the default port, and that only the USER-PI may initiate the use + of non-default ports. + + When data is to be transferred between two servers, A and B (refer + to Figure 2), the user-PI, C, sets up control connections with + both server-PI's. One of the servers, say A, is then sent a PASV + command telling him to "listen" on his data port rather than + initiate a connection when he receives a transfer service command. + When the user-PI receives an acknowledgment to the PASV command, + which includes the identity of the host and port being listened + on, the user-PI then sends A's port, a, to B in a PORT command; a + reply is returned. The user-PI may then send the corresponding + service commands to A and B. Server B initiates the connection + and the transfer proceeds. The command-reply sequence is listed + below where the messages are vertically synchronous but + horizontally asynchronous: + + + + + + + + + + + + + +Postel & Reynolds [Page 44] + + + +RFC 959 October 1985 +File Transfer Protocol + + + User-PI - Server A User-PI - Server B + ------------------ ------------------ + + C->A : Connect C->B : Connect + C->A : PASV + A->C : 227 Entering Passive Mode. A1,A2,A3,A4,a1,a2 + C->B : PORT A1,A2,A3,A4,a1,a2 + B->C : 200 Okay + C->A : STOR C->B : RETR + B->A : Connect to HOST-A, PORT-a + + Figure 3 + + The data connection shall be closed by the server under the + conditions described in the Section on Establishing Data + Connections. If the data connection is to be closed following a + data transfer where closing the connection is not required to + indicate the end-of-file, the server must do so immediately. + Waiting until after a new transfer command is not permitted + because the user-process will have already tested the data + connection to see if it needs to do a "listen"; (remember that the + user must "listen" on a closed data port BEFORE sending the + transfer request). To prevent a race condition here, the server + sends a reply (226) after closing the data connection (or if the + connection is left open, a "file transfer completed" reply (250) + and the user-PI should wait for one of these replies before + issuing a new transfer command). + + Any time either the user or server see that the connection is + being closed by the other side, it should promptly read any + remaining data queued on the connection and issue the close on its + own side. + + 5.3. COMMANDS + + The commands are Telnet character strings transmitted over the + control connections as described in the Section on FTP Commands. + The command functions and semantics are described in the Section + on Access Control Commands, Transfer Parameter Commands, FTP + Service Commands, and Miscellaneous Commands. The command syntax + is specified here. + + The commands begin with a command code followed by an argument + field. The command codes are four or fewer alphabetic characters. + Upper and lower case alphabetic characters are to be treated + identically. Thus, any of the following may represent the + retrieve command: + + +Postel & Reynolds [Page 45] + + + +RFC 959 October 1985 +File Transfer Protocol + + + RETR Retr retr ReTr rETr + + This also applies to any symbols representing parameter values, + such as A or a for ASCII TYPE. The command codes and the argument + fields are separated by one or more spaces. + + The argument field consists of a variable length character string + ending with the character sequence <CRLF> (Carriage Return, Line + Feed) for NVT-ASCII representation; for other negotiated languages + a different end of line character might be used. It should be + noted that the server is to take no action until the end of line + code is received. + + The syntax is specified below in NVT-ASCII. All characters in the + argument field are ASCII characters including any ASCII + represented decimal integers. Square brackets denote an optional + argument field. If the option is not taken, the appropriate + default is implied. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 46] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.3.1. FTP COMMANDS + + The following are the FTP commands: + + USER <SP> <username> <CRLF> + PASS <SP> <password> <CRLF> + ACCT <SP> <account-information> <CRLF> + CWD <SP> <pathname> <CRLF> + CDUP <CRLF> + SMNT <SP> <pathname> <CRLF> + QUIT <CRLF> + REIN <CRLF> + PORT <SP> <host-port> <CRLF> + PASV <CRLF> + TYPE <SP> <type-code> <CRLF> + STRU <SP> <structure-code> <CRLF> + MODE <SP> <mode-code> <CRLF> + RETR <SP> <pathname> <CRLF> + STOR <SP> <pathname> <CRLF> + STOU <CRLF> + APPE <SP> <pathname> <CRLF> + ALLO <SP> <decimal-integer> + [<SP> R <SP> <decimal-integer>] <CRLF> + REST <SP> <marker> <CRLF> + RNFR <SP> <pathname> <CRLF> + RNTO <SP> <pathname> <CRLF> + ABOR <CRLF> + DELE <SP> <pathname> <CRLF> + RMD <SP> <pathname> <CRLF> + MKD <SP> <pathname> <CRLF> + PWD <CRLF> + LIST [<SP> <pathname>] <CRLF> + NLST [<SP> <pathname>] <CRLF> + SITE <SP> <string> <CRLF> + SYST <CRLF> + STAT [<SP> <pathname>] <CRLF> + HELP [<SP> <string>] <CRLF> + NOOP <CRLF> + + + + + + + + + + + +Postel & Reynolds [Page 47] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.3.2. FTP COMMAND ARGUMENTS + + The syntax of the above argument fields (using BNF notation + where applicable) is: + + <username> ::= <string> + <password> ::= <string> + <account-information> ::= <string> + <string> ::= <char> | <char><string> + <char> ::= any of the 128 ASCII characters except <CR> and + <LF> + <marker> ::= <pr-string> + <pr-string> ::= <pr-char> | <pr-char><pr-string> + <pr-char> ::= printable characters, any + ASCII code 33 through 126 + <byte-size> ::= <number> + <host-port> ::= <host-number>,<port-number> + <host-number> ::= <number>,<number>,<number>,<number> + <port-number> ::= <number>,<number> + <number> ::= any decimal integer 1 through 255 + <form-code> ::= N | T | C + <type-code> ::= A [<sp> <form-code>] + | E [<sp> <form-code>] + | I + | L <sp> <byte-size> + <structure-code> ::= F | R | P + <mode-code> ::= S | B | C + <pathname> ::= <string> + <decimal-integer> ::= any decimal integer + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 48] + + + +RFC 959 October 1985 +File Transfer Protocol + + + 5.4. SEQUENCING OF COMMANDS AND REPLIES + + The communication between the user and server is intended to be an + alternating dialogue. As such, the user issues an FTP command and + the server responds with a prompt primary reply. The user should + wait for this initial primary success or failure response before + sending further commands. + + Certain commands require a second reply for which the user should + also wait. These replies may, for example, report on the progress + or completion of file transfer or the closing of the data + connection. They are secondary replies to file transfer commands. + + One important group of informational replies is the connection + greetings. Under normal circumstances, a server will send a 220 + reply, "awaiting input", when the connection is completed. The + user should wait for this greeting message before sending any + commands. If the server is unable to accept input right away, a + 120 "expected delay" reply should be sent immediately and a 220 + reply when ready. The user will then know not to hang up if there + is a delay. + + Spontaneous Replies + + Sometimes "the system" spontaneously has a message to be sent + to a user (usually all users). For example, "System going down + in 15 minutes". There is no provision in FTP for such + spontaneous information to be sent from the server to the user. + It is recommended that such information be queued in the + server-PI and delivered to the user-PI in the next reply + (possibly making it a multi-line reply). + + The table below lists alternative success and failure replies for + each command. These must be strictly adhered to; a server may + substitute text in the replies, but the meaning and action implied + by the code numbers and by the specific command reply sequence + cannot be altered. + + Command-Reply Sequences + + In this section, the command-reply sequence is presented. Each + command is listed with its possible replies; command groups are + listed together. Preliminary replies are listed first (with + their succeeding replies indented and under them), then + positive and negative completion, and finally intermediary + + + + +Postel & Reynolds [Page 49] + + + +RFC 959 October 1985 +File Transfer Protocol + + + replies with the remaining commands from the sequence + following. This listing forms the basis for the state + diagrams, which will be presented separately. + + Connection Establishment + 120 + 220 + 220 + 421 + Login + USER + 230 + 530 + 500, 501, 421 + 331, 332 + PASS + 230 + 202 + 530 + 500, 501, 503, 421 + 332 + ACCT + 230 + 202 + 530 + 500, 501, 503, 421 + CWD + 250 + 500, 501, 502, 421, 530, 550 + CDUP + 200 + 500, 501, 502, 421, 530, 550 + SMNT + 202, 250 + 500, 501, 502, 421, 530, 550 + Logout + REIN + 120 + 220 + 220 + 421 + 500, 502 + QUIT + 221 + 500 + + + + +Postel & Reynolds [Page 50] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Transfer parameters + PORT + 200 + 500, 501, 421, 530 + PASV + 227 + 500, 501, 502, 421, 530 + MODE + 200 + 500, 501, 504, 421, 530 + TYPE + 200 + 500, 501, 504, 421, 530 + STRU + 200 + 500, 501, 504, 421, 530 + File action commands + ALLO + 200 + 202 + 500, 501, 504, 421, 530 + REST + 500, 501, 502, 421, 530 + 350 + STOR + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 452, 553 + 500, 501, 421, 530 + STOU + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 452, 553 + 500, 501, 421, 530 + RETR + 125, 150 + (110) + 226, 250 + 425, 426, 451 + 450, 550 + 500, 501, 421, 530 + + + + +Postel & Reynolds [Page 51] + + + +RFC 959 October 1985 +File Transfer Protocol + + + LIST + 125, 150 + 226, 250 + 425, 426, 451 + 450 + 500, 501, 502, 421, 530 + NLST + 125, 150 + 226, 250 + 425, 426, 451 + 450 + 500, 501, 502, 421, 530 + APPE + 125, 150 + (110) + 226, 250 + 425, 426, 451, 551, 552 + 532, 450, 550, 452, 553 + 500, 501, 502, 421, 530 + RNFR + 450, 550 + 500, 501, 502, 421, 530 + 350 + RNTO + 250 + 532, 553 + 500, 501, 502, 503, 421, 530 + DELE + 250 + 450, 550 + 500, 501, 502, 421, 530 + RMD + 250 + 500, 501, 502, 421, 530, 550 + MKD + 257 + 500, 501, 502, 421, 530, 550 + PWD + 257 + 500, 501, 502, 421, 550 + ABOR + 225, 226 + 500, 501, 502, 421 + + + + + + +Postel & Reynolds [Page 52] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Informational commands + SYST + 215 + 500, 501, 502, 421 + STAT + 211, 212, 213 + 450 + 500, 501, 502, 421, 530 + HELP + 211, 214 + 500, 501, 502, 421 + Miscellaneous commands + SITE + 200 + 202 + 500, 501, 530 + NOOP + 200 + 500 421 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 53] + + + +RFC 959 October 1985 +File Transfer Protocol + + +6. STATE DIAGRAMS + + Here we present state diagrams for a very simple minded FTP + implementation. Only the first digit of the reply codes is used. + There is one state diagram for each group of FTP commands or command + sequences. + + The command groupings were determined by constructing a model for + each command then collecting together the commands with structurally + identical models. + + For each command or command sequence there are three possible + outcomes: success (S), failure (F), and error (E). In the state + diagrams below we use the symbol B for "begin", and the symbol W for + "wait for reply". + + We first present the diagram that represents the largest group of FTP + commands: + + + 1,3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ +---+ +---+ + | + | 4,5 +---+ + ----------->| F | + +---+ + + + This diagram models the commands: + + ABOR, ALLO, DELE, CWD, CDUP, SMNT, HELP, MODE, NOOP, PASV, + QUIT, SITE, PORT, SYST, STAT, RMD, MKD, PWD, STRU, and TYPE. + + + + + + + + + + + + +Postel & Reynolds [Page 54] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The other large group of commands is represented by a very similar + diagram: + + + 3 +---+ + ----------->| E | + | +---+ + | + +---+ cmd +---+ 2 +---+ + | B |---------->| W |---------->| S | + +---+ --->+---+ +---+ + | | | + | | | 4,5 +---+ + | 1 | ----------->| F | + ----- +---+ + + + This diagram models the commands: + + APPE, LIST, NLST, REIN, RETR, STOR, and STOU. + + Note that this second model could also be used to represent the first + group of commands, the only difference being that in the first group + the 100 series replies are unexpected and therefore treated as error, + while the second group expects (some may require) 100 series replies. + Remember that at most, one 100 series reply is allowed per command. + + The remaining diagrams model command sequences, perhaps the simplest + of these is the rename sequence: + + + +---+ RNFR +---+ 1,2 +---+ + | B |---------->| W |---------->| E | + +---+ +---+ -->+---+ + | | | + 3 | | 4,5 | + -------------- ------ | + | | | +---+ + | ------------->| S | + | | 1,3 | | +---+ + | 2| -------- + | | | | + V | | | + +---+ RNTO +---+ 4,5 ----->+---+ + | |---------->| W |---------->| F | + +---+ +---+ +---+ + + + +Postel & Reynolds [Page 55] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The next diagram is a simple model of the Restart command: + + + +---+ REST +---+ 1,2 +---+ + | B |---------->| W |---------->| E | + +---+ +---+ -->+---+ + | | | + 3 | | 4,5 | + -------------- ------ | + | | | +---+ + | ------------->| S | + | | 3 | | +---+ + | 2| -------- + | | | | + V | | | + +---+ cmd +---+ 4,5 ----->+---+ + | |---------->| W |---------->| F | + +---+ -->+---+ +---+ + | | + | 1 | + ------ + + + Where "cmd" is APPE, STOR, or RETR. + + We note that the above three models are similar. The Restart differs + from the Rename two only in the treatment of 100 series replies at + the second stage, while the second group expects (some may require) + 100 series replies. Remember that at most, one 100 series reply is + allowed per command. + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 56] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The most complicated diagram is for the Login sequence: + + + 1 + +---+ USER +---+------------->+---+ + | B |---------->| W | 2 ---->| E | + +---+ +---+------ | -->+---+ + | | | | | + 3 | | 4,5 | | | + -------------- ----- | | | + | | | | | + | | | | | + | --------- | + | 1| | | | + V | | | | + +---+ PASS +---+ 2 | ------>+---+ + | |---------->| W |------------->| S | + +---+ +---+ ---------->+---+ + | | | | | + 3 | |4,5| | | + -------------- -------- | + | | | | | + | | | | | + | ----------- + | 1,3| | | | + V | 2| | | + +---+ ACCT +---+-- | ----->+---+ + | |---------->| W | 4,5 -------->| F | + +---+ +---+------------->+---+ + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 57] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Finally, we present a generalized diagram that could be used to model + the command and reply interchange: + + + ------------------------------------ + | | + Begin | | + | V | + | +---+ cmd +---+ 2 +---+ | + -->| |------->| |---------->| | | + | | | W | | S |-----| + -->| | -->| |----- | | | + | +---+ | +---+ 4,5 | +---+ | + | | | | | | | + | | | 1| |3 | +---+ | + | | | | | | | | | + | | ---- | ---->| F |----- + | | | | | + | | | +---+ + ------------------- + | + | + V + End + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 58] + + + +RFC 959 October 1985 +File Transfer Protocol + + +7. TYPICAL FTP SCENARIO + + User at host U wanting to transfer files to/from host S: + + In general, the user will communicate to the server via a mediating + user-FTP process. The following may be a typical scenario. The + user-FTP prompts are shown in parentheses, '---->' represents + commands from host U to host S, and '<----' represents replies from + host S to host U. + + LOCAL COMMANDS BY USER ACTION INVOLVED + + ftp (host) multics<CR> Connect to host S, port L, + establishing control connections. + <---- 220 Service ready <CRLF>. + username Doe <CR> USER Doe<CRLF>----> + <---- 331 User name ok, + need password<CRLF>. + password mumble <CR> PASS mumble<CRLF>----> + <---- 230 User logged in<CRLF>. + retrieve (local type) ASCII<CR> + (local pathname) test 1 <CR> User-FTP opens local file in ASCII. + (for. pathname) test.pl1<CR> RETR test.pl1<CRLF> ----> + <---- 150 File status okay; + about to open data + connection<CRLF>. + Server makes data connection + to port U. + + <---- 226 Closing data connection, + file transfer successful<CRLF>. + type Image<CR> TYPE I<CRLF> ----> + <---- 200 Command OK<CRLF> + store (local type) image<CR> + (local pathname) file dump<CR> User-FTP opens local file in Image. + (for.pathname) >udd>cn>fd<CR> STOR >udd>cn>fd<CRLF> ----> + <---- 550 Access denied<CRLF> + terminate QUIT <CRLF> ----> + Server closes all + connections. + +8. CONNECTION ESTABLISHMENT + + The FTP control connection is established via TCP between the user + process port U and the server process port L. This protocol is + assigned the service port 21 (25 octal), that is L=21. + + + +Postel & Reynolds [Page 59] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX I - PAGE STRUCTURE + + The need for FTP to support page structure derives principally from + the need to support efficient transmission of files between TOPS-20 + systems, particularly the files used by NLS. + + The file system of TOPS-20 is based on the concept of pages. The + operating system is most efficient at manipulating files as pages. + The operating system provides an interface to the file system so that + many applications view files as sequential streams of characters. + However, a few applications use the underlying page structures + directly, and some of these create holey files. + + A TOPS-20 disk file consists of four things: a pathname, a page + table, a (possibly empty) set of pages, and a set of attributes. + + The pathname is specified in the RETR or STOR command. It includes + the directory name, file name, file name extension, and generation + number. + + The page table contains up to 2**18 entries. Each entry may be + EMPTY, or may point to a page. If it is not empty, there are also + some page-specific access bits; not all pages of a file need have the + same access protection. + + A page is a contiguous set of 512 words of 36 bits each. + + The attributes of the file, in the File Descriptor Block (FDB), + contain such things as creation time, write time, read time, writer's + byte-size, end-of-file pointer, count of reads and writes, backup + system tape numbers, etc. + + Note that there is NO requirement that entries in the page table be + contiguous. There may be empty page table slots between occupied + ones. Also, the end of file pointer is simply a number. There is no + requirement that it in fact point at the "last" datum in the file. + Ordinary sequential I/O calls in TOPS-20 will cause the end of file + pointer to be left after the last datum written, but other operations + may cause it not to be so, if a particular programming system so + requires. + + In fact, in both of these special cases, "holey" files and + end-of-file pointers NOT at the end of the file, occur with NLS data + files. + + + + + +Postel & Reynolds [Page 60] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The TOPS-20 paged files can be sent with the FTP transfer parameters: + TYPE L 36, STRU P, and MODE S (in fact, any mode could be used). + + Each page of information has a header. Each header field, which is a + logical byte, is a TOPS-20 word, since the TYPE is L 36. + + The header fields are: + + Word 0: Header Length. + + The header length is 5. + + Word 1: Page Index. + + If the data is a disk file page, this is the number of that + page in the file's page map. Empty pages (holes) in the file + are simply not sent. Note that a hole is NOT the same as a + page of zeros. + + Word 2: Data Length. + + The number of data words in this page, following the header. + Thus, the total length of the transmission unit is the Header + Length plus the Data Length. + + Word 3: Page Type. + + A code for what type of chunk this is. A data page is type 3, + the FDB page is type 2. + + Word 4: Page Access Control. + + The access bits associated with the page in the file's page + map. (This full word quantity is put into AC2 of an SPACS by + the program reading from net to disk.) + + After the header are Data Length data words. Data Length is + currently either 512 for a data page or 31 for an FDB. Trailing + zeros in a disk file page may be discarded, making Data Length less + than 512 in that case. + + + + + + + + + +Postel & Reynolds [Page 61] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX II - DIRECTORY COMMANDS + + Since UNIX has a tree-like directory structure in which directories + are as easy to manipulate as ordinary files, it is useful to expand + the FTP servers on these machines to include commands which deal with + the creation of directories. Since there are other hosts on the + ARPA-Internet which have tree-like directories (including TOPS-20 and + Multics), these commands are as general as possible. + + Four directory commands have been added to FTP: + + MKD pathname + + Make a directory with the name "pathname". + + RMD pathname + + Remove the directory with the name "pathname". + + PWD + + Print the current working directory name. + + CDUP + + Change to the parent of the current working directory. + + The "pathname" argument should be created (removed) as a + subdirectory of the current working directory, unless the "pathname" + string contains sufficient information to specify otherwise to the + server, e.g., "pathname" is an absolute pathname (in UNIX and + Multics), or pathname is something like "<abso.lute.path>" to + TOPS-20. + + REPLY CODES + + The CDUP command is a special case of CWD, and is included to + simplify the implementation of programs for transferring directory + trees between operating systems having different syntaxes for + naming the parent directory. The reply codes for CDUP be + identical to the reply codes of CWD. + + The reply codes for RMD be identical to the reply codes for its + file analogue, DELE. + + The reply codes for MKD, however, are a bit more complicated. A + freshly created directory will probably be the object of a future + + +Postel & Reynolds [Page 62] + + + +RFC 959 October 1985 +File Transfer Protocol + + + CWD command. Unfortunately, the argument to MKD may not always be + a suitable argument for CWD. This is the case, for example, when + a TOPS-20 subdirectory is created by giving just the subdirectory + name. That is, with a TOPS-20 server FTP, the command sequence + + MKD MYDIR + CWD MYDIR + + will fail. The new directory may only be referred to by its + "absolute" name; e.g., if the MKD command above were issued while + connected to the directory <DFRANKLIN>, the new subdirectory + could only be referred to by the name <DFRANKLIN.MYDIR>. + + Even on UNIX and Multics, however, the argument given to MKD may + not be suitable. If it is a "relative" pathname (i.e., a pathname + which is interpreted relative to the current directory), the user + would need to be in the same current directory in order to reach + the subdirectory. Depending on the application, this may be + inconvenient. It is not very robust in any case. + + To solve these problems, upon successful completion of an MKD + command, the server should return a line of the form: + + 257<space>"<directory-name>"<space><commentary> + + That is, the server will tell the user what string to use when + referring to the created directory. The directory name can + contain any character; embedded double-quotes should be escaped by + double-quotes (the "quote-doubling" convention). + + For example, a user connects to the directory /usr/dm, and creates + a subdirectory, named pathname: + + CWD /usr/dm + 200 directory changed to /usr/dm + MKD pathname + 257 "/usr/dm/pathname" directory created + + An example with an embedded double quote: + + MKD foo"bar + 257 "/usr/dm/foo""bar" directory created + CWD /usr/dm/foo"bar + 200 directory changed to /usr/dm/foo"bar + + + + + +Postel & Reynolds [Page 63] + + + +RFC 959 October 1985 +File Transfer Protocol + + + The prior existence of a subdirectory with the same name is an + error, and the server must return an "access denied" error reply + in that case. + + CWD /usr/dm + 200 directory changed to /usr/dm + MKD pathname + 521-"/usr/dm/pathname" directory already exists; + 521 taking no action. + + The failure replies for MKD are analogous to its file creating + cousin, STOR. Also, an "access denied" return is given if a file + name with the same name as the subdirectory will conflict with the + creation of the subdirectory (this is a problem on UNIX, but + shouldn't be one on TOPS-20). + + Essentially because the PWD command returns the same type of + information as the successful MKD command, the successful PWD + command uses the 257 reply code as well. + + SUBTLETIES + + Because these commands will be most useful in transferring + subtrees from one machine to another, carefully observe that the + argument to MKD is to be interpreted as a sub-directory of the + current working directory, unless it contains enough information + for the destination host to tell otherwise. A hypothetical + example of its use in the TOPS-20 world: + + CWD <some.where> + 200 Working directory changed + MKD overrainbow + 257 "<some.where.overrainbow>" directory created + CWD overrainbow + 431 No such directory + CWD <some.where.overrainbow> + 200 Working directory changed + + CWD <some.where> + 200 Working directory changed to <some.where> + MKD <unambiguous> + 257 "<unambiguous>" directory created + CWD <unambiguous> + + Note that the first example results in a subdirectory of the + connected directory. In contrast, the argument in the second + example contains enough information for TOPS-20 to tell that the + + +Postel & Reynolds [Page 64] + + + +RFC 959 October 1985 +File Transfer Protocol + + + <unambiguous> directory is a top-level directory. Note also that + in the first example the user "violated" the protocol by + attempting to access the freshly created directory with a name + other than the one returned by TOPS-20. Problems could have + resulted in this case had there been an <overrainbow> directory; + this is an ambiguity inherent in some TOPS-20 implementations. + Similar considerations apply to the RMD command. The point is + this: except where to do so would violate a host's conventions for + denoting relative versus absolute pathnames, the host should treat + the operands of the MKD and RMD commands as subdirectories. The + 257 reply to the MKD command must always contain the absolute + pathname of the created directory. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 65] + + + +RFC 959 October 1985 +File Transfer Protocol + + +APPENDIX III - RFCs on FTP + + Bhushan, Abhay, "A File Transfer Protocol", RFC 114 (NIC 5823), + MIT-Project MAC, 16 April 1971. + + Harslem, Eric, and John Heafner, "Comments on RFC 114 (A File + Transfer Protocol)", RFC 141 (NIC 6726), RAND, 29 April 1971. + + Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 172 + (NIC 6794), MIT-Project MAC, 23 June 1971. + + Braden, Bob, "Comments on DTP and FTP Proposals", RFC 238 (NIC 7663), + UCLA/CCN, 29 September 1971. + + Bhushan, Abhay, et al, "The File Transfer Protocol", RFC 265 + (NIC 7813), MIT-Project MAC, 17 November 1971. + + McKenzie, Alex, "A Suggested Addition to File Transfer Protocol", + RFC 281 (NIC 8163), BBN, 8 December 1971. + + Bhushan, Abhay, "The Use of "Set Data Type" Transaction in File + Transfer Protocol", RFC 294 (NIC 8304), MIT-Project MAC, + 25 January 1972. + + Bhushan, Abhay, "The File Transfer Protocol", RFC 354 (NIC 10596), + MIT-Project MAC, 8 July 1972. + + Bhushan, Abhay, "Comments on the File Transfer Protocol (RFC 354)", + RFC 385 (NIC 11357), MIT-Project MAC, 18 August 1972. + + Hicks, Greg, "User FTP Documentation", RFC 412 (NIC 12404), Utah, + 27 November 1972. + + Bhushan, Abhay, "File Transfer Protocol (FTP) Status and Further + Comments", RFC 414 (NIC 12406), MIT-Project MAC, 20 November 1972. + + Braden, Bob, "Comments on File Transfer Protocol", RFC 430 + (NIC 13299), UCLA/CCN, 7 February 1973. + + Thomas, Bob, and Bob Clements, "FTP Server-Server Interaction", + RFC 438 (NIC 13770), BBN, 15 January 1973. + + Braden, Bob, "Print Files in FTP", RFC 448 (NIC 13299), UCLA/CCN, + 27 February 1973. + + McKenzie, Alex, "File Transfer Protocol", RFC 454 (NIC 14333), BBN, + 16 February 1973. + + +Postel & Reynolds [Page 66] + + + +RFC 959 October 1985 +File Transfer Protocol + + + Bressler, Bob, and Bob Thomas, "Mail Retrieval via FTP", RFC 458 + (NIC 14378), BBN-NET and BBN-TENEX, 20 February 1973. + + Neigus, Nancy, "File Transfer Protocol", RFC 542 (NIC 17759), BBN, + 12 July 1973. + + Krilanovich, Mark, and George Gregg, "Comments on the File Transfer + Protocol", RFC 607 (NIC 21255), UCSB, 7 January 1974. + + Pogran, Ken, and Nancy Neigus, "Response to RFC 607 - Comments on the + File Transfer Protocol", RFC 614 (NIC 21530), BBN, 28 January 1974. + + Krilanovich, Mark, George Gregg, Wayne Hathaway, and Jim White, + "Comments on the File Transfer Protocol", RFC 624 (NIC 22054), UCSB, + Ames Research Center, SRI-ARC, 28 February 1974. + + Bhushan, Abhay, "FTP Comments and Response to RFC 430", RFC 463 + (NIC 14573), MIT-DMCG, 21 February 1973. + + Braden, Bob, "FTP Data Compression", RFC 468 (NIC 14742), UCLA/CCN, + 8 March 1973. + + Bhushan, Abhay, "FTP and Network Mail System", RFC 475 (NIC 14919), + MIT-DMCG, 6 March 1973. + + Bressler, Bob, and Bob Thomas "FTP Server-Server Interaction - II", + RFC 478 (NIC 14947), BBN-NET and BBN-TENEX, 26 March 1973. + + White, Jim, "Use of FTP by the NIC Journal", RFC 479 (NIC 14948), + SRI-ARC, 8 March 1973. + + White, Jim, "Host-Dependent FTP Parameters", RFC 480 (NIC 14949), + SRI-ARC, 8 March 1973. + + Padlipsky, Mike, "An FTP Command-Naming Problem", RFC 506 + (NIC 16157), MIT-Multics, 26 June 1973. + + Day, John, "Memo to FTP Group (Proposal for File Access Protocol)", + RFC 520 (NIC 16819), Illinois, 25 June 1973. + + Merryman, Robert, "The UCSD-CC Server-FTP Facility", RFC 532 + (NIC 17451), UCSD-CC, 22 June 1973. + + Braden, Bob, "TENEX FTP Problem", RFC 571 (NIC 18974), UCLA/CCN, + 15 November 1973. + + + + +Postel & Reynolds [Page 67] + + + +RFC 959 October 1985 +File Transfer Protocol + + + McKenzie, Alex, and Jon Postel, "Telnet and FTP Implementation - + Schedule Change", RFC 593 (NIC 20615), BBN and MITRE, + 29 November 1973. + + Sussman, Julie, "FTP Error Code Usage for More Reliable Mail + Service", RFC 630 (NIC 30237), BBN, 10 April 1974. + + Postel, Jon, "Revised FTP Reply Codes", RFC 640 (NIC 30843), + UCLA/NMC, 5 June 1974. + + Harvey, Brian, "Leaving Well Enough Alone", RFC 686 (NIC 32481), + SU-AI, 10 May 1975. + + Harvey, Brian, "One More Try on the FTP", RFC 691 (NIC 32700), SU-AI, + 28 May 1975. + + Lieb, J., "CWD Command of FTP", RFC 697 (NIC 32963), 14 July 1975. + + Harrenstien, Ken, "FTP Extension: XSEN", RFC 737 (NIC 42217), SRI-KL, + 31 October 1977. + + Harrenstien, Ken, "FTP Extension: XRSQ/XRCP", RFC 743 (NIC 42758), + SRI-KL, 30 December 1977. + + Lebling, P. David, "Survey of FTP Mail and MLFL", RFC 751, MIT, + 10 December 1978. + + Postel, Jon, "File Transfer Protocol Specification", RFC 765, ISI, + June 1980. + + Mankins, David, Dan Franklin, and Buzz Owen, "Directory Oriented FTP + Commands", RFC 776, BBN, December 1980. + + Padlipsky, Michael, "FTP Unique-Named Store Command", RFC 949, MITRE, + July 1985. + + + + + + + + + + + + + + +Postel & Reynolds [Page 68] + + + +RFC 959 October 1985 +File Transfer Protocol + + +REFERENCES + + [1] Feinler, Elizabeth, "Internet Protocol Transition Workbook", + Network Information Center, SRI International, March 1982. + + [2] Postel, Jon, "Transmission Control Protocol - DARPA Internet + Program Protocol Specification", RFC 793, DARPA, September 1981. + + [3] Postel, Jon, and Joyce Reynolds, "Telnet Protocol + Specification", RFC 854, ISI, May 1983. + + [4] Reynolds, Joyce, and Jon Postel, "Assigned Numbers", RFC 943, + ISI, April 1985. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Postel & Reynolds [Page 69] + diff --git a/netwerk/protocol/ftp/doc/testdoc b/netwerk/protocol/ftp/doc/testdoc new file mode 100644 index 000000000..61fda16fc --- /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 000000000..3f708952a --- /dev/null +++ b/netwerk/protocol/ftp/ftpCore.h @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#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 000000000..060fb7575 --- /dev/null +++ b/netwerk/protocol/ftp/moz.build @@ -0,0 +1,45 @@ +# -*- 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/. + +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['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/netwerk/protocol/ftp/nsFTPChannel.cpp b/netwerk/protocol/ftp/nsFTPChannel.cpp new file mode 100644 index 000000000..2a0f04915 --- /dev/null +++ b/netwerk/protocol/ftp/nsFTPChannel.cpp @@ -0,0 +1,294 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sts=4 sw=4 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, + nsIChannelWithDivertableParentListener) + +//----------------------------------------------------------------------------- + +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 **stream) +{ + NS_ENSURE_ARG_POINTER(stream); + *stream = mUploadStream; + NS_IF_ADDREF(*stream); + 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) +{ + *aProxyInfo = ProxyInfo(); + NS_IF_ADDREF(*aProxyInfo); + return NS_OK; +} + +//----------------------------------------------------------------------------- + +nsresult +nsFtpChannel::OpenContentStream(bool async, nsIInputStream **result, + nsIChannel** channel) +{ + if (!async) + return NS_ERROR_NOT_IMPLEMENTED; + + nsFtpState *state = new nsFtpState(); + if (!state) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(state); + + nsresult rv = state->Init(this); + if (NS_FAILED(rv)) { + NS_RELEASE(state); + return rv; + } + + *result = state; + 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() {} + +public: + explicit FTPEventSinkProxy(nsIFTPEventSink* aTarget) + : mTarget(aTarget) + , mTargetThread(do_GetCurrentThread()) + { } + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIFTPEVENTSINK + + class OnFTPControlLogRunnable : public Runnable + { + public: + OnFTPControlLogRunnable(nsIFTPEventSink* aTarget, + bool aServer, + const char* aMessage) + : mTarget(aTarget) + , mServer(aServer) + , mMessage(aMessage) + { } + + NS_DECL_NSIRUNNABLE + + private: + nsCOMPtr<nsIFTPEventSink> mTarget; + bool mServer; + nsCString mMessage; + }; + +private: + nsCOMPtr<nsIFTPEventSink> mTarget; + nsCOMPtr<nsIThread> mTargetThread; +}; + +NS_IMPL_ISUPPORTS(FTPEventSinkProxy, nsIFTPEventSink) + +NS_IMETHODIMP +FTPEventSinkProxy::OnFTPControlLog(bool aServer, const char* aMsg) +{ + RefPtr<OnFTPControlLogRunnable> r = + new OnFTPControlLogRunnable(mTarget, aServer, aMsg); + return mTargetThread->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)); + + nsresult rv = nsBaseChannel::Suspend(); + + nsresult rvParentChannel = NS_OK; + if (mParentChannel) { + rvParentChannel = mParentChannel->SuspendMessageDiversion(); + } + + return NS_FAILED(rv) ? rv : rvParentChannel; +} + +NS_IMETHODIMP +nsFtpChannel::Resume() +{ + LOG(("nsFtpChannel::Resume [this=%p]\n", this)); + + nsresult rv = nsBaseChannel::Resume(); + + nsresult rvParentChannel = NS_OK; + if (mParentChannel) { + rvParentChannel = mParentChannel->ResumeMessageDiversion(); + } + + return NS_FAILED(rv) ? rv : rvParentChannel; +} + +//----------------------------------------------------------------------------- +// AChannelHasDivertableParentChannelAsListener internal functions +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +nsFtpChannel::MessageDiversionStarted(ADivertableParentChannel *aParentChannel) +{ + MOZ_ASSERT(!mParentChannel); + mParentChannel = aParentChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpChannel::MessageDiversionStop() +{ + LOG(("nsFtpChannel::MessageDiversionStop [this=%p]", this)); + MOZ_ASSERT(mParentChannel); + mParentChannel = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpChannel::SuspendInternal() +{ + LOG(("nsFtpChannel::SuspendInternal [this=%p]\n", this)); + + return nsBaseChannel::Suspend(); +} + +NS_IMETHODIMP +nsFtpChannel::ResumeInternal() +{ + LOG(("nsFtpChannel::ResumeInternal [this=%p]\n", this)); + + return nsBaseChannel::Resume(); +} diff --git a/netwerk/protocol/ftp/nsFTPChannel.h b/netwerk/protocol/ftp/nsFTPChannel.h new file mode 100644 index 000000000..549e577b3 --- /dev/null +++ b/netwerk/protocol/ftp/nsFTPChannel.h @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 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 "nsIChannelWithDivertableParentListener.h" +#include "nsIFTPChannel.h" +#include "nsIForcePendingChannel.h" +#include "nsIUploadChannel.h" +#include "nsIProxyInfo.h" +#include "nsIProxiedChannel.h" +#include "nsIResumableChannel.h" + +class nsIURI; +using mozilla::net::ADivertableParentChannel; + +class nsFtpChannel final : public nsBaseChannel, + public nsIFTPChannel, + public nsIUploadChannel, + public nsIResumableChannel, + public nsIProxiedChannel, + public nsIForcePendingChannel, + public nsIChannelWithDivertableParentListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIUPLOADCHANNEL + NS_DECL_NSIRESUMABLECHANNEL + NS_DECL_NSIPROXIEDCHANNEL + NS_DECL_NSICHANNELWITHDIVERTABLEPARENTLISTENER + + nsFtpChannel(nsIURI *uri, nsIProxyInfo *pi) + : mProxyInfo(pi) + , mStartPos(0) + , mResumeRequested(false) + , mLastModifiedTime(0) + , mForcePending(false) + { + SetURI(uri); + } + + 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 nsCSubstring &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() {} + 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; + RefPtr<ADivertableParentChannel> mParentChannel; +}; + +#endif /* nsFTPChannel_h___ */ diff --git a/netwerk/protocol/ftp/nsFtpConnectionThread.cpp b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp new file mode 100644 index 000000000..d428b093c --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp @@ -0,0 +1,2256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set tw=80 ts=4 sts=4 sw=4 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 "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 "nsIStreamListenerTee.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIStringBundle.h" +#include "nsAuthInformationHolder.h" +#include "nsIProtocolProxyService.h" +#include "nsICancelable.h" +#include "nsIOutputStream.h" +#include "nsIPrompt.h" +#include "nsIProtocolHandler.h" +#include "nsIProxyInfo.h" +#include "nsIRunnable.h" +#include "nsISocketTransportService.h" +#include "nsIURI.h" +#include "nsILoadInfo.h" +#include "nsNullPrincipal.h" +#include "nsIAuthPrompt2.h" +#include "nsIFTPChannelParentInternal.h" + +#ifdef MOZ_WIDGET_GONK +#include "NetStatistics.h" +#endif + +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) + , mReceivedControlData(false) + , mTryingCachedControl(false) + , mRETRFailed(false) + , mFileSize(kJS_MAX_SAFE_UINTEGER) + , mServerType(FTP_GENERIC_TYPE) + , mAction(GET) + , mAnonymous(true) + , mRetryPass(false) + , mStorReplyReceived(false) + , mInternalError(NS_OK) + , mReconnectAndLoginAgain(false) + , mCacheConnection(true) + , mPort(21) + , mAddressChecked(false) + , mServerIsIPv6(false) + , mUseUTF8(false) + , mControlStatus(NS_OK) + , mDeferredCallbackPending(false) +{ + LOG_INFO(("FTP:(%x) nsFtpState created", this)); + + // make sure handler stays around + NS_ADDREF(gFtpHandler); +} + +nsFtpState::~nsFtpState() +{ + LOG_INFO(("FTP:(%x) nsFtpState destroyed", this)); + + if (mProxyRequest) + mProxyRequest->Cancel(NS_ERROR_FAILURE); + + // release reference to handler + nsFtpProtocolHandler *handler = gFtpHandler; + NS_RELEASE(handler); +} + +// 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 && + isdigit(line[0]) && + isdigit(line[1]) && + isdigit(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 [%x was-cached=%u]\n", + this, mControlConnection.get(), 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:(%x) 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(NS_LITERAL_CSTRING("UTF-8")); + + // 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=%x]\n", this, + mControlConnection.get(), rv)); + mControlConnection = nullptr; + return rv; + } + + return mControlConnection->WaitData(this); +} + +void +nsFtpState::MoveToNextState(FTP_STATE nextState) +{ + if (NS_FAILED(mInternalError)) { + mState = FTP_ERROR; + LOG(("FTP:(%x) FAILED (%x)\n", this, 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:(%x) FTP_ERROR - calling StopProcessing\n", this)); + rv = StopProcessing(); + NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed."); + processingRead = false; + } + break; + + case FTP_COMPLETE: + LOG(("FTP:(%x) 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, + EmptyString(), + EmptyCString()); + + 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.Append(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 { + nsXPIDLCString 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", + getter_Copies(anonPassword)); + } + } + if (!anonPassword.IsEmpty()) { + passwordStr.AppendASCII(anonPassword); + } 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, + EmptyString(), + EmptyCString()); + + 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.Append(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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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."); + // Guessing causes crashes. + // (Of course, the parsing code should be more robust...) + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + if (!bundleService) + return FTP_ERROR; + + nsCOMPtr<nsIStringBundle> bundle; + nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL, + getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return FTP_ERROR; + + char16_t* ucs2Response = ToNewUnicode(mResponseMsg); + const char16_t *formatStrings[1] = { ucs2Response }; + NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer"); + + nsXPIDLString formattedString; + rv = bundle->FormatStringFromName(name.get(), formatStrings, 1, + getter_Copies(formattedString)); + free(ucs2Response); + if (NS_FAILED(rv)) + return FTP_ERROR; + + // TODO(darin): this code should not be dictating UI like this! + nsCOMPtr<nsIPrompt> prompter; + mChannel->GetCallback(prompter); + if (prompter) + prompter->Alert(nullptr, formattedString.get()); + + // since we just alerted the user, 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(NS_LITERAL_CSTRING("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(NS_LITERAL_CSTRING("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.Insert("CWD ",0); + cwdStr.Append(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.Insert("SIZE ",0); + sizeBuf.Append(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.Insert("MDTM ",0); + mdtmBuf.Append(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('/'); + url->SetFilePath(filePath); + } + } + return mChannel->SetContentType( + NS_LITERAL_CSTRING(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(EmptyCString()); + + 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) { + // 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) { + //(DONE) + mNextState = FTP_COMPLETE; + 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.Insert("RETR ",0); + retrStr.Append(CRLF); + return SendFTPCommand(retrStr); +} + +FTP_STATE +nsFtpState::R_retr() { + if (mResponseCode/100 == 2) { + //(DONE) + mNextState = FTP_COMPLETE; + return FTP_COMPLETE; + } + + if (mResponseCode/100 == 1) { + 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.Append(CRLF); + + return SendFTPCommand(restString); +} + +FTP_STATE +nsFtpState::R_rest() { + if (mResponseCode/100 == 4) { + // If REST fails, then we can't resume + mChannel->SetEntityID(EmptyCString()); + + 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.Insert("STOR ",0); + storStr.Append(CRLF); + + return SendFTPCommand(storStr); +} + +FTP_STATE +nsFtpState::R_stor() { + if (mResponseCode/100 == 2) { + //(DONE) + mNextState = FTP_COMPLETE; + mStorReplyReceived = true; + + // Call Close() if it was not called in nsFtpState::OnStoprequest() + if (!mUploadRequest && !IsClosed()) + Close(); + + return FTP_COMPLETE; + } + + if (mResponseCode/100 == 1) { + LOG(("FTP:(%x) writing on DT\n", this)); + 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 (!IsIPAddrAny(&mServerAddress)) + mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) && + !IsIPAddrV4Mapped(&mServerAddress); + 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) && + !IsIPAddrV4Mapped(&selfAddress); + } + } + } + } + + 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; + 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 (!IsIPAddrAny(&mServerAddress)) { + char buf[kIPv6CStrBufSize]; + NetAddrToString(&mServerAddress, 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(nullptr, 0, 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:(%x) created DT (%s:%x)\n", this, host.get(), port)); + + // hook ourself up as a proxy for status notifications + rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread()); + 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; + rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier), + mChannel->UploadStream(), + output, + stEventTarget, + true, // upload stream is buffered + false); // output is NOT buffered + 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(NS_LITERAL_CSTRING("FEAT" CRLF)); +} + +FTP_STATE +nsFtpState::R_feat() { + if (mResponseCode/100 == 2) { + if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) { + // This FTP server supports UTF-8 encoding + mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8")); + 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(NS_LITERAL_CSTRING("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 + + // initialize counter for network metering + mCountRecv = 0; + +#ifdef MOZ_WIDGET_GONK + nsCOMPtr<nsINetworkInfo> activeNetworkInfo; + GetActiveNetworkInfo(activeNetworkInfo); + mActiveNetworkInfo = + new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo); +#endif + + 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()->GetPath(path); + } + if (NS_FAILED(rv)) + return rv; + + removeParamsFromPath(path); + + // FTP parameters such as type=i are ignored + if (url) { + url->SetFilePath(path); + } else { + mChannel->URI()->SetPath(path); + } + + // 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, + 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: %x\n", 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; +} + +class nsFtpAsyncAlert : public Runnable +{ +public: + nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg) + : mPrompter(aPrompter) + , mResponseMsg(aResponseMsg) + { + MOZ_COUNT_CTOR(nsFtpAsyncAlert); + } +protected: + virtual ~nsFtpAsyncAlert() + { + MOZ_COUNT_DTOR(nsFtpAsyncAlert); + } +public: + NS_IMETHOD Run() override + { + if (mPrompter) { + mPrompter->Alert(nullptr, mResponseMsg.get()); + } + return NS_OK; + } +private: + nsCOMPtr<nsIPrompt> mPrompter; + nsString mResponseMsg; +}; + + +nsresult +nsFtpState::StopProcessing() +{ + // Only do this function once. + if (!mKeepRunning) + return NS_OK; + mKeepRunning = false; + + LOG_INFO(("FTP:(%x) nsFtpState stopping", this)); + + if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) { + // check to see if the control status is bad. + // web shell wont throw an alert. we better: + + // XXX(darin): this code should not be dictating UI like this! + nsCOMPtr<nsIPrompt> prompter; + mChannel->GetCallback(prompter); + if (prompter) { + nsCOMPtr<nsIRunnable> alertEvent; + if (mUseUTF8) { + alertEvent = new nsFtpAsyncAlert(prompter, + NS_ConvertUTF8toUTF16(mResponseMsg)); + } else { + alertEvent = new nsFtpAsyncAlert(prompter, + NS_ConvertASCIItoUTF16(mResponseMsg)); + } + NS_DispatchToMainThread(alertEvent); + } + 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 nsCSubstring& command) +{ + NS_ASSERTION(mControlConnection, "null control connection"); + + // we don't want to log the password: + nsAutoCString logcmd(command); + if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) + logcmd = "PASS xxxxx"; + + LOG(("FTP:(%x) 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:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok)); + LOG(("FTP:(%x) 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:(%x) 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:(%x) 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:(%x) ConvertDirspecToVMS to: \"%s\"\n", this, dirSpec.get())); +} + +// Convert an absolute VMS style dirspec to UNIX format +void +nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec) +{ + LOG(("FTP:(%x) 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:(%x) 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: + 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, nsISupports *context) +{ + mStorReplyReceived = false; + return NS_OK; +} + +NS_IMETHODIMP +nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context, + 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); + if (NS_SUCCEEDED(rv)) { + CountRecvBytes(*result); + } + return rv; + } + + return nsBaseContentStream::ReadSegments(writer, closure, count, result); +} + +nsresult +nsFtpState::SaveNetworkStats(bool enforce) +{ +#ifdef MOZ_WIDGET_GONK + // Obtain app id + uint32_t appId; + bool isInBrowser; + NS_GetAppInfo(mChannel, &appId, &isInBrowser); + + // Check if active network and appid are valid. + if (!mActiveNetworkInfo || appId == NECKO_NO_APP_ID) { + return NS_OK; + } + + if (mCountRecv <= 0) { + // There is no traffic, no need to save. + return NS_OK; + } + + // If |enforce| is false, the traffic amount is saved + // only when the total amount exceeds the predefined + // threshold. + if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) { + return NS_OK; + } + + // Create the event to save the network statistics. + // the event is then dispathed to the main thread. + RefPtr<Runnable> event = + new SaveNetworkStatsEvent(appId, isInBrowser, mActiveNetworkInfo, + mCountRecv, 0, false); + NS_DispatchToMainThread(event); + + // Reset the counters after saving. + mCountRecv = 0; + + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsFtpState::CloseWithStatus(nsresult status) +{ + LOG(("FTP:(%p) close [%x]\n", this, status)); + + // Shutdown the control connection processing if we are being closed with an + // error. Note: This method may be called several times. + if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) { + if (NS_SUCCEEDED(mInternalError)) + mInternalError = status; + StopProcessing(); + } + + if (mUploadRequest) { + mUploadRequest->Cancel(NS_ERROR_ABORT); + mUploadRequest = nullptr; + } + + if (mDataTransport) { + // Save the network stats before data transport is closing. + SaveNetworkStats(true); + + // Shutdown the data transport. + mDataTransport->Close(NS_ERROR_ABORT); + mDataTransport = nullptr; + } + + 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->GetLoadInfo(getter_AddRefs(loadInfo)); + + return pph->NewProxiedChannel2(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 000000000..dd48da562 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.h @@ -0,0 +1,231 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef __nsFtpConnectionThread__h_ +#define __nsFtpConnectionThread__h_ + +#include "nsBaseContentStream.h" + +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsAutoPtr.h" +#include "nsITransport.h" +#include "mozilla/net/DNS.h" +#include "nsFtpControlConnection.h" +#include "nsIProtocolProxyCallback.h" + +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkInterface.h" +#include "nsProxyRelease.h" +#endif + +// 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 nsCSubstring& 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 + + // ****** 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 + 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; + +// These members are used for network per-app metering (bug 855948) +// Currently, they are only available on gonk. + uint64_t mCountRecv; +#ifdef MOZ_WIDGET_GONK + nsMainThreadPtrHandle<nsINetworkInfo> mActiveNetworkInfo; +#endif + nsresult SaveNetworkStats(bool); + void CountRecvBytes(uint64_t recvBytes) + { + mCountRecv += recvBytes; + SaveNetworkStats(false); + } +}; + +#endif //__nsFtpConnectionThread__h_ diff --git a/netwerk/protocol/ftp/nsFtpControlConnection.cpp b/netwerk/protocol/ftp/nsFtpControlConnection.cpp new file mode 100644 index 000000000..ab55cd4f6 --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpControlConnection.cpp @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 2; 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 "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 "nsIOutputStream.h" +#include "nsNetCID.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 nsCSubstring& host, + uint32_t port) + : mServerType(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(nullptr, 0, 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, NS_GetCurrentThread()); + + // 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, NS_GetCurrentThread()); +} + +nsresult +nsFtpControlConnection::Disconnect(nsresult status) +{ + if (!mSocket) + return NS_OK; // already disconnected + + LOG_INFO(("FTP:(%p) CC disconnecting (%x)", this, 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 nsCSubstring& 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 000000000..5301bb9ce --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpControlConnection.h @@ -0,0 +1,85 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set et ts=4 sts=4 sw=4 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 "nsAutoPtr.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 nsCSubstring& host, uint32_t port); + + nsresult Connect(nsIProxyInfo* proxyInfo, nsITransportEventSink* eventSink); + nsresult Disconnect(nsresult status); + nsresult Write(const nsCSubstring& 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 000000000..46b12767a --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.cpp @@ -0,0 +1,426 @@ +/* -*- Mode: C++; tab-width: 2; 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/. + * + * + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink + * use in OS2 + */ + +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/FTPChannelChild.h" +using namespace mozilla; +using namespace mozilla::net; + +#include "nsFtpProtocolHandler.h" +#include "nsFTPChannel.h" +#include "nsIStandardURL.h" +#include "mozilla/Logging.h" +#include "nsIPrefService.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 QOS_DATA_PREF "network.ftp.data.qos" +#define QOS_CONTROL_PREF "network.ftp.control.qos" + +nsFtpProtocolHandler *gFtpHandler = nullptr; + +//----------------------------------------------------------------------------- + +nsFtpProtocolHandler::nsFtpProtocolHandler() + : mIdleTimeout(-1) + , mSessionId(0) + , mControlQoSBits(0x00) + , mDataQoSBits(0x00) +{ + LOG(("FTP:creating handler @%x\n", this)); + + gFtpHandler = this; +} + +nsFtpProtocolHandler::~nsFtpProtocolHandler() +{ + LOG(("FTP:destroying handler @%x\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; + + 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::NewURI(const nsACString &aSpec, + const char *aCharset, + nsIURI *aBaseURI, + nsIURI **result) +{ + nsAutoCString spec(aSpec); + spec.Trim(" \t\n\r"); // Match NS_IsAsciiWhitespace instead of HTML5 + + char *fwdPtr = spec.BeginWriting(); + + // now unescape it... %xx reduced inline to resulting character + + int32_t len = NS_UnescapeURL(fwdPtr); + + // NS_UnescapeURL() modified spec's buffer, truncate to ensure + // spec knows its new length. + spec.Truncate(len); + + // return an error if we find a NUL, CR, or LF in the path + if (spec.FindCharInSet(CRLF) >= 0 || spec.FindChar('\0') >= 0) + return NS_ERROR_MALFORMED_URI; + + nsresult rv; + nsCOMPtr<nsIStandardURL> url = do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, 21, aSpec, aCharset, aBaseURI); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(url, result); +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewChannel2(nsIURI* url, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + return NewProxiedChannel2(url, nullptr, 0, nullptr, aLoadInfo, result); +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewChannel(nsIURI* url, nsIChannel* *result) +{ + return NewChannel2(url, nullptr, result); +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewProxiedChannel2(nsIURI* uri, nsIProxyInfo* proxyInfo, + uint32_t proxyResolveFlags, + nsIURI *proxyURI, + nsILoadInfo* aLoadInfo, + nsIChannel* *result) +{ + NS_ENSURE_ARG_POINTER(uri); + RefPtr<nsBaseChannel> channel; + if (IsNeckoChild()) + channel = new FTPChannelChild(uri); + else + channel = new nsFtpChannel(uri, proxyInfo); + + nsresult rv = channel->Init(); + if (NS_FAILED(rv)) { + return rv; + } + + // set the loadInfo on the new channel + rv = channel->SetLoadInfo(aLoadInfo); + if (NS_FAILED(rv)) { + return rv; + } + + channel.forget(result); + return rv; +} + +NS_IMETHODIMP +nsFtpProtocolHandler::NewProxiedChannel(nsIURI* uri, nsIProxyInfo* proxyInfo, + uint32_t proxyResolveFlags, + nsIURI *proxyURI, + nsIChannel* *result) +{ + return NewProxiedChannel2(uri, proxyInfo, proxyResolveFlags, + proxyURI, nullptr /*loadinfo*/, + result); +} + +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())); + + nsresult rv; + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) return rv; + + timerStruct* ts = new timerStruct(); + if (!ts) + return NS_ERROR_OUT_OF_MEMORY; + + rv = timer->InitWithFuncCallback(nsFtpProtocolHandler::Timeout, + ts, + mIdleTimeout*1000, + nsITimer::TYPE_REPEATING_SLACK); + if (NS_FAILED(rv)) { + delete ts; + return rv; + } + + ts->key = ToNewCString(spec); + 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; + + 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 { + NS_NOTREACHED("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 000000000..0bc3b0dfd --- /dev/null +++ b/netwerk/protocol/ftp/nsFtpProtocolHandler.h @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#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; + + // 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 000000000..de8298384 --- /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 000000000..2642c804b --- /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 000000000..077b8d8f7 --- /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 000000000..78c0dad58 --- /dev/null +++ b/netwerk/protocol/ftp/test/frametest/index.html @@ -0,0 +1,13 @@ +<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 000000000..afe3afcf2 --- /dev/null +++ b/netwerk/protocol/ftp/test/frametest/menu.html @@ -0,0 +1,373 @@ +<html> + +<script language="javascript"> + +<!-- + +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> |