Click here to Skip to main content
Click here to Skip to main content

FTP Client Class

By , 8 Dec 2012
 

Introduction

CFTPClient is a class to encapsulate the FTP protocol. I have tried to implement it as platform independent. For the purpose of communication, I have used the classes CBlockingSocket, CSockAddr, ... from David J. Kruglinski's "Inside Visual C++". These classes are only small wrappers for the sockets-API. Further, I have 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 different FTP LIST responses is taken from D. J. Bernstein's (parsing code). 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 and run smoothly.

The main features are:

  • not based on MFC-sockets,
  • not using other MFC-stuffs like CString (uses STL),
  • supports Firewalls,
  • supports resuming,
  • supports file eXchange Protocol (FXP) - uses FTP to transfer data directly from one remote server to another (servers must support this feature),
  • testet under Windows with Visual Studio 2008,
  • testet under Linux (Suse 11.4) with Qt,
  • smart pointer implementation can be easily replaced with boost::shared_ptr or std::shared_ptr by defining USE_BOOST_SMART_PTR or USE_STD_SMART_PTR,
  • parser which parses the output of the LIST command can be replaced by implementing the interface "IFileListParser",
  • can be easily extended.

The example shows how easy it is to use this class. With a few lines of code you can log the communication or visualize file transfers. Notice: The example is not a fully functional FTP-client-application. The example application is only for Windows platforms.

Background

The official specification of the File Transfer Protocol (FTP) is the RFC 959. Most of the documentation in my code are taken from this RFC.

Using the code

There are a lot of classes. But most of them are just simple "datatypes". The most important ones are the following:

  • CFTPClient
    The heart of the application. It accepts a CLogonInfo object. Handles the complete communication with the FTP-server like:
    • get directory listing,
    • download/upload files,
    • delete directories/files,
    • walk through directory-tree,
    • passive mode,
    • ...
  • CLogonInfo
    A simple data structure for logon information, such as host, username, password, firewall, ...
  • CFTPClient::IFileListParser
    Interface for defining a parser class which can be set in the CFTPClient class for parsing the output of the LIST command.

  • CFTPClient::ITransferNotification
    Implementations of this interface can be used in the Download and Upload methods for controlling the byte streams which are be downloaded/uploaded. This can be used for example to download a file only into memory instead of a local file (see class COutputStream).

  • CFTPClient::CNotification
    The base class for notification mechanism. The class which derives from CFTPClient::CNotifaction can be attached to the CFTPClient class as an observer. The CFTPClient object notifies all the attached observers about the various actions (see example application):

    void TestFTP()
    {
       nsFTP::CFTPClient ftpClient;
       nsFTP::CLogonInfo logonInfo(_T("localhost"), 21, _T("anonymous"),
                                               _T("<a href="mailto:anonymous@user.com">anonymous@user.com"));
    
       // connect to server
       ftpClient.Login(logonInfo);
    
       // get directory listing
       nsFTP::TFTPFileStatusShPtrVec list;
       ftpClient.List(_T("/"), list);
    
       // iterate listing
       for( nsFTP::TFTPFileStatusShPtrVec::iterator it=list.begin();
                                                it!=list.end(); ++it )
           TRACE(_T("\n%s"), (*it)->Name().c_str());
    
       // do file operations
       ftpClient.DownloadFile(_T("/pub/test.txt"), _T("c:\\temp\\test.txt"));
    
       ftpClient.UploadFile(_T("c:\\temp\\test.txt"), _T("/upload/test.txt"));
    
       ftpClient.Rename(_T("/upload/test.txt"), _T("/upload/NewName.txt"));
    
       ftpClient.Delete(_T("/upload/NewName.txt"));
    
       // disconnect
       ftpClient.Logout();
    }
    
    void TestFXP()
    {
       nsFTP::CFTPClient ftpClientSource;
       nsFTP::CLogonInfo logonInfoSource(_T("sourceftpserver"), 21, _T("anonymous"),
                                                           _T("<a href="mailto:anonymous@user.com">anonymous@user.com"));
    
       nsFTP::CFTPClient ftpClientTarget;
       nsFTP::CLogonInfo logonInfoTarget(_T("targetftpserver"), 21, _T("anonymous"),
                                                           _T("<a href="mailto:anonymous@user.com">anonymous@user.com"));
    
       // connect to server
       ftpClientSource.Login(logonInfoSource);
       ftpClientTarget.Login(logonInfoTarget);
    
    
       // do file operations
       nsFTP::CFTPClient::TransferFile(ftpClientSource, _T("/file.txt"),
                                       ftpClientTarget, _T("/newFile.txt"));
    
    
       // disconnect
       ftpClientTarget.Logout();
       ftpClientSource.Logout();
    }
    
    
    void TestDownloadAsciiFileIntoTextBuffer()
    {
       nsFTP::CFTPClient ftpClientSource;
       nsFTP::CLogonInfo logonInfoSource(_T("sourceftpserver"), 21, _T("anonymous"),
                                                           _T("<a href="mailto:anonymous@user.com">anonymous@user.com</a>"));
    
       // connect to server
       ftpClientSource.Login(logonInfoSource);
    
       nsFTP::COutputStream outputStream(_T("\r\n"), _T("Example"));
    
       // do file operations
       ftpClientSource.DownloadFile(_T("/file.txt"), outputStream,
                                    nsFTP::CRepresentation(nsFTP::CType::ASCII()));
    
       tstring output = outputStream.GetBuffer();
    
       // disconnect
       ftpClientSource.Logout();
    }
    
    

