Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

A comprehensive CE class library to replace ATL and MFC

, 4 Oct 2000
A collection of classes for CE that do not use ATL or MFC, plus an FTP client, database viewer, and sample application that solves beam deflection equations.
kgwince-old.zip
WinCe
BeamEx
app.ico
BeamEx.dsp
BeamEx.dsw
BeamEx.ini
BeamEx.plg
bitmap1.bmp
bmp00001.bmp
bmp00002.bmp
calc.bmp
calc16.bmp
circ_cross.ico
cir_cross.ico
cross_4.ico
help16.bmp
help4.bmp
ico00001.ico
ico00002.ico
ico00005.ico
ico00008.ico
ico00009.ico
ico00010.ico
icon1.ico
icon2.ico
icon4.ico
id_downl.bmp
Install
BeamEx.inf
BeamEx.ini
BeamEx.MIPS_PPCBW.CAB
BeamEx.MIPS_PPCColor.CAB
BeamEx.SH3_PPCColor.CAB
Setup.exe
i_cross.ico
l_cross.ico
magenic.ico
mssccprj.scc
obl_corss.ico
obl_cross.ico
options.bmp
options1.bmp
ping16.bmp
rect_cross.ico
scene_5.ico
scene_6.ico
tri_cross.ico
vssver.scc
CeLib
CeLabel.inl
CeLib.aps
CeLib.dsp
CeLib.dsw
CeLib.old
CeLib.plg
CeLib.vcp
CeMisc.inl
CeProperty.inl
CeTab.inl
CeWnd.inl
mssccprj.scc
vssver.scc
WinLib.dsp
WinLib.dsw
dbView
bitmap1.bmp
bmp00001.bmp
db5.bmp
dbView.aps
DbView.dsp
dbView.dsw
dbView.old
dbView.plg
DbView.vcp
deldb.bp2
deldb5.bmp
delrec.bp2
delrecor.bmp
help16.bmp
help4.bmp
icon1.ico
icon2.ico
Install
dbView.inf
dbView.ini
Setup.exe
magenic.bmp
magenic.ico
mssccprj.scc
vssver.scc
ftpView
appicon.ico
bitmap1.bmp
bitmap2.bmp
bitmap3.bmp
bitmap4.bmp
bmp00001.bmp
bmp00002.bmp
db5.bmp
deldb.bp2
delrec.bp2
FtpView.dsp
ftpView.dsw
FtpView.old
ftpView.plg
FtpView.vcl
FtpView.vcp
help16.bmp
help4.bmp
ico00001.ico
ico00002.ico
ico171.ico
ico35.ico
icon1.ico
icon2.ico
imagelis.bmp
IMAGES.bmp
Install
ftpView.inf
ftpView.ini
Setup.exe
vssver.scc
magenic.bmp
magenic.ico
mssccprj.scc
options.bmp
options1.bmp
seperato.bmp
vssver.scc
Setup
ico101.ico
Setup.aps
Setup.dsp
setup.ico
Setup.plg
WinCe.dsw
WinLib.dsw
kgwince.zip
app.ico
BeamEx.dsp
BeamEx.dsw
BeamEx.ini
BeamEx.plg
BeamEx.vcl
BeamEx.vcp
bitmap1.bmp
bmp00001.bmp
bmp00002.bmp
calc.bmp
calc16.bmp
circ_cross.ico
cir_cross.ico
cross_4.ico
help16.bmp
help4.bmp
ico00001.ico
ico00002.ico
ico00005.ico
ico00008.ico
ico00009.ico
ico00010.ico
icon1.ico
icon2.ico
icon4.ico
id_downl.bmp
BeamEx.inf
BeamEx.ini
Setup.exe
i_cross.ico
l_cross.ico
magenic.ico
mssccprj.scc
obl_corss.ico
obl_cross.ico
options.bmp
options1.bmp
ping16.bmp
rect_cross.ico
scene_5.ico
scene_6.ico
tri_cross.ico
vssver.scc
CeFtp.vcp
CeFtp.vcw
CeLabel.inl
CeLib.aps
CeLib.dsp
CeLib.dsw
CeLib.plg
CeLib.vcl
CeLib.vcp
CeMisc.inl
CeProperty.inl
CeTab.inl
CeWnd.inl
mssccprj.scc
vssver.scc
WinLib.dsp
WinLib.dsw
bitmap1.bmp
bitmap2.bmp
bmp00001.bmp
db5.bmp
DbView.dsp
dbView.dsw
dbView.plg
DbView.vcl
DbView.vcp
deldb.bp2
deldb5.bmp
delrec.bp2
delrecor.bmp
help16.bmp
help4.bmp
icon1.ico
icon2.ico
dbView.inf
dbView.ini
Setup.exe
magenic.bmp
magenic.ico
menu1_5.bmp
mssccprj.scc
vssver.scc
appicon.ico
bitmap1.bmp
bitmap2.bmp
bitmap3.bmp
bitmap4.bmp
bmp00001.bmp
bmp00002.bmp
db5.bmp
deldb.bp2
delrec.bp2
FtpView.dsp
ftpView.dsw
ftpView.plg
FtpView.vcl
FtpView.vcp
FtpView.vcw
help16.bmp
help4.bmp
ico00001.ico
ico00002.ico
ico171.ico
ico35.ico
icon1.ico
icon2.ico
imagelis.bmp
IMAGES.bmp
ftpView.inf
ftpView.ini
Setup.exe
vssver.scc
magenic.bmp
magenic.ico
mssccprj.scc
newmenu.bmp
options.bmp
options1.bmp
rcdata1.bin
seperato.bmp
toolbar1.bmp
vssver.scc
WinCe.dsw
WinCe.vcw
#include "stdafx.h"
#include "CeFtp.h"
#include <ctype.h>

#define CR		0x0d		// '\r'
#define LF		0x0a		// '\n'

#pragma warning(disable: 4509) // SEH warning

#ifndef isdigit
inline bool isdigit(char c)
{
	return (c >= '0' && c <= '9');
}
#endif

// Ftp connect processing
enum __ftpcommands__
{
	INIT = 0,
	// Ftp Commands that take arguments (subset)
	CWD,
	MKD,
	RMD,
	XMKD,
	XRWD,
	XCWD,
	PASS,
	PORT,
	RETR,
	TYPE,
	USER,
	MODE,
	DELE,
	STOR,
	RNFR,
	RNTO,
	APPE,
	NLST,

	// Ftp commands without arguments (subset)
	ABOR,
	LIST,
	PWD,
	XPWD,
	PASV,
	QUIT,
	HELP,
	SYST,
	STOU,
	NOOP,
	CDUP,
};

//
// Note: these are ASCII strings for the FTP protocol which understans only ASCII
// 
LPSTR g_rgszFtpCmd[] =
{
	"",
	// commands with arguments
	"CWD",
	"MKD",
	"RMD",
	"XMKD",
	"XRWD",
	"XCWD",
	"PASS",
	"PORT",
	"RETR",
	"TYPE",
	"USER",
	"MODE",
	"DELE",
	"STOR",
	"RNFR",
	"RNTO",
	"APPE",
	"NLST",

	// commands without arguments
	"ABOR",
	"LIST",
	"PWD",
	"XPWD",
	"PASV",
	"QUIT",
	"HELP",
	"SYST",
	"STOU",
	"NOOP",
	"CDUP",
};


static TCHAR g_szNotConnected[] = _T("Command requires an active connection.");

///////////////////////////////////////////////////////////////////////////////
// Private implementation
///////////////////////////////////////////////////////////////////////////////

CeFtpClient::CeFtpClient()
{
	m_bConnected = FALSE;
	m_nFtpCtrlPort = IPPORT_FTP;
//	m_nFtpDataPort = IPPORT_FTP - 1;
	m_nFtpDataPort = 0;
	m_bStaticPort = FALSE;
	m_dwByteCount = 0;
	m_nBufInUse = 0;

	m_arCtrlRecv.SetSize(MTU_SIZE);			// 8K initial buffer size one MTU

	m_eTransferType = ftpTtype_ASCII;			// default after connect

	m_dwTimeoutConnect	= 15 * 1000;
	m_dwTimeoutCmdSend	= 15 * 1000;
	m_dwTimeoutCmdRecv	= 15 * 1000;
	m_dwTimeoutDataSend = 15 * 1000;
	m_dwTimeoutDataRecv = 15 * 1000;

	m_nLastFtpReply = ftpRcNoError;
	
	m_sRetries = 0;
	m_hwnd = NULL;

	m_bPassive = FALSE;
#ifdef DEBUG
	m_bDebug = TRUE;
#else
	m_bDebug = FALSE;
#endif

}

CeFtpClient::~CeFtpClient()
{
}


void CeFtpClient::ResetConnection()
{
//	::OutputDebugStringW95(_T("Connection reset by either the host or the peer.\r\n"));

	if (m_sockCtrl)
		m_sockCtrl.Cleanup();

	if (m_sockData)
		m_sockData.Cleanup();

	if (m_sockListen)
		m_sockListen.Cleanup();

	m_bConnected = FALSE;
	m_eTransferType = ftpTtype_ASCII;			// default after connect
	m_nBufInUse = 0;
	m_dwByteCount = 0;
}

void CeFtpClient::ClearFtpError()
{
	m_nLastFtpReply = ftpRcNoError;
	*m_szLastFtpReply = 0;
}

void CeFtpClient::SetFtpError(int nFtpError, LPCTSTR lpszErr)
{
	m_nLastFtpReply = nFtpError;

	for (int ii = 0; *lpszErr != 0 && ii < RESP_BUFSIZ; ii++)
		m_szLastFtpReply[ii] = *lpszErr++;

	m_szLastFtpReply[ii] = 0;

	TRACE1(_T("%s\n"), m_szLastFtpReply);
}



