mirror of
https://github.com/Zeal-Operating-System/ZealOS.git
synced 2024-12-25 23:10:32 +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");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
I64 TCPSendData(CTCPSocket *tcp_socket, U8 flags, U8 *data, I64 length)
|
||||
|
|
Loading…
Reference in a new issue