mirror of
https://github.com/Zeal-Operating-System/ZealOS.git
synced 2024-12-27 07:46:33 +00:00
Implement FTP client.
This commit is contained in:
parent
dedb922567
commit
afc785ba27
2 changed files with 510 additions and 0 deletions
508
src/Home/Net/Programs/FTPClient.ZC
Executable file
508
src/Home/Net/Programs/FTPClient.ZC
Executable file
|
@ -0,0 +1,508 @@
|
||||||
|
#define FTP_BUF_SIZE 1024
|
||||||
|
|
||||||
|
I64 FTPMessageGet(CTCPSocket *message_socket, U8 *buf)
|
||||||
|
{ // returns FTP status code of message
|
||||||
|
U8 *temp;
|
||||||
|
Bool first = TRUE;
|
||||||
|
I64 status;
|
||||||
|
|
||||||
|
"\n";
|
||||||
|
while (TCPSocketReceive(message_socket, buf, FTP_BUF_SIZE) > 0)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
status = Str2I64(buf);
|
||||||
|
first = FALSE;
|
||||||
|
}
|
||||||
|
temp = MStrPrint("%Q", buf);
|
||||||
|
temp = StrReplace(temp, "\\r", "",, TRUE);
|
||||||
|
temp = StrReplace(temp, "\\n", "\n",, TRUE);
|
||||||
|
temp = StrReplace(temp, "\\t", "\t",, TRUE);
|
||||||
|
temp = StrReplace(temp, "\\\"", "\"",, TRUE);
|
||||||
|
"%s", temp;
|
||||||
|
MemSet(buf, 0, FTP_BUF_SIZE);
|
||||||
|
Free(temp);
|
||||||
|
}
|
||||||
|
"\n";
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 FTPReplyPassiveParse(CTCPSocket *message_socket, CSocketAddressIPV4 *dest_addr_out)
|
||||||
|
{ // Parse an FTP reply message to the PASV command, destination written to arg.
|
||||||
|
U8 buf[8192], *str;
|
||||||
|
I64 tk, cur_section = 0, recv = 0, recv_sum = 0, res;
|
||||||
|
U32 ip_addr = 0;
|
||||||
|
U16 port = 0;
|
||||||
|
CCompCtrl *cc;
|
||||||
|
|
||||||
|
while ((recv = TCPSocketReceive(message_socket, buf + recv_sum, 8192 - recv_sum)) > 0)
|
||||||
|
{
|
||||||
|
recv_sum += recv;
|
||||||
|
}
|
||||||
|
str = StrNew(buf);
|
||||||
|
cc = CompCtrlNew(str);
|
||||||
|
|
||||||
|
while ((tk = Lex(cc)))
|
||||||
|
{
|
||||||
|
switch (tk)
|
||||||
|
{
|
||||||
|
case TK_IDENT:
|
||||||
|
cur_section = 0;
|
||||||
|
ip_addr = 0;
|
||||||
|
port = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
break; // skip
|
||||||
|
|
||||||
|
case TK_I64:
|
||||||
|
if (cur_section < 4)
|
||||||
|
{
|
||||||
|
ip_addr.u8[3 - cur_section] = cc->cur_i64;
|
||||||
|
cur_section++;
|
||||||
|
}
|
||||||
|
else if (4 <= cur_section < 6)
|
||||||
|
{
|
||||||
|
port.u8[5 - cur_section] = cc->cur_i64;
|
||||||
|
cur_section++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cur_section >= 6)
|
||||||
|
goto parse_done;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_done:
|
||||||
|
if (cur_section < 6)
|
||||||
|
res = -1;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dest_addr_out->family = AF_INET;
|
||||||
|
dest_addr_out->port = EndianU16(port);
|
||||||
|
dest_addr_out->address.address = ip_addr;
|
||||||
|
|
||||||
|
res = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompCtrlDel(cc);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 FTPFileDownload(CTCPSocket *data_socket, U8 *dest)
|
||||||
|
{
|
||||||
|
CFile *f;
|
||||||
|
I64 data_len = 0, recv_sum = 0, recv;
|
||||||
|
U8 buf[BLK_SIZE];
|
||||||
|
|
||||||
|
progress4 = 0;
|
||||||
|
f = FOpen(dest, "w");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Failed to open %s for writing\n", dest;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
recv = TCPSocketReceive(data_socket, buf + data_len, sizeof(buf) - data_len);
|
||||||
|
if (recv <= 0)
|
||||||
|
{
|
||||||
|
if (recv < 0)
|
||||||
|
ST_ERR_ST "Failed to receive TCP data\n";
|
||||||
|
if (data_len != 0 && !FBlkWrite(f, buf))
|
||||||
|
break;
|
||||||
|
|
||||||
|
f->de.size = recv_sum;
|
||||||
|
FClose(f);
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
|
||||||
|
return recv;
|
||||||
|
}
|
||||||
|
|
||||||
|
data_len += recv;
|
||||||
|
recv_sum += recv;
|
||||||
|
progress4 += recv;
|
||||||
|
|
||||||
|
if (data_len == BLK_SIZE)
|
||||||
|
{
|
||||||
|
if (!FBlkWrite(f, buf))
|
||||||
|
break;
|
||||||
|
|
||||||
|
data_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
ST_ERR_ST "Write failed, %s may be corrupted\n", dest;
|
||||||
|
FClose(f);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 FTPFileView(U8 *filename=NULL, CTask *parent=NULL, CTask **_pu_task=NULL)
|
||||||
|
{
|
||||||
|
U8 *st = MStrPrint("Cd(\"%Q\");Plain(\"%Q\");", __DIR__, filename);
|
||||||
|
I64 res = PopUp(st, parent, _pu_task);
|
||||||
|
|
||||||
|
Free(st);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8 *FTPBasename(U8 *path)
|
||||||
|
{
|
||||||
|
U8 *lastslash = StrLastOcc(path, "/"), *result;
|
||||||
|
|
||||||
|
if (lastslash == NULL)
|
||||||
|
result = path;
|
||||||
|
else
|
||||||
|
result = lastslash + 1;
|
||||||
|
|
||||||
|
//BAD FOR FILENAMES: ? / | = % : ; * + " < > space
|
||||||
|
result = StrReplace(result, "?", "");
|
||||||
|
result = StrReplace(result, "/", "",, TRUE);
|
||||||
|
result = StrReplace(result, "|", "",, TRUE);
|
||||||
|
result = StrReplace(result, "=", "",, TRUE);
|
||||||
|
result = StrReplace(result, "%", "",, TRUE);
|
||||||
|
result = StrReplace(result, ":", "",, TRUE);
|
||||||
|
result = StrReplace(result, ";", "",, TRUE);
|
||||||
|
result = StrReplace(result, "*", "",, TRUE);
|
||||||
|
result = StrReplace(result, "+", "",, TRUE);
|
||||||
|
result = StrReplace(result, "\"", "",, TRUE);
|
||||||
|
result = StrReplace(result, "<", "",, TRUE);
|
||||||
|
result = StrReplace(result, ">", "",, TRUE);
|
||||||
|
result = StrReplace(result, " ", "",, TRUE);
|
||||||
|
|
||||||
|
if (StrLen(result) > 22)
|
||||||
|
result[21] = 0; // truncate filename len
|
||||||
|
|
||||||
|
SysLog("%s\n", result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CFTPFilePrompt
|
||||||
|
{
|
||||||
|
U8 name[256] format "$$DA-P,LEN=255,A=\"FileName:%s\"$$";
|
||||||
|
};
|
||||||
|
U8 *FTPFilePrompt(U8 *path)
|
||||||
|
{
|
||||||
|
CFTPFilePrompt form;
|
||||||
|
U8 *basename = FTPBasename(path);
|
||||||
|
|
||||||
|
MemSet(form.name, 0, 256);
|
||||||
|
MemCopy(form.name, basename, MinI64(StrLen(basename), sizeof(form.name) - 1));
|
||||||
|
form.name[255] = 0;
|
||||||
|
if (PopUpForm(&form))
|
||||||
|
{
|
||||||
|
if (StrLen(form.name) >= 26)
|
||||||
|
form.name[25] = 0;
|
||||||
|
return StrNew(form.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
I64 FTPClient(U8 *hostname=NULL, U16 port=21)
|
||||||
|
{
|
||||||
|
U32 addr;
|
||||||
|
CAddressInfo *current;
|
||||||
|
CAddressInfo *result = NULL;
|
||||||
|
I64 error, tk, i;
|
||||||
|
CSocketAddressIPV4 ipv4_address, *temp_ipv4, data_ipv4;
|
||||||
|
CTCPSocket *message_socket = TCPSocket(AF_INET), *data_socket;
|
||||||
|
I64 status = 0;
|
||||||
|
U8 buf[FTP_BUF_SIZE], *temp, *input_str, *dest;
|
||||||
|
CCompCtrl *cc;
|
||||||
|
|
||||||
|
if (!hostname)
|
||||||
|
hostname = StrGet("\nEnter FTP server address (URL or IPV4): ");
|
||||||
|
|
||||||
|
|
||||||
|
if (!IPV4AddressParse(hostname, &addr))
|
||||||
|
{
|
||||||
|
error = DNSAddressInfoGet(hostname, NULL, &result);
|
||||||
|
if (error < 0)
|
||||||
|
{
|
||||||
|
NetErr("FTP Client: Failed at DNS Get Address Info.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
current = result;
|
||||||
|
while (current)
|
||||||
|
{
|
||||||
|
if (current->family == AF_INET)
|
||||||
|
{
|
||||||
|
temp_ipv4 = current->address;
|
||||||
|
addr = EndianU32(temp_ipv4->address); // why does it need EndianU32
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!current)
|
||||||
|
{
|
||||||
|
NetErr("FTP Client: Failed to resolve address.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
ipv4_address.port = EndianU16(port);
|
||||||
|
ipv4_address.family = AF_INET;
|
||||||
|
ipv4_address.address.address = addr;
|
||||||
|
|
||||||
|
message_socket->timeout = TCP_TIMEOUT;
|
||||||
|
|
||||||
|
if (TCPSocketConnect(message_socket, &ipv4_address) != 0)
|
||||||
|
{
|
||||||
|
"\nFailed to connect to server.\n";
|
||||||
|
TCPSocketClose(message_socket);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
"\nSuccessfully connected.\n";
|
||||||
|
|
||||||
|
message_socket->timeout = 2 * JIFFY_FREQ;
|
||||||
|
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
|
||||||
|
|
||||||
|
"\n\nType HELP for command list.\n\n";
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
input_str = StrGet(">");
|
||||||
|
cc = CompCtrlNew(input_str);
|
||||||
|
|
||||||
|
while ((tk = Lex(cc)))
|
||||||
|
{
|
||||||
|
switch (tk)
|
||||||
|
{
|
||||||
|
case TK_IDENT: // command
|
||||||
|
"COMMAND:%s\n", cc->cur_str;
|
||||||
|
for (i = 0; i < StrLen(cc->cur_str); i++)
|
||||||
|
cc->cur_str[i] = ToUpper(cc->cur_str[i]);
|
||||||
|
|
||||||
|
if (!StrCompare(cc->cur_str, "CWD") ||
|
||||||
|
!StrCompare(cc->cur_str, "CD"))
|
||||||
|
{
|
||||||
|
StrFirstRemove(input_str, " ");
|
||||||
|
if (!StrCompare(input_str, ""))
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Must provide argument!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
"ARG:%s\n", input_str;
|
||||||
|
|
||||||
|
temp = MStrPrint("CWD %s\r\n", input_str);
|
||||||
|
TCPSocketSendString(message_socket, temp);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
Free(temp);
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "LIST") ||
|
||||||
|
!StrCompare(cc->cur_str, "DIR") ||
|
||||||
|
!StrCompare(cc->cur_str, "LS"))
|
||||||
|
{
|
||||||
|
TCPSocketSendString(message_socket, "PASV\r\n");
|
||||||
|
if (FTPReplyPassiveParse(message_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Error parsing server response to PASV command!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
data_socket = TCPSocket(AF_INET);
|
||||||
|
data_socket->timeout = 2 * JIFFY_FREQ;
|
||||||
|
|
||||||
|
if (TCPSocketConnect(data_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Failed at data socket connect!";
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSocketSendString(message_socket, "LIST\r\n");
|
||||||
|
FTPMessageGet(data_socket, buf);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
|
||||||
|
if (TCPSocketClose(data_socket) != 0)
|
||||||
|
ST_ERR_ST "Failed at data socket close!";
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "PWD"))
|
||||||
|
{
|
||||||
|
TCPSocketSendString(message_socket, "PWD\r\n");
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "RETR") ||
|
||||||
|
!StrCompare(cc->cur_str, "GET"))
|
||||||
|
{
|
||||||
|
StrFirstRemove(input_str, " ");
|
||||||
|
if (!StrCompare(input_str, ""))
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Must provide argument!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSocketSendString(message_socket, "PASV\r\n");
|
||||||
|
if (FTPReplyPassiveParse(message_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Error parsing server response to PASV command!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
data_socket = TCPSocket(AF_INET);
|
||||||
|
data_socket->timeout = 2 * JIFFY_FREQ;
|
||||||
|
|
||||||
|
if (TCPSocketConnect(data_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Failed at data socket connect!";
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
dest = FTPFilePrompt(input_str);
|
||||||
|
if (dest == NULL)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Download filename cannot be empty!";
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
temp = MStrPrint("RETR %s\r\n", input_str);
|
||||||
|
TCPSocketSendString(message_socket, temp);
|
||||||
|
FTPFileDownload(data_socket, dest);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
"\nOpen file with Ed? ";
|
||||||
|
if (YorN)
|
||||||
|
FTPFileView(dest);
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "VIEW") ||
|
||||||
|
!StrCompare(cc->cur_str, "CAT"))
|
||||||
|
{
|
||||||
|
StrFirstRemove(input_str, " ");
|
||||||
|
if (!StrCompare(input_str, ""))
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Must provide argument!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
TCPSocketSendString(message_socket, "PASV\r\n");
|
||||||
|
if (FTPReplyPassiveParse(message_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Error parsing server response to PASV command!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
data_socket = TCPSocket(AF_INET);
|
||||||
|
data_socket->timeout = 2 * JIFFY_FREQ;
|
||||||
|
|
||||||
|
if (TCPSocketConnect(data_socket, &data_ipv4) != 0)
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Failed at data socket connect!";
|
||||||
|
TCPSocketClose(data_socket);
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
temp = MStrPrint("RETR %s\r\n", input_str);
|
||||||
|
TCPSocketSendString(message_socket, temp);
|
||||||
|
FTPMessageGet(data_socket, buf);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
|
||||||
|
if (TCPSocketClose(data_socket) != 0)
|
||||||
|
ST_ERR_ST "Failed at data socket close!";
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "USER"))
|
||||||
|
{
|
||||||
|
StrFirstRemove(input_str, " ");
|
||||||
|
if (!StrCompare(input_str, ""))
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Must provide argument!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
"ARG:%s\n", input_str;
|
||||||
|
|
||||||
|
temp = MStrPrint("USER %s\r\n", input_str);
|
||||||
|
TCPSocketSendString(message_socket, temp);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
Free(temp);
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "PASS"))
|
||||||
|
{
|
||||||
|
StrFirstRemove(input_str, " ");
|
||||||
|
if (!StrCompare(input_str, ""))
|
||||||
|
{
|
||||||
|
ST_ERR_ST "Must provide argument!\n";
|
||||||
|
goto lex_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
"ARG:%s\n", input_str;
|
||||||
|
|
||||||
|
temp = MStrPrint("PASS %s\r\n", input_str);
|
||||||
|
TCPSocketSendString(message_socket, temp);
|
||||||
|
FTPMessageGet(message_socket, buf);
|
||||||
|
Free(temp);
|
||||||
|
|
||||||
|
goto lex_done;
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (!StrCompare(cc->cur_str, "QUIT") ||
|
||||||
|
!StrCompare(cc->cur_str, "EXIT") ||
|
||||||
|
!StrCompare(cc->cur_str, "BYE"))
|
||||||
|
{
|
||||||
|
TCPSocketSendString(message_socket, "QUIT\r\n");
|
||||||
|
status = FTPMessageGet(message_socket, buf);
|
||||||
|
if (status == 221)
|
||||||
|
"Server closed successfully, exiting normally...\n\n";
|
||||||
|
else
|
||||||
|
ST_WARN_ST "Server error during close, force exit...\n\n";
|
||||||
|
TCPSocketClose(message_socket);
|
||||||
|
"See ya later!\n\n";
|
||||||
|
CompCtrlDel(cc);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
"
|
||||||
|
Command List:
|
||||||
|
|
||||||
|
(Alternate names separated by '/'; names case-insensitive)
|
||||||
|
|
||||||
|
|
||||||
|
CWD/CD <path> = Change Working Directory to <path>.
|
||||||
|
LIST/DIR/LS = List directory contents.
|
||||||
|
PWD = Print name of current directory.
|
||||||
|
RETR/GET <file> = Download copy of <file> from server.
|
||||||
|
VIEW/CAT <file> = Print the contents of <file> to the screen.
|
||||||
|
USER <username> = Set username to <username>.
|
||||||
|
PASS <password> = Set password to <password>.
|
||||||
|
QUIT/EXIT/BYE = End FTP session.\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
"\nCommand expected. Type HELP for command list.\n";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lex_done:
|
||||||
|
CompCtrlDel(cc);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
FTPClient;
|
|
@ -304,6 +304,8 @@ I64 TCPSendFlags(CTCPSocket *tcp_socket, U8 flags)
|
||||||
NetErr("TCP SEND FLAGS: Error, TCP Send to AF_UNSPEC\n");
|
NetErr("TCP SEND FLAGS: Error, TCP Send to AF_UNSPEC\n");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
I64 TCPSendData(CTCPSocket *tcp_socket, U8 flags, U8 *data, I64 length)
|
I64 TCPSendData(CTCPSocket *tcp_socket, U8 flags, U8 *data, I64 length)
|
||||||
|
|
Loading…
Reference in a new issue