summaryrefslogtreecommitdiffstats
path: root/netwerk/protocol/ftp/nsFtpConnectionThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/protocol/ftp/nsFtpConnectionThread.cpp')
-rw-r--r--netwerk/protocol/ftp/nsFtpConnectionThread.cpp2256
1 files changed, 2256 insertions, 0 deletions
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());
+ }
+}
+