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

FTP Client Class

By , 5 Dec 2005
 
This is an old version of the currently published article.

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 (ports to UNIX a lot easier),
  • not using other MFC-stuffs like CString (uses STL),
  • supports Firewalls,
  • supports resuming,
  • 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::CNotifaction

    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):

    nsFTP::CFTPClient ftpClient;
    nsFTP::CLogonInfo logonInfo("localhost", 21, "anonymous", 
                                          "anonymous@user.com");
    
    // connect to server
    ftpClient.Login(logonInfo);
    
    // get directory listing
    nsFTP::TSpFTPFileStatusVector list;
    ftpClient.List("/", list);
    
    // iterate listing
    for( nsFTP::TSpFTPFileStatusVector::iterator it=list.begin(); 
                                             it!=list.end(); ++it )
        TRACE("\n%s", (*it)->Name().c_str());
    
    // do file operations
    ftpClient.DownloadFile("/pub/test.txt", "c:\\temp\\test.txt");
    
    ftpClient.UploadFile("c:\\temp\\test.txt", "/upload/test.txt");
    
    ftpClient.RenameFile("/upload/test.txt", "/upload/NewName.txt");
    
    ftpClient.Delete("/upload/NewName.txt");
    
    // disconnect
    ftpClient.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.

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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

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


Discussions posted for the Published version of this article. Posting a message here will take you to the publicly available article in order to continue your conversation in public.
 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Suggestionone more adjustment for LinuxmemberMember 860761216 Apr '13 - 9:57 
changes in gcc 4.7. require unistd.h to be included. In BlockingSocket.h line number 34
#elif defined(unix)
   #include <sys/socket.h>
   #include <unistd.h>
   #include <arpa/inet.h>   // needed for inet_ntoa and inet_addr
   #include <netdb.h>       // needed for gethostbyname and gethostbyaddr
   #include <errno.h>
   #include <stddef.h>
#endif

GeneralWorks like a charmmemberrob_toutant7 Mar '13 - 21:54 
Needed to be able to sync an ftp folder to a local folder and your code worked right out of the box
 
Well written ( unlike my spaghetti code ) and no bugs that I can see
 
Thank you for posting the code and spending the time to document
Questionsome small adjustments for Linuxmembergrouby7521 Feb '13 - 2:19 
if compiled with -std=c++0x (c++11), you have a warning:
FTPClient.cpp:313:78: warning: 'auto_ptr' is deprecated
 
and an error:
BlockingSocket.cpp need: #include stddef.h for ptrdiff_t (line 407)
Questionhow to upload large File (>4GB)membergcpony26 Dec '12 - 22:48 
how to use this class when I want to upload large file which than 4GB,
 
thanks
yes

GeneralMy vote of 5memberMihai MOGA14 Dec '12 - 6:11 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
GeneralMy vote of 5memberjustdownloads13 Dec '12 - 0:46 
thanks a lot for this great article !
Questionis there resource leak?memberhaifeng21714 Aug '12 - 4:33 
hi, i found a problem when i use the code as follow
nsFTP::CFTPClient   m_LocalFTPClient;
nsFTP::CLogonInfo LogonInfo;
 
if (!m_LocalFTPClient.IsConnected())
{
   LogonInfo.SetHost(szSVRIP, 21, szUserName, szPassWD, static_cast<LPCTSTR>(""));
   if (m_LocalFTPClient.Login(LogonInfo))
   {
      nsFTP::TSpFTPFileStatusVector list;
      m_LocalFTPClient.List("/", list);
   }
}
 
when i comment out "m_LocalFTPClient.List("/", list);" there is no resource leak,otherwise
the process's memory is increased by 4k once the code is invoked, could anyone come aross
this problem?
Questiondownload files with filename matchingmembermallouna4 Jul '12 - 22:28 
how to download all files with filename matching using c++ from an ftp server
thanks
SuggestionGreat Library small bug for File Modification TimememberLefterisg30 May '12 - 6:03 
In method
 
int CFTPClient::FileModificationTime(const tstring& strPath, tstring& strModificationTime) const
 
Change line 1676 (iPos > -1) to the below
 
if( iPos != tstring::npos ) // instead of (iPos > -1)
 
Leaving to (iPos > -1) is not working properly with the compiler I use, it always fails. Since -1 for unsigned is also the biggest possible number.
QuestionChange protocol Typememberjazaman14 May '12 - 19:31 
In BlockingSocket.cpp function
void CBlockingSocket::Create(int nType /* = SOCK_STREAM */)
 
Had to change
 
