Click here to Skip to main content
15,893,663 members
Articles / Desktop Programming / MFC

The Ultimate TCP/IP Home Page

Rate me:
Please Sign up or sign in to vote.
4.98/5 (77 votes)
25 Aug 2007CPOL13 min read 2.6M   45.4K   267  
Ultimate TCP-IP is now Open Source
// =================================================================
//  class: CUT_FTPClient
//  File:  ftp_c.cpp
//
//  Purpose:
//  The primary function  of FTP is to transfer files efficiently and 
//  reliably among Hosts and to allow the convenient use of 
//  remote file storage capabilities.
//    The objectives of FTP are 
//      1) to promote sharing of files (computer
//         programs and/or data),
//      2) to encourage indirect or implicit (via
//         programs) use of remote computers, 
//      3) to shield a user from
//         variations in file storage systems among Hosts, and 
//      4) to transfer
//         data reliably and efficiently.
// FOR MORE INFORMATION see RFC 959 and it's refrences
// =================================================================
// Ultimate TCP/IP v4.2
// This software along with its related components, documentation and files ("The Libraries")
// is � 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement").  Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office.  For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.
// =================================================================

#ifdef _WINSOCK_2_0_
    #define _WINSOCKAPI_    /* Prevent inclusion of winsock.h in windows.h   */
                            /* Remove this line if you are using WINSOCK 1.1 */
#endif


#include "stdafx.h"
#include <time.h>


#include "ftp_c.h"

#include "ut_strop.h"

// Suppress warnings for non-safe str fns. Transitional, for VC6 support.
#pragma warning (push)
#pragma warning (disable : 4996)

/***************************************************

    CUT_WSDataClient class implementation

***************************************************/

/***************************************************
ReceiveFileStatus
    This virtual function is called during a 
    ReceiveToFile function.
Params
    bytesReceived - number of bytes received so far
Return
    TRUE - allow the receive to continue
    FALSE - abort the receive
****************************************************/
BOOL CUT_WSDataClient::ReceiveFileStatus(long bytesReceived){
    return ptrFTPClient->ReceiveFileStatus(bytesReceived);
}
/***************************************************
SendFileStatus
    This virtual function is called during a 
    SendFile function.
Params
    bytesSent - number of bytes sent so far
Return
    TRUE - allow the send to continue
    FALSE - abort the send
****************************************************/
BOOL CUT_WSDataClient::SendFileStatus(long bytesSent){
    return ptrFTPClient->SendFileStatus(bytesSent);
}

/***************************************************

    CUT_FTPClient class implementation

***************************************************/

/***************************************
Constructor
****************************************/
CUT_FTPClient::CUT_FTPClient() :
    m_nConnected(FALSE),                // Connection is not established yet
    m_nTransferType(1),                 // Set default transfer type -     1 - :image (binary)
    m_nTransferMode(0),                 // Set default transfer mode - stream
    m_nTransferStructure(0),            // Set default transfer structure - file
    m_DirInfo(NULL),                    // Initialize DirInfo pointer
    m_nDirInfoCount(0),                 // Number of DirInfo items - 0
    m_nFirewallMode(FALSE),             // No firewall mode by default
    m_nControlPort(21),                 // Set default control port to 21
    m_nConnectTimeout(5)                // Set default connect time out to 5 sec.
{

    // initialize pointer 
    m_wsData.ptrFTPClient = this;

    //set up the defaults
    m_szResponse[0]         = NULL;     // Last response from the server
    m_nDataPort              =   10000 + GetTickCount()%20000;
    if(m_nDataPort > 32000 || m_nDataPort < 0)
        m_nDataPort = 10000;
}
/***************************************
Destructor
****************************************/
CUT_FTPClient::~CUT_FTPClient(){

    // clear response list
    m_listResponse.ClearList();

    //destory any allocated memory
    ClearDirInfo();
    Close();
}
/***************************************
FTPConnect
    Connect to the given FTP site, the default
    user name is 'anonymous' and password is
    'anonymous@anonymous.com'
    DEFAULT ACCOUNT IS ""
Params
    hostname - the name of the FTP site to
        connect to
    userName - string containing the user name
    password - string containing the password
    acct - string containing the account
Return 
    UTE_SUCCESS         - success
    UTE_BAD_HOSTNAME    - bad hostname format
    UTE_CONNECT_FAILED  - connection failed
    UTE_NO_RESPONSE     - no response 
    UTE_INVALID_RESPONSE- negataive response
    UTE_USER_NA         - USER command not accepted
    UTE_PASS_NA         - PASS command not accepted
    UTE_ACCT_NA         - ACCT command not accepted
****************************************/
#if defined _UNICODE
int CUT_FTPClient::FTPConnect(LPCWSTR hostname,LPCWSTR userName,LPCWSTR password,LPCWSTR account){
	return FTPConnect(AC(hostname), AC(userName), AC(password), AC(account));}
#endif
int CUT_FTPClient::FTPConnect(LPCSTR hostname,LPCSTR userName,LPCSTR password,LPCSTR account){

    int  rt, error;

    //close any existing connection
    Close();

    // clear response list
    m_listResponse.ClearList();
    m_szResponse [0]= NULL;

    //connect
    if((error = Connect(m_nControlPort, hostname, m_nConnectTimeout)) != UTE_SUCCESS)
        return OnError(error);



    //send the user name
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"USER %s\r\n",userName);
    Send(m_szBuf);

    //check for a return of 2?? or 3??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);            //no response
    if(rt < 200 || rt > 399)
        return OnError(UTE_USER_NA);                //negative response

    //if 3?? then enter in PASS
    if(rt >=300 && rt <= 399){

        //send the password
        _snprintf(m_szBuf,sizeof(m_szBuf)-1,"PASS %s\r\n",password);
        Send(m_szBuf);

        //check for a return of 2?? or 3??
        rt = GetResponseCode(this);
        if(rt == 0)
            return OnError(UTE_NO_RESPONSE);        //no response
        if(rt < 200 || rt > 399)
            return OnError(UTE_PASS_NA);            //negative response

        //if 3?? then enter in ACCT
        if(rt >=300 && rt <= 399){

            //send the account
            _snprintf(m_szBuf,sizeof(m_szBuf)-1,"ACCT %s\r\n",account);
            Send(m_szBuf);

            //check for a return of 2?? or 3??
            rt = GetResponseCode(this);
            if(rt == 0)
                return OnError(UTE_NO_RESPONSE);    //no response
            if(rt < 200 || rt > 399)
                return OnError(UTE_ACCT_NA);        //negative response
        }
    }

    //set transfer type and mode
    SetTransferType(0);
    SetTransferMode(0);
    SetTransferStructure(0);

    return OnError(UTE_SUCCESS);
}
/***************************************
Close
    Closes an open connection
Params
    none
Return
    UTE_SUCCESS - success
****************************************/
int CUT_FTPClient::Close() {

    Send("Quit\r\n");

    CloseConnection();
    m_wsData.CloseConnection();

    m_nConnected = FALSE;

    return OnError(UTE_SUCCESS);
}
/***************************************
ReceiveFile
    Retrieves the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
Params
    sourceFile  - name of the file to receive
    destFile    - name of the file to save 
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
****************************************/
#if defined _UNICODE
int CUT_FTPClient::ReceiveFile(LPCWSTR sourceFile, LPCTSTR destFile){
	return ReceiveFile(AC(sourceFile), destFile);}
#endif
int CUT_FTPClient::ReceiveFile(LPCSTR sourceFile, LPCTSTR destFile){
	CUT_FileDataSource ds(destFile);
    return ReceiveFile(ds, sourceFile);
}

/***************************************
ReceiveFile
    Retrieves the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
Params
    sourceFile  - name of the file to receive
    destFile    - name of the file to save 
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
****************************************/
int CUT_FTPClient::ReceiveFile(CUT_DataSource & dest, LPCSTR sourceFile) 
{
    int rt,loop,len;
    char addr[32]; 



    if ( m_nFirewallMode )
        return ReceiveFilePASV(dest, sourceFile);

    //open up a data port, if the one requested is busy then
    //increment to the next port, try 128 times then fail
    for(loop=0; loop < 128; loop++) {
        if( m_wsData.WaitForConnect((unsigned short)m_nDataPort) == UTE_SUCCESS )
            break;
        m_nDataPort++;
        }

    if(loop == 128)
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    //get the host address
    GetHostAddress(addr,sizeof(addr));

    //create the port set up string
    len = (int)strlen(addr);
    for(loop=0;loop<len;loop++){
        if(addr[loop]=='.')
            addr[loop] = ',';
        }

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"PORT %s,%d,%d\r\n",addr,HIBYTE(m_nDataPort),LOBYTE(m_nDataPort));
    //send the PORT command
    Send(m_szBuf);

    //setup the next port number
    m_nDataPort++;
    if(m_nDataPort > 32000 || m_nDataPort < 0)
        m_nDataPort = 10000;

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_PORT_FAILED);
        }

    //send the RETR command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RETR %s\r\n",sourceFile);
    Send(m_szBuf);

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_RETR_FAILED);
        }

    //wait for a connection on the data port
    if(m_wsData.WaitForAccept(5)!= UTE_SUCCESS){
        //close the connection down
        m_wsData.CloseConnection();
        ClearReceiveBuffer();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }

    
    //accept a connect on the data port
    m_wsData.AcceptConnection();

    //retrieve the file
    rt = m_wsData.Receive(dest, UTM_OM_WRITING,5);

    //close the connection down
    m_wsData.CloseConnection();

    if(rt != UTE_SUCCESS) {
        GetResponseCode(this);
        return rt;
    }

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}
/***************************************
ResumeReceiveFile
    Retrieves the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
Params
    sourceFile  - name of the file to receive
    destFile    - name of the file to save 
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
****************************************/
#if defined _UNICODE
int CUT_FTPClient::ResumeReceiveFile(LPCWSTR sourceFile, LPCTSTR destFile){
	return ResumeReceiveFile(AC(sourceFile), destFile);}
