📄 ftpclient.cpp
字号:
////////////////////////////////////////////////////////////////////////////////
//
// The official specification of the File Transfer Protocol (FTP) is the RFC 959.
// Most of the documentation are taken from this RFC.
// This is an implementation of an simple ftp client. I have tried to implement
// platform independent. For the communication i used the classes CBlockingSocket,
// CSockAddr, ... from David J. Kruglinski (Inside Visual C++). These classes are
// only small wrappers for the sockets-API.
// Further I used a smart pointer-implementation from Scott Meyers (Effective C++,
// More Effective C++, Effective STL).
// The implementation of the logon-sequence (with firewall support) was published
// in an article on Codeguru by Phil Anderson.
// The code for the parsing of the different FTP LIST responses is taken from
// D. J. Bernstein (http://cr.yp.to/ftpparse.html). I only wrapped the c-code in
// a class.
// I haven't tested the code on other platforms, but i think with little
// modifications it would compile.
//
// Copyright (c) 2004 Thomas Oswald
//
// Permission to copy, use, sell and distribute this software is granted
// provided this copyright notice appears in all copies.
// Permission to modify the code and to distribute modified code is granted
// provided this copyright notice appears in all copies, and a notice
// that the code was modified is included with the copyright notice.
//
// This software is provided "as is" without express or implied warranty,
// and with no claim as to its suitability for any purpose.
//
// History:
// v1.1 released 2005-12-04
// - Bug in OpenPassiveDataConnection removed: SendCommand was called before data connection was established.
// - Bugs in GetSingleResponseLine removed:
// * Infinite loop if response line doesn't end with CRLF.
// * Return value of std:string->find must be checked against npos.
// - Now runs in unicode.
// - Streams removed.
// - Explicit detaching of observers are not necessary anymore.
// - ExecuteDatachannelCommand now accepts an ITransferNotification object.
// Through this concept there is no need to write the received files to a file.
// For example the bytes can be written only in memory or an other tcp stream.
// - Added an interface for the blocking socket (IBlockingSocket).
// Therefore it is possible to exchange the socket implementation, e.g. for
// writing unit tests (by simulating an specific scenario of an ftp communication).
// - Replaced the magic numbers concerning the reply codes by a class.
// v1.0 released 2004-10-25
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "FTPclient.h"
#include <tchar.h>
#include <limits>
#include <algorithm>
#ifdef __AFX_H__ // MFC only
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif
#endif
using namespace nsHelper;
using namespace nsSocket;
using namespace nsFTP;
long StringToLong(CStringA strIn)
{
CHAR* pStopString = 0;
return strtol(strIn.GetString(), &pStopString, 10);
}
//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////
/// constructor
/// @param[in] pSocket Instance of socket class which will be used for
/// communication with the ftp server.
/// CFTPClient class takes ownership of this instance.
/// It will be deleted on destruction of this object.
/// If this pointer is NULL, the CBlockingSocket implementation
/// will be used.
/// This gives the ability to set an other socket class.
/// For example a socket class can be implemented which simulates
/// a ftp server (for unit testing).
/// @param[in] uiTimeout Timeout used for socket operation.
/// @param[in] uiBufferSize Size of the buffer used for sending and receiving
/// data via sockets. The size have an influence on
/// the performance. Through empiric test i come to the
/// conclusion that 2048 is a good size.
/// @param[in] uiResponseWait Sleep time between receive calls to socket when getting
/// the response. Sometimes the socket hangs if no wait time
/// is set. Normally not wait time is necessary.
CFTPClient::CFTPClient(IBlockingSocket* pSocket/*=NULL*/, unsigned int uiTimeout/*=10*/,
unsigned int uiBufferSize/*=2048*/, unsigned int uiResponseWait/*=0*/) :
mc_uiTimeout(uiTimeout),
mc_uiResponseWait(uiResponseWait),
m_apSckControlConnection(pSocket)
{
mc_strEolCharacterSequence = "\r\n";
m_vBuffer.NewStack(uiBufferSize);
m_fTransferInProgress = false;
m_fAbortTransfer = false;
m_fResumeIfPossible = true;
m_pNotification = NULL;
ASSERT( pSocket );
}
CFTPClient::~CFTPClient()
{
if( IsTransferringData() )
Abort();
if( IsConnected() )
Logout();
}
/// Enables or disables resuming for file transfer operations.
/// @param[in] fEnable True enables resuming, false disables it.
void CFTPClient::SetResumeMode(bool fEnable/*=true*/)
{
m_fResumeIfPossible=fEnable;
}
/// Opens the control channel to the FTP server.
/// @param[in] strServerHost IP-address or name of the server
/// @param[in] iServerPort Port for channel. Usually this is port 21.
bool CFTPClient::OpenControlChannel(CStringA& strServerHost, USHORT ushServerPort/*=DEFAULT_FTP_PORT*/)
{
CloseControlChannel();
try
{
m_apSckControlConnection->Create(SOCK_STREAM);
CSockAddr adr = m_apSckControlConnection->GetHostByName(strServerHost.GetString(), ushServerPort);
m_apSckControlConnection->Connect(adr);
}
catch(CBlockingSocketException& blockingException)
{
ReportError(blockingException.GetErrorMessage(), __FILE__, __LINE__);
m_apSckControlConnection->Cleanup();
return false;
}
return true;
}
/// Returns the connection state of the client.
bool CFTPClient::IsConnected()
{
return m_apSckControlConnection->operator SOCKET()!=0;
}
/// Returns true if a download/upload is running, otherwise false.
bool CFTPClient::IsTransferringData()
{
return m_fTransferInProgress;
}
/// Closes the control channel to the FTP server.
void CFTPClient::CloseControlChannel()
{
try
{
m_apSckControlConnection->Close();
CRepresentation *pRep = m_apCurrentRepresentation.release();
delete pRep;
}
catch(CBlockingSocketException& blockingException)
{
blockingException.GetErrorMessage();
m_apSckControlConnection->Cleanup();
}
}
/// Analyse the repy code of a ftp-server-response.
/// @param[in] Reply Reply of a ftp server.
/// @retval FTP_OK All runs perfect.
/// @retval FTP_ERROR Something went wrong. An other response was expected.
/// @retval NOT_OK The command was not accepted.
int CFTPClient::SimpleErrorCheck(CReply& Reply)
{
if( Reply.Code().IsNegativeReply() )
return FTP_NOTOK;
else if( Reply.Code().IsPositiveCompletionReply() )
return FTP_OK;
ASSERT( Reply.Code().IsPositiveReply() );
return FTP_ERROR;
}
/// Logs on to an ftp-server.
/// @param[in] logonInfo Structure with logon information.
bool CFTPClient::Login(CLogonInfo& logonInfo)
{
m_LastLogonInfo = logonInfo;
enum {LO=-2, ///< Logged On
ER=-1, ///< Error
NUMLOGIN=9, ///< currently supports 9 different login sequences
};
int iLogonSeq[NUMLOGIN][18] = {
// this array stores all of the logon sequences for the various firewalls
// in blocks of 3 nums.
// 1st num is command to send,
// 2nd num is next point in logon sequence array if 200 series response
// is rec'd from server as the result of the command,
// 3rd num is next point in logon sequence if 300 series rec'd
{ 0,LO,3, 1,LO, 6, 2,LO,ER }, // no firewall
{ 3, 6,3, 4, 6,ER, 5,ER, 9, 0,LO,12, 1,LO,15, 2,LO,ER }, // SITE hostname
{ 3, 6,3, 4, 6,ER, 6,LO, 9, 1,LO,12, 2,LO,ER }, // USER after logon
{ 7, 3,3, 0,LO, 6, 1,LO, 9, 2,LO,ER }, // proxy OPEN
{ 3, 6,3, 4, 6,ER, 0,LO, 9, 1,LO,12, 2,LO,ER }, // Transparent
{ 6,LO,3, 1,LO, 6, 2,LO,ER }, // USER with no logon
{ 8, 6,3, 4, 6,ER, 0,LO, 9, 1,LO,12, 2,LO,ER }, // USER fireID@remotehost
{ 9,ER,3, 1,LO, 6, 2,LO,ER }, // USER remoteID@remotehost fireID
{10,LO,3, 11,LO, 6, 2,LO,ER } // USER remoteID@fireID@remotehost
};
// are we connecting directly to the host (logon type 0) or via a firewall? (logon type>0)
CStringA strTemp;
USHORT ushPort=0;
if( logonInfo.FwType() == CFirewallType::None())
{
strTemp = logonInfo.Hostname();
ushPort = logonInfo.Hostport();
}
else
{
strTemp = logonInfo.FwHost();
ushPort = logonInfo.FwPort();
}
CStringA strHostnamePort(logonInfo.Hostname());
if( logonInfo.Hostport()!=DEFAULT_FTP_PORT )
strHostnamePort = (CMakeString() << logonInfo.Hostname() << ":" << logonInfo.Hostport()).GetString(); // add port to hostname (only if port is not 21)
if( IsConnected() )
Logout();
if( !OpenControlChannel(strTemp, ushPort) )
return false;
// get initial connect msg off server
CReply Reply;
if( !GetResponse(Reply) || !Reply.Code().IsPositiveCompletionReply() )
return false;
int iLogonPoint=0;
// go through appropriate logon procedure
#pragma warning(disable:4127)
while( true )
#pragma warning(default:4127)
{
switch(iLogonSeq[logonInfo.FwType().AsEnum()][iLogonPoint])
{
case 0:
strTemp = (CMakeString() << "USER " << logonInfo.Username()).GetString();
break;
case 1:
strTemp= (CMakeString() << "PASS " << logonInfo.Password()).GetString();
break;
case 2:
strTemp= (CMakeString() << "ACCT " << logonInfo.Account()).GetString();
break;
case 3:
strTemp= (CMakeString() << "USER " << logonInfo.FwUsername()).GetString();
break;
case 4:
strTemp= (CMakeString() << "PASS " << logonInfo.FwPassword()).GetString();
break;
case 5:
strTemp= (CMakeString() << "SITE " << strHostnamePort).GetString();
break;
case 6:
strTemp= (CMakeString() << "USER " << logonInfo.Username() << "@" << strHostnamePort).GetString();
break;
case 7:
strTemp= (CMakeString() << "OPEN " << strHostnamePort).GetString();
break;
case 8:
strTemp= (CMakeString() << "USER " << logonInfo.FwUsername() << "@" << strHostnamePort).GetString();
break;
case 9:
strTemp= (CMakeString() << "USER " << logonInfo.Username() << "@" << strHostnamePort << " " << logonInfo.FwUsername()).GetString();
break;
case 10:
strTemp= (CMakeString() << "USER " << logonInfo.Username() << "@" << logonInfo.FwUsername() << "@" << strHostnamePort).GetString();
break;
case 11:
strTemp= (CMakeString() << "PASS " << logonInfo.Password() << "@" << logonInfo.FwPassword()).GetString();
break;
}
// send command, get response
CReply Reply;
if( !SendCommand(strTemp, Reply) )
return false;
if( !Reply.Code().IsPositiveCompletionReply() && !Reply.Code().IsPositiveIntermediateReply() )
return false;
unsigned int uiFirstDigitOfReplyCode = StringToLong(Reply.Code().Value())/100;
iLogonPoint=iLogonSeq[logonInfo.FwType().AsEnum()][iLogonPoint + uiFirstDigitOfReplyCode-1]; //get next command from array
switch(iLogonPoint)
{
case ER: // ER means somewhat has gone wrong
{
ReportError("Logon failed.", __FILE__, __LINE__);
}
return false;
case LO: // LO means we're fully logged on
if( ChangeWorkingDirectory("/")!=FTP_OK )
return false;
return true;
}
}
return false;
}
/// Rename a file on the ftp server.
/// @param[in] strOldName Name of the file to rename.
/// @param[in] strNewName The new name for the file.
/// @return see return values of CFTPClient::SimpleErrorCheck
int CFTPClient::Rename(CStringA& strOldName, CStringA& strNewName)
{
CReply Reply;
CStringA strCmd;
strCmd = (CMakeString() << "RNFR " << strOldName).GetString();
if( !SendCommand(strCmd, Reply) )
return FTP_ERROR;
if( Reply.Code().IsNegativeReply() )
return FTP_NOTOK;
else if( !Reply.Code().IsPositiveIntermediateReply() )
{
ASSERT( Reply.Code().IsPositiveCompletionReply() || Reply.Code().IsPositivePreliminaryReply() );
return FTP_ERROR;
}
strCmd = (CMakeString() << "RNTO " << strNewName).GetString();
if( !SendCommand(strCmd, Reply) )
return FTP_ERROR;
return SimpleErrorCheck(Reply);
}
/// Gets the directory listing of the ftp-server. Sends the LIST command to
/// the ftp-server.
/// @param[in] strPath Starting path for the list command.
/// @param[out] vstrFileList Returns a simple list of the files and folders of the specified directory.
/// @param[in] fPasv see documentation of CFTPClient::Passive
bool CFTPClient::List(CStringA strPath, TStringVector& vstrFileList, bool fPasv)
{
COutputStream outputStream(mc_strEolCharacterSequence);
if( !ExecuteDatachannelCommand(CDatachannelCmd::LIST(), strPath, CRepresentation(CType::ASCII()), fPasv, 0, &outputStream) )
return false;
vstrFileList.clear();
CStringA strLine;
outputStream.SetStartPosition();
while( outputStream.GetNextLine(strLine) )
vstrFileList.push_back(strPath + strLine);
return true;
}
/// Gets the directory listing of the ftp-server. Sends the NLST command to
/// the ftp-server.
/// @param[in] strPath Starting path for the list command.
/// @param[out] vstrFileList Returns a simple list of the files and folders of the specified the directory.
/// @param[in] fPasv see documentation of CFTPClient::Passive
bool CFTPClient::NameList(CStringA strPath, TStringVector& vstrFileList, bool fPasv)
{
COutputStream outputStream(mc_strEolCharacterSequence);
if( !ExecuteDatachannelCommand(CDatachannelCmd::NLST(), strPath, CRepresentation(CType::ASCII()), fPasv, 0, &outputStream) )
return false;
vstrFileList.clear();
CStringA strLine;
outputStream.SetStartPosition();
while( outputStream.GetNextLine(strLine) )
vstrFileList.push_back(strPath + strLine);
return true;
}
/// Gets the directory listing of the ftp-server. Sends the LIST command to
/// the ftp-server.
/// @param[in] strPath Starting path for the list command.
/// @param[out] vFileList Returns a detailed list of the files and folders of the specified directory.
/// vFileList contains CFTPFileStatus-Objects. These Objects provide a lot of
/// information about the file/folder.
/// @param[in] fPasv see documentation of CFTPClient::Passive
bool CFTPClient::List(CStringA strPath, TSpFTPFileStatusVector& vFileList, bool fPasv)
{
COutputStream outputStream(mc_strEolCharacterSequence);
if( !ExecuteDatachannelCommand(CDatachannelCmd::LIST(), strPath, CRepresentation(CType::ASCII()), fPasv, 0, &outputStream) )
return false;
vFileList.clear();
CStringA strLine;
CFTPListParse ftpListParser;
outputStream.SetStartPosition();
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -