netwerk/protocol/ftp/nsFtpConnectionThread.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/netwerk/protocol/ftp/nsFtpConnectionThread.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,2543 @@
     1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     1.5 +/* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include <ctype.h>
    1.11 +
    1.12 +#include "prprf.h"
    1.13 +#include "prlog.h"
    1.14 +#include "prtime.h"
    1.15 +
    1.16 +#include "nsIOService.h"
    1.17 +#include "nsFTPChannel.h"
    1.18 +#include "nsFtpConnectionThread.h"
    1.19 +#include "nsFtpControlConnection.h"
    1.20 +#include "nsFtpProtocolHandler.h"
    1.21 +#include "netCore.h"
    1.22 +#include "nsCRT.h"
    1.23 +#include "nsEscape.h"
    1.24 +#include "nsMimeTypes.h"
    1.25 +#include "nsNetUtil.h"
    1.26 +#include "nsThreadUtils.h"
    1.27 +#include "nsStreamUtils.h"
    1.28 +#include "nsICacheService.h"
    1.29 +#include "nsIURL.h"
    1.30 +#include "nsISocketTransport.h"
    1.31 +#include "nsIStreamListenerTee.h"
    1.32 +#include "nsIPrefService.h"
    1.33 +#include "nsIPrefBranch.h"
    1.34 +#include "nsIStringBundle.h"
    1.35 +#include "nsAuthInformationHolder.h"
    1.36 +#include "nsIProtocolProxyService.h"
    1.37 +#include "nsICancelable.h"
    1.38 +#include "nsICacheEntryDescriptor.h"
    1.39 +#include "nsIOutputStream.h"
    1.40 +#include "nsIPrompt.h"
    1.41 +#include "nsIProtocolHandler.h"
    1.42 +#include "nsIProxyInfo.h"
    1.43 +#include "nsIRunnable.h"
    1.44 +#include "nsISocketTransportService.h"
    1.45 +#include "nsIURI.h"
    1.46 +#include "nsICacheSession.h"
    1.47 +
    1.48 +#ifdef MOZ_WIDGET_GONK
    1.49 +#include "NetStatistics.h"
    1.50 +#endif
    1.51 +
    1.52 +#if defined(PR_LOGGING)
    1.53 +extern PRLogModuleInfo* gFTPLog;
    1.54 +#endif
    1.55 +#define LOG(args)         PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
    1.56 +#define LOG_ALWAYS(args)  PR_LOG(gFTPLog, PR_LOG_ALWAYS, args)
    1.57 +
    1.58 +using namespace mozilla::net;
    1.59 +
    1.60 +// remove FTP parameters (starting with ";") from the path
    1.61 +static void
    1.62 +removeParamsFromPath(nsCString& path)
    1.63 +{
    1.64 +  int32_t index = path.FindChar(';');
    1.65 +  if (index >= 0) {
    1.66 +    path.SetLength(index);
    1.67 +  }
    1.68 +}
    1.69 +
    1.70 +NS_IMPL_ISUPPORTS_INHERITED(nsFtpState,
    1.71 +                            nsBaseContentStream,
    1.72 +                            nsIInputStreamCallback, 
    1.73 +                            nsITransportEventSink,
    1.74 +                            nsICacheListener,
    1.75 +                            nsIRequestObserver,
    1.76 +                            nsIProtocolProxyCallback)
    1.77 +
    1.78 +nsFtpState::nsFtpState()
    1.79 +    : nsBaseContentStream(true)
    1.80 +    , mState(FTP_INIT)
    1.81 +    , mNextState(FTP_S_USER)
    1.82 +    , mKeepRunning(true)
    1.83 +    , mReceivedControlData(false)
    1.84 +    , mTryingCachedControl(false)
    1.85 +    , mRETRFailed(false)
    1.86 +    , mFileSize(UINT64_MAX)
    1.87 +    , mServerType(FTP_GENERIC_TYPE)
    1.88 +    , mAction(GET)
    1.89 +    , mAnonymous(true)
    1.90 +    , mRetryPass(false)
    1.91 +    , mStorReplyReceived(false)
    1.92 +    , mInternalError(NS_OK)
    1.93 +    , mReconnectAndLoginAgain(false)
    1.94 +    , mCacheConnection(true)
    1.95 +    , mPort(21)
    1.96 +    , mAddressChecked(false)
    1.97 +    , mServerIsIPv6(false)
    1.98 +    , mUseUTF8(false)
    1.99 +    , mControlStatus(NS_OK)
   1.100 +    , mDeferredCallbackPending(false)
   1.101 +{
   1.102 +    LOG_ALWAYS(("FTP:(%x) nsFtpState created", this));
   1.103 +
   1.104 +    // make sure handler stays around
   1.105 +    NS_ADDREF(gFtpHandler);
   1.106 +}
   1.107 +
   1.108 +nsFtpState::~nsFtpState() 
   1.109 +{
   1.110 +    LOG_ALWAYS(("FTP:(%x) nsFtpState destroyed", this));
   1.111 +
   1.112 +    if (mProxyRequest)
   1.113 +        mProxyRequest->Cancel(NS_ERROR_FAILURE);
   1.114 +
   1.115 +    // release reference to handler
   1.116 +    nsFtpProtocolHandler *handler = gFtpHandler;
   1.117 +    NS_RELEASE(handler);
   1.118 +}
   1.119 +
   1.120 +// nsIInputStreamCallback implementation
   1.121 +NS_IMETHODIMP
   1.122 +nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream)
   1.123 +{
   1.124 +    LOG(("FTP:(%p) data stream ready\n", this));
   1.125 +
   1.126 +    // We are receiving a notification from our data stream, so just forward it
   1.127 +    // on to our stream callback.
   1.128 +    if (HasPendingCallback())
   1.129 +        DispatchCallbackSync();
   1.130 +
   1.131 +    return NS_OK;
   1.132 +}
   1.133 +
   1.134 +void
   1.135 +nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen)
   1.136 +{
   1.137 +    LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen));
   1.138 +    mControlConnection->WaitData(this);  // queue up another call
   1.139 +
   1.140 +    if (!mReceivedControlData) {
   1.141 +        // parameter can be null cause the channel fills them in.
   1.142 +        OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0);
   1.143 +        mReceivedControlData = true;
   1.144 +    }
   1.145 +
   1.146 +    // Sometimes we can get two responses in the same packet, eg from LIST.
   1.147 +    // So we need to parse the response line by line
   1.148 +
   1.149 +    nsCString buffer = mControlReadCarryOverBuf;
   1.150 +
   1.151 +    // Clear the carryover buf - if we still don't have a line, then it will
   1.152 +    // be reappended below
   1.153 +    mControlReadCarryOverBuf.Truncate();
   1.154 +
   1.155 +    buffer.Append(aData, aDataLen);
   1.156 +
   1.157 +    const char* currLine = buffer.get();
   1.158 +    while (*currLine && mKeepRunning) {
   1.159 +        int32_t eolLength = strcspn(currLine, CRLF);
   1.160 +        int32_t currLineLength = strlen(currLine);
   1.161 +
   1.162 +        // if currLine is empty or only contains CR or LF, then bail.  we can
   1.163 +        // sometimes get an ODA event with the full response line + CR without
   1.164 +        // the trailing LF.  the trailing LF might come in the next ODA event.
   1.165 +        // because we are happy enough to process a response line ending only
   1.166 +        // in CR, we need to take care to discard the extra LF (bug 191220).
   1.167 +        if (eolLength == 0 && currLineLength <= 1)
   1.168 +            break;
   1.169 +
   1.170 +        if (eolLength == currLineLength) {
   1.171 +            mControlReadCarryOverBuf.Assign(currLine);
   1.172 +            break;
   1.173 +        }
   1.174 +
   1.175 +        // Append the current segment, including the LF
   1.176 +        nsAutoCString line;
   1.177 +        int32_t crlfLength = 0;
   1.178 +
   1.179 +        if ((currLineLength > eolLength) &&
   1.180 +            (currLine[eolLength] == nsCRT::CR) &&
   1.181 +            (currLine[eolLength+1] == nsCRT::LF)) {
   1.182 +            crlfLength = 2; // CR +LF 
   1.183 +        } else {
   1.184 +            crlfLength = 1; // + LF or CR
   1.185 +        }
   1.186 +
   1.187 +        line.Assign(currLine, eolLength + crlfLength);
   1.188 +        
   1.189 +        // Does this start with a response code?
   1.190 +        bool startNum = (line.Length() >= 3 &&
   1.191 +                           isdigit(line[0]) &&
   1.192 +                           isdigit(line[1]) &&
   1.193 +                           isdigit(line[2]));
   1.194 +
   1.195 +        if (mResponseMsg.IsEmpty()) {
   1.196 +            // If we get here, then we know that we have a complete line, and
   1.197 +            // that it is the first one
   1.198 +
   1.199 +            NS_ASSERTION(line.Length() > 4 && startNum,
   1.200 +                         "Read buffer doesn't include response code");
   1.201 +            
   1.202 +            mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get());
   1.203 +        }
   1.204 +
   1.205 +        mResponseMsg.Append(line);
   1.206 +
   1.207 +        // This is the last line if its 3 numbers followed by a space
   1.208 +        if (startNum && line[3] == ' ') {
   1.209 +            // yup. last line, let's move on.
   1.210 +            if (mState == mNextState) {
   1.211 +                NS_ERROR("ftp read state mixup");
   1.212 +                mInternalError = NS_ERROR_FAILURE;
   1.213 +                mState = FTP_ERROR;
   1.214 +            } else {
   1.215 +                mState = mNextState;
   1.216 +            }
   1.217 +
   1.218 +            nsCOMPtr<nsIFTPEventSink> ftpSink;
   1.219 +            mChannel->GetFTPEventSink(ftpSink);
   1.220 +            if (ftpSink)
   1.221 +                ftpSink->OnFTPControlLog(true, mResponseMsg.get());
   1.222 +            
   1.223 +            nsresult rv = Process();
   1.224 +            mResponseMsg.Truncate();
   1.225 +            if (NS_FAILED(rv)) {
   1.226 +                CloseWithStatus(rv);
   1.227 +                return;
   1.228 +            }
   1.229 +        }
   1.230 +
   1.231 +        currLine = currLine + eolLength + crlfLength;
   1.232 +    }
   1.233 +}
   1.234 +
   1.235 +void
   1.236 +nsFtpState::OnControlError(nsresult status)
   1.237 +{
   1.238 +    NS_ASSERTION(NS_FAILED(status), "expecting error condition");
   1.239 +
   1.240 +    LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n",
   1.241 +         this, mControlConnection.get(), status, mTryingCachedControl));
   1.242 +
   1.243 +    mControlStatus = status;
   1.244 +    if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) {
   1.245 +        mReconnectAndLoginAgain = false;
   1.246 +        mAnonymous = false;
   1.247 +        mControlStatus = NS_OK;
   1.248 +        Connect();
   1.249 +    } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) {
   1.250 +        mTryingCachedControl = false;
   1.251 +        Connect();
   1.252 +    } else {
   1.253 +        CloseWithStatus(status);
   1.254 +    }
   1.255 +}
   1.256 +
   1.257 +nsresult
   1.258 +nsFtpState::EstablishControlConnection()
   1.259 +{
   1.260 +    NS_ASSERTION(!mControlConnection, "we already have a control connection");
   1.261 +            
   1.262 +    nsresult rv;
   1.263 +
   1.264 +    LOG(("FTP:(%x) trying cached control\n", this));
   1.265 +        
   1.266 +    // Look to see if we can use a cached control connection:
   1.267 +    nsFtpControlConnection *connection = nullptr;
   1.268 +    // Don't use cached control if anonymous (bug #473371)
   1.269 +    if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   1.270 +        gFtpHandler->RemoveConnection(mChannel->URI(), &connection);
   1.271 +
   1.272 +    if (connection) {
   1.273 +        mControlConnection.swap(connection);
   1.274 +        if (mControlConnection->IsAlive())
   1.275 +        {
   1.276 +            // set stream listener of the control connection to be us.        
   1.277 +            mControlConnection->WaitData(this);
   1.278 +            
   1.279 +            // read cached variables into us. 
   1.280 +            mServerType = mControlConnection->mServerType;           
   1.281 +            mPassword   = mControlConnection->mPassword;
   1.282 +            mPwd        = mControlConnection->mPwd;
   1.283 +            mUseUTF8    = mControlConnection->mUseUTF8;
   1.284 +            mTryingCachedControl = true;
   1.285 +
   1.286 +            // we have to set charset to connection if server supports utf-8
   1.287 +            if (mUseUTF8)
   1.288 +                mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
   1.289 +            
   1.290 +            // we're already connected to this server, skip login.
   1.291 +            mState = FTP_S_PASV;
   1.292 +            mResponseCode = 530;  // assume the control connection was dropped.
   1.293 +            mControlStatus = NS_OK;
   1.294 +            mReceivedControlData = false;  // For this request, we have not.
   1.295 +
   1.296 +            // if we succeed, return.  Otherwise, we need to create a transport
   1.297 +            rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
   1.298 +            if (NS_SUCCEEDED(rv))
   1.299 +                return rv;
   1.300 +        }
   1.301 +        LOG(("FTP:(%p) cached CC(%p) is unusable\n", this,
   1.302 +            mControlConnection.get()));
   1.303 +
   1.304 +        mControlConnection->WaitData(nullptr);
   1.305 +        mControlConnection = nullptr;
   1.306 +    }
   1.307 +
   1.308 +    LOG(("FTP:(%p) creating CC\n", this));
   1.309 +        
   1.310 +    mState = FTP_READ_BUF;
   1.311 +    mNextState = FTP_S_USER;
   1.312 +    
   1.313 +    nsAutoCString host;
   1.314 +    rv = mChannel->URI()->GetAsciiHost(host);
   1.315 +    if (NS_FAILED(rv))
   1.316 +        return rv;
   1.317 +
   1.318 +    mControlConnection = new nsFtpControlConnection(host, mPort);
   1.319 +    if (!mControlConnection)
   1.320 +        return NS_ERROR_OUT_OF_MEMORY;
   1.321 +
   1.322 +    rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
   1.323 +    if (NS_FAILED(rv)) {
   1.324 +        LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this,
   1.325 +            mControlConnection.get(), rv));
   1.326 +        mControlConnection = nullptr;
   1.327 +        return rv;
   1.328 +    }
   1.329 +
   1.330 +    return mControlConnection->WaitData(this);
   1.331 +}
   1.332 +
   1.333 +void 
   1.334 +nsFtpState::MoveToNextState(FTP_STATE nextState)
   1.335 +{
   1.336 +    if (NS_FAILED(mInternalError)) {
   1.337 +        mState = FTP_ERROR;
   1.338 +        LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError));
   1.339 +    } else {
   1.340 +        mState = FTP_READ_BUF;
   1.341 +        mNextState = nextState;
   1.342 +    }  
   1.343 +}
   1.344 +
   1.345 +nsresult
   1.346 +nsFtpState::Process() 
   1.347 +{
   1.348 +    nsresult    rv = NS_OK;
   1.349 +    bool        processingRead = true;
   1.350 +    
   1.351 +    while (mKeepRunning && processingRead) {
   1.352 +        switch (mState) {
   1.353 +          case FTP_COMMAND_CONNECT:
   1.354 +            KillControlConnection();
   1.355 +            LOG(("FTP:(%p) establishing CC", this));
   1.356 +            mInternalError = EstablishControlConnection();  // sets mState
   1.357 +            if (NS_FAILED(mInternalError)) {
   1.358 +                mState = FTP_ERROR;
   1.359 +                LOG(("FTP:(%p) FAILED\n", this));
   1.360 +            } else {
   1.361 +                LOG(("FTP:(%p) SUCCEEDED\n", this));
   1.362 +            }
   1.363 +            break;
   1.364 +    
   1.365 +          case FTP_READ_BUF:
   1.366 +            LOG(("FTP:(%p) Waiting for CC(%p)\n", this,
   1.367 +                mControlConnection.get()));
   1.368 +            processingRead = false;
   1.369 +            break;
   1.370 +          
   1.371 +          case FTP_ERROR: // xx needs more work to handle dropped control connection cases
   1.372 +            if ((mTryingCachedControl && mResponseCode == 530 &&
   1.373 +                mInternalError == NS_ERROR_FTP_PASV) ||
   1.374 +                (mResponseCode == 425 &&
   1.375 +                mInternalError == NS_ERROR_FTP_PASV)) {
   1.376 +                // The user was logged out during an pasv operation
   1.377 +                // we want to restart this request with a new control
   1.378 +                // channel.
   1.379 +                mState = FTP_COMMAND_CONNECT;
   1.380 +            } else if (mResponseCode == 421 && 
   1.381 +                       mInternalError != NS_ERROR_FTP_LOGIN) {
   1.382 +                // The command channel dropped for some reason.
   1.383 +                // Fire it back up, unless we were trying to login
   1.384 +                // in which case the server might just be telling us
   1.385 +                // that the max number of users has been reached...
   1.386 +                mState = FTP_COMMAND_CONNECT;
   1.387 +            } else if (mAnonymous && 
   1.388 +                       mInternalError == NS_ERROR_FTP_LOGIN) {
   1.389 +                // If the login was anonymous, and it failed, try again with a username
   1.390 +                // Don't reuse old control connection, see #386167
   1.391 +                mAnonymous = false;
   1.392 +                mState = FTP_COMMAND_CONNECT;
   1.393 +            } else {
   1.394 +                LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this));
   1.395 +                rv = StopProcessing();
   1.396 +                NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
   1.397 +                processingRead = false;
   1.398 +            }
   1.399 +            break;
   1.400 +          
   1.401 +          case FTP_COMPLETE:
   1.402 +            LOG(("FTP:(%x) COMPLETE\n", this));
   1.403 +            rv = StopProcessing();
   1.404 +            NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
   1.405 +            processingRead = false;
   1.406 +            break;
   1.407 +
   1.408 +// USER           
   1.409 +          case FTP_S_USER:
   1.410 +            rv = S_user();
   1.411 +            
   1.412 +            if (NS_FAILED(rv))
   1.413 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.414 +            
   1.415 +            MoveToNextState(FTP_R_USER);
   1.416 +            break;
   1.417 +            
   1.418 +          case FTP_R_USER:
   1.419 +            mState = R_user();
   1.420 +            
   1.421 +            if (FTP_ERROR == mState)
   1.422 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.423 +            
   1.424 +            break;
   1.425 +// PASS            
   1.426 +          case FTP_S_PASS:
   1.427 +            rv = S_pass();
   1.428 +            
   1.429 +            if (NS_FAILED(rv))
   1.430 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.431 +            
   1.432 +            MoveToNextState(FTP_R_PASS);
   1.433 +            break;
   1.434 +            
   1.435 +          case FTP_R_PASS:
   1.436 +            mState = R_pass();
   1.437 +            
   1.438 +            if (FTP_ERROR == mState)
   1.439 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.440 +            
   1.441 +            break;
   1.442 +// ACCT            
   1.443 +          case FTP_S_ACCT:
   1.444 +            rv = S_acct();
   1.445 +            
   1.446 +            if (NS_FAILED(rv))
   1.447 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.448 +            
   1.449 +            MoveToNextState(FTP_R_ACCT);
   1.450 +            break;
   1.451 +            
   1.452 +          case FTP_R_ACCT:
   1.453 +            mState = R_acct();
   1.454 +            
   1.455 +            if (FTP_ERROR == mState)
   1.456 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.457 +            
   1.458 +            break;
   1.459 +
   1.460 +// SYST            
   1.461 +          case FTP_S_SYST:
   1.462 +            rv = S_syst();
   1.463 +            
   1.464 +            if (NS_FAILED(rv))
   1.465 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.466 +            
   1.467 +            MoveToNextState(FTP_R_SYST);
   1.468 +            break;
   1.469 +            
   1.470 +          case FTP_R_SYST:
   1.471 +            mState = R_syst();
   1.472 +            
   1.473 +            if (FTP_ERROR == mState)
   1.474 +                mInternalError = NS_ERROR_FTP_LOGIN;
   1.475 +
   1.476 +            break;
   1.477 +
   1.478 +// TYPE            
   1.479 +          case FTP_S_TYPE:
   1.480 +            rv = S_type();
   1.481 +            
   1.482 +            if (NS_FAILED(rv))
   1.483 +                mInternalError = rv;
   1.484 +            
   1.485 +            MoveToNextState(FTP_R_TYPE);
   1.486 +            break;
   1.487 +            
   1.488 +          case FTP_R_TYPE:
   1.489 +            mState = R_type();
   1.490 +            
   1.491 +            if (FTP_ERROR == mState)
   1.492 +                mInternalError = NS_ERROR_FAILURE;
   1.493 +            
   1.494 +            break;
   1.495 +// CWD            
   1.496 +          case FTP_S_CWD:
   1.497 +            rv = S_cwd();
   1.498 +            
   1.499 +            if (NS_FAILED(rv))
   1.500 +                mInternalError = NS_ERROR_FTP_CWD;
   1.501 +            
   1.502 +            MoveToNextState(FTP_R_CWD);
   1.503 +            break;
   1.504 +            
   1.505 +          case FTP_R_CWD:
   1.506 +            mState = R_cwd();
   1.507 +            
   1.508 +            if (FTP_ERROR == mState)
   1.509 +                mInternalError = NS_ERROR_FTP_CWD;
   1.510 +            break;
   1.511 +       
   1.512 +// LIST
   1.513 +          case FTP_S_LIST:
   1.514 +            rv = S_list();
   1.515 +
   1.516 +            if (rv == NS_ERROR_NOT_RESUMABLE) {
   1.517 +                mInternalError = rv;
   1.518 +            } else if (NS_FAILED(rv)) {
   1.519 +                mInternalError = NS_ERROR_FTP_CWD;
   1.520 +            }
   1.521 +            
   1.522 +            MoveToNextState(FTP_R_LIST);
   1.523 +            break;
   1.524 +
   1.525 +          case FTP_R_LIST:        
   1.526 +            mState = R_list();
   1.527 +            
   1.528 +            if (FTP_ERROR == mState)
   1.529 +                mInternalError = NS_ERROR_FAILURE;
   1.530 +
   1.531 +            break;
   1.532 +
   1.533 +// SIZE            
   1.534 +          case FTP_S_SIZE:
   1.535 +            rv = S_size();
   1.536 +            
   1.537 +            if (NS_FAILED(rv))
   1.538 +                mInternalError = rv;
   1.539 +            
   1.540 +            MoveToNextState(FTP_R_SIZE);
   1.541 +            break;
   1.542 +            
   1.543 +          case FTP_R_SIZE: 
   1.544 +            mState = R_size();
   1.545 +            
   1.546 +            if (FTP_ERROR == mState)
   1.547 +                mInternalError = NS_ERROR_FAILURE;
   1.548 +            
   1.549 +            break;
   1.550 +
   1.551 +// REST        
   1.552 +          case FTP_S_REST:
   1.553 +            rv = S_rest();
   1.554 +            
   1.555 +            if (NS_FAILED(rv))
   1.556 +                mInternalError = rv;
   1.557 +            
   1.558 +            MoveToNextState(FTP_R_REST);
   1.559 +            break;
   1.560 +            
   1.561 +          case FTP_R_REST:
   1.562 +            mState = R_rest();
   1.563 +            
   1.564 +            if (FTP_ERROR == mState)
   1.565 +                mInternalError = NS_ERROR_FAILURE;
   1.566 +            
   1.567 +            break;
   1.568 +
   1.569 +// MDTM
   1.570 +          case FTP_S_MDTM:
   1.571 +            rv = S_mdtm();
   1.572 +            if (NS_FAILED(rv))
   1.573 +                mInternalError = rv;
   1.574 +            MoveToNextState(FTP_R_MDTM);
   1.575 +            break;
   1.576 +
   1.577 +          case FTP_R_MDTM:
   1.578 +            mState = R_mdtm();
   1.579 +
   1.580 +            // Don't want to overwrite a more explicit status code
   1.581 +            if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   1.582 +                mInternalError = NS_ERROR_FAILURE;
   1.583 +            
   1.584 +            break;
   1.585 +            
   1.586 +// RETR        
   1.587 +          case FTP_S_RETR:
   1.588 +            rv = S_retr();
   1.589 +            
   1.590 +            if (NS_FAILED(rv)) 
   1.591 +                mInternalError = rv;
   1.592 +            
   1.593 +            MoveToNextState(FTP_R_RETR);
   1.594 +            break;
   1.595 +            
   1.596 +          case FTP_R_RETR:
   1.597 +
   1.598 +            mState = R_retr();
   1.599 +            
   1.600 +            if (FTP_ERROR == mState)
   1.601 +                mInternalError = NS_ERROR_FAILURE;
   1.602 +            
   1.603 +            break;
   1.604 +            
   1.605 +// STOR        
   1.606 +          case FTP_S_STOR:
   1.607 +            rv = S_stor();
   1.608 +
   1.609 +            if (NS_FAILED(rv))
   1.610 +                mInternalError = rv;
   1.611 +            
   1.612 +            MoveToNextState(FTP_R_STOR);
   1.613 +            break;
   1.614 +            
   1.615 +          case FTP_R_STOR:
   1.616 +            mState = R_stor();
   1.617 +
   1.618 +            if (FTP_ERROR == mState)
   1.619 +                mInternalError = NS_ERROR_FAILURE;
   1.620 +
   1.621 +            break;
   1.622 +            
   1.623 +// PASV        
   1.624 +          case FTP_S_PASV:
   1.625 +            rv = S_pasv();
   1.626 +
   1.627 +            if (NS_FAILED(rv))
   1.628 +                mInternalError = NS_ERROR_FTP_PASV;
   1.629 +            
   1.630 +            MoveToNextState(FTP_R_PASV);
   1.631 +            break;
   1.632 +            
   1.633 +          case FTP_R_PASV:
   1.634 +            mState = R_pasv();
   1.635 +
   1.636 +            if (FTP_ERROR == mState) 
   1.637 +                mInternalError = NS_ERROR_FTP_PASV;
   1.638 +
   1.639 +            break;
   1.640 +            
   1.641 +// PWD        
   1.642 +          case FTP_S_PWD:
   1.643 +            rv = S_pwd();
   1.644 +
   1.645 +            if (NS_FAILED(rv))
   1.646 +                mInternalError = NS_ERROR_FTP_PWD;
   1.647 +            
   1.648 +            MoveToNextState(FTP_R_PWD);
   1.649 +            break;
   1.650 +            
   1.651 +          case FTP_R_PWD:
   1.652 +            mState = R_pwd();
   1.653 +
   1.654 +            if (FTP_ERROR == mState) 
   1.655 +                mInternalError = NS_ERROR_FTP_PWD;
   1.656 +
   1.657 +            break;
   1.658 +
   1.659 +// FEAT for RFC2640 support
   1.660 +          case FTP_S_FEAT:
   1.661 +            rv = S_feat();
   1.662 +
   1.663 +            if (NS_FAILED(rv))
   1.664 +                mInternalError = rv;
   1.665 +
   1.666 +            MoveToNextState(FTP_R_FEAT);
   1.667 +            break;
   1.668 +
   1.669 +          case FTP_R_FEAT:
   1.670 +            mState = R_feat();
   1.671 +
   1.672 +            // Don't want to overwrite a more explicit status code
   1.673 +            if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   1.674 +                mInternalError = NS_ERROR_FAILURE;
   1.675 +            break;
   1.676 +
   1.677 +// OPTS for some non-RFC2640-compliant servers support
   1.678 +          case FTP_S_OPTS:
   1.679 +            rv = S_opts();
   1.680 +
   1.681 +            if (NS_FAILED(rv))
   1.682 +                mInternalError = rv;
   1.683 +
   1.684 +            MoveToNextState(FTP_R_OPTS);
   1.685 +            break;
   1.686 +
   1.687 +          case FTP_R_OPTS:
   1.688 +            mState = R_opts();
   1.689 +
   1.690 +            // Don't want to overwrite a more explicit status code
   1.691 +            if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   1.692 +                mInternalError = NS_ERROR_FAILURE;
   1.693 +            break;
   1.694 +
   1.695 +          default:
   1.696 +            ;
   1.697 +            
   1.698 +        }
   1.699 +    }
   1.700 +
   1.701 +    return rv;
   1.702 +}
   1.703 +
   1.704 +///////////////////////////////////
   1.705 +// STATE METHODS
   1.706 +///////////////////////////////////
   1.707 +nsresult
   1.708 +nsFtpState::S_user() {
   1.709 +    // some servers on connect send us a 421 or 521.  (84525) (141784)
   1.710 +    if ((mResponseCode == 421) || (mResponseCode == 521))
   1.711 +        return NS_ERROR_FAILURE;
   1.712 +
   1.713 +    nsresult rv;
   1.714 +    nsAutoCString usernameStr("USER ");
   1.715 +
   1.716 +    mResponseMsg = "";
   1.717 +
   1.718 +    if (mAnonymous) {
   1.719 +        mReconnectAndLoginAgain = true;
   1.720 +        usernameStr.AppendLiteral("anonymous");
   1.721 +    } else {
   1.722 +        mReconnectAndLoginAgain = false;
   1.723 +        if (mUsername.IsEmpty()) {
   1.724 +
   1.725 +            // No prompt for anonymous requests (bug #473371)
   1.726 +            if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   1.727 +              return NS_ERROR_FAILURE;
   1.728 +
   1.729 +            nsCOMPtr<nsIAuthPrompt2> prompter;
   1.730 +            NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
   1.731 +                                getter_AddRefs(prompter));
   1.732 +            if (!prompter)
   1.733 +                return NS_ERROR_NOT_INITIALIZED;
   1.734 +
   1.735 +            nsRefPtr<nsAuthInformationHolder> info =
   1.736 +                new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST,
   1.737 +                                            EmptyString(),
   1.738 +                                            EmptyCString());
   1.739 +
   1.740 +            bool retval;
   1.741 +            rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
   1.742 +                                      info, &retval);
   1.743 +
   1.744 +            // if the user canceled or didn't supply a username we want to fail
   1.745 +            if (NS_FAILED(rv) || !retval || info->User().IsEmpty())
   1.746 +                return NS_ERROR_FAILURE;
   1.747 +
   1.748 +            mUsername = info->User();
   1.749 +            mPassword = info->Password();
   1.750 +        }
   1.751 +        // XXX Is UTF-8 the best choice?
   1.752 +        AppendUTF16toUTF8(mUsername, usernameStr);
   1.753 +    }
   1.754 +    usernameStr.Append(CRLF);
   1.755 +
   1.756 +    return SendFTPCommand(usernameStr);
   1.757 +}
   1.758 +
   1.759 +FTP_STATE
   1.760 +nsFtpState::R_user() {
   1.761 +    mReconnectAndLoginAgain = false;
   1.762 +    if (mResponseCode/100 == 3) {
   1.763 +        // send off the password
   1.764 +        return FTP_S_PASS;
   1.765 +    }
   1.766 +    if (mResponseCode/100 == 2) {
   1.767 +        // no password required, we're already logged in
   1.768 +        return FTP_S_SYST;
   1.769 +    }
   1.770 +    if (mResponseCode/100 == 5) {
   1.771 +        // problem logging in. typically this means the server
   1.772 +        // has reached it's user limit.
   1.773 +        return FTP_ERROR;
   1.774 +    }
   1.775 +    // LOGIN FAILED
   1.776 +    return FTP_ERROR;
   1.777 +}
   1.778 +
   1.779 +
   1.780 +nsresult
   1.781 +nsFtpState::S_pass() {
   1.782 +    nsresult rv;
   1.783 +    nsAutoCString passwordStr("PASS ");
   1.784 +
   1.785 +    mResponseMsg = "";
   1.786 +
   1.787 +    if (mAnonymous) {
   1.788 +        if (!mPassword.IsEmpty()) {
   1.789 +            // XXX Is UTF-8 the best choice?
   1.790 +            AppendUTF16toUTF8(mPassword, passwordStr);
   1.791 +        } else {
   1.792 +            nsXPIDLCString anonPassword;
   1.793 +            bool useRealEmail = false;
   1.794 +            nsCOMPtr<nsIPrefBranch> prefs =
   1.795 +                    do_GetService(NS_PREFSERVICE_CONTRACTID);
   1.796 +            if (prefs) {
   1.797 +                rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
   1.798 +                if (NS_SUCCEEDED(rv) && useRealEmail) {
   1.799 +                    prefs->GetCharPref("network.ftp.anonymous_password",
   1.800 +                                       getter_Copies(anonPassword));
   1.801 +                }
   1.802 +            }
   1.803 +            if (!anonPassword.IsEmpty()) {
   1.804 +                passwordStr.AppendASCII(anonPassword);
   1.805 +            } else {
   1.806 +                // We need to default to a valid email address - bug 101027
   1.807 +                // example.com is reserved (rfc2606), so use that
   1.808 +                passwordStr.AppendLiteral("mozilla@example.com");
   1.809 +            }
   1.810 +        }
   1.811 +    } else {
   1.812 +        if (mPassword.IsEmpty() || mRetryPass) {
   1.813 +            
   1.814 +            // No prompt for anonymous requests (bug #473371)
   1.815 +            if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   1.816 +                return NS_ERROR_FAILURE;
   1.817 +
   1.818 +            nsCOMPtr<nsIAuthPrompt2> prompter;
   1.819 +            NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
   1.820 +                                getter_AddRefs(prompter));
   1.821 +            if (!prompter)
   1.822 +                return NS_ERROR_NOT_INITIALIZED;
   1.823 +
   1.824 +            nsRefPtr<nsAuthInformationHolder> info =
   1.825 +                new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST |
   1.826 +                                            nsIAuthInformation::ONLY_PASSWORD,
   1.827 +                                            EmptyString(),
   1.828 +                                            EmptyCString());
   1.829 +
   1.830 +            info->SetUserInternal(mUsername);
   1.831 +
   1.832 +            bool retval;
   1.833 +            rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
   1.834 +                                      info, &retval);
   1.835 +
   1.836 +            // we want to fail if the user canceled. Note here that if they want
   1.837 +            // a blank password, we will pass it along.
   1.838 +            if (NS_FAILED(rv) || !retval)
   1.839 +                return NS_ERROR_FAILURE;
   1.840 +
   1.841 +            mPassword = info->Password();
   1.842 +        }
   1.843 +        // XXX Is UTF-8 the best choice?
   1.844 +        AppendUTF16toUTF8(mPassword, passwordStr);
   1.845 +    }
   1.846 +    passwordStr.Append(CRLF);
   1.847 +
   1.848 +    return SendFTPCommand(passwordStr);
   1.849 +}
   1.850 +
   1.851 +FTP_STATE
   1.852 +nsFtpState::R_pass() {
   1.853 +    if (mResponseCode/100 == 3) {
   1.854 +        // send account info
   1.855 +        return FTP_S_ACCT;
   1.856 +    }
   1.857 +    if (mResponseCode/100 == 2) {
   1.858 +        // logged in
   1.859 +        return FTP_S_SYST;
   1.860 +    }
   1.861 +    if (mResponseCode == 503) {
   1.862 +        // start over w/ the user command.
   1.863 +        // note: the password was successful, and it's stored in mPassword
   1.864 +        mRetryPass = false;
   1.865 +        return FTP_S_USER;
   1.866 +    }
   1.867 +    if (mResponseCode/100 == 5 || mResponseCode==421) {
   1.868 +        // There is no difference between a too-many-users error,
   1.869 +        // a wrong-password error, or any other sort of error
   1.870 +
   1.871 +        if (!mAnonymous)
   1.872 +            mRetryPass = true;
   1.873 +
   1.874 +        return FTP_ERROR;
   1.875 +    }
   1.876 +    // unexpected response code
   1.877 +    return FTP_ERROR;
   1.878 +}
   1.879 +
   1.880 +nsresult
   1.881 +nsFtpState::S_pwd() {
   1.882 +    return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF));
   1.883 +}
   1.884 +
   1.885 +FTP_STATE
   1.886 +nsFtpState::R_pwd() {
   1.887 +    // Error response to PWD command isn't fatal, but don't cache the connection
   1.888 +    // if CWD command is sent since correct mPwd is needed for further requests.
   1.889 +    if (mResponseCode/100 != 2)
   1.890 +        return FTP_S_TYPE;
   1.891 +
   1.892 +    nsAutoCString respStr(mResponseMsg);
   1.893 +    int32_t pos = respStr.FindChar('"');
   1.894 +    if (pos > -1) {
   1.895 +        respStr.Cut(0, pos+1);
   1.896 +        pos = respStr.FindChar('"');
   1.897 +        if (pos > -1) {
   1.898 +            respStr.Truncate(pos);
   1.899 +            if (mServerType == FTP_VMS_TYPE)
   1.900 +                ConvertDirspecFromVMS(respStr);
   1.901 +            if (respStr.IsEmpty() || respStr.Last() != '/')
   1.902 +                respStr.Append('/');
   1.903 +            mPwd = respStr;
   1.904 +        }
   1.905 +    }
   1.906 +    return FTP_S_TYPE;
   1.907 +}
   1.908 +
   1.909 +nsresult
   1.910 +nsFtpState::S_syst() {
   1.911 +    return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF));
   1.912 +}
   1.913 +
   1.914 +FTP_STATE
   1.915 +nsFtpState::R_syst() {
   1.916 +    if (mResponseCode/100 == 2) {
   1.917 +        if (( mResponseMsg.Find("L8") > -1) || 
   1.918 +            ( mResponseMsg.Find("UNIX") > -1) || 
   1.919 +            ( mResponseMsg.Find("BSD") > -1) ||
   1.920 +            ( mResponseMsg.Find("MACOS Peter's Server") > -1) ||
   1.921 +            ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
   1.922 +            ( mResponseMsg.Find("MVS") > -1) ||
   1.923 +            ( mResponseMsg.Find("OS/390") > -1) ||
   1.924 +            ( mResponseMsg.Find("OS/400") > -1)) {
   1.925 +            mServerType = FTP_UNIX_TYPE;
   1.926 +        } else if (( mResponseMsg.Find("WIN32", true) > -1) ||
   1.927 +                   ( mResponseMsg.Find("windows", true) > -1)) {
   1.928 +            mServerType = FTP_NT_TYPE;
   1.929 +        } else if (mResponseMsg.Find("OS/2", true) > -1) {
   1.930 +            mServerType = FTP_OS2_TYPE;
   1.931 +        } else if (mResponseMsg.Find("VMS", true) > -1) {
   1.932 +            mServerType = FTP_VMS_TYPE;
   1.933 +        } else {
   1.934 +            NS_ERROR("Server type list format unrecognized.");
   1.935 +            // Guessing causes crashes.
   1.936 +            // (Of course, the parsing code should be more robust...)
   1.937 +            nsCOMPtr<nsIStringBundleService> bundleService =
   1.938 +                do_GetService(NS_STRINGBUNDLE_CONTRACTID);
   1.939 +            if (!bundleService)
   1.940 +                return FTP_ERROR;
   1.941 +
   1.942 +            nsCOMPtr<nsIStringBundle> bundle;
   1.943 +            nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL,
   1.944 +                                                      getter_AddRefs(bundle));
   1.945 +            if (NS_FAILED(rv))
   1.946 +                return FTP_ERROR;
   1.947 +            
   1.948 +            char16_t* ucs2Response = ToNewUnicode(mResponseMsg);
   1.949 +            const char16_t *formatStrings[1] = { ucs2Response };
   1.950 +            NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer");
   1.951 +
   1.952 +            nsXPIDLString formattedString;
   1.953 +            rv = bundle->FormatStringFromName(name.get(), formatStrings, 1,
   1.954 +                                              getter_Copies(formattedString));
   1.955 +            nsMemory::Free(ucs2Response);
   1.956 +            if (NS_FAILED(rv))
   1.957 +                return FTP_ERROR;
   1.958 +
   1.959 +            // TODO(darin): this code should not be dictating UI like this!
   1.960 +            nsCOMPtr<nsIPrompt> prompter;
   1.961 +            mChannel->GetCallback(prompter);
   1.962 +            if (prompter)
   1.963 +                prompter->Alert(nullptr, formattedString.get());
   1.964 +            
   1.965 +            // since we just alerted the user, clear mResponseMsg,
   1.966 +            // which is displayed to the user.
   1.967 +            mResponseMsg = "";
   1.968 +            return FTP_ERROR;
   1.969 +        }
   1.970 +        
   1.971 +        return FTP_S_FEAT;
   1.972 +    }
   1.973 +
   1.974 +    if (mResponseCode/100 == 5) {   
   1.975 +        // server didn't like the SYST command.  Probably (500, 501, 502)
   1.976 +        // No clue.  We will just hope it is UNIX type server.
   1.977 +        mServerType = FTP_UNIX_TYPE;
   1.978 +
   1.979 +        return FTP_S_FEAT;
   1.980 +    }
   1.981 +    return FTP_ERROR;
   1.982 +}
   1.983 +
   1.984 +nsresult
   1.985 +nsFtpState::S_acct() {
   1.986 +    return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF));
   1.987 +}
   1.988 +
   1.989 +FTP_STATE
   1.990 +nsFtpState::R_acct() {
   1.991 +    if (mResponseCode/100 == 2)
   1.992 +        return FTP_S_SYST;
   1.993 +
   1.994 +    return FTP_ERROR;
   1.995 +}
   1.996 +
   1.997 +nsresult
   1.998 +nsFtpState::S_type() {
   1.999 +    return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF));
  1.1000 +}
  1.1001 +
  1.1002 +FTP_STATE
  1.1003 +nsFtpState::R_type() {
  1.1004 +    if (mResponseCode/100 != 2) 
  1.1005 +        return FTP_ERROR;
  1.1006 +    
  1.1007 +    return FTP_S_PASV;
  1.1008 +}
  1.1009 +
  1.1010 +nsresult
  1.1011 +nsFtpState::S_cwd() {
  1.1012 +    // Don't cache the connection if PWD command failed
  1.1013 +    if (mPwd.IsEmpty())
  1.1014 +        mCacheConnection = false;
  1.1015 +
  1.1016 +    nsAutoCString cwdStr;
  1.1017 +    if (mAction != PUT)
  1.1018 +        cwdStr = mPath;
  1.1019 +    if (cwdStr.IsEmpty() || cwdStr.First() != '/')
  1.1020 +        cwdStr.Insert(mPwd,0);
  1.1021 +    if (mServerType == FTP_VMS_TYPE)
  1.1022 +        ConvertDirspecToVMS(cwdStr);
  1.1023 +    cwdStr.Insert("CWD ",0);
  1.1024 +    cwdStr.Append(CRLF);
  1.1025 +
  1.1026 +    return SendFTPCommand(cwdStr);
  1.1027 +}
  1.1028 +
  1.1029 +FTP_STATE
  1.1030 +nsFtpState::R_cwd() {
  1.1031 +    if (mResponseCode/100 == 2) {
  1.1032 +        if (mAction == PUT)
  1.1033 +            return FTP_S_STOR;
  1.1034 +        
  1.1035 +        return FTP_S_LIST;
  1.1036 +    }
  1.1037 +    
  1.1038 +    return FTP_ERROR;
  1.1039 +}
  1.1040 +
  1.1041 +nsresult
  1.1042 +nsFtpState::S_size() {
  1.1043 +    nsAutoCString sizeBuf(mPath);
  1.1044 +    if (sizeBuf.IsEmpty() || sizeBuf.First() != '/')
  1.1045 +        sizeBuf.Insert(mPwd,0);
  1.1046 +    if (mServerType == FTP_VMS_TYPE)
  1.1047 +        ConvertFilespecToVMS(sizeBuf);
  1.1048 +    sizeBuf.Insert("SIZE ",0);
  1.1049 +    sizeBuf.Append(CRLF);
  1.1050 +
  1.1051 +    return SendFTPCommand(sizeBuf);
  1.1052 +}
  1.1053 +
  1.1054 +FTP_STATE
  1.1055 +nsFtpState::R_size() {
  1.1056 +    if (mResponseCode/100 == 2) {
  1.1057 +        PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
  1.1058 +        mChannel->SetContentLength(mFileSize);
  1.1059 +    }
  1.1060 +
  1.1061 +    // We may want to be able to resume this
  1.1062 +    return FTP_S_MDTM;
  1.1063 +}
  1.1064 +
  1.1065 +nsresult
  1.1066 +nsFtpState::S_mdtm() {
  1.1067 +    nsAutoCString mdtmBuf(mPath);
  1.1068 +    if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/')
  1.1069 +        mdtmBuf.Insert(mPwd,0);
  1.1070 +    if (mServerType == FTP_VMS_TYPE)
  1.1071 +        ConvertFilespecToVMS(mdtmBuf);
  1.1072 +    mdtmBuf.Insert("MDTM ",0);
  1.1073 +    mdtmBuf.Append(CRLF);
  1.1074 +
  1.1075 +    return SendFTPCommand(mdtmBuf);
  1.1076 +}
  1.1077 +
  1.1078 +FTP_STATE
  1.1079 +nsFtpState::R_mdtm() {
  1.1080 +    if (mResponseCode == 213) {
  1.1081 +        mResponseMsg.Cut(0,4);
  1.1082 +        mResponseMsg.Trim(" \t\r\n");
  1.1083 +        // yyyymmddhhmmss
  1.1084 +        if (mResponseMsg.Length() != 14) {
  1.1085 +            NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
  1.1086 +        } else {
  1.1087 +            mModTime = mResponseMsg;
  1.1088 +
  1.1089 +            // Save lastModified time for downloaded files.
  1.1090 +            nsAutoCString timeString;
  1.1091 +            nsresult error;
  1.1092 +            PRExplodedTime exTime;
  1.1093 +
  1.1094 +            mResponseMsg.Mid(timeString, 0, 4);
  1.1095 +            exTime.tm_year  = timeString.ToInteger(&error);
  1.1096 +            mResponseMsg.Mid(timeString, 4, 2);
  1.1097 +            exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0
  1.1098 +            mResponseMsg.Mid(timeString, 6, 2);
  1.1099 +            exTime.tm_mday  = timeString.ToInteger(&error);
  1.1100 +            mResponseMsg.Mid(timeString, 8, 2);
  1.1101 +            exTime.tm_hour  = timeString.ToInteger(&error);
  1.1102 +            mResponseMsg.Mid(timeString, 10, 2);
  1.1103 +            exTime.tm_min   = timeString.ToInteger(&error);
  1.1104 +            mResponseMsg.Mid(timeString, 12, 2);
  1.1105 +            exTime.tm_sec   = timeString.ToInteger(&error);
  1.1106 +            exTime.tm_usec  = 0;
  1.1107 +
  1.1108 +            exTime.tm_params.tp_gmt_offset = 0;
  1.1109 +            exTime.tm_params.tp_dst_offset = 0;
  1.1110 +
  1.1111 +            PR_NormalizeTime(&exTime, PR_GMTParameters);
  1.1112 +            exTime.tm_params = PR_LocalTimeParameters(&exTime);
  1.1113 +
  1.1114 +            PRTime time = PR_ImplodeTime(&exTime);
  1.1115 +            (void)mChannel->SetLastModifiedTime(time);
  1.1116 +        }
  1.1117 +    }
  1.1118 +
  1.1119 +    nsCString entityID;
  1.1120 +    entityID.Truncate();
  1.1121 +    entityID.AppendInt(int64_t(mFileSize));
  1.1122 +    entityID.Append('/');
  1.1123 +    entityID.Append(mModTime);
  1.1124 +    mChannel->SetEntityID(entityID);
  1.1125 +
  1.1126 +    // We weren't asked to resume
  1.1127 +    if (!mChannel->ResumeRequested())
  1.1128 +        return FTP_S_RETR;
  1.1129 +
  1.1130 +    //if (our entityID == supplied one (if any))
  1.1131 +    if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID))
  1.1132 +        return FTP_S_REST;
  1.1133 +
  1.1134 +    mInternalError = NS_ERROR_ENTITY_CHANGED;
  1.1135 +    mResponseMsg.Truncate();
  1.1136 +    return FTP_ERROR;
  1.1137 +}
  1.1138 +
  1.1139 +nsresult 
  1.1140 +nsFtpState::SetContentType()
  1.1141 +{
  1.1142 +    // FTP directory URLs don't always end in a slash.  Make sure they do.
  1.1143 +    // This check needs to be here rather than a more obvious place
  1.1144 +    // (e.g. LIST command processing) so that it ensures the terminating
  1.1145 +    // slash is appended for the new request case, as well as the case
  1.1146 +    // where the URL is being loaded from the cache.
  1.1147 +
  1.1148 +    if (!mPath.IsEmpty() && mPath.Last() != '/') {
  1.1149 +        nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI()));
  1.1150 +        nsAutoCString filePath;
  1.1151 +        if(NS_SUCCEEDED(url->GetFilePath(filePath))) {
  1.1152 +            filePath.Append('/');
  1.1153 +            url->SetFilePath(filePath);
  1.1154 +        }
  1.1155 +    }
  1.1156 +    return mChannel->SetContentType(
  1.1157 +        NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
  1.1158 +}
  1.1159 +
  1.1160 +nsresult
  1.1161 +nsFtpState::S_list() {
  1.1162 +    nsresult rv = SetContentType();
  1.1163 +    if (NS_FAILED(rv)) 
  1.1164 +        // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
  1.1165 +        // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
  1.1166 +        return (nsresult)FTP_ERROR;
  1.1167 +
  1.1168 +    rv = mChannel->PushStreamConverter("text/ftp-dir",
  1.1169 +                                       APPLICATION_HTTP_INDEX_FORMAT);
  1.1170 +    if (NS_FAILED(rv)) {
  1.1171 +        // clear mResponseMsg which is displayed to the user.
  1.1172 +        // TODO: we should probably set this to something meaningful.
  1.1173 +        mResponseMsg = "";
  1.1174 +        return rv;
  1.1175 +    }
  1.1176 +    
  1.1177 +    if (mCacheEntry) {
  1.1178 +        // save off the server type if we are caching.
  1.1179 +        nsAutoCString serverType;
  1.1180 +        serverType.AppendInt(mServerType);
  1.1181 +        mCacheEntry->SetMetaDataElement("servertype", serverType.get());
  1.1182 +
  1.1183 +        nsAutoCString useUTF8;
  1.1184 +        useUTF8.AppendInt(mUseUTF8);
  1.1185 +        mCacheEntry->SetMetaDataElement("useUTF8", useUTF8.get());
  1.1186 +
  1.1187 +        // open cache entry for writing, and configure it to receive data.
  1.1188 +        if (NS_FAILED(InstallCacheListener())) {
  1.1189 +            mCacheEntry->AsyncDoom(nullptr);
  1.1190 +            mCacheEntry = nullptr;
  1.1191 +        }
  1.1192 +    }
  1.1193 +
  1.1194 +    // dir listings aren't resumable
  1.1195 +    NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE);
  1.1196 +
  1.1197 +    mChannel->SetEntityID(EmptyCString());
  1.1198 +
  1.1199 +    const char *listString;
  1.1200 +    if (mServerType == FTP_VMS_TYPE) {
  1.1201 +        listString = "LIST *.*;0" CRLF;
  1.1202 +    } else {
  1.1203 +        listString = "LIST" CRLF;
  1.1204 +    }
  1.1205 +
  1.1206 +    return SendFTPCommand(nsDependentCString(listString));
  1.1207 +}
  1.1208 +
  1.1209 +FTP_STATE
  1.1210 +nsFtpState::R_list() {
  1.1211 +    if (mResponseCode/100 == 1) {
  1.1212 +        // OK, time to start reading from the data connection.
  1.1213 +        if (mDataStream && HasPendingCallback())
  1.1214 +            mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1.1215 +        return FTP_READ_BUF;
  1.1216 +    }
  1.1217 +
  1.1218 +    if (mResponseCode/100 == 2) {
  1.1219 +        //(DONE)
  1.1220 +        mNextState = FTP_COMPLETE;
  1.1221 +        mDoomCache = false;
  1.1222 +        return FTP_COMPLETE;
  1.1223 +    }
  1.1224 +    return FTP_ERROR;
  1.1225 +}
  1.1226 +
  1.1227 +nsresult
  1.1228 +nsFtpState::S_retr() {
  1.1229 +    nsAutoCString retrStr(mPath);
  1.1230 +    if (retrStr.IsEmpty() || retrStr.First() != '/')
  1.1231 +        retrStr.Insert(mPwd,0);
  1.1232 +    if (mServerType == FTP_VMS_TYPE)
  1.1233 +        ConvertFilespecToVMS(retrStr);
  1.1234 +    retrStr.Insert("RETR ",0);
  1.1235 +    retrStr.Append(CRLF);
  1.1236 +    return SendFTPCommand(retrStr);
  1.1237 +}
  1.1238 +
  1.1239 +FTP_STATE
  1.1240 +nsFtpState::R_retr() {
  1.1241 +    if (mResponseCode/100 == 2) {
  1.1242 +        //(DONE)
  1.1243 +        mNextState = FTP_COMPLETE;
  1.1244 +        return FTP_COMPLETE;
  1.1245 +    }
  1.1246 +
  1.1247 +    if (mResponseCode/100 == 1) {
  1.1248 +        // We're going to grab a file, not a directory. So we need to clear
  1.1249 +        // any cache entry, otherwise we'll have problems reading it later.
  1.1250 +        // See bug 122548
  1.1251 +        if (mCacheEntry) {
  1.1252 +            (void)mCacheEntry->AsyncDoom(nullptr);
  1.1253 +            mCacheEntry = nullptr;
  1.1254 +        }
  1.1255 +        if (mDataStream && HasPendingCallback())
  1.1256 +            mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1.1257 +        return FTP_READ_BUF;
  1.1258 +    }
  1.1259 +    
  1.1260 +    // These error codes are related to problems with the connection.  
  1.1261 +    // If we encounter any at this point, do not try CWD and abort.
  1.1262 +    if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
  1.1263 +        return FTP_ERROR;
  1.1264 +
  1.1265 +    if (mResponseCode/100 == 5) {
  1.1266 +        mRETRFailed = true;
  1.1267 +        return FTP_S_PASV;
  1.1268 +    }
  1.1269 +
  1.1270 +    return FTP_S_CWD;
  1.1271 +}
  1.1272 +
  1.1273 +
  1.1274 +nsresult
  1.1275 +nsFtpState::S_rest() {
  1.1276 +    
  1.1277 +    nsAutoCString restString("REST ");
  1.1278 +    // The int64_t cast is needed to avoid ambiguity
  1.1279 +    restString.AppendInt(int64_t(mChannel->StartPos()), 10);
  1.1280 +    restString.Append(CRLF);
  1.1281 +
  1.1282 +    return SendFTPCommand(restString);
  1.1283 +}
  1.1284 +
  1.1285 +FTP_STATE
  1.1286 +nsFtpState::R_rest() {
  1.1287 +    if (mResponseCode/100 == 4) {
  1.1288 +        // If REST fails, then we can't resume
  1.1289 +        mChannel->SetEntityID(EmptyCString());
  1.1290 +
  1.1291 +        mInternalError = NS_ERROR_NOT_RESUMABLE;
  1.1292 +        mResponseMsg.Truncate();
  1.1293 +
  1.1294 +        return FTP_ERROR;
  1.1295 +    }
  1.1296 +   
  1.1297 +    return FTP_S_RETR; 
  1.1298 +}
  1.1299 +
  1.1300 +nsresult
  1.1301 +nsFtpState::S_stor() {
  1.1302 +    NS_ENSURE_STATE(mChannel->UploadStream());
  1.1303 +
  1.1304 +    NS_ASSERTION(mAction == PUT, "Wrong state to be here");
  1.1305 +    
  1.1306 +    nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
  1.1307 +    NS_ASSERTION(url, "I thought you were a nsStandardURL");
  1.1308 +
  1.1309 +    nsAutoCString storStr;
  1.1310 +    url->GetFilePath(storStr);
  1.1311 +    NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
  1.1312 +        
  1.1313 +    // kill the first slash since we want to be relative to CWD.
  1.1314 +    if (storStr.First() == '/')
  1.1315 +        storStr.Cut(0,1);
  1.1316 +
  1.1317 +    if (mServerType == FTP_VMS_TYPE)
  1.1318 +        ConvertFilespecToVMS(storStr);
  1.1319 +
  1.1320 +    NS_UnescapeURL(storStr);
  1.1321 +    storStr.Insert("STOR ",0);
  1.1322 +    storStr.Append(CRLF);
  1.1323 +
  1.1324 +    return SendFTPCommand(storStr);
  1.1325 +}
  1.1326 +
  1.1327 +FTP_STATE
  1.1328 +nsFtpState::R_stor() {
  1.1329 +    if (mResponseCode/100 == 2) {
  1.1330 +        //(DONE)
  1.1331 +        mNextState = FTP_COMPLETE;
  1.1332 +        mStorReplyReceived = true;
  1.1333 +
  1.1334 +        // Call Close() if it was not called in nsFtpState::OnStoprequest()
  1.1335 +        if (!mUploadRequest && !IsClosed())
  1.1336 +            Close();
  1.1337 +
  1.1338 +        return FTP_COMPLETE;
  1.1339 +    }
  1.1340 +
  1.1341 +    if (mResponseCode/100 == 1) {
  1.1342 +        LOG(("FTP:(%x) writing on DT\n", this));
  1.1343 +        return FTP_READ_BUF;
  1.1344 +    }
  1.1345 +
  1.1346 +   mStorReplyReceived = true;
  1.1347 +   return FTP_ERROR;
  1.1348 +}
  1.1349 +
  1.1350 +
  1.1351 +nsresult
  1.1352 +nsFtpState::S_pasv() {
  1.1353 +    if (!mAddressChecked) {
  1.1354 +        // Find socket address
  1.1355 +        mAddressChecked = true;
  1.1356 +        mServerAddress.raw.family = AF_INET;
  1.1357 +        mServerAddress.inet.ip = htonl(INADDR_ANY);
  1.1358 +        mServerAddress.inet.port = htons(0);
  1.1359 +
  1.1360 +        nsITransport *controlSocket = mControlConnection->Transport();
  1.1361 +        if (!controlSocket)
  1.1362 +            // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
  1.1363 +            // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
  1.1364 +            return (nsresult)FTP_ERROR;
  1.1365 +
  1.1366 +        nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket);
  1.1367 +        if (sTrans) {
  1.1368 +            nsresult rv = sTrans->GetPeerAddr(&mServerAddress);
  1.1369 +            if (NS_SUCCEEDED(rv)) {
  1.1370 +                if (!IsIPAddrAny(&mServerAddress))
  1.1371 +                    mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
  1.1372 +                                    !IsIPAddrV4Mapped(&mServerAddress);
  1.1373 +                else {
  1.1374 +                    /*
  1.1375 +                     * In case of SOCKS5 remote DNS resolution, we do
  1.1376 +                     * not know the remote IP address. Still, if it is
  1.1377 +                     * an IPV6 host, then the external address of the
  1.1378 +                     * socks server should also be IPv6, and this is the
  1.1379 +                     * self address of the transport.
  1.1380 +                     */
  1.1381 +                    NetAddr selfAddress;
  1.1382 +                    rv = sTrans->GetSelfAddr(&selfAddress);
  1.1383 +                    if (NS_SUCCEEDED(rv))
  1.1384 +                        mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) &&
  1.1385 +                                        !IsIPAddrV4Mapped(&selfAddress);
  1.1386 +                }
  1.1387 +            }
  1.1388 +        }
  1.1389 +    }
  1.1390 +
  1.1391 +    const char *string;
  1.1392 +    if (mServerIsIPv6) {
  1.1393 +        string = "EPSV" CRLF;
  1.1394 +    } else {
  1.1395 +        string = "PASV" CRLF;
  1.1396 +    }
  1.1397 +
  1.1398 +    return SendFTPCommand(nsDependentCString(string));
  1.1399 +    
  1.1400 +}
  1.1401 +
  1.1402 +FTP_STATE
  1.1403 +nsFtpState::R_pasv() {
  1.1404 +    if (mResponseCode/100 != 2)
  1.1405 +        return FTP_ERROR;
  1.1406 +
  1.1407 +    nsresult rv;
  1.1408 +    int32_t port;
  1.1409 +
  1.1410 +    nsAutoCString responseCopy(mResponseMsg);
  1.1411 +    char *response = responseCopy.BeginWriting();
  1.1412 +
  1.1413 +    char *ptr = response;
  1.1414 +
  1.1415 +    // Make sure to ignore the address in the PASV response (bug 370559)
  1.1416 +
  1.1417 +    if (mServerIsIPv6) {
  1.1418 +        // The returned string is of the form
  1.1419 +        // text (|||ppp|)
  1.1420 +        // Where '|' can be any single character
  1.1421 +        char delim;
  1.1422 +        while (*ptr && *ptr != '(')
  1.1423 +            ptr++;
  1.1424 +        if (*ptr++ != '(')
  1.1425 +            return FTP_ERROR;
  1.1426 +        delim = *ptr++;
  1.1427 +        if (!delim || *ptr++ != delim ||
  1.1428 +                      *ptr++ != delim || 
  1.1429 +                      *ptr < '0' || *ptr > '9')
  1.1430 +            return FTP_ERROR;
  1.1431 +        port = 0;
  1.1432 +        do {
  1.1433 +            port = port * 10 + *ptr++ - '0';
  1.1434 +        } while (*ptr >= '0' && *ptr <= '9');
  1.1435 +        if (*ptr++ != delim || *ptr != ')')
  1.1436 +            return FTP_ERROR;
  1.1437 +    } else {
  1.1438 +        // The returned address string can be of the form
  1.1439 +        // (xxx,xxx,xxx,xxx,ppp,ppp) or
  1.1440 +        //  xxx,xxx,xxx,xxx,ppp,ppp (without parens)
  1.1441 +        int32_t h0, h1, h2, h3, p0, p1;
  1.1442 +
  1.1443 +        uint32_t fields = 0;
  1.1444 +        // First try with parens
  1.1445 +        while (*ptr && *ptr != '(')
  1.1446 +            ++ptr;
  1.1447 +        if (*ptr) {
  1.1448 +            ++ptr;
  1.1449 +            fields = PR_sscanf(ptr, 
  1.1450 +                               "%ld,%ld,%ld,%ld,%ld,%ld",
  1.1451 +                               &h0, &h1, &h2, &h3, &p0, &p1);
  1.1452 +        }
  1.1453 +        if (!*ptr || fields < 6) {
  1.1454 +            // OK, lets try w/o parens
  1.1455 +            ptr = response;
  1.1456 +            while (*ptr && *ptr != ',')
  1.1457 +                ++ptr;
  1.1458 +            if (*ptr) {
  1.1459 +                // backup to the start of the digits
  1.1460 +                do {
  1.1461 +                    ptr--;
  1.1462 +                } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
  1.1463 +                ptr++; // get back onto the numbers
  1.1464 +                fields = PR_sscanf(ptr, 
  1.1465 +                                   "%ld,%ld,%ld,%ld,%ld,%ld",
  1.1466 +                                   &h0, &h1, &h2, &h3, &p0, &p1);
  1.1467 +            }
  1.1468 +        }
  1.1469 +
  1.1470 +        NS_ASSERTION(fields == 6, "Can't parse PASV response");
  1.1471 +        if (fields < 6)
  1.1472 +            return FTP_ERROR;
  1.1473 +
  1.1474 +        port = ((int32_t) (p0<<8)) + p1;
  1.1475 +    }
  1.1476 +
  1.1477 +    bool newDataConn = true;
  1.1478 +    if (mDataTransport) {
  1.1479 +        // Reuse this connection only if its still alive, and the port
  1.1480 +        // is the same
  1.1481 +        nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport);
  1.1482 +        if (strans) {
  1.1483 +            int32_t oldPort;
  1.1484 +            nsresult rv = strans->GetPort(&oldPort);
  1.1485 +            if (NS_SUCCEEDED(rv)) {
  1.1486 +                if (oldPort == port) {
  1.1487 +                    bool isAlive;
  1.1488 +                    if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive)
  1.1489 +                        newDataConn = false;
  1.1490 +                }
  1.1491 +            }
  1.1492 +        }
  1.1493 +
  1.1494 +        if (newDataConn) {
  1.1495 +            mDataTransport->Close(NS_ERROR_ABORT);
  1.1496 +            mDataTransport = nullptr;
  1.1497 +            mDataStream = nullptr;
  1.1498 +        }
  1.1499 +    }
  1.1500 +
  1.1501 +    if (newDataConn) {
  1.1502 +        // now we know where to connect our data channel
  1.1503 +        nsCOMPtr<nsISocketTransportService> sts =
  1.1504 +            do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
  1.1505 +        if (!sts)
  1.1506 +            return FTP_ERROR;
  1.1507 +       
  1.1508 +        nsCOMPtr<nsISocketTransport> strans;
  1.1509 +
  1.1510 +        nsAutoCString host;
  1.1511 +        if (!IsIPAddrAny(&mServerAddress)) {
  1.1512 +            char buf[kIPv6CStrBufSize];
  1.1513 +            NetAddrToString(&mServerAddress, buf, sizeof(buf));
  1.1514 +            host.Assign(buf);
  1.1515 +        } else {
  1.1516 +            /*
  1.1517 +             * In case of SOCKS5 remote DNS resolving, the peer address
  1.1518 +             * fetched previously will be invalid (0.0.0.0): it is unknown
  1.1519 +             * to us. But we can pass on the original hostname to the
  1.1520 +             * connect for the data connection.
  1.1521 +             */
  1.1522 +            rv = mChannel->URI()->GetAsciiHost(host);
  1.1523 +            if (NS_FAILED(rv))
  1.1524 +                return FTP_ERROR;
  1.1525 +        }
  1.1526 +
  1.1527 +        rv =  sts->CreateTransport(nullptr, 0, host,
  1.1528 +                                   port, mChannel->ProxyInfo(),
  1.1529 +                                   getter_AddRefs(strans)); // the data socket
  1.1530 +        if (NS_FAILED(rv))
  1.1531 +            return FTP_ERROR;
  1.1532 +        mDataTransport = strans;
  1.1533 +
  1.1534 +        strans->SetQoSBits(gFtpHandler->GetDataQoSBits());
  1.1535 +        
  1.1536 +        LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port));
  1.1537 +        
  1.1538 +        // hook ourself up as a proxy for status notifications
  1.1539 +        rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread());
  1.1540 +        NS_ENSURE_SUCCESS(rv, FTP_ERROR);
  1.1541 +
  1.1542 +        if (mAction == PUT) {
  1.1543 +            NS_ASSERTION(!mRETRFailed, "Failed before uploading");
  1.1544 +
  1.1545 +            // nsIUploadChannel requires the upload stream to support ReadSegments.
  1.1546 +            // therefore, we can open an unbuffered socket output stream.
  1.1547 +            nsCOMPtr<nsIOutputStream> output;
  1.1548 +            rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
  1.1549 +                                                  0, 0, getter_AddRefs(output));
  1.1550 +            if (NS_FAILED(rv))
  1.1551 +                return FTP_ERROR;
  1.1552 +
  1.1553 +            // perform the data copy on the socket transport thread.  we do this
  1.1554 +            // because "output" is a socket output stream, so the result is that
  1.1555 +            // all work will be done on the socket transport thread.
  1.1556 +            nsCOMPtr<nsIEventTarget> stEventTarget =
  1.1557 +                do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
  1.1558 +            if (!stEventTarget)
  1.1559 +                return FTP_ERROR;
  1.1560 +            
  1.1561 +            nsCOMPtr<nsIAsyncStreamCopier> copier;
  1.1562 +            rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
  1.1563 +                                         mChannel->UploadStream(),
  1.1564 +                                         output,
  1.1565 +                                         stEventTarget,
  1.1566 +                                         true,   // upload stream is buffered
  1.1567 +                                         false); // output is NOT buffered
  1.1568 +            if (NS_FAILED(rv))
  1.1569 +                return FTP_ERROR;
  1.1570 +        
  1.1571 +            rv = copier->AsyncCopy(this, nullptr);
  1.1572 +            if (NS_FAILED(rv))
  1.1573 +                return FTP_ERROR;
  1.1574 +
  1.1575 +            // hold a reference to the copier so we can cancel it if necessary.
  1.1576 +            mUploadRequest = copier;
  1.1577 +
  1.1578 +            // update the current working directory before sending the STOR
  1.1579 +            // command.  this is needed since we might be reusing a control
  1.1580 +            // connection.
  1.1581 +            return FTP_S_CWD;
  1.1582 +        }
  1.1583 +
  1.1584 +        //
  1.1585 +        // else, we are reading from the data connection...
  1.1586 +        //
  1.1587 +
  1.1588 +        // open a buffered, asynchronous socket input stream
  1.1589 +        nsCOMPtr<nsIInputStream> input;
  1.1590 +        rv = mDataTransport->OpenInputStream(0,
  1.1591 +                                             nsIOService::gDefaultSegmentSize,
  1.1592 +                                             nsIOService::gDefaultSegmentCount,
  1.1593 +                                             getter_AddRefs(input));
  1.1594 +        NS_ENSURE_SUCCESS(rv, FTP_ERROR);
  1.1595 +        mDataStream = do_QueryInterface(input);
  1.1596 +    }
  1.1597 +
  1.1598 +    if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/')
  1.1599 +        return FTP_S_CWD;
  1.1600 +    return FTP_S_SIZE;
  1.1601 +}
  1.1602 +
  1.1603 +nsresult
  1.1604 +nsFtpState::S_feat() {
  1.1605 +    return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF));
  1.1606 +}
  1.1607 +
  1.1608 +FTP_STATE
  1.1609 +nsFtpState::R_feat() {
  1.1610 +    if (mResponseCode/100 == 2) {
  1.1611 +        if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) {
  1.1612 +            // This FTP server supports UTF-8 encoding
  1.1613 +            mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
  1.1614 +            mUseUTF8 = true;
  1.1615 +            return FTP_S_OPTS;
  1.1616 +        }
  1.1617 +    }
  1.1618 +
  1.1619 +    mUseUTF8 = false;
  1.1620 +    return FTP_S_PWD;
  1.1621 +}
  1.1622 +
  1.1623 +nsresult
  1.1624 +nsFtpState::S_opts() {
  1.1625 +     // This command is for compatibility of old FTP spec (IETF Draft)
  1.1626 +    return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF));
  1.1627 +}
  1.1628 +
  1.1629 +FTP_STATE
  1.1630 +nsFtpState::R_opts() {
  1.1631 +    // Ignore error code because "OPTS UTF8 ON" is for compatibility of
  1.1632 +    // FTP server using IETF draft
  1.1633 +    return FTP_S_PWD;
  1.1634 +}
  1.1635 +
  1.1636 +////////////////////////////////////////////////////////////////////////////////
  1.1637 +// nsIRequest methods:
  1.1638 +
  1.1639 +static inline
  1.1640 +uint32_t GetFtpTime()
  1.1641 +{
  1.1642 +    return uint32_t(PR_Now() / PR_USEC_PER_SEC);
  1.1643 +}
  1.1644 +
  1.1645 +uint32_t nsFtpState::mSessionStartTime = GetFtpTime();
  1.1646 +
  1.1647 +/* Is this cache entry valid to use for reading?
  1.1648 + * Since we make up an expiration time for ftp, use the following rules:
  1.1649 + * (see bug 103726)
  1.1650 + *
  1.1651 + * LOAD_FROM_CACHE                    : always use cache entry, even if expired
  1.1652 + * LOAD_BYPASS_CACHE                  : overwrite cache entry
  1.1653 + * LOAD_NORMAL|VALIDATE_ALWAYS        : overwrite cache entry
  1.1654 + * LOAD_NORMAL                        : honor expiration time
  1.1655 + * LOAD_NORMAL|VALIDATE_ONCE_PER_SESSION : overwrite cache entry if first access 
  1.1656 + *                                         this session, otherwise use cache entry 
  1.1657 + *                                         even if expired.
  1.1658 + * LOAD_NORMAL|VALIDATE_NEVER         : always use cache entry, even if expired
  1.1659 + *
  1.1660 + * Note that in theory we could use the mdtm time on the directory
  1.1661 + * In practice, the lack of a timezone plus the general lack of support for that
  1.1662 + * on directories means that its not worth it, I suspect. Revisit if we start
  1.1663 + * caching files - bbaetz
  1.1664 + */
  1.1665 +bool
  1.1666 +nsFtpState::CanReadCacheEntry()
  1.1667 +{
  1.1668 +    NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1.1669 +
  1.1670 +    nsCacheAccessMode access;
  1.1671 +    nsresult rv = mCacheEntry->GetAccessGranted(&access);
  1.1672 +    if (NS_FAILED(rv))
  1.1673 +        return false;
  1.1674 +    
  1.1675 +    // If I'm not granted read access, then I can't reuse it...
  1.1676 +    if (!(access & nsICache::ACCESS_READ))
  1.1677 +        return false;
  1.1678 +
  1.1679 +    if (mChannel->HasLoadFlag(nsIRequest::LOAD_FROM_CACHE))
  1.1680 +        return true;
  1.1681 +
  1.1682 +    if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE))
  1.1683 +        return false;
  1.1684 +    
  1.1685 +    if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ALWAYS))
  1.1686 +        return false;
  1.1687 +    
  1.1688 +    uint32_t time;
  1.1689 +    if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
  1.1690 +        rv = mCacheEntry->GetLastModified(&time);
  1.1691 +        if (NS_FAILED(rv))
  1.1692 +            return false;
  1.1693 +        return (mSessionStartTime > time);
  1.1694 +    }
  1.1695 +
  1.1696 +    if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_NEVER))
  1.1697 +        return true;
  1.1698 +
  1.1699 +    // OK, now we just check the expiration time as usual
  1.1700 +    rv = mCacheEntry->GetExpirationTime(&time);
  1.1701 +    if (NS_FAILED(rv))
  1.1702 +        return false;
  1.1703 +
  1.1704 +    return (GetFtpTime() <= time);
  1.1705 +}
  1.1706 +
  1.1707 +nsresult
  1.1708 +nsFtpState::InstallCacheListener()
  1.1709 +{
  1.1710 +    NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1.1711 +
  1.1712 +    nsCOMPtr<nsIOutputStream> out;
  1.1713 +    mCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
  1.1714 +    NS_ENSURE_STATE(out);
  1.1715 +
  1.1716 +    nsCOMPtr<nsIStreamListenerTee> tee =
  1.1717 +            do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
  1.1718 +    NS_ENSURE_STATE(tee);
  1.1719 +
  1.1720 +    nsresult rv = tee->Init(mChannel->StreamListener(), out, nullptr);
  1.1721 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1722 +
  1.1723 +    mChannel->SetStreamListener(tee);
  1.1724 +    return NS_OK;
  1.1725 +}
  1.1726 +
  1.1727 +nsresult
  1.1728 +nsFtpState::OpenCacheDataStream()
  1.1729 +{
  1.1730 +    NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1.1731 +
  1.1732 +    // Get a transport to the cached data...
  1.1733 +    nsCOMPtr<nsIInputStream> input;
  1.1734 +    mCacheEntry->OpenInputStream(0, getter_AddRefs(input));
  1.1735 +    NS_ENSURE_STATE(input);
  1.1736 +
  1.1737 +    nsCOMPtr<nsIStreamTransportService> sts =
  1.1738 +            do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
  1.1739 +    NS_ENSURE_STATE(sts);
  1.1740 +
  1.1741 +    nsCOMPtr<nsITransport> transport;
  1.1742 +    sts->CreateInputTransport(input, -1, -1, true,
  1.1743 +                              getter_AddRefs(transport));
  1.1744 +    NS_ENSURE_STATE(transport);
  1.1745 +
  1.1746 +    nsresult rv = transport->SetEventSink(this, NS_GetCurrentThread());
  1.1747 +    NS_ENSURE_SUCCESS(rv, rv);
  1.1748 +
  1.1749 +    // Open a non-blocking, buffered input stream...
  1.1750 +    nsCOMPtr<nsIInputStream> transportInput;
  1.1751 +    transport->OpenInputStream(0,
  1.1752 +                               nsIOService::gDefaultSegmentSize,
  1.1753 +                               nsIOService::gDefaultSegmentCount,
  1.1754 +                               getter_AddRefs(transportInput));
  1.1755 +    NS_ENSURE_STATE(transportInput);
  1.1756 +
  1.1757 +    mDataStream = do_QueryInterface(transportInput);
  1.1758 +    NS_ENSURE_STATE(mDataStream);
  1.1759 +
  1.1760 +    mDataTransport = transport;
  1.1761 +    return NS_OK;
  1.1762 +}
  1.1763 +
  1.1764 +nsresult
  1.1765 +nsFtpState::Init(nsFtpChannel *channel)
  1.1766 +{
  1.1767 +    // parameter validation
  1.1768 +    NS_ASSERTION(channel, "FTP: needs a channel");
  1.1769 +
  1.1770 +    mChannel = channel; // a straight ref ptr to the channel
  1.1771 +
  1.1772 +    // initialize counter for network metering
  1.1773 +    mCountRecv = 0;
  1.1774 +
  1.1775 +#ifdef MOZ_WIDGET_GONK
  1.1776 +    nsCOMPtr<nsINetworkInterface> activeNetwork;
  1.1777 +    GetActiveNetworkInterface(activeNetwork);
  1.1778 +    mActiveNetwork =
  1.1779 +        new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
  1.1780 +#endif
  1.1781 +
  1.1782 +    mKeepRunning = true;
  1.1783 +    mSuppliedEntityID = channel->EntityID();
  1.1784 +
  1.1785 +    if (channel->UploadStream())
  1.1786 +        mAction = PUT;
  1.1787 +
  1.1788 +    nsresult rv;
  1.1789 +    nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
  1.1790 +
  1.1791 +    nsAutoCString host;
  1.1792 +    if (url) {
  1.1793 +        rv = url->GetAsciiHost(host);
  1.1794 +    } else {
  1.1795 +        rv = mChannel->URI()->GetAsciiHost(host);
  1.1796 +    }
  1.1797 +    if (NS_FAILED(rv) || host.IsEmpty()) {
  1.1798 +        return NS_ERROR_MALFORMED_URI;
  1.1799 +    }
  1.1800 +
  1.1801 +    nsAutoCString path;
  1.1802 +    if (url) {
  1.1803 +        rv = url->GetFilePath(path);
  1.1804 +    } else {
  1.1805 +        rv = mChannel->URI()->GetPath(path);
  1.1806 +    }
  1.1807 +    if (NS_FAILED(rv))
  1.1808 +        return rv;
  1.1809 +
  1.1810 +    removeParamsFromPath(path);
  1.1811 +    
  1.1812 +    // FTP parameters such as type=i are ignored
  1.1813 +    if (url) {
  1.1814 +        url->SetFilePath(path);
  1.1815 +    } else {
  1.1816 +        mChannel->URI()->SetPath(path);
  1.1817 +    }
  1.1818 +        
  1.1819 +    // Skip leading slash
  1.1820 +    char *fwdPtr = path.BeginWriting();
  1.1821 +    if (!fwdPtr)
  1.1822 +        return NS_ERROR_OUT_OF_MEMORY;
  1.1823 +    if (*fwdPtr == '/')
  1.1824 +        fwdPtr++;
  1.1825 +    if (*fwdPtr != '\0') {
  1.1826 +        // now unescape it... %xx reduced inline to resulting character
  1.1827 +        int32_t len = NS_UnescapeURL(fwdPtr);
  1.1828 +        mPath.Assign(fwdPtr, len);
  1.1829 +
  1.1830 +#ifdef DEBUG
  1.1831 +        if (mPath.FindCharInSet(CRLF) >= 0)
  1.1832 +            NS_ERROR("NewURI() should've prevented this!!!");
  1.1833 +#endif
  1.1834 +    }
  1.1835 +
  1.1836 +    // pull any username and/or password out of the uri
  1.1837 +    nsAutoCString uname;
  1.1838 +    rv = mChannel->URI()->GetUsername(uname);
  1.1839 +    if (NS_FAILED(rv))
  1.1840 +        return rv;
  1.1841 +
  1.1842 +    if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
  1.1843 +        mAnonymous = false;
  1.1844 +        CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
  1.1845 +        
  1.1846 +        // return an error if we find a CR or LF in the username
  1.1847 +        if (uname.FindCharInSet(CRLF) >= 0)
  1.1848 +            return NS_ERROR_MALFORMED_URI;
  1.1849 +    }
  1.1850 +
  1.1851 +    nsAutoCString password;
  1.1852 +    rv = mChannel->URI()->GetPassword(password);
  1.1853 +    if (NS_FAILED(rv))
  1.1854 +        return rv;
  1.1855 +
  1.1856 +    CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
  1.1857 +
  1.1858 +    // return an error if we find a CR or LF in the password
  1.1859 +    if (mPassword.FindCharInSet(CRLF) >= 0)
  1.1860 +        return NS_ERROR_MALFORMED_URI;
  1.1861 +
  1.1862 +    // setup the connection cache key
  1.1863 +
  1.1864 +    int32_t port;
  1.1865 +    rv = mChannel->URI()->GetPort(&port);
  1.1866 +    if (NS_FAILED(rv))
  1.1867 +        return rv;
  1.1868 +
  1.1869 +    if (port > 0)
  1.1870 +        mPort = port;
  1.1871 +
  1.1872 +    // Lookup Proxy information asynchronously if it isn't already set
  1.1873 +    // on the channel and if we aren't configured explicitly to go directly
  1.1874 +    nsCOMPtr<nsIProtocolProxyService> pps =
  1.1875 +        do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
  1.1876 +
  1.1877 +    if (pps && !mChannel->ProxyInfo()) {
  1.1878 +        pps->AsyncResolve(mChannel, 0, this,
  1.1879 +                          getter_AddRefs(mProxyRequest));
  1.1880 +    }
  1.1881 +
  1.1882 +    return NS_OK;
  1.1883 +}
  1.1884 +
  1.1885 +void
  1.1886 +nsFtpState::Connect()
  1.1887 +{
  1.1888 +    mState = FTP_COMMAND_CONNECT;
  1.1889 +    mNextState = FTP_S_USER;
  1.1890 +
  1.1891 +    nsresult rv = Process();
  1.1892 +
  1.1893 +    // check for errors.
  1.1894 +    if (NS_FAILED(rv)) {
  1.1895 +        LOG(("FTP:Process() failed: %x\n", rv));
  1.1896 +        mInternalError = NS_ERROR_FAILURE;
  1.1897 +        mState = FTP_ERROR;
  1.1898 +        CloseWithStatus(mInternalError);
  1.1899 +    }
  1.1900 +}
  1.1901 +
  1.1902 +void
  1.1903 +nsFtpState::KillControlConnection()
  1.1904 +{
  1.1905 +    mControlReadCarryOverBuf.Truncate(0);
  1.1906 +
  1.1907 +    mAddressChecked = false;
  1.1908 +    mServerIsIPv6 = false;
  1.1909 +
  1.1910 +    // if everything went okay, save the connection. 
  1.1911 +    // FIX: need a better way to determine if we can cache the connections.
  1.1912 +    //      there are some errors which do not mean that we need to kill the connection
  1.1913 +    //      e.g. fnf.
  1.1914 +
  1.1915 +    if (!mControlConnection)
  1.1916 +        return;
  1.1917 +
  1.1918 +    // kill the reference to ourselves in the control connection.
  1.1919 +    mControlConnection->WaitData(nullptr);
  1.1920 +
  1.1921 +    if (NS_SUCCEEDED(mInternalError) &&
  1.1922 +        NS_SUCCEEDED(mControlStatus) &&
  1.1923 +        mControlConnection->IsAlive() &&
  1.1924 +        mCacheConnection) {
  1.1925 +
  1.1926 +        LOG_ALWAYS(("FTP:(%p) caching CC(%p)", this, mControlConnection.get()));
  1.1927 +
  1.1928 +        // Store connection persistent data
  1.1929 +        mControlConnection->mServerType = mServerType;           
  1.1930 +        mControlConnection->mPassword = mPassword;
  1.1931 +        mControlConnection->mPwd = mPwd;
  1.1932 +        mControlConnection->mUseUTF8 = mUseUTF8;
  1.1933 +        
  1.1934 +        nsresult rv = NS_OK;
  1.1935 +        // Don't cache controlconnection if anonymous (bug #473371)
  1.1936 +        if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
  1.1937 +            rv = gFtpHandler->InsertConnection(mChannel->URI(),
  1.1938 +                                               mControlConnection);
  1.1939 +        // Can't cache it?  Kill it then.  
  1.1940 +        mControlConnection->Disconnect(rv);
  1.1941 +    } else {
  1.1942 +        mControlConnection->Disconnect(NS_BINDING_ABORTED);
  1.1943 +    }     
  1.1944 +
  1.1945 +    mControlConnection = nullptr;
  1.1946 +}
  1.1947 +
  1.1948 +class nsFtpAsyncAlert : public nsRunnable
  1.1949 +{
  1.1950 +public:
  1.1951 +    nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
  1.1952 +        : mPrompter(aPrompter)
  1.1953 +        , mResponseMsg(aResponseMsg)
  1.1954 +    {
  1.1955 +        MOZ_COUNT_CTOR(nsFtpAsyncAlert);
  1.1956 +    }
  1.1957 +    virtual ~nsFtpAsyncAlert()
  1.1958 +    {
  1.1959 +        MOZ_COUNT_DTOR(nsFtpAsyncAlert);
  1.1960 +    }
  1.1961 +    NS_IMETHOD Run()
  1.1962 +    {
  1.1963 +        if (mPrompter) {
  1.1964 +            mPrompter->Alert(nullptr, mResponseMsg.get());
  1.1965 +        }
  1.1966 +        return NS_OK;
  1.1967 +    }
  1.1968 +private:
  1.1969 +    nsCOMPtr<nsIPrompt> mPrompter;
  1.1970 +    nsString mResponseMsg;
  1.1971 +};
  1.1972 +
  1.1973 +
  1.1974 +nsresult
  1.1975 +nsFtpState::StopProcessing()
  1.1976 +{
  1.1977 +    // Only do this function once.
  1.1978 +    if (!mKeepRunning)
  1.1979 +        return NS_OK;
  1.1980 +    mKeepRunning = false;
  1.1981 +
  1.1982 +    LOG_ALWAYS(("FTP:(%x) nsFtpState stopping", this));
  1.1983 +
  1.1984 +#ifdef DEBUG_dougt
  1.1985 +    printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get());
  1.1986 +#endif
  1.1987 +
  1.1988 +    if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
  1.1989 +        // check to see if the control status is bad.
  1.1990 +        // web shell wont throw an alert.  we better:
  1.1991 +
  1.1992 +        // XXX(darin): this code should not be dictating UI like this!
  1.1993 +        nsCOMPtr<nsIPrompt> prompter;
  1.1994 +        mChannel->GetCallback(prompter);
  1.1995 +        if (prompter) {
  1.1996 +            nsCOMPtr<nsIRunnable> alertEvent;
  1.1997 +            if (mUseUTF8) {
  1.1998 +                alertEvent = new nsFtpAsyncAlert(prompter,
  1.1999 +                    NS_ConvertUTF8toUTF16(mResponseMsg));
  1.2000 +            } else {
  1.2001 +                alertEvent = new nsFtpAsyncAlert(prompter,
  1.2002 +                    NS_ConvertASCIItoUTF16(mResponseMsg));
  1.2003 +            }
  1.2004 +            NS_DispatchToMainThread(alertEvent, NS_DISPATCH_NORMAL);
  1.2005 +        }
  1.2006 +    }
  1.2007 +    
  1.2008 +    nsresult broadcastErrorCode = mControlStatus;
  1.2009 +    if (NS_SUCCEEDED(broadcastErrorCode))
  1.2010 +        broadcastErrorCode = mInternalError;
  1.2011 +
  1.2012 +    mInternalError = broadcastErrorCode;
  1.2013 +
  1.2014 +    KillControlConnection();
  1.2015 +
  1.2016 +    // XXX This can fire before we are done loading data.  Is that a problem?
  1.2017 +    OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0);
  1.2018 +
  1.2019 +    if (NS_FAILED(broadcastErrorCode))
  1.2020 +        CloseWithStatus(broadcastErrorCode);
  1.2021 +
  1.2022 +    return NS_OK;
  1.2023 +}
  1.2024 +
  1.2025 +nsresult 
  1.2026 +nsFtpState::SendFTPCommand(const nsCSubstring& command)
  1.2027 +{
  1.2028 +    NS_ASSERTION(mControlConnection, "null control connection");        
  1.2029 +    
  1.2030 +    // we don't want to log the password:
  1.2031 +    nsAutoCString logcmd(command);
  1.2032 +    if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) 
  1.2033 +        logcmd = "PASS xxxxx";
  1.2034 +    
  1.2035 +    LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get()));
  1.2036 +
  1.2037 +    nsCOMPtr<nsIFTPEventSink> ftpSink;
  1.2038 +    mChannel->GetFTPEventSink(ftpSink);
  1.2039 +    if (ftpSink)
  1.2040 +        ftpSink->OnFTPControlLog(false, logcmd.get());
  1.2041 +    
  1.2042 +    if (mControlConnection)
  1.2043 +        return mControlConnection->Write(command);
  1.2044 +
  1.2045 +    return NS_ERROR_FAILURE;
  1.2046 +}
  1.2047 +
  1.2048 +// Convert a unix-style filespec to VMS format
  1.2049 +// /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
  1.2050 +// /foo/file.txt -> foo:[000000]file.txt
  1.2051 +void
  1.2052 +nsFtpState::ConvertFilespecToVMS(nsCString& fileString)
  1.2053 +{
  1.2054 +    int ntok=1;
  1.2055 +    char *t, *nextToken;
  1.2056 +    nsAutoCString fileStringCopy;
  1.2057 +
  1.2058 +    // Get a writeable copy we can strtok with.
  1.2059 +    fileStringCopy = fileString;
  1.2060 +    t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
  1.2061 +    if (t)
  1.2062 +        while (nsCRT::strtok(nextToken, "/", &nextToken))
  1.2063 +            ntok++; // count number of terms (tokens)
  1.2064 +    LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
  1.2065 +    LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
  1.2066 +
  1.2067 +    if (fileString.First() == '/') {
  1.2068 +        // absolute filespec
  1.2069 +        //   /        -> []
  1.2070 +        //   /a       -> a (doesn't really make much sense)
  1.2071 +        //   /a/b     -> a:[000000]b
  1.2072 +        //   /a/b/c   -> a:[b]c
  1.2073 +        //   /a/b/c/d -> a:[b.c]d
  1.2074 +        if (ntok == 1) {
  1.2075 +            if (fileString.Length() == 1) {
  1.2076 +                // Just a slash
  1.2077 +                fileString.Truncate();
  1.2078 +                fileString.AppendLiteral("[]");
  1.2079 +            } else {
  1.2080 +                // just copy the name part (drop the leading slash)
  1.2081 +                fileStringCopy = fileString;
  1.2082 +                fileString = Substring(fileStringCopy, 1,
  1.2083 +                                       fileStringCopy.Length()-1);
  1.2084 +            }
  1.2085 +        } else {
  1.2086 +            // Get another copy since the last one was written to.
  1.2087 +            fileStringCopy = fileString;
  1.2088 +            fileString.Truncate();
  1.2089 +            fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), 
  1.2090 +                              "/", &nextToken));
  1.2091 +            fileString.AppendLiteral(":[");
  1.2092 +            if (ntok > 2) {
  1.2093 +                for (int i=2; i<ntok; i++) {
  1.2094 +                    if (i > 2) fileString.Append('.');
  1.2095 +                    fileString.Append(nsCRT::strtok(nextToken,
  1.2096 +                                      "/", &nextToken));
  1.2097 +                }
  1.2098 +            } else {
  1.2099 +                fileString.AppendLiteral("000000");
  1.2100 +            }
  1.2101 +            fileString.Append(']');
  1.2102 +            fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
  1.2103 +        }
  1.2104 +    } else {
  1.2105 +       // relative filespec
  1.2106 +        //   a       -> a
  1.2107 +        //   a/b     -> [.a]b
  1.2108 +        //   a/b/c   -> [.a.b]c
  1.2109 +        if (ntok == 1) {
  1.2110 +            // no slashes, just use the name as is
  1.2111 +        } else {
  1.2112 +            // Get another copy since the last one was written to.
  1.2113 +            fileStringCopy = fileString;
  1.2114 +            fileString.Truncate();
  1.2115 +            fileString.AppendLiteral("[.");
  1.2116 +            fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
  1.2117 +                              "/", &nextToken));
  1.2118 +            if (ntok > 2) {
  1.2119 +                for (int i=2; i<ntok; i++) {
  1.2120 +                    fileString.Append('.');
  1.2121 +                    fileString.Append(nsCRT::strtok(nextToken,
  1.2122 +                                      "/", &nextToken));
  1.2123 +                }
  1.2124 +            }
  1.2125 +            fileString.Append(']');
  1.2126 +            fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
  1.2127 +        }
  1.2128 +    }
  1.2129 +    LOG(("FTP:(%x) ConvertFilespecToVMS   to: \"%s\"\n", this, fileString.get()));
  1.2130 +}
  1.2131 +
  1.2132 +// Convert a unix-style dirspec to VMS format
  1.2133 +// /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
  1.2134 +// /foo/fred -> foo:[fred]
  1.2135 +// /foo -> foo:[000000]
  1.2136 +// (null) -> (null)
  1.2137 +void
  1.2138 +nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec)
  1.2139 +{
  1.2140 +    LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
  1.2141 +    if (!dirSpec.IsEmpty()) {
  1.2142 +        if (dirSpec.Last() != '/')
  1.2143 +            dirSpec.Append('/');
  1.2144 +        // we can use the filespec routine if we make it look like a file name
  1.2145 +        dirSpec.Append('x');
  1.2146 +        ConvertFilespecToVMS(dirSpec);
  1.2147 +        dirSpec.Truncate(dirSpec.Length()-1);
  1.2148 +    }
  1.2149 +    LOG(("FTP:(%x) ConvertDirspecToVMS   to: \"%s\"\n", this, dirSpec.get()));
  1.2150 +}
  1.2151 +
  1.2152 +// Convert an absolute VMS style dirspec to UNIX format
  1.2153 +void
  1.2154 +nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
  1.2155 +{
  1.2156 +    LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
  1.2157 +    if (dirSpec.IsEmpty()) {
  1.2158 +        dirSpec.Insert('.', 0);
  1.2159 +    } else {
  1.2160 +        dirSpec.Insert('/', 0);
  1.2161 +        dirSpec.ReplaceSubstring(":[", "/");
  1.2162 +        dirSpec.ReplaceChar('.', '/');
  1.2163 +        dirSpec.ReplaceChar(']', '/');
  1.2164 +    }
  1.2165 +    LOG(("FTP:(%x) ConvertDirspecFromVMS   to: \"%s\"\n", this, dirSpec.get()));
  1.2166 +}
  1.2167 +
  1.2168 +//-----------------------------------------------------------------------------
  1.2169 +
  1.2170 +NS_IMETHODIMP
  1.2171 +nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status,
  1.2172 +                              uint64_t progress, uint64_t progressMax)
  1.2173 +{
  1.2174 +    // Mix signals from both the control and data connections.
  1.2175 +
  1.2176 +    // Ignore data transfer events on the control connection.
  1.2177 +    if (mControlConnection && transport == mControlConnection->Transport()) {
  1.2178 +        switch (status) {
  1.2179 +        case NS_NET_STATUS_RESOLVING_HOST:
  1.2180 +        case NS_NET_STATUS_RESOLVED_HOST:
  1.2181 +        case NS_NET_STATUS_CONNECTING_TO:
  1.2182 +        case NS_NET_STATUS_CONNECTED_TO:
  1.2183 +            break;
  1.2184 +        default:
  1.2185 +            return NS_OK;
  1.2186 +        }
  1.2187 +    }
  1.2188 +
  1.2189 +    // Ignore the progressMax value from the socket.  We know the true size of
  1.2190 +    // the file based on the response from our SIZE request. Additionally, only
  1.2191 +    // report the max progress based on where we started/resumed.
  1.2192 +    mChannel->OnTransportStatus(nullptr, status, progress,
  1.2193 +                                mFileSize - mChannel->StartPos());
  1.2194 +    return NS_OK;
  1.2195 +}
  1.2196 +
  1.2197 +//-----------------------------------------------------------------------------
  1.2198 +
  1.2199 +
  1.2200 +NS_IMETHODIMP
  1.2201 +nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
  1.2202 +                                  nsCacheAccessMode access,
  1.2203 +                                  nsresult status)
  1.2204 +{
  1.2205 +    // We may have been closed while we were waiting for this cache entry.
  1.2206 +    if (IsClosed())
  1.2207 +        return NS_OK;
  1.2208 +
  1.2209 +    if (NS_SUCCEEDED(status) && entry) {
  1.2210 +        mDoomCache = true;
  1.2211 +        mCacheEntry = entry;
  1.2212 +        if (CanReadCacheEntry() && ReadCacheEntry()) {
  1.2213 +            mState = FTP_READ_CACHE;
  1.2214 +            return NS_OK;
  1.2215 +        }
  1.2216 +    }
  1.2217 +
  1.2218 +    Connect();
  1.2219 +    return NS_OK;
  1.2220 +}
  1.2221 +
  1.2222 +//-----------------------------------------------------------------------------
  1.2223 +
  1.2224 +NS_IMETHODIMP
  1.2225 +nsFtpState::OnCacheEntryDoomed(nsresult status)
  1.2226 +{
  1.2227 +    return NS_ERROR_NOT_IMPLEMENTED;
  1.2228 +}
  1.2229 +
  1.2230 +//-----------------------------------------------------------------------------
  1.2231 +
  1.2232 +NS_IMETHODIMP
  1.2233 +nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context)
  1.2234 +{
  1.2235 +    mStorReplyReceived = false;
  1.2236 +    return NS_OK;
  1.2237 +}
  1.2238 +
  1.2239 +NS_IMETHODIMP
  1.2240 +nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
  1.2241 +                          nsresult status)
  1.2242 +{
  1.2243 +    mUploadRequest = nullptr;
  1.2244 +
  1.2245 +    // Close() will be called when reply to STOR command is received
  1.2246 +    // see bug #389394
  1.2247 +    if (!mStorReplyReceived)
  1.2248 +      return NS_OK;
  1.2249 +
  1.2250 +    // We're done uploading.  Let our consumer know that we're done.
  1.2251 +    Close();
  1.2252 +    return NS_OK;
  1.2253 +}
  1.2254 +
  1.2255 +//-----------------------------------------------------------------------------
  1.2256 +
  1.2257 +NS_IMETHODIMP
  1.2258 +nsFtpState::Available(uint64_t *result)
  1.2259 +{
  1.2260 +    if (mDataStream)
  1.2261 +        return mDataStream->Available(result);
  1.2262 +
  1.2263 +    return nsBaseContentStream::Available(result);
  1.2264 +}
  1.2265 +
  1.2266 +NS_IMETHODIMP
  1.2267 +nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure,
  1.2268 +                         uint32_t count, uint32_t *result)
  1.2269 +{
  1.2270 +    // Insert a thunk here so that the input stream passed to the writer is this
  1.2271 +    // input stream instead of mDataStream.
  1.2272 +
  1.2273 +    if (mDataStream) {
  1.2274 +        nsWriteSegmentThunk thunk = { this, writer, closure };
  1.2275 +        nsresult rv;
  1.2276 +        rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
  1.2277 +                                       result);
  1.2278 +        if (NS_SUCCEEDED(rv)) {
  1.2279 +            CountRecvBytes(*result);
  1.2280 +        }
  1.2281 +        return rv;
  1.2282 +    }
  1.2283 +
  1.2284 +    return nsBaseContentStream::ReadSegments(writer, closure, count, result);
  1.2285 +}
  1.2286 +
  1.2287 +nsresult
  1.2288 +nsFtpState::SaveNetworkStats(bool enforce)
  1.2289 +{
  1.2290 +#ifdef MOZ_WIDGET_GONK
  1.2291 +    // Obtain app id
  1.2292 +    uint32_t appId;
  1.2293 +    bool isInBrowser;
  1.2294 +    NS_GetAppInfo(mChannel, &appId, &isInBrowser);
  1.2295 +
  1.2296 +    // Check if active network and appid are valid.
  1.2297 +    if (!mActiveNetwork || appId == NECKO_NO_APP_ID) {
  1.2298 +        return NS_OK;
  1.2299 +    }
  1.2300 +
  1.2301 +    if (mCountRecv <= 0) {
  1.2302 +        // There is no traffic, no need to save.
  1.2303 +        return NS_OK;
  1.2304 +    }
  1.2305 +
  1.2306 +    // If |enforce| is false, the traffic amount is saved
  1.2307 +    // only when the total amount exceeds the predefined
  1.2308 +    // threshold.
  1.2309 +    if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) {
  1.2310 +        return NS_OK;
  1.2311 +    }
  1.2312 +
  1.2313 +    // Create the event to save the network statistics.
  1.2314 +    // the event is then dispathed to the main thread.
  1.2315 +    nsRefPtr<nsRunnable> event =
  1.2316 +        new SaveNetworkStatsEvent(appId, mActiveNetwork, mCountRecv, 0, false);
  1.2317 +    NS_DispatchToMainThread(event);
  1.2318 +
  1.2319 +    // Reset the counters after saving.
  1.2320 +    mCountRecv = 0;
  1.2321 +
  1.2322 +    return NS_OK;
  1.2323 +#else
  1.2324 +    return NS_ERROR_NOT_IMPLEMENTED;
  1.2325 +#endif
  1.2326 +}
  1.2327 +
  1.2328 +NS_IMETHODIMP
  1.2329 +nsFtpState::CloseWithStatus(nsresult status)
  1.2330 +{
  1.2331 +    LOG(("FTP:(%p) close [%x]\n", this, status));
  1.2332 +
  1.2333 +    // Shutdown the control connection processing if we are being closed with an
  1.2334 +    // error.  Note: This method may be called several times.
  1.2335 +    if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) {
  1.2336 +        if (NS_SUCCEEDED(mInternalError))
  1.2337 +            mInternalError = status;
  1.2338 +        StopProcessing();
  1.2339 +    }
  1.2340 +
  1.2341 +    if (mUploadRequest) {
  1.2342 +        mUploadRequest->Cancel(NS_ERROR_ABORT);
  1.2343 +        mUploadRequest = nullptr;
  1.2344 +    }
  1.2345 +
  1.2346 +    if (mDataTransport) {
  1.2347 +        // Save the network stats before data transport is closing.
  1.2348 +        SaveNetworkStats(true);
  1.2349 +
  1.2350 +        // Shutdown the data transport.
  1.2351 +        mDataTransport->Close(NS_ERROR_ABORT);
  1.2352 +        mDataTransport = nullptr;
  1.2353 +    }
  1.2354 +
  1.2355 +    mDataStream = nullptr;
  1.2356 +    if (mDoomCache && mCacheEntry)
  1.2357 +        mCacheEntry->AsyncDoom(nullptr);
  1.2358 +    mCacheEntry = nullptr;
  1.2359 +
  1.2360 +    return nsBaseContentStream::CloseWithStatus(status);
  1.2361 +}
  1.2362 +
  1.2363 +static nsresult
  1.2364 +CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel)
  1.2365 +{
  1.2366 +    nsresult rv;
  1.2367 +    nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
  1.2368 +    if (NS_FAILED(rv))
  1.2369 +        return rv;
  1.2370 +
  1.2371 +    nsCOMPtr<nsIProtocolHandler> handler;
  1.2372 +    rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
  1.2373 +    if (NS_FAILED(rv))
  1.2374 +        return rv;
  1.2375 +
  1.2376 +    nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
  1.2377 +    if (NS_FAILED(rv))
  1.2378 +        return rv;
  1.2379 +
  1.2380 +    nsCOMPtr<nsIURI> uri;
  1.2381 +    channel->GetURI(getter_AddRefs(uri));
  1.2382 +
  1.2383 +    return pph->NewProxiedChannel(uri, pi, 0, nullptr, newChannel);
  1.2384 +}
  1.2385 +
  1.2386 +NS_IMETHODIMP
  1.2387 +nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
  1.2388 +                             nsIProxyInfo *pi, nsresult status)
  1.2389 +{
  1.2390 +  mProxyRequest = nullptr;
  1.2391 +
  1.2392 +  // failed status code just implies DIRECT processing
  1.2393 +
  1.2394 +  if (NS_SUCCEEDED(status)) {
  1.2395 +    nsAutoCString type;
  1.2396 +    if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
  1.2397 +        // Proxy the FTP url via HTTP
  1.2398 +        // This would have been easier to just return a HTTP channel directly
  1.2399 +        // from nsIIOService::NewChannelFromURI(), but the proxy type cannot
  1.2400 +        // be reliabliy determined synchronously without jank due to pac, etc..
  1.2401 +        LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this));
  1.2402 +
  1.2403 +        nsCOMPtr<nsIChannel> newChannel;
  1.2404 +        if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi,
  1.2405 +                                                  getter_AddRefs(newChannel))) &&
  1.2406 +            NS_SUCCEEDED(mChannel->Redirect(newChannel,
  1.2407 +                                            nsIChannelEventSink::REDIRECT_INTERNAL,
  1.2408 +                                            true))) {
  1.2409 +            LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this));
  1.2410 +            return NS_OK;
  1.2411 +        }
  1.2412 +    }
  1.2413 +    else if (pi) {
  1.2414 +        // Proxy using the FTP protocol routed through a socks proxy
  1.2415 +        LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this));
  1.2416 +        mChannel->SetProxyInfo(pi);
  1.2417 +    }
  1.2418 +  }
  1.2419 +
  1.2420 +  if (mDeferredCallbackPending) {
  1.2421 +      mDeferredCallbackPending = false;
  1.2422 +      OnCallbackPending();
  1.2423 +  }
  1.2424 +  return NS_OK;
  1.2425 +}
  1.2426 +
  1.2427 +void
  1.2428 +nsFtpState::OnCallbackPending()
  1.2429 +{
  1.2430 +    // If this is the first call, then see if we could use the cache.  If we
  1.2431 +    // aren't going to read from (or write to) the cache, then just proceed to
  1.2432 +    // connect to the server.
  1.2433 +
  1.2434 +    if (mState == FTP_INIT) {
  1.2435 +        if (mProxyRequest) {
  1.2436 +            mDeferredCallbackPending = true;
  1.2437 +            return;
  1.2438 +        }
  1.2439 +
  1.2440 +        if (CheckCache()) {
  1.2441 +            mState = FTP_WAIT_CACHE;
  1.2442 +            return;
  1.2443 +        } 
  1.2444 +        if (mCacheEntry && CanReadCacheEntry() && ReadCacheEntry()) {
  1.2445 +            mState = FTP_READ_CACHE;
  1.2446 +            return;
  1.2447 +        }
  1.2448 +        Connect();
  1.2449 +    } else if (mDataStream) {
  1.2450 +        mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1.2451 +    }
  1.2452 +}
  1.2453 +
  1.2454 +bool
  1.2455 +nsFtpState::ReadCacheEntry()
  1.2456 +{
  1.2457 +    NS_ASSERTION(mCacheEntry, "should have a cache entry");
  1.2458 +
  1.2459 +    // make sure the channel knows wassup
  1.2460 +    SetContentType();
  1.2461 +
  1.2462 +    nsXPIDLCString serverType;
  1.2463 +    mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType));
  1.2464 +    nsAutoCString serverNum(serverType.get());
  1.2465 +    nsresult err;
  1.2466 +    mServerType = serverNum.ToInteger(&err);
  1.2467 +
  1.2468 +    nsXPIDLCString charset;
  1.2469 +    mCacheEntry->GetMetaDataElement("useUTF8", getter_Copies(charset));
  1.2470 +    const char *useUTF8 = charset.get();
  1.2471 +    if (useUTF8 && atoi(useUTF8) == 1)
  1.2472 +        mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
  1.2473 +    
  1.2474 +    mChannel->PushStreamConverter("text/ftp-dir",
  1.2475 +                                  APPLICATION_HTTP_INDEX_FORMAT);
  1.2476 +    
  1.2477 +    mChannel->SetEntityID(EmptyCString());
  1.2478 +
  1.2479 +    if (NS_FAILED(OpenCacheDataStream()))
  1.2480 +        return false;
  1.2481 +
  1.2482 +    if (mDataStream && HasPendingCallback())
  1.2483 +        mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1.2484 +
  1.2485 +    mDoomCache = false;
  1.2486 +    return true;
  1.2487 +}
  1.2488 +
  1.2489 +bool
  1.2490 +nsFtpState::CheckCache()
  1.2491 +{
  1.2492 +    // This function is responsible for setting mCacheEntry if there is a cache
  1.2493 +    // entry that we can use.  It returns true if we end up waiting for access
  1.2494 +    // to the cache.
  1.2495 +
  1.2496 +    // In some cases, we don't want to use the cache:
  1.2497 +    if (mChannel->UploadStream() || mChannel->ResumeRequested())
  1.2498 +        return false;
  1.2499 +
  1.2500 +    nsCOMPtr<nsICacheService> cache = do_GetService(NS_CACHESERVICE_CONTRACTID);
  1.2501 +    if (!cache)
  1.2502 +        return false;
  1.2503 +
  1.2504 +    bool isPrivate = NS_UsePrivateBrowsing(mChannel);
  1.2505 +    const char* sessionName = isPrivate ? "FTP-private" : "FTP";
  1.2506 +    nsCacheStoragePolicy policy =
  1.2507 +        isPrivate ? nsICache::STORE_IN_MEMORY : nsICache::STORE_ANYWHERE;
  1.2508 +    nsCOMPtr<nsICacheSession> session;
  1.2509 +    cache->CreateSession(sessionName,
  1.2510 +                         policy,
  1.2511 +                         nsICache::STREAM_BASED,
  1.2512 +                         getter_AddRefs(session));
  1.2513 +    if (!session)
  1.2514 +        return false;
  1.2515 +    session->SetDoomEntriesIfExpired(false);
  1.2516 +    session->SetIsPrivate(isPrivate);
  1.2517 +
  1.2518 +    // Set cache access requested:
  1.2519 +    nsCacheAccessMode accessReq;
  1.2520 +    if (NS_IsOffline()) {
  1.2521 +        accessReq = nsICache::ACCESS_READ; // can only read
  1.2522 +    } else if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) {
  1.2523 +        accessReq = nsICache::ACCESS_WRITE; // replace cache entry
  1.2524 +    } else {
  1.2525 +        accessReq = nsICache::ACCESS_READ_WRITE; // normal browsing
  1.2526 +    }
  1.2527 +
  1.2528 +    // Check to see if we are not allowed to write to the cache:
  1.2529 +    if (mChannel->HasLoadFlag(nsIRequest::INHIBIT_CACHING)) {
  1.2530 +        accessReq &= ~nsICache::ACCESS_WRITE;
  1.2531 +        if (accessReq == nsICache::ACCESS_NONE)
  1.2532 +            return false;
  1.2533 +    }
  1.2534 +
  1.2535 +    // Generate cache key (remove trailing #ref if any):
  1.2536 +    nsAutoCString key;
  1.2537 +    mChannel->URI()->GetAsciiSpec(key);
  1.2538 +    int32_t pos = key.RFindChar('#');
  1.2539 +    if (pos != kNotFound)
  1.2540 +        key.Truncate(pos);
  1.2541 +    NS_ENSURE_FALSE(key.IsEmpty(), false);
  1.2542 +
  1.2543 +    nsresult rv = session->AsyncOpenCacheEntry(key, accessReq, this, false);
  1.2544 +    return NS_SUCCEEDED(rv);
  1.2545 +
  1.2546 +}

mercurial