#endif
int CUT_FTPClient::ResumeReceiveFile(LPCSTR sourceFile, LPCTSTR destFile){
	CUT_FileDataSource ds(destFile);
    return ResumeReceiveFile(ds, sourceFile);
}
/***************************************
ResumeReceiveFile
    Resumes a file receive call from the point where we have received it
    The file we have already received will be searched first if it exists then the 
    file pointer is advanced to the end of the file and the current available length 
    is sent to the server to inform it at what point to start tranfsering.
    If the file does not exist it is created then.
Params
    dest    - name of the file to save 
    sourceFile  - name of the file to receive
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
****************************************/
int CUT_FTPClient::ResumeReceiveFile(CUT_DataSource & dest, LPCSTR sourceFile) 
{
    int rt,loop,len;
    char addr[32];
    OpenMsgType fileType = UTM_OM_WRITING;  // by default we will write a file unless it exist 
                                            // then we will append to it

    if ( m_nFirewallMode ) 
        return ResumeReceiveFilePASV(dest, sourceFile); 
    //open up a data port, if the one requested is busy then
    //increment to the next port, try 128 times then fail
    for(loop=0; loop < 128; loop++) {
        if( m_wsData.WaitForConnect((unsigned short)m_nDataPort) == UTE_SUCCESS )
            break;
        m_nDataPort++;
        }

    if(loop == 128)
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    //get the host address
    GetHostAddress(addr,sizeof(addr));

    //create the port set up string
    len = (int)strlen(addr);
    for(loop=0;loop<len;loop++){
        if(addr[loop]=='.')
            addr[loop] = ',';
        }

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"PORT %s,%d,%d\r\n",addr,HIBYTE(m_nDataPort),LOBYTE(m_nDataPort));
    //send the PORT command
    Send(m_szBuf);

    //setup the next port number
    m_nDataPort++;
    if(m_nDataPort > 32000 || m_nDataPort < 0)
        m_nDataPort = 10000;

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_PORT_FAILED);
        }

    rt = dest.Open (UTM_OM_READING);

    if (rt == UTE_SUCCESS)
    {

    // if the file exist then we will send the REST command  with the size of the file we have 
    // otherwise we just call retrieve as we do normally
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"REST %d\r\n",dest.Seek (0,SEEK_END));
    dest.Close ();
    Send(m_szBuf);
    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if( rt != 350){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_REST_COMMAND_NOT_SUPPORTED);
        }
    fileType = UTM_OM_APPEND ; // appending 
    }

    //send the RETR command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RETR %s\r\n",sourceFile);
    Send(m_szBuf);

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_RETR_FAILED);
        }

    //wait for a connection on the data port
    if(m_wsData.WaitForAccept(5)!= UTE_SUCCESS){
        //close the connection down
        m_wsData.CloseConnection();
        ClearReceiveBuffer();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }

    
    //accept a connect on the data port
    m_wsData.AcceptConnection();

    //retrieve the file based on the fileType  UTM_OM_APPEND if it does exist UTM_OM_WRITING if we need to creat it
    rt = m_wsData.Receive(dest, fileType);

    //close the connection down
    m_wsData.CloseConnection();

    if(rt != UTE_SUCCESS)
        return rt;

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}

/***************************************
    FIXME (This function Will never be called ) ??

ReceiveFilePASV
    Retrieves the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
    This routine duplicates the ReceiveFile()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    sourceFile - name of the file to receive
    destFile - name of the file to save 
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_CONNECT_TIMEOUT             - connection timeout
****************************************/
int CUT_FTPClient::ReceiveFilePASV( LPCSTR sourceFile,LPCTSTR destFile ){
	CUT_FileDataSource ds(destFile);
    return ReceiveFilePASV(ds, sourceFile);
}
/***************************************
ResumeReceiveFilePASV
    Resumes retrieving the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
    This routine duplicates the ReceiveFile()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    dest        - data source to receive to
    sourceFile  - file to get
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_CONNECT_TIMEOUT             - connection timeout
****************************************/
int CUT_FTPClient::ResumeReceiveFilePASV(CUT_DataSource & dest, LPCSTR sourceFile) {

    UINT    loop;
    int     rt, error;
    char    responseBuf[100];
    int     port;
    char    ipAddress[20];
    char    *token;
    OpenMsgType fileType = UTM_OM_WRITING;  // by default we will write a file unless it exist 
                                            // then we will append to it



    //send the port command
    Send("PASV\r\n");
    
    // we need to get the full IP address and port number from the 
    // PASV return line, so that we can originate the data connection.

    //check for a return of 2??, which indicates success
    rt = GetResponseCode( this, responseBuf, sizeof(responseBuf) );
    if(rt < 200 || rt >299)
        return OnError(UTE_PORT_FAILED);
    

    // find the first '(' in the response and then parse out
    // the address supplied by the server
    loop = 0;
    while ( loop < strlen(responseBuf) ){
        if ( responseBuf[loop] == '(' )
            break;
        loop++;
        }

    if ( loop == strlen(responseBuf) ) // we hit the end of the buffer
        return OnError(UTE_RETR_FAILED);

    // token out the ipaddress and port string
    token = strtok( &responseBuf[loop], "()" );

    // get the four portions of the ip address
    strcpy(ipAddress, strtok( token, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));

    // get the two portions that make up the port number
    port = atoi( strtok(NULL, ",)\r\n") ) * 256;
    port += atoi( strtok(NULL, ",)\r\n") );

    // test the results to ensure everything is OK.
    if ( !IsIPAddress(ipAddress) )
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    if ( port<=0 || port > 65535 )
        return OnError(UTE_CONNECT_TERMINATED);

    // Check for abortion flag
    if(IsAborted()) {                               
        m_wsData.CloseConnection();
        return OnError(UTE_ABORTED);
        }

    rt = dest.Open (UTM_OM_READING);

    if (rt == UTE_SUCCESS)
    {

    // if the file exist then we will send the REST command  with the size of the file we have 
    // otherwise we just call retrieve as we do normally
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"REST %d\r\n",dest.Seek (0,SEEK_END));
    dest.Close ();
    Send(m_szBuf);
    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if( rt != 350){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_REST_COMMAND_NOT_SUPPORTED);
        }
    fileType = UTM_OM_APPEND ; // appending 
    }
   
    //send the RETR command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RETR %s\r\n",sourceFile);
    Send(m_szBuf);

    // connect to the server supplied port to establish the 
    // data connection.
    // connect using a timeout
    if((error = m_wsData.Connect(port, ipAddress, m_nConnectTimeout)) != UTE_SUCCESS) {
        m_wsData.CloseConnection();
        return OnError(error);
        }

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        m_wsData.CloseConnection();
        return OnError(UTE_RETR_FAILED);
        }   

     //retrieve the file based on the fileType  UTM_OM_APPEND if it does exist UTM_OM_WRITING if we need to creat it
    rt = m_wsData.Receive(dest, fileType);

    //close the connection down
    m_wsData.CloseConnection();

    if(rt != UTE_SUCCESS) {
        GetResponseCode(this);
        return rt;
    }

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}
/***************************************
ReceiveFilePASV
    Retrieves the specified file from the
    currently connected FTP site. The file
    is then saved to the specified destination
    file.
    This routine duplicates the ReceiveFile()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    dest        - data source to receive to
    sourceFile  - file to get
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_CONNECT_TIMEOUT             - connection timeout
****************************************/
int CUT_FTPClient::ReceiveFilePASV(CUT_DataSource & dest, LPCSTR sourceFile) {

    UINT    loop;
    int     rt, error;
    char    responseBuf[100];
    int     port;
    char    ipAddress[20];
    char    *token;


    //send the port command
    Send("PASV\r\n");
    
    // we need to get the full IP address and port number from the 
    // PASV return line, so that we can originate the data connection.

    //check for a return of 2??, which indicates success
    rt = GetResponseCode( this, responseBuf, sizeof(responseBuf) );
    if(rt < 200 || rt >299)
        return OnError(UTE_PORT_FAILED);
    

    // find the first '(' in the response and then parse out
    // the address supplied by the server
    loop = 0;
    while ( loop < strlen(responseBuf) ){
        if ( responseBuf[loop] == '(' )
            break;
        loop++;
        }

    if ( loop == strlen(responseBuf) ) // we hit the end of the buffer
        return OnError(UTE_RETR_FAILED);

    // token out the ipaddress and port string
    token = strtok( &responseBuf[loop], "()" );

    // get the four portions of the ip address
    strcpy(ipAddress, strtok( token, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));

    // get the two portions that make up the port number
    port = atoi( strtok(NULL, ",)\r\n") ) * 256;
    port += atoi( strtok(NULL, ",)\r\n") );

    // test the results to ensure everything is OK.
    if ( !IsIPAddress(ipAddress) )
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    if ( port<=0 || port > 65535 )
        return OnError(UTE_CONNECT_TERMINATED);

    // Check for abortion flag
    if(IsAborted()) {                               
        m_wsData.CloseConnection();
        return OnError(UTE_ABORTED);
        }
   
    //send the RETR command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RETR %s\r\n",sourceFile);
    Send(m_szBuf);

    // connect to the server supplied port to establish the 
    // data connection.
    // connect using a timeout
    if((error = m_wsData.Connect(port, ipAddress, m_nConnectTimeout)) != UTE_SUCCESS) {
        m_wsData.CloseConnection();
        return OnError(error);
        }

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        m_wsData.CloseConnection();
        return OnError(UTE_RETR_FAILED);
        }   

    //retrieve the file
    rt = m_wsData.Receive(dest, UTM_OM_WRITING);

    //close the connection down
    m_wsData.CloseConnection();

    if(rt != UTE_SUCCESS)
        return rt;

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}

/***************************************
SendFile
    Sends the specified local file to
    the given destination on the currently
    connected FTP site.
Params
    sourceFile - name of file to send
    destFile - name of FTP site destination
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_STOR_FAILED                 - STOR command failed
****************************************/
#if defined _UNICODE
int CUT_FTPClient::SendFile(LPCTSTR sourceFile,LPCWSTR destFile){
	return SendFile(sourceFile, AC(destFile));}
#endif
int CUT_FTPClient::SendFile(LPCTSTR sourceFile,LPCSTR destFile){
	CUT_FileDataSource ds(sourceFile);
    return SendFile(ds, destFile);
}