void CeFtpClient::SetSocketError(int nError)
{
	CeString strErr;

	// pick out some specific error codes for better messages
	switch (nError)
	{
	case 0:						strErr = _T("No error!");					break;
	case WSANOTINITIALISED:		strErr = _T("Socket Library not initialied.");	break;
	case WSAEADDRNOTAVAIL:		strErr = _T("Unable to contact name server.");	break;
	case WSAEHOSTUNREACH:		strErr = _T("Host is unreachable.");		break;
	case WSAEHOSTDOWN:			strErr = _T("Host is down.");				break;
	case WSAECONNREFUSED:		strErr = _T("Connection refused.");			break;
	case WSAENOTCONN:			strErr = _T("Not connected.");				break;
	case WSAENOTSOCK:			strErr = _T("Not a socket.");				break;
	case WSAEINTR:				strErr = _T("Socket call interupted.");		break;
	case WSAEALREADY:			strErr = _T("Socket call in already in progress.");	break;
	case WSAEACCES:				strErr = _T("Access denied.");				break;
	case WSAETIMEDOUT:			strErr = _T("Operation timed out.");		break;
	case WSAECONNRESET:			strErr = _T("Connection reset by peer.");		break;
	case WSAENETDOWN:			strErr = _T("Network is down.");			break;
	case WSAHOST_NOT_FOUND:		strErr = _T("The host name was not found.");		break;
		
	default:					strErr.Format(_T("Unknown Error: %d\n"), nError);
	}

	SetFtpError(ftpErrorSocket, strErr);
}

//
// Set up the listening socket for a data connection
//
BOOL CeFtpClient::InitDataConn()
{
//	TRACE0("InitDataConn()\n");

	m_dwByteCount = 0;  // init byte counter
	CeString str;

	int nError = NOERROR;

	if (NULL != m_sockListen)
	{
		TRACE0("Cleaning up OLD unaccepted socket\n");
		m_sockListen.Cleanup();
	}

	// Get a TCP socket to use for data connection listen
	m_sockListen.Create();

	// Debug, tried stuff
	struct linger linger;
	linger.l_onoff = TRUE;
	linger.l_linger = 10;
	m_sockListen.SetOption(SOL_SOCKET, SO_LINGER, &linger, sizeof linger);

	if (m_bStaticPort)
	{
		BOOL bReUse = TRUE;
		m_sockListen.SetOption(SOL_SOCKET, SO_REUSEADDR, &bReUse, sizeof BOOL);
	}

	if (! m_bPassive)
	{
		// Name the local socket with bind()
		if (! m_nFtpDataPort || ! m_bStaticPort)
			// allow bind to generate a new address
			m_saClientData = CeSockAddr();

		m_sockListen.Bind(m_saClientData);

		// Get local port number and address assigned by bind()
		m_sockListen.GetSockAddr(m_saClientData);
		if (INADDR_ANY == m_saClientData.IPAddr())
		{
			// Get local port number assigned by Bind() and the
			// IP address from the control connection
			CeSockAddr sa;
			m_sockCtrl.GetSockAddr(sa);
			m_saClientData = CeSockAddr(sa.IPAddr(), m_saClientData.Port());
		}

		// Listen for incoming data connection 
		m_sockListen.Listen();

//		if (! m_nFtpDataPort || ! m_bStaticPort)
//		{
			// ASCII string sent down TCP socket
			str.Format(_T("%d,%d,%d,%d,%d,%d"),
				// local addr (network byte order)
				m_saClientData.sin_addr.S_un.S_un_b.s_b1,
				m_saClientData.sin_addr.S_un.S_un_b.s_b2,
				m_saClientData.sin_addr.S_un.S_un_b.s_b3,
				m_saClientData.sin_addr.S_un.S_un_b.s_b4,
				// local port (network byte order)
				m_saClientData.sin_port & 0xFF,
				(m_saClientData.sin_port & 0xFF00)>>8);

			// we need to send a PORT command to the server
			// to know where to look
			if (SendFtpCmd(PORT, str) <= 0 || ftpRc_CMD_OK != GetFtpReply())
			{
				m_sockListen.Cleanup();
				return false;
			}

			m_nFtpDataPort = m_saClientData.Port();
//		}
	}
	else	// Passive
	{
		// we need to send a PORT command to the server
		// to know where to look
		if (SendFtpCmd(PASV) <= 0 || ftpRc_PASS_MODE != GetFtpReply())
		{
			m_sockListen.Cleanup();
			return false;
		}

		// ASCII string sent down TCP socket _T("%d,%d,%d,%d,%d,%d")
		// local addr and port are in network byte order
		BYTE rgbyAddress[6];	

		LPTSTR psz;
		if ( ( psz = _tcschr(m_szLastFtpReply, _T('(')) ) == NULL)
			return false;

		// start looking for "," after the open paren to the digits
		psz = _tcstok( psz+1, _T(",") );

		for (int ii = 0; NULL != psz && ii <= 4; ii++)
		{
			rgbyAddress[ii] = (BYTE) _tcstol(psz, NULL, 0);
			psz = _tcstok(NULL, _T(","));
		}
		rgbyAddress[ii] = (BYTE) _tcstol(psz, NULL, 0);

		m_saClientData.sin_addr.S_un.S_un_b.s_b1 = rgbyAddress[0];
		m_saClientData.sin_addr.S_un.S_un_b.s_b2 = rgbyAddress[1];
		m_saClientData.sin_addr.S_un.S_un_b.s_b3 = rgbyAddress[2];
		m_saClientData.sin_addr.S_un.S_un_b.s_b4 = rgbyAddress[3];
		m_saClientData.sin_port = rgbyAddress[4] | (rgbyAddress[5] << 8);

		m_sockData.Create(SOCK_STREAM);

		if (! m_sockData.Connect(m_saClientData, m_dwTimeoutDataRecv))
		{
			TRACE0("Passinve connect failed!");
			SetSocketError(m_sockData.GetLastError());
			return false;
		}
	}

/*
	{
		// If we had an error or we still don't know our IP address, 
		// then we have a problem.
		SetSocketError(m_sockListen.GetLastError());
		m_sockListen.Close();
		return false;
	}
*/
	return true;
}


//
// Thread procedure to accept an incoming data connection
//

DWORD WINAPI CeFtpClient::StartAccept(LPVOID pVoid)
{
	CeFtpClient* pThis = (CeFtpClient*) pVoid;

//	TRACE("Accepting Data Connection\n");

	if (NULL == pThis->m_sockListen)
	{
		// accept call canceled by an error, abort attempt
		TRACE0("Accept Canceled before thread call\n");
		// Let the waiting thread go
		pThis->m_eventAccept.Set();
		return 1;
	}

	if (! pThis->m_sockListen.Accept(pThis->m_sockData, pThis->m_saServerData))
	{
		pThis->SetSocketError(pThis->m_sockListen.GetLastError());
		TRACE0("Accept failed\n");

		pThis->m_sockListen.Cleanup();
		// Let the waiting thread go
		pThis->m_eventAccept.Set();
		return 1;
	}

	// Set linger so we stay around a while
	struct linger linger;
	linger.l_onoff = TRUE;
	linger.l_linger = 10;
	pThis->m_sockData.SetOption(SOL_SOCKET, SO_LINGER, &linger, sizeof linger);

	// set timeout valuez
	pThis->m_sockData.SetWriteTimeout(pThis->m_dwTimeoutDataSend);
	pThis->m_sockData.SetReadTimeout(pThis->m_dwTimeoutDataRecv);

//	TRACE0("Accepting Data Success!!!\n");

	// need to validate that we accepted from the server
	if (pThis->m_saServerData.IPAddr() != pThis->m_saServerCtrl.IPAddr())
	{
		TRACE0("Accept failed, improper IP address connected\n");
		pThis->m_sockListen.Cleanup();
		pThis->m_sockData.Close();
		// Let the waiting thread go
		pThis->m_eventAccept.Set();
		return 1;
	}

	// Close down the listening thread
	pThis->m_sockListen.Cleanup();

	// Let the waiting thread go
	pThis->m_eventAccept.Set();

	return 0;
}


BOOL CeFtpClient::AcceptDataConn()
{
//	TRACE0("AcceptDataConn()\n");

	if (! m_bPassive)
	{
		// BEFORE ANYTHING ELSE, RESET THE event so the
		// waiting thread MUST WAIT!!!

		m_eventAccept.Reset();

		// We don't keep track of the actual thread handles or IDs, the
		// event flags and socket handles should mitigate any 
		// thread loses
		DWORD dwThread;
		if (NULL == CreateThread(NULL, 0, StartAccept, this, 0, &dwThread))
		{
			SetFtpError(ftpErrorThreadCreate, _T("Thread creation failed."));
			m_eventAccept.Set();
			return FALSE;
		}

		return TRUE;
	}
	else
	{
		// does nothing, already set to go
		return TRUE;
	}
}


