#include <base/Application.h>
#include <base/string/FormatInputStream.h>
#include <base/string/FormatOutputStream.h>
#include <base/string/StringOutputStream.h>
#include <base/UnsignedInteger.h>
#include <base/net/StreamSocket.h>
#include <base/net/ServerSocket.h>
#include <base/net/InetInterface.h>
#include <base/net/InetService.h>
#include <base/net/InetEndPoint.h>
#include <base/concurrency/Thread.h>
#include <base/Primitives.h>
#include <base/Timer.h>
#include <base/io/File.h>
#include <base/net/Url.h>
using namespace com::azure::dev::base;
const Literal CMD_ACCOUNT = MESSAGE(
"ACCT");
const Literal CMD_CDUP = MESSAGE(
"CDUP");
const Literal CMD_CWD = MESSAGE(
"CWD");
const Literal CMD_LOGOUT = MESSAGE(
"QUIT");
const Literal CMD_PASSWORD = MESSAGE(
"PASS");
const Literal CMD_REINITIALIZE = MESSAGE(
"REIN");
const Literal CMD_USER = MESSAGE(
"USER");
const Literal CMD_DATA_PORT = MESSAGE(
"PORT");
const Literal CMD_PASSIVE = MESSAGE(
"PASV");
const Literal CMD_REPRESENTATION = MESSAGE(
"TYPE");
const Literal CMD_FILE_STRUCTURE = MESSAGE(
"STRU");
const Literal CMD_TRANSFER_MODE = MESSAGE(
"MODE");
const Literal CMD_RETRIEVE = MESSAGE(
"RETR");
const Literal CMD_STORE = MESSAGE(
"STOR");
const Literal CMD_STORE_UNIQUE = MESSAGE(
"STOU");
const Literal CMD_APPEND = MESSAGE(
"APPE");
const Literal CMD_ALLOCATE = MESSAGE(
"ALLO");
const Literal CMD_RESTART = MESSAGE(
"REST");
const Literal CMD_RENAME_FROM = MESSAGE(
"RNFR");
const Literal CMD_RENAME_TO = MESSAGE(
"RNTO");
const Literal CMD_ABORT = MESSAGE(
"ABOR");
const Literal CMD_DELETE = MESSAGE(
"DELE");
const Literal CMD_REMOVE_DIRECTORY = MESSAGE(
"RMD");
const Literal CMD_MAKE_DIRECTORY = MESSAGE(
"MKD");
const Literal CMD_PWD = MESSAGE(
"PWD");
const Literal CMD_LIST = MESSAGE(
"LIST");
const Literal CMD_NAME_LIST = MESSAGE(
"NLST");
const Literal CMD_SITE = MESSAGE(
"SITE");
const Literal CMD_SYSTEM = MESSAGE(
"SYST");
const Literal CMD_STATUS = MESSAGE(
"STAT");
const Literal CMD_HELP = MESSAGE(
"HELP");
const Literal CMD_NOOP = MESSAGE(
"NOOP");
public:
inline FTPException()
{
}
inline FTPException(const char* message)
{
}
};
class FileTransferProtocolClient :
public Object {
public:
static const unsigned int DEFAULT_RETRY_DELAY = 15;
static const unsigned int DEFAULT_RETRY_ATTEMPTS = 5;
enum Verbosity {SILENT, SHORT, ALL, DEBUG_NORMAL, DEBUG_EXTENDED};
enum Reply {POSITIVE_PRELIMINARY, POSITIVE, POSITIVE_INTERMEDIATE, NEGATIVE, PERMANENT_NEGATIVE};
enum ReplyGroup {SYNTAX, INFORMATION, CONNECTIONS, AUTHENTICATION, UNSPECIFIED, FILESYSTEM};
enum Representation {ASCII, EBCDIC, BINARY, LOCALSIZE};
enum Mode {STREAM, BLOCK, COMPRESSED};
enum Structure {FILE, RECORD, PAGE};
typedef struct {
Reply reply;
ReplyGroup group;
int minor;
int code;
bool valid;
} ReplyCode;
private:
bool responsePending = false;
ReplyCode replyCode;
Verbosity verbosity;
unsigned int retryDelay = 0;
unsigned int retryAttempts = 0;
protected:
static bool translateReplyCode(char a, char b, char c, ReplyCode& result)
{
result.valid = false;
switch (a) {
case '1':
result.reply = POSITIVE_PRELIMINARY;
break;
case '2':
result.reply = POSITIVE;
break;
case '3':
result.reply = POSITIVE_INTERMEDIATE;
break;
case '4':
result.reply = NEGATIVE;
break;
case '5':
result.reply = PERMANENT_NEGATIVE;
break;
default:
return false;
}
switch (b) {
case '0':
result.group = SYNTAX;
break;
case '1':
result.group = INFORMATION;
break;
case '2':
result.group = CONNECTIONS;
break;
case '3':
result.group = AUTHENTICATION;
break;
case '4':
result.group = UNSPECIFIED;
break;
case '5':
result.group = FILESYSTEM;
break;
default:
return false;
}
if ((c < '0') || (c > '9')) {
return false;
}
result.minor = static_cast<int>('c'-'0');
result.code = (static_cast<int>(a-'0') * 10 + static_cast<int>(b-'0')) * 10 + static_cast<int>(c-'0');
result.valid = true;
return true;
}
void getResponse()
{
replyCode.valid = false;
bool multipleLines = false;
int terminationCode = -1;
while (!instream.wait(1000000)) {
}
while (true) {
while (true) {
char ch = 0;
instream >> ch;
bassert(
FTPException("Reply contains invalid character.")
);
if (ch == '\n') {
break;
} else if (ch == '\r') {
} else {
line.append(ch);
}
}
if (verbosity >= ALL) {
fout << ">> " << line << ENDL;
}
ReplyCode replyCode;
if ((line.getLength() >= 4) && translateReplyCode(line[0], line[1], line[2], replyCode)) {
if (line[3] == '-') {
if (!multipleLines) {
terminationCode = replyCode.code;
multipleLines = true;
}
} else if (line[3] == ' ') {
if ((verbosity >= SHORT) && (verbosity < ALL)) {
fout << ">> " << line << ENDL;
}
if (!multipleLines || (multipleLines && (replyCode.code == terminationCode))) {
response = line;
this->replyCode = replyCode;
break;
}
}
} else if (!multipleLines) {
_throw FTPException("Invalid reply.");
}
}
responsePending = false;
bassert(replyCode.valid, FTPException("Failed to get reply."));
}
void requestPassword(
const String& password)
{
if (verbosity >= DEBUG_EXTENDED) {
fout << "DEBUG: Sending command '" << CMD_PASSWORD
<< "' with value '" << password << '\'' << ENDL;
}
responsePending = true;
outstream << CMD_PASSWORD << ' ' << password << "\r\n" << FLUSH;
}
void request(
const String& command)
{
if (verbosity >= DEBUG_EXTENDED) {
fout << "DEBUG: Sending command '" << command << "' with no value" << ENDL;
}
responsePending = true;
outstream << command << "\r\n" << FLUSH;
}
{
if (verbosity >= DEBUG_EXTENDED) {
fout << "DEBUG: Sending command '" << command << "' with value '" << value << '\'' << ENDL;
}
responsePending = true;
outstream << command << ' ' << value << "\r\n" << FLUSH;
}
public:
static bool isValidString(
const String& str)
{
return false;
}
return false;
}
}
return true;
}
static bool isValidPrintableString(
const String& str)
{
return false;
}
if ((*i < 33) || (*i > 126)) {
return false;
}
}
return true;
}
FileTransferProtocolClient(
InetEndPoint ep, Verbosity v = DEBUG_EXTENDED)
: endPoint(ep),
verbosity(v),
retryDelay(DEFAULT_RETRY_DELAY),
retryAttempts(DEFAULT_RETRY_ATTEMPTS)
{
}
unsigned int getRetryDelay() const
{
return retryDelay;
}
void setRetryDelay(unsigned int value)
{
retryDelay = value;
}
unsigned int getRetryAttempts() const
{
return retryAttempts;
}
void setRetryAttempts(unsigned int value)
{
retryAttempts = value;
}
void connect()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Establishing control connection to: "
<< endPoint << ENDL;
}
getResponse();
}
int sendUser(
const String& username)
{
bassert(isValidString(username),
InvalidFormat(
"Invalid FTP string."));
request(CMD_USER, username);
getResponse();
return replyCode.reply;
}
int sendPassword(
const String& password)
{
bassert(isValidString(password),
InvalidFormat(
"Invalid FTP string."));
requestPassword(password);
getResponse();
return replyCode.reply;
}
int sendAccount(
const String& account)
{
bassert(isValidString(account),
InvalidFormat(
"Invalid FTP string."));
request(CMD_ACCOUNT, account);
getResponse();
return replyCode.reply;
}
void sendReinitialize()
{
request(CMD_REINITIALIZE);
getResponse();
}
void sendHelp()
{
request(CMD_HELP);
getResponse();
}
void sendNoOperation()
{
request(CMD_NOOP);
getResponse();
}
void getSystemType()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Getting system type..." << ENDL;
}
request(CMD_SYSTEM);
getResponse();
}
void getStatus()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Getting status..." << ENDL;
}
request(CMD_STATUS);
getResponse();
}
void setType(Representation representation)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Setting type..." << ENDL;
}
switch (representation) {
case ASCII:
request(CMD_REPRESENTATION, "A");
break;
case BINARY:
request(CMD_REPRESENTATION, "I");
break;
case EBCDIC:
break;
case LOCALSIZE:
break;
}
getResponse();
}
void setStructure(Structure structure)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Setting file structure..." << ENDL;
}
switch (structure) {
case FILE:
request(CMD_FILE_STRUCTURE, "F");
break;
case RECORD:
request(CMD_FILE_STRUCTURE, "R");
break;
case PAGE:
request(CMD_FILE_STRUCTURE, "P");
break;
}
getResponse();
}
void setMode(Mode mode)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Setting mode..." << ENDL;
}
switch (mode) {
case STREAM:
request(CMD_TRANSFER_MODE, "S");
break;
case BLOCK:
request(CMD_TRANSFER_MODE, "B");
break;
case COMPRESSED:
request(CMD_TRANSFER_MODE, "C");
break;
}
getResponse();
}
void getList()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: List..." << ENDL;
}
request(CMD_LIST);
getResponse();
}
void readDirectoryList()
{
while (true) {
unsigned int result =
dataConnection.
read(buffer.getElements(), buffer.getSize(),
true);
if (result == 0) {
break;
}
const char* i = Cast::pointer<const char*>(buffer.getElements());
const char* end = i + result;
while (i < end) {
const char* p = i;
while ((i < end) && (*i != '\n') && (*i != '\r')) {
++i;
}
if (*i == '\r') {
++i;
if (*i == '\n') {
++i;
}
} else if (*i == '\n') {
++i;
if (*i == '\r') {
++i;
}
}
if (verbosity >= DEBUG_NORMAL) {
fout << "-> " << line << ENDL;
}
}
}
}
void readData(long long sizeOfFile)
{
long long bytesRead = 0;
while (bytesRead < sizeOfFile) {
unsigned int bytesToRead =
minimum<long long>(buffer.getSize(), sizeOfFile - bytesRead);
unsigned int result =
dataConnection.
read(buffer.getElements(), bytesToRead);
bytesRead += result;
file.write(buffer.getElements(), result);
if (verbosity >= SHORT) {
fout << "bytes read=" << bytesRead << ' '
<< "completed=" << static_cast<float>(bytesRead)/sizeOfFile*100 << '%' << ' '
<<
"rate=" << (1000000./1024. *
static_cast<float>(bytesRead)/timer.
getLiveMicroseconds()) <<
"kbs\r" << FLUSH;
}
}
fout << ENDL;
}
void restart(
const String& marker)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Restarting..." << ENDL;
}
bassert(isValidPrintableString(marker),
InvalidFormat(
"Invalid marker."));
request(CMD_RESTART, marker);
getResponse();
}
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Renaming file system object..." << ENDL;
}
bassert(isValidString(from) && isValidString(to),
InvalidFormat(
"Invalid path."));
request(CMD_RENAME_FROM, from);
request(CMD_RENAME_TO, to);
getResponse();
}
void deleteFile(
const String& path)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Deleting file system object..." << ENDL;
}
request(CMD_DELETE, path);
getResponse();
}
void getCurrentDirectory()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Getting current directory..." << ENDL;
}
request(CMD_PWD);
getResponse();
}
void changeDirectory(
const String& path)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Changing working directory..." << ENDL;
}
if (path == "..") {
request(CMD_CDUP);
getResponse();
} else {
request(CMD_CWD, path);
getResponse();
}
}
void makeDirectory(
const String& path)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Creating new directory..." << ENDL;
}
request(CMD_MAKE_DIRECTORY);
getResponse();
}
void removeDirectory(
const String& path)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Removing directory..." << ENDL;
}
request(CMD_REMOVE_DIRECTORY);
getResponse();
}
private:
String clipPassiveResponse(
const String& response,
int& index)
{
for (int count = 3;
count && (index < static_cast<int>(response.
getLength())) &&
(response[index] >= '0') && (response[index] <= '9'); --count) {
result += response[index++];
}
bassert(!result.isEmpty(), FTPException("Invalid reply."));
return result;
}
public:
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Sending user and password..." << ENDL;
}
switch (sendUser(username)) {
case POSITIVE:
break;
case POSITIVE_INTERMEDIATE:
break;
default:
_throw FTPException("Unable to login.");
}
switch (sendPassword(password)) {
case POSITIVE:
break;
default:
_throw FTPException("Unable to login.");
}
}
void logout()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Requesting logout..." << ENDL;
}
request(CMD_LOGOUT);
getResponse();
}
void getDirectoryList()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Retrieving directory list..." << ENDL;
}
request(CMD_LIST);
getResponse();
readDirectoryList();
getResponse();
}
void retrieveFile(
const String& filename)
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Retrieving file..." << ENDL;
}
request(CMD_RETRIEVE, filename);
getResponse();
int i = response.
indexOf(
" byte", 4);
bassert(i >= 0, FTPException("Invalid reply."));
--i;
bassert(
(response[i] >= '0') && (response[i] <= '9'),
FTPException("Invalid reply.")
);
while ((response[i] >= '0') && (response[i] <= '9')) {
--i;
}
++i;
long long numberOfBytes = 0;
while ((response[i] >= '0') && (response[i] <= '9')) {
numberOfBytes = (response[i++] - '0') + 10 * numberOfBytes;
}
request(CMD_LIST);
readData(numberOfBytes);
getResponse();
}
void abort()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Aborting..." << ENDL;
}
request(CMD_ABORT);
getResponse();
}
void requestActiveTransfer()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Requesting active transfer mode..." << ENDL;
}
endPoint << static_cast<unsigned int>(binaryAddress[0]) << ','
<< static_cast<unsigned int>(binaryAddress[1]) << ','
<< static_cast<unsigned int>(binaryAddress[2]) << ','
<< static_cast<unsigned int>(binaryAddress[3]) << ','
<< static_cast<unsigned int>(port >> 8) << ',' << static_cast<unsigned int>(port & 0xff) << FLUSH;
request(CMD_DATA_PORT, endPoint.
getString());
getResponse();
request(CMD_LIST);
dataConnection = serverDataConnection.
accept();
fout <<
"Incoming connection from: " << dataConnection.
getAddress() << ENDL;
getResponse();
}
void waitForConnection()
{
dataConnection = serverDataConnection.
accept();
fout <<
"Incoming connection from: " << dataConnection.
getAddress() << ENDL;
}
void requestPassiveTransfer()
{
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Requesting passive transfer mode..." << ENDL;
}
request(CMD_PASSIVE);
getResponse();
int i = 0;
for (i = 4; (i < static_cast<int>(response.
getLength())) && ((response[i] <
'0') || (response[i] >
'9')); ++i);
bassert(i <
static_cast<int>(response.
getLength()), FTPException(
"Invalid reply."));
for (unsigned int number = 0; number < 4; ++number) {
String result = clipPassiveResponse(response, i);
bassert(response[i++] == ',', FTPException("Invalid reply."));
address += result;
if (number < 3) {
address += '.';
}
}
String portHigh = clipPassiveResponse(response, i);
bassert(response[i++] == ',', FTPException("Invalid reply."));
String portLow = clipPassiveResponse(response, i);
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Establishing data connection to: "
}
dataConnection.
connect(endPoint.getAddress(), endPoint.getPort());
}
~FileTransferProtocolClient() {
if (verbosity >= DEBUG_NORMAL) {
fout << "DEBUG: Closing sockets..." << ENDL;
}
controlConnection.
close();
}
};
private:
FileTransferProtocolClient& client;
public:
MyThread(FileTransferProtocolClient& _client) : client(_client)
{
}
void run()
{
while (true) {
client.waitForConnection();
}
}
};
{
if (url.getScheme().isEmpty()) {
url.setScheme("ftp");
}
if (url.getUser().isEmpty()) {
url.setUser("anonymous");
}
if (url.getPassword().isEmpty()) {
url.setPassword("just@testing.my.app");
}
if (url.getPort().isEmpty()) {
url.setPort("21");
}
fout << "Individual parts of the specified url:" << EOL
<< " scheme: " << url.getScheme() << EOL
<< " user: " << url.getUser() << EOL
<< " password: " << url.getPassword() << EOL
<< " host: " << url.getHost() << EOL
<< " port: " << url.getPort() << EOL
<< " path: " << url.getPath() << ENDL;
if (url.getScheme() != "ftp") {
fout << "Invalid url" << ENDL;
return;
}
{
fout << "Server addresses:" << ENDL;
unsigned int index = 0;
if (index == 0) {
address = temp;
fout << " address " << index++ << ": " << temp << " (USING THIS)" << ENDL;
} else {
fout << " address " << index++ << ": " << temp << ENDL;
}
}
}
FileTransferProtocolClient client(endPoint);
client.connect();
client.login(url.getUser(), url.getPassword());
if (url.getPath().endsWith("/")) {
client.changeDirectory(url.getPath());
}
client.getSystemType();
client.getCurrentDirectory();
client.setType(FileTransferProtocolClient::BINARY);
client.setMode(FileTransferProtocolClient::STREAM);
client.requestPassiveTransfer();
client.getDirectoryList();
client.makeDirectory("fonseca");
if (url.getPath().isProper()) {
client.setMode(FileTransferProtocolClient::BLOCK);
client.requestPassiveTransfer();
client.retrieveFile(url.getPath());
}
client.logout();
}
private:
static const unsigned int MAJOR_VERSION = 1;
static const unsigned int MINOR_VERSION = 0;
public:
FTPApplication()
{
}
void main()
{
fout << getFormalName() << " version "
<< MAJOR_VERSION << '.' << MINOR_VERSION << EOL
<< "The Base Framework (Test Suite)" << EOL
<< ENDL;
String url =
"ftp.sunsite.auc.dk";
case 0:
break;
case 1:
url = arguments[0];
break;
case 2:
url = arguments[0];
file = arguments[1];
break;
default:
fout << "Usage: " << getFormalName() << " [url] [output]" << ENDL;
return;
}
try {
fout << "Testing File Transfer Protocol (FTP) class..." << ENDL;
ftpclient(url, file);
setExitCode(EXIT_CODE_ERROR);
}
}
};
APPLICATION_STUB(FTPApplication);