/***************************************
SendFile
    Sends the specified local file to
    the given destination on the currently
    connected FTP site.
Params
    sourceFile  - name of the data source to send
    destFile    - name of FTP site destination
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_STOR_FAILED                 - STOR command failed
****************************************/
int CUT_FTPClient::SendFile(CUT_DataSource & source, LPCSTR destFile) 
{
    int     rt, loop, len;
    char    addr[32];

    if (m_nFirewallMode)
        return SendFilePASV(source, destFile);
    
    //open up a data port, if the one requested is busy then
    //increment to the next port, try 128 times then fail
    for(loop=0;loop<128;loop++){
		if(m_wsData.WaitForConnect((unsigned short)m_nDataPort)==UTE_SUCCESS)
            break;
        m_nDataPort++;
        }

    if(loop==128)
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    //get the host address
    GetHostAddress(addr,sizeof(addr));

    //create the port set up string
    len = (int)strlen(addr);
    for(loop=0;loop<len;loop++){
        if(addr[loop]=='.')
            addr[loop] = ',';
        }

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"PORT %s,%d,%d\r\n",addr,HIBYTE(m_nDataPort),LOBYTE(m_nDataPort));
    //send the port command
    Send(m_szBuf);

    //setup the next port number
    m_nDataPort++;
    if(m_nDataPort > 32000 || m_nDataPort < 0)
        m_nDataPort = 10000;

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_PORT_FAILED);
        }

    // Check for abortion flag
    if(IsAborted()) {                               
        m_wsData.CloseConnection();
        return OnError(UTE_ABORTED);
        }

    //send the store command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"STOR %s\r\n",destFile);
    Send(m_szBuf);

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_STOR_FAILED);
        }

    //wait for a connection on the data port
    if(m_wsData.WaitForAccept(5)!= UTE_SUCCESS){
        //close the connection down
        m_wsData.CloseConnection();
        ClearReceiveBuffer();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }

    //accept a connect on the data port
    m_wsData.AcceptConnection();

    //retrieve the file
    rt = m_wsData.Send(source);

    //close the connection down
    m_wsData.CloseConnection();

    if(rt != UTE_SUCCESS) {
        GetResponseCode(this);
        return rt;
    }

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}

/***************************************
SendFilePASV
    Sends the specified local file to
    the given destination on the currently
    connected FTP site.
    This routine duplicates the SendFilePASV()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    sourceFile  - name of file to send
    destFile    - name of FTP site destination
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_STOR_FAILED                 - STOR command failed
    UTE_CONNECT_TIMEOUT             - connection time out
****************************************/
int CUT_FTPClient::SendFilePASV(LPCTSTR sourceFile,LPCSTR destFile){
	CUT_FileDataSource ds(sourceFile);
    return SendFilePASV(ds, destFile);
}

/***************************************
SendFilePASV
    Sends the specified local file to
    the given destination on the currently
    connected FTP site.
    This routine duplicates the SendFilePASV()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    source      - data source to send
    destFile    - name of FTP site destination
Return
    UTE_SUCCESS                     - success
    UTE_SVR_DATA_CONNECT_FAILED     - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_RETR_FAILED                 - RETR command failed
    UTE_CONNECT_TERMINATED          - Connection terminated before completion
    UTE_STOR_FAILED                 - STOR command failed
    UTE_CONNECT_TIMEOUT             - connection time out
****************************************/
int CUT_FTPClient::SendFilePASV(CUT_DataSource & source, LPCSTR destFile) {

    UINT    loop;
    int     error, rt, port;
    char    *token;
    char    ipAddress[20];
    char    responseBuf[100];


    //send the port command
    Send("PASV\r\n");
    
    // we need to get the full IP address and port number from the 
    // PASV return line, so that we can originate the data connection.

    //check for a return of 2??, which indicates success
    rt = GetResponseCode( this, responseBuf, sizeof(responseBuf) );
    if(rt < 200 || rt >299)
        return OnError(UTE_PORT_FAILED);
    
    // find the first '(' in the response and then parse out
    // the address supplied by the server
    loop = 0;
    while ( loop < strlen(responseBuf) ){
        if ( responseBuf[loop] == '(' )
            break;
        loop++;
        }

    if ( loop == strlen(responseBuf) ) // we hit the end of the buffer
        return OnError(UTE_STOR_FAILED);

    // token out the ipaddress and port string
    token = strtok( &responseBuf[loop], "()" );

    // get the four portions of the ip address
    strcpy(ipAddress, strtok( token, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));

    // get the two portions that make up the port number
    port = atoi( strtok(NULL, ",)\r\n") ) * 256;
    port += atoi( strtok(NULL, ",)\r\n") );

    // test the results to ensure everything is OK.
    if ( !IsIPAddress(ipAddress) )
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    if ( port<=0 || port > 65535 )
        return OnError(UTE_CONNECT_TERMINATED);

    // Check for abortion flag
    if(IsAborted()) {                               
        return OnError(UTE_ABORTED);
        }

    //send the store command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"STOR %s\r\n",destFile);
    Send(m_szBuf);

    // connect to the server supplied port to establish the 
    // data connection.
    // connect using a timeout
    if((error = m_wsData.Connect(port, ipAddress, m_nConnectTimeout)) != UTE_SUCCESS) {
        m_wsData.CloseConnection();
        return OnError(error);
        }

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300){
        m_wsData.CloseConnection();
        return OnError(UTE_STOR_FAILED);
        }

    //retrieve the file
    rt = m_wsData.Send(source);

    //close the connection down
    m_wsData.CloseConnection();

    // If there is an error sending data
    if(rt != UTE_SUCCESS) {
        GetResponseCode(this);
        return rt;
    }

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >=300)
        return OnError(UTE_CONNECT_TERMINATED);
    else
        return OnError(UTE_SUCCESS);
}


/***************************************
DeleteFile
    Deletes  the specified file off of the
    currently connect FTP server.
Params
    file - name of file to delete
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
#if defined _UNICODE
int CUT_FTPClient::DeleteFile(LPCWSTR file){
	return DeleteFile(AC(file));}
#endif
int CUT_FTPClient::DeleteFile(LPCSTR file){

    int     rt;

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"DELE %s\r\n",file);
    Send(m_szBuf);

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
RenameFile
    Renames the given file on the currently
    connected FTP site.
Params
    sourceFile - name of file on the FTP server
    destFile - new name to give the file
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_RNFR_NA             - RNFR command not accepted
    UTE_RNTO_NA             - RNTO command not accepted
****************************************/
#if defined _UNICODE
int CUT_FTPClient::RenameFile(LPCWSTR sourceFile,LPCWSTR destFile){
	return RenameFile(AC(sourceFile), AC(destFile));}