History

  • 2004-10-25 - First public release.
  • 2005-12-04 - Version 1.1
    • Some interfaces changed (e.g. CNotification).
    • Bug in OpenPassiveDataConnection removed: SendCommand was called before data connection was established.
    • Bugs in GetSingleResponseLine removed:
      • Infinite loop if the 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 another 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 a specific scenario of a FTP communication).
    • Replaced the magic numbers concerning the reply codes with a class.
    • New example added. A console application created with Bloodshed Dev-C++. It is only a small application which should demonstrate the use of the classes in a non Microsoft environment.
  • 2012-12-02 - Version 2.0
    • fixed some bugs
    • introduced more "data types" for more secure interfaces
    • code modified so it also runs under Linux
    • support for file eXchange Protocol (FXP)

What will be done next

  • Example application with Linux GNU-C++.
  • New features for FTP client class (for example: copy and delete recursively).
  • Unit tests.

License

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

About the Author

otom
Software Developer (Senior)
Germany Germany
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralRe: Calling of ::List() does not react at allmemberMember 863475024 Apr '13 - 1:19 
Hi otom,
 
Firstly, thanks for this code sample, it is very clean and mostly works perfectly.
 
However, I think I have seen the same problem that is reported here, namely, the ::List() operation blocks forever.
 
I was wondering if you have seen this, or whether you have made any changes that might fix this in V2.0?
 
Thank you again,
 
Ben
BugSending/Receiving data using select()memberKHSIEMENS21 Dec '11 - 21:56 
I got the FTPClient Code compiled for Linux, but first it didn't work. The problem was,
that for some select() calls in the code, the first parameter nfds was set to 0. This doesn't
matter under Windows, because this parameter is ignored.
Under Linux, the first parameter in select() must be set to the highest file descriptor + 1
 
Regards,
KH
QuestionFtp Onresponse event [modified]memberpippo pioppo20 Dec '11 - 0:00 
Hi,
I'm testing your demo project, but when I run it, I don't obtain the logging response. Using a break point in function
void CFTPProtocolOutput::OnResponse(const tstring& strResponse)
on first line, the execution of the application is not stopped. Of course response string is not logging.
 
Can you help me to understand why this event occurred?
 
10x
 
===============================================================
Ok,
I found the solution to what was previously reported.
In the example you have made
 
void CFTPProtocolOutput: OnResponse (const & tstring strResponse)
 
but evidently, after this, you have modified the parent object
and then you must override
 
void CFTPProtocolOutput: OnResponse (const CReply & reply)
 
and working properly.
 
Because I have written, I must point out that in the function
 
void CFTPProtocolOutput: OnSendCommand (const tstring & strCommand)
 
you have entered the instruction
 
if (strCommand.length ()> 4 & & strCommand.substr (5) == _T ("PASS"))
 
but, at least with VS2008, this does not work. I corrected it in
 
if (strCommand.length ()> 4 & & strCommand.substr (0, 5) == _T ("PASS"))
 
and it's work fine.
 
Excellent work,
10x
Sandro


modified 20 Dec '11 - 8:48.

QuestionLicence??memberpippo pioppo8 Dec '11 - 23:45 
Hi,
I'll want to know what's kind of licence is applied to this project. Can I use it for free in commercial and/or open source code?
 
10x
Sandro
Sandro

AnswerRe: Licence??memberotom11 Dec '11 - 6:07 
yes, you can use it in commercial applications; i have set the license to "The Code Project Open License (CPOL)"
QuestionGettingmemberSaviovt1020 Nov '11 - 23:12 
Hello Sir,
 
