Checking into the branch...already checked into the trunk r/sr=darin git-svn-id: svn://10.0.0.236/branches/MOZILLA_0_9_4_BRANCH@112028 18797224-902f-48f8-a5cc-f745e15eee43
2056 lines
59 KiB
C++
2056 lines
59 KiB
C++
/* -*- 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 "nsFTPChannel.h"
|
|
#include "nsFtpConnectionThread.h"
|
|
#include "nsFtpControlConnection.h"
|
|
#include "nsFtpProtocolHandler.h"
|
|
|
|
#include <limits.h>
|
|
|
|
#include "nsISocketTransport.h"
|
|
#include "nsIStreamConverterService.h"
|
|
#include "prprf.h"
|
|
#include "prlog.h"
|
|
#include "prnetdb.h"
|
|
#include "netCore.h"
|
|
#include "ftpCore.h"
|
|
#include "nsProxiedService.h"
|
|
#include "nsCRT.h"
|
|
#include "nsIInterfaceRequestor.h"
|
|
#include "nsIURL.h"
|
|
#include "nsEscape.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsIDNSService.h" // for host error code
|
|
#include "nsIWalletService.h"
|
|
#include "nsIMemory.h"
|
|
#include "nsIStringStream.h"
|
|
#include "nsIPref.h"
|
|
#include "nsMimeTypes.h"
|
|
#include "nsIStringBundle.h"
|
|
|
|
#include "nsICacheEntryDescriptor.h"
|
|
#include "nsICacheListener.h"
|
|
|
|
static NS_DEFINE_CID(kPrefCID, NS_PREF_CID);
|
|
static NS_DEFINE_CID(kWalletServiceCID, NS_WALLETSERVICE_CID);
|
|
static NS_DEFINE_CID(kStreamConverterServiceCID, NS_STREAMCONVERTERSERVICE_CID);
|
|
static NS_DEFINE_CID(kIOServiceCID, NS_IOSERVICE_CID);
|
|
static NS_DEFINE_CID(kStreamListenerTeeCID, NS_STREAMLISTENERTEE_CID);
|
|
static NS_DEFINE_CID(kSocketTransportServiceCID, NS_SOCKETTRANSPORTSERVICE_CID);
|
|
|
|
|
|
#if defined(PR_LOGGING)
|
|
extern PRLogModuleInfo* gFTPLog;
|
|
#endif /* PR_LOGGING */
|
|
|
|
class DataRequestForwarder : public nsIFTPChannel,
|
|
public nsIStreamListener,
|
|
public nsIInterfaceRequestor,
|
|
public nsIProgressEventSink
|
|
{
|
|
public:
|
|
DataRequestForwarder();
|
|
virtual ~DataRequestForwarder();
|
|
nsresult Init(nsIRequest *request);
|
|
|
|
nsresult SetStreamListener(nsIStreamListener *listener);
|
|
nsresult SetCacheEntry(nsICacheEntryDescriptor *entry, PRBool writing);
|
|
|
|
NS_DECL_ISUPPORTS
|
|
NS_DECL_NSISTREAMLISTENER
|
|
NS_DECL_NSIREQUESTOBSERVER
|
|
NS_DECL_NSIINTERFACEREQUESTOR
|
|
NS_DECL_NSIPROGRESSEVENTSINK
|
|
|
|
NS_FORWARD_NSIREQUEST(mRequest->)
|
|
NS_FORWARD_NSICHANNEL(mFTPChannel->)
|
|
NS_FORWARD_NSIFTPCHANNEL(mFTPChannel->)
|
|
|
|
PRUint32 GetBytesTransfered() {return mBytesTransfered;} ;
|
|
void Uploading(PRBool value);
|
|
void RetryConnection();
|
|
|
|
protected:
|
|
|
|
nsCOMPtr<nsIRequest> mRequest;
|
|
nsCOMPtr<nsIFTPChannel> mFTPChannel;
|
|
nsCOMPtr<nsIStreamListener> mListener;
|
|
nsCOMPtr<nsIProgressEventSink> mEventSink;
|
|
nsCOMPtr<nsICacheEntryDescriptor> mCacheEntry;
|
|
|
|
PRUint32 mBytesTransfered;
|
|
PRPackedBool mDelayedOnStartFired;
|
|
PRPackedBool mUploading;
|
|
PRPackedBool mRetrying;
|
|
|
|
nsresult DelayedOnStartRequest(nsIRequest *request, nsISupports *ctxt);
|
|
};
|
|
|
|
|
|
// The whole purpose of this class is to opaque
|
|
// the socket transport so that clients only see
|
|
// the same nsIChannel/nsIRequest that they started.
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS7(DataRequestForwarder,
|
|
nsIStreamListener,
|
|
nsIRequestObserver,
|
|
nsIFTPChannel,
|
|
nsIChannel,
|
|
nsIRequest,
|
|
nsIInterfaceRequestor,
|
|
nsIProgressEventSink);
|
|
|
|
|
|
DataRequestForwarder::DataRequestForwarder()
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder CREATED\n", this));
|
|
|
|
mBytesTransfered = 0;
|
|
mRetrying = mUploading = mDelayedOnStartFired = PR_FALSE;
|
|
NS_INIT_ISUPPORTS();
|
|
}
|
|
|
|
DataRequestForwarder::~DataRequestForwarder()
|
|
{
|
|
}
|
|
|
|
// nsIInterfaceRequestor method
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::GetInterface(const nsIID &anIID, void **aResult )
|
|
{
|
|
if (anIID.Equals(NS_GET_IID(nsIProgressEventSink)))
|
|
{
|
|
*aResult = NS_STATIC_CAST(nsIProgressEventSink*, this);
|
|
NS_ADDREF_THIS();
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_NO_INTERFACE;
|
|
}
|
|
|
|
|
|
nsresult
|
|
DataRequestForwarder::Init(nsIRequest *request)
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder Init [request=%x]\n", this, request));
|
|
|
|
NS_ENSURE_ARG(request);
|
|
|
|
// for the forwarding declarations.
|
|
mRequest = request;
|
|
mFTPChannel = do_QueryInterface(request);
|
|
mEventSink = do_QueryInterface(request);
|
|
mListener = do_QueryInterface(request);
|
|
|
|
if (!mRequest || !mFTPChannel)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
DataRequestForwarder::Uploading(PRBool value)
|
|
{
|
|
mUploading = value;
|
|
}
|
|
|
|
nsresult
|
|
DataRequestForwarder::SetCacheEntry(nsICacheEntryDescriptor *cacheEntry, PRBool writing)
|
|
{
|
|
// if there is a cache entry descriptor, send data to it.
|
|
if (!cacheEntry)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
mCacheEntry = cacheEntry;
|
|
if (!writing)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsITransport> cacheTransport;
|
|
nsresult rv = cacheEntry->GetTransport(getter_AddRefs(cacheTransport));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIOutputStream> out;
|
|
rv = cacheTransport->OpenOutputStream(0, PRUint32(-1), 0, getter_AddRefs(out));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIStreamListenerTee> tee =
|
|
do_CreateInstance(kStreamListenerTeeCID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = tee->Init(mListener, out);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mListener = do_QueryInterface(tee, &rv);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
nsresult
|
|
DataRequestForwarder::SetStreamListener(nsIStreamListener *listener)
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder SetStreamListener [listener=%x]\n", this, listener));
|
|
|
|
mListener = listener;
|
|
if (!mListener)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
DataRequestForwarder::DelayedOnStartRequest(nsIRequest *request, nsISupports *ctxt)
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder DelayedOnStartRequest \n", this));
|
|
return mListener->OnStartRequest(this, ctxt);
|
|
}
|
|
|
|
void
|
|
DataRequestForwarder::RetryConnection()
|
|
{
|
|
// The problem here is that if we send a second PASV, our listener would
|
|
// get an OnStop from the socket transport, and fail. So we temporarily
|
|
// suspend notifications
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder RetryConnection \n", this));
|
|
|
|
mRetrying = PR_TRUE;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
|
|
{
|
|
NS_ASSERTION(mListener, "No Listener Set.");
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder OnStartRequest \n[mRetrying=%d]", this, mRetrying));
|
|
|
|
if (mRetrying) {
|
|
mRetrying = PR_FALSE;
|
|
return NS_OK;
|
|
}
|
|
|
|
if (!mListener)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
if (mUploading)
|
|
return mListener->OnStartRequest(this, ctxt);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult statusCode)
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) DataRequestForwarder OnStopRequest [status=%x, mRetrying=%d]\n", this, statusCode, mRetrying));
|
|
|
|
if (mRetrying)
|
|
return NS_OK;
|
|
|
|
nsCOMPtr<nsITransportRequest> trequest(do_QueryInterface(request));
|
|
if (trequest)
|
|
{
|
|
nsCOMPtr<nsITransport> trans;
|
|
trequest->GetTransport(getter_AddRefs(trans));
|
|
nsCOMPtr<nsISocketTransport> sTrans (do_QueryInterface(trans));
|
|
if (sTrans)
|
|
sTrans->SetReuseConnection(PR_FALSE);
|
|
}
|
|
if (!mListener)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
return mListener->OnStopRequest(this, ctxt, statusCode);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, nsIInputStream *input, PRUint32 offset, PRUint32 count)
|
|
{
|
|
nsresult rv;
|
|
NS_ASSERTION(mListener, "No Listener Set.");
|
|
NS_ASSERTION(!mUploading, "Since we are uploading, we should never get a ODA");
|
|
|
|
if (!mListener)
|
|
return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
// we want to delay firing the onStartRequest until we know that there is data
|
|
if (!mDelayedOnStartFired) {
|
|
mDelayedOnStartFired = PR_TRUE;
|
|
rv = DelayedOnStartRequest(request, ctxt);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
rv = mListener->OnDataAvailable(this, ctxt, input, mBytesTransfered, count);
|
|
mBytesTransfered += count;
|
|
return rv;
|
|
}
|
|
|
|
|
|
// nsIProgressEventSink methods
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::OnStatus(nsIRequest *request, nsISupports *aContext,
|
|
nsresult aStatus, const PRUnichar* aStatusArg)
|
|
{
|
|
if (!mEventSink)
|
|
return NS_OK;
|
|
|
|
return mEventSink->OnStatus(nsnull, nsnull, aStatus, nsnull);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
DataRequestForwarder::OnProgress(nsIRequest *request, nsISupports* aContext,
|
|
PRUint32 aProgress, PRUint32 aProgressMax) {
|
|
if (!mEventSink)
|
|
return NS_OK;
|
|
PRUint32 count = mUploading ? aProgress : mBytesTransfered;
|
|
PRUint32 max = mUploading ? aProgressMax : 0;
|
|
return mEventSink->OnProgress(this, nsnull, count, max);
|
|
}
|
|
|
|
|
|
|
|
NS_IMPL_THREADSAFE_ISUPPORTS3(nsFtpState,
|
|
nsIStreamListener,
|
|
nsIRequestObserver,
|
|
nsIRequest);
|
|
|
|
nsFtpState::nsFtpState() {
|
|
NS_INIT_REFCNT();
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_ALWAYS, ("(%x) nsFtpState created", this));
|
|
// bool init
|
|
mRETRFailed = PR_FALSE;
|
|
mWaitingForDConn = mTryingCachedControl = mRetryPass = PR_FALSE;
|
|
mFireCallbacks = mKeepRunning = mAnonymous = PR_TRUE;
|
|
|
|
mAction = GET;
|
|
mState = FTP_COMMAND_CONNECT;
|
|
mNextState = FTP_S_USER;
|
|
|
|
mInternalError = NS_OK;
|
|
mSuspendCount = 0;
|
|
mPort = 21;
|
|
|
|
mReceivedControlData = PR_FALSE;
|
|
mControlStatus = NS_OK;
|
|
mControlReadContinue = PR_FALSE;
|
|
mControlReadBrokenLine = PR_FALSE;
|
|
|
|
mIPv6Checked = PR_FALSE;
|
|
mIPv6ServerAddress = nsnull;
|
|
|
|
mControlConnection = nsnull;
|
|
mDRequestForwarder = nsnull;
|
|
|
|
mGenerateRawContent = mGenerateHTMLContent = PR_FALSE;
|
|
nsresult rv;
|
|
nsCOMPtr<nsIPref> pPref(do_GetService(kPrefCID, &rv));
|
|
if (NS_SUCCEEDED(rv) || pPref) {
|
|
pPref->GetBoolPref("network.dir.generate_html", &mGenerateHTMLContent);
|
|
pPref->GetBoolPref("network.ftp.raw_output", &mGenerateRawContent);
|
|
}
|
|
}
|
|
|
|
nsFtpState::~nsFtpState()
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_ALWAYS, ("(%x) nsFtpState destroyed", this));
|
|
|
|
if (mIPv6ServerAddress) nsMemory::Free(mIPv6ServerAddress);
|
|
NS_IF_RELEASE(mDRequestForwarder);
|
|
}
|
|
|
|
|
|
// nsIStreamListener implementation
|
|
NS_IMETHODIMP
|
|
nsFtpState::OnDataAvailable(nsIRequest *request,
|
|
nsISupports *aContext,
|
|
nsIInputStream *aInStream,
|
|
PRUint32 aOffset,
|
|
PRUint32 aCount)
|
|
{
|
|
if (aCount == 0)
|
|
return NS_OK; /*** should this be an error?? */
|
|
|
|
if (!mReceivedControlData) {
|
|
nsCOMPtr<nsIProgressEventSink> sink(do_QueryInterface(mChannel));
|
|
if (sink)
|
|
// parameter can be null cause the channel fills them in.
|
|
sink->OnStatus(nsnull, nsnull,
|
|
NS_NET_STATUS_BEGIN_FTP_TRANSACTION, nsnull);
|
|
|
|
mReceivedControlData = PR_TRUE;
|
|
}
|
|
|
|
char* buffer = (char*)nsMemory::Alloc(aCount + 1);
|
|
nsresult rv = aInStream->Read(buffer, aCount, &aCount);
|
|
if (NS_FAILED(rv))
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) nsFtpState - error reading %x\n", this, rv));
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
buffer[aCount] = '\0';
|
|
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) reading %d bytes: \"%s\"", this, aCount, buffer));
|
|
|
|
// get the response code out.
|
|
if (!mControlReadContinue && !mControlReadBrokenLine) {
|
|
PR_sscanf(buffer, "%d", &mResponseCode);
|
|
if (buffer[3] == '-') {
|
|
mControlReadContinue = PR_TRUE;
|
|
} else {
|
|
mControlReadBrokenLine = !PL_strstr(buffer, CRLF);
|
|
}
|
|
}
|
|
|
|
// see if we're handling a multi-line response.
|
|
if (mControlReadContinue) {
|
|
// yup, multi-line response, start appending
|
|
char *tmpBuffer = nsnull, *crlf = nsnull;
|
|
|
|
// if we have data from our last round be sure
|
|
// to prepend it.
|
|
char *cleanup = nsnull;
|
|
if (!mControlReadCarryOverBuf.IsEmpty() ) {
|
|
mControlReadCarryOverBuf += buffer;
|
|
cleanup = tmpBuffer = mControlReadCarryOverBuf.ToNewCString();
|
|
mControlReadCarryOverBuf.Truncate(0);
|
|
if (!tmpBuffer) return NS_ERROR_OUT_OF_MEMORY;
|
|
} else {
|
|
cleanup = tmpBuffer = buffer;
|
|
}
|
|
|
|
PRBool lastLine = PR_FALSE;
|
|
while ( (crlf = PL_strstr(tmpBuffer, CRLF)) ) {
|
|
char tmpChar = crlf[2];
|
|
crlf[2] = '\0';
|
|
// see if this is the last line
|
|
if (tmpBuffer[3] != '-' &&
|
|
nsCRT::IsAsciiDigit(tmpBuffer[0]) &&
|
|
nsCRT::IsAsciiDigit(tmpBuffer[1]) &&
|
|
nsCRT::IsAsciiDigit(tmpBuffer[2]) ) {
|
|
lastLine = PR_TRUE;
|
|
}
|
|
mResponseMsg += tmpBuffer+4; // skip over the code and '-'
|
|
crlf[2] = tmpChar;
|
|
tmpBuffer = crlf+2;
|
|
}
|
|
if (*tmpBuffer)
|
|
mControlReadCarryOverBuf = tmpBuffer;
|
|
|
|
if (cleanup) nsMemory::Free(cleanup);
|
|
|
|
// see if this was the last line
|
|
if (lastLine) {
|
|
// 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;
|
|
}
|
|
mControlReadContinue = PR_FALSE;
|
|
} else {
|
|
// don't increment state, we need to read more.
|
|
mControlReadContinue = PR_TRUE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NS_ASSERTION(mState != mNextState, "ftp read state mixup");
|
|
|
|
// get the rest of the line
|
|
if (mControlReadBrokenLine) {
|
|
mResponseMsg += buffer;
|
|
mControlReadBrokenLine = !PL_strstr(buffer, CRLF);
|
|
if (!mControlReadBrokenLine)
|
|
mState = mNextState;
|
|
} else {
|
|
mResponseMsg = buffer+4;
|
|
mState = mNextState;
|
|
}
|
|
}
|
|
|
|
if (!mControlReadContinue)
|
|
{
|
|
if (mFTPEventSink)
|
|
mFTPEventSink->OnFTPControlLog(PR_TRUE, mResponseMsg);
|
|
|
|
return Process();
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
|
|
// nsIRequestObserver implementation
|
|
NS_IMETHODIMP
|
|
nsFtpState::OnStartRequest(nsIRequest *request, nsISupports *aContext)
|
|
{
|
|
#if defined(PR_LOGGING)
|
|
nsXPIDLCString spec;
|
|
(void)mURL->GetSpec(getter_Copies(spec));
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) nsFtpState::OnStartRequest() (spec =%s)\n",
|
|
this, NS_STATIC_CAST(const char*, spec)));
|
|
#endif
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::OnStopRequest(nsIRequest *request, nsISupports *aContext,
|
|
nsresult aStatus)
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) nsFtpState::OnStopRequest() rv=%x\n", this, aStatus));
|
|
mControlStatus = aStatus;
|
|
|
|
// HACK. This may not always work. I am assuming that I can mask the OnStopRequest
|
|
// notification since I created it via the control connection.
|
|
if (mTryingCachedControl && NS_FAILED(aStatus) && NS_SUCCEEDED(mInternalError)) {
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) nsFtpState::OnStopRequest() cache connect failed. Reconnecting...\n", this, aStatus));
|
|
mTryingCachedControl = PR_FALSE;
|
|
Connect();
|
|
return NS_OK;
|
|
}
|
|
|
|
if (NS_FAILED(aStatus)) // aStatus will be NS_OK if we are sucessfully disconnecing the control connection.
|
|
StopProcessing();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::EstablishControlConnection()
|
|
{
|
|
nsresult rv;
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) trying cached control\n", this));
|
|
|
|
nsISupports* connection;
|
|
(void) nsFtpProtocolHandler::RemoveConnection(mURL, &connection);
|
|
|
|
if (connection) {
|
|
mControlConnection = NS_STATIC_CAST(nsFtpControlConnection*, (nsIStreamListener*)connection);
|
|
if (mControlConnection->IsAlive())
|
|
{
|
|
// set stream listener of the control connection to be us.
|
|
(void) mControlConnection->SetStreamListener(NS_STATIC_CAST(nsIStreamListener*, this));
|
|
|
|
// read cached variables into us.
|
|
mServerType = mControlConnection->mServerType;
|
|
mPassword = mControlConnection->mPassword;
|
|
|
|
mTryingCachedControl = PR_TRUE;
|
|
|
|
// we're already connected to this server, skip login.
|
|
mState = FTP_S_PASV;
|
|
mResponseCode = 530; //assume the control connection was dropped.
|
|
mControlStatus = NS_OK;
|
|
mReceivedControlData = PR_FALSE; // For this request, we have not.
|
|
|
|
// if we succeed, return. Otherwise, we need to
|
|
// create a transport
|
|
rv = mControlConnection->Connect(mProxyInfo);
|
|
if (NS_SUCCEEDED(rv))
|
|
return rv;
|
|
}
|
|
#if defined(PR_LOGGING)
|
|
else
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) isAlive return false\n", this));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) creating control\n", this));
|
|
|
|
mState = FTP_READ_BUF;
|
|
mNextState = FTP_S_USER;
|
|
|
|
nsXPIDLCString host;
|
|
rv = mURL->GetHost(getter_Copies(host));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mControlConnection = new nsFtpControlConnection(host, mPort);
|
|
if (!mControlConnection) return NS_ERROR_OUT_OF_MEMORY;
|
|
|
|
NS_ADDREF(mControlConnection);
|
|
|
|
// Must do it this way 'cuz the channel intercepts the progress notifications.
|
|
(void) mControlConnection->SetStreamListener(NS_STATIC_CAST(nsIStreamListener*, this));
|
|
|
|
return mControlConnection->Connect(mProxyInfo);
|
|
}
|
|
|
|
void
|
|
nsFtpState::MoveToNextState(FTP_STATE nextState)
|
|
{
|
|
if (NS_FAILED(mInternalError))
|
|
{
|
|
mState = FTP_ERROR;
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) FAILED (%x)\n", this, mInternalError));
|
|
}
|
|
else
|
|
{
|
|
mState = FTP_READ_BUF;
|
|
mNextState = nextState;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::Process()
|
|
{
|
|
nsresult rv = NS_OK;
|
|
PRBool processingRead = PR_TRUE;
|
|
|
|
while (mKeepRunning && processingRead)
|
|
{
|
|
switch(mState)
|
|
{
|
|
case FTP_COMMAND_CONNECT:
|
|
KillControlConnection();
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) Establishing control connection...", this));
|
|
mInternalError = EstablishControlConnection(); // sets mState
|
|
if (NS_FAILED(mInternalError)) {
|
|
mState = FTP_ERROR;
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) FAILED\n", this));
|
|
}
|
|
else
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) SUCCEEDED\n", this));
|
|
break;
|
|
|
|
case FTP_READ_BUF:
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) Waiting for control data (%x)...", this, rv));
|
|
processingRead = PR_FALSE;
|
|
break;
|
|
|
|
case FTP_ERROR: // xx needs more work to handle dropped control connection cases
|
|
if (mTryingCachedControl && mResponseCode == 530 &&
|
|
mInternalError == NS_ERROR_FTP_PASV) {
|
|
// The user was logged out during an pasv operation
|
|
// we want to restart this request with a new control
|
|
// channel.
|
|
mState = FTP_COMMAND_CONNECT;
|
|
}
|
|
else if (mResponseCode == 421 &&
|
|
mInternalError != NS_ERROR_FTP_LOGIN) {
|
|
// The command channel dropped for some reason.
|
|
// Fire it back up, unless we were trying to login
|
|
// in which case the server might just be telling us
|
|
// that the max number of users has been reached...
|
|
mState = FTP_COMMAND_CONNECT;
|
|
} else {
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) FTP_ERROR - Calling StopProcessing\n", this));
|
|
rv = StopProcessing();
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
|
|
processingRead = PR_FALSE;
|
|
}
|
|
break;
|
|
|
|
case FTP_COMPLETE:
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) COMPLETE\n", this));
|
|
rv = StopProcessing();
|
|
NS_ASSERTION(NS_SUCCEEDED(rv), "StopProcessing failed.");
|
|
processingRead = PR_FALSE;
|
|
break;
|
|
|
|
// USER
|
|
case FTP_S_USER:
|
|
rv = S_user();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
MoveToNextState(FTP_R_USER);
|
|
break;
|
|
|
|
case FTP_R_USER:
|
|
mState = R_user();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
break;
|
|
// PASS
|
|
case FTP_S_PASS:
|
|
rv = S_pass();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
MoveToNextState(FTP_R_PASS);
|
|
break;
|
|
|
|
case FTP_R_PASS:
|
|
mState = R_pass();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
break;
|
|
// ACCT
|
|
case FTP_S_ACCT:
|
|
rv = S_acct();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
MoveToNextState(FTP_R_ACCT);
|
|
break;
|
|
|
|
case FTP_R_ACCT:
|
|
mState = R_acct();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
break;
|
|
|
|
// SYST
|
|
case FTP_S_SYST:
|
|
rv = S_syst();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
MoveToNextState(FTP_R_SYST);
|
|
break;
|
|
|
|
case FTP_R_SYST:
|
|
mState = R_syst();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_LOGIN;
|
|
|
|
break;
|
|
|
|
// TYPE
|
|
case FTP_S_TYPE:
|
|
rv = S_type();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = rv;
|
|
|
|
MoveToNextState(FTP_R_TYPE);
|
|
break;
|
|
|
|
case FTP_R_TYPE:
|
|
mState = R_type();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
// CWD
|
|
case FTP_S_CWD:
|
|
rv = S_cwd();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_CWD;
|
|
|
|
MoveToNextState(FTP_R_CWD);
|
|
break;
|
|
|
|
case FTP_R_CWD:
|
|
mState = R_cwd();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_CWD;
|
|
break;
|
|
|
|
// LIST
|
|
case FTP_S_LIST:
|
|
rv = S_list();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_CWD;
|
|
|
|
MoveToNextState(FTP_R_LIST);
|
|
break;
|
|
|
|
case FTP_R_LIST:
|
|
mState = R_list();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
|
|
// SIZE
|
|
case FTP_S_SIZE:
|
|
rv = S_size();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = rv;
|
|
|
|
MoveToNextState(FTP_R_SIZE);
|
|
break;
|
|
|
|
case FTP_R_SIZE:
|
|
mState = R_size();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
|
|
// REST
|
|
case FTP_S_REST:
|
|
rv = S_rest();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = rv;
|
|
|
|
MoveToNextState(FTP_R_REST);
|
|
break;
|
|
|
|
case FTP_R_REST:
|
|
mState = R_rest();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
|
|
// RETR
|
|
case FTP_S_RETR:
|
|
rv = S_retr();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = rv;
|
|
|
|
MoveToNextState(FTP_R_RETR);
|
|
break;
|
|
|
|
case FTP_R_RETR:
|
|
|
|
mState = R_retr();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
|
|
// STOR
|
|
case FTP_S_STOR:
|
|
rv = S_stor();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = rv;
|
|
|
|
MoveToNextState(FTP_R_STOR);
|
|
break;
|
|
|
|
case FTP_R_STOR:
|
|
mState = R_stor();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
|
|
break;
|
|
|
|
// PASV
|
|
case FTP_S_PASV:
|
|
rv = S_pasv();
|
|
|
|
if (NS_FAILED(rv))
|
|
mInternalError = NS_ERROR_FTP_PASV;
|
|
|
|
MoveToNextState(FTP_R_PASV);
|
|
break;
|
|
|
|
case FTP_R_PASV:
|
|
mState = R_pasv();
|
|
|
|
if (FTP_ERROR == mState)
|
|
mInternalError = NS_ERROR_FTP_PASV;
|
|
|
|
break;
|
|
|
|
default:
|
|
;
|
|
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
///////////////////////////////////
|
|
// STATE METHODS
|
|
///////////////////////////////////
|
|
nsresult
|
|
nsFtpState::S_user() {
|
|
// some servers on connect send us a 421. (84525)
|
|
if (mResponseCode == 421)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
nsresult rv;
|
|
nsCAutoString usernameStr("USER ");
|
|
|
|
if (mAnonymous) {
|
|
usernameStr.Append("anonymous");
|
|
} else {
|
|
if (!mUsername.Length()) {
|
|
if (!mAuthPrompter) return NS_ERROR_NOT_INITIALIZED;
|
|
PRUnichar *user = nsnull, *passwd = nsnull;
|
|
PRBool retval;
|
|
nsXPIDLCString host;
|
|
rv = mURL->GetHost(getter_Copies(host));
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsAutoString hostU; hostU.AppendWithConversion(host);
|
|
|
|
nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIStringBundle> bundle;
|
|
rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
|
|
|
|
|
|
nsXPIDLString formatedString;
|
|
const PRUnichar *formatStrings[1] = { hostU.get()};
|
|
rv = bundle->FormatStringFromName(NS_LITERAL_STRING("EnterUserPasswordFor").get(),
|
|
formatStrings, 1,
|
|
getter_Copies(formatedString));
|
|
|
|
rv = mAuthPrompter->PromptUsernameAndPassword(nsnull,
|
|
formatedString,
|
|
NS_ConvertASCIItoUCS2(host).get(), // XXX i18n
|
|
nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
|
|
&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.AppendWithConversion(mUsername);
|
|
}
|
|
usernameStr.Append(CRLF);
|
|
|
|
return SendFTPCommand(usernameStr);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_user() {
|
|
if (mResponseCode/100 == 3) {
|
|
// send off the password
|
|
return FTP_S_PASS;
|
|
} else if (mResponseCode/100 == 2) {
|
|
// no password required, we're already logged in
|
|
return FTP_S_SYST;
|
|
} else if (mResponseCode/100 == 5) {
|
|
// problem logging in. typically this means the server
|
|
// has reached it's user limit.
|
|
return FTP_ERROR;
|
|
} 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
|
|
nsFtpState::S_pass() {
|
|
nsresult rv;
|
|
nsCAutoString passwordStr("PASS ");
|
|
|
|
mResponseMsg = "";
|
|
|
|
if (mAnonymous) {
|
|
char* anonPassword;
|
|
nsCOMPtr<nsIPref> pPref(do_GetService(kPrefCID, &rv));
|
|
if (NS_SUCCEEDED(rv) || pPref)
|
|
rv = pPref->CopyCharPref("network.ftp.anonymous_password", &anonPassword);
|
|
if (NS_SUCCEEDED(rv) && anonPassword && *anonPassword != '\0') {
|
|
passwordStr.Append(anonPassword);
|
|
nsMemory::Free(anonPassword);
|
|
}
|
|
else {
|
|
// default to
|
|
passwordStr.Append("mozilla@");
|
|
}
|
|
} else {
|
|
if (!mPassword.Length() || mRetryPass) {
|
|
if (!mAuthPrompter) return NS_ERROR_NOT_INITIALIZED;
|
|
|
|
PRUnichar *passwd = nsnull;
|
|
PRBool retval;
|
|
|
|
nsXPIDLCString host;
|
|
rv = mURL->GetHost(getter_Copies(host));
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsAutoString hostU; hostU.AppendWithConversion(host);
|
|
|
|
nsCOMPtr<nsIStringBundleService> bundleService = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsIStringBundle> bundle;
|
|
rv = bundleService->CreateBundle(NECKO_MSGS_URL, getter_AddRefs(bundle));
|
|
|
|
nsXPIDLString formatedString;
|
|
const PRUnichar *formatStrings[2] = { mUsername.get(), hostU.get() };
|
|
rv = bundle->FormatStringFromName(NS_LITERAL_STRING("EnterPasswordFor").get(),
|
|
formatStrings, 2,
|
|
getter_Copies(formatedString));
|
|
|
|
nsXPIDLCString prePath;
|
|
rv = mURL->GetPrePath(getter_Copies(prePath));
|
|
if (NS_FAILED(rv)) return rv;
|
|
rv = mAuthPrompter->PromptPassword(nsnull,
|
|
formatedString,
|
|
NS_ConvertASCIItoUCS2(prePath).get(),
|
|
nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY,
|
|
&passwd, &retval);
|
|
|
|
// 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.AppendWithConversion(mPassword);
|
|
}
|
|
passwordStr.Append(CRLF);
|
|
|
|
return SendFTPCommand(passwordStr);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_pass() {
|
|
if (mResponseCode/100 == 3) {
|
|
// send account info
|
|
return FTP_S_ACCT;
|
|
} else if (mResponseCode/100 == 2) {
|
|
// logged in
|
|
return FTP_S_SYST;
|
|
} else if (mResponseCode == 503) {
|
|
// start over w/ the user command.
|
|
// note: the password was successful, and it's stored in mPassword
|
|
mRetryPass = PR_FALSE;
|
|
return FTP_S_USER;
|
|
} else if (mResponseCode == 530) {
|
|
// user limit reached
|
|
return FTP_ERROR;
|
|
} else {
|
|
// kick back out to S_pass() and ask the user again.
|
|
nsresult rv = NS_OK;
|
|
|
|
nsCOMPtr<nsIWalletService> walletService =
|
|
do_GetService(kWalletServiceCID, &rv);
|
|
if (NS_FAILED(rv)) return FTP_ERROR;
|
|
|
|
nsXPIDLCString uri;
|
|
rv = mURL->GetSpec(getter_Copies(uri));
|
|
if (NS_FAILED(rv)) return FTP_ERROR;
|
|
|
|
rv = walletService->SI_RemoveUser(uri, nsnull);
|
|
if (NS_FAILED(rv)) return FTP_ERROR;
|
|
|
|
mRetryPass = PR_TRUE;
|
|
return FTP_S_PASS;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_syst() {
|
|
nsCString systString("SYST" CRLF);
|
|
return SendFTPCommand( systString );
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_syst() {
|
|
if (mResponseCode/100 == 2) {
|
|
if (( mResponseMsg.Find("UNIX") > -1) ||
|
|
( mResponseMsg.Find("BSD") > -1) ) // non standard response (91019)
|
|
{
|
|
mServerType = FTP_UNIX_TYPE;
|
|
}
|
|
else if ( ( mResponseMsg.Find("WIN32", PR_TRUE) > -1) ||
|
|
( mResponseMsg.Find("windows", PR_TRUE) > -1) )
|
|
{
|
|
mServerType = FTP_NT_TYPE;
|
|
}
|
|
else
|
|
{
|
|
NS_ASSERTION(0, "Server type list format unrecognized.");
|
|
// Guessing causes crashes.
|
|
#if DEBUG
|
|
printf("Server listing unrecognized: %s \n", mResponseMsg.get());
|
|
#endif
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
return FTP_S_TYPE;
|
|
}
|
|
|
|
if (mResponseCode/100 == 5) {
|
|
// server didn't like the SYST command. Probably (500, 501, 502)
|
|
// No clue. We will just hope it is UNIX type server.
|
|
mServerType = FTP_UNIX_TYPE;
|
|
|
|
return FTP_S_TYPE;
|
|
}
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_acct() {
|
|
nsCString acctString("ACCT noaccount" CRLF);
|
|
return SendFTPCommand(acctString);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_acct() {
|
|
if (mResponseCode/100 == 2)
|
|
return FTP_S_SYST;
|
|
else
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_type() {
|
|
nsCString typeString("TYPE I" CRLF);
|
|
return SendFTPCommand(typeString);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_type() {
|
|
if (mResponseCode/100 != 2)
|
|
return FTP_ERROR;
|
|
|
|
return FTP_S_PASV;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_cwd() {
|
|
nsCAutoString cwdStr("CWD ");
|
|
cwdStr.Append(mPath);
|
|
cwdStr.Append(CRLF);
|
|
|
|
return SendFTPCommand(cwdStr);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_cwd() {
|
|
if (mResponseCode/100 == 2) {
|
|
if (mAction == PUT)
|
|
return FTP_S_STOR;
|
|
|
|
return FTP_S_LIST;
|
|
}
|
|
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_size() {
|
|
nsCAutoString sizeBuf("SIZE ");
|
|
sizeBuf.Append(mPath);
|
|
sizeBuf.Append(CRLF);
|
|
|
|
return SendFTPCommand(sizeBuf);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_size() {
|
|
if (mResponseCode/100 == 2) {
|
|
PRInt32 conversionError;
|
|
PRInt32 length = mResponseMsg.ToInteger(&conversionError);
|
|
if (NS_FAILED(mChannel->SetContentLength(length))) return FTP_ERROR;
|
|
}
|
|
|
|
if (mResponseCode == 550) // File unavailable (e.g., file not found, no access).
|
|
return FTP_S_RETR; // Even if the file reports zero size, lets try retr (91292)
|
|
|
|
// if we tried downloading this, lets try restarting it...
|
|
if (mDRequestForwarder && mDRequestForwarder->GetBytesTransfered() > 0)
|
|
return FTP_S_REST;
|
|
|
|
return FTP_S_RETR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::SetContentType()
|
|
{
|
|
if (mGenerateRawContent) {
|
|
nsAutoString fromStr(NS_LITERAL_STRING("text/ftp-dir-"));
|
|
SetDirMIMEType(fromStr);
|
|
|
|
nsCAutoString contentType;contentType.AssignWithConversion(fromStr);
|
|
return mChannel->SetContentType(contentType);
|
|
}
|
|
|
|
if (mGenerateHTMLContent)
|
|
return mChannel->SetContentType("text/html");
|
|
|
|
return mChannel->SetContentType("application/http-index-format");
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_list() {
|
|
nsresult rv;
|
|
|
|
if (!mDRequestForwarder)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
rv = SetContentType();
|
|
|
|
if (NS_FAILED(rv))
|
|
return FTP_ERROR;
|
|
|
|
// save off the server type if we are caching.
|
|
if(mCacheEntry) {
|
|
nsCAutoString serverType;
|
|
serverType.AppendInt(mServerType);
|
|
(void) mCacheEntry->SetMetaDataElement("servertype", serverType);
|
|
}
|
|
|
|
nsCOMPtr<nsIStreamListener> converter;
|
|
|
|
rv = BuildStreamConverter(getter_AddRefs(converter));
|
|
if (NS_FAILED(rv)) {
|
|
// clear mResponseMsg which is displayed to the user.
|
|
// TODO: we should probably set this to something
|
|
// meaningful.
|
|
mResponseMsg = "";
|
|
return rv;
|
|
}
|
|
|
|
// the data forwarder defaults to sending notifications
|
|
// to the channel. Lets hijack and send the notifications
|
|
// to the stream converter.
|
|
mDRequestForwarder->SetStreamListener(converter);
|
|
mDRequestForwarder->SetCacheEntry(mCacheEntry, PR_TRUE);
|
|
|
|
nsCAutoString listString("LIST" CRLF);
|
|
|
|
return SendFTPCommand(listString);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_list() {
|
|
if (mResponseCode/100 == 1)
|
|
return FTP_READ_BUF;
|
|
|
|
if (mResponseCode/100 == 2) {
|
|
//(DONE)
|
|
mNextState = FTP_COMPLETE;
|
|
return FTP_COMPLETE;
|
|
}
|
|
|
|
mFireCallbacks = PR_TRUE;
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_retr() {
|
|
nsresult rv = NS_OK;
|
|
nsCAutoString retrStr("RETR ");
|
|
retrStr.Append(mPath);
|
|
retrStr.Append(CRLF);
|
|
|
|
if (!mDRequestForwarder)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
rv = SendFTPCommand(retrStr);
|
|
return rv;
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_retr() {
|
|
|
|
if (mResponseCode/100 == 2) {
|
|
//(DONE)
|
|
mNextState = FTP_COMPLETE;
|
|
return FTP_COMPLETE;
|
|
}
|
|
|
|
if (mResponseCode/100 == 1)
|
|
return FTP_READ_BUF;
|
|
|
|
// These error codes are related to problems with the connection.
|
|
// If we encounter any at this point, do not try CWD and abort.
|
|
if (mResponseCode == 421 || mResponseCode == 425 || mResponseCode == 426)
|
|
return FTP_ERROR;
|
|
|
|
if (mResponseCode/100 == 5) {
|
|
mRETRFailed = PR_TRUE;
|
|
|
|
// We need to kill off the existing connection - see bug 101128
|
|
mDRequestForwarder->RetryConnection();
|
|
nsCOMPtr<nsISocketTransport> st = do_QueryInterface(mDPipe);
|
|
if (st)
|
|
st->SetReuseConnection(PR_FALSE);
|
|
mDPipe = 0;
|
|
|
|
return FTP_S_PASV;
|
|
}
|
|
|
|
return FTP_S_CWD;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsFtpState::S_rest() {
|
|
|
|
nsCAutoString restString("REST ");
|
|
restString.AppendInt(mDRequestForwarder->GetBytesTransfered(), 10);
|
|
restString.Append(CRLF);
|
|
|
|
return SendFTPCommand(restString);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_rest() {
|
|
// only if something terrible happens do we want to error out in this state.
|
|
if (mResponseCode/100 == 4)
|
|
return FTP_ERROR;
|
|
|
|
return FTP_S_RETR;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::S_stor() {
|
|
nsresult rv = NS_OK;
|
|
nsCAutoString storStr("STOR ");
|
|
storStr.Append(mPath.get());
|
|
storStr.Append(CRLF);
|
|
|
|
rv = SendFTPCommand(storStr);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
NS_ASSERTION(mWriteStream, "we're trying to upload without any data");
|
|
if (!mWriteStream)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
PRUint32 len;
|
|
mWriteStream->Available(&len);
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) writing on Data Transport\n", this));
|
|
return NS_AsyncWriteFromStream(getter_AddRefs(mDPipeRequest),
|
|
mDPipe,
|
|
mWriteStream, 0, len, 0, mDRequestForwarder);
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_stor() {
|
|
if (mResponseCode/100 == 2) {
|
|
//(DONE)
|
|
mNextState = FTP_COMPLETE;
|
|
return FTP_COMPLETE;
|
|
}
|
|
|
|
if (mResponseCode/100 == 1) {
|
|
return FTP_READ_BUF;
|
|
}
|
|
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
|
|
nsresult
|
|
nsFtpState::S_pasv() {
|
|
nsresult rv;
|
|
|
|
if (mIPv6Checked == PR_FALSE) {
|
|
// Find IPv6 socket address, if server is IPv6
|
|
mIPv6Checked = PR_TRUE;
|
|
PR_ASSERT(mIPv6ServerAddress == 0);
|
|
nsCOMPtr<nsITransport> controlSocket;
|
|
mControlConnection->GetTransport(getter_AddRefs(controlSocket));
|
|
if (!controlSocket) return FTP_ERROR;
|
|
nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(controlSocket, &rv);
|
|
|
|
if (sTrans) {
|
|
rv = sTrans->GetIPStr(100, &mIPv6ServerAddress);
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
PRNetAddr addr;
|
|
if (PR_StringToNetAddr(mIPv6ServerAddress, &addr) != PR_SUCCESS ||
|
|
PR_IsNetAddrType(&addr, PR_IpAddrV4Mapped)) {
|
|
nsMemory::Free(mIPv6ServerAddress);
|
|
mIPv6ServerAddress = 0;
|
|
}
|
|
PR_ASSERT(!mIPv6ServerAddress || addr.raw.family == PR_AF_INET6);
|
|
}
|
|
}
|
|
|
|
|
|
char * string;
|
|
if (mIPv6ServerAddress)
|
|
string = "EPSV" CRLF;
|
|
else
|
|
string = "PASV" CRLF;
|
|
|
|
nsCString pasvString(string);
|
|
return SendFTPCommand(pasvString);
|
|
|
|
}
|
|
|
|
FTP_STATE
|
|
nsFtpState::R_pasv() {
|
|
nsresult rv;
|
|
PRInt32 port;
|
|
|
|
if (mResponseCode/100 != 2) {
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
char *response = mResponseMsg.ToNewCString();
|
|
if (!response) return FTP_ERROR;
|
|
char *ptr = response;
|
|
|
|
nsCAutoString host;
|
|
if (mIPv6ServerAddress) {
|
|
// The returned string is of the form
|
|
// text (|||ppp|)
|
|
// Where '|' can be any single character
|
|
char delim;
|
|
while (*ptr && *ptr != '(') ptr++;
|
|
if (*ptr++ != '(') {
|
|
return FTP_ERROR;
|
|
}
|
|
delim = *ptr++;
|
|
if (!delim || *ptr++ != delim || *ptr++ != delim ||
|
|
*ptr < '0' || *ptr > '9') {
|
|
return FTP_ERROR;
|
|
}
|
|
port = 0;
|
|
do {
|
|
port = port * 10 + *ptr++ - '0';
|
|
} while (*ptr >= '0' && *ptr <= '9');
|
|
if (*ptr++ != delim || *ptr != ')') {
|
|
return FTP_ERROR;
|
|
}
|
|
} else {
|
|
// The returned address string can be of the form
|
|
// (xxx,xxx,xxx,xxx,ppp,ppp) or
|
|
// xxx,xxx,xxx,xxx,ppp,ppp (without parens)
|
|
PRInt32 h0, h1, h2, h3, p0, p1;
|
|
while (*ptr) {
|
|
if (*ptr == '(') {
|
|
// move just beyond the open paren
|
|
ptr++;
|
|
break;
|
|
}
|
|
if (*ptr == ',') {
|
|
// backup to the start of the digits
|
|
do {
|
|
ptr--;
|
|
} while ((ptr >=response) && (*ptr >= '0') && (*ptr <= '9'));
|
|
ptr++; // get back onto the numbers
|
|
break;
|
|
}
|
|
ptr++;
|
|
} // end while
|
|
|
|
PRInt32 fields = PR_sscanf(ptr,
|
|
"%ld,%ld,%ld,%ld,%ld,%ld",
|
|
&h0, &h1, &h2, &h3, &p0, &p1);
|
|
|
|
if (fields < 6) {
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
port = ((PRInt32) (p0<<8)) + p1;
|
|
host.AppendInt(h0);
|
|
host.Append('.');
|
|
host.AppendInt(h1);
|
|
host.Append('.');
|
|
host.AppendInt(h2);
|
|
host.Append('.');
|
|
host.AppendInt(h3);
|
|
}
|
|
|
|
nsMemory::Free(response);
|
|
|
|
const char* hostStr = mIPv6ServerAddress ? mIPv6ServerAddress : host.get();
|
|
|
|
// now we know where to connect our data channel
|
|
nsCOMPtr<nsISocketTransportService> sts = do_GetService(kSocketTransportServiceCID, &rv);
|
|
|
|
rv = sts->CreateTransport(hostStr,
|
|
port,
|
|
mProxyInfo,
|
|
FTP_DATA_CHANNEL_SEG_SIZE,
|
|
FTP_DATA_CHANNEL_MAX_SIZE,
|
|
getter_AddRefs(mDPipe)); // the data channel
|
|
if (NS_FAILED(rv)) return FTP_ERROR;
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) Created Data Transport (%s:%x)\n", this, hostStr, port));
|
|
|
|
nsCOMPtr<nsISocketTransport> sTrans = do_QueryInterface(mDPipe, &rv);
|
|
if (NS_FAILED(rv)) return FTP_ERROR;
|
|
|
|
if (NS_FAILED(sTrans->SetReuseConnection(PR_TRUE))) return FTP_ERROR;
|
|
|
|
if (!mDRequestForwarder) {
|
|
mDRequestForwarder = new DataRequestForwarder;
|
|
if (!mDRequestForwarder) return FTP_ERROR;
|
|
NS_ADDREF(mDRequestForwarder);
|
|
|
|
rv = mDRequestForwarder->Init(mChannel);
|
|
if (NS_FAILED(rv)){
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) forwarder->Init failed (rv=%x)\n", this, rv));
|
|
return FTP_ERROR;
|
|
}
|
|
}
|
|
|
|
// hook ourself up as a proxy for progress notifications
|
|
mWaitingForDConn = PR_TRUE;
|
|
rv = mDPipe->SetNotificationCallbacks(NS_STATIC_CAST(nsIInterfaceRequestor*, mDRequestForwarder), PR_FALSE);
|
|
if (NS_FAILED(rv)){
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) forwarder->SetNotificationCallbacks failed (rv=%x)\n", this, rv));
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
if (mAction == PUT) {
|
|
NS_ASSERTION(!mRETRFailed, "Failed before uploading");
|
|
mDRequestForwarder->Uploading(PR_TRUE);
|
|
return FTP_S_STOR;
|
|
}
|
|
|
|
rv = mDPipe->AsyncRead(mDRequestForwarder, nsnull, 0, PRUint32(-1), 0, getter_AddRefs(mDPipeRequest));
|
|
if (NS_FAILED(rv)){
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) forwarder->AsyncRead failed (rv=%x)\n", this, rv));
|
|
return FTP_ERROR;
|
|
}
|
|
|
|
if (mRETRFailed)
|
|
return FTP_S_CWD;
|
|
|
|
return FTP_S_SIZE;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// nsIRequest methods:
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::GetName(PRUnichar* *result)
|
|
{
|
|
nsresult rv;
|
|
nsXPIDLCString urlStr;
|
|
rv = mURL->GetSpec(getter_Copies(urlStr));
|
|
if (NS_FAILED(rv)) return rv;
|
|
nsString name;
|
|
name.AppendWithConversion(urlStr);
|
|
*result = name.ToNewUnicode();
|
|
return *result ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::IsPending(PRBool *result)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
*result = PR_FALSE;
|
|
|
|
nsCOMPtr<nsIRequest> request;
|
|
mControlConnection->GetReadRequest(getter_AddRefs(request));
|
|
|
|
if (request) {
|
|
rv = request->IsPending(result);
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::GetStatus(nsresult *status)
|
|
{
|
|
*status = mInternalError;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::Cancel(nsresult status)
|
|
{
|
|
NS_ASSERTION(NS_FAILED(status), "shouldn't cancel with a success code");
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) nsFtpState::Cancel() rv=%x\n", this, status));
|
|
|
|
// we should try to recover the control connection....
|
|
if (NS_SUCCEEDED(mControlStatus))
|
|
mControlStatus = status;
|
|
|
|
// kill the data connection immediately.
|
|
NS_IF_RELEASE(mDRequestForwarder);
|
|
if (mDPipeRequest) {
|
|
mDPipeRequest->Cancel(status);
|
|
mDPipeRequest = 0;
|
|
}
|
|
|
|
(void) StopProcessing();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::Suspend(void)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
if (!mControlConnection)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// 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++;
|
|
|
|
// only worry about the read request.
|
|
nsCOMPtr<nsIRequest> request;
|
|
mControlConnection->GetReadRequest(getter_AddRefs(request));
|
|
|
|
if (request) {
|
|
rv = request->Suspend();
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
|
|
if (mDPipeRequest) {
|
|
rv = mDPipeRequest->Suspend();
|
|
if (NS_FAILED(rv)) return rv;
|
|
}
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::Resume(void)
|
|
{
|
|
nsresult rv = NS_ERROR_FAILURE;
|
|
|
|
// resuming the underlying socket transports will
|
|
// cause the FTP state machine to unblock and
|
|
// go on about it's business.
|
|
if (mSuspendCount) {
|
|
|
|
PRBool dataAlive = PR_FALSE;
|
|
|
|
nsCOMPtr<nsISocketTransport> dataSocket = do_QueryInterface(mDPipe);
|
|
if (dataSocket)
|
|
dataSocket->IsAlive(0, &dataAlive);
|
|
|
|
if (dataSocket && dataAlive && mControlConnection->IsAlive())
|
|
{
|
|
nsCOMPtr<nsIRequest> controlRequest;
|
|
mControlConnection->GetReadRequest(getter_AddRefs(controlRequest));
|
|
NS_ASSERTION(controlRequest, "where did my request go!");
|
|
|
|
controlRequest->Resume();
|
|
rv = mDPipeRequest->Resume();
|
|
}
|
|
else
|
|
{
|
|
// control or data connection went down. need to reconnect.
|
|
// if we were downloading, we need to perform REST.
|
|
rv = EstablishControlConnection();
|
|
}
|
|
}
|
|
mSuspendCount--;
|
|
return rv;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::GetLoadGroup(nsILoadGroup **aLoadGroup)
|
|
{
|
|
*aLoadGroup = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::SetLoadGroup(nsILoadGroup *aLoadGroup)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::GetLoadFlags(nsLoadFlags *aLoadFlags)
|
|
{
|
|
*aLoadFlags = nsIRequest::LOAD_NORMAL;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFtpState::SetLoadFlags(nsLoadFlags aLoadFlags)
|
|
{
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::Init(nsIFTPChannel* aChannel,
|
|
nsIPrompt* aPrompter,
|
|
nsIAuthPrompt* aAuthPrompter,
|
|
nsIFTPEventSink* sink,
|
|
nsICacheEntryDescriptor* cacheEntry,
|
|
nsIProxyInfo* proxyInfo)
|
|
{
|
|
nsresult rv = NS_OK;
|
|
|
|
mKeepRunning = PR_TRUE;
|
|
mPrompter = aPrompter;
|
|
mFTPEventSink = sink;
|
|
mAuthPrompter = aAuthPrompter;
|
|
mCacheEntry = cacheEntry;
|
|
mProxyInfo = proxyInfo;
|
|
|
|
// parameter validation
|
|
NS_ASSERTION(aChannel, "FTP: needs a channel");
|
|
|
|
mChannel = aChannel; // a straight com ptr to the channel
|
|
|
|
rv = aChannel->GetURI(getter_AddRefs(mURL));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
if (mCacheEntry) {
|
|
nsCacheAccessMode access;
|
|
mCacheEntry->GetAccessGranted(&access);
|
|
if (access & nsICache::ACCESS_READ) {
|
|
|
|
// make sure the channel knows wassup
|
|
SetContentType();
|
|
|
|
NS_ASSERTION(!mDRequestForwarder, "there should not be a data forwarder");
|
|
mDRequestForwarder = new DataRequestForwarder;
|
|
if (!mDRequestForwarder) return NS_ERROR_OUT_OF_MEMORY;
|
|
NS_ADDREF(mDRequestForwarder);
|
|
|
|
rv = mDRequestForwarder->Init(mChannel);
|
|
|
|
nsXPIDLCString serverType;
|
|
(void) mCacheEntry->GetMetaDataElement("servertype", getter_Copies(serverType));
|
|
nsCAutoString serverNum(serverType.get());
|
|
PRInt32 err;
|
|
mServerType = serverNum.ToInteger(&err);
|
|
|
|
nsCOMPtr<nsIStreamListener> converter;
|
|
rv = BuildStreamConverter(getter_AddRefs(converter));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
mDRequestForwarder->SetStreamListener(converter);
|
|
mDRequestForwarder->SetCacheEntry(mCacheEntry, PR_FALSE);
|
|
|
|
// Get a transport to the cached data...
|
|
nsCOMPtr<nsITransport> transport;
|
|
rv = mCacheEntry->GetTransport(getter_AddRefs(transport));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// Pump the cache data downstream
|
|
return transport->AsyncRead(mDRequestForwarder,
|
|
nsnull,
|
|
0, PRUint32(-1), 0,
|
|
getter_AddRefs(mDPipeRequest));
|
|
}
|
|
}
|
|
char *path = nsnull;
|
|
nsCOMPtr<nsIURL> aURL(do_QueryInterface(mURL));
|
|
|
|
if (aURL)
|
|
rv = aURL->GetFilePath(&path);
|
|
else
|
|
rv = mURL->GetPath(&path);
|
|
|
|
if (NS_FAILED(rv)) return rv;
|
|
mPath.Adopt(nsUnescape(path));
|
|
|
|
// pull any username and/or password out of the uri
|
|
nsXPIDLCString uname;
|
|
rv = mURL->GetUsername(getter_Copies(uname));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
} else {
|
|
if ((const char*)uname && *(const char*)uname) {
|
|
mAnonymous = PR_FALSE;
|
|
mUsername.AssignWithConversion(uname);
|
|
}
|
|
}
|
|
|
|
nsXPIDLCString password;
|
|
rv = mURL->GetPassword(getter_Copies(password));
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
else
|
|
mPassword.AssignWithConversion(password);
|
|
|
|
// 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;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::Connect()
|
|
{
|
|
if (mDRequestForwarder)
|
|
return NS_OK; // we are already connected.
|
|
|
|
nsresult rv;
|
|
|
|
mState = FTP_COMMAND_CONNECT;
|
|
mNextState = FTP_S_USER;
|
|
|
|
rv = Process();
|
|
|
|
// check for errors.
|
|
if (NS_FAILED(rv)) {
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("-- Connect() on Control Connect FAILED with rv = %x\n", rv));
|
|
mInternalError = NS_ERROR_FAILURE;
|
|
mState = FTP_ERROR;
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
nsresult
|
|
nsFtpState::SetWriteStream(nsIInputStream* aInStream) {
|
|
if (!aInStream)
|
|
return NS_OK;
|
|
|
|
mAction = PUT;
|
|
mWriteStream = aInStream;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsFtpState::KillControlConnection() {
|
|
mControlReadContinue = PR_FALSE;
|
|
mControlReadBrokenLine = PR_FALSE;
|
|
mControlReadCarryOverBuf.Truncate(0);
|
|
|
|
if (mDPipe) {
|
|
mDPipe->SetNotificationCallbacks(nsnull, PR_FALSE);
|
|
mDPipe = 0;
|
|
}
|
|
|
|
NS_IF_RELEASE(mDRequestForwarder);
|
|
|
|
mIPv6Checked = PR_FALSE;
|
|
if (mIPv6ServerAddress) {
|
|
nsMemory::Free(mIPv6ServerAddress);
|
|
mIPv6ServerAddress = 0;
|
|
}
|
|
// if everything went okay, save the connection.
|
|
// FIX: need a better way to determine if we can cache the connections.
|
|
// there are some errors which do not mean that we need to kill the connection
|
|
// e.g. fnf.
|
|
|
|
if (!mControlConnection)
|
|
return;
|
|
|
|
// kill the reference to ourselves in the control connection.
|
|
(void) mControlConnection->SetStreamListener(nsnull);
|
|
|
|
if (FTP_CACHE_CONTROL_CONNECTION &&
|
|
NS_SUCCEEDED(mInternalError) &&
|
|
NS_SUCCEEDED(mControlStatus) &&
|
|
mControlConnection->IsAlive()) {
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_ALWAYS, ("(%x) nsFtpState caching control connection", this));
|
|
|
|
// Store connection persistant data
|
|
mControlConnection->mServerType = mServerType;
|
|
mControlConnection->mPassword = mPassword;
|
|
nsresult rv = nsFtpProtocolHandler::InsertConnection(mURL,
|
|
NS_STATIC_CAST(nsISupports*, (nsIStreamListener*)mControlConnection));
|
|
// Can't cache it? Kill it then.
|
|
mControlConnection->Disconnect(rv);
|
|
}
|
|
else
|
|
mControlConnection->Disconnect(NS_BINDING_ABORTED);
|
|
|
|
NS_RELEASE(mControlConnection);
|
|
|
|
return;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::StopProcessing() {
|
|
nsresult rv;
|
|
PR_LOG(gFTPLog, PR_LOG_ALWAYS, ("(%x) nsFtpState stopping", this));
|
|
|
|
#ifdef DEBUG_dougt
|
|
printf("FTP Stopped: [response code %d] [response msg follows:]\n%s\n", mResponseCode, mResponseMsg.get());
|
|
#endif
|
|
|
|
if ( NS_FAILED(mInternalError) && mResponseMsg.Length())
|
|
{
|
|
// check to see if the control status is bad.
|
|
// web shell wont throw an alert. we better:
|
|
nsAutoString text;
|
|
text.AssignWithConversion(mResponseMsg);
|
|
|
|
NS_ASSERTION(mPrompter, "no prompter!");
|
|
if (mPrompter)
|
|
(void) mPrompter->Alert(nsnull, text.get());
|
|
}
|
|
|
|
nsresult broadcastErrorCode = mControlStatus;
|
|
if ( NS_SUCCEEDED(broadcastErrorCode))
|
|
broadcastErrorCode = mInternalError;
|
|
|
|
if (mFireCallbacks && mChannel) {
|
|
nsCOMPtr<nsIStreamListener> channelListener = do_QueryInterface(mChannel);
|
|
nsCOMPtr<nsIRequest> channelRequest = do_QueryInterface(mChannel);
|
|
NS_ASSERTION(channelListener && channelRequest, "ftp channel better have these interfaces");
|
|
|
|
nsCOMPtr<nsIStreamListener> asyncListener;
|
|
rv = NS_NewAsyncStreamListener(getter_AddRefs(asyncListener), channelListener, NS_UI_THREAD_EVENTQ);
|
|
if(asyncListener) {
|
|
(void) asyncListener->OnStartRequest(channelRequest, nsnull);
|
|
(void) asyncListener->OnStopRequest(channelRequest, nsnull, broadcastErrorCode);
|
|
}
|
|
}
|
|
|
|
// Clean up the event loop
|
|
mKeepRunning = PR_FALSE;
|
|
|
|
KillControlConnection();
|
|
|
|
nsCOMPtr<nsIProgressEventSink> sink(do_QueryInterface(mChannel));
|
|
if (sink)
|
|
sink->OnStatus(nsnull, nsnull, NS_NET_STATUS_END_FTP_TRANSACTION, nsnull);
|
|
|
|
// Release the Observers
|
|
mWriteStream = 0;
|
|
|
|
mPrompter = 0;
|
|
mAuthPrompter = 0;
|
|
mChannel = 0;
|
|
mProxyInfo = 0;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsFtpState::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(NS_LITERAL_STRING("unix"));
|
|
break;
|
|
case FTP_NT_TYPE:
|
|
aString.Append(NS_LITERAL_STRING("nt"));
|
|
break;
|
|
default:
|
|
aString.Append(NS_LITERAL_STRING("generic"));
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::BuildStreamConverter(nsIStreamListener** convertStreamListener)
|
|
{
|
|
nsresult 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<nsIStreamListener> converterListener;
|
|
nsCOMPtr<nsIStreamListener> listener = do_QueryInterface(mChannel);
|
|
|
|
nsCOMPtr<nsIStreamConverterService> scs =
|
|
do_GetService(kStreamConverterServiceCID, &rv);
|
|
|
|
if (NS_FAILED(rv))
|
|
return rv;
|
|
|
|
nsAutoString fromStr(NS_LITERAL_STRING("text/ftp-dir-"));
|
|
SetDirMIMEType(fromStr);
|
|
if (mGenerateRawContent) {
|
|
converterListener = listener;
|
|
}
|
|
else if (mGenerateHTMLContent) {
|
|
rv = scs->AsyncConvertData(fromStr.get(),
|
|
NS_LITERAL_STRING("text/html").get(),
|
|
listener,
|
|
mURL,
|
|
getter_AddRefs(converterListener));
|
|
}
|
|
else
|
|
{
|
|
rv = scs->AsyncConvertData(fromStr.get(),
|
|
NS_LITERAL_STRING(APPLICATION_HTTP_INDEX_FORMAT).get(),
|
|
listener,
|
|
mURL,
|
|
getter_AddRefs(converterListener));
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) scs->AsyncConvertData failed (rv=%x)\n", this, rv));
|
|
return rv;
|
|
}
|
|
|
|
NS_ADDREF(*convertStreamListener = converterListener);
|
|
return rv;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::DataConnectionEstablished()
|
|
{
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x) Data Connection established.", this));
|
|
|
|
mFireCallbacks = PR_FALSE; // observer callbacks will be handled by the transport.
|
|
mWaitingForDConn= PR_FALSE;
|
|
|
|
// sending empty string with (mWaitingForDConn == PR_FALSE) will cause the
|
|
// control socket to write out its buffer.
|
|
nsCString a("");
|
|
SendFTPCommand(a);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsFtpState::SendFTPCommand(nsCString& command)
|
|
{
|
|
NS_ASSERTION(mControlConnection, "null control connection");
|
|
|
|
// we don't want to log the password:
|
|
nsCAutoString logcmd(command);
|
|
if (command.CompareWithConversion("PASS ", PR_FALSE, 5) == 0)
|
|
logcmd = "PASS xxxxx";
|
|
|
|
PR_LOG(gFTPLog, PR_LOG_DEBUG, ("(%x)(dwait=%d) Writing \"%s\"\n", this, mWaitingForDConn, logcmd.get()));
|
|
if (mFTPEventSink)
|
|
mFTPEventSink->OnFTPControlLog(PR_FALSE, logcmd.get());
|
|
|
|
if (mControlConnection) {
|
|
return mControlConnection->Write(command, mWaitingForDConn);
|
|
}
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|