BOOL CeFtpClient::StartDataConn()
{
	// initiate the data connection on a seperate thread
	// it has to be active BEFORE we retrieve the reply from
	// the control socket 
	if (! AcceptDataConn())
		return FALSE;

	// check for the appropriate continuation return code
	int nReply = GetFtpReply();
	if (ftpRc_FILE_OK_START_DATA != nReply && ftpRc_DATA_OPEN != nReply)
	{
		// need to close the data connection here, otherwise the 
		// accept will wait until something else comes along
		// closing the socket breaks the wait initiated by
		// AcceptDataConn()
		TRACE0("Failed FTP reply\n");
		m_sockListen.Cleanup();
		return FALSE;
	}

//	TRACE("Success FTP reply\n");

	if (!m_bPassive)
	{
		// wait for the accept to happen first
		// the accept thread sets this event when we can continue
		m_eventAccept.WaitFor();
	}

	// we have to check the data socket value in case a failure occured
	if (! m_sockData)
	{
		TRACE0("Data connection complete, error\n");
		// error occured
		return FALSE;
	}

	// hunky dorey
//	TRACE0("Data connection complete, success\n");
	return TRUE;
}


int CeFtpClient::SendFtpCmd(int nFtpCmd)
{
	int nLen, nBytesSent = 0;
	char szFtpCmd[255];

	// Create a command string (if we don't already have one)
	switch (nFtpCmd)
	{
	case ABOR:
	case NLST:
	case LIST:
	case PWD:
	case XPWD:
	case PASV:
	case QUIT:
	case HELP:
	case SYST:
	case STOU:
	case CDUP:
	case NOOP:
		// Solitary Ftp command string (no parameters)
		strcpy(szFtpCmd, g_rgszFtpCmd[nFtpCmd]);
		strcat(szFtpCmd, "\r\n");
		break;

	default:
		TRACE0("Bogus or unimplemented command\n");
		return -1;  // we have a bogus command!
	}

	nLen = strlen(szFtpCmd);
	
	// send all the buffer data with default timeout on each block
	nBytesSent = m_sockCtrl.Write((BYTE*)szFtpCmd, nLen, m_dwTimeoutCmdSend);
	if (nBytesSent <= 0)
	{
		SetSocketError(m_sockCtrl.GetLastError());
		ResetConnection();
		return -1;
	}

	if (nBytesSent == nLen)
	{		
		CeString str(szFtpCmd);
		TRACE1(_T("CMDSent: %s\n"), (LPCTSTR) str);
	}

	return nBytesSent;
}


//
// Description: Format and send an FTP command to the server
//
int CeFtpClient::SendFtpCmd(int nFtpCmd, LPCTSTR lpszArg)
{
	int nLen, nBytesSent = 0;
	char szFtpCmd[256];
	char szArg[128];

	if (NULL == lpszArg || 0 == *lpszArg)
	{
		TRACE0("NULL string passed as argument to command requiring one.\n");
		return 0;
	}

	for (int ii = 0; *lpszArg != 0; lpszArg++, ii++)
	{
		szArg[ii] = (char) *lpszArg;
		if (szArg[ii] == '\\')
			szArg[ii] = '/';
	}
	szArg[ii] = 0;

	// Create a command string (if we don't already have one)
	switch (nFtpCmd)
	{
	case CWD:
	case MKD:
	case RMD:
	case XMKD:
	case XRWD:
	case XCWD:
	case PASS:
	case PORT:
	case RETR:
	case TYPE:
	case USER:
	case MODE:
	case DELE:
	case STOR:
	case RNFR:
	case RNTO:
	case APPE:
	case NLST:
	case LIST:
		// Ftp commmand and parameter
		strcpy(szFtpCmd, g_rgszFtpCmd[nFtpCmd]);
		strcat(szFtpCmd, " ");
		strcat(szFtpCmd, szArg);
		strcat(szFtpCmd, "\r\n");
		break;

	default:
		TRACE0("Bogus or unimplemented command\n");
		return 0;  // we have a bogus command!
	}
	nLen = strlen(szFtpCmd);
	
	// send all the buffer data with default timeout on each block
	nBytesSent = m_sockCtrl.Write((BYTE*)szFtpCmd, nLen, m_dwTimeoutCmdSend);
	if (nBytesSent <= 0)
	{
		SetSocketError(m_sockCtrl.GetLastError());
		ResetConnection();
		return 0;
	}

	// if we sent it all, update our status and move everything up 
	//  in command queue
	if (nBytesSent == nLen)
	{
		if (nFtpCmd == PASS)
		{
			// hide password
			for (int ii = 5; ii < nBytesSent && szFtpCmd[ii] != CR; ii++)
				szFtpCmd[ii] = 'x';
		}

		TRACE1(_T("CMDSent: %hs\n"), (LPCTSTR) szFtpCmd);
	}

	return nBytesSent;
}

int CeFtpClient::ColumnStrings(const CeString& strRow, CeStringArray& arCol, int nMaxCols)
{
	register int nRowLen = strRow.GetLength();
	if (nRowLen <= 0)
		return 0;

	arCol.SetSize(0);
	LPCTSTR sz = strRow;

	CeString str;

	BOOL bWhiteSpace = TRUE;
	for (register int ii=0, nStart=0; ii < nRowLen; ii++)
	{
		if (' ' == sz[ii] && !bWhiteSpace)
		{
			// end column
			str = CeString(sz + nStart, ii - nStart);
			arCol.Add( &str );
			bWhiteSpace = TRUE;

			if (nMaxCols > 1 && arCol.GetSize() >= nMaxCols - 1)
			{
				// the rest will go into the last column
				bWhiteSpace = FALSE;
				nStart = ii+1;
				ii = nRowLen;
				break;
			}
		}
		else if (' ' != sz[ii] && bWhiteSpace)
		{
			// start column
			bWhiteSpace = FALSE;
			nStart = ii;
		}
	}

	if (! bWhiteSpace)
	{
		// add final column
		str = CeString(sz + nStart, ii - nStart);
		arCol.Add( &str );
	}

	return arCol.GetSize();
}



//
// Read the FTP reply from server (and log it)
// and process the results based on the command sent
//
int CeFtpClient::GetFtpReply()
{
	BYTE* pBuf = m_arCtrlRecv.GetData();
	int nBufLen = m_arCtrlRecv.GetSize();
	int nRead = 0;

	m_nLastFtpReply = 0;

	if (m_nBufInUse < 4)
	{
		// need an initial/extended read to get a complete line
		nRead = m_sockCtrl.Receive( pBuf + m_nBufInUse, nBufLen - m_nBufInUse, m_dwTimeoutCmdRecv);
		if (nRead <= 0)
		{
			if (nRead < 0)
			{
				// control socket disconnected
				SetFtpError(ftpErrorConnectionReset, _T("Connection closed by host."));
				ResetConnection();
			}
			else
				SetFtpError(ftpErrorTimeout, _T("Server timeout (on control)."));
			return 0;
		}

		m_nBufInUse += nRead;
	}

	// NUL terminate the string buffer
	pBuf[m_nBufInUse] = '\0';

	// figure the numeric value of the response
	m_nLastFtpReply = -1;
	if (m_nBufInUse >= 3)
	{
		// the command MUST begin with a three digit response
		if (isdigit(*(pBuf)) && isdigit(*(pBuf+1)) && isdigit(*(pBuf+2)))
			m_nLastFtpReply = (*(pBuf) - '0') * 100 + (*(pBuf+1) - '0') * 10 + (*(pBuf+2) - '0');
	}

	if (m_nLastFtpReply < 100 || m_nLastFtpReply >= 600)
	{
		// Bad response from server, terminate connection
		SetFtpError(ftpErrorBadServerResponse, _T("Invalid Server Response"));
		ResetConnection();
		return -1;
	}

	//
	// Look through the buffer until we find a start of line with
	// a matching three digit number and a SPACE in the fourth position
	//
	// Note: this could be the first line, so the first time through
	//       there will be a match on the first three conditions but we
	//       may or may not match on the fourth
	//
	int nBufPos = 0;
	BOOL bEndOfResp = FALSE;
	BOOL bEndOfLine = FALSE;
//	CeString strNewResp;
	do
	{
		if (nBufPos + 4 > m_nBufInUse)
		{
			// test if we have enough buffer to read more
			if (nBufPos + MTU_SIZE > nBufLen)
			{
				// extend if we don't want ATLEAST MTU_SIZE more
				m_arCtrlRecv.SetSize(m_arCtrlRecv.GetSize() + MTU_SIZE);

				// adjust for changes after allocation
				nBufLen = m_arCtrlRecv.GetSize();
				pBuf = m_arCtrlRecv.GetData();
			}

			// incomplete read, try again, extending the amount read
			nRead = m_sockCtrl.Receive(pBuf + m_nBufInUse,
				nBufLen - m_nBufInUse,
				m_dwTimeoutCmdRecv);

			if (nRead <= 0)
			{
				if (nRead < 0)
				{
					SetFtpError(ftpErrorConnectionReset, _T("Connection closed by host."));
					ResetConnection();
				}
				else
				{
					SetFtpError(ftpErrorTimeout, _T("Server timeout (on control)."));
				}
				return 0;
			}

			m_nBufInUse += nRead;

			pBuf[m_nBufInUse] = '\0';
		}

		// test for end of response
		LPCSTR pszMsgStart = (LPCSTR) pBuf + nBufPos;
		if (*(pBuf) == *(pBuf+nBufPos) && *(pBuf+1) == *(pBuf+nBufPos+1) && *(pBuf+2) == *(pBuf+nBufPos+2))
		{
			// we have a reply line starting with a numeric
			if (' ' == *(pBuf+nBufPos+3))
			{
				// we found the end of the response, continue on
				bEndOfResp = TRUE;
			}
			pszMsgStart = (LPCSTR) pBuf + nBufPos + 3;
		}

		// find the end of the line <CR><LF> pair
		for ( ; *(pBuf+nBufPos) && LF != *(pBuf+nBufPos); nBufPos++)
			;

		// append this portion of the text reply to the response string
//		strNewResp += CeString((LPCSTR) pszMsgStart, (int)(((BYTE*)pszMsgStart)-pBuf)+nBufPos);
//		strNewResp.RTrim();

		// test for the end of line and end of response conditions
		if (LF == *(pBuf+nBufPos))
		{
			// move to first character of the next line
			nBufPos++;
			if (bEndOfResp)
				// not EOL, need to read up to atleast that
				bEndOfLine = TRUE;
		}
	}
	while (! (bEndOfResp && bEndOfLine));

	// First digit in 3-digit Ftp reply code is the most significant
	switch (m_nLastFtpReply / 100)
	{
    case 1:	// Positive preliminary reply
			// (incomplete command sequence, waiting on data connection)
	case 2:	// Positive completion reply
	case 3:	// Positive intermediate reply (send next command to complete)
		break;

	case 4:	// Transient negative completion reply
	case 5:	// Permenant negative completion reply
		// depending on the command this is a response to, we may need to
		// clean out some of the sockets and the socket addresses
		break;
	}

	// allocate and copy the buffer as output
	// Note: never wide strings
	ASSERT(nBufPos < sizeof m_szLastFtpReply);
	for (int ii = 0; ii < nBufPos && ii < RESP_BUFSIZ; ii++)
		m_szLastFtpReply[ii] = (char) pBuf[ii];
	m_szLastFtpReply[ii] = 0;

	ASSERT(nBufPos <= m_nBufInUse);

	// move up the unprocessed chunk to the front of the buffer
	if (nBufPos != m_nBufInUse)
		memmove(pBuf, pBuf + nBufPos, m_nBufInUse - nBufPos);
	m_nBufInUse -= nBufPos;

	ASSERT(m_nBufInUse >= 0);

	char szBuf[1024];
	for (ii = 0; m_szLastFtpReply[ii] != 0 && ii < sizeof szBuf; ii++)
		szBuf[ii] = (char) m_szLastFtpReply[ii];
	szBuf[ii] = 0;

	OnFtpMessage(m_nLastFtpReply, szBuf);

	return m_nLastFtpReply;
}