I am using your source code in my project & Its working fine. But I have one problem I could not find the size of file which is on server(Linux). I am getting following message.
 
550 SIZE not allowed in ASCII mode
 
Could please provide solution to calculate size of file.
 
Regards,
Savio
AnswerRe: Gettingmemberotom11 Dec '11 - 6:13 
it seems that the server doesn't understand the SIZE command; you have to check the server; maybe there is another command that you can send; in windows you can use the Microsoft command line tool "ftp.exe"; first you have to connect to the FTP server and then you must type "remotehelp"; the server should show you the commands which are supported
QuestionReg: Writing a stream over FTPSessionmemberSivaram Kannan3 Aug '11 - 4:10 
Hi,
 
Thanks for the project. It works well, and the code is great and understandable.
 
I have a requirement to write a Data Stream instead of File stream over a FTP session. Would that be possible by modifying this code? This is my requirement,
 
1. Establish a ftp session
2. Create the target file on the ftp session.
3. Send a data send a data stream over the session.
4. Once data stream is done, close the session.
 
I could not visualize what I need to modify to achieve this. Is this achievable, if yes can you tell me your views what I should do to achieve this?
 
Siva
QuestionRegarding secure FTPmemberSaviovt1012 Jul '11 - 19:51 
I am using your source code for FTP communication. But when I try to connect with secure FTP I could not upload data on secure FTP. So how do I communicate with secure FTP?

Thanks & Regards,
Savio
AnswerRe: Regarding secure FTPmemberotom12 Jul '11 - 21:03 
SFTP is NOT RELATED to FTP. This means that you can't use my classes for SFTP communication. I don't know if there exists free classes for doing SFTP stuff.
GeneralWSAStartup Problemmemberwolf30572 Jan '11 - 14:58 
Good job, Thanks! Smile | :)
I have take this project to use in VC7 for a MFC App.
I find that you must add the WSAStartup to initial the socket.
But why your demo project don't need the WSAStartup.
Generalerror when download file to strLocalFile which contains chinese wordmemberSeshaal4 May '10 - 17:36 
dear otom
 
when using CFTPClient::DownloadFile,I set strLocalFile value with chinese word, such as _T("D:\\下载文件夹\\down.txt").
the ConvertToString(strFileName).c_str() in
m_pFile = fopen(CCnv::ConvertToString(strFileName).c_str(),
CCnv::ConvertToString(strMode).c_str());
get wrong. not “D:\\下载文件夹\\down.txt”.
 
i change the code in definements.h, then it goes right
 
static std::string& ConvertToString(const tstring& strIn, std::string& strOut)
{
#ifdef _UNICODE
if( strIn.size() == 0 )
{
strOut.clear();
}
else
{
setlocale(LC_ALL, ".936");
int nSizeRequired = wcstombs(NULL, strIn.c_str(), 0);
strOut.resize(nSizeRequired);
wcstombs(&*strOut.begin(), strIn.c_str(), nSizeRequired);
//strOut.resize(strIn.size());
//wcstombs(&*strOut.begin(), strIn.c_str(), strOut.size());
}
#else
strOut = strIn;
#endif
return strOut;
}
 

but i think the better way to fix this problem, i should fix CFile, make it support unicode,such as using
_tfopen instead of fopen. Maybe you have a better way, please tell me
GeneralAbort takes a lot of time!memberSpringMVC19 Mar '10 - 0:43 
Hello.
First of all, it's really a good ftp library, but, unfortunately, I have one problem with it.
Abort command takes a lot of time. I did some debugging, and noticed, that after "ABOR" command is sent
GetResponse(Reply) - at line 1002 in FTPClient.cpp takes a lot of time.
I tried provided example with Progress Dialog with two different ftp servers.
In both cases, when I press Abort button, Progress Dialog disappears and everything is disabled(I can't even move main window)
for a noticeable amount of time(less than minute, but still).
Any help would be highly appreciated!
P.S Sorry for my english Smile | :)
QuestionAny one can help me to download file on ftp server recursively by using these codes?membersongge17 Dec '09 - 21:28 
Any one can help me to download file on ftp server recursively by using these codes?
Thanks a lot!
 
songge

GeneralCompilling and running under VC++ 2002memberLeandro T C Melo14 May '09 - 4:28 
Nice! Just in case anyone needs... To run the simple example from the article I had to:
 
- #include <ctime> and #include <cassert> in Definements.h.
- Add Ws2_32.lib to the linker dependencies (this is already mentioned in another post).
- Call WSAStartup.
GeneralRe: Compilling and running under VC++ 2002memberJackSimmons1 Jun '09 - 13:17 
Many thanks to Leandro for his insights into initializing the use of Winsock, and for his other hints.
 
