Back to index

lightning-sunbird  0.9+nobinonly
nsFtpConnectionThread.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* vim:set tw=80 ts=4 sts=4 sw=4 et cin: */
00003 /* ***** BEGIN LICENSE BLOCK *****
00004  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00005  *
00006  * The contents of this file are subject to the Mozilla Public License Version
00007  * 1.1 (the "License"); you may not use this file except in compliance with
00008  * the License. You may obtain a copy of the License at
00009  * http://www.mozilla.org/MPL/
00010  *
00011  * Software distributed under the License is distributed on an "AS IS" basis,
00012  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00013  * for the specific language governing rights and limitations under the
00014  * License.
00015  *
00016  * The Original Code is mozilla.org code.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Bradley Baetz <bbaetz@student.usyd.edu.au>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nsFTPChannel.h"
00041 #include "nsFtpConnectionThread.h"
00042 #include "nsFtpControlConnection.h"
00043 #include "nsFtpProtocolHandler.h"
00044 
00045 #include <limits.h>
00046 #include <ctype.h>
00047 
00048 #include "nsISocketTransport.h"
00049 #include "nsIStreamConverterService.h"
00050 #include "nsIStreamListenerTee.h"
00051 #include "nsReadableUtils.h"
00052 #include "prprf.h"
00053 #include "prlog.h"
00054 #include "prtime.h"
00055 #include "prnetdb.h"
00056 #include "netCore.h"
00057 #include "ftpCore.h"
00058 #include "nsProxiedService.h"
00059 #include "nsCRT.h"
00060 #include "nsIInterfaceRequestor.h"
00061 #include "nsIInterfaceRequestorUtils.h"
00062 #include "nsIURL.h"
00063 #include "nsEscape.h"
00064 #include "nsNetUtil.h"
00065 #include "nsIDNSService.h" // for host error code
00066 #include "nsCPasswordManager.h"
00067 #include "nsIMemory.h"
00068 #include "nsIStringStream.h"
00069 #include "nsIPrefService.h"
00070 #include "nsIPrefBranch.h"
00071 #include "nsMimeTypes.h"
00072 #include "nsIStringBundle.h"
00073 #include "nsEventQueueUtils.h"
00074 #include "nsChannelProperties.h"
00075 
00076 #include "nsICacheEntryDescriptor.h"
00077 #include "nsICacheListener.h"
00078 
00079 #include "nsIResumableChannel.h"
00080 
00081 static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
00082 static NS_DEFINE_CID(kStreamListenerTeeCID,      NS_STREAMLISTENERTEE_CID);
00083 static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
00084 
00085 
00086 #if defined(PR_LOGGING)
00087 extern PRLogModuleInfo* gFTPLog;
00088 #define LOG(args)         PR_LOG(gFTPLog, PR_LOG_DEBUG, args)
00089 #define LOG_ALWAYS(args)  PR_LOG(gFTPLog, PR_LOG_ALWAYS, args)
00090 #else
00091 #define LOG(args)
00092 #define LOG_ALWAYS(args)
00093 #endif
00094 
00095 
00102 class TransportEventForwarder : public nsITransportEventSink
00103 {
00104 public:
00105     TransportEventForwarder(nsIProgressEventSink* aSink) : mSink(aSink) {}
00106 
00107     NS_DECL_ISUPPORTS
00108     NS_DECL_NSITRANSPORTEVENTSINK
00109 
00110 private:
00111     nsCOMPtr<nsIProgressEventSink> mSink;
00112 };
00113 
00114 NS_IMPL_ISUPPORTS1(TransportEventForwarder, nsITransportEventSink)
00115 
00116 NS_IMETHODIMP
00117 TransportEventForwarder::OnTransportStatus(nsITransport *transport, nsresult status,
00118                                            PRUint64 progress, PRUint64 progressMax)
00119 {
00120     // We only want to forward the resolving and connecting states of the
00121     // control connection and report the data connection for the rest of the
00122     // transfer, to avoid continuously switching between "sending to" and
00123     // "receiving from"
00124     if (mSink &&
00125             (status == NS_NET_STATUS_RESOLVING_HOST ||
00126              status == NS_NET_STATUS_CONNECTING_TO ||
00127              status == NS_NET_STATUS_CONNECTED_TO))
00128         mSink->OnStatus(nsnull, nsnull, status, nsnull);
00129     return NS_OK;
00130 }
00131 
00132 
00136 class DataRequestForwarder : public nsIFTPChannel, 
00137                              public nsIStreamListener,
00138                              public nsIResumableChannel,
00139                              public nsITransportEventSink
00140 {
00141 public:
00142     DataRequestForwarder();
00143 
00144     nsresult Init(nsIRequest *request);
00145 
00146     nsresult SetStreamListener(nsIStreamListener *listener);
00147     nsresult SetCacheEntry(nsICacheEntryDescriptor *entry, PRBool writing);
00148     nsresult SetEntityID(const nsACString& entity);
00149     void     SetFileSize(PRUint64 fileSize) { mFileSize = fileSize; }
00150 
00151     NS_DECL_ISUPPORTS
00152     NS_DECL_NSISTREAMLISTENER
00153     NS_DECL_NSIREQUESTOBSERVER
00154     NS_DECL_NSIRESUMABLECHANNEL
00155     NS_DECL_NSITRANSPORTEVENTSINK
00156 
00157     NS_FORWARD_NSIREQUEST(mRequest->)
00158     NS_FORWARD_NSICHANNEL(mFTPChannel->)
00159     NS_FORWARD_NSIFTPCHANNEL(mFTPChannel->)
00160     
00161     PRUint64 GetBytesTransfered() {return mBytesTransfered;}
00162     void Uploading(PRBool value, PRUint32 uploadCount);
00163     void SetRetrying(PRBool retry);
00164     
00165 protected:
00166 
00167     nsCOMPtr<nsIRequest>              mRequest;
00168     nsCOMPtr<nsIFTPChannel>           mFTPChannel;
00169     nsCOMPtr<nsIStreamListener>       mListener;
00170     nsCOMPtr<nsIProgressEventSink>    mEventSink;
00171     nsCOMPtr<nsICacheEntryDescriptor> mCacheEntry;
00172     nsCString                         mEntityID;
00173 
00174     nsUint64 mBytesTransfered;
00175     nsUint64 mBytesToUpload;
00176     PRUint64 mFileSize;
00177     PRPackedBool   mDelayedOnStartFired;
00178     PRPackedBool   mUploading;
00179     PRPackedBool   mRetrying;
00180 
00181     nsresult DelayedOnStartRequest(nsIRequest *request, nsISupports *ctxt);
00182 };
00183 
00184 
00185 // The whole purpose of this class is to opaque 
00186 // the socket transport so that clients only see
00187 // the same nsIChannel/nsIRequest that they started.
00188 
00189 NS_IMPL_THREADSAFE_ISUPPORTS7(DataRequestForwarder, 
00190                               nsIStreamListener, 
00191                               nsIRequestObserver, 
00192                               nsIFTPChannel,
00193                               nsIResumableChannel,
00194                               nsIChannel,
00195                               nsIRequest,
00196                               nsITransportEventSink)
00197 
00198 
00199 DataRequestForwarder::DataRequestForwarder()
00200 {
00201     LOG(("(%x) DataRequestForwarder CREATED\n", this));
00202 
00203     mBytesTransfered = 0;
00204     mBytesToUpload = 0;
00205     mRetrying = mUploading = mDelayedOnStartFired = PR_FALSE;
00206     mFileSize = LL_MAXUINT;
00207 }
00208 
00209 nsresult 
00210 DataRequestForwarder::Init(nsIRequest *request)
00211 {
00212     LOG(("(%x) DataRequestForwarder Init [request=%x]\n", this, request));
00213 
00214     NS_ENSURE_ARG(request);
00215 
00216     // for the forwarding declarations.
00217     mRequest    = request;
00218     mFTPChannel = do_QueryInterface(request);
00219     mEventSink  = do_QueryInterface(request);
00220     mListener   = do_QueryInterface(request);
00221 
00222     if (!mRequest || !mFTPChannel)
00223         return NS_ERROR_FAILURE;
00224     
00225     return NS_OK;
00226 }
00227 
00228 void 
00229 DataRequestForwarder::Uploading(PRBool value, PRUint32 uploadCount)
00230 {
00231     mUploading = value;
00232     mBytesToUpload = uploadCount;
00233 }
00234 
00235 nsresult 
00236 DataRequestForwarder::SetCacheEntry(nsICacheEntryDescriptor *cacheEntry, PRBool writing)
00237 {
00238     // if there is a cache entry descriptor, send data to it.
00239     if (!cacheEntry) 
00240         return NS_ERROR_FAILURE;
00241     
00242     mCacheEntry = cacheEntry;
00243     if (!writing)
00244         return NS_OK;
00245 
00246     nsresult rv;
00247     nsCOMPtr<nsIOutputStream> out;
00248     rv = cacheEntry->OpenOutputStream(0, getter_AddRefs(out));
00249     if (NS_FAILED(rv)) return rv;
00250 
00251     nsCOMPtr<nsIStreamListenerTee> tee =
00252         do_CreateInstance(kStreamListenerTeeCID, &rv);
00253     if (NS_FAILED(rv)) return rv;
00254 
00255     rv = tee->Init(mListener, out);
00256     if (NS_FAILED(rv)) return rv;
00257 
00258     mListener = do_QueryInterface(tee, &rv);
00259 
00260     return NS_OK;
00261 }
00262 
00263 
00264 nsresult 
00265 DataRequestForwarder::SetStreamListener(nsIStreamListener *listener)
00266 {
00267     LOG(("(%x) DataRequestForwarder SetStreamListener [listener=%x]\n", this, listener)); 
00268     
00269     mListener = listener;
00270     if (!mListener) 
00271         return NS_ERROR_FAILURE;
00272 
00273     return NS_OK;
00274 }
00275 
00276 nsresult
00277 DataRequestForwarder::SetEntityID(const nsACString& aEntityID)
00278 {
00279     mEntityID = aEntityID;
00280     return NS_OK;
00281 }
00282 
00283 NS_IMETHODIMP
00284 DataRequestForwarder::GetEntityID(nsACString& aEntityID)
00285 {
00286     if (mEntityID.IsEmpty())
00287         return NS_ERROR_NOT_RESUMABLE;
00288 
00289     aEntityID = mEntityID;
00290     return NS_OK;
00291 }
00292 
00293 NS_IMETHODIMP
00294 DataRequestForwarder::ResumeAt(PRUint64, 
00295                                const nsACString&)
00296 {
00297     // We shouldn't get here. This class only exists in the middle of a
00298     // request
00299     NS_NOTREACHED("DataRequestForwarder::ResumeAt");
00300     return NS_ERROR_NOT_IMPLEMENTED;
00301 }
00302 
00303 nsresult 
00304 DataRequestForwarder::DelayedOnStartRequest(nsIRequest *request, nsISupports *ctxt)
00305 {
00306     LOG(("(%x) DataRequestForwarder DelayedOnStartRequest \n", this)); 
00307     return mListener->OnStartRequest(this, ctxt); 
00308 }
00309 
00310 void
00311 DataRequestForwarder::SetRetrying(PRBool retry)
00312 {
00313     // The problem here is that if we send a second PASV, our listener would
00314     // get an OnStop from the socket transport, and fail. So we temporarily
00315     // suspend notifications
00316     LOG(("(%x) DataRequestForwarder SetRetrying [retry=%d]\n", this, retry));
00317 
00318     mRetrying = retry;
00319     mDelayedOnStartFired = PR_FALSE;
00320 }
00321 
00322 NS_IMETHODIMP 
00323 DataRequestForwarder::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
00324 {
00325     NS_ASSERTION(mListener, "No Listener Set.");
00326     LOG(("(%x) DataRequestForwarder OnStartRequest \n[mRetrying=%d]", this, mRetrying)); 
00327 
00328     if (!mListener)
00329         return NS_ERROR_NOT_INITIALIZED;
00330 
00331     // OnStartRequest is delayed.
00332     return NS_OK;
00333 }
00334 
00335 NS_IMETHODIMP
00336 DataRequestForwarder::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult statusCode)
00337 {
00338     LOG(("(%x) DataRequestForwarder OnStopRequest [status=%x, mRetrying=%d]\n", this, statusCode, mRetrying)); 
00339 
00340     if (mRetrying) {
00341         mRetrying = PR_FALSE;
00342         return NS_OK;
00343     }
00344 
00345     // If there were no calls to ODA, then the onstart won't have been
00346     // fired - bug 122913
00347     if (!mDelayedOnStartFired) { 
00348         mDelayedOnStartFired = PR_TRUE;
00349         nsresult rv = DelayedOnStartRequest(request, ctxt);
00350         if (NS_FAILED(rv)) return rv;
00351     }
00352 
00353     if (!mListener)
00354         return NS_ERROR_NOT_INITIALIZED;
00355 
00356     return mListener->OnStopRequest(this, ctxt, statusCode);
00357 }
00358 
00359 NS_IMETHODIMP
00360 DataRequestForwarder::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *input, PRUint32 offset, PRUint32 count)
00361 { 
00362     nsresult rv;
00363     NS_ASSERTION(mListener, "No Listener Set.");
00364     NS_ASSERTION(!mUploading, "Since we are uploading, we should never get a ODA");
00365  
00366     if (!mListener)
00367         return NS_ERROR_NOT_INITIALIZED;
00368 
00369     // we want to delay firing the onStartRequest until we know that there is data
00370     if (!mDelayedOnStartFired) { 
00371         mDelayedOnStartFired = PR_TRUE;
00372         rv = DelayedOnStartRequest(request, ctxt);
00373         if (NS_FAILED(rv)) return rv;
00374     }
00375 
00376     // XXX mBytesTransfered will get truncated from 64-bit to 32-bit
00377     rv = mListener->OnDataAvailable(this, ctxt, input, mBytesTransfered, count); 
00378     if (NS_SUCCEEDED(rv))
00379         mBytesTransfered += count;
00380     return rv;
00381 } 
00382 
00383 // nsITransportEventSink methods
00384 NS_IMETHODIMP
00385 DataRequestForwarder::OnTransportStatus(nsITransport *transport, nsresult status,
00386                                         PRUint64 progress, PRUint64 progressMax)
00387 {
00388     if (mEventSink) {
00389         mEventSink->OnStatus(nsnull, nsnull, status, nsnull);
00390 
00391         if (status == nsISocketTransport::STATUS_RECEIVING_FROM ||
00392             status == nsISocketTransport::STATUS_SENDING_TO) {
00393             // compute progress based on whether we are uploading or receiving...
00394             PRUint64 count = mUploading ? progress                 : PRUint64(mBytesTransfered);
00395             PRUint64 max   = mUploading ? PRUint64(mBytesToUpload) : mFileSize;
00396             mEventSink->OnProgress(this, nsnull, count, max);
00397         }
00398     }
00399     return NS_OK;
00400 }
00401 
00402 
00403 NS_IMPL_THREADSAFE_ISUPPORTS3(nsFtpState,
00404                               nsIStreamListener, 
00405                               nsIRequestObserver, 
00406                               nsIRequest)
00407 
00408 nsFtpState::nsFtpState()
00409 {
00410     LOG_ALWAYS(("(%x) nsFtpState created", this));
00411 
00412     // bool init
00413     mRETRFailed = PR_FALSE;
00414     mWaitingForDConn = mTryingCachedControl = mRetryPass = PR_FALSE;
00415     mKeepRunning = mAnonymous = PR_TRUE;
00416 
00417     mAction = GET;
00418     mState = FTP_COMMAND_CONNECT;
00419     mNextState = FTP_S_USER;
00420 
00421     mInternalError = NS_OK;
00422     mSuspendCount = 0;
00423     mPort = 21;
00424 
00425     mReceivedControlData = PR_FALSE;
00426     mControlStatus = NS_OK;
00427 
00428     mWriteCount = 0;
00429 
00430     mAddressChecked = PR_FALSE;
00431     mServerIsIPv6 = PR_FALSE;
00432     
00433     mControlConnection = nsnull;
00434     mDRequestForwarder = nsnull;
00435     mFileSize          = LL_MAXUINT;
00436 
00437     // make sure handler stays around
00438     NS_ADDREF(gFtpHandler);
00439 }
00440 
00441 nsFtpState::~nsFtpState() 
00442 {
00443     LOG_ALWAYS(("(%x) nsFtpState destroyed", this));
00444     
00445     NS_IF_RELEASE(mDRequestForwarder);
00446 
00447     // release reference to handler
00448     nsFtpProtocolHandler *handler = gFtpHandler;
00449     NS_RELEASE(handler);
00450 }
00451 
00452 nsresult
00453 nsFtpState::GetEntityID(nsACString& aEntityID)
00454 {
00455     if (mEntityID.IsEmpty())
00456         return NS_ERROR_NOT_RESUMABLE;
00457 
00458     aEntityID = mEntityID;
00459     return NS_OK;
00460 }
00461 
00462 // nsIStreamListener implementation
00463 NS_IMETHODIMP
00464 nsFtpState::OnDataAvailable(nsIRequest *request,
00465                             nsISupports *aContext,
00466                             nsIInputStream *aInStream,
00467                             PRUint32 aOffset, 
00468                             PRUint32 aCount)
00469 {    
00470     if (aCount == 0)
00471         return NS_OK; /*** should this be an error?? */
00472     
00473     if (!mReceivedControlData) {
00474         // parameter can be null cause the channel fills them in.
00475         mChannel->OnStatus(nsnull, nsnull, 
00476                            NS_NET_STATUS_BEGIN_FTP_TRANSACTION, nsnull);
00477         
00478         mReceivedControlData = PR_TRUE;
00479     }
00480 
00481     char* buffer = (char*)nsMemory::Alloc(aCount + 1);
00482     if (!buffer)
00483         return NS_ERROR_OUT_OF_MEMORY;
00484     nsresult rv = aInStream->Read(buffer, aCount, &aCount);
00485     if (NS_FAILED(rv)) 
00486     {
00487         LOG(("(%x) nsFtpState - error reading %x\n", this, rv));
00488         nsMemory::Free(buffer);
00489         return NS_ERROR_FAILURE;
00490     }
00491     buffer[aCount] = '\0';
00492     
00493     nsXPIDLCString data;
00494     data.Adopt(buffer);
00495 
00496     LOG(("(%x) reading %d bytes: \"%s\"", this, aCount, buffer));
00497 
00498     // Sometimes we can get two responses in the same packet, eg from LIST.
00499     // So we need to parse the response line by line
00500     nsCString lines(mControlReadCarryOverBuf);
00501     lines.Append(data, aCount);
00502     // Clear the carryover buf - if we still don't have a line, then it will
00503     // be reappended below
00504     mControlReadCarryOverBuf.Truncate();
00505 
00506     const char* currLine = lines.get();
00507     while (*currLine && mKeepRunning) {
00508         PRInt32 eolLength = strcspn(currLine, CRLF);
00509         PRInt32 currLineLength = strlen(currLine);
00510 
00511         // if currLine is empty or only contains CR or LF, then bail.  we can
00512         // sometimes get an ODA event with the full response line + CR without
00513         // the trailing LF.  the trailing LF might come in the next ODA event.
00514         // because we are happy enough to process a response line ending only
00515         // in CR, we need to take care to discard the extra LF (bug 191220).
00516         if (eolLength == 0 && currLineLength <= 1)
00517             break;
00518 
00519         if (eolLength == currLineLength) {
00520             mControlReadCarryOverBuf.Assign(currLine);
00521             break;
00522         }
00523 
00524         // Append the current segment, including the LF
00525         nsCAutoString line;
00526         PRInt32 crlfLength = 0;
00527 
00528         if ((currLineLength > eolLength) &&
00529             (currLine[eolLength] == nsCRT::CR) &&
00530             (currLine[eolLength+1] == nsCRT::LF))
00531             crlfLength = 2; // CR +LF 
00532         else 
00533             crlfLength = 1; // + LF or CR
00534 
00535         line.Assign(currLine, eolLength + crlfLength);
00536         
00537         // Does this start with a response code?
00538         PRBool startNum = (line.Length() >= 3 &&
00539                            isdigit(line[0]) &&
00540                            isdigit(line[1]) &&
00541                            isdigit(line[2]));
00542 
00543         if (mResponseMsg.IsEmpty()) {
00544             // If we get here, then we know that we have a complete line, and
00545             // that it is the first one
00546 
00547             NS_ASSERTION(line.Length() > 4 && startNum,
00548                          "Read buffer doesn't include response code");
00549             
00550             mResponseCode = atoi(PromiseFlatCString(Substring(line,0,3)).get());
00551         }
00552 
00553         mResponseMsg.Append(line);
00554 
00555         // This is the last line if its 3 numbers followed by a space
00556         if (startNum && line[3] == ' ') {
00557             // yup. last line, let's move on.
00558             if (mState == mNextState) {
00559                 NS_ASSERTION(0, "ftp read state mixup");
00560                 mInternalError = NS_ERROR_FAILURE;
00561                 mState = FTP_ERROR;
00562             } else {
00563                 mState = mNextState;
00564             }
00565 
00566             nsCOMPtr<nsIFTPEventSink> ftpSink;
00567             mChannel->GetFTPEventSink(ftpSink);
00568             if (ftpSink)
00569                 ftpSink->OnFTPControlLog(PR_TRUE, mResponseMsg.get());
00570             
00571             rv = Process();
00572             mResponseMsg.Truncate();
00573             if (NS_FAILED(rv)) return rv;
00574         }
00575 
00576         currLine = currLine + eolLength + crlfLength;
00577     }
00578 
00579     return NS_OK;
00580 }
00581 
00582 
00583 // nsIRequestObserver implementation
00584 NS_IMETHODIMP
00585 nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *aContext)
00586 {
00587 #if defined(PR_LOGGING)
00588     nsCAutoString spec;
00589     (void)mURL->GetAsciiSpec(spec);
00590 
00591     LOG(("(%x) nsFtpState::OnStartRequest() (spec =%s)\n", this, spec.get()));
00592 #endif
00593     return NS_OK;
00594 }
00595 
00596 NS_IMETHODIMP
00597 nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *aContext,
00598                             nsresult aStatus)
00599 {
00600     LOG(("(%x) nsFtpState::OnStopRequest() rv=%x\n", this, aStatus));
00601     mControlStatus = aStatus;
00602 
00603     // HACK.  This may not always work.  I am assuming that I can mask the OnStopRequest
00604     // notification since I created it via the control connection.
00605     if (mTryingCachedControl && NS_FAILED(aStatus) && NS_SUCCEEDED(mInternalError)) {
00606         LOG(("(%x) nsFtpState::OnStopRequest() cache connect failed.  Reconnecting...\n", this, aStatus));
00607         mTryingCachedControl = PR_FALSE;
00608         Connect();
00609         return NS_OK;
00610     }        
00611 
00612     if (NS_FAILED(aStatus)) // aStatus will be NS_OK if we are sucessfully disconnecing the control connection. 
00613         StopProcessing();
00614 
00615     return NS_OK;
00616 }
00617 
00618 nsresult
00619 nsFtpState::EstablishControlConnection()
00620 {
00621     nsresult rv;
00622 
00623     LOG(("(%x) trying cached control\n", this));
00624         
00625     nsFtpControlConnection* connection;
00626     (void) gFtpHandler->RemoveConnection(mURL, &connection);
00627 
00628     nsRefPtr<TransportEventForwarder> fwd(new TransportEventForwarder(mChannel));
00629     if (connection) {
00630         mControlConnection = connection;
00631         if (mControlConnection->IsAlive())
00632         {
00633             // set stream listener of the control connection to be us.        
00634             (void) mControlConnection->SetStreamListener(NS_STATIC_CAST(nsIStreamListener*, this));
00635             
00636             // read cached variables into us. 
00637             mServerType = mControlConnection->mServerType;           
00638             mPassword   = mControlConnection->mPassword;
00639             mPwd        = mControlConnection->mPwd;
00640             mTryingCachedControl = PR_TRUE;
00641             
00642             // we're already connected to this server, skip login.
00643             mState = FTP_S_PASV;
00644             mResponseCode = 530;  //assume the control connection was dropped.
00645             mControlStatus = NS_OK;
00646             mReceivedControlData = PR_FALSE;  // For this request, we have not.
00647 
00648             // if we succeed, return.  Otherwise, we need to 
00649             // create a transport
00650             rv = mControlConnection->Connect(mProxyInfo, fwd);
00651             if (NS_SUCCEEDED(rv))
00652                 return rv;
00653         }
00654         else 
00655         {
00656             LOG(("(%x) isAlive return false\n", this));
00657             NS_RELEASE(mControlConnection);
00658         }
00659     }
00660 
00661     LOG(("(%x) creating control\n", this));
00662         
00663     mState = FTP_READ_BUF;
00664     mNextState = FTP_S_USER;
00665     
00666     nsCAutoString host;
00667     rv = mURL->GetAsciiHost(host);
00668     if (NS_FAILED(rv)) return rv;
00669 
00670     mControlConnection = new nsFtpControlConnection(host.get(), mPort);
00671     if (!mControlConnection) return NS_ERROR_OUT_OF_MEMORY;
00672 
00673     NS_ADDREF(mControlConnection);
00674         
00675     // Must do it this way 'cuz the channel intercepts the progress notifications.
00676     (void) mControlConnection->SetStreamListener(NS_STATIC_CAST(nsIStreamListener*, this));
00677 
00678     return mControlConnection->Connect(mProxyInfo, fwd);
00679 }
00680 
00681 void 
00682 nsFtpState::MoveToNextState(FTP_STATE nextState)
00683 {
00684     if (NS_FAILED(mInternalError)) 
00685     {
00686         mState = FTP_ERROR;
00687         LOG(("(%x) FAILED (%x)\n", this, mInternalError));
00688     }
00689     else
00690     {
00691         mState = FTP_READ_BUF;
00692         mNextState = nextState;
00693     }  
00694 }
00695 
00696 nsresult
00697 nsFtpState::Process() 
00698 {
00699     nsresult    rv = NS_OK;
00700     PRBool      processingRead = PR_TRUE;
00701     
00702     while (mKeepRunning && processingRead) 
00703     {
00704         switch(mState) 
00705         {
00706           case FTP_COMMAND_CONNECT:
00707               KillControlConnection();
00708               LOG(("(%x) Establishing control connection...", this));
00709               mInternalError = EstablishControlConnection();  // sets mState
00710               if (NS_FAILED(mInternalError)) {
00711                   mState = FTP_ERROR;
00712                   LOG(("(%x) FAILED\n", this));
00713               }
00714               else
00715                   LOG(("(%x) SUCCEEDED\n", this));
00716               break;
00717     
00718           case FTP_READ_BUF:
00719               LOG(("(%x) Waiting for control data (%x)...", this, rv));
00720               processingRead = PR_FALSE;
00721               break;
00722           
00723           case FTP_ERROR: // xx needs more work to handle dropped control connection cases
00724               if ((mTryingCachedControl && mResponseCode == 530 &&
00725                   mInternalError == NS_ERROR_FTP_PASV) ||
00726                   (mResponseCode == 425 &&
00727                   mInternalError == NS_ERROR_FTP_PASV)) {
00728                   // The user was logged out during an pasv operation
00729                   // we want to restart this request with a new control
00730                   // channel.
00731                   mState = FTP_COMMAND_CONNECT;
00732               }
00733               else if (mResponseCode == 421 && 
00734                        mInternalError != NS_ERROR_FTP_LOGIN) {
00735                   // The command channel dropped for some reason.
00736                   // Fire it back up, unless we were trying to login
00737                   // in which case the server might just be telling us
00738                   // that the max number of users has been reached...
00739                   mState = FTP_COMMAND_CONNECT;
00740               } else {
00741                   LOG(("(%x) FTP_ERROR - Calling StopProcessing\n", this));
00742                   rv = StopProcessing();
00743                   NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
00744                   processingRead = PR_FALSE;
00745               }
00746               break;
00747           
00748           case FTP_COMPLETE:
00749               LOG(("(%x) COMPLETE\n", this));
00750               rv = StopProcessing();
00751               NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
00752               processingRead = PR_FALSE;
00753               break;
00754 
00755 // USER           
00756           case FTP_S_USER:
00757             rv = S_user();
00758             
00759             if (NS_FAILED(rv))
00760                 mInternalError = NS_ERROR_FTP_LOGIN;
00761             
00762             MoveToNextState(FTP_R_USER);
00763             break;
00764             
00765           case FTP_R_USER:
00766             mState = R_user();
00767             
00768             if (FTP_ERROR == mState)
00769                 mInternalError = NS_ERROR_FTP_LOGIN;
00770             
00771             break;
00772 // PASS            
00773           case FTP_S_PASS:
00774             rv = S_pass();
00775             
00776             if (NS_FAILED(rv))
00777                 mInternalError = NS_ERROR_FTP_LOGIN;
00778             
00779             MoveToNextState(FTP_R_PASS);
00780             break;
00781             
00782           case FTP_R_PASS:
00783             mState = R_pass();
00784             
00785             if (FTP_ERROR == mState)
00786                 mInternalError = NS_ERROR_FTP_LOGIN;
00787             
00788             break;
00789 // ACCT            
00790           case FTP_S_ACCT:
00791             rv = S_acct();
00792             
00793             if (NS_FAILED(rv))
00794                 mInternalError = NS_ERROR_FTP_LOGIN;
00795             
00796             MoveToNextState(FTP_R_ACCT);
00797             break;
00798             
00799           case FTP_R_ACCT:
00800             mState = R_acct();
00801             
00802             if (FTP_ERROR == mState)
00803                 mInternalError = NS_ERROR_FTP_LOGIN;
00804             
00805             break;
00806 
00807 // SYST            
00808           case FTP_S_SYST:
00809             rv = S_syst();
00810             
00811             if (NS_FAILED(rv))
00812                 mInternalError = NS_ERROR_FTP_LOGIN;
00813             
00814             MoveToNextState(FTP_R_SYST);
00815             break;
00816             
00817           case FTP_R_SYST:
00818             mState = R_syst();
00819             
00820             if (FTP_ERROR == mState)
00821                 mInternalError = NS_ERROR_FTP_LOGIN;
00822 
00823             break;
00824 
00825 // TYPE            
00826           case FTP_S_TYPE:
00827             rv = S_type();
00828             
00829             if (NS_FAILED(rv))
00830                 mInternalError = rv;
00831             
00832             MoveToNextState(FTP_R_TYPE);
00833             break;
00834             
00835           case FTP_R_TYPE:
00836             mState = R_type();
00837             
00838             if (FTP_ERROR == mState)
00839                 mInternalError = NS_ERROR_FAILURE;
00840             
00841             break;
00842 // CWD            
00843           case FTP_S_CWD:
00844             rv = S_cwd();
00845             
00846             if (NS_FAILED(rv))
00847                 mInternalError = NS_ERROR_FTP_CWD;
00848             
00849             MoveToNextState(FTP_R_CWD);
00850             break;
00851             
00852           case FTP_R_CWD:
00853             mState = R_cwd();
00854             
00855             if (FTP_ERROR == mState)
00856                 mInternalError = NS_ERROR_FTP_CWD;
00857             break;
00858        
00859 // LIST
00860           case FTP_S_LIST:
00861             rv = S_list();
00862 
00863             if (rv == NS_ERROR_NOT_RESUMABLE)
00864                 mInternalError = rv;
00865             else if (NS_FAILED(rv))
00866                 mInternalError = NS_ERROR_FTP_CWD;
00867             
00868             MoveToNextState(FTP_R_LIST);
00869             break;
00870 
00871           case FTP_R_LIST:        
00872             mState = R_list();
00873             
00874             if (FTP_ERROR == mState)
00875                 mInternalError = NS_ERROR_FAILURE;
00876 
00877             break;
00878 
00879 // SIZE            
00880           case FTP_S_SIZE:
00881             rv = S_size();
00882             
00883             if (NS_FAILED(rv))
00884                 mInternalError = rv;
00885             
00886             MoveToNextState(FTP_R_SIZE);
00887             break;
00888             
00889           case FTP_R_SIZE: 
00890             mState = R_size();
00891             
00892             if (FTP_ERROR == mState)
00893                 mInternalError = NS_ERROR_FAILURE;
00894             
00895             break;
00896 
00897 // REST        
00898           case FTP_S_REST:
00899             rv = S_rest();
00900             
00901             if (NS_FAILED(rv))
00902                 mInternalError = rv;
00903             
00904             MoveToNextState(FTP_R_REST);
00905             break;
00906             
00907           case FTP_R_REST:
00908             mState = R_rest();
00909             
00910             if (FTP_ERROR == mState)
00911                 mInternalError = NS_ERROR_FAILURE;
00912             
00913             break;
00914 
00915 // MDTM
00916           case FTP_S_MDTM:
00917             rv = S_mdtm();
00918             if (NS_FAILED(rv))
00919                 mInternalError = rv;
00920             MoveToNextState(FTP_R_MDTM);
00921             break;
00922 
00923           case FTP_R_MDTM:
00924             mState = R_mdtm();
00925 
00926             // Don't want to overwrite a more explicit status code
00927             if (FTP_ERROR == mState && NS_SUCCEEDED(mInternalError))
00928                 mInternalError = NS_ERROR_FAILURE;
00929             
00930             break;
00931             
00932 // RETR        
00933           case FTP_S_RETR:
00934             rv = S_retr();
00935             
00936             if (NS_FAILED(rv)) 
00937                 mInternalError = rv;
00938             
00939             MoveToNextState(FTP_R_RETR);
00940             break;
00941             
00942           case FTP_R_RETR:
00943 
00944             mState = R_retr();
00945             
00946             if (FTP_ERROR == mState)
00947                 mInternalError = NS_ERROR_FAILURE;
00948             
00949             break;
00950             
00951 // STOR        
00952           case FTP_S_STOR:
00953             rv = S_stor();
00954 
00955             if (NS_FAILED(rv))
00956                 mInternalError = rv;
00957             
00958             MoveToNextState(FTP_R_STOR);
00959             break;
00960             
00961           case FTP_R_STOR:
00962             mState = R_stor();
00963 
00964             if (FTP_ERROR == mState)
00965                 mInternalError = NS_ERROR_FAILURE;
00966 
00967             break;
00968             
00969 // PASV        
00970           case FTP_S_PASV:
00971             rv = S_pasv();
00972 
00973             if (NS_FAILED(rv))
00974                 mInternalError = NS_ERROR_FTP_PASV;
00975             
00976             MoveToNextState(FTP_R_PASV);
00977             break;
00978             
00979           case FTP_R_PASV:
00980             mState = R_pasv();
00981 
00982             if (FTP_ERROR == mState) 
00983                 mInternalError = NS_ERROR_FTP_PASV;
00984 
00985             break;
00986             
00987 // PWD        
00988           case FTP_S_PWD:
00989             rv = S_pwd();
00990 
00991             if (NS_FAILED(rv))
00992                 mInternalError = NS_ERROR_FTP_PWD;
00993             
00994             MoveToNextState(FTP_R_PWD);
00995             break;
00996             
00997           case FTP_R_PWD:
00998             mState = R_pwd();
00999 
01000             if (FTP_ERROR == mState) 
01001                 mInternalError = NS_ERROR_FTP_PWD;
01002 
01003             break;
01004             
01005           default:
01006             ;
01007             
01008         } 
01009     } 
01010 
01011 return rv;
01012 }
01013 
01015 // STATE METHODS
01017 nsresult
01018 nsFtpState::S_user() {
01019     // some servers on connect send us a 421 or 521.  (84525) (141784)
01020     if ((mResponseCode == 421) || (mResponseCode == 521))
01021         return NS_ERROR_FAILURE;
01022 
01023     nsresult rv;
01024     nsCAutoString usernameStr("USER ");
01025 
01026     if (mAnonymous) {
01027         usernameStr.AppendLiteral("anonymous");
01028     } else {
01029         if (mUsername.IsEmpty()) {
01030             nsCOMPtr<nsIAuthPrompt> prompter;
01031             mChannel->GetCallback(prompter);
01032             if (!prompter)
01033                 return NS_ERROR_NOT_INITIALIZED;
01034 
01035             nsXPIDLString user, passwd;
01036             PRBool retval;
01037             nsCAutoString prePath;
01038             rv = mURL->GetPrePath(prePath);
01039             if (NS_FAILED(rv)) return rv;
01040             NS_ConvertUTF8toUCS2 prePathU(prePath);
01041 
01042             nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01043             if (NS_FAILED(rv)) return rv;
01044 
01045             nsCOMPtr<nsIStringBundle> bundle;
01046             rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
01047             if (NS_FAILED(rv)) return rv;
01048             
01049             nsXPIDLString formatedString;
01050             const PRUnichar *formatStrings[1] = { prePathU.get()};
01051             rv = bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordFor").get(),
01052                                               formatStrings, 1,
01053                                               getter_Copies(formatedString));                   
01054             rv = prompter->PromptUsernameAndPassword(nsnull,
01055                                                      formatedString,
01056                                                      prePathU.get(),
01057                                                      nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
01058                                                      getter_Copies(user), 
01059                                                      getter_Copies(passwd), 
01060                                                      &retval);
01061 
01062             // if the user canceled or didn't supply a username we want to fail
01063             if (!retval || (user && !*user) )
01064                 return NS_ERROR_FAILURE;
01065             mUsername = user;
01066             mPassword = passwd;
01067         }
01068         // XXX Is UTF-8 the best choice?
01069         AppendUTF16toUTF8(mUsername, usernameStr);
01070     }
01071     usernameStr.Append(CRLF);
01072 
01073     return SendFTPCommand(usernameStr);
01074 }
01075 
01076 FTP_STATE
01077 nsFtpState::R_user() {
01078     if (mResponseCode/100 == 3) {
01079         // send off the password
01080         return FTP_S_PASS;
01081     } else if (mResponseCode/100 == 2) {
01082         // no password required, we're already logged in
01083         return FTP_S_SYST;
01084     } else if (mResponseCode/100 == 5) {
01085         // problem logging in. typically this means the server
01086         // has reached it's user limit.
01087         return FTP_ERROR;
01088     } else {
01089         // LOGIN FAILED
01090         if (mAnonymous) {
01091             // we just tried to login anonymously and failed.
01092             // kick back out to S_user() and try again after
01093             // gathering uname/pwd info from the user.
01094             mAnonymous = PR_FALSE;
01095             return FTP_S_USER;
01096         } else {
01097             return FTP_ERROR;
01098         }
01099     }
01100 }
01101 
01102 
01103 nsresult
01104 nsFtpState::S_pass() {
01105     nsresult rv;
01106     nsCAutoString passwordStr("PASS ");
01107 
01108     mResponseMsg = "";
01109 
01110     if (mAnonymous) {
01111         if (!mPassword.IsEmpty()) {
01112             // XXX Is UTF-8 the best choice?
01113             AppendUTF16toUTF8(mPassword, passwordStr);
01114         } else {
01115             nsXPIDLCString anonPassword;
01116             PRBool useRealEmail = PR_FALSE;
01117             nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
01118             if (prefs) {
01119                 rv = prefs->GetBoolPref("advanced.mailftp", &useRealEmail);
01120                 if (NS_SUCCEEDED(rv) && useRealEmail)
01121                     prefs->GetCharPref("network.ftp.anonymous_password", getter_Copies(anonPassword));
01122             }
01123             if (!anonPassword.IsEmpty()) {
01124                 passwordStr.AppendASCII(anonPassword);
01125             } else {
01126                 // We need to default to a valid email address - bug 101027
01127                 // example.com is reserved (rfc2606), so use that
01128                 passwordStr.AppendLiteral("mozilla@example.com");
01129             }
01130         }
01131     } else {
01132         if (mPassword.IsEmpty() || mRetryPass) {
01133             nsCOMPtr<nsIAuthPrompt> prompter;
01134             mChannel->GetCallback(prompter);
01135             if (!prompter)
01136                 return NS_ERROR_NOT_INITIALIZED;
01137 
01138             nsXPIDLString passwd;
01139             PRBool retval;
01140             
01141             nsCAutoString prePath;
01142             rv = mURL->GetPrePath(prePath);
01143             if (NS_FAILED(rv)) return rv;
01144             NS_ConvertUTF8toUCS2 prePathU(prePath);
01145             
01146             nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01147             if (NS_FAILED(rv)) return rv;
01148 
01149             nsCOMPtr<nsIStringBundle> bundle;
01150             rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
01151 
01152             nsXPIDLString formatedString;
01153             const PRUnichar *formatStrings[2] = { mUsername.get(), prePathU.get() };
01154             rv = bundle->FormatStringFromName(NS_LITERAL_STRING("EnterPasswordFor").get(),
01155                                               formatStrings, 2,
01156                                               getter_Copies(formatedString)); 
01157 
01158             if (NS_FAILED(rv)) return rv;
01159             rv = prompter->PromptPassword(nsnull,
01160                                           formatedString,
01161                                           prePathU.get(), 
01162                                           nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
01163                                           getter_Copies(passwd), &retval);
01164 
01165             // we want to fail if the user canceled. Note here that if they want
01166             // a blank password, we will pass it along.
01167             if (!retval)
01168                 return NS_ERROR_FAILURE;
01169 
01170             mPassword = passwd;
01171         }
01172         // XXX Is UTF-8 the best choice?
01173         AppendUTF16toUTF8(mPassword, passwordStr);
01174     }
01175     passwordStr.Append(CRLF);
01176 
01177     return SendFTPCommand(passwordStr);
01178 }
01179 
01180 FTP_STATE
01181 nsFtpState::R_pass() {
01182     if (mResponseCode/100 == 3) {
01183         // send account info
01184         return FTP_S_ACCT;
01185     } else if (mResponseCode/100 == 2) {
01186         // logged in
01187         return FTP_S_SYST;
01188     } else if (mResponseCode == 503) {
01189         // start over w/ the user command.
01190         // note: the password was successful, and it's stored in mPassword
01191         mRetryPass = PR_FALSE;
01192         return FTP_S_USER;
01193     } else if (mResponseCode/100 == 5 || mResponseCode==421) {
01194         // There is no difference between a too-many-users error,
01195         // a wrong-password error, or any other sort of error
01196         // So we need to tell wallet to forget the password if we had one,
01197         // and error out. That will then show the error message, and the
01198         // user can retry if they want to
01199 
01200         if (!mPassword.IsEmpty()) {
01201             nsCOMPtr<nsIPasswordManager> pm = do_GetService(NS_PASSWORDMANAGER_CONTRACTID);
01202             if (pm) {
01203                 nsCAutoString prePath;
01204                 nsresult rv = mURL->GetPrePath(prePath);
01205                 NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to get prepath");
01206                 if (NS_SUCCEEDED(rv)) {
01207                     pm->RemoveUser(prePath, EmptyString());
01208                 }
01209             }
01210         }
01211 
01212         // If the login was anonymous, and it failed, try again with a username
01213         if (mAnonymous) {
01214             mAnonymous = PR_FALSE;
01215             return FTP_S_USER;
01216         }
01217 
01218         mRetryPass = PR_TRUE;
01219         return FTP_ERROR;
01220     }
01221     // unexpected response code
01222     return FTP_ERROR;
01223 }
01224 
01225 nsresult
01226 nsFtpState::S_pwd() {
01227     nsCString pwdStr("PWD" CRLF);
01228     return SendFTPCommand(pwdStr);
01229 }
01230 
01231 FTP_STATE
01232 nsFtpState::R_pwd() {
01233     if (mResponseCode/100 != 2) 
01234         return FTP_ERROR;
01235     nsCAutoString respStr(mResponseMsg);
01236     PRInt32 pos = respStr.FindChar('"');
01237     if (pos > -1) {
01238         respStr.Cut(0,pos+1);
01239         pos = respStr.FindChar('"');
01240         if (pos > -1) {
01241             respStr.Truncate(pos);
01242             if (mServerType == FTP_VMS_TYPE)
01243                 ConvertDirspecFromVMS(respStr);
01244             if (respStr.Last() != '/')
01245                 respStr.Append('/');
01246             mPwd = respStr;
01247         }
01248     }
01249     return FTP_S_TYPE;
01250 }
01251 
01252 nsresult
01253 nsFtpState::S_syst() {
01254     nsCString systString("SYST" CRLF);
01255     return SendFTPCommand( systString );
01256 }
01257 
01258 FTP_STATE
01259 nsFtpState::R_syst() {
01260     if (mResponseCode/100 == 2) {
01261         if (( mResponseMsg.Find("L8") > -1) || 
01262             ( mResponseMsg.Find("UNIX") > -1) || 
01263             ( mResponseMsg.Find("BSD") > -1) ||
01264             ( mResponseMsg.Find("MACOS Peter's Server") > -1) ||
01265             ( mResponseMsg.Find("MACOS WebSTAR FTP") > -1) ||
01266             ( mResponseMsg.Find("MVS") > -1) ||
01267             ( mResponseMsg.Find("OS/390") > -1))
01268         {
01269             mServerType = FTP_UNIX_TYPE;
01270         }
01271         else if ( ( mResponseMsg.Find("WIN32", PR_TRUE) > -1) ||
01272                   ( mResponseMsg.Find("windows", PR_TRUE) > -1) )
01273         {
01274             mServerType = FTP_NT_TYPE;
01275         }
01276         else if ( mResponseMsg.Find("OS/2", PR_TRUE) > -1)
01277         {
01278             mServerType = FTP_OS2_TYPE;
01279         }
01280         else if ( mResponseMsg.Find("VMS", PR_TRUE) > -1)
01281         {
01282             mServerType = FTP_VMS_TYPE;
01283         }
01284         else
01285         {
01286             NS_ASSERTION(0, "Server type list format unrecognized.");
01287             // Guessing causes crashes.
01288             // (Of course, the parsing code should be more robust...)
01289             nsresult rv;
01290             nsCOMPtr<nsIStringBundleService> bundleService =
01291                 do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01292             if (NS_FAILED(rv)) return FTP_ERROR;
01293 
01294             nsCOMPtr<nsIStringBundle> bundle;
01295             rv = bundleService->CreateBundle(NECKO_MSGS_URL,
01296                                              getter_AddRefs(bundle));
01297             if (NS_FAILED(rv)) return FTP_ERROR;
01298             
01299             nsXPIDLString formatedString;
01300             PRUnichar* ucs2Response = ToNewUnicode(mResponseMsg);
01301             const PRUnichar *formatStrings[1] = { ucs2Response };
01302             rv = bundle->FormatStringFromName(NS_LITERAL_STRING("UnsupportedFTPServer").get(),
01303                                               formatStrings, 1,
01304                                               getter_Copies(formatedString));
01305             nsMemory::Free(ucs2Response);
01306             if (NS_FAILED(rv)) return FTP_ERROR;
01307 
01308             // XXX(darin): this code should not be dictating UI like this!
01309             nsCOMPtr<nsIPrompt> prompter;
01310             mChannel->GetCallback(prompter);
01311             if (prompter)
01312                 prompter->Alert(nsnull, formatedString.get());
01313             
01314             // since we just alerted the user, clear mResponseMsg,
01315             // which is displayed to the user.
01316             mResponseMsg = "";
01317             return FTP_ERROR;
01318         }
01319         
01320         return FTP_S_PWD;
01321     }
01322 
01323     if (mResponseCode/100 == 5) {   
01324         // server didn't like the SYST command.  Probably (500, 501, 502)
01325         // No clue.  We will just hope it is UNIX type server.
01326         mServerType = FTP_UNIX_TYPE;
01327 
01328         return FTP_S_PWD;
01329     }
01330     return FTP_ERROR;
01331 }
01332 
01333 nsresult
01334 nsFtpState::S_acct() {
01335     nsCString acctString("ACCT noaccount" CRLF);
01336     return SendFTPCommand(acctString);
01337 }
01338 
01339 FTP_STATE
01340 nsFtpState::R_acct() {
01341     if (mResponseCode/100 == 2)
01342         return FTP_S_SYST;
01343     else
01344         return FTP_ERROR;
01345 }
01346 
01347 nsresult
01348 nsFtpState::S_type() {
01349     nsCString typeString("TYPE I" CRLF);
01350     return SendFTPCommand(typeString);
01351 }
01352 
01353 FTP_STATE
01354 nsFtpState::R_type() {
01355     if (mResponseCode/100 != 2) 
01356         return FTP_ERROR;
01357     
01358     return FTP_S_PASV;
01359 }
01360 
01361 nsresult
01362 nsFtpState::S_cwd() {
01363     nsCAutoString cwdStr;
01364     if (mAction != PUT)
01365         cwdStr = mPath;
01366     if (cwdStr.IsEmpty() || cwdStr.First() != '/')
01367         cwdStr.Insert(mPwd,0);
01368     if (mServerType == FTP_VMS_TYPE)
01369         ConvertDirspecToVMS(cwdStr);
01370     cwdStr.Insert("CWD ",0);
01371     cwdStr.Append(CRLF);
01372 
01373     return SendFTPCommand(cwdStr);
01374 }
01375 
01376 FTP_STATE
01377 nsFtpState::R_cwd() {
01378     if (mResponseCode/100 == 2) {
01379         if (mAction == PUT)
01380             return FTP_S_STOR;
01381         
01382         return FTP_S_LIST;
01383     }
01384     
01385     return FTP_ERROR;
01386 }
01387 
01388 nsresult
01389 nsFtpState::S_size() {
01390     nsCAutoString sizeBuf(mPath);
01391     if (sizeBuf.IsEmpty() || sizeBuf.First() != '/')
01392         sizeBuf.Insert(mPwd,0);
01393     if (mServerType == FTP_VMS_TYPE)
01394         ConvertFilespecToVMS(sizeBuf);
01395     sizeBuf.Insert("SIZE ",0);
01396     sizeBuf.Append(CRLF);
01397 
01398     return SendFTPCommand(sizeBuf);
01399 }
01400 
01401 FTP_STATE
01402 nsFtpState::R_size() {
01403     if (mResponseCode/100 == 2) {
01404         PR_sscanf(mResponseMsg.get() + 4, "%llu", &mFileSize);
01405         PRUint32 size32;
01406         LL_L2UI(size32, mFileSize);
01407         if (NS_FAILED(mChannel->SetContentLength(size32))) return FTP_ERROR;
01408 
01409         // Set the 64-bit length too
01410         mChannel->SetPropertyAsUint64(NS_CHANNEL_PROP_CONTENT_LENGTH,
01411                                       mFileSize);
01412 
01413         mDRequestForwarder->SetFileSize(mFileSize);
01414     }
01415 
01416     // We may want to be able to resume this
01417     return FTP_S_MDTM;
01418 }
01419 
01420 nsresult
01421 nsFtpState::S_mdtm() {
01422     nsCAutoString mdtmBuf(mPath);
01423     if (mdtmBuf.IsEmpty() || mdtmBuf.First() != '/')
01424         mdtmBuf.Insert(mPwd,0);
01425     if (mServerType == FTP_VMS_TYPE)
01426         ConvertFilespecToVMS(mdtmBuf);
01427     mdtmBuf.Insert("MDTM ",0);
01428     mdtmBuf.Append(CRLF);
01429 
01430     return SendFTPCommand(mdtmBuf);
01431 }
01432 
01433 FTP_STATE
01434 nsFtpState::R_mdtm() {
01435     if (mResponseCode == 213) {
01436         mResponseMsg.Cut(0,4);
01437         mResponseMsg.Trim(" \t\r\n");
01438         // yyyymmddhhmmss
01439         if (mResponseMsg.Length() != 14) {
01440             NS_ASSERTION(mResponseMsg.Length() == 14, "Unknown MDTM response");
01441         } else {
01442             mModTime = mResponseMsg;
01443         }
01444     }
01445 
01446     mEntityID.Truncate();
01447     mEntityID.AppendInt(PRInt64(mFileSize));
01448     mEntityID.Append('/');
01449     mEntityID.Append(mModTime);
01450     mDRequestForwarder->SetEntityID(mEntityID);
01451 
01452     // if we tried downloading this, lets try restarting it...
01453     if (mDRequestForwarder && mDRequestForwarder->GetBytesTransfered() > 0) {
01454         mStartPos = mDRequestForwarder->GetBytesTransfered();
01455         return FTP_S_REST;
01456     }
01457     
01458     // We weren't asked to resume
01459     if (mStartPos == LL_MAXUINT)
01460         return FTP_S_RETR;
01461 
01462     //if (our entityID == supplied one (if any))
01463     if (mSuppliedEntityID.IsEmpty() ||
01464         mEntityID.Equals(mSuppliedEntityID))
01465     {
01466         return FTP_S_REST;
01467     } else {
01468         mInternalError = NS_ERROR_ENTITY_CHANGED;
01469         mResponseMsg.Truncate();
01470         return FTP_ERROR;
01471     }
01472 }
01473 
01474 nsresult 
01475 nsFtpState::SetContentType()
01476 {
01477     return mChannel->SetContentType(NS_LITERAL_CSTRING(APPLICATION_HTTP_INDEX_FORMAT));
01478 }
01479 
01480 nsresult
01481 nsFtpState::S_list() {
01482     nsresult rv;
01483 
01484     if (!mDRequestForwarder) 
01485         return NS_ERROR_FAILURE;
01486 
01487     rv = SetContentType();
01488     
01489     if (NS_FAILED(rv)) 
01490         return FTP_ERROR;
01491     
01492     // save off the server type if we are caching.
01493     if(mCacheEntry) {
01494         nsCAutoString serverType;
01495         serverType.AppendInt(mServerType);
01496         (void) mCacheEntry->SetMetaDataElement("servertype", serverType.get());
01497     }
01498 
01499     nsCOMPtr<nsIStreamListener> converter;
01500     
01501     rv = BuildStreamConverter(getter_AddRefs(converter));
01502     if (NS_FAILED(rv)) {
01503         // clear mResponseMsg which is displayed to the user.
01504         // TODO: we should probably set this to something 
01505         // meaningful.
01506         mResponseMsg = "";
01507         return rv;
01508     }
01509     
01510     // the data forwarder defaults to sending notifications
01511     // to the channel.  Lets hijack and send the notifications
01512     // to the stream converter.
01513     mDRequestForwarder->SetStreamListener(converter);
01514     mDRequestForwarder->SetCacheEntry(mCacheEntry, PR_TRUE);
01515     // dir listings aren't resumable
01516     NS_ASSERTION(mSuppliedEntityID.IsEmpty(),
01517                  "Entity ID given to directory request");
01518     NS_ASSERTION(mStartPos == LL_MAXUINT || mStartPos == nsUint64(0),
01519                  "Non-initial start position given to directory request");
01520     if (!mSuppliedEntityID.IsEmpty() || (mStartPos != LL_MAXUINT && mStartPos != nsUint64(0))) {
01521         // If we reach this code, then the caller is in error
01522         return NS_ERROR_NOT_RESUMABLE;
01523     }
01524 
01525     mDRequestForwarder->SetEntityID(EmptyCString());
01526 
01527     nsCAutoString listString;
01528     if (mServerType == FTP_VMS_TYPE)
01529         listString.AssignLiteral("LIST *.*;0" CRLF);
01530     else
01531         listString.AssignLiteral("LIST" CRLF);
01532 
01533     return SendFTPCommand(listString);
01534 
01535 }
01536 
01537 FTP_STATE
01538 nsFtpState::R_list() {
01539     if (mResponseCode/100 == 1) {
01540         nsresult rv = mDPipeRequest->Resume();
01541         if (NS_FAILED(rv)) {
01542             LOG(("(%x) dataPipe->Resume (rv=%x)\n", this, rv));
01543             return FTP_ERROR;
01544         }
01545         return FTP_READ_BUF;
01546     }
01547 
01548     if (mResponseCode/100 == 2) {
01549         //(DONE)
01550         mNextState = FTP_COMPLETE;
01551         return FTP_COMPLETE;
01552     }
01553     return FTP_ERROR;
01554 }
01555 
01556 nsresult
01557 nsFtpState::S_retr() {
01558     nsresult rv = NS_OK;
01559     nsCAutoString retrStr(mPath);
01560     if (retrStr.IsEmpty() || retrStr.First() != '/')
01561         retrStr.Insert(mPwd,0);
01562     if (mServerType == FTP_VMS_TYPE)
01563         ConvertFilespecToVMS(retrStr);
01564     retrStr.Insert("RETR ",0);
01565     retrStr.Append(CRLF);
01566     
01567     if (!mDRequestForwarder)
01568         return NS_ERROR_FAILURE;
01569     
01570     rv = SendFTPCommand(retrStr);
01571     return rv;
01572 }
01573 
01574 FTP_STATE
01575 nsFtpState::R_retr() {
01576     if (mResponseCode/100 == 2) {
01577         //(DONE)
01578         mNextState = FTP_COMPLETE;
01579         return FTP_COMPLETE;
01580     }
01581 
01582     if (mResponseCode/100 == 1) {
01583         // We're going to grab a file, not a directory. So we need to clear
01584         // any cache entry, otherwise we'll have problems reading it later.
01585         // See bug 122548
01586         if (mCacheEntry) {
01587             (void)mCacheEntry->Doom();
01588             mCacheEntry = nsnull;
01589         }
01590         nsresult rv = mDPipeRequest->Resume();
01591         if (NS_FAILED(rv)) {
01592             LOG(("(%x) dataPipe->Resume (rv=%x)\n", this, rv));
01593             return FTP_ERROR;
01594         }
01595         return FTP_READ_BUF;
01596     }
01597     
01598     // These error codes are related to problems with the connection.  
01599     // If we encounter any at this point, do not try CWD and abort.
01600     if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
01601         return FTP_ERROR;
01602 
01603     if (mResponseCode/100 == 5) {
01604         mRETRFailed = PR_TRUE;
01605         mDRequestForwarder->SetRetrying(PR_TRUE);
01606         return FTP_S_PASV;
01607     }
01608 
01609     return FTP_S_CWD;
01610 }
01611 
01612 
01613 nsresult
01614 nsFtpState::S_rest() {
01615     
01616     nsCAutoString restString("REST ");
01617     // The PRInt64 cast is needed to avoid ambiguity
01618     restString.AppendInt(PRInt64(PRUint64(mStartPos)), 10);
01619     restString.Append(CRLF);
01620 
01621     return SendFTPCommand(restString);
01622 }
01623 
01624 FTP_STATE
01625 nsFtpState::R_rest() {
01626     if (mResponseCode/100 == 4) {
01627         // If REST fails, then we can't resume
01628         mEntityID.Truncate();
01629 
01630         mInternalError = NS_ERROR_NOT_RESUMABLE;
01631         mResponseMsg.Truncate();
01632 
01633         return FTP_ERROR;
01634     }
01635    
01636     return FTP_S_RETR; 
01637 }
01638 
01639 nsresult
01640 nsFtpState::S_stor() {
01641     NS_ASSERTION(mWriteStream, "we're trying to upload without any data");
01642 
01643     if (!mWriteStream)
01644         return NS_ERROR_FAILURE;
01645 
01646     NS_ASSERTION(mAction == PUT, "Wrong state to be here");
01647     
01648     nsCAutoString storStr;
01649     nsresult rv;
01650     nsCOMPtr<nsIURL> aURL(do_QueryInterface(mURL, &rv));
01651     if (NS_FAILED(rv)) return rv;
01652 
01653     rv = aURL->GetFilePath(storStr);
01654     if (NS_FAILED(rv)) return rv;
01655     NS_ASSERTION(!storStr.IsEmpty(), "What does it mean to store a empty path");
01656         
01657     if (storStr.First() == '/')
01658     {
01659         // kill the first slash since we want to be relative to CWD.
01660         storStr.Cut(0,1);
01661     }
01662     if (mServerType == FTP_VMS_TYPE) {
01663         ConvertFilespecToVMS(storStr);
01664     }
01665     NS_UnescapeURL(storStr);
01666     storStr.Insert("STOR ",0);
01667     storStr.Append(CRLF);
01668 
01669     return SendFTPCommand(storStr);
01670 }
01671 
01672 FTP_STATE
01673 nsFtpState::R_stor() {
01674     if (mResponseCode/100 == 2) {
01675         //(DONE)
01676         mNextState = FTP_COMPLETE;
01677         return FTP_COMPLETE;
01678     }
01679 
01680     if (mResponseCode/100 == 1) {
01681         LOG(("(%x) writing on Data Transport\n", this));
01682         return FTP_READ_BUF;
01683     }
01684 
01685    return FTP_ERROR;
01686 }
01687 
01688 
01689 nsresult
01690 nsFtpState::S_pasv() {
01691     nsresult rv;
01692 
01693     if (mAddressChecked == PR_FALSE) {
01694         // Find socket address
01695         mAddressChecked = PR_TRUE;
01696         nsITransport *controlSocket = mControlConnection->Transport();
01697         if (!controlSocket) return FTP_ERROR;
01698         nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket, &rv);
01699         
01700         if (sTrans) {
01701             PRNetAddr addr;
01702             rv = sTrans->GetPeerAddr(&addr);
01703             if (NS_SUCCEEDED(rv)) {
01704                 mServerIsIPv6 = addr.raw.family == PR_AF_INET6 &&
01705                                 !PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped);
01706                 PR_NetAddrToString(&addr, mServerAddress,
01707                                    sizeof(mServerAddress));
01708             }
01709         }
01710     }
01711 
01712     const char * string;
01713     if (mServerIsIPv6)
01714         string = "EPSV" CRLF;
01715     else
01716         string = "PASV" CRLF;
01717 
01718     nsCString pasvString(string);
01719     return SendFTPCommand(pasvString);
01720     
01721 }
01722 
01723 FTP_STATE
01724 nsFtpState::R_pasv() {
01725     nsresult rv;
01726     PRInt32 port;
01727 
01728     if (mResponseCode/100 != 2)  {
01729         return FTP_ERROR;
01730     }
01731 
01732     char *response = ToNewCString(mResponseMsg);
01733     if (!response) return FTP_ERROR;
01734     char *ptr = response;
01735 
01736     // Make sure to ignore the address in the PASV response (bug 370559)
01737     if (mServerIsIPv6) {
01738         // The returned string is of the form
01739         // text (|||ppp|)
01740         // Where '|' can be any single character
01741         char delim;
01742         while (*ptr && *ptr != '(') ptr++;
01743         if (*ptr++ != '(') {
01744             return FTP_ERROR;
01745         }
01746         delim = *ptr++;
01747         if (!delim || *ptr++ != delim || *ptr++ != delim || 
01748             *ptr < '0' || *ptr > '9') {
01749             return FTP_ERROR;
01750         }
01751         port = 0;
01752         do {
01753             port = port * 10 + *ptr++ - '0';
01754         } while (*ptr >= '0' && *ptr <= '9');
01755         if (*ptr++ != delim || *ptr != ')') {
01756             return FTP_ERROR;
01757         }
01758     } else {
01759         // The returned address string can be of the form
01760         // (xxx,xxx,xxx,xxx,ppp,ppp) or
01761         //  xxx,xxx,xxx,xxx,ppp,ppp (without parens)
01762         PRInt32 h0, h1, h2, h3, p0, p1;
01763 
01764         PRUint32 fields = 0;
01765         // First try with parens
01766         while (*ptr && *ptr != '(')
01767             ++ptr;
01768         if (*ptr) {
01769             ++ptr;
01770             fields = PR_sscanf(ptr, 
01771                                "%ld,%ld,%ld,%ld,%ld,%ld",
01772                                &h0, &h1, &h2, &h3, &p0, &p1);
01773         }
01774         if (!*ptr || fields < 6) {
01775             // OK, lets try w/o parens
01776             ptr = response;
01777             while (*ptr && *ptr != ',')
01778                 ++ptr;
01779             if (*ptr) {
01780                 // backup to the start of the digits
01781                 do {
01782                     ptr--;
01783                 } while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
01784                 ptr++; // get back onto the numbers
01785                 fields = PR_sscanf(ptr, 
01786                                    "%ld,%ld,%ld,%ld,%ld,%ld",
01787                                    &h0, &h1, &h2, &h3, &p0, &p1);
01788             }
01789         }
01790 
01791         NS_ASSERTION(fields == 6, "Can't parse PASV response");
01792         if (fields < 6) {
01793             return FTP_ERROR;
01794         }
01795 
01796         port = ((PRInt32) (p0<<8)) + p1;
01797     }
01798 
01799     nsMemory::Free(response);
01800 
01801     PRBool newDataConn = PR_TRUE;
01802     if (mDPipeRequest) {
01803         // Reuse this connection only if its still alive, and the port
01804         // is the same
01805 
01806         if (mDPipe) {
01807             PRInt32 oldPort;
01808             nsresult rv = mDPipe->GetPort(&oldPort);
01809             if (NS_SUCCEEDED(rv)) {
01810                 if (oldPort == port) {
01811                     PRBool isAlive;
01812                     if (NS_SUCCEEDED(mDPipe->IsAlive(&isAlive)) && isAlive)
01813                         newDataConn = PR_FALSE;
01814                 }
01815             }
01816         }
01817 
01818         if (newDataConn) {
01819             mDPipeRequest->Cancel(NS_ERROR_ABORT);
01820             mDPipeRequest = 0;
01821             mDPipe = 0;
01822         } else {
01823             mDRequestForwarder->SetRetrying(PR_FALSE);
01824         }
01825     }
01826 
01827     if (newDataConn) {
01828         // now we know where to connect our data channel
01829         nsCOMPtr<nsISocketTransportService> sts = do_GetService(kSocketTransportServiceCID, &rv);
01830         
01831         rv =  sts->CreateTransport(nsnull, 0,
01832                                    nsDependentCString(mServerAddress), port, mProxyInfo,
01833                                    getter_AddRefs(mDPipe)); // the data socket
01834         if (NS_FAILED(rv)) return FTP_ERROR;
01835         
01836         LOG(("(%x) Created Data Transport (%s:%x)\n", this, mServerAddress, port));
01837         
01838         if (!mDRequestForwarder) {
01839             mDRequestForwarder = new DataRequestForwarder;
01840             if (!mDRequestForwarder) return FTP_ERROR;
01841             NS_ADDREF(mDRequestForwarder);
01842             
01843             rv = mDRequestForwarder->Init(mChannel);
01844             if (NS_FAILED(rv)){
01845                 LOG(("(%x) forwarder->Init failed (rv=%x)\n", this, rv));
01846                 return FTP_ERROR;
01847             }
01848         }
01849         
01850         mWaitingForDConn = PR_TRUE;
01851 
01852         // hook ourself up as a proxy for status notifications
01853         nsCOMPtr<nsIEventQueue> eventQ;
01854         rv = NS_GetCurrentEventQ(getter_AddRefs(eventQ));
01855         if (NS_FAILED(rv)){
01856             LOG(("(%x) NS_GetCurrentEventQ failed (rv=%x)\n", this, rv));
01857             return FTP_ERROR;
01858         }
01859         rv = mDPipe->SetEventSink(NS_STATIC_CAST(nsITransportEventSink*, mDRequestForwarder), eventQ);
01860         if (NS_FAILED(rv)){
01861             LOG(("(%x) forwarder->SetEventSink failed (rv=%x)\n", this, rv));
01862             return FTP_ERROR;
01863         }
01864 
01865         if (mAction == PUT) {
01866             NS_ASSERTION(!mRETRFailed, "Failed before uploading");
01867             mDRequestForwarder->Uploading(PR_TRUE, mWriteCount);
01868 
01869             // nsIUploadChannel requires the upload stream to support ReadSegments.
01870             // therefore, we can open an unbuffered socket output stream.
01871             nsCOMPtr<nsIOutputStream> output;
01872             rv = mDPipe->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0,
01873                                           getter_AddRefs(output));
01874             if (NS_FAILED(rv)) return FTP_ERROR;
01875 
01876             // perform the data copy on the socket transport thread.  we do this
01877             // because "output" is a socket output stream, so the result is that
01878             // all work will be done on the socket transport thread.
01879             nsCOMPtr<nsIEventTarget> stEventTarget = do_GetService(kSocketTransportServiceCID, &rv);
01880             if (NS_FAILED(rv)) return FTP_ERROR;
01881             
01882             nsCOMPtr<nsIAsyncStreamCopier> copier;
01883             rv = NS_NewAsyncStreamCopier(getter_AddRefs(copier),
01884                                          mWriteStream,
01885                                          output,
01886                                          stEventTarget,
01887                                          PR_TRUE,   // mWriteStream is buffered
01888                                          PR_FALSE); // output is NOT buffered
01889             if (NS_FAILED(rv)) return FTP_ERROR;
01890         
01891             rv = copier->AsyncCopy(mDRequestForwarder, nsnull);
01892             if (NS_FAILED(rv)) return FTP_ERROR;
01893 
01894             // hold a reference to the copier so we can cancel it if necessary.
01895             mDPipeRequest = copier;
01896 
01897             // update the current working directory before sending the STOR
01898             // command.  this is needed since we might be reusing a control
01899             // connection.
01900             return FTP_S_CWD;
01901         }
01902 
01903         //
01904         // else, we are reading from the data connection...
01905         //
01906 
01907         // open a buffered, asynchronous socket input stream
01908         nsCOMPtr<nsIInputStream> input;
01909         rv = mDPipe->OpenInputStream(0,
01910                                      FTP_DATA_CHANNEL_SEG_SIZE,
01911                                      FTP_DATA_CHANNEL_SEG_COUNT,
01912                                      getter_AddRefs(input));
01913         if (NS_FAILED(rv)) {
01914             LOG(("(%x) OpenInputStream failed (rv=%x)\n", this, rv));
01915             return FTP_ERROR;
01916         }
01917 
01918         // pump data to the request forwarder...
01919         nsCOMPtr<nsIInputStreamPump> pump;
01920         rv = NS_NewInputStreamPump(getter_AddRefs(pump), input, nsInt64(-1),
01921                                    nsInt64(-1), 0, 0, PR_TRUE);
01922         if (NS_FAILED(rv)) {
01923             LOG(("(%x) NS_NewInputStreamPump failed (rv=%x)\n", this, rv));
01924             return FTP_ERROR;
01925         }
01926         
01927         rv = pump->AsyncRead(mDRequestForwarder, nsnull);
01928         if (NS_FAILED(rv)) {
01929             LOG(("(%x) AsyncRead failed (rv=%x)\n", this, rv));
01930             return FTP_ERROR;
01931         }
01932 
01933         // hold a reference to the input stream pump so we can cancel it.
01934         mDPipeRequest = pump;
01935 
01936         // Suspend the read
01937         // If we don't do this, then the remote server could close the
01938         // connection before we get the error message, and then we process the
01939         // onstop as if it was from the real data connection
01940         rv = mDPipeRequest->Suspend();
01941         if (NS_FAILED(rv)){
01942             LOG(("(%x) dataPipe->Suspend failed (rv=%x)\n", this, rv));
01943             return FTP_ERROR;
01944         }
01945     }
01946 
01947     if (mRETRFailed)
01948         return FTP_S_CWD;
01949     return FTP_S_SIZE;
01950 }
01951 
01952 
01954 // nsIRequest methods:
01955 
01956 NS_IMETHODIMP
01957 nsFtpState::GetName(nsACString &result)
01958 {
01959     return mURL->GetSpec(result);
01960 }
01961 
01962 NS_IMETHODIMP
01963 nsFtpState::IsPending(PRBool *result)
01964 {
01965     nsresult rv = NS_OK;
01966     *result = PR_FALSE;
01967     
01968     nsIRequest *request = mControlConnection->ReadRequest();
01969     if (request)
01970         rv = request->IsPending(result);
01971 
01972     return rv;
01973 }
01974 
01975 NS_IMETHODIMP
01976 nsFtpState::GetStatus(nsresult *status)
01977 {
01978     *status = mInternalError;
01979     return NS_OK;
01980 }
01981 
01982 NS_IMETHODIMP
01983 nsFtpState::Cancel(nsresult status)
01984 {
01985     NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
01986     LOG(("(%x) nsFtpState::Cancel() rv=%x\n", this, status));
01987 
01988     // we should try to recover the control connection....
01989     if (NS_SUCCEEDED(mControlStatus))
01990         mControlStatus = status;
01991 
01992     if (mKeepRunning)
01993         (void) StopProcessing();   
01994     return NS_OK;
01995 }
01996 
01997 NS_IMETHODIMP
01998 nsFtpState::Suspend(void)
01999 {
02000     nsresult rv = NS_OK;
02001     
02002     if (!mControlConnection)
02003         return NS_ERROR_FAILURE;
02004 
02005     // suspending the underlying socket transport will
02006     // cause the FTP state machine to "suspend" when it
02007     // tries to use the transport. May not be granular 
02008     // enough.
02009     if (mSuspendCount < 1) {
02010         mSuspendCount++;
02011 
02012         // only worry about the read request.
02013         nsIRequest *request = mControlConnection->ReadRequest();
02014         if (request) {
02015             rv = request->Suspend();
02016             if (NS_FAILED(rv)) return rv;
02017         }
02018 
02019         if (mDPipeRequest)
02020             rv = mDPipeRequest->Suspend();
02021     }
02022 
02023     return rv;
02024 }
02025 
02026 NS_IMETHODIMP
02027 nsFtpState::Resume(void)
02028 {
02029     nsresult rv = NS_ERROR_FAILURE;
02030     
02031     // resuming the underlying socket transports will
02032     // cause the FTP state machine to unblock and 
02033     // go on about it's business.
02034     if (mSuspendCount) {
02035 
02036         PRBool dataAlive = PR_FALSE;
02037 
02038         if (mDPipe) 
02039             mDPipe->IsAlive(&dataAlive);            
02040             
02041         if (mDPipe && dataAlive && mControlConnection->IsAlive())
02042         {
02043             nsIRequest *controlRequest = mControlConnection->ReadRequest();
02044             NS_ASSERTION(controlRequest, "where did my request go!");
02045             
02046             controlRequest->Resume();
02047             rv = mDPipeRequest->Resume();
02048         }
02049         else
02050         {
02051             // control or data connection went down.  need to reconnect.  
02052             // if we were downloading, we need to perform REST.
02053             rv = EstablishControlConnection();
02054         }
02055     }
02056     mSuspendCount--;
02057     return rv;
02058 }
02059 
02060 NS_IMETHODIMP
02061 nsFtpState::GetLoadGroup(nsILoadGroup **aLoadGroup)
02062 {
02063     *aLoadGroup = nsnull;
02064     return NS_OK;
02065 }
02066 
02067 NS_IMETHODIMP
02068 nsFtpState::SetLoadGroup(nsILoadGroup *aLoadGroup)
02069 {
02070     return NS_ERROR_NOT_IMPLEMENTED;
02071 }
02072 
02073 NS_IMETHODIMP
02074 nsFtpState::GetLoadFlags(nsLoadFlags *aLoadFlags)
02075 {
02076     *aLoadFlags = nsIRequest::LOAD_NORMAL;
02077     return NS_OK;
02078 }
02079 
02080 NS_IMETHODIMP
02081 nsFtpState::SetLoadFlags(nsLoadFlags aLoadFlags)
02082 {
02083     return NS_ERROR_NOT_IMPLEMENTED;
02084 }
02085 
02086 // This really really needs to be somewhere else
02087 static inline PRUint32
02088 PRTimeToSeconds(PRTime t_usec)
02089 {
02090     PRTime usec_per_sec;
02091     PRUint32 t_sec;
02092     LL_I2L(usec_per_sec, PR_USEC_PER_SEC);
02093     LL_DIV(t_usec, t_usec, usec_per_sec);
02094     LL_L2I(t_sec, t_usec);
02095     return t_sec;
02096 }
02097 
02098 #define NowInSeconds() PRTimeToSeconds(PR_Now())
02099 
02100 PRUint32 nsFtpState::mSessionStartTime = NowInSeconds();
02101 
02102 /* Is this cache entry valid to use for reading?
02103  * Since we make up an expiration time for ftp, use the following rules:
02104  * (see bug 103726)
02105  *
02106  * LOAD_FROM_CACHE                    : always use cache entry, even if expired
02107  * LOAD_BYPASS_CACHE                  : overwrite cache entry
02108  * LOAD_NORMAL|VALIDATE_ALWAYS        : overwrite cache entry
02109  * LOAD_NORMAL                        : honor expiration time
02110  * LOAD_NORMAL|VALIDATE_ONCE_PER_SESSION : overwrite cache entry if first access 
02111  *                                         this session, otherwise use cache entry 
02112  *                                         even if expired.
02113  * LOAD_NORMAL|VALIDATE_NEVER         : always use cache entry, even if expired
02114  *
02115  * Note that in theory we could use the mdtm time on the directory
02116  * In practice, the lack of a timezone plus the general lack of support for that
02117  * on directories means that its not worth it, I suspect. Revisit if we start
02118  * caching files - bbaetz
02119  */
02120 PRBool
02121 nsFtpState::CanReadEntry()
02122 {
02123     nsCacheAccessMode access;
02124     nsresult rv = mCacheEntry->GetAccessGranted(&access);
02125     if (NS_FAILED(rv)) return PR_FALSE;
02126     
02127     // If I'm not granted read access, then I can't reuse it...
02128     if (!(access & nsICache::ACCESS_READ))
02129         return PR_FALSE;
02130 
02131     nsLoadFlags flags;
02132     rv = mChannel->GetLoadFlags(&flags);
02133     if (NS_FAILED(rv)) return PR_FALSE;
02134 
02135     if (flags & LOAD_FROM_CACHE)
02136         return PR_TRUE;
02137 
02138     if (flags & LOAD_BYPASS_CACHE)
02139         return PR_FALSE;
02140     
02141     if (flags & VALIDATE_ALWAYS)
02142         return PR_FALSE;
02143     
02144     PRUint32 time;
02145     if (flags & VALIDATE_ONCE_PER_SESSION) {
02146         rv = mCacheEntry->GetLastModified(&time);
02147         if (NS_FAILED(rv)) return PR_FALSE;
02148         return (mSessionStartTime > time);
02149     }
02150 
02151     if (flags & VALIDATE_NEVER)
02152         return PR_TRUE;
02153 
02154     // OK, now we just check the expiration time as usual
02155     rv = mCacheEntry->GetExpirationTime(&time);
02156     if (NS_FAILED(rv)) return rv;
02157     return (NowInSeconds() <= time);
02158 }
02159 
02160 nsresult
02161 nsFtpState::Init(nsFTPChannel* aChannel,
02162                  nsICacheEntryDescriptor* cacheEntry,
02163                  nsIProxyInfo* proxyInfo,
02164                  PRUint64 startPos,
02165                  const nsACString& entity) 
02166 {
02167     mKeepRunning = PR_TRUE;
02168     mCacheEntry = cacheEntry;
02169     mProxyInfo = proxyInfo;
02170     mStartPos = startPos;
02171     mSuppliedEntityID = entity;
02172     
02173     // parameter validation
02174     NS_ASSERTION(aChannel, "FTP: needs a channel");
02175 
02176     mChannel = aChannel; // a straight ref ptr to the channel
02177 
02178     nsresult rv = aChannel->GetURI(getter_AddRefs(mURL));
02179     if (NS_FAILED(rv))
02180         return rv;
02181         
02182     if (mCacheEntry) {
02183         if (CanReadEntry()) {
02184             // XXX - all this code assumes that we only cache directories
02185             // If we start caching files, this needs to be updated
02186 
02187             // make sure the channel knows wassup
02188             SetContentType();
02189             
02190             NS_ASSERTION(!mDRequestForwarder, "there should not be a data forwarder");
02191             mDRequestForwarder = new DataRequestForwarder;
02192             if (!mDRequestForwarder) return NS_ERROR_OUT_OF_MEMORY;
02193             NS_ADDREF(mDRequestForwarder);
02194 
02195             rv = mDRequestForwarder->Init(mChannel);
02196             
02197             nsXPIDLCString serverType;
02198             (void) mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType));
02199             nsCAutoString serverNum(serverType.get());
02200             PRInt32 err;
02201             mServerType = serverNum.ToInteger(&err);
02202             
02203             nsCOMPtr<nsIStreamListener> converter;
02204             rv = BuildStreamConverter(getter_AddRefs(converter));
02205             if (NS_FAILED(rv)) return rv;
02206             
02207             mDRequestForwarder->SetStreamListener(converter);
02208             mDRequestForwarder->SetCacheEntry(mCacheEntry, PR_FALSE);
02209             mDRequestForwarder->SetEntityID(EmptyCString());
02210 
02211             // Get a transport to the cached data...
02212             nsCOMPtr<nsIInputStream> input;
02213             rv = mCacheEntry->OpenInputStream(0, getter_AddRefs(input));
02214             if (NS_FAILED(rv)) return rv;
02215 
02216             nsCOMPtr<nsIInputStreamPump> pump;
02217             rv = NS_NewInputStreamPump(getter_AddRefs(pump), input);
02218             if (NS_FAILED(rv)) return rv;
02219 
02220             // Pump the cache data downstream
02221             rv = pump->AsyncRead(mDRequestForwarder, nsnull);
02222             if (NS_FAILED(rv)) return rv;
02223 
02224             mDPipeRequest = pump;
02225         }
02226     }
02227 
02228     nsCAutoString path;
02229     nsCOMPtr<nsIURL> aURL(do_QueryInterface(mURL));
02230     if (aURL)
02231         rv = aURL->GetFilePath(path);
02232     else
02233         rv = mURL->GetPath(path);
02234     if (NS_FAILED(rv)) return rv;
02235 
02236     // Skip leading slash
02237     char *fwdPtr = path.BeginWriting();
02238     if (fwdPtr && (*fwdPtr == '/'))
02239         fwdPtr++;
02240     if (*fwdPtr != '\0') {
02241         // now unescape it... %xx reduced inline to resulting character
02242         PRInt32 len = NS_UnescapeURL(fwdPtr);
02243         mPath.Assign(fwdPtr, len);
02244 
02245 #ifdef DEBUG
02246         if (mPath.FindCharInSet(CRLF) >= 0)
02247             NS_ERROR("NewURI() should've prevented this!!!");
02248 #endif
02249     }
02250 
02251     // pull any username and/or password out of the uri
02252     nsCAutoString uname;
02253     rv = mURL->GetUsername(uname);
02254     if (NS_FAILED(rv))
02255         return rv;
02256 
02257     if (!uname.IsEmpty() && !uname.EqualsLiteral("anonymous")) {
02258         mAnonymous = PR_FALSE;
02259         CopyUTF8toUTF16(NS_UnescapeURL(uname), mUsername);
02260         
02261         // return an error if we find a CR or LF in the username
02262         if (uname.FindCharInSet(CRLF) >= 0)
02263             return NS_ERROR_MALFORMED_URI;
02264     }
02265 
02266     nsCAutoString password;
02267     rv = mURL->GetPassword(password);
02268     if (NS_FAILED(rv))
02269         return rv;
02270 
02271     CopyUTF8toUTF16(NS_UnescapeURL(password), mPassword);
02272 
02273     // return an error if we find a CR or LF in the password
02274     if (mPassword.FindCharInSet(CRLF) >= 0)
02275         return NS_ERROR_MALFORMED_URI;
02276 
02277     // setup the connection cache key
02278 
02279     PRInt32 port;
02280     rv = mURL->GetPort(&port);
02281     if (NS_FAILED(rv)) return rv;
02282 
02283     if (port > 0)
02284         mPort = port;
02285 
02286     return NS_OK;
02287 }
02288 
02289 nsresult
02290 nsFtpState::Connect()
02291 {
02292     if (mDRequestForwarder)
02293         return NS_OK;  // we are already connected.
02294     
02295     nsresult rv;
02296 
02297     mState = FTP_COMMAND_CONNECT;
02298     mNextState = FTP_S_USER;
02299 
02300     rv = Process();
02301 
02302     // check for errors.
02303     if (NS_FAILED(rv)) {
02304         LOG(("-- Connect() on Control Connect FAILED with rv = %x\n", rv));
02305         mInternalError = NS_ERROR_FAILURE;
02306         mState = FTP_ERROR;
02307     }
02308 
02309     return rv;
02310 }
02311 
02312 nsresult
02313 nsFtpState::SetWriteStream(nsIInputStream* aInStream)
02314 {
02315     if (!aInStream)
02316         return NS_OK;
02317 
02318     mAction = PUT;
02319     mWriteStream = aInStream;
02320     mWriteStream->Available(&mWriteCount);
02321     return NS_OK;
02322 }
02323 
02324 void
02325 nsFtpState::KillControlConnection()
02326 {
02327     mControlReadCarryOverBuf.Truncate(0);
02328 
02329     NS_IF_RELEASE(mDRequestForwarder);
02330 
02331     mAddressChecked = PR_FALSE;
02332     mServerIsIPv6 = PR_FALSE;
02333 
02334     // if everything went okay, save the connection. 
02335     // FIX: need a better way to determine if we can cache the connections.
02336     //      there are some errors which do not mean that we need to kill the connection
02337     //      e.g. fnf.
02338 
02339     if (!mControlConnection)
02340         return;
02341 
02342     // kill the reference to ourselves in the control connection.
02343     (void) mControlConnection->SetStreamListener(nsnull);
02344 
02345     if (FTP_CACHE_CONTROL_CONNECTION && 
02346         NS_SUCCEEDED(mInternalError) &&
02347         NS_SUCCEEDED(mControlStatus) &&
02348         mControlConnection->IsAlive()) {
02349 
02350         LOG_ALWAYS(("(%x) nsFtpState caching control connection", this));
02351 
02352         // Store connection persistant data
02353         mControlConnection->mServerType = mServerType;           
02354         mControlConnection->mPassword = mPassword;
02355         mControlConnection->mPwd = mPwd;
02356         nsresult rv = gFtpHandler->InsertConnection(mURL, mControlConnection);
02357         // Can't cache it?  Kill it then.  
02358         mControlConnection->Disconnect(rv);
02359     } 
02360     else
02361         mControlConnection->Disconnect(NS_BINDING_ABORTED);
02362 
02363     NS_RELEASE(mControlConnection);
02364  
02365     return;
02366 }
02367 
02368 nsresult
02369 nsFtpState::StopProcessing()
02370 {
02371     // Only do this function once.
02372     if (!mKeepRunning)
02373         return NS_OK;
02374     mKeepRunning = PR_FALSE;
02375 
02376     LOG_ALWAYS(("(%x) nsFtpState stopping", this));
02377 
02378 #ifdef DEBUG_dougt
02379     printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get());
02380 #endif
02381 
02382     // We do this check here because the value of mInternalError changes during
02383     // this function. However, we only show the actual alert once we are done
02384     // changing member variables, because showing the dialog can process events
02385     // and we don't want to risk reentering this function. (compare bug 343371)
02386     nsCOMPtr<nsIPrompt> prompter;
02387     if (NS_FAILED(mInternalError) && !mResponseMsg.IsEmpty())
02388         mChannel->GetCallback(prompter);
02389    
02390     nsresult broadcastErrorCode = mControlStatus;
02391     if ( NS_SUCCEEDED(broadcastErrorCode))
02392         broadcastErrorCode = mInternalError;
02393 
02394     mInternalError = broadcastErrorCode;
02395 
02396     if (mDPipeRequest && NS_FAILED(broadcastErrorCode))
02397         mDPipeRequest->Cancel(broadcastErrorCode);
02398     
02399     if (mDRequestForwarder) {
02400         NS_RELEASE(mDRequestForwarder);
02401     }
02402     else
02403     {
02404         // The forwarding object was never created which  means that we never sent our notifications.
02405         
02406         nsCOMPtr<nsIRequestObserver> asyncObserver;
02407 
02408         NS_NewRequestObserverProxy(getter_AddRefs(asyncObserver), 
02409                                    mChannel,
02410                                    NS_CURRENT_EVENTQ);
02411         if(asyncObserver) {
02412             (void) asyncObserver->OnStartRequest(this, nsnull);
02413             (void) asyncObserver->OnStopRequest(this, nsnull, broadcastErrorCode);
02414         }
02415 
02416 
02417     }
02418 
02419     KillControlConnection();
02420 
02421     mChannel->OnStatus(nsnull, nsnull, NS_NET_STATUS_END_FTP_TRANSACTION, nsnull);
02422 
02423     // Release the Observers
02424     mWriteStream = 0;  // should this call close before setting to null?
02425 
02426     mChannel = 0;
02427     mProxyInfo = 0;
02428 
02429     // This code must be at the end of this function. See the comment about
02430     // prompter for why.
02431     if (prompter) 
02432     {
02433         // web shell wont throw an alert.  we better:
02434 
02435         // XXX(darin): this code should not be dictating UI like this!
02436         prompter->Alert(nsnull, NS_ConvertASCIItoUCS2(mResponseMsg).get());
02437     }
02438  
02439     return NS_OK;
02440 }
02441 
02442 
02443 nsresult
02444 nsFtpState::BuildStreamConverter(nsIStreamListener** convertStreamListener)
02445 {
02446     nsresult rv;
02447     // setup a listener to push the data into. This listener sits inbetween the
02448     // unconverted data of fromType, and the final listener in the chain (in this case
02449     // the mListener).
02450     nsCOMPtr<nsIStreamListener> converterListener;
02451 
02452     nsCOMPtr<nsIStreamConverterService> scs = 
02453              do_GetService(kStreamConverterServiceCID, &rv);
02454 
02455     if (NS_FAILED(rv)) 
02456         return rv;
02457 
02458     rv = scs->AsyncConvertData("text/ftp-dir",
02459                                APPLICATION_HTTP_INDEX_FORMAT,
02460                                mChannel,
02461                                mURL, 
02462                                getter_AddRefs(converterListener));
02463     if (NS_FAILED(rv)) {
02464         LOG(("(%x) scs->AsyncConvertData failed (rv=%x)\n", this, rv));
02465         return rv;
02466     }
02467 
02468     NS_ADDREF(*convertStreamListener = converterListener);
02469     return rv;
02470 }
02471 
02472 void
02473 nsFtpState::DataConnectionEstablished()
02474 {
02475     LOG(("(%x) Data Connection established.", this));
02476     mWaitingForDConn = PR_FALSE;
02477 
02478     // If we no longer have a channel, we don't need this connection
02479     if (!mChannel) {
02480         LOG(("    Ignoring data connection"));
02481         return;
02482     }
02483 
02484     // sending empty string with (mWaitingForDConn == PR_FALSE) will cause the 
02485     // control socket to write out its buffer.
02486     nsCString a("");
02487     SendFTPCommand(a);
02488 }
02489 
02490 void
02491 nsFtpState::DataConnectionComplete()
02492 {
02493     LOG(("(%x) Data Connection complete.", this));
02494 
02495     if (mDPipe) {
02496         mDPipe->SetEventSink(nsnull, nsnull);
02497         mDPipe->Close(NS_ERROR_ABORT);
02498         mDPipe = 0;
02499     }
02500 }
02501 
02502 nsresult 
02503 nsFtpState::SendFTPCommand(nsCString& command)
02504 {
02505     NS_ASSERTION(mControlConnection, "null control connection");        
02506     
02507     // we don't want to log the password:
02508     nsCAutoString logcmd(command);
02509     if (StringBeginsWith(command, NS_LITERAL_CSTRING("PASS "))) 
02510         logcmd = "PASS xxxxx";
02511     
02512     LOG(("(%x)(dwait=%d) Writing \"%s\"\n", this, mWaitingForDConn, logcmd.get()));
02513 
02514     nsCOMPtr<nsIFTPEventSink> ftpSink;
02515     mChannel->GetFTPEventSink(ftpSink);
02516     if (ftpSink)
02517         ftpSink->OnFTPControlLog(PR_FALSE, logcmd.get());
02518     
02519     if (mControlConnection) {
02520         return mControlConnection->Write(command, mWaitingForDConn);
02521     }
02522     return NS_ERROR_FAILURE;
02523 }
02524 
02525 // Convert a unix-style filespec to VMS format
02526 // /foo/fred/barney/file.txt -> foo:[fred.barney]file.txt
02527 // /foo/file.txt -> foo:[000000]file.txt
02528 void
02529 nsFtpState::ConvertFilespecToVMS(nsCString& fileString)
02530 {
02531     int ntok=1;
02532     char *t, *nextToken;
02533     nsCAutoString fileStringCopy;
02534 
02535     // Get a writeable copy we can strtok with.
02536     fileStringCopy = fileString;
02537     t = nsCRT::strtok(fileStringCopy.BeginWriting(), "/", &nextToken);
02538     if (t) while (nsCRT::strtok(nextToken, "/", &nextToken)) ntok++; // count number of terms (tokens)
02539     LOG(("(%x) ConvertFilespecToVMS ntok: %d\n", this, ntok));
02540     LOG(("(%x) ConvertFilespecToVMS from: \"%s\"\n", this, fileString.get()));
02541 
02542     if (fileString.First() == '/') {
02543         // absolute filespec
02544         //   /        -> []
02545         //   /a       -> a (doesn't really make much sense)
02546         //   /a/b     -> a:[000000]b
02547         //   /a/b/c   -> a:[b]c
02548         //   /a/b/c/d -> a:[b.c]d
02549         if (ntok == 1) {
02550             if (fileString.Length() == 1) {
02551                 // Just a slash
02552                 fileString.Truncate();
02553                 fileString.AppendLiteral("[]");
02554             }
02555             else {
02556                 // just copy the name part (drop the leading slash)
02557                 fileStringCopy = fileString;
02558                 fileString = Substring(fileStringCopy, 1,
02559                                        fileStringCopy.Length()-1);
02560             }
02561         }
02562         else {
02563             // Get another copy since the last one was written to.
02564             fileStringCopy = fileString;
02565             fileString.Truncate();
02566             fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(), 
02567                               "/", &nextToken));
02568             fileString.AppendLiteral(":[");
02569             if (ntok > 2) {
02570                 for (int i=2; i<ntok; i++) {
02571                     if (i > 2) fileString.Append('.');
02572                     fileString.Append(nsCRT::strtok(nextToken,
02573                                       "/", &nextToken));
02574                 }
02575             }
02576             else {
02577                 fileString.AppendLiteral("000000");
02578             }
02579             fileString.Append(']');
02580             fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
02581         }
02582     } else {
02583        // relative filespec
02584         //   a       -> a
02585         //   a/b     -> [.a]b
02586         //   a/b/c   -> [.a.b]c
02587         if (ntok == 1) {
02588             // no slashes, just use the name as is
02589         }
02590         else {
02591             // Get another copy since the last one was written to.
02592             fileStringCopy = fileString;
02593             fileString.Truncate();
02594             fileString.AppendLiteral("[.");
02595             fileString.Append(nsCRT::strtok(fileStringCopy.BeginWriting(),
02596                               "/", &nextToken));
02597             if (ntok > 2) {
02598                 for (int i=2; i<ntok; i++) {
02599                     fileString.Append('.');
02600                     fileString.Append(nsCRT::strtok(nextToken,
02601                                       "/", &nextToken));
02602                 }
02603             }
02604             fileString.Append(']');
02605             fileString.Append(nsCRT::strtok(nextToken, "/", &nextToken));
02606         }
02607     }
02608     LOG(("(%x) ConvertFilespecToVMS   to: \"%s\"\n", this, fileString.get()));
02609 }
02610 
02611 // Convert a unix-style dirspec to VMS format
02612 // /foo/fred/barney/rubble -> foo:[fred.barney.rubble]
02613 // /foo/fred -> foo:[fred]
02614 // /foo -> foo:[000000]
02615 // (null) -> (null)
02616 void
02617 nsFtpState::ConvertDirspecToVMS(nsCString& dirSpec)
02618 {
02619     LOG(("(%x) ConvertDirspecToVMS from: \"%s\"\n", this, dirSpec.get()));
02620     if (!dirSpec.IsEmpty()) {
02621         if (dirSpec.Last() != '/')
02622             dirSpec.Append('/');
02623         // we can use the filespec routine if we make it look like a file name
02624         dirSpec.Append('x');
02625         ConvertFilespecToVMS(dirSpec);
02626         dirSpec.Truncate(dirSpec.Length()-1);
02627     }
02628     LOG(("(%x) ConvertDirspecToVMS   to: \"%s\"\n", this, dirSpec.get()));
02629 }
02630 
02631 // Convert an absolute VMS style dirspec to UNIX format
02632 void
02633 nsFtpState::ConvertDirspecFromVMS(nsCString& dirSpec)
02634 {
02635     LOG(("(%x) ConvertDirspecFromVMS from: \"%s\"\n", this, dirSpec.get()));
02636     if (dirSpec.IsEmpty()) {
02637         dirSpec.Insert('.', 0);
02638     }
02639     else {
02640         dirSpec.Insert('/', 0);
02641         dirSpec.ReplaceSubstring(":[", "/");
02642         dirSpec.ReplaceChar('.', '/');
02643         dirSpec.ReplaceChar(']', '/');
02644     }
02645     LOG(("(%x) ConvertDirspecFromVMS   to: \"%s\"\n", this, dirSpec.get()));
02646 }