DWORD CeFtpClient::RecvBuffer(BOOL bTerminate, DWORD dwSize)
{
	BYTE* pBuf = m_arDataRecv.GetData();
	DWORD dwLen = m_arDataRecv.GetSize();

	m_dwByteCount = 0;

	int nReadTot = 0;
	while (TRUE)
	{
		int nRead = 0;

		if (dwLen - m_dwByteCount <= 0)
		{
			// expand the buffer
			m_arDataRecv.SetSize(m_arDataRecv.GetSize() + MTU_SIZE);

			// reset the state variables
			pBuf = m_arDataRecv.GetData();
			dwLen = m_arDataRecv.GetSize();
		}

		// Note: receive will block pending data, when
		// the socket is closed on the other end, the receive
		// will be 0 bytes.
		nRead = m_sockData.Receive(pBuf + m_dwByteCount, dwLen - m_dwByteCount, m_dwTimeoutDataRecv);
		if (nRead < 0)
		{
			SetSocketError(m_sockData.GetLastError());
			break;
		}

		m_dwByteCount += nRead;
		if (nRead <= 0 || (dwSize > 0 && m_dwByteCount >= dwSize))
			// data socket closed by caller
			break;

		OnTransferCallback(m_dwByteCount, dwSize, NULL);
	}

	if (bTerminate)
		// null terminate, should be string data
		pBuf[m_dwByteCount] = 0;

	return m_dwByteCount;
}


//
// locates and returns the position of the first LF in the string
// the length of the string that DOESN'T contain this pair is the returned pointer
// substracted from the string passed in
//
char* strlf (char *sz, size_t count)
{
	// find the end of the line <LF>
	for (char* szEnd = sz + count; sz <= szEnd && *sz; )
	{
		if (LF == *sz)
		{
			*sz = 0;
			return sz+1;
		}
		else
			sz++;
	}

	return NULL;
}


//
// locates and returns the first character of the CRLF pair in the string
// the length of the string that DOESN'T contain this pair is the returned pointer
// substracted from the string passed in
//
char* strcrlf (char *sz, size_t count)
{
	char* szStart = sz;
	// find the end of the line <CR><LF> pair
	for (char* szEnd = sz + count; sz <= szEnd && *sz; )
	{
		if (CR == *sz)
		{
			sz++;

			// the carriage-return character found, check the length and THEN see
			// if the next character is the line-feed

			if (sz > szEnd)
				return NULL;

			if (LF == *sz)
			{
				// null terminate
				*(sz-1) = 0; *sz = 0;
				return sz + 1;
			}
		}
		else
			sz++;
	}

	// some systems only return linefeeds (pSOS for one), try to look for just that
	// it should be ok on other systems, since the LF somes after the CR and
	// we should have seen it already in that case.
	char* s = strlf(szStart, count);
	return s;
}


DWORD CeFtpClient::RecvFileList(CeFileAttrArray& arFiles)
{
	// reset the global read size
	m_dwByteCount = 0;

	// empty the file cache
	arFiles.SetSize(0);

	// get the buffer pointers and sizes
	BYTE* pBuf = m_arDataRecv.GetData();
	int nLen = m_arDataRecv.GetSize();

	// total read size
	int nReadTotal = 0;

	// file description structure
	WIN32_FIND_DATA fd;

	while (TRUE)
	{
		if (nLen - nReadTotal <= 0)
		{
			// expand the buffer
			m_arDataRecv.SetSize(nLen + MTU_SIZE);

			// reset the state variables
			pBuf = m_arDataRecv.GetData();
			nLen = m_arDataRecv.GetSize();
		}

		int nRead = 0;

		// Note: receive will block pending data, when
		// the socket is closed on the other end, the receive
		// will be 0 bytes.
		nRead = m_sockData.Receive(pBuf + nReadTotal, nLen - nReadTotal, m_dwTimeoutDataRecv);
		if (nRead < 0)
		{
			SetSocketError(m_sockData.GetLastError());
			break;
		}

		if (nRead <= 0)
			// data socket closed by caller
			break;

		nReadTotal += nRead;

		// split what full ones we have
		int nChewed = 0;
		char *lpszName, *lpsz;

		for (lpszName = lpsz = (char*) pBuf; lpsz = strcrlf(lpsz, nReadTotal-nChewed); lpszName = lpsz)
		{
			// the end-of-line is null terminated upon return

			if (OnParseListFile(lpszName, &fd))
				arFiles.Add(fd);

			nChewed = lpsz - (char*) pBuf;
		}

		// move the reset back (should be small amounts)
		memmove(pBuf, pBuf + nChewed, nReadTotal - nChewed);
		nReadTotal -= nChewed;
	}

	return m_dwByteCount;
}


DWORD CeFtpClient::RecvFile(HANDLE hFile, DWORD dwSize)
{
	BYTE* pBuf = m_arDataRecv.GetData();
	DWORD dwBufLen = m_arDataRecv.GetSize();

	m_dwByteCount = 0;

	while (TRUE)
	{
		int nRead = 0;
		DWORD dwWritten = 0;

		// Note: receive will block pending data, when
		// the socket is closed on the other end, the receive
		// will be 0 bytes.
		nRead = m_sockData.Receive(pBuf, dwBufLen, m_dwTimeoutDataRecv);
		if (nRead < 0)
		{
			SetSocketError(m_sockData.GetLastError());
			break;
		}

		::WriteFile(hFile, pBuf, nRead, &dwWritten, NULL);

		if ((DWORD) nRead != dwWritten)
		{
			// error occured
			return 0;
		}

		m_dwByteCount += nRead;
		if (nRead <= 0)
			// data socket closed by caller
			break;

		OnTransferCallback(m_dwByteCount, dwSize, NULL);
	}

	return m_dwByteCount;
}


DWORD CeFtpClient::SendBuffer(BYTE* pBuf, DWORD dwLen)
{
	int nWritten = 0;

	// Note: receive will block pending data, when
	// the socket is closed on the other end, the receive
	// will be 0 bytes.
	nWritten = m_sockData.Write(pBuf, dwLen, m_dwTimeoutDataSend);
	if (nWritten  <= 0)
	{
		SetSocketError(m_sockData.GetLastError());
		return 0;
	}

	if (dwLen != nWritten)
	{
		// error occured
		return 0;
	}

	return nWritten;
}


DWORD CeFtpClient::SendFile(HANDLE hFile, DWORD dwSize)
{
	CeByteArray arBuf;
	arBuf.SetSize(MTU_SIZE);
	BYTE* pBuf = arBuf.GetData();

	DWORD dwWrittenTot = 0;

	while (TRUE)
	{
		DWORD dwRead = 0;
		if (! ::ReadFile(hFile, pBuf, MTU_SIZE, &dwRead, NULL))
		{
			// file error
		}

		if (dwRead <= 0)
			// EOF hit
			break;

		int nWritten = 0;
		// Note: receive will block pending data, when
		// the socket is closed on the other end, the receive
		// will be 0 bytes.
		nWritten = m_sockData.Write(pBuf, dwRead, m_dwTimeoutDataSend);
		if (nWritten < 0)
		{
			SetSocketError(m_sockData.GetLastError());
			break;
		}

		if (dwRead != nWritten)
		{
			// error occured
			return 0;
		}

		dwWrittenTot += dwRead;

		OnTransferCallback(dwWrittenTot, dwSize, NULL);

		if (MTU_SIZE != dwRead)
			// last piece of the file
			break;
	}

	return dwWrittenTot;
}


void CeFtpClient::SplitBuffer(LPCSTR szBuf, DWORD dwByteCount, CeStringArray& ar)
{
	DWORD dwBufPos = 0;
	const char* pBuf = szBuf;
	const char* pEnd = szBuf + dwByteCount;

	ar.SetSize(0);

	if (dwByteCount == 0)
		return;

	while (TRUE)
	{
		// find the end of the line <CR><LF> pair
		int dwStart = dwBufPos;
		for ( ; *(pBuf+dwBufPos) && dwBufPos <= dwByteCount; dwBufPos++)
		{
			if (CR == *(pBuf+dwBufPos) || LF == *(pBuf+dwBufPos))
				break;
		}

		CeString str(pBuf + dwStart, dwBufPos - dwStart);
		ar.Add( str );

		for ( ;	(dwBufPos <= dwByteCount) &&
			    *(pBuf+dwBufPos) != '\0' &&
			    (CR == *(pBuf+dwBufPos) || LF == *(pBuf+dwBufPos));
			 dwBufPos++)
			;

		if (dwBufPos >= dwByteCount)
			break;

		if (*(pBuf + dwBufPos) == 0)
			break;
	}
}


BOOL CeFtpClient::SimpleCmd(int nFtpCmd, LPCTSTR szArg)
{
	int nResp = 0;

	if (SendFtpCmd(nFtpCmd, szArg) <= 0 || ftpRc_FILE_REQ_OK != (nResp = GetFtpReply()))
		return FALSE;

	return TRUE;
}


BOOL CeFtpClient::SetTransferType(FtpTransferType eTransferType)
{
	if (! m_bConnected)
		return FALSE;

	if (eTransferType != m_eTransferType)
	{
		TCHAR szArg[2];
		szArg[1] = 0;

		switch (eTransferType)
		{
		case ftpTtype_ASCII:	*szArg = _T('A'); break;
		case ftpTtype_BINARY:	*szArg = _T('I'); break;
		case ftpTtype_EBCDIC:	*szArg = _T('E'); break;
		default:
			return FALSE;
		}

		m_eTransferType = eTransferType;

		// VC6 Change - Comment These lines of code out since the if statement generates a warning
		// int nResp;
		// SendFtpCmd(TYPE, szArg);
		// if (ftpRc_CMD_OK != (nResp = GetFtpReply()))
		//	;

		// Replacement Lines of code
		if (SendFtpCmd(TYPE, szArg) <= 0 || ftpRc_CMD_OK != GetFtpReply())
			return FALSE;
	}

	return TRUE;
}

///////////////////////////////////////////////////////////////////////////////
//
// Implementation of User Callable Methods
//
///////////////////////////////////////////////////////////////////////////////

BOOL CeFtpClient::SendNoOp()
{
	if (SendFtpCmd(NOOP) <= 0 || ftpRc_CMD_OK != GetFtpReply())
		return FALSE;

	return TRUE;
}


BOOL CeFtpClient::Connect(LPCTSTR szServer, LPCTSTR pstrUserName /*=NULL*/, LPCTSTR pstrPassword /*=NULL*/)
{
	ClearFtpError();

	if (m_bConnected)
		return FALSE;

	char szAddr[255];
	int nServer = _tcslen(szServer);
	for (int ii = 0; ii <= nServer; ii++)
		szAddr[ii] = (char) szServer[ii];

	// set up the server address for connection attempt
	// first try dotted decimal conversion
	ULONG ulAddr = inet_addr(szAddr);
	if (INADDR_NONE != ulAddr)
	{
		// swap to host byte order, that's what CSockAddr() expects
		ulAddr = ntohl(ulAddr);
		// set the address and port we want to connect to
		m_saServerCtrl = CeSockAddr(ulAddr, m_nFtpCtrlPort);
	}
	else
	{
		// it isn't that so try a host name lookup
		TRACE0("Not a valid IP number, trying name resolution.\n");

		CeSockAddr sa = CeSocket::GetHostByName(szAddr, m_nFtpCtrlPort);
		ulAddr = sa.IPAddr();
		if (ulAddr == 0)
		{
			SetSocketError(WSAGetLastError());
			return FALSE;	// invalid address
		}
	}

	if (INADDR_ANY == ulAddr)
	{
		TRACE0("Invalid address.\n");
		return FALSE;
	}

	// continue processing 
	return Connect(ulAddr, pstrUserName, pstrPassword);

}


BOOL CeFtpClient::Connect(const ULONG ulAddr, LPCTSTR pstrUserName /*= NULL*/, LPCTSTR pstrPassword /*= NULL*/)
{
	ClearFtpError();

	if (m_bConnected)
		return FALSE;

	m_saServerCtrl = CeSockAddr(ulAddr, m_nFtpCtrlPort);

	// try to create and connect the server control line socket

	// Get a TCP socket for control connection
	if (! m_sockCtrl.Create())
		return FALSE;

	struct linger linger;
	linger.l_onoff = TRUE;
	linger.l_linger = 10;
	m_sockCtrl.SetOption(SOL_SOCKET, SO_LINGER, &linger, sizeof linger);

	// Initiate connect to server
	if (! m_sockCtrl.Connect(m_saServerCtrl, m_dwTimeoutConnect))
	{
		SetSocketError(GetLastError());
		m_sockCtrl.Close();
		return FALSE;
	}

	m_sockCtrl.SetWriteTimeout(m_dwTimeoutCmdSend);
	m_sockCtrl.SetReadTimeout(m_dwTimeoutCmdRecv);

	// this seems to be optional, i'll allow failure for now...

	// check for success, doesn't hand 120 response of
	// being ready in some amount of time
	int nResp;
	if (ftpRc_READY_FOR_USER != (nResp = GetFtpReply()))
		return FALSE;

	LPCTSTR strUser;
	if (NULL == pstrUserName || ! *pstrUserName)
		strUser = _T("anonymous");
	else
		strUser = pstrUserName;

	// log in
	if (SendFtpCmd(USER, strUser) <= 0)
		return FALSE;

	if (ftpRc_USER_OK_SEND_PASS == (nResp = GetFtpReply()))
	{
		if (NULL == pstrPassword || ! *pstrPassword)
			strUser = _T("anonymous@anonymous.com");
		else
			strUser = pstrPassword;

		// may not require password
		SendFtpCmd(PASS, strUser);
		nResp = GetFtpReply();
		// VC6 Change - Comment These lines of code out since the if statement generates a warning
		//if (ftpRc_USER_LOGGED_IN == nResp || ftpRc_CMD_NOT_REQUIRED == nResp)
		//	;
	}

	if (ftpRc_USER_LOGGED_IN != nResp)
	{
		// error of some type, either need an account,
		// which isn't supported at this time or
		// invalid password or server too busy
		return FALSE;
	}

	// we are now considered connected
	m_bConnected = TRUE;

	// default transfer mode
	m_eTransferType = ftpTtype_ASCII;

	// testing code, will eventually parse and enable/disable commands
	SendFtpCmd(HELP);
	nResp = GetFtpReply();
	if (ftpRc_SYSTEM_HELP_REPLY != nResp && ftpRc_HELP_REPLY != nResp)
	{
		// ignore error on this command
		TRACE1(_T("Help command failed with an error of %d"), nResp);
	}

	// retrieve the system type
	m_strSystem.Empty();
	SendFtpCmd(SYST);
	if (ftpRc_SYSTEM_TYPE == GetFtpReply())
	{
		CeString strReply = GetFtpErrorReplyString();

		// Parse the system type for later use
		for (int ii = 4; ii < strReply.GetLength(); ii++)
		{
			if (strReply[ii] == ' ')
				break;
			m_strSystem += strReply[ii];
		}
	}
	/*
	__except (HANDLE_EXCEPTION(STATUS_CE_SOCKET_EXCEPTION))
	{
		SetSocketError(WSAGetLastError());
		ResetConnection();
		return FALSE;	// invalid address
	}
	*/

	m_strDir = m_strRoot = GetCurDir();

	return TRUE;
}

BOOL CeFtpClient::Disconnect()
{
	ClearFtpError();

	if (m_bConnected)
	{
		SendFtpCmd(QUIT);
		GetFtpReply();

		m_sockListen.Cleanup();
		m_sockData.Cleanup();
		m_sockCtrl.Cleanup();
		m_bConnected = FALSE;
		m_nBufInUse = 0;

		m_strSystem.Empty();
	}

	return TRUE;
}

BOOL CeFtpClient::RenameFile(LPCTSTR szRemoteName, LPCTSTR szNewRemoteName)
{
	ClearFtpError();

	if (NULL == szRemoteName || ! *szRemoteName ||
		NULL == szNewRemoteName || ! *szNewRemoteName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	int nResp = 0;

	SendFtpCmd(RNFR, szRemoteName);
	if (ftpRc_FILE_REQ_OK_PENDING != (nResp = GetFtpReply()))
		return FALSE;

	SendFtpCmd(RNTO, szNewRemoteName);
	if (ftpRc_FILE_REQ_OK != (nResp = GetFtpReply()))
		return FALSE;

	return TRUE;
}

BOOL CeFtpClient::DeleteFile(LPCTSTR szRemoteName)
{
	ClearFtpError();

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	return SimpleCmd(DELE, szRemoteName);
}

BOOL CeFtpClient::GetFileAsBuffer(LPCTSTR szRemoteName, CeByteArray& arBuf, DWORD dwReq, FtpTransferType eTransType/*=ftpTtype__BINARY*/)
{
	ClearFtpError();

	if (NULL == szRemoteName || ! *szRemoteName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	SetTransferType(eTransType);

	if (! InitDataConn())
		return FALSE;

	SendFtpCmd(RETR, szRemoteName);

	if (! StartDataConn())
		return FALSE;

	DWORD dwRecv = RecvBuffer(FALSE, dwReq);

	m_sockData.Cleanup();

	int nResp = GetFtpReply();
	if (ftpRc_CLOSING_DATA != nResp && ftpRc_CONNECT_CLOSED != nResp)
		return FALSE;

	if (dwReq == 0 || dwReq > dwRecv)
		// get ONLY what the caller wanted!!!!
		// when 0 get ALL, when larger than what we got, give that
		dwReq = dwRecv;

	arBuf.SetSize(dwReq);
	::memcpy(arBuf.GetData(), m_arDataRecv.GetData(), dwReq);

	return TRUE;
}

BOOL CeFtpClient::PutBufferAsFile(LPCTSTR szRemoteName, BYTE* pBuf, DWORD dwSize, FtpTransferType eTransType/*=ftpTtype_BINARY*/)
{
	ClearFtpError();

	if (NULL == szRemoteName || ! *szRemoteName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	__try
	{
		SetTransferType(eTransType);
		
		if (! InitDataConn())
			return FALSE;

		// store as the specified name
		SendFtpCmd(STOR, szRemoteName);

		if (! StartDataConn())
			return FALSE;

		SendBuffer(pBuf, dwSize);

		m_sockData.Cleanup();

		if (ftpRc_CLOSING_DATA != GetFtpReply())
			return FALSE;
	}
	__except (HANDLE_EXCEPTION(STATUS_CE_SOCKET_EXCEPTION))
	{
		SetSocketError(WSAGetLastError());

		// report the error and delete
		TRACE0("Exception while placing remote file.\n");
		//e->Delete();
		return FALSE;
	}

	return TRUE;
}

CeString CeFtpClient::GetCurDir()
{
	CeString strDir;
	int nResp;
	CeString strReply;

	ClearFtpError();

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return "";
	}

	SendFtpCmd(PWD);
	if (ftpRc_DIR_REQ_OK != (nResp = GetFtpReply()))
	{
		CeString strSystem = GetSystemType();
		if (ftpRc_FILE_REQUEST_DENIED == nResp)
		{
			if (strSystem == _T("pSOSytem"))
			{
				// special processing, because of pSOS BUG
				strReply = GetFtpErrorReplyString();
				for (int ii = 4; ii < strReply.GetLength(); ii++)
				{
					if (strReply[ii] == ' ' || strReply[ii] == CR || strReply[ii] == LF)
						break;

					strDir += strReply[ii];
				}
			}
		}

		return strDir;
	}

	// Parse the directory name from the response format should be:
	// '257 "PATHNAME" created.'
	BOOL bInQuote = FALSE;
	strReply = GetFtpErrorReplyString();
	for (int ii = 0; ii < strReply.GetLength(); ii++)
	{
		if (strReply[ii] == '"')
		{
			if (bInQuote)
			{
				bInQuote = FALSE;
				break;
			}
			else
			{
				bInQuote = TRUE;
				continue;
			}
		}

		if (bInQuote)
			strDir += strReply[ii];
	}

	if (bInQuote)
	{
		// incomplete directory name, return empty string
		strDir.Empty();
	}
	else if (strDir.IsEmpty())
	{
		// no quoted directory name found, will return empty string
		;
	}

	return strDir;
}

BOOL CeFtpClient::SetCurDir(LPCTSTR szPath)
{
	ClearFtpError();

	if (NULL == szPath || ! *szPath)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	BOOL bRet = SimpleCmd(CWD, szPath);

	if (bRet)
		m_strDir = GetCurDir();

	return bRet;
}

BOOL CeFtpClient::DeleteDir(LPCTSTR szPath)
{
	ClearFtpError();

	if (NULL == szPath || ! *szPath)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	return SimpleCmd(RMD, szPath);
}

BOOL CeFtpClient::CreateDir(LPCTSTR szPath)
{
	ClearFtpError();

	if (NULL == szPath || ! *szPath)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	SendFtpCmd(MKD, szPath);
	if (ftpRc_DIR_REQ_OK != GetFtpReply())
		return FALSE;

	return TRUE;
}


BOOL CeFtpClient::GetFileList(CeStringArray& strFiles)
{
	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	ClearFtpError();

	SetTransferType(ftpTtype_ASCII);

	if (! InitDataConn())
		return FALSE;

	SendFtpCmd(NLST);

/**/
	// initiate the data connection on a seperate thread
	// it has to be active BEFORE we retrieve the reply from
	// the control socket 
	if (! AcceptDataConn())
		return FALSE;

	// check for the appropriate continuation return code
	int nReply = GetFtpReply();
	if (ftpRc_FILE_OK_START_DATA != nReply && ftpRc_DATA_OPEN != nReply)
	{
		// need to close the data connection here, otherwise the 
		// accept if wait until something else comse along
		m_sockListen.Cleanup();
		m_sockData.Cleanup();

		// check for pSOS bug, returns an error when there are
		// no files in the directory, which means we can't distinguish
		// an error from an empty directory, but this is the best we can do
		CeString strSystem = GetSystemType();
		if (m_strSystem == _T("pSOSytem") && ftpRc_FILE_REQUEST_DENIED == nReply)
			return TRUE;
		
		return FALSE;
	}

	// read the listing, wait for the accept to happend first
	// the accept thread sets this event when we can continue
	if (!m_bPassive)
	{
		// wait for the accept to happen first
		// the accept thread sets this event when we can continue
		m_eventAccept.WaitFor();
	}

	// we have to check the data socket value in case a failure occured
	if (! m_sockData)
		// error occured
		return FALSE;
/**/
	// fetch data
	if (RecvBuffer(TRUE) > 0)
	{
		// breakup into list entries
		SplitBuffer((LPCSTR) m_arDataRecv.GetData(), m_dwByteCount, strFiles);
	}

	// close up, we're done
	m_sockData.Cleanup();

	// get the complete message
	int nResp = GetFtpReply();
	if (ftpRc_CLOSING_DATA != nResp && ftpRc_FILE_REQ_OK != nResp)
		return FALSE;

	return TRUE;
}


BOOL CeFtpClient::GetFileList(CeFileAttrArray& arFiles, LPCTSTR lpszFileSpec /*=NULL*/)
{
	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	arFiles.SetSize(0);

	ClearFtpError();

	SetTransferType(ftpTtype_ASCII);

	if (! InitDataConn())
		return FALSE;

	if (NULL == lpszFileSpec || 0 == *lpszFileSpec)
		SendFtpCmd(LIST);
	else
	{
		LPCTSTR pszTmp = lpszFileSpec;
		TCHAR szSpec[MAX_PATH];

		// convert all backward slashs to forward, thye seem to work better
		for (int ii = 0; *pszTmp; pszTmp++, ii++)
		{
			szSpec[ii] = *pszTmp;
			if (szSpec[ii] == _T('\\'))
				szSpec[ii] = _T('/');
		}
		szSpec[ii] = 0;

		// List doesn't like directory names with spaces, so we need
		// to CWD to the directory (which doesn't mind spaces) and do a directory
		// from the new current directory

		pszTmp = _tcsrchr(szSpec, _T('/'));
		if (NULL != pszTmp)
		{
			if (szSpec == pszTmp)
			{
				szSpec[0] = _T('/');
				szSpec[1] = 0;
			}
			else
			{
				//_tcsncpy(szSpec, lpszFileSpec, pszTmp-lpszFileSpec);
				szSpec[pszTmp-szSpec] = 0;
			}

			if (! SetCurDir(szSpec))
				return FALSE;

			_tcscpy(szSpec, pszTmp+1);
		}
		else
			_tcscpy(szSpec, szSpec);

		if (*szSpec == 0)
			SendFtpCmd(LIST);
		else			
			SendFtpCmd(LIST, szSpec);
	}

	// initiate the data connection on a seperate thread
	// it has to be active BEFORE we retrieve the reply from
	// the control socket 
	if (! AcceptDataConn())
		return FALSE;

	// check for the appropriate continuation return code
	int nReply = GetFtpReply();
	if (ftpRc_FILE_OK_START_DATA != nReply && ftpRc_DATA_OPEN != nReply)
	{
		// need to close the data connection here, otherwise the 
		// accept if wait until something else comse along
		m_sockListen.Cleanup();
		m_sockData.Cleanup();

		// check for pSOS bug, returns an error when there are
		// no files in the directory, which means we can't distinguish
		// an error from an empty directory, but this is the best we can do
		CeString strSystem = GetSystemType();
		if (m_strSystem == _T("pSOSytem") && ftpRc_FILE_REQUEST_DENIED == nReply)
			return TRUE;
		
		return FALSE;
	}

	// read the listing, wait for the accept to happend first
	// the accept thread sets this event when we can continue
	if (!m_bPassive)
	{
		// wait for the accept to happen first
		// the accept thread sets this event when we can continue
		m_eventAccept.WaitFor();
	}

	// we have to check the data socket value in case a failure occured
	if (! m_sockData)
		// error occured
		return FALSE;

	// get the file list
	DWORD dwSize = RecvFileList(arFiles);

	// always close the socket, some FTPs require detection of the close
	m_sockData.Cleanup();

	// get the reply from the server
	int nResp = GetFtpReply();
	if (ftpRc_CLOSING_DATA == nResp || ftpRc_FILE_REQ_OK == nResp)
		return TRUE;
	else
		return FALSE;
}


// file info
BOOL CeFtpClient::GetFileInfo(LPCTSTR lpszFileName, WIN32_FIND_DATA& finddata)
{
	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	memset(&finddata, 0, sizeof finddata);

	ClearFtpError();

	SetTransferType(ftpTtype_ASCII);

	if (! InitDataConn() || SendFtpCmd(LIST, lpszFileName) <= 0)
		return FALSE;

	// initiate the data connection on a seperate thread
	// it has to be active BEFORE we retrieve the reply from
	// the control socket 
	if (! AcceptDataConn())
		return FALSE;

	// check for the appropriate continuation return code
	int nReply = GetFtpReply();
	if (ftpRc_FILE_OK_START_DATA != nReply && ftpRc_DATA_OPEN != nReply)
	{
		// need to close the data connection here, otherwise the 
		// accept if wait until something else comse along
		m_sockListen.Cleanup();
		m_sockData.Cleanup();

		return FALSE;
	}

	// read the listing, wait for the accept to happend first
	// the accept thread sets this event when we can continue
	if (!m_bPassive)
	{
		// wait for the accept to happen first
		// the accept thread sets this event when we can continue
		m_eventAccept.WaitFor();
	}

	// we have to check the data socket value in case a failure occured
	if (! m_sockData)
		// error occured
		return FALSE;

	BOOL bRet = FALSE;
	if (RecvBuffer() >= 0)
	{
		int nResp = GetFtpReply();
		if (ftpRc_CLOSING_DATA == nResp || ftpRc_FILE_REQ_OK == nResp)
		{
			BYTE* pData = m_arDataRecv.GetData();
			LPSTR lpsz = strcrlf((char*)pData, m_dwByteCount);

			// null terminate by nulling the CRLF
			*lpsz = 0;

			// parse the result
			bRet = OnParseListFile((char*)pData, &finddata);
		}
	}

	m_sockData.Cleanup();

	return bRet;
}


void CeFtpClient::SetOption(DWORD dwOption, DWORD dwValue)
{
	switch (dwOption)
	{
	case ftpOpt_ConnectTimeout:		m_dwTimeoutConnect = dwValue;	break;
	case ftpOpt_ConnectRetries:		m_sRetries = (short) dwValue;	break;
	case ftpOpt_CommandSendTimeout:	m_dwTimeoutCmdSend = dwValue;	break;
	case ftpOpt_CommandRecvTimeout:	m_dwTimeoutCmdRecv = dwValue;	break;
	case ftpOpt_DataSendTimeout:	m_dwTimeoutDataSend = dwValue;	break;
	case ftpOpt_DataRecvTimeout:	m_dwTimeoutDataRecv = dwValue;	break;
	}
}

DWORD CeFtpClient::GetOption(DWORD dwOption) const
{
	switch (dwOption)
	{
	case ftpOpt_ConnectTimeout:		return m_dwTimeoutConnect;	// DWORD, milliseconds, default INFINITE
	case ftpOpt_ConnectRetries:		return m_sRetries;			// SHORT, default 5
	case ftpOpt_CommandSendTimeout:	return m_dwTimeoutCmdSend;	// DWORD, milliseconds, default INFINITE
	case ftpOpt_CommandRecvTimeout:	return m_dwTimeoutCmdRecv;	// DWORD, milliseconds, default INFINITE
	case ftpOpt_DataSendTimeout:	return m_dwTimeoutDataSend;	// DWORD, milliseconds, default INFINITE
	case ftpOpt_DataRecvTimeout:	return m_dwTimeoutDataRecv;	// DWORD, milliseconds, default INFINITE
	}

	return 0;
}

CeString& CeFtpClient::GetSystemType() const
{
	return (CeString&) this->m_strSystem;
}


// virtual overrides
void CeFtpClient::OnStatusCallback()
{
}


void CeFtpClient::OnTransferCallback(DWORD dwSent, DWORD dwTotal, LPCTSTR szFile)
{
	::SendMessage(m_hwnd, WM_FTP_CALLBACK, (WPARAM) dwSent, (LPARAM) dwTotal);
}


void CeFtpClient::OnFtpMessage(int nResp, LPCSTR szMsg)
{
	CeStringArray ar;
	int nLen = strlen(szMsg);
	SplitBuffer(szMsg, nLen, ar);

	for (int ii = 0; ii < ar.GetSize(); ii++)
		TRACE1(_T("%s\n"), (LPCTSTR) ar[ii]);
}


inline int chr(char c)
{
	// equivalent to (c - '0')
	return (0x0f & c);
}

class CToken
{
private:
	char* m_psz;
public:
	CToken(char* psz = NULL)
		{ m_psz = psz; }

	char* GetNext()
		{
			for (char* szSave = m_psz; *m_psz; m_psz++)
			{
				if (! iswspace((wchar_t) *m_psz))
					continue;

				// null out all the tokens until the end
				while (*m_psz && iswspace((wchar_t) *m_psz))
					*m_psz++ = 0;

				return szSave;
			}

			return NULL;
		}

	char* SkipNext(int nSkip)
		{
			char* lpsz = GetNext();

			for (int ii = 0; ii < nSkip; ii++)
			{
				if ((lpsz = GetNext()) == NULL)
					break;
			}

			return lpsz;
		}

	char* Remainder()
		{ return m_psz; }
};




// default interpretation, override for specific FTP server output
BOOL CeFtpClient::OnParseListFile(LPSTR szFileDesc, PWIN32_FIND_DATA pFindData)
{
	CeString strSystem = GetSystemType();

	memset(pFindData, 0, sizeof(WIN32_FIND_DATA));

	if (strSystem.IsEmpty())
		//assume we can't figure it out since we don't know the system type
		return FALSE;

	TRACE(_T("%hs\n"), szFileDesc);

/*
	Summary of field use:

    DWORD dwFileAttributes;	// only this one: FILE_ATTRIBUTE_DIRECTORY 
	FILETIME ftCreationTime; 
    FILETIME ftLastAccessTime;
	FILETIME ftLastWriteTime;		// all times should be equal...
    DWORD    nFileSizeHigh = 0;
	DWORD    nFileSizeLow = size;
    DWORD    dwReserved0 = 0;
	DWORD    dwReserved1 = 0; 
    TCHAR    cFileName[ MAX_PATH ];
	TCHAR    cAlternateFileName[ 14 ]; // not used
*/

	// get the first column/token
	CToken tok(szFileDesc);

	char* lpszTok = tok.GetNext();

	if (strSystem == _T("pSOSytem"))
	{
		// parse the pSOSytem way
		// format: permissions, inodes, owner, size, name

		// directory style, in permissions
		if ('d' == lpszTok[0])
			pFindData->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;

		// skip columns 1 and 2
		if (NULL == (lpszTok = tok.SkipNext(2)))
			return FALSE;

		// file size
		pFindData->nFileSizeLow = atoi(lpszTok);

		if (NULL == (lpszTok = tok.Remainder()))
			return FALSE;

		// file name
		// MAX_PATH
		#ifdef _WIN32_WCE
			wce_AsciiToWide(pFindData->cFileName, lpszTok);
		#else
			strcpy(pFindData->cFileName, lpszTok);
		#endif

		return TRUE;
	}

	if (memcmp(lpszTok, "totals", 7) == 0)
		// some UNIX FTPs output a list totals line
		return FALSE;

	if (! _tcsncmp(strSystem, _T("Windows_NT"), 10))
	{
		if ('d' != *lpszTok && '-' != *lpszTok && 's' != *lpszTok)
		{
			SYSTEMTIME st;

			// date
			st.wDay   = chr(lpszTok[3]) * 10 + chr(lpszTok[4]);
			st.wMonth = chr(lpszTok[0]) * 10 + chr(lpszTok[1]);
			st.wYear  = chr(lpszTok[6]) * 10 + chr(lpszTok[7]);
			st.wYear += 1900;
			if (st.wYear < 80)
				st.wYear += 100;

			// time
			if (NULL == (lpszTok = tok.GetNext()))
				return FALSE;
			st.wHour   = chr(lpszTok[0]) * 10 + chr(lpszTok[1]);
			st.wMinute = chr(lpszTok[3]) * 10 + chr(lpszTok[4]);

			if (_T('P') == lpszTok[5] && 12 != st.wHour)
				st.wHour += 12;
			else if (_T('A') == lpszTok[5] && 12 == st.wHour)
				st.wHour = 0;

			// directory or size
			if (NULL == (lpszTok = tok.GetNext()))
				return FALSE;
			if (memcmp(lpszTok, "<DIR>", 6) == 0)
			{
				// directory marker
				pFindData->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
			}
			else
			{
				// numeric size
				pFindData->nFileSizeLow = atoi(lpszTok);
			}

			// file name
			if (NULL == (lpszTok = tok.Remainder()))
				return FALSE;
#ifdef _WIN32_WCE
			wce_AsciiToWide(pFindData->cFileName, lpszTok);
			//pFindData->cFileName[MAX_PATH-1] = 0;
#else
			strcpy(pFindData->cFileName, lpszTok);
#endif

			if (! SystemTimeToFileTime(&st, &pFindData->ftCreationTime))
				return FALSE;
			pFindData->ftLastAccessTime =
				pFindData->ftLastWriteTime =
				pFindData->ftCreationTime;

			return TRUE;
		}
	}

	if ('d' != *lpszTok && '-' != *lpszTok && 's' != *lpszTok)
	{
		// not a unix style format, we don't understand it...
		TRACE0("not a unix style format, we don't understand it...\n");
		return FALSE;
	}

	// default full UNIX type processing, UNIX 'ls -l' formatting
	// premissions, inodes, owner, group, size, month, day#, time/year, name

	// directory style, in permissions
	if (lpszTok[0] == 'd')
		pFindData->dwFileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
	else if (lpszTok[0] == 's')
	{
		// NOTE: symbolic links come out as a single file
		// e.g., "file1 -> file2"
		// symbolic link, parse as if the format is "file1 -> file2"
		// but for now skip symbolic links
		TRACE0("symbolic links, we don't understand it...\n");
		return FALSE;
	}

	// skip to file size
	if (NULL == (lpszTok = tok.SkipNext(3)))
	{
		TRACE0("short field...\n");
		return FALSE;
	}

	// file size
	pFindData->nFileSizeLow = atoi(lpszTok);

	// file date
	// consists of the month, then day, then either the time
	// if the file is in the last six months, or the year, if not
	if (NULL == (lpszTok = tok.GetNext()))
	{
		TRACE0("short field...\n");
		return FALSE;
	}

	SYSTEMTIME st;
	memset(&st, 0, sizeof st);
	CeString strMonth(lpszTok);

	if (strMonth == _T("Jan"))
		st.wMonth = 1;
	else if (strMonth == _T("Feb"))
		st.wMonth = 2;
	else if (strMonth == _T("Mar"))
		st.wMonth = 3;
	else if (strMonth == _T("Apr"))
		st.wMonth = 4;
	else if (strMonth == _T("May"))
		st.wMonth = 5;
	else if (strMonth == _T("Jun"))
		st.wMonth = 6;
	else if (strMonth == _T("Jul"))
		st.wMonth = 7;
	else if (strMonth == _T("Aug"))
		st.wMonth = 8;
	else if (strMonth == _T("Sep"))
		st.wMonth = 9;
	else if (strMonth == _T("Oct"))
		st.wMonth = 10;
	else if (strMonth == _T("Nov"))
		st.wMonth = 11;
	else if (strMonth == _T("Dec"))
		st.wMonth = 12;
	else
	{
		TRACE0("short field...\n");
		return FALSE;
	}

	// month day
	if (NULL == (lpszTok = tok.GetNext()))
	{
		TRACE0("short field...\n");
		return FALSE;
	}
	st.wDay = (WORD) atoi(lpszTok);

	// year or time
	if (NULL == (lpszTok = tok.GetNext()))
	{
		TRACE0("short field...\n");
		return FALSE;
	}
	for (char* lpszSearch = lpszTok; ':' != *lpszSearch && *lpszSearch; lpszSearch++)
		;

	if (*lpszSearch)
	{
		SYSTEMTIME stNow;
		::GetSystemTime(&stNow);

		// time field
		if (1 == (lpszSearch - lpszTok))
			st.wHour = chr(lpszTok[0]);
		else if (2 == (lpszSearch - lpszTok))
			st.wHour = chr(lpszTok[0]) * 10 + chr(lpszTok[1]);

		st.wMinute = chr(lpszSearch[1]) * 10 + chr(lpszSearch[2]);

		// it's in te last 6 months (UNIX thing)
		st.wYear = stNow.wYear;
		if (st.wMonth > stNow.wMonth)
			// not in the future
			st.wYear--;
	}
	else
	{
		// year field
		st.wYear = (WORD) atoi(lpszTok);

		// don't know the time
		st.wHour = 0;
		st.wMinute = 0;
	}

	if (! SystemTimeToFileTime(&st, &pFindData->ftCreationTime))
	{
		TRACE0("bad file time...\n");
		return FALSE;
	}

	pFindData->ftLastAccessTime = pFindData->ftLastWriteTime = pFindData->ftCreationTime;

	// file name
	if (NULL == (lpszTok = tok.Remainder()))
	{
		TRACE0("bas file name...\n");
		return FALSE;
	}

#ifdef _WIN32_WCE
	wce_AsciiToWide(pFindData->cFileName, lpszTok);
	pFindData->cFileName[MAX_PATH-1] = 0;
#else
	strcpy(pFindData->cFileName, lpszTok);
#endif

	return TRUE;
}

BOOL CeFtpClient::AppendFile(LPCTSTR szFileName, LPCTSTR szRemoteName, DWORD dwSize/*= 0*/, FtpTransferType eTransType)
{
	ClearFtpError();

	if (NULL == szFileName || NULL == szRemoteName || ! *szFileName || ! *szRemoteName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	HANDLE hFile;
	hFile = ::CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ,
		NULL, OPEN_EXISTING, 0, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		SetFtpError(ftpErrorClientFile, _T("CreateFile error"));
		return FALSE;
	}

	SetTransferType(eTransType);

	if (! InitDataConn())
		return FALSE;

	SendFtpCmd(APPE);

	if (! StartDataConn())
		return FALSE;

	SendFile(hFile, dwSize);

	m_sockData.Cleanup();

	if (ftpRc_CLOSING_DATA != GetFtpReply())
		return FALSE;

	::CloseHandle(hFile);

	return TRUE;
}

BOOL CeFtpClient::PutFile(LPCTSTR szFileName, LPCTSTR szRemoteName, DWORD dwSize/* = 0*/, FtpTransferType eTransType/*=ftpTtype_BINARY*/)
{
	ClearFtpError();

	if (NULL == szFileName || ! *szFileName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	HANDLE hFile;
	hFile = ::CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ,	NULL, OPEN_EXISTING, 0, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		SetFtpError(ftpErrorClientFile, _T("OpenFile error"));
		return FALSE;
	}

	SetTransferType(eTransType);

	if (! InitDataConn())
		return FALSE;

	if (NULL == szRemoteName || '\0' == *szRemoteName)
		// store as unique file on remote system, in the current
		// directory
		SendFtpCmd(STOU);
	else
		// store as the specified name
		SendFtpCmd(STOR, szRemoteName);

	if (! StartDataConn())
		return FALSE;

	SendFile(hFile, dwSize);

	m_sockData.Cleanup();

	if (ftpRc_CLOSING_DATA != GetFtpReply())
		return FALSE;

	::CloseHandle(hFile);

	return TRUE;
}

BOOL CeFtpClient::GetFile(LPCTSTR szRemoteName, LPCTSTR szFileName,
						 DWORD dwSize/*= 0*/,
						 BOOL bFailIfExists/*=TRUE*/,
						 DWORD dwAttributes/*=FILE_ATTRIBUTE_NORMAL*/,
						 FtpTransferType eTransType/*=ftpTtype_BINARY*/)
{
	if (NULL == szFileName || NULL == szRemoteName ||
		! *szFileName || ! *szRemoteName)
	{
		SetFtpError(ftpErrorInvalidArg, _T("Invalid filename argument"));
		return FALSE;
	}

	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return FALSE;
	}

	HANDLE hFile;
	DWORD dwCreateFlag = (bFailIfExists) ? CREATE_NEW: CREATE_ALWAYS;

	hFile = ::CreateFile(szFileName, GENERIC_WRITE,
		FILE_SHARE_READ, NULL, dwCreateFlag, dwAttributes, NULL);
	if (INVALID_HANDLE_VALUE == hFile)
	{
		SetFtpError(ftpErrorClientFile, _T("CreateFile error"));
		return FALSE;
	}

	SetTransferType(eTransType);

	if (! InitDataConn())
		return FALSE;

	SendFtpCmd(RETR, szRemoteName);

	if (! StartDataConn())
		return FALSE;

	RecvFile(hFile, dwSize);

	m_sockData.Cleanup();

	if (ftpRc_CLOSING_DATA != GetFtpReply())
		return FALSE;

	::CloseHandle(hFile);

	return TRUE;
}


void CeFtpClient::CancelTransfer()
{
	// close the socket hard
	m_sockData.Cleanup();
}


int CeFtpClient::SendUserCommand(LPCTSTR lpszUserCmd)
{
	if (! m_bConnected)
	{
		SetFtpError(ftpErrorNotConnected, g_szNotConnected);
		return -1;
	}

	char szFtpCmd[255];
	int nBytesSent = 0;

	ClearFtpError();

	// Ftp commmand and parameter
	for (int ii = 0; *lpszUserCmd != 0; ii++)
		szFtpCmd[ii] = (char) *lpszUserCmd++;
	szFtpCmd[ii+1] = CR;
	szFtpCmd[ii+2] = LF;
	szFtpCmd[ii+3] = 0;

	int nLen = strlen(szFtpCmd);

	// send all the buffer data with default timeout on each block
	nBytesSent = m_sockCtrl.Write((BYTE*)szFtpCmd, nLen, m_dwTimeoutCmdSend);
	if (nBytesSent <= 0)
	{
		SetSocketError(m_sockCtrl.GetLastError());
		ResetConnection();
		return -1;
	}

	return GetFtpReply();
}


//
// The shutdown function does not close the socket. Any resources
// attached to the socket will not be freed until closesocket is invoked. 
//
// To assure that all data is sent and received on a connected socket before it is closed, an
// application should use shutdown to close connection before calling closesocket.
// For example, to initiate a graceful disconnect: 
//
// 1. Call WSAAsyncSelect to register for FD_CLOSE notification. 
//
// 2. Call shutdown with how=SD_SEND. 
// 3. When FD_CLOSE received, call recv until zero returned, or SOCKET_ERROR. 
//
// 4. Call closesocket. 
//
// Note The shutdown function does not block regardless of the SO_LINGER setting on the socket. 
//
// An application should not rely on being able to re-use a socket after it
// has been shut down. In particular, a Windows Sockets provider is not required
// to support the use of connect on a socket that has been shutdown. 
//

// Future attempt at closing the socket more cleanly.
int CeFtpClient::CleanupSocket()
{
	return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Kenny G

United States United States
No Biography provided

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 5 Oct 2000
Article Copyright 2000 by Kenny G
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid