FTP upload/download over TCP
FTP is standard protocol to transfer files by using separate control and data connections between the client and server. There are two operating modes - active and passive. Details on the available commands and how to use them can be found on the web. Here, a working C++ code is shown which downloads and uploads files in both modes. To simplify the work, Boost library is used for dealing with sockets. For the purpose of samples, FTP server on this domain is used, but the username and password in the source code are hidden for obvious reasons.:) To use them, just replace server parameters to FTP commands with your own. In all samples, commands and file sizes are small enough to be hold in given buffers with single send/receive commands. For the larger ones, send/receive must be executed while all data is sent/received, find a socket tutorial for that.
Downloading in passive mode means that a socket connects to the FTP server and retreives a given file at the given directory. Two sockets are used - one for executing commands, and other to get the file. Those two connections are performed simultaneously in two threads.
#include <iostream> #include <string> #include <vector> #include <utility> #include <cstdio> #include <boost/asio.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/thread.hpp> using std::cout; using std::endl; using std::vector; using std::string; using std::pair; using namespace boost::asio; using boost::system::error_code; using boost::system::system_error; using boost::thread; struct callable { private: string _hostname; string _port; public: callable(const string& hostname, const string& port) : _hostname(hostname), _port(port) { } void operator()() { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query(_hostname, _port); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); const int BUFLEN = 8192; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; } }; pair<string, string> parseHostPort(string s) { size_t paramsPos = s.find('('); string params = s.substr(paramsPos + 1); size_t ip1Pos = params.find(','); string ip1 = params.substr(0, ip1Pos); size_t ip2Pos = params.find(',', ip1Pos + 1); string ip2 = params.substr(ip1Pos + 1, ip2Pos - ip1Pos - 1); size_t ip3Pos = params.find(',', ip2Pos + 1); string ip3 = params.substr(ip2Pos + 1, ip3Pos - ip2Pos - 1); size_t ip4Pos = params.find(',', ip3Pos + 1); string ip4 = params.substr(ip3Pos + 1, ip4Pos - ip3Pos - 1); size_t port1Pos = params.find(',', ip4Pos + 1); string port1 = params.substr(ip4Pos + 1, port1Pos - ip4Pos - 1); size_t port2Pos = params.find(')', port1Pos + 1); string port2 = params.substr(port1Pos + 1, port2Pos - port1Pos - 1); pair<string, string> hostPort; hostPort.first = ip1 + "." + ip2 + "." + ip3 + "." + ip4; int portVal = atoi(port1.c_str()) * 256 + atoi(port2.c_str()); char portStr[10]; sprintf(portStr, "%d", portVal); hostPort.second = string(portStr); return hostPort; } int main() { try { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query("ftp.alepho.com", "21"); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); const int BUFLEN = 1024; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; string request = "USER ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASS ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "CWD alepho.com/public_html\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASV\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; pair<string, string> portHost = parseHostPort(string(buf.data(), len)); callable call(portHost.first, portHost.second); thread th(call); request = "RETR index.php\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; th.join(); } catch (system_error& exc) { cout << "main(): exc.what()=" << exc.what() << endl; } return EXIT_SUCCESS; }
Downloading in active mode means that the FTP server connects to the client's machine on the given port. So, client gives it address in the local network (192.168.1.102) and the port 50000. FTP server will refuse the given address and use the public IP address. (If the client is behind proxy or NAT, probably the active method will not work.) When client accepts server's connection, it can receive the content over socket. Again, one thread uses socket for sending commands and another to receive the file.
#include <iostream> #include <string> #include <vector> #include <utility> #include <cstdio> #include <boost/asio.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/thread.hpp> using std::cout; using std::endl; using std::vector; using std::string; using std::pair; using namespace boost::asio; using boost::asio::ip::tcp; using boost::system::error_code; using boost::system::system_error; using boost::thread; struct callable { private: string _hostname; string _port; public: callable(const string& hostname, const string& port) : _hostname(hostname), _port(port) { } void operator()() { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query(_hostname, _port); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint ep = *it; tcp::acceptor server(ios, ep); tcp::socket client(ios); server.accept(client); cout << "main(): client accepted from " << client.remote_endpoint().address() << endl; const int BUFLEN = 8192; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; } }; int main() { try { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query("ftp.alepho.com", "21"); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); const int BUFLEN = 1024; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; string request = "USER ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASS ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PORT 192,168,1,102,195,80\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; callable call("192.168.1.102", "50000"); thread th(call); request = "RETR alepho.com/public_html/index.php\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; th.join(); } catch (system_error& exc) { cout << "main(): exc.what()=" << exc.what() << endl; } return EXIT_SUCCESS; }
Uploading in passive mode is similar to downloading in passive method except that file is sent over socket.
#include <iostream> #include <string> #include <vector> #include <utility> #include <cstdio> #include <boost/asio.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/thread.hpp> using std::cout; using std::endl; using std::vector; using std::string; using std::pair; using namespace boost::asio; using boost::system::error_code; using boost::system::system_error; using boost::thread; struct callable { private: string _hostname; string _port; public: callable(const string& hostname, const string& port) : _hostname(hostname), _port(port) { } void operator()() { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query(_hostname, _port); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); string content = "Hello, World!"; client.send(buffer(content, content.size())); cout << "Content sent" << endl; } }; pair<string, string> parseHostPort(string s) { size_t paramsPos = s.find('('); string params = s.substr(paramsPos + 1); size_t ip1Pos = params.find(','); string ip1 = params.substr(0, ip1Pos); size_t ip2Pos = params.find(',', ip1Pos + 1); string ip2 = params.substr(ip1Pos + 1, ip2Pos - ip1Pos - 1); size_t ip3Pos = params.find(',', ip2Pos + 1); string ip3 = params.substr(ip2Pos + 1, ip3Pos - ip2Pos - 1); size_t ip4Pos = params.find(',', ip3Pos + 1); string ip4 = params.substr(ip3Pos + 1, ip4Pos - ip3Pos - 1); size_t port1Pos = params.find(',', ip4Pos + 1); string port1 = params.substr(ip4Pos + 1, port1Pos - ip4Pos - 1); size_t port2Pos = params.find(')', port1Pos + 1); string port2 = params.substr(port1Pos + 1, port2Pos - port1Pos - 1); pair<string, string> hostPort; hostPort.first = ip1 + "." + ip2 + "." + ip3 + "." + ip4; int portVal = atoi(port1.c_str()) * 256 + atoi(port2.c_str()); char portStr[10]; sprintf(portStr, "%d", portVal); hostPort.second = string(portStr); return hostPort; } int main() { try { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query("ftp.alepho.com", "21"); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); const int BUFLEN = 1024; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; string request = "USER ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASS ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "CWD alepho.com/public_html\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASV\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; pair<string, string> portHost = parseHostPort(string(buf.data(), len)); callable call(portHost.first, portHost.second); thread th(call); request = "STOR hello.txt\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; th.join(); } catch (system_error& exc) { cout << "main(): exc.what()=" << exc.what() << endl; } return EXIT_SUCCESS; }
Finally, uploading in active mode is similar to downloading in active method except that file is sent over socket.
#include <iostream> #include <string> #include <vector> #include <utility> #include <cstdio> #include <boost/asio.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/thread.hpp> using std::cout; using std::endl; using std::vector; using std::string; using std::pair; using namespace boost::asio; using boost::asio::ip::tcp; using boost::system::error_code; using boost::system::system_error; using boost::thread; struct callable { private: string _hostname; string _port; public: callable(const string& hostname, const string& port) : _hostname(hostname), _port(port) { } void operator()() { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query(_hostname, _port); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; tcp::acceptor server(ios, endpoint); tcp::socket client(ios); server.accept(client); cout << "main(): client accepted from " << client.remote_endpoint().address() << endl; string content = "Hello, World!"; client.send(buffer(content, content.size())); cout << "Content sent" << endl; } }; int main() { try { io_service ios; ip::tcp::resolver resolver(ios); ip::tcp::resolver::query query("ftp.alepho.com", "21"); ip::tcp::resolver::iterator it = resolver.resolve(query); ip::tcp::endpoint endpoint = *it; ip::tcp::socket client(ios); client.connect(endpoint); const int BUFLEN = 1024; vector<char> buf(BUFLEN); error_code error; int len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; string request = "USER ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PASS ***\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; request = "PORT 192,168,1,102,195,80\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; callable call("192.168.1.102", "50000"); thread th(call); request = "STOR alepho.com/public_html/hello.txt\r\n"; cout << request; client.send(buffer(request, request.size())); len = client.receive(buffer(buf, BUFLEN), 0, error); cout.write(buf.data(), len); cout << endl; th.join(); } catch (system_error& exc) { cout << "main(): exc.what()=" << exc.what() << endl; } return EXIT_SUCCESS; }
Samples are tested under Linux 2.6 64bit/gcc 4.5.2/boost 1.45 compiled as specified in the Makefile
.
CC = gcc CXX = g++ LFLAGS = -g LIBS = -lpthread -lboost_system -lboost_thread all: Makefile download_pasv download_act upload_pasv upload_act download_pasv: $(CXX) $(LIBS) download_pasv.cpp -odownload_pasv download_act: $(CXX) $(LIBS) download_act.cpp -odownload_act upload_pasv: $(CXX) $(LIBS) upload_pasv.cpp -oupload_pasv upload_act: $(CXX) $(LIBS) upload_act.cpp -oupload_act