#endif
int CUT_FTPClient::RenameFile(LPCSTR sourceFile,LPCSTR destFile){

    int     rt;

    // send rename from command
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RNFR %s\r\n",sourceFile);
    Send(m_szBuf);

    //check for a return of 3??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    
    else if(rt >= 300 && rt <= 399) {
        //send rename to command
        _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RNTO %s\r\n",destFile);
        Send(m_szBuf);

        //check for a return of 2??
        rt = GetResponseCode(this);
        if(rt == 0)
            return OnError(UTE_NO_RESPONSE);   //no response
        else if(rt >=200 && rt <=299)
            return OnError(UTE_SUCCESS);
        else
            return OnError(UTE_RNTO_NA);
    }
    else
        return OnError(UTE_RNFR_NA);
}
/***************************************
GetCurDir
    Returns the name of the current
    directory on the currently connected
    FTP server.
Params
    directory - buffer to hold the dir. name
    maxlen - length of the buffer
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
#if defined _UNICODE
int CUT_FTPClient::GetCurDir(LPWSTR directory,int maxlen){
	char * directoryA = (char*) alloca(maxlen);
	*directoryA = '\0';
	int result = GetCurDir(directoryA, maxlen);
	if(result == UTE_SUCCESS) {
		CUT_Str::cvtcpy(directory, maxlen, directoryA);
	}
	return result;}
#endif
int CUT_FTPClient::GetCurDir(LPSTR directory,int maxlen){

    int rt;

    Send("PWD\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this,directory,maxlen);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);            //no response
    if(rt < 200 || rt > 299)
        return OnError(UTE_SVR_REQUEST_DENIED);     //negative response

    // now lets parse the directory from the actual response
    char buf1[MAX_PATH+1];
    char buf2[MAX_PATH+1];
    strcpy(buf1,directory);
    CUT_StrMethods::ParseString(buf1,"\"",0,buf2,sizeof(buf2));
    CUT_StrMethods::ParseString(buf1,"\"",0,buf2,sizeof(buf2));
    strcpy(directory,buf2);//,&buf1[strlen(buf2)]);

    return OnError(UTE_SUCCESS);
}
/***************************************
ChDir
    Changes the current directory of the 
    currently connected FTP server
Params
    directory - name of directory to move to
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
#if defined _UNICODE
int CUT_FTPClient::ChDir(LPCWSTR directory){
	return ChDir(AC(directory));}
#endif
int CUT_FTPClient::ChDir(LPCSTR directory){

    int     rt;

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"CWD %s\r\n",directory); // FEB 1999 added /
    Send(m_szBuf);

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
CdUP
    Moves up one directory level on the 
    currently connected FTP server
Params
    none
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
int CUT_FTPClient::CdUp(){

    int     rt;

    Send("CDUP\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=500 && rt <=599)
        return OnError(UTE_SVR_REQUEST_DENIED);
    else if(rt >=400 && rt <=499)
        return OnError(UTE_SVR_REQUEST_DENIED);
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
MkDir
    Creates a new directory on the 
    currently connected FTP server
Params
    directory - name of directory to create
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
#if defined _UNICODE
int CUT_FTPClient::MkDir(LPCWSTR directory){
	return MkDir(AC(directory));}
#endif
int CUT_FTPClient::MkDir(LPCSTR directory){

    int     rt;

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"MKD %s\r\n",directory);
    Send(m_szBuf);

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);
    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
RmDir
    Removes a directory on the 
    currently connected FTP server
Params
    directory - name of directory to remove
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
#if defined _UNICODE
int CUT_FTPClient::RmDir(LPCWSTR directory){
	return RmDir(AC(directory));}
#endif
int CUT_FTPClient::RmDir(LPCSTR directory){

    int     rt;

    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"RMD %s\r\n",directory);
    Send(m_szBuf);

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);
    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
NoOp
    Performs a No-op operation. This is 
    usually used to check and see if the
    connection is still up.
Params
    none
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server
****************************************/
int CUT_FTPClient::NoOp(){

    int     rt;

    Send("NOOP\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    else if(rt >=200 && rt <=299)
        return OnError(UTE_SUCCESS);
    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
SetTransferType
    Sets the data transfer type. The data 
    representation type used for data transfer and
    storage.  Type implies certain transformations 
    between the time of data storage and data transfer. 
    The representation types defined in FTP are described 
    in the Section on Establishing Data Connections. of RFC 959
    NOTE that we only supporting asscii and image types.
PARAM
    type
    0 - :ascii 
        "
        This is the default type and must be accepted by all FTP
         implementations.  It is intended primarily for the transfer of
         text files, except when both Hosts would find the EBCDIC type
         more convenient.
         The sender converts the data from his internal character
         representation to the standard 8-bit NVT-ASCII representation
         (see the TELNET specification).  The receiver will convert the
         data from the standard form to his own internal form.
         In accordance with the NVT standard, the <CRLF> sequence should
         be used, where necessary, to denote the end of a line of text.
         (See the discussion of file structure at the end of the Section
         on Data Representation and Storage).
         Using the standard NVT-ASCII representation means that data
         must be interpreted as 8-bit bytes." RFC 959

    1 - :image 
        " The data are sent as contiguous bits which, for transfer, are
         packed into transfer bytes of the size specified in the BYTE
         command.  The receiving site must store the data as contiguous
         bits.  The structure of the storage system might necessitate
         the padding of the file (or of each record, for a
         record-structured file) to some convenient boundary (byte, word
         or block).  This padding, which must be all zeroes, may occur
         only at the end of the file (or at the end of each record) and
         there must be a way of identifying the padding bits so that
         they may be stripped off if the file is retrieved.  The padding
         transformation should be well publicized to enable a user to
         process a file at the storage site.
         Image type is intended for the efficient storage and retrieval
         of files and for the transfer of binary data.  It is
         recommended that this type be accepted by all FTP
         implementations. " RFC 959
Return
    UTE_SUCCESS                 - success
    UTE_NO_RESPONSE             - no response
    UTE_SVR_REQUEST_DENIED      - request denied by server
    UTE_PARAMETER_INVALID_VALUE - invalid type
****************************************/
int CUT_FTPClient::SetTransferType(int type){

    int     rt;

    //check for a valid range
    if(type <0 || type >1)
        return OnError(UTE_PARAMETER_INVALID_VALUE);

    //send the type info
    if(type ==0)        //ascii
        Send("TYPE A\r\n");
    else if(type ==1)       //image
        Send("TYPE I\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this);
    
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response

    else if(rt >=200 && rt <=299) {
        m_nTransferType = type;
        return OnError(UTE_SUCCESS);
        }
    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
GetTransferType
    Gets the data transfer type. The data 
    representation type used for data transfer and
    storage.  Type implies certain transformations 
    between the time of data storage and data transfer. 
    The representation types defined in FTP are described 
    in the Section on Establishing Data Connections. of RFC 959
    NOTE that we only supporting asscii and image types.
PARAM
    none
Return
    type
    0 - :ascii 
        "
        This is the default type and must be accepted by all FTP
         implementations.  It is intended primarily for the transfer of
         text files, except when both Hosts would find the EBCDIC type
         more convenient.
         The sender converts the data from his internal character
         representation to the standard 8-bit NVT-ASCII representation
         (see the TELNET specification).  The receiver will convert the
         data from the standard form to his own internal form.
         In accordance with the NVT standard, the <CRLF> sequence should
         be used, where necessary, to denote the end of a line of text.
         (See the discussion of file structure at the end of the Section
         on Data Representation and Storage).
         Using the standard NVT-ASCII representation means that data
         must be interpreted as 8-bit bytes." RFC 959

    1 - :image 
        " The data are sent as contiguous bits which, for transfer, are
         packed into transfer bytes of the size specified in the BYTE
         command.  The receiving site must store the data as contiguous
         bits.  The structure of the storage system might necessitate
         the padding of the file (or of each record, for a
         record-structured file) to some convenient boundary (byte, word
         or block).  This padding, which must be all zeroes, may occur
         only at the end of the file (or at the end of each record) and
         there must be a way of identifying the padding bits so that
         they may be stripped off if the file is retrieved.  The padding
         transformation should be well publicized to enable a user to
         process a file at the storage site.
         Image type is intended for the efficient storage and retrieval
         of files and for the transfer of binary data.  It is
         recommended that this type be accepted by all FTP
         implementations. " RFC 959
****************************************/
int CUT_FTPClient::GetTransferType() const
{
    return m_nTransferType;
}
/***************************************
SetTransferMode
    Sets the data transfer mode in which data is 
    to be transferred via the data  connection.
    The mode defines the data format during transfer
    including EOR and EOF. 
    The transfer modes defined in FTP are as described
    below in the param section
Params
    type 
     0   -:stream
        "The data is transmitted as a stream of bytes.  There is no
         restriction on the representation type used; record structures
         are allowed, in which case the transfer byte size must be at
         least 3 bits!
         In a record structured file EOR and EOF will each be indicated
         by a two-byte control code of whatever byte size is used for
         the transfer.  The first byte of the control code will be all
         ones, the escape character.  The second byte will have the low
         order bit on and zeroes elsewhere for EOR and the second low
         order bit on for EOF; that is, the byte will have value 1 for
         EOR and value 2 for EOF.  EOR and EOF may be indicated together
         on the last byte transmitted by turning both low order bits on,
         i.e., the value 3.  If a byte of all ones was intended to be
         sent as data, it should be repeated in the second byte of the
         control code.
         If the file does not have record structure, the EOF is
         indicated by the sending Host closing the data connection and
         all bytes are data bytes."
    1  -:block 
        " The file is transmitted as a series of data blocks preceded by
         one or more header bytes.  The header bytes contain a count
         field, and descriptor code.  The count field indicates the
         total length of the data block in bytes, thus marking the
         beginning of the next data block (there are no filler bits).
         The descriptor code defines:  last block in the file (EOF) last
         block in the record (EOR), restart marker (see the Section on
         Error Recovery and Restart) or suspect data (i.e., the data being 
         transferred is suspected of errors and is not reliable).
         This last code is NOT intended for error control within FTP.
         It is motivated by the desire of sites exchanging certain types
         of data (e.g., seismic or weather data) to send and receive all
         the data despite local errors (such as "magnetic tape read
         errors"), but to indicate in the transmission that certain
         portions are suspect).  Record structures are allowed in this
         mode, and any representation type may be used.  There is no
         restriction on the transfer byte size. "
    2  -:compressed
        "The file is transmitted as series of bytes of the size
         specified by the BYTE command.  There are three kinds of
         information to be sent:  regular data, sent in a byte string;
         compressed data, consisting of replications or filler; and
         control information, sent in a two-byte escape sequence.  If
         the byte size is B bits and n>0 bytes of regular data are sent,
         these n bytes are preceded by a byte with the left-most bit set
         to 0 and the right-most B-1 bits containing the number n."
         FOR MORE INFORMATION SEE RFC 959
Return
    UTE_SUCCESS                 - success
    UTE_NO_RESPONSE             - no response
    UTE_SVR_REQUEST_DENIED      - request denied by server
    UTE_PARAMETER_INVALID_VALUE - invalid type
****************************************/
int CUT_FTPClient::SetTransferMode(int mode){

    int     rt;

    //check for a valid range
    if(mode <0 || mode >3)
        return OnError(UTE_PARAMETER_INVALID_VALUE);

    //send the mode info
    if(mode ==0)        //stream
        Send("MODE S\r\n");
    else if(mode ==1)       //block
        Send("MODE B\r\n");
    else if(mode ==2)       //compressed
        Send("MODE C\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response

    else if(rt >=200 && rt <=299) {
        m_nTransferMode  = mode;
        return OnError(UTE_SUCCESS);
        }

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
GetTransferMode
    Gets the data transfer mode in which data is 
    to be transferred via the data  connection.
    The mode defines the data format during transfer
    including EOR and EOF. 
    The transfer modes defined in FTP are as described
    below in the param section
Params
    none
Return
    type 
     0   -:stream
        "The data is transmitted as a stream of bytes.  There is no
         restriction on the representation type used; record structures
         are allowed, in which case the transfer byte size must be at
         least 3 bits!
         In a record structured file EOR and EOF will each be indicated
         by a two-byte control code of whatever byte size is used for
         the transfer.  The first byte of the control code will be all
         ones, the escape character.  The second byte will have the low
         order bit on and zeroes elsewhere for EOR and the second low
         order bit on for EOF; that is, the byte will have value 1 for
         EOR and value 2 for EOF.  EOR and EOF may be indicated together
         on the last byte transmitted by turning both low order bits on,
         i.e., the value 3.  If a byte of all ones was intended to be
         sent as data, it should be repeated in the second byte of the
         control code.
         If the file does not have record structure, the EOF is
         indicated by the sending Host closing the data connection and
         all bytes are data bytes."
    1  -:block 
        " The file is transmitted as a series of data blocks preceded by
         one or more header bytes.  The header bytes contain a count
         field, and descriptor code.  The count field indicates the
         total length of the data block in bytes, thus marking the
         beginning of the next data block (there are no filler bits).
         The descriptor code defines:  last block in the file (EOF) last
         block in the record (EOR), restart marker (see the Section on
         Error Recovery and Restart) or suspect data (i.e., the data being 
         transferred is suspected of errors and is not reliable).
         This last code is NOT intended for error control within FTP.
         It is motivated by the desire of sites exchanging certain types
         of data (e.g., seismic or weather data) to send and receive all
         the data despite local errors (such as "magnetic tape read
         errors"), but to indicate in the transmission that certain
         portions are suspect).  Record structures are allowed in this
         mode, and any representation type may be used.  There is no
         restriction on the transfer byte size. "
    2  -:compressed
        "The file is transmitted as series of bytes of the size
         specified by the BYTE command.  There are three kinds of
         information to be sent:  regular data, sent in a byte string;
         compressed data, consisting of replications or filler; and
         control information, sent in a two-byte escape sequence.  If
         the byte size is B bits and n>0 bytes of regular data are sent,
         these n bytes are preceded by a byte with the left-most bit set
         to 0 and the right-most B-1 bits containing the number n."
         FOR MORE INFORMATION SEE RFC 959
****************************************/
int CUT_FTPClient::GetTransferMode() const
{
    return m_nTransferMode;
}
/***************************************
SetTransferStructure
    Sets the data transfer structure
Params
    type - 0:file 1:record 2:page
Return
    UTE_SUCCESS                 - success
    UTE_NO_RESPONSE             - no response
    UTE_SVR_REQUEST_DENIED      - request denied by server
    UTE_PARAMETER_INVALID_VALUE - invalid type
****************************************/
int CUT_FTPClient::SetTransferStructure(int structure){
    int     rt;

    //check for a valid range
    if(structure < 0 || structure > 3)
        return OnError(UTE_PARAMETER_INVALID_VALUE);

    //send the mode info
    if(structure ==0)   //file
        Send("STRU F\r\n");
    else if(structure ==1)    //record
        Send("STRU R\r\n");
    else if(structure ==2)    //page
        Send("STRU P\r\n");

    //check for a return of 2??
    rt = GetResponseCode(this);

    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response

    else if(rt >=200 && rt <=299) {
        m_nTransferStructure = structure;
        return OnError(UTE_SUCCESS);
        }

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
GetTransferStructure
    Gets the data transfer structure
Params
    none
Return
    transfer structure type -   
                    0 : file 
                    1 : record 
                    2 : page
****************************************/
int CUT_FTPClient::GetTransferStructure() const
{
    return m_nTransferStructure;
}

/***************************************
SetControlPort
    Sets the default Control port
Params
    port - default 21
Return
    UTE_SUCCESS - success
    UTE_ERROR   - error
****************************************/
int CUT_FTPClient::SetControlPort(int port){
    m_nControlPort = port;
    return UTE_SUCCESS;
}
/***************************************
GetControlPort
    Gets the Control port
Params
    none
Return
    port 
****************************************/
int CUT_FTPClient::GetControlPort() const
{
    return m_nControlPort;
}
/***************************************
SetDataPort
    Sets the default data port
Params
    port (0-32000) over 1000 recommended
Return
    UTE_SUCCESS - success
    UTE_ERROR   - error
****************************************/
int CUT_FTPClient::SetDataPort(int port){
    m_nDataPort = port;
    return UTE_SUCCESS;
}
/***************************************
GetDataPort
    Gets the data port
Params
    none
Return
    port (0-32000) over 1000 recommended
****************************************/
int CUT_FTPClient::GetDataPort() const
{
    return m_nDataPort;
}
/***************************************
GetDirInfo
    Retrieves the current directory infomation
    on the currently connected FTP server.
Params
    none
Return
    UTE_SUCCESS                     - success
    UTE_DATAPORT_FAILED             - data port could not be opened
    UTE_PORT_FAILED                 - PORT command failed
    UTE_SVR_DATA_CONNECT_FAILED     - Server failed to connect on data port
    UTE_LIST_FAILED                 - LIST command failed
    UTE_ABORTED                     - aborted
****************************************/
int CUT_FTPClient::GetDirInfo(){

    int     rt,loop,len;
    char    addr[32];

    if (m_nFirewallMode)
        return GetDirInfoPASV();

    //open up a data port, if the one requested is busy then
    //increment to the next port, try 50 times then fail
    for(loop=0;loop<128;loop++) {
		if(m_wsData.WaitForConnect((unsigned short)m_nDataPort)==UTE_SUCCESS)
            break;
        m_nDataPort++;
        }

    if(loop==128)
        return OnError(UTE_DATAPORT_FAILED);

    //get the host address
    GetHostAddress( addr,sizeof(addr) );

    //create the port set up string
    len = (int)strlen(addr);
    for(loop=0;loop<len;loop++){
        if(addr[loop]=='.')
            addr[loop] = ',';
    }
    _snprintf(m_szBuf,sizeof(m_szBuf)-1,"PORT %s,%d,%d\r\n",addr,HIBYTE(m_nDataPort),LOBYTE(m_nDataPort));

    //setup the next port number
    m_nDataPort++;
    if(m_nDataPort > 32000 || m_nDataPort < 0)
        m_nDataPort = 10000;

    //send the port command
    Send(m_szBuf);
    
    //check for a return of 2??
    rt = GetResponseCode(this);
    if(rt < 200 || rt >299){
        //close the connection down
        m_wsData.CloseConnection();
        return OnError(UTE_PORT_FAILED);
        }

    //send the list command
    Send("LIST\r\n");

    //wait for a connection on the data port
    if(m_wsData.WaitForAccept(15)!= UTE_SUCCESS){  // GW: July 18 the wait Time Out is increased to 15 sec
        //close the connection down
        m_wsData.CloseConnection();
        ClearReceiveBuffer();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }


	  //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300) {
        m_wsData.CloseConnection();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }

    m_wsData.AcceptConnection();

    //clear the DirInfo linked list
    ClearDirInfo();
    CUT_DIRINFOA * di = NULL;
    BOOL once = TRUE;

	// v4.2 change to eliminate C4127: conditional expression is constant
	for(;;) {
        // Check for abortion flag
        if(IsAborted()) {                               
            m_wsData.CloseConnection();
            return OnError(UTE_ABORTED);
            }

        //retrive a dir line
        if (m_wsData.ReceiveLine(m_szBuf,sizeof(m_szBuf)) <= 0)
            break;
        
        CUT_StrMethods::RemoveCRLF(m_szBuf);

      
        // With out this step the client will assume the field TOTAL (which is provided by some unix servers), It will be assumed 
        // as a directory. Misleading the user to think that GetDirInfo returns an extra directory entry
        if ((_strnicmp("total",m_szBuf,5) == 0) && once == TRUE) {
            once = FALSE;
            continue;
            }

        //create the linked list item
        if(di==NULL) {
            di = new CUT_DIRINFOA;
            m_DirInfo = di;
            di->next = NULL;
            }
        else {
            di->next = new CUT_DIRINFOA;
            di = di->next;
            di->next = NULL;
            }

        //parse and store the directory information
        
        if ( isdigit(m_szBuf[0]))   
            GetInfoInDOSFormat( di);
        //  call the dos function 
        // end of Dos Format file names
        else   /// Unix  Style
            // Call the unix Format 
            GetInfoInUNIXFormat(di);
        
        //increment the dirinfo count
        m_nDirInfoCount ++;
        }

    //close the connection down
    m_wsData.CloseConnection();

    //check for a return of 2??
    rt = GetResponseCode(this);
    if (rt < 200 || rt >= 300) {
        if (rt <200) {
            rt = GetResponseCode(this);
            if (rt < 200 || rt >= 300)
                return OnError(UTE_LIST_FAILED);
            else
                return OnError(UTE_SUCCESS);
            }
        return OnError(UTE_LIST_FAILED);
        }
    else
        return OnError(UTE_SUCCESS);
}
    
/***************************************
GetDirInfoPASV
    Retrieves the current directory infomation
    on the currently connected FTP server.

    This routine duplicates the GetDirInfo()
    member, but in this routine the client
    originates data connections.  Client side
    origination of data connections is 
    preferred for use in firewall controlled
    environments.
Params
    none
Return
    UTE_SUCCESS                     - success
    UTE_PORT_FAILED                 - PORT command failed
    UTE_SVR_DATA_CONNECT_FAILED     - Server failed to connect on data port
    UTE_LIST_FAILED                 - LIST command failed
    UTE_PORT_FAILED                 - data port could not be opened
    UTE_CONNECT_TIMEOUT             - connection timeout
    UTE_ABORTED                     - aborted
****************************************/
int CUT_FTPClient::GetDirInfoPASV(){
    int     error, rt;
    char    responseBuf[100];
    char    *token;
    char    ipAddress[20];
    int     port;
    UINT    loop;

    //send the port command
    Send("PASV\r\n");
    

    // we need to get the full IP address and port number from the 
    // PASV return line, so that we can originate the data connection.

    //check for a return of 2??
    rt = GetResponseCode(this, responseBuf, sizeof(responseBuf));
    if(rt < 200 || rt >299)
        return OnError(UTE_PORT_FAILED);
    
    // find the first '(' in the response and then parse out
    // the address supplied by the server
    loop = 0;
    while ( loop < strlen(responseBuf) ) {
        if ( responseBuf[loop] == '(' )
            break;
        loop++;
        }

    if ( loop == strlen(responseBuf) ) // we hit the end of the buffer
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);

    // token out the ipaddress and port string
    token = strtok( &responseBuf[loop], "()" );

    // get the four portions of the ip address
    strcpy(ipAddress, strtok( token, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));
    strcat(ipAddress, ".");
    strcat(ipAddress, strtok( NULL, ",)\r\n" ));

    // get the two portions that make up the port number
    port = atoi( strtok(NULL, ",)\r\n") ) * 256;
    port += atoi( strtok(NULL, ",)\r\n") );

    // test the results to ensure everything is OK.
    if ( !IsIPAddress(ipAddress) )
        return OnError(UTE_LIST_FAILED);

    if ( port <= 0 || port > 65535 )
        return OnError(UTE_DATAPORT_FAILED);
    
    
    //send the list command, the server will then wait for us to 
    // connect on the port it provided in the PASV statement.
    Send("LIST\r\n");

    // connect to the server supplied port to establish the 
    // data connection.
    // connect using a timeout
    if((error = m_wsData.Connect(port, ipAddress, m_nConnectTimeout)) != UTE_SUCCESS) {
        m_wsData.CloseConnection();
        return OnError(error);
        }

    //check for a return of 100 or 200 code
    rt = GetResponseCode(this);
    if(rt < 100 || rt >=300) {
        m_wsData.CloseConnection();
        return OnError(UTE_SVR_DATA_CONNECT_FAILED);
        }

    BOOL once = TRUE;
    //clear the DirInfo linked list
    ClearDirInfo();
    CUT_DIRINFOA * di = NULL;

	// v4.2 change to eliminate C4127: conditional expression is constant
	for (;;) {
		// Check for abortion flag
        if(IsAborted()) {                               
            m_wsData.CloseConnection();
            return OnError(UTE_ABORTED);
            }

        //retrive a dir line
        if (m_wsData.ReceiveLine(m_szBuf,sizeof(m_szBuf),m_wsData.GetReceiveTimeOut ()/1000) <= 0)
            break;
        
        CUT_StrMethods::RemoveCRLF(m_szBuf);

        // GW: 
        // With out this step the client will assume the field TOTAL (which is provided by some unix servers), It will be assumed 
        // as a directory. Misleading the user to think that GetDirInfo returns an extra directory entry
        //Added on July 22nd 1998
        if ((_strnicmp("total",m_szBuf,5) == 0) && once == TRUE) {
            once = FALSE;
            continue;
            }

        //create the linked list item
        if(di==NULL) {
            di = new CUT_DIRINFOA;
            m_DirInfo = di;
            di->next = NULL;
            }
        else {
            di->next = new CUT_DIRINFOA;
            di = di->next;
            di->next = NULL;
            }

        //parse and store the directory information
        
        //filename
        if ( isdigit(m_szBuf[0]))  //  call the dos function 
            GetInfoInDOSFormat(di);
            // end of Dos Format file names
        else   /// Unix  Style 
        // Call the unix Format 
            GetInfoInUNIXFormat( di);
        
        //increment the dirinfo count
        m_nDirInfoCount ++;
        }
    
    //close the connection down
    m_wsData.CloseConnection();

    //check for a return of 2??
    rt = GetResponseCode(this);
    if (rt < 200 || rt >= 300) {
        if (rt <200) {
            rt = GetResponseCode(this);
            if (rt < 200 || rt >= 300)
                return OnError(UTE_LIST_FAILED);
            else
                return OnError(UTE_SUCCESS);
            }
        return OnError(UTE_LIST_FAILED);
        }
    else
        return OnError(UTE_SUCCESS);
}
/***************************************
GetDirInfoCount
    Returns the number of directory entries
    retrieved during the last call to
    GetDirInfo
Params
    none
Return
    The number of directory entries
****************************************/
int CUT_FTPClient::GetDirInfoCount() const
{
    return m_nDirInfoCount;
}

/***************************************
GetDirEntry
    Returns a directory entry name using a
    zero based index value
Params
    index - 0 based index 0 to count-n
    entry - buffer where the name is returned
    maxlen - length of the buffer
Return
    UTE_SUCCESS     - success
    UTE_ERROR       - invalid index
****************************************/
#if defined _UNICODE
int CUT_FTPClient::GetDirEntry(int index, LPWSTR entry, int maxlen) {
	char * entryA = (char*) alloca(maxlen);
	*entryA = '\0';
	int result = GetDirEntry(index, entryA, maxlen);
	if(result == UTE_SUCCESS) {
		CUT_Str::cvtcpy(entry, maxlen, entryA);
	}
	return result;}
#endif
int CUT_FTPClient::GetDirEntry(int index, LPSTR entry, int maxlen) {

    CUT_DIRINFOA     *di   = m_DirInfo;
    int             count = 0;

    //check for a valid range
    if(index < 0 || index >= m_nDirInfoCount)
        return OnError(UTE_ERROR);

    //find the correct record
    while(count < index) {
        if(di->next == NULL)
            break;
        di = di->next;
        count++;
        }

    //copy the record
    maxlen--;
    strncpy(entry, di->fileName, maxlen);
    entry[maxlen] = 0;

    return OnError(UTE_SUCCESS);
}
/***************************************
GetDirEntry
    Returns a directory entry into a 
    CUT_DIRINFO structure. The structure
    contains all of the information on a
    directory entry.
Params
    index - 0 based index 0 to count-n
    dirInfo - structure where the directory
     information is copied into
    maxlen - length of the buffer
Return
    UTE_SUCCESS             - success
    UTE_INDEX_OUTOFRANGE    - invalid index
****************************************/
int CUT_FTPClient::GetDirEntry(int index, CUT_DIRINFO *dirInfo) {

    CUT_DIRINFOA    *di   = m_DirInfo;
    int             count = 0;

    //check for a valid range
    if(index < 0 || index >= m_nDirInfoCount)
        return OnError(UTE_INDEX_OUTOFRANGE);

    //find the correct record
    while(count <index) {
        if(di->next == NULL)
            return OnError(UTE_INDEX_OUTOFRANGE);
        di = di->next;
        count++;
        }

    //copy the record, switching from char to _TCHAR for filename
	CUT_Str::cvtcpy(dirInfo->fileName,MAX_PATH, di->fileName);
    dirInfo->fileSize   = di->fileSize;
    dirInfo->day        = di->day;
    dirInfo->month      = di->month;
    dirInfo->year       = di->year;
    dirInfo->hour       = di->hour;
    dirInfo->minute     = di->minute;
    dirInfo->isDir      = di->isDir;

    return OnError(UTE_SUCCESS);
}
/***************************************
GetHelp
    Returns help information from the 
    currently connected server. Once this 
    command completes successfully then
    the information can be retrieved using 
    the GetMultiLineResponse function.
Params
    param - the command to retrieve help on
        if "" is sent in then help on all
        commands is sent back
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    UTE_SVR_REQUEST_DENIED  - request denied by server    
****************************************/
#if defined _UNICODE
int CUT_FTPClient::GetHelp(LPCWSTR param) {
	return GetHelp(AC(param));}
#endif
int CUT_FTPClient::GetHelp(LPCSTR param) {

    // clear response list
    m_listResponse.ClearList();

    //send the help command
    if(param == NULL)
        _snprintf(m_szBuf,sizeof(m_szBuf)-1,"HELP\r\n");
    else if(param[0] == 0)
        _snprintf(m_szBuf,sizeof(m_szBuf)-1,"HELP\r\n");
    else
        _snprintf(m_szBuf,sizeof(m_szBuf)-1,"HELP %s\r\n",param);

    Send(m_szBuf);

    //check for a return of 2??
    int rt = GetResponseCode(this);

    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response

    else if(rt >=0 && rt <=299)
        return OnError(UTE_SUCCESS);

    return OnError(UTE_SVR_REQUEST_DENIED);
}
/***************************************
GetResponseCode(CUT_WSClient *ws,LPSTR string,int maxlen)
    A reply (ResponseCode) is an acknowledgment (positive or negative) sent from
    server to user via the TELNET connections in response to FTP
    commands.  The general form of a reply is a completion code
    (including error codes) followed by a text string.  The codes
    are for use by programs and the text is usually intended for
    human users
    "	The server sends FTP replies over the TELNET connection in response
   to user FTP commands.  The FTP replies constitute the acknowledgment
   or completion code (including errors).  The FTP-server replies are
   formatted for human or program interpretation.  Single line replies
   consist of a leading three-digit numeric code followed by a space,
   followed by a one-line text explanation of the code.  For replies
   that contain several lines of text, the first line will have a
   leading three-digit numeric code followed immediately by the
   character "-" (Hyphen, ASCII code 45), and possibly some text.  All
   succeeding continuation lines except the last are constrained NOT to
   begin with three digits; the last line must repeat the numeric code
   of the first line and be followed immediately by a space.  For   example:
          100-First Line    
          Continuation Line   
          Another Line 
          100 Last Line
The assigned reply codes relating to FTP are:   
    000  Announcing FTP.  
    010  Message from system operator.
    020  Exected delay.
   030  Server availability information.
   050  FTP commentary or user information.
   100  System status reply.
   110  System busy doing...  
   150  File status reply.
   151  Directory listing reply. 
   200  Last command received correctly.
   201  An ABORT has terminated activity, as requested.
   202  Abort request ignored, no activity in progress.
   230  User is "logged in".  May proceed.
   231  User is "logged out".  Service terminated.
   232  Logout command noted, will complete when transfer done.
   233  User is "logged out".  Parameters reinitialized.
   250  FTP file transfer started correctly.  
   251  FTP Restart-marker reply.
      Text is:  MARK yyyy = mmmm
         where 'yyyy' is user's data stream marker (yours)
         and mmmm is server's equivalent marker (mine)
      (Note the spaces between the markers and '=').
   252  FTP transfer completed correctly.  
   253  Rename completed.
   254  Delete completed.
   257  Closing the data connection, transfer completed.
   300  Connection greeting message, awaiting input.
   301  Current command incomplete (no <CRLF> for long time).
   330  Enter password
   331  Enter account (if account required as part of login sequence).
   332  Login first, please. 
   400  This service not implemented.
   401  This service not accepting users now, goodbye.
   402  Command not implemented for requested value or action.
   430  Log-on time or tries exceeded, goodbye.
   431  Log-on unsuccessful.  User and/or password invalid.
   432  User not valid for this service.
   433  Cannot transfer files without valid account.  Enter account and
        resend command.  
   434  Log-out forced by operator action.  Phone site.
   435  Log-out forced by system problem.  
   436  Service shutting down, goodbye.
   450  FTP:  File not found.  
   451  FTP:  File access denied to you.
   452  FTP:  File transfer incomplete, data connection closed.
   453  FTP:  File transfer incomplete, insufficient storage space.
   454  FTP:  Cannot connect to your data socket.
   455  FTP:  File system error not covered by other reply codes.
   456  FTP:  Name duplication; rename failed.
   457  FTP:  Transfer parameters in error.
   500  Last command line completely unrecognized.
   501  Syntax of last command is incorrect.
   502  Last command incomplete, parameters missing.
   503  Last command invalid (ignored), illegal parameter combination.
   504  Last command invalid, action not possible at this time.
   505  Last command conflicts illegally with previous command(s).
   506  Last command not implemented by the server.  
   507  Catchall error reply.
   550  Bad pathname specification (e.g., syntax error).
    " SEE ALSO RFC 542 & 959
PARAM
    CUT_WSClient *ws - the connection class be it the data connection or the Command connection
RETURN
    int - The Server reply code
****************************************/
int CUT_FTPClient::GetResponseCode(CUT_WSClient *ws, LPSTR string, int maxlen) {

    char c;
    int  code;
    int  once = TRUE;
    char mlCode[5];

    if(ws->ReceiveLine(m_szBuf,sizeof(m_szBuf),m_wsData.GetReceiveTimeOut ()/1000) <= 0)
        return 0;  //no response

    CUT_StrMethods::RemoveCRLF(m_szBuf);

    //get the code to return
    c = m_szBuf[3];
    m_szBuf[3] = 0;
    code = atoi(m_szBuf);
    m_szBuf[3] = c;
    mlCode[0] = m_szBuf[0];
    mlCode[1] = m_szBuf[1];
    mlCode[2] = m_szBuf[2];
    mlCode[3] = ' ';
    mlCode[4] = 0;

    //check for a multi-line response
    if(c =='-') {
        while(strstr(m_szBuf,mlCode) != m_szBuf) {
            
            //clear the multi-line response list the first time through
            if(once) {
                once = FALSE;

                // clear response list
                m_listResponse.ClearList();
                }

            m_listResponse.AddString(m_szBuf);

            //get the line
            if(ws->ReceiveLine(m_szBuf,sizeof(m_szBuf),m_wsData.GetReceiveTimeOut ()/1000) <=0)
                break;
            CUT_StrMethods::RemoveCRLF(m_szBuf);          
            }

        m_listResponse.AddString(m_szBuf);
        }
    else 
        m_listResponse.AddString(m_szBuf);

    strncpy(m_szResponse, m_szBuf, MAX_PATH);
    m_szResponse[MAX_PATH - 1] = '\0';

    //copy the rest of the data
    if(string != NULL) {
        maxlen--;
        strncpy(string, &m_szBuf[4], maxlen);
        string[maxlen - 1]  =0;
        }

    return code;
}

/***************************************
ClearDirInfo
    Clears the directory information that
    was stored during the last call to GetDirInfo
PARAM
    NONE
RETURN
    UTE_SUCCESS
****************************************/
int CUT_FTPClient::ClearDirInfo(){

    CUT_DIRINFOA * next;
    CUT_DIRINFOA * current;

    current = m_DirInfo;
    while(current != NULL){
        next = current->next;
        delete current;
        current = next;
    }
    m_DirInfo = NULL;
    m_nDirInfoCount = 0;

    return OnError(UTE_SUCCESS);
}

/***************************************
SetFireWallMode()
    This function, which sets the 
    m_nFirewallMode member variable, 
    changes the operation of the 
    class so that all data connections
    are originated by the client.
    This behavior is required for use
    with some firewall software.
    Firewall software will often restrict
    incoming connections to a corporate
    computer, but will allow outgoing
    connections. 
    By default, FTP clients contact the 
    server to establish a "control" connection
    and the server calls the client back to 
    establish the "data" connection.
    By calling this function the ftp client class
    will originate both connections.
PARAM
    BOOL mode - TRUE or FALSE 
****************************************/
void CUT_FTPClient::SetFireWallMode(BOOL mode){
    m_nFirewallMode = mode;  
}
/***************************************
GetFireWallMode()
    This function, which gets the 
    m_nFirewallMode member variable, 
    changes the operation of the 
    class so that all data connections
    are originated by the client.
    This behavior is required for use
    with some firewall software.
    Firewall software will often restrict
    incoming connections to a corporate
    computer, but will allow outgoing
    connections. 
    By default, FTP clients contact the 
    server to establish a "control" connection
    and the server calls the client back to 
    establish the "data" connection.
    By calling this function the ftp client class
    will originate both connections.
PARAM
    none
****************************************/
int CUT_FTPClient::GetFireWallMode() const
{
    return m_nFirewallMode;  
}
/****************************************************
GetLastResponse()
    Use this function to debug your application and to be 
    aware of each response a server sends for each command you
    prompt the server for.
PARAM
    NONE
RETURN
    LPCSTR - the last response string received from the server.
********************************************************/
LPCSTR CUT_FTPClient::GetLastResponse() const
{
    return m_szResponse;
}
/*************************************************
GetLastResponse()
Gets the last response returned from the server 
PARAM
response - [out] pointer to buffer to receive response
maxSize  - length of buffer
index    - index response 
size     - [out] length of response

  RETURN				
  UTE_SUCCES			- ok - 
  UTE_NULL_PARAM		- response and/or size is a null pointer
  UTE_INDEX_OUTOFRANGE  - response not found
  UTE_BUFFER_TOO_SHORT  - space in name buffer indicated by maxSize insufficient, realloc 
  based on size returned.
  UTE_OUT_OF_MEMORY		- possible in wide char overload
**************************************************/
int	CUT_FTPClient::GetLastResponse(LPSTR response, size_t maxSize, size_t *size) {
	
	int retval = UTE_SUCCESS;
	
	if(response == NULL || size == NULL) {
		retval = UTE_NULL_PARAM;
	}
	else {
		
		LPCSTR str = GetLastResponse();
		
		if(str == NULL) {
			retval = UTE_INDEX_OUTOFRANGE;
		}
		else {
			*size = strlen(str);
			if(*size >= maxSize) {
				++(*size);
				retval = UTE_BUFFER_TOO_SHORT;
			}
			else {
				strcpy(response, str);
			}
		}
	}
	return retval;
}
#if defined _UNICODE
int	CUT_FTPClient::GetLastResponse(LPWSTR response, size_t maxSize, size_t *size) {
	
	int retval;
	
	if(maxSize > 0) {
		char * responseA = new char [maxSize];
		
		if(responseA != NULL) {
			retval = GetLastResponse( responseA, maxSize, size);
			
			if(retval == UTE_SUCCESS) {
				CUT_Str::cvtcpy(response, maxSize, responseA);
			}
			delete [] responseA;
		}
		else {
			retval = UTE_OUT_OF_MEMORY;
		}
	}
	else {
		if(size == NULL) (retval = UTE_NULL_PARAM);
		else {
			LPCSTR lpStr = GetLastResponse();
			if(lpStr != NULL) {
				*size = strlen(lpStr)+1;
				retval = UTE_BUFFER_TOO_SHORT;
			}
			else {
				retval = UTE_INDEX_OUTOFRANGE;
			}
		}
	}
	return retval;

}
#endif

/***********************************************
GetInfoInDOSFormat()
    Although it is strongly discouraged to use DOS file format 
    for the server directory structure. There are still the few
    number of FTP servers sites that provides the names in a DOS format.
    This function will parse the file information details based 
    on the DOS format
PARAM:
    CUT_DIRINFO di - the directory information entry to be populated 
RETURN 
    VOID
**********************************************/
void CUT_FTPClient::GetInfoInDOSFormat( CUT_DIRINFOA * di){
    //parse and store the directory information
     char    buf[32];
    char    bufDos[MAX_PATH+1];
    char    dateBuf[20];
    long    value;

        //parse and store the directory information

        // Get the file name 
        int nSpaces = 0, loop = 0;
        while(m_szBuf[loop] != NULL) {
            if(m_szBuf[loop] == ' ') {
                ++ nSpaces;
                while(m_szBuf[loop] == ' ')
                    ++ loop;
                }
            else if(nSpaces == 3) {
                strncpy(di->fileName, &m_szBuf[loop], sizeof(di->fileName));
                break;
                }
            else
                ++ loop;
        }

        CUT_StrMethods::ParseString(m_szBuf, " ", 2, bufDos, sizeof(bufDos));
        
        //directory  attrib
        if(bufDos[1]=='d' || bufDos[1] =='D')
            di->isDir = TRUE;
        else
            di->isDir = FALSE;
        
        //size portion of the file date 
        di->fileSize = 0;
        CUT_StrMethods::ParseString(m_szBuf," ",2,&di->fileSize);


        //month portion of the file date 
        di->month = 1;
        CUT_StrMethods::ParseString(m_szBuf," ",0,dateBuf,sizeof(dateBuf));
        CUT_StrMethods::ParseString(dateBuf,"-",0,buf,sizeof(buf));
        //find the month number from the string
        di->month = atoi(buf);
        //day portion of the file date 
        di->day =1;
        CUT_StrMethods::ParseString(dateBuf,"-",1,&value);
        di->day = (int)value;
        //year and or hour portion of the file date 
        di->year = 1900;
        di->hour = 12;
        di->minute = 0;
        CUT_StrMethods::ParseString(m_szBuf," ",0,dateBuf,sizeof(dateBuf));

        strncpy(dateBuf, &dateBuf[6],2);    
        int temp = atoi(dateBuf);
        if( 70 > temp)
            temp+= 100;
        di->year = di->year + temp;
        CUT_StrMethods::ParseString(m_szBuf," ",1,buf,sizeof(buf));
    
        //get the hour portion of the file date 
        CUT_StrMethods::ParseString(buf,":",0,&value);
        di->hour = (int)value;
        //get the minute portion of the file date 
        CUT_StrMethods::ParseString(buf,":",1,dateBuf,sizeof(dateBuf));
    	if (dateBuf[2] =='P')	//AM or PM Digit
			di->hour +=12;

		//strncpy(dateBuf,&dateBuf[3],2);
	    dateBuf[2] = '\0';
        di->minute = atoi(dateBuf);
}
/***********************************************
GetInfoInUNIXFormat()
        This function parses the directory entry information
        based on the UNIX format.
PARAM:
      CUT_DIRINFO di - the directory information entry to be populated 
RET:
      VOID
**********************************************/
void CUT_FTPClient::GetInfoInUNIXFormat( CUT_DIRINFOA * di){

   
    const char *Month[]={"Jan","Feb","Mar","Apr","May","Jun","Jul",
        "Aug","Sep","Oct","Nov","Dec"};

    char        buf[32];
    int         loop;
    time_t      timer;
    struct tm   *tblock;
    long        value;
	int			linksIncluded = 0;
    di->fileName[0] = NULL;

    // Get the file name 
    int nSpaces = 0;
    loop = 0;

	// check if the links or blocks attribute is included in the server answer
	if (CUT_StrMethods::GetParseStringPieces (m_szBuf," ") > 8)
		linksIncluded = 0 ; 
	else
		linksIncluded = -1;

	while(m_szBuf[loop] != NULL) {
        if(m_szBuf[loop] == ' ') {
            ++ nSpaces;
			int spaceCounter = 0;
            while(m_szBuf[loop] == ' ')
			{
				spaceCounter++;
				++ loop;
			}

            }
        else if(nSpaces == 8 +linksIncluded) {
            strncpy(di->fileName, &m_szBuf[loop], sizeof(di->fileName));
            break;
            }
        else
            ++ loop;
    }


    //directory  attrib
    if(m_szBuf[0]=='d' || m_szBuf[0] =='D')
        di->isDir = TRUE;
    else
        di->isDir = FALSE;
    
    //file size
    di->fileSize = 0;
    CUT_StrMethods::ParseString(m_szBuf," ",4+linksIncluded,&di->fileSize);

    //month portion of the file date 
    di->month = 1;
    CUT_StrMethods::ParseString(m_szBuf," ",5+linksIncluded,buf,sizeof(buf));
    
    //find the month number from the string
    for(loop=0;loop<12;loop++) {
        if(_stricmp(buf,Month[loop])==0) {
            di->month = loop+1;
            break;
            }
        }

    //day portion of the file date 
    di->day =1;
    CUT_StrMethods::ParseString(m_szBuf," ",6+linksIncluded,&value);
    di->day = (int)value;

    //year and or hour portion of the file date 
    di->year = 1900;        // a unix type of ls -l will give a year or a time - not both
    di->hour = 00;          // we default to current year (below) and 12:00AM
    di->minute = 00;
    CUT_StrMethods::ParseString(m_szBuf," ",7+linksIncluded,buf,sizeof(buf));
    
    //check to see if it is a time
    if(strstr(buf,":") != NULL) {
        //get the current year
        timer = time(NULL);                 // default to current year...
        tblock = localtime(&timer);
        di->year = tblock->tm_year+1900;
        //get the hour
        CUT_StrMethods::ParseString(buf,":",0,&value);
        di->hour = (int)value;
		// So all of the time shown is  
        //get the minute
        CUT_StrMethods::ParseString(buf,":",1,&value);
        di->minute = (int)value;
		// So all of the time shown is  
        }
    else
        di->year = atoi(buf);

}

/***********************************************
GetMultiLineResponseLineCount
      Returns a number of lines in the multiline 
      response list.
PARAM:
      none
RET:
      number of lines
**********************************************/
LONG CUT_FTPClient::GetMultiLineResponseLineCount() const
{
    return m_listResponse.GetCount();
}
/***********************************************
GetMultiLineResponse
      Returns a line from the multiline response
      list.
PARAM:
      index     - index of the line
RET:
      pointer to line or NULL
**********************************************/
LPCSTR CUT_FTPClient::GetMultiLineResponse(int index) const
{
    return m_listResponse.GetString(index);
}
/*************************************************
GetMultiLineResponse()
Gets the one line of a multiline response returned from the server (e.g. a response to Connect)
PARAM
response - [out] pointer to buffer to receive response
maxSize  - length of buffer
index    - index response 
size     - [out] length of response

  RETURN				
  UTE_SUCCES			- ok - 
  UTE_NULL_PARAM		- response and/or size is a null pointer
  UTE_INDEX_OUTOFRANGE  - response not found
  UTE_BUFFER_TOO_SHORT  - space in name buffer indicated by maxSize insufficient, realloc 
  based on size returned.
  UTE_OUT_OF_MEMORY		- possible in wide char overload
**************************************************/
int	CUT_FTPClient::GetMultiLineResponse(LPSTR response, size_t maxSize, int index, size_t *size) {
	
	int retval = UTE_SUCCESS;
	
	if(response == NULL || size == NULL) {
		retval = UTE_NULL_PARAM;
	}
	else {
		
		LPCSTR str = GetMultiLineResponse(index);
		
		if(str == NULL) {
			retval = UTE_INDEX_OUTOFRANGE;
		}
		else {
			*size = strlen(str);
			if(*size >= maxSize) {
				++(*size);
				retval = UTE_BUFFER_TOO_SHORT;
			}
			else {
				strcpy(response, str);
			}
		}
	}
	return retval;
}
#if defined _UNICODE
int	CUT_FTPClient::GetMultiLineResponse(LPWSTR response, size_t maxSize, int index, size_t *size) {
	
	int retval;
	
	if(maxSize > 0) {
		char * responseA = new char [maxSize];
		
		if(responseA != NULL) {
			retval = GetMultiLineResponse( responseA, maxSize, index, size);
			
			if(retval == UTE_SUCCESS) {
				CUT_Str::cvtcpy(response, maxSize, responseA);
			}
			delete [] responseA;
		}
		else {
			retval = UTE_OUT_OF_MEMORY;
		}
	}
	else {
		if(size == NULL) (retval = UTE_NULL_PARAM);
		else {
			LPCSTR lpStr = GetMultiLineResponse(index);
			if(lpStr != NULL) {
				*size = strlen(lpStr)+1;
				retval = UTE_BUFFER_TOO_SHORT;
			}
			else {
				retval = UTE_INDEX_OUTOFRANGE;
			}
		}
	}
	return retval;
}
#endif

/***************************************************
ReceiveFileStatus
    This virtual function is called during a 
    ReceiveToFile function.
Params
    bytesReceived - number of bytes received so far
Return
    TRUE - allow the receive to continue
    FALSE - abort the receive
****************************************************/
BOOL CUT_FTPClient::ReceiveFileStatus(long /* bytesReceived */){
    return !IsAborted();
}
/***************************************************
SendFileStatus
    This virtual function is called during a 
    SendFile function.
Params
    bytesSent - number of bytes sent so far
Return
    TRUE - allow the send to continue
    FALSE - abort the send
****************************************************/
BOOL CUT_FTPClient::SendFileStatus(long /* bytesSent */){
    return !IsAborted();
}
/*********************************************
SetConnectTimeout
    Sets the time to wait for a connection 
    in seconds
    5 seconds is the default time
Params
    secs - seconds to wait
  Return
    UTE_SUCCESS - success
    UTE_ERROR   - invalid input value
**********************************************/
int CUT_FTPClient::SetConnectTimeout(int secs){
    
    if(secs <= 0)
        return OnError(UTE_ERROR);

    m_nConnectTimeout = secs;

    return OnError(UTE_SUCCESS);
}
/*********************************************
GetConnectTimeout
    Gets the time to wait for a connection 
    in seconds
Params
    none
Return
    current time out value in seconds
**********************************************/
int CUT_FTPClient::GetConnectTimeout() const
{
    return m_nConnectTimeout;
}
/***************************************
Quote(LPCSTR command)
    sends a custom command to the server
    custom command can be any valid FTP command or 
    any server specific command.
    To see the server response call GetMultiResponseLine()
Params
    command - the command to Send to the server  
Return
    UTE_SUCCESS             - success
    UTE_NO_RESPONSE         - no response
    sending error code
****************************************/
#if defined _UNICODE
int CUT_FTPClient::Quote(LPCWSTR command) {
	return Quote(AC(command));}
#endif
int CUT_FTPClient::Quote(LPCSTR command) {

    m_szResponse[0]         = NULL; 
    if (!command || strlen(command) <1)
        return OnError(UTE_QUOTE_LINE_IS_EMPTY);
    // clear response list
    m_listResponse.ClearList();
    int rt = 0;
    SendAsLine(command, (int)strlen(command),256);
    // we will leave the server error messages to be handled by the developer
    rt = GetResponseCode(this);

    if(rt == 0)
        return OnError(UTE_NO_RESPONSE);   //no response
    return OnError(UTE_SUCCESS);
}


/**************************************************************
SocketOnConnected(SOCKET s, const char *lpszName)
	
		If the security is enabled then perform te SSL neogotiation
		otherwise just return a success and let the plain text FTP handles
		the comunication

		To let the server know that we are looking for SSL or TLS we need to
		send the following command
		FEAT  
		The Feature negotiation mechanism for the File Transfer Protocol 
		
		To ask the server for SSL or TLS negotiation 
		we will send the AUTH command.

		A parameter for the AUTH command to indicate that TLS is
		required.  It is recommended that 'TLS', 'TLS-C', 'SSL' and 'TLS-P'
		are acceptable, and mean the following :-

		 'TLS' or 'TLS-C' - the TLS protocol or the SSL protocol will be
			negotiated on the control connection.  The default protection
			setting for the Data connection is 'Clear'.

			'SSL' or 'TLS-P' - the TLS protocol or the SSL protocol will be
			negotiated on the control connection.  The default protection
			setting for the Data connection is 'Private'.  This is primarily
			for backward compatibility.

	Notice that we will first send a TLS P to select The highest implementation.
	the server might response with the response
	503 unknown security mechanism 

	if this happened we will issue an Auth SSL command.

Param:
	SOCKET s		- The newly created socket 
	lpszName		- apointer to the host name we are attempting to connect to


Return:
	UTE_NO_RESPONSE - Server did not response to our command
	UTE_CONNECT_FAIL_NO_SSL_SUPPORT - Server does not support SSL.
	UTE_CONNECT_FAILED	-	 The connection have failed
	security  errors   - This function may fail with other error
			UTE_LOAD_SECURITY_LIBRARIES_FAILED
			UTE_OUT_OF_MEMORY
			UTE_FAILED_TO_GET_SECURITY_STREAM_SIZE
			UTE_OUT_OF_MEMORY
			UTE_FAILED_TO_QUERY_CERTIFICATE
			UTE_NULL_PARAM
			UTE_PARAMETER_INVALID_VALUE
			UTE_FAILED_TO_GET_CERTIFICATE_CHAIN
			UTE_FAILED_TO_VERIFY_CERTIFICATE_CHAIN
			UTE_FAILED_TO_VERIFY_CERTIFICATE_TRUST
	
**************************************************************/
int CUT_FTPClient::SocketOnConnected(SOCKET s, const char *lpszName){

		int rt = UTE_SUCCESS;	
	
#ifdef CUT_SECURE_SOCKET
	
		BOOL bSecFlag = GetSecurityEnabled();


		if (bSecFlag)
		{
			// disable the secure comunication so we can send the plain data 
				SetSecurityEnabled(FALSE);

				// if the security is enabled then 
				// Attempt to send the auth command
				rt = GetResponseCode(this);
				
				if(rt == 0)
							 return OnError(UTE_NO_RESPONSE);     //no response

				if(rt < 200 || rt > 399)
							return OnError(UTE_CONNECT_FAILED);      //negative response

				Send("AUTH TLS-P\r\n");									// Send the TLS negotiation command

				rt = GetResponseCode(this);
				
				if(rt == 0)
					return OnError(UTE_NO_RESPONSE);					//no response
				
				// check the response
				if(rt < 200 || rt > 399)
				{
					Send("AUTH SSL\r\n");								// Send the TLS negotiation command
					
					rt = GetResponseCode(this);
				
					if(rt == 0)
						return OnError(UTE_NO_RESPONSE);        //no response

					if(rt < 200 || rt > 399)
					{
						return OnError(UTE_CONNECT_FAIL_NO_SSL_SUPPORT);        //negative response
					}
					else																// If the SSL succeded then set the protocol to SSL
					{
						SetSecurityProtocol(SP_PROT_SSL3);
						m_wsData.	SetSecurityEnabled(bSecFlag);
						m_wsData.SetSecurityProtocol(SP_PROT_SSL3);

					}
				}
				else
				{
						SetSecurityProtocol(SP_PROT_TLS1);	// If the SSL succeded then set the protocol to SSL
						m_wsData.	SetSecurityEnabled(bSecFlag);
						m_wsData.SetSecurityProtocol(SP_PROT_TLS1);
				}
			
			
				if(IsAborted()) {                     // before we start the negotiation 
						return OnError(UTE_ABORTED);		//let's make sure we are not aborting
				}
																							// this call will continue the call of 
																							// the secure ssl negotiation

				// reset back the security so we can proceed
				SetSecurityEnabled(bSecFlag);
				rt =  CUT_SecureSocketClient::SocketOnConnected(s, lpszName);
				
				// since we have approved the certificate for the 
				// conrol connection 
				// then we have no problem in accepting certificate 
				// regarding the data connection.

				// note that this is a raised security issued as outlined by 
				if (rt == UTE_SUCCESS)
					m_wsData.SetCertValidation (CERT_DONOT_VERIFY);

				return rt;
		}
		else
		{
					//check for a return of 2?? or 3??
				rt = GetResponseCode(this);
				if(rt == 0)
						return OnError(UTE_NO_RESPONSE);         //no response
				if(rt < 200 || rt > 399)
						return OnError(UTE_USER_NA);                //negataive response

				// Check for abortion flag
				if(IsAborted())
				{                               
						return OnError(UTE_ABORTED);
				 }
				return UTE_SUCCESS;

		}

#else
		UNREFERENCED_PARAMETER(lpszName);
		UNREFERENCED_PARAMETER(s);
		//check for a return of 2?? or 3??
		rt = GetResponseCode(this);
		if(rt == 0)
				return OnError(UTE_NO_RESPONSE);         //no response
		if(rt < 200 || rt > 399)
				return OnError(UTE_CONNECT_FAILED);                //negataive response

		// Check for abortion flag
		if(IsAborted())
		{                               
				return OnError(UTE_ABORTED);
		}

		// v4.2 change - unreachable code in secure build - moved endif
		return OnError(UTE_SUCCESS);
#endif
}

#pragma warning ( pop )

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)


Written By
Web Developer
Canada Canada
In January 2005, David Cunningham and Chris Maunder created TheUltimateToolbox.com, a new group dedicated to the continued development, support and growth of Dundas Software’s award winning line of MFC, C++ and ActiveX control products.

Ultimate Grid for MFC, Ultimate Toolbox for MFC, and Ultimate TCP/IP have been stalwarts of C++/MFC development for a decade. Thousands of developers have used these products to speed their time to market, improve the quality of their finished products, and enhance the reliability and flexibility of their software.
This is a Organisation

476 members

Comments and Discussions