What is needed for the raw source zip is an example main. Here is one based on Leandro's advice, MSDN documentation for WSAStartup, and the example code given in the original posting.
 
When populated with my account particulars, this code lists the destination directory. (Sorry but this post has all its indentations removed.)
 

// FTP_Test.cpp : Defines the entry point for the console application.
//
 
#include
#include
#include "FTPClient.h"
 
int _tmain(int argc, _TCHAR* argv[])
{
int port_number = 21;
nsFTP::CFTPClient ftpClient;
nsFTP::CLogonInfo logonInfo(_T(""), port_number,
_T(""), _T(""));
 
// Initialize use of Winsock DLL by a process
 
WORD wVersionRequested;
WSADATA wsaData;
int err;
 
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
 
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable Winsock DLL. */
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
 
/* Confirm that the WinSock DLL supports 2.2.*/
/* Note that if the DLL supports versions greater */
/* than 2.2 in addition to 2.2, it will still return */
/* 2.2 in wVersion since that is the version we */
/* requested. */
 
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
/* Tell the user that we could not find a usable */
/* WinSock DLL. */
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else printf("The Winsock 2.2 dll was found okay\n");

/* The Winsock DLL is acceptable. Proceed to use it. */
/* Add network programming using Winsock here */
// In our case, do the ftp work
 
// connect to server
ftpClient.Login(logonInfo);
 
// get directory listing
nsFTP::TSpFTPFileStatusVector list;
ftpClient.List(_T("/"), list);
 
// iterate listing
for( nsFTP::TSpFTPFileStatusVector::iterator it=list.begin();
it!=list.end(); ++it )
printf("\n%s", (*it)->Name().c_str());
 
// do file operations
 
//ftpClient.DownloadFile(_T("/pub/test.txt"),
_T("c:\\temp\\test.txt"));
 
//ftpClient.UploadFile(_T("c:\\temp\\test.txt"),
_T("/upload/test.txt"));
 
//ftpClient.Delete(_T("/upload/NewName.txt"));
 
// disconnect
ftpClient.Logout();
 
/* Call WSACleanup when done using the Winsock dll */
WSACleanup();
 
// Finished
return 0;
}
GeneralBug in CFTPClient::UploadFilememberGluck23 Apr '09 - 23:12 
Begin of uploaded file is truncated, if m_fResumeIfPossible is true and name of file starts with digits and file not exists in server folder.
 
Need to check result of FileSize() like following:
 
   long lRemoteFileSize = 0;
   if( m_fResumeIfPossible ) {
      if (FTP_OK != FileSize(strRemoteFile, lRemoteFileSize)) lRemoteFileSize = 0;
   }
 
Also possible need fix FileSize() function to set lSize = 0 on error
 
PS: sorry for my bad English...
General[Message Deleted]memberit.ragester2 Apr '09 - 21:58 
[Message Deleted]
GeneralA very easy port to LinuxmemberMember 455517230 Sep '08 - 6:14 
Very nice. Thank you. most of the problems i had were with the windows data types (USHORT, DWORD, SOCKADDR, etc.). i created another header file "ftp.h" to hold the typedefs and a few other definitions. also had to change the calls to "select".
 
still need to write a program to test all the functions, but from what i have seen it seems to work quite well.
 
thank you.
Charles Wright
GeneralA comfortable FTP class in .NETmemberElmue27 Aug '08 - 12:18 
Hello
 
If you search a comfortable and reusable FTP client,
 
-- which is running on .NET Framework 1.1 or higher,
-- which can automatically put together splitted files on the server,
-- which allows to download only a part of a file on the server,
-- which allows to resume any broken download,
-- which automatically starts a separate thread,
-- which can be aborted any time from your main thread,
-- which supports UTF8 encoded filenames,
-- which has a built-in download scheduler,
-- which has a built-in bandwidth control,
-- which has a built-in preview function for the download of movies,
-- which automatically reconnects the server after an error has occurred,
-- which displays download progress in percent and in bytes and the remaining time,
-- which writes a detailed logging for all operations it does,
-- which is based on Wininet.dll and has one workaround for each of the 4 known Wininet.dll bugs,
-- which is very well tested and bug-free,
-- which is written by a very experienced programmer and has a very clean and well documented sourcecode,
 
then have a look at this project:
 
ElmueSoft Partial FTP Downloader[^]
 

Elmü
QuestionHas anyone got this to compile with VC6?memberpscholl21 Jul '08 - 5:28 
Apparently quite a few people have already tried to compile this with VC6 - but has anyone managed to compile this code without errors? What modifications are necessary and where? Please share any information that you may have. Thank you.
AnswerRe: Has anyone got this to compile with VC6?memberDamDaDum28 Oct '08 - 3:26 
Hello,
 
I am also facing the problem, programming in VC6. As a matter of fact there are tons of ftpclient classes available but only the least work with c++ (without .net).
 
This class looks just perfect for my needs, unfortunately I cannot get it to work.
 
I get the following linker errors no matter what I try.
 
wnFTP.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: virtual __thiscall nsFTP::CFTPClient::~CFTPClient(void)" (??1CFTPClient@nsFTP@@UAE@XZ)
ownFTPDlg.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: virtual __thiscall nsFTP::CFTPClient::~CFTPClient(void)" (??1CFTPClient@nsFTP@@UAE@XZ)
ownFTPDlg.obj : error LNK2001: Nichtaufgeloestes externes Symbol "public: __thiscall nsFTP::CFTPClient::CFTPClient(class nsSocket::IBlockingSocket *,unsigned int,unsigned int,unsigned int)" (??0CFTPClient@nsFTP@@QAE@PAVIBlockingSocket@nsSocket@@III@
Z)
ownFTPDlg.obj : error LNK2001: Nichtaufgeloestes externes Symbol "class nsSocket::IBlockingSocket * __cdecl nsSocket::CreateDefaultBlockingSocketInstance(void)" (?CreateDefaultBlockingSocketInstance@nsSocket@@YAPAVIBlockingSocket@1@XZ)
Debug/ownFTP.exe : fatal error LNK1120: 3 unaufgeloeste externe Verweise
Nichtaufgeloestes externes Symbol should mean sth. like unreferenced external symbol
 
Can anyone help me?
 
I have tried to add the Ws2_32.lib to my project, also to include the winsock2.h and the windows.h in the stdafx.h, but it won't work.
 
These linker errors occur, no matter if the lib is included or the winsock2 is included or neither of them.
 
I am trying to build a mfc windows application. It is completely empty except the definition nsFTP::CFTPClient m_ftp; in the header.
 
Thanks in advance,
Richard
General...memberAHTOXA18 Jun '08 - 2:41 
To use the example code WSAStartup() is required...
GeneralSOCKET handle(Control Link which listen to accept data link) unclosed when calling UploadmemberMember 458698613 Apr '08 - 22:10 
bool CFTPClient::ExecuteDatachannelCommand(const CDatachannelCmd& crDatachannelCmd, const tstring& strPath, const CRepresentation& representation, bool fPasv, DWORD dwByteOffset, ITransferNotification* pObserver) const
{
if( m_fTransferInProgress )
return false;
 
if( !IsConnected() )
return false;
 
// check representation
if( m_apCurrentRepresentation.get()==NULL )
m_apCurrentRepresentation.reset(new CRepresentation(CType::ASCII()));

if( representation!=*m_apCurrentRepresentation )
{
// transmit representation to server
if( RepresentationType(representation)!=FTP_OK )
return false;
*m_apCurrentRepresentation = representation;
}
 
std::auto_ptr apSckDataConnection(m_apSckControlConnection->CreateInstance());
if( fPasv )
{
if( !OpenPassiveDataConnection(*apSckDataConnection, crDatachannelCmd, strPath, dwByteOffset) )
return false;
}
else
{
if( !OpenActiveDataConnection(*apSckDataConnection, crDatachannelCmd, strPath, dwByteOffset) )
return false;
}
 
const bool fTransferOK = TransferData(crDatachannelCmd, pObserver, *apSckDataConnection);

apSckDataConnection->Close();
 
// get response from ftp server
CReply Reply;
if( !fTransferOK || !GetResponse(Reply) || !Reply.Code().IsPositiveCompletionReply() )
return false;
 
return true;
}
GeneralRe: SOCKET handle(Control Link which listen to accept data link) unclosed when calling Uploadmemberotom14 Apr '08 - 7:50 
You are right. There is a bug in the CBlockingSocket class (see forum entry "Bug on connection break."). Add the following destructor to the file "BlockingSocket.cpp".
CBlockingSocket::~CBlockingSocket()
{
Cleanup();
}
After defining this destructor you can remove the line "apSckDataConnection->Close();" from ExecuteDatachannelCommand. The solution with the destructor is better than calling it directly.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 9 Dec 2012
Article Copyright 2004 by otom
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid