/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ #include "nsFtpConnectionThread.h" #include "nsIEventQueueService.h" #include "nsIStringStream.h" #include "nsIPipe.h" #include "nsIMIMEService.h" #include "nsIStreamConverterService.h" #include "prprf.h" #include "prlog.h" #include "prmon.h" #include "netCore.h" #include "ftpCore.h" #include "nsProxiedService.h" #include "nsCRT.h" #include "nsIInterfaceRequestor.h" #include "nsFTPListener.h" #include "nsAppShellCIDs.h" // TODO remove later #include "nsIAppShellService.h" // TODO remove later #include "nsIWebShellWindow.h" // TODO remove later #include "nsINetPrompt.h" static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID); static NS_DEFINE_CID(kMIMEServiceCID, NS_MIMESERVICE_CID); static NS_DEFINE_CID(kEventQueueServiceCID, NS_EVENTQUEUESERVICE_CID); static NS_DEFINE_CID(kAppShellServiceCID, NS_APPSHELL_SERVICE_CID); static NS_DEFINE_CID(kProxyObjectManagerCID, NS_PROXYEVENT_MANAGER_CID); static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID); #if defined(PR_LOGGING) extern PRLogModuleInfo* gFTPLog; #endif /* PR_LOGGING */ class nsFTPAsyncReadEvent { public: nsFTPAsyncReadEvent(nsIChannel *aChannel, nsIStreamListener *aListener, nsISupports *aContext); virtual ~nsFTPAsyncReadEvent(); nsresult Fire(nsIEventQueue* aEventQ); nsresult HandleEvent(); protected: static void PR_CALLBACK HandlePLEvent(PLEvent* aEvent); static void PR_CALLBACK DestroyPLEvent(PLEvent* aEvent); nsCOMPtr mChannel; nsCOMPtr mListener; nsCOMPtr mContext; PLEvent *mEvent; }; //////////////////////////////////////////////////////////////////////////////// nsFTPAsyncReadEvent::nsFTPAsyncReadEvent(nsIChannel *aChannel, nsIStreamListener *aListener, nsISupports *aContext) : mChannel(aChannel), mListener(aListener), mContext(aContext), mEvent(nsnull) {} nsFTPAsyncReadEvent::~nsFTPAsyncReadEvent() { if (mEvent) { delete mEvent; mEvent = nsnull; } } nsresult nsFTPAsyncReadEvent::HandleEvent() { return mChannel->AsyncRead(0, -1, mContext, mListener); } void PR_CALLBACK nsFTPAsyncReadEvent::HandlePLEvent(PLEvent* aEvent) { nsFTPAsyncReadEvent *ev = (nsFTPAsyncReadEvent*) PL_GetEventOwner(aEvent); NS_ASSERTION(nsnull != ev,"null event."); ev->HandleEvent(); } void PR_CALLBACK nsFTPAsyncReadEvent::DestroyPLEvent(PLEvent* aEvent) { nsFTPAsyncReadEvent *ev = (nsFTPAsyncReadEvent*) PL_GetEventOwner(aEvent); NS_ASSERTION(nsnull != ev,"null event."); delete ev; } nsresult nsFTPAsyncReadEvent::Fire(nsIEventQueue* aEventQueue) { NS_PRECONDITION(nsnull != aEventQueue, "nsIEventQueue for thread is null"); NS_PRECONDITION(nsnull == mEvent, "Init plevent only once."); mEvent = new PLEvent; if (!mEvent) return NS_ERROR_OUT_OF_MEMORY; PL_InitEvent(mEvent, this, (PLHandleEventProc) nsFTPAsyncReadEvent::HandlePLEvent, (PLDestroyEventProc) nsFTPAsyncReadEvent::DestroyPLEvent); PRStatus status = aEventQueue->PostEvent(mEvent); return status == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE; } NS_IMPL_ISUPPORTS3(nsFtpConnectionThread, nsIRunnable, nsIRequest, nsIStreamObserver); nsFtpConnectionThread::nsFtpConnectionThread() { NS_INIT_REFCNT(); // bool init mConnected = mResetMode = mList = mRetryPass = mCachedConn = mSentStart = PR_FALSE; mUsePasv = mBin = mKeepRunning = mAnonymous = PR_TRUE; mAction = GET; mState = FTP_S_USER; mNextState = FTP_S_USER; mLength = -1; mInternalError = NS_OK; mConn = nsnull; mSuspendCount = 0; mPort = 21; mLock = nsnull; mMonitor = nsnull; } nsFtpConnectionThread::~nsFtpConnectionThread() { PR_LOG(gFTPLog, PR_LOG_ALWAYS, ("~nsFtpConnectionThread() called")); if (mLock) { PR_DestroyLock(mLock); mLock = nsnull; } if (mMonitor) { PR_DestroyMonitor(mMonitor); mMonitor = nsnull; } } nsresult nsFtpConnectionThread::Process() { nsresult rv = NS_OK; PRBool continueRead = PR_FALSE; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("nsFtpConnectionThread::Process() started for %x (spec =%s)\n", mURL.get(), (const char *) mURLSpec)); while (mKeepRunning) { switch(mState) { ////////////////////////////// //// INTERNAL STATES ////////////////////////////// case FTP_READ_BUF: { char buffer[NS_FTP_BUFFER_READ_SIZE]; PRUint32 read; rv = mCInStream->Read(buffer, NS_FTP_BUFFER_READ_SIZE, &read); if (NS_FAILED(rv)) { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - READ_BUF - Read() FAILED with rv = %d\n", mURL.get(), rv)); mInternalError = NS_ERROR_FAILURE; mState = FTP_ERROR; break; } if (0 == read) { // EOF PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - READ_BUF received EOF\n", mURL.get())); mState = FTP_COMPLETE; mInternalError = NS_ERROR_FAILURE; break; } buffer[read] = '\0'; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - READ_BUF - read \"%s\" (%d bytes)", mURL.get(), buffer, read)); // get the response code out. if (!continueRead) { PR_sscanf(buffer, "%d", &mResponseCode); mResponseCode = mResponseCode / 100; // truncate it } // see if we're handling a multi-line response. if (continueRead || (buffer[3] == '-')) { // yup, multi-line response, start appending char *tmpBuffer = buffer, *crlf = nsnull; PRBool lastLine = PR_FALSE; while ( (crlf = PL_strstr(tmpBuffer, CRLF)) ) { char tmpChar = crlf[2]; crlf[2] = '\0'; // see if this is the last line lastLine = tmpBuffer[3] != '-'; mResponseMsg += tmpBuffer+4; // skip over the code and '-' crlf[2] = tmpChar; tmpBuffer = crlf+2; } if (*tmpBuffer) mResponseMsg += tmpBuffer+4; // see if this was the last line if (lastLine || (*tmpBuffer && (tmpBuffer[3] != '-')) ) { // yup. last line, let's move on. if (mState == mNextState) { NS_ASSERTION(0, "ftp read state mixup"); mInternalError = NS_ERROR_FAILURE; mState = FTP_ERROR; } else { mState = mNextState; } continueRead = PR_FALSE; } else { // don't increment state, we need to read more. continueRead = PR_TRUE; } break; } // get the rest of the line mResponseMsg = buffer+4; if (mState == mNextState) { NS_ASSERTION(0, "ftp read state mixup"); mInternalError = NS_ERROR_FAILURE; mState = FTP_ERROR; } else { mState = mNextState; } break; } // END: FTP_READ_BUF case FTP_ERROR: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - ERROR\n", mURL.get())); // if something went wrong, delete this connection entry and don't // bother putting it in the cache. NS_ASSERTION(mConn, "we should have created the conn obj upon Init()"); delete mConn; rv = StopProcessing(); break; } // END: FTP_ERROR case FTP_COMPLETE: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - COMPLETE\n", mURL.get())); if (NS_SUCCEEDED(mInternalError)) { // we only want to cache the connection if things are ok. // push through all of the pertinent state into the cache entry; mConn->mCwd = mCwd; rv = mConnCache->InsertConn(mCacheKey.GetBuffer(), mConn); if (NS_FAILED(rv)) return rv; } rv = StopProcessing(); break; } // END: FTP_COMPLETE ////////////////////////////// //// CONNECTION SETUP STATES ////////////////////////////// case FTP_S_USER: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_USER - ", mURL.get())); rv = S_user(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_LOGIN; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_USER; } break; } // END: FTP_S_USER case FTP_R_USER: { mState = R_user(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; break; } // END: FTP_R_USER case FTP_S_PASS: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_PASS - ", mURL.get())); rv = S_pass(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_LOGIN; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_PASS; } break; } // END: FTP_S_PASS case FTP_R_PASS: { mState = R_pass(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; break; } // END: FTP_R_PASS case FTP_S_SYST: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_SYST - ", mURL.get())); rv = S_syst(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_SYST; } break; } // END: FTP_S_SYST case FTP_R_SYST: { mState = R_syst(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_SYST case FTP_S_ACCT: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_ACCT - ", mURL.get())); rv = S_acct(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_LOGIN; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_ACCT; } break; } // END: FTP_S_ACCT case FTP_R_ACCT: { mState = R_acct(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_LOGIN; break; } // END: FTP_R_ACCT case FTP_S_MACB: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_MACB - ", mURL.get())); rv = S_macb(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_MACB; } break; } // END: FTP_S_MACB case FTP_R_MACB: { mState = R_macb(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_MACB case FTP_S_PWD: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_PWD - ", mURL.get())); rv = S_pwd(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_PWD; } break; } // END: FTP_S_PWD case FTP_R_PWD: { mState = R_pwd(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_PWD case FTP_S_MODE: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_MODE - ", mURL.get())); rv = S_mode(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_MODE; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_MODE; } break; } // END: FTP_S_MODE case FTP_R_MODE: { mState = R_mode(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_MODE; break; } // END: FTP_R_MODE case FTP_S_CWD: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_CWD - ", mURL.get())); rv = S_cwd(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_CWD; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_CWD; } break; } // END: FTP_S_CWD case FTP_R_CWD: { mState = R_cwd(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_CWD; break; } // END: FTP_R_CWD case FTP_S_SIZE: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_SIZE - ", mURL.get())); rv = S_size(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_SIZE; } break; } // END: FTP_S_SIZE case FTP_R_SIZE: { mState = R_size(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_SIZE case FTP_S_MDTM: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_MDTM - ", mURL.get())); rv = S_mdtm(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_MDTM; } break; } // END: FTP_S_MDTM; case FTP_R_MDTM: { mState = R_mdtm(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_MDTM case FTP_S_LIST: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_LIST - ", mURL.get())); rv = S_list(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_LIST; } break; } // END: FTP_S_LIST case FTP_R_LIST: { mState = R_list(); if (FTP_ERROR == mState) { mInternalError = rv; mNextState = FTP_ERROR; } else { mNextState = FTP_COMPLETE; } break; } // END: FTP_R_LIST case FTP_S_RETR: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_RETR - ", mURL.get())); rv = S_retr(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_RETR; } break; } // END: FTP_S_RETR case FTP_R_RETR: { mState = R_retr(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; mNextState = FTP_COMPLETE; break; } // END: FTP_R_RETR ////////////////////////////// //// DATA CONNECTION STATES ////////////////////////////// case FTP_S_PASV: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_PASV - ", mURL.get())); rv = S_pasv(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_PASV; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_PASV; } break; } // FTP: FTP_S_PASV case FTP_R_PASV: { mState = R_pasv(); PLEvent *event = nsnull; if ( NS_FAILED(mFTPEventQueue->WaitForEvent(&event)) || NS_FAILED(mFTPEventQueue->HandleEvent(event))) return rv; if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_PASV; mNextState = FTP_READ_BUF; break; } // END: FTP_R_PASV ////////////////////////////// //// ACTION STATES ////////////////////////////// case FTP_S_DEL_FILE: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_DEL_FILE - ", mURL.get())); rv = S_del_file(); if (NS_FAILED(rv)) { mInternalError = rv; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_DEL_FILE; } break; } // END: FTP_S_DEL_FILE case FTP_R_DEL_FILE: { mState = R_del_file(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FAILURE; break; } // END: FTP_R_DEL_FILE case FTP_S_DEL_DIR: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_DEL_DIR - ", mURL.get())); rv = S_del_dir(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_DEL_DIR; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_DEL_DIR; } break; } // END: FTP_S_DEL_DIR case FTP_R_DEL_DIR: { mState = R_del_dir(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_DEL_DIR; break; } // END: FTP_R_DEL_DIR case FTP_S_MKDIR: { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Process() - S_MKDIR - ", mURL.get())); rv = S_mkdir(); if (NS_FAILED(rv)) { mInternalError = NS_ERROR_FTP_MKDIR; mState = FTP_ERROR; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("FAILED\n")); } else { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("SUCCEEDED\n")); mState = FTP_READ_BUF; mNextState = FTP_R_MKDIR; } break; } // END: FTP_S_MKDIR case FTP_R_MKDIR: { mState = R_mkdir(); if (FTP_ERROR == mState) mInternalError = NS_ERROR_FTP_MKDIR; break; } // END: FTP_R_MKDIR default: ; } // END: switch } // END: while loop PR_LOG(gFTPLog, PR_LOG_DEBUG, ("nsFtpConnectionThread::Process() ended for %x (spec =%s)\n\n\n", mURL.get(), (const char *) mURLSpec)); return NS_OK; } nsresult nsFtpConnectionThread::SetStreamObserver(nsIStreamObserver* aObserver, nsISupports *aContext) { nsresult rv = NS_OK; if (mConnected) return NS_ERROR_ALREADY_CONNECTED; mObserverContext = aContext; NS_WITH_SERVICE(nsIProxyObjectManager, pIProxyObjectManager, kProxyObjectManagerCID, &rv); if(NS_FAILED(rv)) return rv; return pIProxyObjectManager->GetProxyObject(NS_UI_THREAD_EVENTQ, NS_GET_IID(nsIStreamObserver), aObserver, PROXY_ASYNC | PROXY_ALWAYS, getter_AddRefs(mObserver)); } nsresult nsFtpConnectionThread::SetStreamListener(nsIStreamListener* aListener, nsISupports *aContext) { nsresult rv = NS_OK; mListener = aListener; mListenerContext = aContext; return NS_OK; } /////////////////////////////////// // STATE METHODS /////////////////////////////////// nsresult nsFtpConnectionThread::S_user() { nsresult rv; PRUint32 bytes; nsCAutoString usernameStr("USER "); if (mAnonymous) { usernameStr.Append("anonymous"); } else { if (!mUsername.Length()) { NS_WITH_SERVICE(nsIAppShellService, appshellservice, kAppShellServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr webshellwindow; appshellservice->GetHiddenWindow(getter_AddRefs( webshellwindow ) ); nsCOMPtr prompter( do_QueryInterface( webshellwindow ) ); NS_WITH_SERVICE(nsIProxyObjectManager, pIProxyObjectManager, kProxyObjectManagerCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr proxyprompter; rv = pIProxyObjectManager->GetProxyObject(NS_UI_THREAD_EVENTQ, NS_GET_IID(nsINetPrompt), prompter, PROXY_SYNC, getter_AddRefs(proxyprompter)); PRUnichar *user = nsnull, *passwd = nsnull; PRBool retval; static nsAutoString message; nsXPIDLCString host; rv = mURL->GetHost(getter_Copies(host)); if (NS_FAILED(rv)) return rv; if (message.Length() < 1) { message = "Enter username and password for "; //TODO localize it! message += host; } rv = proxyprompter->PromptUsernameAndPassword(host, NULL, message.GetUnicode(), &user, &passwd, &retval); // if the user canceled or didn't supply a username we want to fail if (!retval || (user && !*user) ) return NS_ERROR_FAILURE; mUsername = user; mPassword = passwd; } usernameStr.Append(mUsername); } usernameStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), usernameStr.GetBuffer())); rv = mCOutStream->Write(usernameStr.GetBuffer(), usernameStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_user() { if (mResponseCode == 3) { // send off the password return FTP_S_PASS; } else if (mResponseCode == 2) { // no password required, we're already logged in return FTP_S_SYST; } else { // LOGIN FAILED if (mAnonymous) { // we just tried to login anonymously and failed. // kick back out to S_user() and try again after // gathering uname/pwd info from the user. mAnonymous = PR_FALSE; return FTP_S_USER; } else { return FTP_ERROR; } } } nsresult nsFtpConnectionThread::S_pass() { nsresult rv; PRUint32 bytes; nsCAutoString passwordStr("PASS "); if (mAnonymous) { passwordStr.Append("mozilla@"); } else { if (!mPassword.Length() || mRetryPass) { // ignore any password we have, it's not working NS_WITH_SERVICE(nsIAppShellService, appshellservice, kAppShellServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr webshellwindow; appshellservice->GetHiddenWindow(getter_AddRefs( webshellwindow ) ); nsCOMPtr prompter( do_QueryInterface( webshellwindow ) ); NS_WITH_SERVICE(nsIProxyObjectManager, pIProxyObjectManager, kProxyObjectManagerCID, &rv); if (NS_FAILED(rv)) return rv; nsCOMPtr proxyprompter; rv = pIProxyObjectManager->GetProxyObject(NS_UI_THREAD_EVENTQ, NS_GET_IID(nsINetPrompt), prompter, PROXY_SYNC, getter_AddRefs(proxyprompter)); PRUnichar *passwd = nsnull; PRBool retval; static nsAutoString message; nsXPIDLCString host2; rv = mURL->GetHost(getter_Copies(host2)); if (NS_FAILED(rv)) { return rv; } nsAutoString userAtHost; userAtHost = mUsername; userAtHost += "@"; userAtHost += host2; char * userAtHostC = userAtHost.ToNewCString(); if (message.Length() < 1) { nsXPIDLCString host; rv = mURL->GetHost(getter_Copies(host)); if (NS_FAILED(rv)) return rv; message = "Enter password for "; //TODO localize it! message += mUsername; message += " on "; message += host; } rv = proxyprompter->PromptPassword(userAtHostC, NULL, message.GetUnicode(), &passwd, &retval); nsCRT::free(userAtHostC); // we want to fail if the user canceled or didn't enter a password. if (!retval || (passwd && !*passwd) ) return NS_ERROR_FAILURE; mPassword = passwd; } passwordStr.Append(mPassword); } passwordStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), passwordStr.GetBuffer())); rv = mCOutStream->Write(passwordStr.GetBuffer(), passwordStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_pass() { if (mResponseCode == 3) { // send account info return FTP_S_ACCT; } else if (mResponseCode == 2) { // logged in return FTP_S_SYST; } else { // kick back out to S_pass() and ask the user again. mRetryPass = PR_TRUE; return FTP_S_PASS; } } nsresult nsFtpConnectionThread::S_syst() { char *buffer = "SYST" CRLF; PRUint32 bytes; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_syst() { FTP_STATE state = FTP_S_PWD; if (mResponseCode == 2) { SetSystInternals(); // must be called first to setup member vars. // setup next state based on server type. if (mServerType == FTP_PETER_LEWIS_TYPE || mServerType == FTP_WEBSTAR_TYPE) state = FTP_S_MACB; else state = FindActionState(); } return state; } nsresult nsFtpConnectionThread::S_acct() { char *buffer = "ACCT noaccount" CRLF; PRUint32 bytes; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_acct() { if (mResponseCode == 2) return FTP_S_SYST; else return FTP_ERROR; } nsresult nsFtpConnectionThread::S_macb() { char *buffer = "MACB ENABLE" CRLF; PRUint32 bytes; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_macb() { if (mResponseCode == 2) { // set the mac binary if (mServerType == FTP_UNIX_TYPE) { // This state is carry over from the old ftp implementation // I'm not sure what's really going on here. // original comment "we were unsure here" mServerType = FTP_NCSA_TYPE; } } return FindActionState(); } nsresult nsFtpConnectionThread::S_pwd() { char *buffer = "PWD" CRLF; PRUint32 bytes; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_pwd() { FTP_STATE state = FTP_ERROR; nsCAutoString lNewMsg(mResponseMsg); // fun response interpretation begins :) // check for a quoted path PRInt32 beginQ, endQ; beginQ = lNewMsg.Find("\""); if (beginQ > -1) { endQ = lNewMsg.RFind("\""); if (endQ > beginQ) { // quoted path nsCAutoString tmpPath; lNewMsg.Mid(tmpPath, beginQ+1, (endQ-beginQ-1)); lNewMsg = tmpPath; } } // default next state state = FindActionState(); // reset server types if necessary if (mServerType == FTP_TCPC_TYPE) { if (lNewMsg.CharAt(1) == '/') { mServerType = FTP_NCSA_TYPE; } } else if(mServerType == FTP_GENERIC_TYPE) { if (lNewMsg.CharAt(1) == '/') { // path names ending with '/' imply unix mServerType = FTP_UNIX_TYPE; mList = PR_TRUE; } else if (lNewMsg.Last() == ']') { // path names ending with ']' imply vms mServerType = FTP_VMS_TYPE; mList = PR_TRUE; } } // change state for these servers. if (mServerType == FTP_GENERIC_TYPE || mServerType == FTP_NCSA_TYPE || mServerType == FTP_TCPC_TYPE || mServerType == FTP_WEBSTAR_TYPE || mServerType == FTP_PETER_LEWIS_TYPE) state = FTP_S_MACB; return state; } nsresult nsFtpConnectionThread::S_mode() { char *buffer; PRUint32 bytes; if (mBin) { buffer = "TYPE I" CRLF; } else { buffer = "TYPE A" CRLF; } PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_mode() { if (mResponseCode != 2) { return FTP_ERROR; } if (mAction == PUT) { return FTP_S_CWD; } else { if (mResetMode) { // if this was a mode reset, we know it was from a previous R_size() call // don't bother going back into size return FTP_S_CWD; } else { return FTP_S_SIZE; } } } nsresult nsFtpConnectionThread::S_cwd() { nsresult rv; nsXPIDLCString path; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(path)); if (NS_FAILED(rv)) return rv; nsCAutoString cwdStr("CWD "); if (mServerType == FTP_VMS_TYPE) { char *slash = nsnull; if ( (slash = PL_strrchr(path, '/')) ) { *slash = '\0'; cwdStr.Append("["); cwdStr.Append(path); cwdStr.Append("]" CRLF); *slash = '/'; // turn '/'s into '.'s //while ( (slash = PL_strchr(buffer, '/')) ) // *slash = '.'; } else { cwdStr.Append("["); cwdStr.Append("."); cwdStr.Append(path); cwdStr.Append("]" CRLF); } } else { // non VMS server cwdStr.Append(path); cwdStr.Append(CRLF); } PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), cwdStr.GetBuffer())); cwdStr.Mid(mCwdAttempt, 3, cwdStr.Length() - 4); rv = mCOutStream->Write(cwdStr.GetBuffer(), cwdStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_cwd() { FTP_STATE state = FTP_ERROR; if (mResponseCode == 2) { mCwd = mCwdAttempt; // success if (mAction == PUT) { // we are uploading state = FTP_S_PUT; } // we are GETting a file or dir if (mServerType == FTP_VMS_TYPE) { if (mFilename.Length() > 0) { // it's a file state = FTP_S_RETR; } } // we're not VMS, and we've already failed a RETR. state = FTP_S_LIST; } else { state = FTP_ERROR; } return state; } nsresult nsFtpConnectionThread::S_size() { nsresult rv; nsXPIDLCString path; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(path)); if (NS_FAILED(rv)) return rv; // XXX should the actual file name be parsed out??? mFilename = path; nsCAutoString sizeBuf("SIZE "); sizeBuf.Append(path); sizeBuf.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), sizeBuf.GetBuffer())); rv = mCOutStream->Write(sizeBuf.GetBuffer(), sizeBuf.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_size() { FTP_STATE retState = FTP_S_MDTM; nsresult rv; if (mResponseCode == 2) { PRInt32 conversionError; mLength = mResponseMsg.ToInteger(&conversionError); rv = mFTPChannel->SetContentLength(mLength); if (NS_FAILED(rv)) return FTP_ERROR; } if (mResponseCode == 5) { // couldn't get the size of what we asked for, must be a dir. mResetMode = PR_TRUE; rv = mFTPChannel->SetContentType("application/http-index-format"); if (NS_FAILED(rv)) return FTP_ERROR; retState = FTP_S_MODE; } if (mObserver) { // this was an AsyncOpen call. Notify the AsyncOpen observer // then wait for further instructions. rv = mObserver->OnStartRequest(mChannel, mObserverContext); if (NS_FAILED(rv)) return FTP_ERROR; nsAutoCMonitor mon(this); mon.Wait(); } return retState; } nsresult nsFtpConnectionThread::S_mdtm() { nsresult rv; nsXPIDLCString path; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(path)); if (NS_FAILED(rv)) return rv; /*if (mServerType == FTP_VMS_TYPE) { mState = FindGetState(); break; }*/ nsCAutoString mdtmStr("MDTM "); mdtmStr.Append(path); mdtmStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), mdtmStr.GetBuffer())); rv = mCOutStream->Write(mdtmStr.GetBuffer(), mdtmStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_mdtm() { if (mResponseCode == 2) { // The time is returned in ISO 3307 "Representation // of the Time of Day" format. This format is YYYYMMDDHHmmSS or // YYYYMMDDHHmmSS.xxx, where // YYYY is the year // MM is the month (01-12) // DD is the day of the month (01-31) // HH is the hour of the day (00-23) // mm is the minute of the hour (00-59) // SS is the second of the hour (00-59) // xxx if present, is a fractional second and may be any length // Time is expressed in UTC (GMT), not local time. PRExplodedTime ts; char *timeStr = mResponseMsg.ToNewCString(); if (!timeStr) return FTP_ERROR; ts.tm_year = (timeStr[0] - '0') * 1000 + (timeStr[1] - '0') * 100 + (timeStr[2] - '0') * 10 + (timeStr[3] - '0'); ts.tm_month = ((timeStr[4] - '0') * 10 + (timeStr[5] - '0')) - 1; ts.tm_mday = (timeStr[6] - '0') * 10 + (timeStr[7] - '0'); ts.tm_hour = (timeStr[8] - '0') * 10 + (timeStr[9] - '0'); ts.tm_min = (timeStr[10] - '0') * 10 + (timeStr[11] - '0'); ts.tm_sec = (timeStr[12] - '0') * 10 + (timeStr[13] - '0'); ts.tm_usec = 0; mLastModified = PR_ImplodeTime(&ts); } return FindGetState(); } nsresult nsFtpConnectionThread::S_list() { nsresult rv; char *buffer; PRUint32 bytes; if (mList) buffer = "LIST" CRLF; else buffer = "NLST" CRLF; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); rv = mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); if (NS_FAILED(rv)) return rv; // setup a listener to push the data into. This listener sits inbetween the // unconverted data of fromType, and the final listener in the chain (in this case // the mListener). nsCOMPtr converterListener; NS_WITH_SERVICE(nsIStreamConverterService, StreamConvService, kStreamConverterServiceCID, &rv); if (NS_FAILED(rv)) return rv; nsAutoString fromStr("text/ftp-dir-"); SetDirMIMEType(fromStr); nsAutoString toStr("application/http-index-format"); rv = StreamConvService->AsyncConvertData(fromStr.GetUnicode(), toStr.GetUnicode(), mListener, mURL, getter_AddRefs(converterListener)); if (NS_FAILED(rv)) return rv; nsFTPListener *FTPListener = new nsFTPListener(converterListener, mChannel); if (!FTPListener) return rv; nsFTPAsyncReadEvent *event = new nsFTPAsyncReadEvent(mDPipe, FTPListener, mListenerContext); if (!event) return NS_ERROR_OUT_OF_MEMORY; return event->Fire(mUIEventQ); } FTP_STATE nsFtpConnectionThread::R_list() { nsresult rv = NS_OK; if ((mResponseCode == 4) || (mResponseCode == 5)) { // unable to open the data connection. return FTP_ERROR; } return FTP_READ_BUF; } nsresult nsFtpConnectionThread::S_retr() { nsresult rv = NS_OK; nsXPIDLCString path; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(path)); if (NS_FAILED(rv)) return rv; nsCAutoString retrStr("RETR "); if (mServerType == FTP_VMS_TYPE) { NS_ASSERTION(mFilename.Length() > 0, "ftp filename not present"); retrStr.Append(mFilename); } else { retrStr.Append(path); } retrStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), retrStr.GetBuffer())); rv = mCOutStream->Write(retrStr.GetBuffer(), retrStr.Length(), &bytes); nsFTPListener *FTPListener = new nsFTPListener(mListener, mChannel); if (!FTPListener) return NS_ERROR_OUT_OF_MEMORY; nsFTPAsyncReadEvent *event = new nsFTPAsyncReadEvent(mDPipe, FTPListener, mListenerContext); if (!event) return NS_ERROR_OUT_OF_MEMORY; return event->Fire(mUIEventQ); } FTP_STATE nsFtpConnectionThread::R_retr() { nsresult rv = NS_OK; if (mResponseCode == 1) { // success. return FTP_READ_BUF; } else { if (mServerType == FTP_VMS_TYPE) { return FTP_ERROR; } else { return FTP_S_CWD; } } } nsresult nsFtpConnectionThread::S_pasv() { char *buffer = "PASV" CRLF; PRUint32 bytes; PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), buffer)); return mCOutStream->Write(buffer, PL_strlen(buffer), &bytes); } FTP_STATE nsFtpConnectionThread::R_pasv() { nsresult rv; PRInt32 h0, h1, h2, h3, p0, p1; PRInt32 port; if (mResponseCode != 2) { // failed. increment to port. // mState = FTP_S_PORT; mUsePasv = PR_FALSE; return FTP_ERROR; } mUsePasv = PR_TRUE; char *ptr = nsnull; char *response = mResponseMsg.ToNewCString(); if (!response) return FTP_ERROR; // The returned address string can be of the form // (xxx,xxx,xxx,xxx,ppp,ppp) or // xxx,xxx,xxx,xxx,ppp,ppp (without parens) ptr = response; while (*ptr++) { if (*ptr == '(') { // move just beyond the open paren ptr++; break; } if (*ptr == ',') { ptr--; // backup to the start of the digits while ( (*ptr >= '0') && (*ptr <= '9')) ptr--; ptr++; // get back onto the numbers break; } } // end while PRInt32 fields = PR_sscanf(ptr, #ifdef __alpha "%d,%d,%d,%d,%d,%d", #else "%ld,%ld,%ld,%ld,%ld,%ld", #endif &h0, &h1, &h2, &h3, &p0, &p1); if (fields < 6) { // bad format. we'll try PORT, but it's probably over. mUsePasv = PR_FALSE; return FTP_ERROR; } port = ((PRInt32) (p0<<8)) + p1; nsCString host; host.Append(h0); host.Append("."); host.Append(h1); host.Append("."); host.Append(h2); host.Append("."); host.Append(h3); nsAllocator::Free(response); // now we know where to connect our data channel rv = mSTS->CreateTransport(host.GetBuffer(), port, nsnull, mBufferSegmentSize, mBufferMaxSize, getter_AddRefs(mDPipe)); // the data channel if (NS_FAILED(rv)) return FTP_ERROR; // The FTP connection thread will receive transport leve // AsyncOpen notifications. rv = mDPipe->AsyncOpen(this, nsnull); if (NS_FAILED(rv)) return FTP_ERROR; // hook ourself up as a proxy for progress notifications nsCOMPtr progressProxy(do_QueryInterface(mChannel)); rv = mDPipe->SetNotificationCallbacks(progressProxy); if (NS_FAILED(rv)) return FTP_ERROR; // we're connected figure out what type of transfer we're doing (ascii or binary) nsXPIDLCString type; rv = mFTPChannel->GetContentType(getter_Copies(type)); nsCAutoString typeStr; if (NS_FAILED(rv) || !type) typeStr = "bin"; else typeStr = type; PRInt32 textType = typeStr.Find("text"); if (textType == 0) // only send ascii for text type files mBin = PR_FALSE; else // all others are bin mBin = PR_TRUE; return FTP_S_MODE; } nsresult nsFtpConnectionThread::S_del_file() { nsresult rv; nsXPIDLCString filename; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(filename)); // XXX we should probably check to // XXX make sure we have an actual filename. if (NS_FAILED(rv)) return rv; nsCAutoString delStr("DELE "); delStr.Append(filename); delStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), delStr.GetBuffer())); rv = mCOutStream->Write(delStr.GetBuffer(), delStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_del_file() { if (mResponseCode != 2) { return FTP_S_DEL_DIR; } return FTP_COMPLETE; } nsresult nsFtpConnectionThread::S_del_dir() { nsresult rv; nsXPIDLCString dir; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(dir)); if (NS_FAILED(rv)) return rv; nsCAutoString delDirStr("RMD "); delDirStr.Append(dir); delDirStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), delDirStr.GetBuffer())); rv = mCOutStream->Write(delDirStr.GetBuffer(), delDirStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_del_dir() { if (mResponseCode != 2) { return FTP_ERROR; } return FTP_COMPLETE; } nsresult nsFtpConnectionThread::S_mkdir() { nsresult rv; nsXPIDLCString dir; PRUint32 bytes; rv = mURL->GetPath(getter_Copies(dir)); if (NS_FAILED(rv)) return rv; nsCAutoString mkdirStr("MKD "); mkdirStr.Append(dir); mkdirStr.Append(CRLF); PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x Writing \"%s\"\n", mURL.get(), mkdirStr.GetBuffer())); rv = mCOutStream->Write(mkdirStr.GetBuffer(), mkdirStr.Length(), &bytes); return rv; } FTP_STATE nsFtpConnectionThread::R_mkdir() { if (mResponseCode != 2) { return FTP_ERROR; } return FTP_COMPLETE; } /////////////////////////////////// // END: STATE METHODS /////////////////////////////////// #define FTP_COMMAND_CHANNEL_SEG_SIZE 64 #define FTP_COMMAND_CHANNEL_MAX_SIZE 512 //////////////////////////////////////////////////////////////////////////////// // nsIRunnable method: NS_IMETHODIMP nsFtpConnectionThread::Run() { nsresult rv; NS_WITH_SERVICE(nsIEventQueueService, eventQService, kEventQueueServiceCID, &rv); if (NS_FAILED(rv)) return rv; rv = eventQService->CreateThreadEventQueue(); if (NS_FAILED(rv)) return rv; rv = eventQService->GetThreadEventQueue(NS_CURRENT_THREAD, getter_AddRefs(mFTPEventQueue)); if (NS_FAILED(rv)) return rv; rv = eventQService->GetThreadEventQueue(NS_UI_THREAD, getter_AddRefs(mUIEventQ)); if (NS_FAILED(rv)) return rv; rv = nsServiceManager::GetService(kSocketTransportServiceCID, NS_GET_IID(nsISocketTransportService), getter_AddRefs(mSTS)); if(NS_FAILED(rv)) return rv; ///////////////////////// // COMMAND CHANNEL SETUP ///////////////////////// // use a cached connection if there is one. rv = mConnCache->RemoveConn(mCacheKey.GetBuffer(), &mConn); if (NS_FAILED(rv)) return rv; if (mConn) { mCachedConn = PR_TRUE; // we were passed in a connection to use mCPipe = mConn->mSocketTransport; mCInStream = mConn->mInputStream; mCOutStream = mConn->mOutputStream; mServerType = mConn->mServerType; mCwd = mConn->mCwd; mList = mConn->mList; } else { nsXPIDLCString host; rv = mURL->GetHost(getter_Copies(host)); if (NS_FAILED(rv)) return rv; // build our own rv = mSTS->CreateTransport(host, mPort, host, FTP_COMMAND_CHANNEL_SEG_SIZE, FTP_COMMAND_CHANNEL_MAX_SIZE, getter_AddRefs(mCPipe)); // the command channel if (NS_FAILED(rv)) return rv; // get the output stream so we can write to the server rv = mCPipe->OpenOutputStream(0, getter_AddRefs(mCOutStream)); if (NS_FAILED(rv)) return rv; rv = mCPipe->OpenInputStream(0, -1, getter_AddRefs(mCInStream)); if (NS_FAILED(rv)) return rv; // cache this stuff. mConn = new nsConnectionCacheObj(mCPipe, mCInStream, mCOutStream); if (!mConn) return NS_ERROR_OUT_OF_MEMORY; } /////////////////////////////// // END - COMMAND CHANNEL SETUP /////////////////////////////// if (!mCachedConn) { rv = DigestServerGreeting(); if (NS_FAILED(rv)) return rv; } else { // we're already connected to this server, skip login. mState = FTP_S_PASV; } mConnected = PR_TRUE; rv = Process(); mListener = 0; mChannel = 0; mConnCache = 0; return rv; } //////////////////////////////////////////////////////////////////////////////// // nsIRequest methods: NS_IMETHODIMP nsFtpConnectionThread::IsPending(PRBool *result) { nsresult rv = NS_OK; *result = PR_FALSE; nsAutoLock aLock(mLock); if (mCPipe) { rv = mCPipe->IsPending(result); if (NS_FAILED(rv)) return rv; } if (mDPipe) { rv = mDPipe->IsPending(result); if (NS_FAILED(rv)) return rv; } return rv; } NS_IMETHODIMP nsFtpConnectionThread::Cancel(void) { nsresult rv = NS_OK; nsAutoLock aLock(mLock); if (mCPipe) { rv = mCPipe->Cancel(); if (NS_FAILED(rv)) return rv; } if (mDPipe) { rv = mDPipe->Cancel(); if (NS_FAILED(rv)) return rv; } mInternalError = NS_ERROR_FAILURE; mState = FTP_COMPLETE; return rv; } NS_IMETHODIMP nsFtpConnectionThread::Suspend(void) { nsresult rv = NS_OK; nsAutoLock aLock(mLock); // suspending the underlying socket transport will // cause the FTP state machine to "suspend" when it // tries to use the transport. May not be granular // enough. if (mSuspendCount < 1) { mSuspendCount++; if (mCPipe) { rv = mCPipe->Suspend(); if (NS_FAILED(rv)) return rv; } if (mDPipe) { rv = mDPipe->Suspend(); if (NS_FAILED(rv)) return rv; } } return rv; } NS_IMETHODIMP nsFtpConnectionThread::Resume(void) { nsresult rv = NS_ERROR_FAILURE; nsAutoLock aLock(mLock); // resuming the underlying socket transports will // cause the FTP state machine to unblock and // go on about it's business. if (mSuspendCount) { // only a suspended thread can be resumed if (mCPipe) { rv = mCPipe->Resume(); if (NS_FAILED(rv)) return rv; } if (mDPipe) { rv = mDPipe->Resume(); if (NS_FAILED(rv)) return rv; } rv = NS_OK; } mSuspendCount--; return rv; } // nsIStreamObserver methods NS_IMETHODIMP nsFtpConnectionThread::OnStartRequest(nsIChannel *aChannel, nsISupports *aContext) { mState = FTP_S_MODE; // bump to the next state. return NS_OK; } NS_IMETHODIMP nsFtpConnectionThread::OnStopRequest(nsIChannel *aChannel, nsISupports *aContext, nsresult aStatus, const PRUnichar *aMsg) { return NS_OK; } nsresult nsFtpConnectionThread::Init(nsIProtocolHandler* aHandler, nsIChannel* aChannel, PRUint32 bufferSegmentSize, PRUint32 bufferMaxSize) { nsresult rv = NS_OK; if (mConnected) return NS_ERROR_ALREADY_CONNECTED; mBufferSegmentSize = bufferSegmentSize; mBufferMaxSize = bufferMaxSize; mLock = PR_NewLock(); if (!mLock) return NS_ERROR_OUT_OF_MEMORY; mMonitor = PR_NewMonitor(); if (!mMonitor) return NS_ERROR_OUT_OF_MEMORY; // parameter validation NS_ASSERTION(aChannel, "FTP: thread needs a channel"); // setup internal member variables mChannel = aChannel; // a straight com ptr to the channel rv = aChannel->GetURI(getter_AddRefs(mURL)); if (NS_FAILED(rv)) return rv; mURL->GetSpec(getter_Copies(mURLSpec)); // pull any username and/or password out of the uri nsXPIDLCString preHost; rv = mURL->GetPreHost(getter_Copies(preHost)); if (NS_FAILED(rv)) return rv; if (preHost) { // we found some username (and maybe password info) in the URL. // we know we're not going anonymously now. mAnonymous = PR_FALSE; char *colon = PL_strchr(preHost, ':'); if (colon) { *colon = '\0'; mPassword = colon+1; } mUsername = preHost; } // setup the connection cache key nsXPIDLCString host; rv = mURL->GetHost(getter_Copies(host)); if (NS_FAILED(rv)) return rv; PRInt32 port; rv = mURL->GetPort(&port); if (NS_FAILED(rv)) return rv; if (port > 0) mPort = port; mCacheKey.SetString(host); mCacheKey.Append(port); NS_WITH_SERVICE(nsIProxyObjectManager, pIProxyObjectManager, kProxyObjectManagerCID, &rv); if(NS_FAILED(rv)) return rv; // This proxied channel is used to set channel related // state on the *real* channel back in the main thread. rv = pIProxyObjectManager->GetProxyObject(NS_UI_THREAD_EVENTQ, NS_GET_IID(nsPIFTPChannel), aChannel, PROXY_SYNC | PROXY_ALWAYS, getter_AddRefs(mFTPChannel)); if (NS_FAILED(rv)) return rv; // get a proxied ptr to the FTP protocol handler service so we can control // the connection cache from here. rv = pIProxyObjectManager->GetProxyObject(NS_UI_THREAD_EVENTQ, NS_GET_IID(nsIConnectionCache), aHandler, PROXY_SYNC | PROXY_ALWAYS, getter_AddRefs(mConnCache)); return rv; } nsresult nsFtpConnectionThread::SetAction(FTP_ACTION aAction) { if (mConnected) return NS_ERROR_ALREADY_CONNECTED; mAction = aAction; return NS_OK; } nsresult nsFtpConnectionThread::DigestServerGreeting() { nsresult rv = NS_OK; char greetBuf[NS_FTP_BUFFER_READ_SIZE+1]; PRUint32 read = -1; mResponseCode = 0; PRBool multiline = PR_FALSE, lastline = PR_FALSE; while (1) { rv = mCInStream->Read(greetBuf, NS_FTP_BUFFER_READ_SIZE, &read); if (NS_FAILED(rv)) { PR_LOG(gFTPLog, PR_LOG_DEBUG, ("%x FTP:Digest() greeting read failed with rv = %d\n", mURL.get(), rv)); return rv; } if (read < 1) break; greetBuf[read] = '\0'; // we got something. look for a response code if (!mResponseCode) { PR_sscanf(greetBuf, "%d", &mResponseCode); mResponseCode = mResponseCode / 100; // truncate it } // first determine whether or not we're dealing with a // multiline response. //multiline = greetBuf[3] == '-'; // handle all the lines we Read() char *buf = greetBuf, *lineEnd = nsnull; while ( (lineEnd = PL_strstr(buf, CRLF)) ) { char tmpChar = lineEnd[2]; lineEnd[2] = '\0'; mResponseMsg += buf+4; lastline = buf[3] != '-'; // look for last line indicator lineEnd[2] = tmpChar; buf = lineEnd+2; // move to the next line } if (lineEnd && lineEnd[4]) mResponseMsg += lineEnd+4; if (lastline) break; } return rv; } nsresult nsFtpConnectionThread::StopProcessing() { nsresult rv; PRUnichar* errorMsg = nsnull; // Release the transports mCPipe = 0; mDPipe = 0; // kill the event loop mKeepRunning = PR_FALSE; // setup any internal error message to propegate if (NS_FAILED(mInternalError)) { // generate a FTP specific error msg. rv = MapResultCodeToString(mInternalError, &errorMsg); if (NS_FAILED(rv)) return rv; } rv = mFTPChannel->Stopped(mInternalError, errorMsg); if (NS_FAILED(rv)) return rv; // if we have an observer, end the transaction if (mObserver) { rv = mObserver->OnStopRequest(mChannel, mObserverContext, mInternalError, errorMsg); } return rv; } // Here's where we do all the string whacking/parsing magic to determine // what type of server it is we're dealing with. void nsFtpConnectionThread::SetSystInternals(void) { if (mResponseMsg.Equals("UNIX Type: L8 MAC-OS MachTen", 28)) { mServerType = FTP_MACHTEN_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Find("UNIX") > -1) { mServerType = FTP_UNIX_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Find("windows", PR_TRUE) > -1) { // treat any server w/ "windows" in it as a dos based NT server. mServerType = FTP_NT_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Equals("VMS", 3)) { mServerType = FTP_VMS_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Equals("VMS/CMS", 6) || mResponseMsg.Equals("VM ", 3)) { mServerType = FTP_CMS_TYPE; } else if (mResponseMsg.Equals("DCTS", 4)) { mServerType = FTP_DCTS_TYPE; } else if (mResponseMsg.Find("MAC-OS TCP/Connect II") > -1) { mServerType = FTP_TCPC_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Equals("MACOS Peter's Server", 20)) { mServerType = FTP_PETER_LEWIS_TYPE; mList = PR_TRUE; } else if (mResponseMsg.Equals("MACOS WebSTAR FTP", 17)) { mServerType = FTP_WEBSTAR_TYPE; mList = PR_TRUE; } mConn->mServerType = mServerType; mConn->mList = mList; } FTP_STATE nsFtpConnectionThread::FindActionState(void) { // These operations require the separate data channel. if (mAction == GET || mAction == PUT) { // we're doing an operation that requies the data channel. // figure out what kind of data channel we want to setup, // and do it. if (mUsePasv) return FTP_S_PASV; else // until we have PORT support, we'll just fail. // return FTP_S_PORT; return FTP_ERROR; } // These operations use the command channel response as the // data to return to the user. if (mAction == DEL) return FTP_S_DEL_FILE; // we assume it's a file if (mAction == MKDIR) return FTP_S_MKDIR; return FTP_ERROR; } FTP_STATE nsFtpConnectionThread::FindGetState(void) { nsXPIDLCString path; nsresult rv; FTP_STATE result = FTP_ERROR; rv = mURL->GetPath(getter_Copies(path)); if (NS_FAILED(rv)) return FTP_ERROR; if (mServerType == FTP_VMS_TYPE) { // check for directory if (!path[0] || (path[0] == '/' && !path[1]) ) { result = FTP_S_LIST; } else if (!PL_strchr(path, '/')) { result = FTP_S_RETR; } else { result = FTP_S_CWD; } } else { if (path[PL_strlen(path) -1] == '/') { result = FTP_S_CWD; } else { result = FTP_S_RETR; } } return result; } nsresult nsFtpConnectionThread::MapResultCodeToString(nsresult aResultCode, PRUnichar* *aOutMsg) { nsCAutoString errorMsg; switch (aResultCode) { case NS_ERROR_FTP_LOGIN: { errorMsg = "FTP: Login failed."; break; } case NS_ERROR_FTP_MODE: { errorMsg = "FTP: MODE command failed."; break; } case NS_ERROR_FTP_CWD: { errorMsg = "FTP: CWD command failed."; break; } case NS_ERROR_FTP_PASV: { errorMsg = "FTP: PASV command failed."; break; } case NS_ERROR_FTP_DEL_DIR: { errorMsg = "FTP: DEL directory command failed."; break; } case NS_ERROR_FTP_MKDIR: { errorMsg = "FTP: MKDIR command failed"; break; } default: { errorMsg = "Unknown FTP error."; break; } } // END: switch *aOutMsg = errorMsg.ToNewUnicode(); return NS_OK; } void nsFtpConnectionThread::SetDirMIMEType(nsString& aString) { // the from content type is a string of the form // "text/ftp-dir-SERVER_TYPE" where SERVER_TYPE represents the server we're talking to. switch (mServerType) { case FTP_UNIX_TYPE: aString.Append("unix"); break; case FTP_DCTS_TYPE: aString.Append("dcts"); break; case FTP_NCSA_TYPE: aString.Append("ncsa"); break; case FTP_PETER_LEWIS_TYPE: aString.Append("peter_lewis"); break; case FTP_MACHTEN_TYPE: aString.Append("machten"); break; case FTP_CMS_TYPE: aString.Append("cms"); break; case FTP_TCPC_TYPE: aString.Append("tcpc"); break; case FTP_VMS_TYPE: aString.Append("vms"); break; case FTP_NT_TYPE: aString.Append("nt"); break; case FTP_WEBSTAR_TYPE: aString.Append("webstar"); break; default: aString.Append("generic"); } }