if( (m_hSocket=socket(AF_INET, nType, 0)
 
to
 
if( (m_hSocket=socket(AF_INET, nType, IPPROTO_TCP)
 
Otherwise the socket never received a response.
QuestionCan't compile the codememberHooch18012 Feb '12 - 4:35 
Hello.
I can't compile the code.
I'm getting those errors.
 
Cannot open include file: stdafx.h
 
This error occures in those files:
BlockingSocket.cpp line 8
FTPClient.cpp line 49
FTPDataTypes line 16
FTPFIleState.cpp line 16
FTPListPare line 29
AnswerRe: Can't compile the codememberFrank Heimes11 Mar '12 - 23:09 
The stdafx.h/stdafx.cpp files are Microsoft specific extensions.
 
You need to open FTPexample.sln using Microsoft Visual Studio in order to build the project without code changes.
 
If you use another compiler and linker (like gcc) then you need to port the code for your environment.
 
Please google "porting Visual Studio projects to XYZ....".
QuestionCalling of ::List() does not react at allmemberMember 440367423 Dec '11 - 5:00 
Hi otom,
I try to retrieve all folders and files on the ftp-server and an instance of CFTPClient runs in a thread. but i get always a problem when excuting List(), just like this:
 
nsFTP::TSpFTPFileStatusVector list;
if (!client.List(url.toStdWString(), list)) { .... }
 
so the thread is blocked... as i debugged, i saw that occurred in the function
CFTPClient::OpenActiveDataConnection(...) {
...
if( !apSckServer->Accept(sckDataConnection, sockAddrTemp) ) {
...
}
and in the function
bool CBlockingSocket::Accept(...) {
...
pConnect->m_hSocket = accept(m_hSocket, psa, &nLengthAddr);
...
}
 
Calling of accept(m_hSocket, psa, &nLengthAddr) returns never!
this phenomenon occurs at some subfolders on the server that i have access to them.
 
any idea? thank you
 
aurora
AnswerRe: Calling of ::List() does not react at allmemberotom23 Dec '11 - 6:00 
Have you more than one CFTPClient connection to the server open? The list command opens a data connection on the server. Sometimes servers do not allow to open more than one data connection from a client at the same time.
Maybe there is a bug in the posted version. I am going to release a new version soon.
GeneralRe: Calling of ::List() does not react at all [modified]memberMember 440367423 Dec '11 - 6:54 
Thank you Otom
Danke!
No. there is only one CFTPClient running. here is my code, very sample:
 

 

#include "stdafx.h"
#include "cstring"
 

typedef struct node_s {
std::wstring name;
std::wstring path;
int state;
} node_t;
 
bool itr_url(nsFTP::CFTPClient& client, const node_t& node)
{
if (node.name.empty()) {
return false;
}
nsFTP::TSpFTPFileStatusVector list;
if (!client.List(node.path, list)) {
return false;
}
printf("scanning %ls \n", node.name.c_str());
for(nsFTP::TSpFTPFileStatusVector::iterator it = list.begin();
it != list.end(); ++it) {
if ((*it)->IsCwdPossible()) { //this is a folder
node_t n;
n.name = (*it)->Name().c_str();
n.path = node.path + _T("/") + n.name;
n.state = 0;
if (!itr_url(client, n)) return false;
}
}
return true;
}
 
bool itr_url(nsFTP::CFTPClient& client, const std::wstring path)
{
 
nsFTP::TSpFTPFileStatusVector list;
if (!client.List(path, list)) return false;
printf("scanning %ls \n", path.c_str());
 
for(nsFTP::TSpFTPFileStatusVector::iterator it = list.begin();
it != list.end(); ++it) {
if ((*it)->IsCwdPossible()) { //this is a folder
node_t n;
n.name = (*it)->Name().c_str();
n.path = path + _T("/") + n.name;
n.state = 0;
if (!itr_url(client, n)) { return false; }
}
}
if (client.IsConnected()) client.Logout();
return true;
}
 

 
int _tmain(int argc, _TCHAR* argv[])
{
 
int port_number = 21;
nsFTP::CFTPClient ftpClient;
nsFTP::CLogonInfo logonInfo(_T("214.25.77.227"), 21,
_T("test"), _T("Q+683bjM"));

// Initialize use of Winsock DLL by a process
WORD wVersionRequested;
WSADATA wsaData;
int err;


wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
printf("WSAStartup failed with error: %d\n", err);
return 1;
}
 

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2) {

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");
 

ftpClient.Login(logonInfo);
 
itr_url(ftpClient, _T("./content"));
 
ftpClient.Logout();
WSACleanup();
return 0;
 
}
 
if you have many subfolders/files under ./content, you can see that it not work.
and you can also see that the processing of List() becomes very slow before it blocked...
could you help me?
 
Aurora

modified 23 Dec '11 - 13:02.

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.

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

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 6 Dec 2005
Article Copyright 2004 by otom
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid