netwerk/protocol/ftp/nsFtpConnectionThread.cpp

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

     1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
     2 /* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include <ctype.h>
     9 #include "prprf.h"
    10 #include "prlog.h"
    11 #include "prtime.h"
    13 #include "nsIOService.h"
    14 #include "nsFTPChannel.h"
    15 #include "nsFtpConnectionThread.h"
    16 #include "nsFtpControlConnection.h"
    17 #include "nsFtpProtocolHandler.h"
    18 #include "netCore.h"
    19 #include "nsCRT.h"
    20 #include "nsEscape.h"
    21 #include "nsMimeTypes.h"
    22 #include "nsNetUtil.h"
    23 #include "nsThreadUtils.h"
    24 #include "nsStreamUtils.h"
    25 #include "nsICacheService.h"
    26 #include "nsIURL.h"
    27 #include "nsISocketTransport.h"
    28 #include "nsIStreamListenerTee.h"
    29 #include "nsIPrefService.h"
    30 #include "nsIPrefBranch.h"
    31 #include "nsIStringBundle.h"
    32 #include "nsAuthInformationHolder.h"
    33 #include "nsIProtocolProxyService.h"
    34 #include "nsICancelable.h"
    35 #include "nsICacheEntryDescriptor.h"
    36 #include "nsIOutputStream.h"
    37 #include "nsIPrompt.h"
    38 #include "nsIProtocolHandler.h"
    39 #include "nsIProxyInfo.h"
    40 #include "nsIRunnable.h"
    41 #include "nsISocketTransportService.h"
    42 #include "nsIURI.h"
    43 #include "nsICacheSession.h"
    45 #ifdef MOZ_WIDGET_GONK
    46 #include "NetStatistics.h"
    47 #endif
    49 #if defined(PR_LOGGING)
    50 extern PRLogModuleInfo* gFTPLog;
    51 #endif
    52 #define LOG(args)         PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
    53 #define LOG_ALWAYS(args)  PR_LOG(gFTPLog, PR_LOG_ALWAYS, args)
    55 using namespace mozilla::net;
    57 // remove FTP parameters (starting with ";") from the path
    58 static void
    59 removeParamsFromPath(nsCString& path)
    60 {
    61   int32_t index = path.FindChar(';');
    62   if (index >= 0) {
    63     path.SetLength(index);
    64   }
    65 }
    67 NS_IMPL_ISUPPORTS_INHERITED(nsFtpState,
    68                             nsBaseContentStream,
    69                             nsIInputStreamCallback, 
    70                             nsITransportEventSink,
    71                             nsICacheListener,
    72                             nsIRequestObserver,
    73                             nsIProtocolProxyCallback)
    75 nsFtpState::nsFtpState()
    76     : nsBaseContentStream(true)
    77     , mState(FTP_INIT)
    78     , mNextState(FTP_S_USER)
    79     , mKeepRunning(true)
    80     , mReceivedControlData(false)
    81     , mTryingCachedControl(false)
    82     , mRETRFailed(false)
    83     , mFileSize(UINT64_MAX)
    84     , mServerType(FTP_GENERIC_TYPE)
    85     , mAction(GET)
    86     , mAnonymous(true)
    87     , mRetryPass(false)
    88     , mStorReplyReceived(false)
    89     , mInternalError(NS_OK)
    90     , mReconnectAndLoginAgain(false)
    91     , mCacheConnection(true)
    92     , mPort(21)
    93     , mAddressChecked(false)
    94     , mServerIsIPv6(false)
    95     , mUseUTF8(false)
    96     , mControlStatus(NS_OK)
    97     , mDeferredCallbackPending(false)
    98 {
    99     LOG_ALWAYS(("FTP:(%x) nsFtpState created", this));
   101     // make sure handler stays around
   102     NS_ADDREF(gFtpHandler);
   103 }
   105 nsFtpState::~nsFtpState() 
   106 {
   107     LOG_ALWAYS(("FTP:(%x) nsFtpState destroyed", this));
   109     if (mProxyRequest)
   110         mProxyRequest->Cancel(NS_ERROR_FAILURE);
   112     // release reference to handler
   113     nsFtpProtocolHandler *handler = gFtpHandler;
   114     NS_RELEASE(handler);
   115 }
   117 // nsIInputStreamCallback implementation
   118 NS_IMETHODIMP
   119 nsFtpState::OnInputStreamReady(nsIAsyncInputStream *aInStream)
   120 {
   121     LOG(("FTP:(%p) data stream ready\n", this));
   123     // We are receiving a notification from our data stream, so just forward it
   124     // on to our stream callback.
   125     if (HasPendingCallback())
   126         DispatchCallbackSync();
   128     return NS_OK;
   129 }
   131 void
   132 nsFtpState::OnControlDataAvailable(const char *aData, uint32_t aDataLen)
   133 {
   134     LOG(("FTP:(%p) control data available [%u]\n", this, aDataLen));
   135     mControlConnection->WaitData(this);  // queue up another call
   137     if (!mReceivedControlData) {
   138         // parameter can be null cause the channel fills them in.
   139         OnTransportStatus(nullptr, NS_NET_STATUS_BEGIN_FTP_TRANSACTION, 0, 0);
   140         mReceivedControlData = true;
   141     }
   143     // Sometimes we can get two responses in the same packet, eg from LIST.
   144     // So we need to parse the response line by line
   146     nsCString buffer = mControlReadCarryOverBuf;
   148     // Clear the carryover buf - if we still don't have a line, then it will
   149     // be reappended below
   150     mControlReadCarryOverBuf.Truncate();
   152     buffer.Append(aData, aDataLen);
   154     const char* currLine = buffer.get();
   155     while (*currLine && mKeepRunning) {
   156         int32_t eolLength = strcspn(currLine, CRLF);
   157         int32_t currLineLength = strlen(currLine);
   159         // if currLine is empty or only contains CR or LF, then bail.  we can
   160         // sometimes get an ODA event with the full response line + CR without
   161         // the trailing LF.  the trailing LF might come in the next ODA event.
   162         // because we are happy enough to process a response line ending only
   163         // in CR, we need to take care to discard the extra LF (bug 191220).
   164         if (eolLength == 0 && currLineLength <= 1)
   165             break;
   167         if (eolLength == currLineLength) {
   168             mControlReadCarryOverBuf.Assign(currLine);
   169             break;
   170         }
   172         // Append the current segment, including the LF
   173         nsAutoCString line;
   174         int32_t crlfLength = 0;
   176         if ((currLineLength > eolLength) &&
   177             (currLine[eolLength] == nsCRT::CR) &&
   178             (currLine[eolLength+1] == nsCRT::LF)) {
   179             crlfLength = 2; // CR +LF 
   180         } else {
   181             crlfLength = 1; // + LF or CR
   182         }
   184         line.Assign(currLine, eolLength + crlfLength);
   186         // Does this start with a response code?
   187         bool startNum = (line.Length() >= 3 &&
   188                            isdigit(line[0]) &&
   189                            isdigit(line[1]) &&
   190                            isdigit(line[2]));
   192         if (mResponseMsg.IsEmpty()) {
   193             // If we get here, then we know that we have a complete line, and
   194             // that it is the first one
   196             NS_ASSERTION(line.Length() > 4 && startNum,
   197                          "Read buffer doesn't include response code");
   199             mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get());
   200         }
   202         mResponseMsg.Append(line);
   204         // This is the last line if its 3 numbers followed by a space
   205         if (startNum && line[3] == ' ') {
   206             // yup. last line, let's move on.
   207             if (mState == mNextState) {
   208                 NS_ERROR("ftp read state mixup");
   209                 mInternalError = NS_ERROR_FAILURE;
   210                 mState = FTP_ERROR;
   211             } else {
   212                 mState = mNextState;
   213             }
   215             nsCOMPtr<nsIFTPEventSink> ftpSink;
   216             mChannel->GetFTPEventSink(ftpSink);
   217             if (ftpSink)
   218                 ftpSink->OnFTPControlLog(true, mResponseMsg.get());
   220             nsresult rv = Process();
   221             mResponseMsg.Truncate();
   222             if (NS_FAILED(rv)) {
   223                 CloseWithStatus(rv);
   224                 return;
   225             }
   226         }
   228         currLine = currLine + eolLength + crlfLength;
   229     }
   230 }
   232 void
   233 nsFtpState::OnControlError(nsresult status)
   234 {
   235     NS_ASSERTION(NS_FAILED(status), "expecting error condition");
   237     LOG(("FTP:(%p) CC(%p) error [%x was-cached=%u]\n",
   238          this, mControlConnection.get(), status, mTryingCachedControl));
   240     mControlStatus = status;
   241     if (mReconnectAndLoginAgain && NS_SUCCEEDED(mInternalError)) {
   242         mReconnectAndLoginAgain = false;
   243         mAnonymous = false;
   244         mControlStatus = NS_OK;
   245         Connect();
   246     } else if (mTryingCachedControl && NS_SUCCEEDED(mInternalError)) {
   247         mTryingCachedControl = false;
   248         Connect();
   249     } else {
   250         CloseWithStatus(status);
   251     }
   252 }
   254 nsresult
   255 nsFtpState::EstablishControlConnection()
   256 {
   257     NS_ASSERTION(!mControlConnection, "we already have a control connection");
   259     nsresult rv;
   261     LOG(("FTP:(%x) trying cached control\n", this));
   263     // Look to see if we can use a cached control connection:
   264     nsFtpControlConnection *connection = nullptr;
   265     // Don't use cached control if anonymous (bug #473371)
   266     if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   267         gFtpHandler->RemoveConnection(mChannel->URI(), &connection);
   269     if (connection) {
   270         mControlConnection.swap(connection);
   271         if (mControlConnection->IsAlive())
   272         {
   273             // set stream listener of the control connection to be us.        
   274             mControlConnection->WaitData(this);
   276             // read cached variables into us. 
   277             mServerType = mControlConnection->mServerType;           
   278             mPassword   = mControlConnection->mPassword;
   279             mPwd        = mControlConnection->mPwd;
   280             mUseUTF8    = mControlConnection->mUseUTF8;
   281             mTryingCachedControl = true;
   283             // we have to set charset to connection if server supports utf-8
   284             if (mUseUTF8)
   285                 mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
   287             // we're already connected to this server, skip login.
   288             mState = FTP_S_PASV;
   289             mResponseCode = 530;  // assume the control connection was dropped.
   290             mControlStatus = NS_OK;
   291             mReceivedControlData = false;  // For this request, we have not.
   293             // if we succeed, return.  Otherwise, we need to create a transport
   294             rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
   295             if (NS_SUCCEEDED(rv))
   296                 return rv;
   297         }
   298         LOG(("FTP:(%p) cached CC(%p) is unusable\n", this,
   299             mControlConnection.get()));
   301         mControlConnection->WaitData(nullptr);
   302         mControlConnection = nullptr;
   303     }
   305     LOG(("FTP:(%p) creating CC\n", this));
   307     mState = FTP_READ_BUF;
   308     mNextState = FTP_S_USER;
   310     nsAutoCString host;
   311     rv = mChannel->URI()->GetAsciiHost(host);
   312     if (NS_FAILED(rv))
   313         return rv;
   315     mControlConnection = new nsFtpControlConnection(host, mPort);
   316     if (!mControlConnection)
   317         return NS_ERROR_OUT_OF_MEMORY;
   319     rv = mControlConnection->Connect(mChannel->ProxyInfo(), this);
   320     if (NS_FAILED(rv)) {
   321         LOG(("FTP:(%p) CC(%p) failed to connect [rv=%x]\n", this,
   322             mControlConnection.get(), rv));
   323         mControlConnection = nullptr;
   324         return rv;
   325     }
   327     return mControlConnection->WaitData(this);
   328 }
   330 void 
   331 nsFtpState::MoveToNextState(FTP_STATE nextState)
   332 {
   333     if (NS_FAILED(mInternalError)) {
   334         mState = FTP_ERROR;
   335         LOG(("FTP:(%x) FAILED (%x)\n", this, mInternalError));
   336     } else {
   337         mState = FTP_READ_BUF;
   338         mNextState = nextState;
   339     }  
   340 }
   342 nsresult
   343 nsFtpState::Process() 
   344 {
   345     nsresult    rv = NS_OK;
   346     bool        processingRead = true;
   348     while (mKeepRunning && processingRead) {
   349         switch (mState) {
   350           case FTP_COMMAND_CONNECT:
   351             KillControlConnection();
   352             LOG(("FTP:(%p) establishing CC", this));
   353             mInternalError = EstablishControlConnection();  // sets mState
   354             if (NS_FAILED(mInternalError)) {
   355                 mState = FTP_ERROR;
   356                 LOG(("FTP:(%p) FAILED\n", this));
   357             } else {
   358                 LOG(("FTP:(%p) SUCCEEDED\n", this));
   359             }
   360             break;
   362           case FTP_READ_BUF:
   363             LOG(("FTP:(%p) Waiting for CC(%p)\n", this,
   364                 mControlConnection.get()));
   365             processingRead = false;
   366             break;
   368           case FTP_ERROR: // xx needs more work to handle dropped control connection cases
   369             if ((mTryingCachedControl && mResponseCode == 530 &&
   370                 mInternalError == NS_ERROR_FTP_PASV) ||
   371                 (mResponseCode == 425 &&
   372                 mInternalError == NS_ERROR_FTP_PASV)) {
   373                 // The user was logged out during an pasv operation
   374                 // we want to restart this request with a new control
   375                 // channel.
   376                 mState = FTP_COMMAND_CONNECT;
   377             } else if (mResponseCode == 421 && 
   378                        mInternalError != NS_ERROR_FTP_LOGIN) {
   379                 // The command channel dropped for some reason.
   380                 // Fire it back up, unless we were trying to login
   381                 // in which case the server might just be telling us
   382                 // that the max number of users has been reached...
   383                 mState = FTP_COMMAND_CONNECT;
   384             } else if (mAnonymous && 
   385                        mInternalError == NS_ERROR_FTP_LOGIN) {
   386                 // If the login was anonymous, and it failed, try again with a username
   387                 // Don't reuse old control connection, see #386167
   388                 mAnonymous = false;
   389                 mState = FTP_COMMAND_CONNECT;
   390             } else {
   391                 LOG(("FTP:(%x) FTP_ERROR - calling StopProcessing\n", this));
   392                 rv = StopProcessing();
   393                 NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
   394                 processingRead = false;
   395             }
   396             break;
   398           case FTP_COMPLETE:
   399             LOG(("FTP:(%x) COMPLETE\n", this));
   400             rv = StopProcessing();
   401             NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
   402             processingRead = false;
   403             break;
   405 // USER           
   406           case FTP_S_USER:
   407             rv = S_user();
   409             if (NS_FAILED(rv))
   410                 mInternalError = NS_ERROR_FTP_LOGIN;
   412             MoveToNextState(FTP_R_USER);
   413             break;
   415           case FTP_R_USER:
   416             mState = R_user();
   418             if (FTP_ERROR == mState)
   419                 mInternalError = NS_ERROR_FTP_LOGIN;
   421             break;
   422 // PASS            
   423           case FTP_S_PASS:
   424             rv = S_pass();
   426             if (NS_FAILED(rv))
   427                 mInternalError = NS_ERROR_FTP_LOGIN;
   429             MoveToNextState(FTP_R_PASS);
   430             break;
   432           case FTP_R_PASS:
   433             mState = R_pass();
   435             if (FTP_ERROR == mState)
   436                 mInternalError = NS_ERROR_FTP_LOGIN;
   438             break;
   439 // ACCT            
   440           case FTP_S_ACCT:
   441             rv = S_acct();
   443             if (NS_FAILED(rv))
   444                 mInternalError = NS_ERROR_FTP_LOGIN;
   446             MoveToNextState(FTP_R_ACCT);
   447             break;
   449           case FTP_R_ACCT:
   450             mState = R_acct();
   452             if (FTP_ERROR == mState)
   453                 mInternalError = NS_ERROR_FTP_LOGIN;
   455             break;
   457 // SYST            
   458           case FTP_S_SYST:
   459             rv = S_syst();
   461             if (NS_FAILED(rv))
   462                 mInternalError = NS_ERROR_FTP_LOGIN;
   464             MoveToNextState(FTP_R_SYST);
   465             break;
   467           case FTP_R_SYST:
   468             mState = R_syst();
   470             if (FTP_ERROR == mState)
   471                 mInternalError = NS_ERROR_FTP_LOGIN;
   473             break;
   475 // TYPE            
   476           case FTP_S_TYPE:
   477             rv = S_type();
   479             if (NS_FAILED(rv))
   480                 mInternalError = rv;
   482             MoveToNextState(FTP_R_TYPE);
   483             break;
   485           case FTP_R_TYPE:
   486             mState = R_type();
   488             if (FTP_ERROR == mState)
   489                 mInternalError = NS_ERROR_FAILURE;
   491             break;
   492 // CWD            
   493           case FTP_S_CWD:
   494             rv = S_cwd();
   496             if (NS_FAILED(rv))
   497                 mInternalError = NS_ERROR_FTP_CWD;
   499             MoveToNextState(FTP_R_CWD);
   500             break;
   502           case FTP_R_CWD:
   503             mState = R_cwd();
   505             if (FTP_ERROR == mState)
   506                 mInternalError = NS_ERROR_FTP_CWD;
   507             break;
   509 // LIST
   510           case FTP_S_LIST:
   511             rv = S_list();
   513             if (rv == NS_ERROR_NOT_RESUMABLE) {
   514                 mInternalError = rv;
   515             } else if (NS_FAILED(rv)) {
   516                 mInternalError = NS_ERROR_FTP_CWD;
   517             }
   519             MoveToNextState(FTP_R_LIST);
   520             break;
   522           case FTP_R_LIST:        
   523             mState = R_list();
   525             if (FTP_ERROR == mState)
   526                 mInternalError = NS_ERROR_FAILURE;
   528             break;
   530 // SIZE            
   531           case FTP_S_SIZE:
   532             rv = S_size();
   534             if (NS_FAILED(rv))
   535                 mInternalError = rv;
   537             MoveToNextState(FTP_R_SIZE);
   538             break;
   540           case FTP_R_SIZE: 
   541             mState = R_size();
   543             if (FTP_ERROR == mState)
   544                 mInternalError = NS_ERROR_FAILURE;
   546             break;
   548 // REST        
   549           case FTP_S_REST:
   550             rv = S_rest();
   552             if (NS_FAILED(rv))
   553                 mInternalError = rv;
   555             MoveToNextState(FTP_R_REST);
   556             break;
   558           case FTP_R_REST:
   559             mState = R_rest();
   561             if (FTP_ERROR == mState)
   562                 mInternalError = NS_ERROR_FAILURE;
   564             break;
   566 // MDTM
   567           case FTP_S_MDTM:
   568             rv = S_mdtm();
   569             if (NS_FAILED(rv))
   570                 mInternalError = rv;
   571             MoveToNextState(FTP_R_MDTM);
   572             break;
   574           case FTP_R_MDTM:
   575             mState = R_mdtm();
   577             // Don't want to overwrite a more explicit status code
   578             if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   579                 mInternalError = NS_ERROR_FAILURE;
   581             break;
   583 // RETR        
   584           case FTP_S_RETR:
   585             rv = S_retr();
   587             if (NS_FAILED(rv)) 
   588                 mInternalError = rv;
   590             MoveToNextState(FTP_R_RETR);
   591             break;
   593           case FTP_R_RETR:
   595             mState = R_retr();
   597             if (FTP_ERROR == mState)
   598                 mInternalError = NS_ERROR_FAILURE;
   600             break;
   602 // STOR        
   603           case FTP_S_STOR:
   604             rv = S_stor();
   606             if (NS_FAILED(rv))
   607                 mInternalError = rv;
   609             MoveToNextState(FTP_R_STOR);
   610             break;
   612           case FTP_R_STOR:
   613             mState = R_stor();
   615             if (FTP_ERROR == mState)
   616                 mInternalError = NS_ERROR_FAILURE;
   618             break;
   620 // PASV        
   621           case FTP_S_PASV:
   622             rv = S_pasv();
   624             if (NS_FAILED(rv))
   625                 mInternalError = NS_ERROR_FTP_PASV;
   627             MoveToNextState(FTP_R_PASV);
   628             break;
   630           case FTP_R_PASV:
   631             mState = R_pasv();
   633             if (FTP_ERROR == mState) 
   634                 mInternalError = NS_ERROR_FTP_PASV;
   636             break;
   638 // PWD        
   639           case FTP_S_PWD:
   640             rv = S_pwd();
   642             if (NS_FAILED(rv))
   643                 mInternalError = NS_ERROR_FTP_PWD;
   645             MoveToNextState(FTP_R_PWD);
   646             break;
   648           case FTP_R_PWD:
   649             mState = R_pwd();
   651             if (FTP_ERROR == mState) 
   652                 mInternalError = NS_ERROR_FTP_PWD;
   654             break;
   656 // FEAT for RFC2640 support
   657           case FTP_S_FEAT:
   658             rv = S_feat();
   660             if (NS_FAILED(rv))
   661                 mInternalError = rv;
   663             MoveToNextState(FTP_R_FEAT);
   664             break;
   666           case FTP_R_FEAT:
   667             mState = R_feat();
   669             // Don't want to overwrite a more explicit status code
   670             if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   671                 mInternalError = NS_ERROR_FAILURE;
   672             break;
   674 // OPTS for some non-RFC2640-compliant servers support
   675           case FTP_S_OPTS:
   676             rv = S_opts();
   678             if (NS_FAILED(rv))
   679                 mInternalError = rv;
   681             MoveToNextState(FTP_R_OPTS);
   682             break;
   684           case FTP_R_OPTS:
   685             mState = R_opts();
   687             // Don't want to overwrite a more explicit status code
   688             if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
   689                 mInternalError = NS_ERROR_FAILURE;
   690             break;
   692           default:
   693             ;
   695         }
   696     }
   698     return rv;
   699 }
   701 ///////////////////////////////////
   702 // STATE METHODS
   703 ///////////////////////////////////
   704 nsresult
   705 nsFtpState::S_user() {
   706     // some servers on connect send us a 421 or 521.  (84525) (141784)
   707     if ((mResponseCode == 421) || (mResponseCode == 521))
   708         return NS_ERROR_FAILURE;
   710     nsresult rv;
   711     nsAutoCString usernameStr("USER ");
   713     mResponseMsg = "";
   715     if (mAnonymous) {
   716         mReconnectAndLoginAgain = true;
   717         usernameStr.AppendLiteral("anonymous");
   718     } else {
   719         mReconnectAndLoginAgain = false;
   720         if (mUsername.IsEmpty()) {
   722             // No prompt for anonymous requests (bug #473371)
   723             if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   724               return NS_ERROR_FAILURE;
   726             nsCOMPtr<nsIAuthPrompt2> prompter;
   727             NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
   728                                 getter_AddRefs(prompter));
   729             if (!prompter)
   730                 return NS_ERROR_NOT_INITIALIZED;
   732             nsRefPtr<nsAuthInformationHolder> info =
   733                 new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST,
   734                                             EmptyString(),
   735                                             EmptyCString());
   737             bool retval;
   738             rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
   739                                       info, &retval);
   741             // if the user canceled or didn't supply a username we want to fail
   742             if (NS_FAILED(rv) || !retval || info->User().IsEmpty())
   743                 return NS_ERROR_FAILURE;
   745             mUsername = info->User();
   746             mPassword = info->Password();
   747         }
   748         // XXX Is UTF-8 the best choice?
   749         AppendUTF16toUTF8(mUsername, usernameStr);
   750     }
   751     usernameStr.Append(CRLF);
   753     return SendFTPCommand(usernameStr);
   754 }
   756 FTP_STATE
   757 nsFtpState::R_user() {
   758     mReconnectAndLoginAgain = false;
   759     if (mResponseCode/100 == 3) {
   760         // send off the password
   761         return FTP_S_PASS;
   762     }
   763     if (mResponseCode/100 == 2) {
   764         // no password required, we're already logged in
   765         return FTP_S_SYST;
   766     }
   767     if (mResponseCode/100 == 5) {
   768         // problem logging in. typically this means the server
   769         // has reached it's user limit.
   770         return FTP_ERROR;
   771     }
   772     // LOGIN FAILED
   773     return FTP_ERROR;
   774 }
   777 nsresult
   778 nsFtpState::S_pass() {
   779     nsresult rv;
   780     nsAutoCString passwordStr("PASS ");
   782     mResponseMsg = "";
   784     if (mAnonymous) {
   785         if (!mPassword.IsEmpty()) {
   786             // XXX Is UTF-8 the best choice?
   787             AppendUTF16toUTF8(mPassword, passwordStr);
   788         } else {
   789             nsXPIDLCString anonPassword;
   790             bool useRealEmail = false;
   791             nsCOMPtr<nsIPrefBranch> prefs =
   792                     do_GetService(NS_PREFSERVICE_CONTRACTID);
   793             if (prefs) {
   794                 rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
   795                 if (NS_SUCCEEDED(rv) && useRealEmail) {
   796                     prefs->GetCharPref("network.ftp.anonymous_password",
   797                                        getter_Copies(anonPassword));
   798                 }
   799             }
   800             if (!anonPassword.IsEmpty()) {
   801                 passwordStr.AppendASCII(anonPassword);
   802             } else {
   803                 // We need to default to a valid email address - bug 101027
   804                 // example.com is reserved (rfc2606), so use that
   805                 passwordStr.AppendLiteral("mozilla@example.com");
   806             }
   807         }
   808     } else {
   809         if (mPassword.IsEmpty() || mRetryPass) {
   811             // No prompt for anonymous requests (bug #473371)
   812             if (mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
   813                 return NS_ERROR_FAILURE;
   815             nsCOMPtr<nsIAuthPrompt2> prompter;
   816             NS_QueryAuthPrompt2(static_cast<nsIChannel*>(mChannel),
   817                                 getter_AddRefs(prompter));
   818             if (!prompter)
   819                 return NS_ERROR_NOT_INITIALIZED;
   821             nsRefPtr<nsAuthInformationHolder> info =
   822                 new nsAuthInformationHolder(nsIAuthInformation::AUTH_HOST |
   823                                             nsIAuthInformation::ONLY_PASSWORD,
   824                                             EmptyString(),
   825                                             EmptyCString());
   827             info->SetUserInternal(mUsername);
   829             bool retval;
   830             rv = prompter->PromptAuth(mChannel, nsIAuthPrompt2::LEVEL_NONE,
   831                                       info, &retval);
   833             // we want to fail if the user canceled. Note here that if they want
   834             // a blank password, we will pass it along.
   835             if (NS_FAILED(rv) || !retval)
   836                 return NS_ERROR_FAILURE;
   838             mPassword = info->Password();
   839         }
   840         // XXX Is UTF-8 the best choice?
   841         AppendUTF16toUTF8(mPassword, passwordStr);
   842     }
   843     passwordStr.Append(CRLF);
   845     return SendFTPCommand(passwordStr);
   846 }
   848 FTP_STATE
   849 nsFtpState::R_pass() {
   850     if (mResponseCode/100 == 3) {
   851         // send account info
   852         return FTP_S_ACCT;
   853     }
   854     if (mResponseCode/100 == 2) {
   855         // logged in
   856         return FTP_S_SYST;
   857     }
   858     if (mResponseCode == 503) {
   859         // start over w/ the user command.
   860         // note: the password was successful, and it's stored in mPassword
   861         mRetryPass = false;
   862         return FTP_S_USER;
   863     }
   864     if (mResponseCode/100 == 5 || mResponseCode==421) {
   865         // There is no difference between a too-many-users error,
   866         // a wrong-password error, or any other sort of error
   868         if (!mAnonymous)
   869             mRetryPass = true;
   871         return FTP_ERROR;
   872     }
   873     // unexpected response code
   874     return FTP_ERROR;
   875 }
   877 nsresult
   878 nsFtpState::S_pwd() {
   879     return SendFTPCommand(NS_LITERAL_CSTRING("PWD" CRLF));
   880 }
   882 FTP_STATE
   883 nsFtpState::R_pwd() {
   884     // Error response to PWD command isn't fatal, but don't cache the connection
   885     // if CWD command is sent since correct mPwd is needed for further requests.
   886     if (mResponseCode/100 != 2)
   887         return FTP_S_TYPE;
   889     nsAutoCString respStr(mResponseMsg);
   890     int32_t pos = respStr.FindChar('"');
   891     if (pos > -1) {
   892         respStr.Cut(0, pos+1);
   893         pos = respStr.FindChar('"');
   894         if (pos > -1) {
   895             respStr.Truncate(pos);
   896             if (mServerType == FTP_VMS_TYPE)
   897                 ConvertDirspecFromVMS(respStr);
   898             if (respStr.IsEmpty() || respStr.Last() != '/')
   899                 respStr.Append('/');
   900             mPwd = respStr;
   901         }
   902     }
   903     return FTP_S_TYPE;
   904 }
   906 nsresult
   907 nsFtpState::S_syst() {
   908     return SendFTPCommand(NS_LITERAL_CSTRING("SYST" CRLF));
   909 }
   911 FTP_STATE
   912 nsFtpState::R_syst() {
   913     if (mResponseCode/100 == 2) {
   914         if (( mResponseMsg.Find("L8") > -1) || 
   915             ( mResponseMsg.Find("UNIX") > -1) || 
   916             ( mResponseMsg.Find("BSD") > -1) ||
   917             ( mResponseMsg.Find("MACOS Peter's Server") > -1) ||
   918             ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
   919             ( mResponseMsg.Find("MVS") > -1) ||
   920             ( mResponseMsg.Find("OS/390") > -1) ||
   921             ( mResponseMsg.Find("OS/400") > -1)) {
   922             mServerType = FTP_UNIX_TYPE;
   923         } else if (( mResponseMsg.Find("WIN32", true) > -1) ||
   924                    ( mResponseMsg.Find("windows", true) > -1)) {
   925             mServerType = FTP_NT_TYPE;
   926         } else if (mResponseMsg.Find("OS/2", true) > -1) {
   927             mServerType = FTP_OS2_TYPE;
   928         } else if (mResponseMsg.Find("VMS", true) > -1) {
   929             mServerType = FTP_VMS_TYPE;
   930         } else {
   931             NS_ERROR("Server type list format unrecognized.");
   932             // Guessing causes crashes.
   933             // (Of course, the parsing code should be more robust...)
   934             nsCOMPtr<nsIStringBundleService> bundleService =
   935                 do_GetService(NS_STRINGBUNDLE_CONTRACTID);
   936             if (!bundleService)
   937                 return FTP_ERROR;
   939             nsCOMPtr<nsIStringBundle> bundle;
   940             nsresult rv = bundleService->CreateBundle(NECKO_MSGS_URL,
   941                                                       getter_AddRefs(bundle));
   942             if (NS_FAILED(rv))
   943                 return FTP_ERROR;
   945             char16_t* ucs2Response = ToNewUnicode(mResponseMsg);
   946             const char16_t *formatStrings[1] = { ucs2Response };
   947             NS_NAMED_LITERAL_STRING(name, "UnsupportedFTPServer");
   949             nsXPIDLString formattedString;
   950             rv = bundle->FormatStringFromName(name.get(), formatStrings, 1,
   951                                               getter_Copies(formattedString));
   952             nsMemory::Free(ucs2Response);
   953             if (NS_FAILED(rv))
   954                 return FTP_ERROR;
   956             // TODO(darin): this code should not be dictating UI like this!
   957             nsCOMPtr<nsIPrompt> prompter;
   958             mChannel->GetCallback(prompter);
   959             if (prompter)
   960                 prompter->Alert(nullptr, formattedString.get());
   962             // since we just alerted the user, clear mResponseMsg,
   963             // which is displayed to the user.
   964             mResponseMsg = "";
   965             return FTP_ERROR;
   966         }
   968         return FTP_S_FEAT;
   969     }
   971     if (mResponseCode/100 == 5) {   
   972         // server didn't like the SYST command.  Probably (500, 501, 502)
   973         // No clue.  We will just hope it is UNIX type server.
   974         mServerType = FTP_UNIX_TYPE;
   976         return FTP_S_FEAT;
   977     }
   978     return FTP_ERROR;
   979 }
   981 nsresult
   982 nsFtpState::S_acct() {
   983     return SendFTPCommand(NS_LITERAL_CSTRING("ACCT noaccount" CRLF));
   984 }
   986 FTP_STATE
   987 nsFtpState::R_acct() {
   988     if (mResponseCode/100 == 2)
   989         return FTP_S_SYST;
   991     return FTP_ERROR;
   992 }
   994 nsresult
   995 nsFtpState::S_type() {
   996     return SendFTPCommand(NS_LITERAL_CSTRING("TYPE I" CRLF));
   997 }
   999 FTP_STATE
  1000 nsFtpState::R_type() {
  1001     if (mResponseCode/100 != 2) 
  1002         return FTP_ERROR;
  1004     return FTP_S_PASV;
  1007 nsresult
  1008 nsFtpState::S_cwd() {
  1009     // Don't cache the connection if PWD command failed
  1010     if (mPwd.IsEmpty())
  1011         mCacheConnection = false;
  1013     nsAutoCString cwdStr;
  1014     if (mAction != PUT)
  1015         cwdStr = mPath;
  1016     if (cwdStr.IsEmpty() || cwdStr.First() != '/')
  1017         cwdStr.Insert(mPwd,0);
  1018     if (mServerType == FTP_VMS_TYPE)
  1019         ConvertDirspecToVMS(cwdStr);
  1020     cwdStr.Insert("CWD ",0);
  1021     cwdStr.Append(CRLF);
  1023     return SendFTPCommand(cwdStr);
  1026 FTP_STATE
  1027 nsFtpState::R_cwd() {
  1028     if (mResponseCode/100 == 2) {
  1029         if (mAction == PUT)
  1030             return FTP_S_STOR;
  1032         return FTP_S_LIST;
  1035     return FTP_ERROR;
  1038 nsresult
  1039 nsFtpState::S_size() {
  1040     nsAutoCString sizeBuf(mPath);
  1041     if (sizeBuf.IsEmpty() || sizeBuf.First() != '/')
  1042         sizeBuf.Insert(mPwd,0);
  1043     if (mServerType == FTP_VMS_TYPE)
  1044         ConvertFilespecToVMS(sizeBuf);
  1045     sizeBuf.Insert("SIZE ",0);
  1046     sizeBuf.Append(CRLF);
  1048     return SendFTPCommand(sizeBuf);
  1051 FTP_STATE
  1052 nsFtpState::R_size() {
  1053     if (mResponseCode/100 == 2) {
  1054         PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
  1055         mChannel->SetContentLength(mFileSize);
  1058     // We may want to be able to resume this
  1059     return FTP_S_MDTM;
  1062 nsresult
  1063 nsFtpState::S_mdtm() {
  1064     nsAutoCString mdtmBuf(mPath);
  1065     if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/')
  1066         mdtmBuf.Insert(mPwd,0);
  1067     if (mServerType == FTP_VMS_TYPE)
  1068         ConvertFilespecToVMS(mdtmBuf);
  1069     mdtmBuf.Insert("MDTM ",0);
  1070     mdtmBuf.Append(CRLF);
  1072     return SendFTPCommand(mdtmBuf);
  1075 FTP_STATE
  1076 nsFtpState::R_mdtm() {
  1077     if (mResponseCode == 213) {
  1078         mResponseMsg.Cut(0,4);
  1079         mResponseMsg.Trim(" \t\r\n");
  1080         // yyyymmddhhmmss
  1081         if (mResponseMsg.Length() != 14) {
  1082             NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
  1083         } else {
  1084             mModTime = mResponseMsg;
  1086             // Save lastModified time for downloaded files.
  1087             nsAutoCString timeString;
  1088             nsresult error;
  1089             PRExplodedTime exTime;
  1091             mResponseMsg.Mid(timeString, 0, 4);
  1092             exTime.tm_year  = timeString.ToInteger(&error);
  1093             mResponseMsg.Mid(timeString, 4, 2);
  1094             exTime.tm_month = timeString.ToInteger(&error) - 1; //january = 0
  1095             mResponseMsg.Mid(timeString, 6, 2);
  1096             exTime.tm_mday  = timeString.ToInteger(&error);
  1097             mResponseMsg.Mid(timeString, 8, 2);
  1098             exTime.tm_hour  = timeString.ToInteger(&error);
  1099             mResponseMsg.Mid(timeString, 10, 2);
  1100             exTime.tm_min   = timeString.ToInteger(&error);
  1101             mResponseMsg.Mid(timeString, 12, 2);
  1102             exTime.tm_sec   = timeString.ToInteger(&error);
  1103             exTime.tm_usec  = 0;
  1105             exTime.tm_params.tp_gmt_offset = 0;
  1106             exTime.tm_params.tp_dst_offset = 0;
  1108             PR_NormalizeTime(&exTime, PR_GMTParameters);
  1109             exTime.tm_params = PR_LocalTimeParameters(&exTime);
  1111             PRTime time = PR_ImplodeTime(&exTime);
  1112             (void)mChannel->SetLastModifiedTime(time);
  1116     nsCString entityID;
  1117     entityID.Truncate();
  1118     entityID.AppendInt(int64_t(mFileSize));
  1119     entityID.Append('/');
  1120     entityID.Append(mModTime);
  1121     mChannel->SetEntityID(entityID);
  1123     // We weren't asked to resume
  1124     if (!mChannel->ResumeRequested())
  1125         return FTP_S_RETR;
  1127     //if (our entityID == supplied one (if any))
  1128     if (mSuppliedEntityID.IsEmpty() || entityID.Equals(mSuppliedEntityID))
  1129         return FTP_S_REST;
  1131     mInternalError = NS_ERROR_ENTITY_CHANGED;
  1132     mResponseMsg.Truncate();
  1133     return FTP_ERROR;
  1136 nsresult 
  1137 nsFtpState::SetContentType()
  1139     // FTP directory URLs don't always end in a slash.  Make sure they do.
  1140     // This check needs to be here rather than a more obvious place
  1141     // (e.g. LIST command processing) so that it ensures the terminating
  1142     // slash is appended for the new request case, as well as the case
  1143     // where the URL is being loaded from the cache.
  1145     if (!mPath.IsEmpty() && mPath.Last() != '/') {
  1146         nsCOMPtr<nsIURL> url = (do_QueryInterface(mChannel->URI()));
  1147         nsAutoCString filePath;
  1148         if(NS_SUCCEEDED(url->GetFilePath(filePath))) {
  1149             filePath.Append('/');
  1150             url->SetFilePath(filePath);
  1153     return mChannel->SetContentType(
  1154         NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
  1157 nsresult
  1158 nsFtpState::S_list() {
  1159     nsresult rv = SetContentType();
  1160     if (NS_FAILED(rv)) 
  1161         // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
  1162         // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
  1163         return (nsresult)FTP_ERROR;
  1165     rv = mChannel->PushStreamConverter("text/ftp-dir",
  1166                                        APPLICATION_HTTP_INDEX_FORMAT);
  1167     if (NS_FAILED(rv)) {
  1168         // clear mResponseMsg which is displayed to the user.
  1169         // TODO: we should probably set this to something meaningful.
  1170         mResponseMsg = "";
  1171         return rv;
  1174     if (mCacheEntry) {
  1175         // save off the server type if we are caching.
  1176         nsAutoCString serverType;
  1177         serverType.AppendInt(mServerType);
  1178         mCacheEntry->SetMetaDataElement("servertype", serverType.get());
  1180         nsAutoCString useUTF8;
  1181         useUTF8.AppendInt(mUseUTF8);
  1182         mCacheEntry->SetMetaDataElement("useUTF8", useUTF8.get());
  1184         // open cache entry for writing, and configure it to receive data.
  1185         if (NS_FAILED(InstallCacheListener())) {
  1186             mCacheEntry->AsyncDoom(nullptr);
  1187             mCacheEntry = nullptr;
  1191     // dir listings aren't resumable
  1192     NS_ENSURE_TRUE(!mChannel->ResumeRequested(), NS_ERROR_NOT_RESUMABLE);
  1194     mChannel->SetEntityID(EmptyCString());
  1196     const char *listString;
  1197     if (mServerType == FTP_VMS_TYPE) {
  1198         listString = "LIST *.*;0" CRLF;
  1199     } else {
  1200         listString = "LIST" CRLF;
  1203     return SendFTPCommand(nsDependentCString(listString));
  1206 FTP_STATE
  1207 nsFtpState::R_list() {
  1208     if (mResponseCode/100 == 1) {
  1209         // OK, time to start reading from the data connection.
  1210         if (mDataStream && HasPendingCallback())
  1211             mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1212         return FTP_READ_BUF;
  1215     if (mResponseCode/100 == 2) {
  1216         //(DONE)
  1217         mNextState = FTP_COMPLETE;
  1218         mDoomCache = false;
  1219         return FTP_COMPLETE;
  1221     return FTP_ERROR;
  1224 nsresult
  1225 nsFtpState::S_retr() {
  1226     nsAutoCString retrStr(mPath);
  1227     if (retrStr.IsEmpty() || retrStr.First() != '/')
  1228         retrStr.Insert(mPwd,0);
  1229     if (mServerType == FTP_VMS_TYPE)
  1230         ConvertFilespecToVMS(retrStr);
  1231     retrStr.Insert("RETR ",0);
  1232     retrStr.Append(CRLF);
  1233     return SendFTPCommand(retrStr);
  1236 FTP_STATE
  1237 nsFtpState::R_retr() {
  1238     if (mResponseCode/100 == 2) {
  1239         //(DONE)
  1240         mNextState = FTP_COMPLETE;
  1241         return FTP_COMPLETE;
  1244     if (mResponseCode/100 == 1) {
  1245         // We're going to grab a file, not a directory. So we need to clear
  1246         // any cache entry, otherwise we'll have problems reading it later.
  1247         // See bug 122548
  1248         if (mCacheEntry) {
  1249             (void)mCacheEntry->AsyncDoom(nullptr);
  1250             mCacheEntry = nullptr;
  1252         if (mDataStream && HasPendingCallback())
  1253             mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  1254         return FTP_READ_BUF;
  1257     // These error codes are related to problems with the connection.  
  1258     // If we encounter any at this point, do not try CWD and abort.
  1259     if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
  1260         return FTP_ERROR;
  1262     if (mResponseCode/100 == 5) {
  1263         mRETRFailed = true;
  1264         return FTP_S_PASV;
  1267     return FTP_S_CWD;
  1271 nsresult
  1272 nsFtpState::S_rest() {
  1274     nsAutoCString restString("REST ");
  1275     // The int64_t cast is needed to avoid ambiguity
  1276     restString.AppendInt(int64_t(mChannel->StartPos()), 10);
  1277     restString.Append(CRLF);
  1279     return SendFTPCommand(restString);
  1282 FTP_STATE
  1283 nsFtpState::R_rest() {
  1284     if (mResponseCode/100 == 4) {
  1285         // If REST fails, then we can't resume
  1286         mChannel->SetEntityID(EmptyCString());
  1288         mInternalError = NS_ERROR_NOT_RESUMABLE;
  1289         mResponseMsg.Truncate();
  1291         return FTP_ERROR;
  1294     return FTP_S_RETR; 
  1297 nsresult
  1298 nsFtpState::S_stor() {
  1299     NS_ENSURE_STATE(mChannel->UploadStream());
  1301     NS_ASSERTION(mAction == PUT, "Wrong state to be here");
  1303     nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
  1304     NS_ASSERTION(url, "I thought you were a nsStandardURL");
  1306     nsAutoCString storStr;
  1307     url->GetFilePath(storStr);
  1308     NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
  1310     // kill the first slash since we want to be relative to CWD.
  1311     if (storStr.First() == '/')
  1312         storStr.Cut(0,1);
  1314     if (mServerType == FTP_VMS_TYPE)
  1315         ConvertFilespecToVMS(storStr);
  1317     NS_UnescapeURL(storStr);
  1318     storStr.Insert("STOR ",0);
  1319     storStr.Append(CRLF);
  1321     return SendFTPCommand(storStr);
  1324 FTP_STATE
  1325 nsFtpState::R_stor() {
  1326     if (mResponseCode/100 == 2) {
  1327         //(DONE)
  1328         mNextState = FTP_COMPLETE;
  1329         mStorReplyReceived = true;
  1331         // Call Close() if it was not called in nsFtpState::OnStoprequest()
  1332         if (!mUploadRequest && !IsClosed())
  1333             Close();
  1335         return FTP_COMPLETE;
  1338     if (mResponseCode/100 == 1) {
  1339         LOG(("FTP:(%x) writing on DT\n", this));
  1340         return FTP_READ_BUF;
  1343    mStorReplyReceived = true;
  1344    return FTP_ERROR;
  1348 nsresult
  1349 nsFtpState::S_pasv() {
  1350     if (!mAddressChecked) {
  1351         // Find socket address
  1352         mAddressChecked = true;
  1353         mServerAddress.raw.family = AF_INET;
  1354         mServerAddress.inet.ip = htonl(INADDR_ANY);
  1355         mServerAddress.inet.port = htons(0);
  1357         nsITransport *controlSocket = mControlConnection->Transport();
  1358         if (!controlSocket)
  1359             // XXX Invalid cast of FTP_STATE to nsresult -- FTP_ERROR has
  1360             // value < 0x80000000 and will pass NS_SUCCEEDED() (bug 778109)
  1361             return (nsresult)FTP_ERROR;
  1363         nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket);
  1364         if (sTrans) {
  1365             nsresult rv = sTrans->GetPeerAddr(&mServerAddress);
  1366             if (NS_SUCCEEDED(rv)) {
  1367                 if (!IsIPAddrAny(&mServerAddress))
  1368                     mServerIsIPv6 = (mServerAddress.raw.family == AF_INET6) &&
  1369                                     !IsIPAddrV4Mapped(&mServerAddress);
  1370                 else {
  1371                     /*
  1372                      * In case of SOCKS5 remote DNS resolution, we do
  1373                      * not know the remote IP address. Still, if it is
  1374                      * an IPV6 host, then the external address of the
  1375                      * socks server should also be IPv6, and this is the
  1376                      * self address of the transport.
  1377                      */
  1378                     NetAddr selfAddress;
  1379                     rv = sTrans->GetSelfAddr(&selfAddress);
  1380                     if (NS_SUCCEEDED(rv))
  1381                         mServerIsIPv6 = (selfAddress.raw.family == AF_INET6) &&
  1382                                         !IsIPAddrV4Mapped(&selfAddress);
  1388     const char *string;
  1389     if (mServerIsIPv6) {
  1390         string = "EPSV" CRLF;
  1391     } else {
  1392         string = "PASV" CRLF;
  1395     return SendFTPCommand(nsDependentCString(string));
  1399 FTP_STATE
  1400 nsFtpState::R_pasv() {
  1401     if (mResponseCode/100 != 2)
  1402         return FTP_ERROR;
  1404     nsresult rv;
  1405     int32_t port;
  1407     nsAutoCString responseCopy(mResponseMsg);
  1408     char *response = responseCopy.BeginWriting();
  1410     char *ptr = response;
  1412     // Make sure to ignore the address in the PASV response (bug 370559)
  1414     if (mServerIsIPv6) {
  1415         // The returned string is of the form
  1416         // text (|||ppp|)
  1417         // Where '|' can be any single character
  1418         char delim;
  1419         while (*ptr && *ptr != '(')
  1420             ptr++;
  1421         if (*ptr++ != '(')
  1422             return FTP_ERROR;
  1423         delim = *ptr++;
  1424         if (!delim || *ptr++ != delim ||
  1425                       *ptr++ != delim || 
  1426                       *ptr < '0' || *ptr > '9')
  1427             return FTP_ERROR;
  1428         port = 0;
  1429         do {
  1430             port = port * 10 + *ptr++ - '0';
  1431         } while (*ptr >= '0' && *ptr <= '9');
  1432         if (*ptr++ != delim || *ptr != ')')
  1433             return FTP_ERROR;
  1434     } else {
  1435         // The returned address string can be of the form
  1436         // (xxx,xxx,xxx,xxx,ppp,ppp) or
  1437         //  xxx,xxx,xxx,xxx,ppp,ppp (without parens)
  1438         int32_t h0, h1, h2, h3, p0, p1;
  1440         uint32_t fields = 0;
  1441         // First try with parens
  1442         while (*ptr && *ptr != '(')
  1443             ++ptr;
  1444         if (*ptr) {
  1445             ++ptr;
  1446             fields = PR_sscanf(ptr, 
  1447                                "%ld,%ld,%ld,%ld,%ld,%ld",
  1448                                &h0, &h1, &h2, &h3, &p0, &p1);
  1450         if (!*ptr || fields < 6) {
  1451             // OK, lets try w/o parens
  1452             ptr = response;
  1453             while (*ptr && *ptr != ',')
  1454                 ++ptr;
  1455             if (*ptr) {
  1456                 // backup to the start of the digits
  1457                 do {
  1458                     ptr--;
  1459                 } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
  1460                 ptr++; // get back onto the numbers
  1461                 fields = PR_sscanf(ptr, 
  1462                                    "%ld,%ld,%ld,%ld,%ld,%ld",
  1463                                    &h0, &h1, &h2, &h3, &p0, &p1);
  1467         NS_ASSERTION(fields == 6, "Can't parse PASV response");
  1468         if (fields < 6)
  1469             return FTP_ERROR;
  1471         port = ((int32_t) (p0<<8)) + p1;
  1474     bool newDataConn = true;
  1475     if (mDataTransport) {
  1476         // Reuse this connection only if its still alive, and the port
  1477         // is the same
  1478         nsCOMPtr<nsISocketTransport> strans = do_QueryInterface(mDataTransport);
  1479         if (strans) {
  1480             int32_t oldPort;
  1481             nsresult rv = strans->GetPort(&oldPort);
  1482             if (NS_SUCCEEDED(rv)) {
  1483                 if (oldPort == port) {
  1484                     bool isAlive;
  1485                     if (NS_SUCCEEDED(strans->IsAlive(&isAlive)) && isAlive)
  1486                         newDataConn = false;
  1491         if (newDataConn) {
  1492             mDataTransport->Close(NS_ERROR_ABORT);
  1493             mDataTransport = nullptr;
  1494             mDataStream = nullptr;
  1498     if (newDataConn) {
  1499         // now we know where to connect our data channel
  1500         nsCOMPtr<nsISocketTransportService> sts =
  1501             do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
  1502         if (!sts)
  1503             return FTP_ERROR;
  1505         nsCOMPtr<nsISocketTransport> strans;
  1507         nsAutoCString host;
  1508         if (!IsIPAddrAny(&mServerAddress)) {
  1509             char buf[kIPv6CStrBufSize];
  1510             NetAddrToString(&mServerAddress, buf, sizeof(buf));
  1511             host.Assign(buf);
  1512         } else {
  1513             /*
  1514              * In case of SOCKS5 remote DNS resolving, the peer address
  1515              * fetched previously will be invalid (0.0.0.0): it is unknown
  1516              * to us. But we can pass on the original hostname to the
  1517              * connect for the data connection.
  1518              */
  1519             rv = mChannel->URI()->GetAsciiHost(host);
  1520             if (NS_FAILED(rv))
  1521                 return FTP_ERROR;
  1524         rv =  sts->CreateTransport(nullptr, 0, host,
  1525                                    port, mChannel->ProxyInfo(),
  1526                                    getter_AddRefs(strans)); // the data socket
  1527         if (NS_FAILED(rv))
  1528             return FTP_ERROR;
  1529         mDataTransport = strans;
  1531         strans->SetQoSBits(gFtpHandler->GetDataQoSBits());
  1533         LOG(("FTP:(%x) created DT (%s:%x)\n", this, host.get(), port));
  1535         // hook ourself up as a proxy for status notifications
  1536         rv = mDataTransport->SetEventSink(this, NS_GetCurrentThread());
  1537         NS_ENSURE_SUCCESS(rv, FTP_ERROR);
  1539         if (mAction == PUT) {
  1540             NS_ASSERTION(!mRETRFailed, "Failed before uploading");
  1542             // nsIUploadChannel requires the upload stream to support ReadSegments.
  1543             // therefore, we can open an unbuffered socket output stream.
  1544             nsCOMPtr<nsIOutputStream> output;
  1545             rv = mDataTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED,
  1546                                                   0, 0, getter_AddRefs(output));
  1547             if (NS_FAILED(rv))
  1548                 return FTP_ERROR;
  1550             // perform the data copy on the socket transport thread.  we do this
  1551             // because "output" is a socket output stream, so the result is that
  1552             // all work will be done on the socket transport thread.
  1553             nsCOMPtr<nsIEventTarget> stEventTarget =
  1554                 do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID);
  1555             if (!stEventTarget)
  1556                 return FTP_ERROR;
  1558             nsCOMPtr<nsIAsyncStreamCopier> copier;
  1559             rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
  1560                                          mChannel->UploadStream(),
  1561                                          output,
  1562                                          stEventTarget,
  1563                                          true,   // upload stream is buffered
  1564                                          false); // output is NOT buffered
  1565             if (NS_FAILED(rv))
  1566                 return FTP_ERROR;
  1568             rv = copier->AsyncCopy(this, nullptr);
  1569             if (NS_FAILED(rv))
  1570                 return FTP_ERROR;
  1572             // hold a reference to the copier so we can cancel it if necessary.
  1573             mUploadRequest = copier;
  1575             // update the current working directory before sending the STOR
  1576             // command.  this is needed since we might be reusing a control
  1577             // connection.
  1578             return FTP_S_CWD;
  1581         //
  1582         // else, we are reading from the data connection...
  1583         //
  1585         // open a buffered, asynchronous socket input stream
  1586         nsCOMPtr<nsIInputStream> input;
  1587         rv = mDataTransport->OpenInputStream(0,
  1588                                              nsIOService::gDefaultSegmentSize,
  1589                                              nsIOService::gDefaultSegmentCount,
  1590                                              getter_AddRefs(input));
  1591         NS_ENSURE_SUCCESS(rv, FTP_ERROR);
  1592         mDataStream = do_QueryInterface(input);
  1595     if (mRETRFailed || mPath.IsEmpty() || mPath.Last() == '/')
  1596         return FTP_S_CWD;
  1597     return FTP_S_SIZE;
  1600 nsresult
  1601 nsFtpState::S_feat() {
  1602     return SendFTPCommand(NS_LITERAL_CSTRING("FEAT" CRLF));
  1605 FTP_STATE
  1606 nsFtpState::R_feat() {
  1607     if (mResponseCode/100 == 2) {
  1608         if (mResponseMsg.Find(NS_LITERAL_CSTRING(CRLF " UTF8" CRLF), true) > -1) {
  1609             // This FTP server supports UTF-8 encoding
  1610             mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
  1611             mUseUTF8 = true;
  1612             return FTP_S_OPTS;
  1616     mUseUTF8 = false;
  1617     return FTP_S_PWD;
  1620 nsresult
  1621 nsFtpState::S_opts() {
  1622      // This command is for compatibility of old FTP spec (IETF Draft)
  1623     return SendFTPCommand(NS_LITERAL_CSTRING("OPTS UTF8 ON" CRLF));
  1626 FTP_STATE
  1627 nsFtpState::R_opts() {
  1628     // Ignore error code because "OPTS UTF8 ON" is for compatibility of
  1629     // FTP server using IETF draft
  1630     return FTP_S_PWD;
  1633 ////////////////////////////////////////////////////////////////////////////////
  1634 // nsIRequest methods:
  1636 static inline
  1637 uint32_t GetFtpTime()
  1639     return uint32_t(PR_Now() / PR_USEC_PER_SEC);
  1642 uint32_t nsFtpState::mSessionStartTime = GetFtpTime();
  1644 /* Is this cache entry valid to use for reading?
  1645  * Since we make up an expiration time for ftp, use the following rules:
  1646  * (see bug 103726)
  1648  * LOAD_FROM_CACHE                    : always use cache entry, even if expired
  1649  * LOAD_BYPASS_CACHE                  : overwrite cache entry
  1650  * LOAD_NORMAL|VALIDATE_ALWAYS        : overwrite cache entry
  1651  * LOAD_NORMAL                        : honor expiration time
  1652  * LOAD_NORMAL|VALIDATE_ONCE_PER_SESSION : overwrite cache entry if first access 
  1653  *                                         this session, otherwise use cache entry 
  1654  *                                         even if expired.
  1655  * LOAD_NORMAL|VALIDATE_NEVER         : always use cache entry, even if expired
  1657  * Note that in theory we could use the mdtm time on the directory
  1658  * In practice, the lack of a timezone plus the general lack of support for that
  1659  * on directories means that its not worth it, I suspect. Revisit if we start
  1660  * caching files - bbaetz
  1661  */
  1662 bool
  1663 nsFtpState::CanReadCacheEntry()
  1665     NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1667     nsCacheAccessMode access;
  1668     nsresult rv = mCacheEntry->GetAccessGranted(&access);
  1669     if (NS_FAILED(rv))
  1670         return false;
  1672     // If I'm not granted read access, then I can't reuse it...
  1673     if (!(access & nsICache::ACCESS_READ))
  1674         return false;
  1676     if (mChannel->HasLoadFlag(nsIRequest::LOAD_FROM_CACHE))
  1677         return true;
  1679     if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE))
  1680         return false;
  1682     if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ALWAYS))
  1683         return false;
  1685     uint32_t time;
  1686     if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_ONCE_PER_SESSION)) {
  1687         rv = mCacheEntry->GetLastModified(&time);
  1688         if (NS_FAILED(rv))
  1689             return false;
  1690         return (mSessionStartTime > time);
  1693     if (mChannel->HasLoadFlag(nsIRequest::VALIDATE_NEVER))
  1694         return true;
  1696     // OK, now we just check the expiration time as usual
  1697     rv = mCacheEntry->GetExpirationTime(&time);
  1698     if (NS_FAILED(rv))
  1699         return false;
  1701     return (GetFtpTime() <= time);
  1704 nsresult
  1705 nsFtpState::InstallCacheListener()
  1707     NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1709     nsCOMPtr<nsIOutputStream> out;
  1710     mCacheEntry->OpenOutputStream(0, getter_AddRefs(out));
  1711     NS_ENSURE_STATE(out);
  1713     nsCOMPtr<nsIStreamListenerTee> tee =
  1714             do_CreateInstance(NS_STREAMLISTENERTEE_CONTRACTID);
  1715     NS_ENSURE_STATE(tee);
  1717     nsresult rv = tee->Init(mChannel->StreamListener(), out, nullptr);
  1718     NS_ENSURE_SUCCESS(rv, rv);
  1720     mChannel->SetStreamListener(tee);
  1721     return NS_OK;
  1724 nsresult
  1725 nsFtpState::OpenCacheDataStream()
  1727     NS_ASSERTION(mCacheEntry, "must have a cache entry");
  1729     // Get a transport to the cached data...
  1730     nsCOMPtr<nsIInputStream> input;
  1731     mCacheEntry->OpenInputStream(0, getter_AddRefs(input));
  1732     NS_ENSURE_STATE(input);
  1734     nsCOMPtr<nsIStreamTransportService> sts =
  1735             do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
  1736     NS_ENSURE_STATE(sts);
  1738     nsCOMPtr<nsITransport> transport;
  1739     sts->CreateInputTransport(input, -1, -1, true,
  1740                               getter_AddRefs(transport));
  1741     NS_ENSURE_STATE(transport);
  1743     nsresult rv = transport->SetEventSink(this, NS_GetCurrentThread());
  1744     NS_ENSURE_SUCCESS(rv, rv);
  1746     // Open a non-blocking, buffered input stream...
  1747     nsCOMPtr<nsIInputStream> transportInput;
  1748     transport->OpenInputStream(0,
  1749                                nsIOService::gDefaultSegmentSize,
  1750                                nsIOService::gDefaultSegmentCount,
  1751                                getter_AddRefs(transportInput));
  1752     NS_ENSURE_STATE(transportInput);
  1754     mDataStream = do_QueryInterface(transportInput);
  1755     NS_ENSURE_STATE(mDataStream);
  1757     mDataTransport = transport;
  1758     return NS_OK;
  1761 nsresult
  1762 nsFtpState::Init(nsFtpChannel *channel)
  1764     // parameter validation
  1765     NS_ASSERTION(channel, "FTP: needs a channel");
  1767     mChannel = channel; // a straight ref ptr to the channel
  1769     // initialize counter for network metering
  1770     mCountRecv = 0;
  1772 #ifdef MOZ_WIDGET_GONK
  1773     nsCOMPtr<nsINetworkInterface> activeNetwork;
  1774     GetActiveNetworkInterface(activeNetwork);
  1775     mActiveNetwork =
  1776         new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
  1777 #endif
  1779     mKeepRunning = true;
  1780     mSuppliedEntityID = channel->EntityID();
  1782     if (channel->UploadStream())
  1783         mAction = PUT;
  1785     nsresult rv;
  1786     nsCOMPtr<nsIURL> url = do_QueryInterface(mChannel->URI());
  1788     nsAutoCString host;
  1789     if (url) {
  1790         rv = url->GetAsciiHost(host);
  1791     } else {
  1792         rv = mChannel->URI()->GetAsciiHost(host);
  1794     if (NS_FAILED(rv) || host.IsEmpty()) {
  1795         return NS_ERROR_MALFORMED_URI;
  1798     nsAutoCString path;
  1799     if (url) {
  1800         rv = url->GetFilePath(path);
  1801     } else {
  1802         rv = mChannel->URI()->GetPath(path);
  1804     if (NS_FAILED(rv))
  1805         return rv;
  1807     removeParamsFromPath(path);
  1809     // FTP parameters such as type=i are ignored
  1810     if (url) {
  1811         url->SetFilePath(path);
  1812     } else {
  1813         mChannel->URI()->SetPath(path);
  1816     // Skip leading slash
  1817     char *fwdPtr = path.BeginWriting();
  1818     if (!fwdPtr)
  1819         return NS_ERROR_OUT_OF_MEMORY;
  1820     if (*fwdPtr == '/')
  1821         fwdPtr++;
  1822     if (*fwdPtr != '\0') {
  1823         // now unescape it... %xx reduced inline to resulting character
  1824         int32_t len = NS_UnescapeURL(fwdPtr);
  1825         mPath.Assign(fwdPtr, len);
  1827 #ifdef DEBUG
  1828         if (mPath.FindCharInSet(CRLF) >= 0)
  1829             NS_ERROR("NewURI() should've prevented this!!!");
  1830 #endif
  1833     // pull any username and/or password out of the uri
  1834     nsAutoCString uname;
  1835     rv = mChannel->URI()->GetUsername(uname);
  1836     if (NS_FAILED(rv))
  1837         return rv;
  1839     if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
  1840         mAnonymous = false;
  1841         CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
  1843         // return an error if we find a CR or LF in the username
  1844         if (uname.FindCharInSet(CRLF) >= 0)
  1845             return NS_ERROR_MALFORMED_URI;
  1848     nsAutoCString password;
  1849     rv = mChannel->URI()->GetPassword(password);
  1850     if (NS_FAILED(rv))
  1851         return rv;
  1853     CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
  1855     // return an error if we find a CR or LF in the password
  1856     if (mPassword.FindCharInSet(CRLF) >= 0)
  1857         return NS_ERROR_MALFORMED_URI;
  1859     // setup the connection cache key
  1861     int32_t port;
  1862     rv = mChannel->URI()->GetPort(&port);
  1863     if (NS_FAILED(rv))
  1864         return rv;
  1866     if (port > 0)
  1867         mPort = port;
  1869     // Lookup Proxy information asynchronously if it isn't already set
  1870     // on the channel and if we aren't configured explicitly to go directly
  1871     nsCOMPtr<nsIProtocolProxyService> pps =
  1872         do_GetService(NS_PROTOCOLPROXYSERVICE_CONTRACTID);
  1874     if (pps && !mChannel->ProxyInfo()) {
  1875         pps->AsyncResolve(mChannel, 0, this,
  1876                           getter_AddRefs(mProxyRequest));
  1879     return NS_OK;
  1882 void
  1883 nsFtpState::Connect()
  1885     mState = FTP_COMMAND_CONNECT;
  1886     mNextState = FTP_S_USER;
  1888     nsresult rv = Process();
  1890     // check for errors.
  1891     if (NS_FAILED(rv)) {
  1892         LOG(("FTP:Process() failed: %x\n", rv));
  1893         mInternalError = NS_ERROR_FAILURE;
  1894         mState = FTP_ERROR;
  1895         CloseWithStatus(mInternalError);
  1899 void
  1900 nsFtpState::KillControlConnection()
  1902     mControlReadCarryOverBuf.Truncate(0);
  1904     mAddressChecked = false;
  1905     mServerIsIPv6 = false;
  1907     // if everything went okay, save the connection. 
  1908     // FIX: need a better way to determine if we can cache the connections.
  1909     //      there are some errors which do not mean that we need to kill the connection
  1910     //      e.g. fnf.
  1912     if (!mControlConnection)
  1913         return;
  1915     // kill the reference to ourselves in the control connection.
  1916     mControlConnection->WaitData(nullptr);
  1918     if (NS_SUCCEEDED(mInternalError) &&
  1919         NS_SUCCEEDED(mControlStatus) &&
  1920         mControlConnection->IsAlive() &&
  1921         mCacheConnection) {
  1923         LOG_ALWAYS(("FTP:(%p) caching CC(%p)", this, mControlConnection.get()));
  1925         // Store connection persistent data
  1926         mControlConnection->mServerType = mServerType;           
  1927         mControlConnection->mPassword = mPassword;
  1928         mControlConnection->mPwd = mPwd;
  1929         mControlConnection->mUseUTF8 = mUseUTF8;
  1931         nsresult rv = NS_OK;
  1932         // Don't cache controlconnection if anonymous (bug #473371)
  1933         if (!mChannel->HasLoadFlag(nsIRequest::LOAD_ANONYMOUS))
  1934             rv = gFtpHandler->InsertConnection(mChannel->URI(),
  1935                                                mControlConnection);
  1936         // Can't cache it?  Kill it then.  
  1937         mControlConnection->Disconnect(rv);
  1938     } else {
  1939         mControlConnection->Disconnect(NS_BINDING_ABORTED);
  1942     mControlConnection = nullptr;
  1945 class nsFtpAsyncAlert : public nsRunnable
  1947 public:
  1948     nsFtpAsyncAlert(nsIPrompt *aPrompter, nsString aResponseMsg)
  1949         : mPrompter(aPrompter)
  1950         , mResponseMsg(aResponseMsg)
  1952         MOZ_COUNT_CTOR(nsFtpAsyncAlert);
  1954     virtual ~nsFtpAsyncAlert()
  1956         MOZ_COUNT_DTOR(nsFtpAsyncAlert);
  1958     NS_IMETHOD Run()
  1960         if (mPrompter) {
  1961             mPrompter->Alert(nullptr, mResponseMsg.get());
  1963         return NS_OK;
  1965 private:
  1966     nsCOMPtr<nsIPrompt> mPrompter;
  1967     nsString mResponseMsg;
  1968 };
  1971 nsresult
  1972 nsFtpState::StopProcessing()
  1974     // Only do this function once.
  1975     if (!mKeepRunning)
  1976         return NS_OK;
  1977     mKeepRunning = false;
  1979     LOG_ALWAYS(("FTP:(%x) nsFtpState stopping", this));
  1981 #ifdef DEBUG_dougt
  1982     printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get());
  1983 #endif
  1985     if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty()) {
  1986         // check to see if the control status is bad.
  1987         // web shell wont throw an alert.  we better:
  1989         // XXX(darin): this code should not be dictating UI like this!
  1990         nsCOMPtr<nsIPrompt> prompter;
  1991         mChannel->GetCallback(prompter);
  1992         if (prompter) {
  1993             nsCOMPtr<nsIRunnable> alertEvent;
  1994             if (mUseUTF8) {
  1995                 alertEvent = new nsFtpAsyncAlert(prompter,
  1996                     NS_ConvertUTF8toUTF16(mResponseMsg));
  1997             } else {
  1998                 alertEvent = new nsFtpAsyncAlert(prompter,
  1999                     NS_ConvertASCIItoUTF16(mResponseMsg));
  2001             NS_DispatchToMainThread(alertEvent, NS_DISPATCH_NORMAL);
  2005     nsresult broadcastErrorCode = mControlStatus;
  2006     if (NS_SUCCEEDED(broadcastErrorCode))
  2007         broadcastErrorCode = mInternalError;
  2009     mInternalError = broadcastErrorCode;
  2011     KillControlConnection();
  2013     // XXX This can fire before we are done loading data.  Is that a problem?
  2014     OnTransportStatus(nullptr, NS_NET_STATUS_END_FTP_TRANSACTION, 0, 0);
  2016     if (NS_FAILED(broadcastErrorCode))
  2017         CloseWithStatus(broadcastErrorCode);
  2019     return NS_OK;
  2022 nsresult 
  2023 nsFtpState::SendFTPCommand(const nsCSubstring& command)
  2025     NS_ASSERTION(mControlConnection, "null control connection");        
  2027     // we don't want to log the password:
  2028     nsAutoCString logcmd(command);
  2029     if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) 
  2030         logcmd = "PASS xxxxx";
  2032     LOG(("FTP:(%x) writing \"%s\"\n", this, logcmd.get()));
  2034     nsCOMPtr<nsIFTPEventSink> ftpSink;
  2035     mChannel->GetFTPEventSink(ftpSink);
  2036     if (ftpSink)
  2037         ftpSink->OnFTPControlLog(false, logcmd.get());
  2039     if (mControlConnection)
  2040         return mControlConnection->Write(command);
  2042     return NS_ERROR_FAILURE;
  2045 // Convert a unix-style filespec to VMS format
  2046 // /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
  2047 // /foo/file.txt -> foo:[000000]file.txt
  2048 void
  2049 nsFtpState::ConvertFilespecToVMS(nsCString& fileString)
  2051     int ntok=1;
  2052     char *t, *nextToken;
  2053     nsAutoCString fileStringCopy;
  2055     // Get a writeable copy we can strtok with.
  2056     fileStringCopy = fileString;
  2057     t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
  2058     if (t)
  2059         while (nsCRT::strtok(nextToken, "/", &nextToken))
  2060             ntok++; // count number of terms (tokens)
  2061     LOG(("FTP:(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
  2062     LOG(("FTP:(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
  2064     if (fileString.First() == '/') {
  2065         // absolute filespec
  2066         //   /        -> []
  2067         //   /a       -> a (doesn't really make much sense)
  2068         //   /a/b     -> a:[000000]b
  2069         //   /a/b/c   -> a:[b]c
  2070         //   /a/b/c/d -> a:[b.c]d
  2071         if (ntok == 1) {
  2072             if (fileString.Length() == 1) {
  2073                 // Just a slash
  2074                 fileString.Truncate();
  2075                 fileString.AppendLiteral("[]");
  2076             } else {
  2077                 // just copy the name part (drop the leading slash)
  2078                 fileStringCopy = fileString;
  2079                 fileString = Substring(fileStringCopy, 1,
  2080                                        fileStringCopy.Length()-1);
  2082         } else {
  2083             // Get another copy since the last one was written to.
  2084             fileStringCopy = fileString;
  2085             fileString.Truncate();
  2086             fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), 
  2087                               "/", &nextToken));
  2088             fileString.AppendLiteral(":[");
  2089             if (ntok > 2) {
  2090                 for (int i=2; i<ntok; i++) {
  2091                     if (i > 2) fileString.Append('.');
  2092                     fileString.Append(nsCRT::strtok(nextToken,
  2093                                       "/", &nextToken));
  2095             } else {
  2096                 fileString.AppendLiteral("000000");
  2098             fileString.Append(']');
  2099             fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
  2101     } else {
  2102        // relative filespec
  2103         //   a       -> a
  2104         //   a/b     -> [.a]b
  2105         //   a/b/c   -> [.a.b]c
  2106         if (ntok == 1) {
  2107             // no slashes, just use the name as is
  2108         } else {
  2109             // Get another copy since the last one was written to.
  2110             fileStringCopy = fileString;
  2111             fileString.Truncate();
  2112             fileString.AppendLiteral("[.");
  2113             fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
  2114                               "/", &nextToken));
  2115             if (ntok > 2) {
  2116                 for (int i=2; i<ntok; i++) {
  2117                     fileString.Append('.');
  2118                     fileString.Append(nsCRT::strtok(nextToken,
  2119                                       "/", &nextToken));
  2122             fileString.Append(']');
  2123             fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
  2126     LOG(("FTP:(%x) ConvertFilespecToVMS   to: \"%s\"\n", this, fileString.get()));
  2129 // Convert a unix-style dirspec to VMS format
  2130 // /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
  2131 // /foo/fred -> foo:[fred]
  2132 // /foo -> foo:[000000]
  2133 // (null) -> (null)
  2134 void
  2135 nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec)
  2137     LOG(("FTP:(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
  2138     if (!dirSpec.IsEmpty()) {
  2139         if (dirSpec.Last() != '/')
  2140             dirSpec.Append('/');
  2141         // we can use the filespec routine if we make it look like a file name
  2142         dirSpec.Append('x');
  2143         ConvertFilespecToVMS(dirSpec);
  2144         dirSpec.Truncate(dirSpec.Length()-1);
  2146     LOG(("FTP:(%x) ConvertDirspecToVMS   to: \"%s\"\n", this, dirSpec.get()));
  2149 // Convert an absolute VMS style dirspec to UNIX format
  2150 void
  2151 nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
  2153     LOG(("FTP:(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
  2154     if (dirSpec.IsEmpty()) {
  2155         dirSpec.Insert('.', 0);
  2156     } else {
  2157         dirSpec.Insert('/', 0);
  2158         dirSpec.ReplaceSubstring(":[", "/");
  2159         dirSpec.ReplaceChar('.', '/');
  2160         dirSpec.ReplaceChar(']', '/');
  2162     LOG(("FTP:(%x) ConvertDirspecFromVMS   to: \"%s\"\n", this, dirSpec.get()));
  2165 //-----------------------------------------------------------------------------
  2167 NS_IMETHODIMP
  2168 nsFtpState::OnTransportStatus(nsITransport *transport, nsresult status,
  2169                               uint64_t progress, uint64_t progressMax)
  2171     // Mix signals from both the control and data connections.
  2173     // Ignore data transfer events on the control connection.
  2174     if (mControlConnection && transport == mControlConnection->Transport()) {
  2175         switch (status) {
  2176         case NS_NET_STATUS_RESOLVING_HOST:
  2177         case NS_NET_STATUS_RESOLVED_HOST:
  2178         case NS_NET_STATUS_CONNECTING_TO:
  2179         case NS_NET_STATUS_CONNECTED_TO:
  2180             break;
  2181         default:
  2182             return NS_OK;
  2186     // Ignore the progressMax value from the socket.  We know the true size of
  2187     // the file based on the response from our SIZE request. Additionally, only
  2188     // report the max progress based on where we started/resumed.
  2189     mChannel->OnTransportStatus(nullptr, status, progress,
  2190                                 mFileSize - mChannel->StartPos());
  2191     return NS_OK;
  2194 //-----------------------------------------------------------------------------
  2197 NS_IMETHODIMP
  2198 nsFtpState::OnCacheEntryAvailable(nsICacheEntryDescriptor *entry,
  2199                                   nsCacheAccessMode access,
  2200                                   nsresult status)
  2202     // We may have been closed while we were waiting for this cache entry.
  2203     if (IsClosed())
  2204         return NS_OK;
  2206     if (NS_SUCCEEDED(status) && entry) {
  2207         mDoomCache = true;
  2208         mCacheEntry = entry;
  2209         if (CanReadCacheEntry() && ReadCacheEntry()) {
  2210             mState = FTP_READ_CACHE;
  2211             return NS_OK;
  2215     Connect();
  2216     return NS_OK;
  2219 //-----------------------------------------------------------------------------
  2221 NS_IMETHODIMP
  2222 nsFtpState::OnCacheEntryDoomed(nsresult status)
  2224     return NS_ERROR_NOT_IMPLEMENTED;
  2227 //-----------------------------------------------------------------------------
  2229 NS_IMETHODIMP
  2230 nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *context)
  2232     mStorReplyReceived = false;
  2233     return NS_OK;
  2236 NS_IMETHODIMP
  2237 nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *context,
  2238                           nsresult status)
  2240     mUploadRequest = nullptr;
  2242     // Close() will be called when reply to STOR command is received
  2243     // see bug #389394
  2244     if (!mStorReplyReceived)
  2245       return NS_OK;
  2247     // We're done uploading.  Let our consumer know that we're done.
  2248     Close();
  2249     return NS_OK;
  2252 //-----------------------------------------------------------------------------
  2254 NS_IMETHODIMP
  2255 nsFtpState::Available(uint64_t *result)
  2257     if (mDataStream)
  2258         return mDataStream->Available(result);
  2260     return nsBaseContentStream::Available(result);
  2263 NS_IMETHODIMP
  2264 nsFtpState::ReadSegments(nsWriteSegmentFun writer, void *closure,
  2265                          uint32_t count, uint32_t *result)
  2267     // Insert a thunk here so that the input stream passed to the writer is this
  2268     // input stream instead of mDataStream.
  2270     if (mDataStream) {
  2271         nsWriteSegmentThunk thunk = { this, writer, closure };
  2272         nsresult rv;
  2273         rv = mDataStream->ReadSegments(NS_WriteSegmentThunk, &thunk, count,
  2274                                        result);
  2275         if (NS_SUCCEEDED(rv)) {
  2276             CountRecvBytes(*result);
  2278         return rv;
  2281     return nsBaseContentStream::ReadSegments(writer, closure, count, result);
  2284 nsresult
  2285 nsFtpState::SaveNetworkStats(bool enforce)
  2287 #ifdef MOZ_WIDGET_GONK
  2288     // Obtain app id
  2289     uint32_t appId;
  2290     bool isInBrowser;
  2291     NS_GetAppInfo(mChannel, &appId, &isInBrowser);
  2293     // Check if active network and appid are valid.
  2294     if (!mActiveNetwork || appId == NECKO_NO_APP_ID) {
  2295         return NS_OK;
  2298     if (mCountRecv <= 0) {
  2299         // There is no traffic, no need to save.
  2300         return NS_OK;
  2303     // If |enforce| is false, the traffic amount is saved
  2304     // only when the total amount exceeds the predefined
  2305     // threshold.
  2306     if (!enforce && mCountRecv < NETWORK_STATS_THRESHOLD) {
  2307         return NS_OK;
  2310     // Create the event to save the network statistics.
  2311     // the event is then dispathed to the main thread.
  2312     nsRefPtr<nsRunnable> event =
  2313         new SaveNetworkStatsEvent(appId, mActiveNetwork, mCountRecv, 0, false);
  2314     NS_DispatchToMainThread(event);
  2316     // Reset the counters after saving.
  2317     mCountRecv = 0;
  2319     return NS_OK;
  2320 #else
  2321     return NS_ERROR_NOT_IMPLEMENTED;
  2322 #endif
  2325 NS_IMETHODIMP
  2326 nsFtpState::CloseWithStatus(nsresult status)
  2328     LOG(("FTP:(%p) close [%x]\n", this, status));
  2330     // Shutdown the control connection processing if we are being closed with an
  2331     // error.  Note: This method may be called several times.
  2332     if (!IsClosed() && status != NS_BASE_STREAM_CLOSED && NS_FAILED(status)) {
  2333         if (NS_SUCCEEDED(mInternalError))
  2334             mInternalError = status;
  2335         StopProcessing();
  2338     if (mUploadRequest) {
  2339         mUploadRequest->Cancel(NS_ERROR_ABORT);
  2340         mUploadRequest = nullptr;
  2343     if (mDataTransport) {
  2344         // Save the network stats before data transport is closing.
  2345         SaveNetworkStats(true);
  2347         // Shutdown the data transport.
  2348         mDataTransport->Close(NS_ERROR_ABORT);
  2349         mDataTransport = nullptr;
  2352     mDataStream = nullptr;
  2353     if (mDoomCache && mCacheEntry)
  2354         mCacheEntry->AsyncDoom(nullptr);
  2355     mCacheEntry = nullptr;
  2357     return nsBaseContentStream::CloseWithStatus(status);
  2360 static nsresult
  2361 CreateHTTPProxiedChannel(nsIChannel *channel, nsIProxyInfo *pi, nsIChannel **newChannel)
  2363     nsresult rv;
  2364     nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
  2365     if (NS_FAILED(rv))
  2366         return rv;
  2368     nsCOMPtr<nsIProtocolHandler> handler;
  2369     rv = ioService->GetProtocolHandler("http", getter_AddRefs(handler));
  2370     if (NS_FAILED(rv))
  2371         return rv;
  2373     nsCOMPtr<nsIProxiedProtocolHandler> pph = do_QueryInterface(handler, &rv);
  2374     if (NS_FAILED(rv))
  2375         return rv;
  2377     nsCOMPtr<nsIURI> uri;
  2378     channel->GetURI(getter_AddRefs(uri));
  2380     return pph->NewProxiedChannel(uri, pi, 0, nullptr, newChannel);
  2383 NS_IMETHODIMP
  2384 nsFtpState::OnProxyAvailable(nsICancelable *request, nsIChannel *channel,
  2385                              nsIProxyInfo *pi, nsresult status)
  2387   mProxyRequest = nullptr;
  2389   // failed status code just implies DIRECT processing
  2391   if (NS_SUCCEEDED(status)) {
  2392     nsAutoCString type;
  2393     if (pi && NS_SUCCEEDED(pi->GetType(type)) && type.EqualsLiteral("http")) {
  2394         // Proxy the FTP url via HTTP
  2395         // This would have been easier to just return a HTTP channel directly
  2396         // from nsIIOService::NewChannelFromURI(), but the proxy type cannot
  2397         // be reliabliy determined synchronously without jank due to pac, etc..
  2398         LOG(("FTP:(%p) Configured to use a HTTP proxy channel\n", this));
  2400         nsCOMPtr<nsIChannel> newChannel;
  2401         if (NS_SUCCEEDED(CreateHTTPProxiedChannel(channel, pi,
  2402                                                   getter_AddRefs(newChannel))) &&
  2403             NS_SUCCEEDED(mChannel->Redirect(newChannel,
  2404                                             nsIChannelEventSink::REDIRECT_INTERNAL,
  2405                                             true))) {
  2406             LOG(("FTP:(%p) Redirected to use a HTTP proxy channel\n", this));
  2407             return NS_OK;
  2410     else if (pi) {
  2411         // Proxy using the FTP protocol routed through a socks proxy
  2412         LOG(("FTP:(%p) Configured to use a SOCKS proxy channel\n", this));
  2413         mChannel->SetProxyInfo(pi);
  2417   if (mDeferredCallbackPending) {
  2418       mDeferredCallbackPending = false;
  2419       OnCallbackPending();
  2421   return NS_OK;
  2424 void
  2425 nsFtpState::OnCallbackPending()
  2427     // If this is the first call, then see if we could use the cache.  If we
  2428     // aren't going to read from (or write to) the cache, then just proceed to
  2429     // connect to the server.
  2431     if (mState == FTP_INIT) {
  2432         if (mProxyRequest) {
  2433             mDeferredCallbackPending = true;
  2434             return;
  2437         if (CheckCache()) {
  2438             mState = FTP_WAIT_CACHE;
  2439             return;
  2441         if (mCacheEntry && CanReadCacheEntry() && ReadCacheEntry()) {
  2442             mState = FTP_READ_CACHE;
  2443             return;
  2445         Connect();
  2446     } else if (mDataStream) {
  2447         mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  2451 bool
  2452 nsFtpState::ReadCacheEntry()
  2454     NS_ASSERTION(mCacheEntry, "should have a cache entry");
  2456     // make sure the channel knows wassup
  2457     SetContentType();
  2459     nsXPIDLCString serverType;
  2460     mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType));
  2461     nsAutoCString serverNum(serverType.get());
  2462     nsresult err;
  2463     mServerType = serverNum.ToInteger(&err);
  2465     nsXPIDLCString charset;
  2466     mCacheEntry->GetMetaDataElement("useUTF8", getter_Copies(charset));
  2467     const char *useUTF8 = charset.get();
  2468     if (useUTF8 && atoi(useUTF8) == 1)
  2469         mChannel->SetContentCharset(NS_LITERAL_CSTRING("UTF-8"));
  2471     mChannel->PushStreamConverter("text/ftp-dir",
  2472                                   APPLICATION_HTTP_INDEX_FORMAT);
  2474     mChannel->SetEntityID(EmptyCString());
  2476     if (NS_FAILED(OpenCacheDataStream()))
  2477         return false;
  2479     if (mDataStream && HasPendingCallback())
  2480         mDataStream->AsyncWait(this, 0, 0, CallbackTarget());
  2482     mDoomCache = false;
  2483     return true;
  2486 bool
  2487 nsFtpState::CheckCache()
  2489     // This function is responsible for setting mCacheEntry if there is a cache
  2490     // entry that we can use.  It returns true if we end up waiting for access
  2491     // to the cache.
  2493     // In some cases, we don't want to use the cache:
  2494     if (mChannel->UploadStream() || mChannel->ResumeRequested())
  2495         return false;
  2497     nsCOMPtr<nsICacheService> cache = do_GetService(NS_CACHESERVICE_CONTRACTID);
  2498     if (!cache)
  2499         return false;
  2501     bool isPrivate = NS_UsePrivateBrowsing(mChannel);
  2502     const char* sessionName = isPrivate ? "FTP-private" : "FTP";
  2503     nsCacheStoragePolicy policy =
  2504         isPrivate ? nsICache::STORE_IN_MEMORY : nsICache::STORE_ANYWHERE;
  2505     nsCOMPtr<nsICacheSession> session;
  2506     cache->CreateSession(sessionName,
  2507                          policy,
  2508                          nsICache::STREAM_BASED,
  2509                          getter_AddRefs(session));
  2510     if (!session)
  2511         return false;
  2512     session->SetDoomEntriesIfExpired(false);
  2513     session->SetIsPrivate(isPrivate);
  2515     // Set cache access requested:
  2516     nsCacheAccessMode accessReq;
  2517     if (NS_IsOffline()) {
  2518         accessReq = nsICache::ACCESS_READ; // can only read
  2519     } else if (mChannel->HasLoadFlag(nsIRequest::LOAD_BYPASS_CACHE)) {
  2520         accessReq = nsICache::ACCESS_WRITE; // replace cache entry
  2521     } else {
  2522         accessReq = nsICache::ACCESS_READ_WRITE; // normal browsing
  2525     // Check to see if we are not allowed to write to the cache:
  2526     if (mChannel->HasLoadFlag(nsIRequest::INHIBIT_CACHING)) {
  2527         accessReq &= ~nsICache::ACCESS_WRITE;
  2528         if (accessReq == nsICache::ACCESS_NONE)
  2529             return false;
  2532     // Generate cache key (remove trailing #ref if any):
  2533     nsAutoCString key;
  2534     mChannel->URI()->GetAsciiSpec(key);
  2535     int32_t pos = key.RFindChar('#');
  2536     if (pos != kNotFound)
  2537         key.Truncate(pos);
  2538     NS_ENSURE_FALSE(key.IsEmpty(), false);
  2540     nsresult rv = session->AsyncOpenCacheEntry(key, accessReq, this, false);
  2541     return NS_SUCCEEDED(rv);

mercurial