mirror of
https://github.com/Zeal-Operating-System/ZealOS.git
synced 2025-01-13 16:16:31 +00:00
Port Gopher client.
(Gopher client from https://github.com/DeclanHoare/tos-gopher) Implement TCPSocketReceiveLine and TCPConnectionCreate. Update HolyC --> CosmiC conversion script. Include DNS before TCP so that DNSAddressInfoGet can be used in TCP methods.
This commit is contained in:
parent
8306996ff4
commit
9ac4b35c29
4 changed files with 376 additions and 2 deletions
|
@ -17,9 +17,10 @@ Cd(__DIR__);;
|
||||||
|
|
||||||
#include "Utilities/BST"
|
#include "Utilities/BST"
|
||||||
#include "Protocols/UDP/MakeUDP"
|
#include "Protocols/UDP/MakeUDP"
|
||||||
#include "Protocols/TCP/MakeTCP"
|
|
||||||
|
|
||||||
#include "Protocols/DNS"
|
#include "Protocols/DNS"
|
||||||
|
|
||||||
|
#include "Protocols/TCP/MakeTCP"
|
||||||
|
|
||||||
#include "Protocols/DHCP"
|
#include "Protocols/DHCP"
|
||||||
|
|
||||||
#include "Utilities/NetHandler" // needs IPV4, UDP, ICMP
|
#include "Utilities/NetHandler" // needs IPV4, UDP, ICMP
|
290
src/Home/Net/Programs/Gopher.CC
Executable file
290
src/Home/Net/Programs/Gopher.CC
Executable file
|
@ -0,0 +1,290 @@
|
||||||
|
/* Gopher client for TempleOS/Sockets
|
||||||
|
by Declan Hoare 2020
|
||||||
|
ported to ZealOS by TomAwezome in 2021
|
||||||
|
Public Domain - No Warranty */
|
||||||
|
|
||||||
|
#define GOPHER_CLIENT_LOADED
|
||||||
|
|
||||||
|
#ifndef GOPHER_ASSOCIATIONS
|
||||||
|
#define GOPHER_ASSOCIATIONS
|
||||||
|
U8* gopher_associations[256];
|
||||||
|
#endif
|
||||||
|
|
||||||
|
I64 GopherOpen(U8* host, U16 port, U8* selector, U8* query)
|
||||||
|
{
|
||||||
|
U8* line;
|
||||||
|
I64 sock;
|
||||||
|
|
||||||
|
if (host == NULL)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// sock = create_connection(host, port);
|
||||||
|
sock = TCPConnectionCreate(host, port);
|
||||||
|
if (sock < 0)
|
||||||
|
{
|
||||||
|
PrintErr("Failed to connect to %s:%d\n", host, port);
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query == NULL)
|
||||||
|
{
|
||||||
|
line = StrPrint(NULL, "%s\r\n", selector);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
line = StrPrint(NULL, "%s\t%s\r\n", selector, query);
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendString(sock, line, 0);
|
||||||
|
TCPSocketSendString(sock, line);
|
||||||
|
Free(line);
|
||||||
|
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
public I64 GopherDl
|
||||||
|
(U8* host, U16 port = 70, U8* selector, U8* query = NULL, U8* dest)
|
||||||
|
{
|
||||||
|
CFile* f;
|
||||||
|
U8 buf[BLK_SIZE];
|
||||||
|
I64 data_len = 0, total_len = 0, got, sock;
|
||||||
|
|
||||||
|
f = FOpen(dest, "w");
|
||||||
|
if (!f)
|
||||||
|
{
|
||||||
|
PrintErr("Failed to open %s for writing\n", dest);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
sock = GopherOpen(host, port, selector, query);
|
||||||
|
if (sock < 0)
|
||||||
|
{
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
// got = recv(sock, buf + data_len, sizeof(buf) - data_len, 0);
|
||||||
|
got = TCPSocketReceive(sock, buf + data_len, sizeof(buf) - data_len);
|
||||||
|
if (got <= 0)
|
||||||
|
{
|
||||||
|
if (data_len != 0 && !FBlkWrite(f, buf))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
f->de.size = total_len;
|
||||||
|
FClose(f);
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
data_len += got;
|
||||||
|
total_len += got;
|
||||||
|
if (data_len == BLK_SIZE)
|
||||||
|
{
|
||||||
|
if (!FBlkWrite(f, buf))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PrintErr("Write failed, %s may be corrupted\n", dest);
|
||||||
|
FClose(f);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 PrintEscaped(U8* txt, U8* backslashes = "")
|
||||||
|
{
|
||||||
|
U8* cur;
|
||||||
|
U8 offending;
|
||||||
|
|
||||||
|
while (cur = StrFirstOcc(txt, "$$\\\""))
|
||||||
|
{
|
||||||
|
offending = *cur;
|
||||||
|
*cur = 0;
|
||||||
|
if (offending == '$$')
|
||||||
|
{
|
||||||
|
"%s$$$$", txt;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
"%s%s%c", txt, backslashes, offending;
|
||||||
|
}
|
||||||
|
txt = cur + 1;
|
||||||
|
}
|
||||||
|
"%s", txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
U8* GopherBasename(U8* selector)
|
||||||
|
{
|
||||||
|
U8* lastslash = StrLastOcc(selector, "/");
|
||||||
|
if (lastslash == NULL)
|
||||||
|
{
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return lastslash + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 GopherTextView(U8* host, U16 port, U8* selector)
|
||||||
|
{
|
||||||
|
U8* basename;
|
||||||
|
U8* tmpname;
|
||||||
|
|
||||||
|
DirMake("::/Tmp/Gopher");
|
||||||
|
|
||||||
|
basename = ExtChange(GopherBasename(selector), "TXT");
|
||||||
|
tmpname = StrPrint(NULL, "::/Tmp/Gopher/%s", basename);
|
||||||
|
Free(basename);
|
||||||
|
|
||||||
|
if (GopherDl(host, port, selector, , tmpname) == 0)
|
||||||
|
{
|
||||||
|
Plain(tmpname);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PrintErr("Failed to download %s from %s:%d\n",
|
||||||
|
selector, host, port);
|
||||||
|
}
|
||||||
|
Free(tmpname);
|
||||||
|
}
|
||||||
|
|
||||||
|
U0 GopherLine(U8* line)
|
||||||
|
{
|
||||||
|
U8 type;
|
||||||
|
U8* display;
|
||||||
|
U8* selector;
|
||||||
|
U8* host = NULL;
|
||||||
|
U16 port = 0;
|
||||||
|
|
||||||
|
if (*line == 0)
|
||||||
|
{
|
||||||
|
"\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type = *line++;
|
||||||
|
display = line;
|
||||||
|
|
||||||
|
line = StrFind("\t", line);
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
*line = 0;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
selector = line;
|
||||||
|
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line = StrFind("\t", line);
|
||||||
|
}
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
*line = 0;
|
||||||
|
line++;
|
||||||
|
}
|
||||||
|
host = line;
|
||||||
|
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
line = StrFind("\t", line);
|
||||||
|
}
|
||||||
|
if (line)
|
||||||
|
{
|
||||||
|
*line = 0;
|
||||||
|
line++;
|
||||||
|
port = Str2I64(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case '3':
|
||||||
|
PrintErr("");
|
||||||
|
case 'i':
|
||||||
|
PrintEscaped(display);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
"$$MA,\"";
|
||||||
|
PrintEscaped(display, "\\");
|
||||||
|
"\",LM=\"%s(\\\"", gopher_associations[type];
|
||||||
|
PrintEscaped(host, "\\\\\\");
|
||||||
|
"\\\",%d,\\\"", port;
|
||||||
|
PrintEscaped(selector, "\\\\\\");
|
||||||
|
"\\\");\\n\"$$";
|
||||||
|
}
|
||||||
|
"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public I64 GopherMenu
|
||||||
|
(U8* host, U16 port = 70, U8* selector = "/", U8* query = NULL)
|
||||||
|
{
|
||||||
|
I64 sock, n;
|
||||||
|
U8 buf[256];
|
||||||
|
|
||||||
|
sock = GopherOpen(host, port, selector, query);
|
||||||
|
if (sock < 0)
|
||||||
|
{
|
||||||
|
return sock;
|
||||||
|
}
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
// n = recvLine(sock, buf, sizeof(buf), 0);
|
||||||
|
n = TCPSocketReceiveLine(sock, buf, sizeof(buf));
|
||||||
|
if (StrCompare(buf, ".") == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
GopherLine(buf);
|
||||||
|
}
|
||||||
|
while (n > 0);
|
||||||
|
|
||||||
|
// close(sock);
|
||||||
|
TCPSocketClose(sock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CQueryForm
|
||||||
|
{
|
||||||
|
U8 query[65] format "$$DA-P,A=\"Query:%s\"$$";
|
||||||
|
};
|
||||||
|
U0 GopherQueryPrompt(U8* host, U16 port, U8* selector)
|
||||||
|
{
|
||||||
|
CQueryForm form;
|
||||||
|
|
||||||
|
form.query[0] = 0;
|
||||||
|
if (PopUpForm(&form))
|
||||||
|
{
|
||||||
|
GopherMenu(host, port, selector, form.query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CDlForm
|
||||||
|
{
|
||||||
|
U8 name[256] format "$$DA-P,LEN=255,A=\"FileName:%s\"$$";
|
||||||
|
};
|
||||||
|
U0 GopherDlPrompt(U8* host, U16 port, U8* selector)
|
||||||
|
{
|
||||||
|
CDlForm form;
|
||||||
|
U8* basename;
|
||||||
|
|
||||||
|
basename = GopherBasename(selector);
|
||||||
|
MemCopy(form.name, basename,
|
||||||
|
MinI64(StrLen(basename), sizeof(form.name) - 1));
|
||||||
|
form.name[255] = 0;
|
||||||
|
if (PopUpForm(&form))
|
||||||
|
{
|
||||||
|
GopherDl(host, port, selector, , form.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MemSetI64(gopher_associations, "GopherDlPrompt", 256);
|
||||||
|
gopher_associations['0'] = "GopherTextView";
|
||||||
|
gopher_associations['1'] = "GopherMenu";
|
||||||
|
gopher_associations['7'] = "GopherQueryPrompt";
|
||||||
|
/* Include this file from your HomeSys, then add more associations */
|
||||||
|
|
||||||
|
"\n\nTry using GopherMenu to connect to a gopher server,"
|
||||||
|
"for example: GopherMenu(\"gopher.floodgap.com\");\n\n";
|
|
@ -1076,7 +1076,10 @@ I64 TCPSocketConnect(CTCPSocket *tcp_socket, CSocketAddressStorage *address)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tcp_socket->state != TCP_STATE_ESTABLISHED)
|
if (tcp_socket->state != TCP_STATE_ESTABLISHED)
|
||||||
|
{
|
||||||
|
NetErr("TCP SOCKET CONNECT: Failed to establish TCP Socket connection.");
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
switch (tcp_socket->socket->state)
|
switch (tcp_socket->socket->state)
|
||||||
{
|
{
|
||||||
|
@ -1303,6 +1306,28 @@ I64 TCPSocketReceive(CTCPSocket *tcp_socket, U8 *buffer, I64 length)
|
||||||
return read_total;
|
return read_total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
I64 TCPSocketReceiveLine(CTCPSocket *tcp_socket, U8 *buffer, I64 size)
|
||||||
|
{
|
||||||
|
I64 received = 0;
|
||||||
|
|
||||||
|
while (received + 1 < size)
|
||||||
|
{
|
||||||
|
if (TCPSocketReceive(tcp_socket, buffer + received, 1) <= 0)
|
||||||
|
{
|
||||||
|
NetErr("TCP SOCKET RECEIVE LINE: Failed at TCPSocketReceive.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (buffer[received] == '\n')
|
||||||
|
break;
|
||||||
|
else if (buffer[received] != '\r')
|
||||||
|
received++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrine has 'FIXME: safe but incorrect behavior on overflow'
|
||||||
|
buffer[received] = 0; // terminate string
|
||||||
|
return received;
|
||||||
|
}
|
||||||
|
|
||||||
I64 TCPSocketSend(CTCPSocket *tcp_socket, U8 *buffer, I64 length)
|
I64 TCPSocketSend(CTCPSocket *tcp_socket, U8 *buffer, I64 length)
|
||||||
{
|
{
|
||||||
I64 sent_total = 0;
|
I64 sent_total = 0;
|
||||||
|
@ -1399,6 +1424,62 @@ I64 TCPSocketSendString(CTCPSocket *tcp_socket, U8 *string)
|
||||||
return TCPSocketSendAll(tcp_socket, string, StrLen(string));
|
return TCPSocketSendAll(tcp_socket, string, StrLen(string));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CTCPSocket *TCPConnectionCreate(U8 *hostname, U16 port)
|
||||||
|
{
|
||||||
|
CSocketAddressIPV4 socket_addr;
|
||||||
|
CAddressInfo *current;
|
||||||
|
CAddressInfo *info = NULL;
|
||||||
|
I64 error = DNSAddressInfoGet(hostname, NULL, &info);
|
||||||
|
CTCPSocket *result;
|
||||||
|
|
||||||
|
if (error < 0)
|
||||||
|
{
|
||||||
|
NetErr("TCP CONNECTION CREATE: Failed at DNSAddressInfoGet.");
|
||||||
|
AddressInfoFree(info);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
socket_addr.family = AF_INET;
|
||||||
|
socket_addr.port = EndianU16(port);
|
||||||
|
socket_addr.address.address = INADDR_ANY;
|
||||||
|
|
||||||
|
current = info;
|
||||||
|
while (current)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (current->family == AF_INET && (current->socket_type == 0 || current->socket_type == SOCKET_STREAM))
|
||||||
|
{ // TODO: IPV6 support !
|
||||||
|
socket_addr.address.address = EndianU32(current->address(CSocketAddressIPV4 *)->address.address); // ?? why are bits flipped here
|
||||||
|
AddressInfoFree(info);
|
||||||
|
|
||||||
|
result = TCPSocket(AF_INET);
|
||||||
|
if (!result)
|
||||||
|
{
|
||||||
|
NetErr("TCP CONNECTION CREATE: Failed to create new TCP Socket.");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
result->timeout = TCP_TIMEOUT;
|
||||||
|
error = TCPSocketConnect(result, &socket_addr);
|
||||||
|
if (error < 0)
|
||||||
|
{
|
||||||
|
NetErr("TCP CONNECTION CREATE: Failed to connect TCP Socket to address.");
|
||||||
|
TCPSocketClose(result);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
current = current->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
NetErr("TCP CONNECTION CREATE: Failed to find a suitable address.");
|
||||||
|
|
||||||
|
AddressInfoFree(info);
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
U0 TCPTreeNodeRep(CTCPTreeNode *node)
|
U0 TCPTreeNodeRep(CTCPTreeNode *node)
|
||||||
{
|
{
|
||||||
CTCPTreeQueue *queue = node->queue->next;
|
CTCPTreeQueue *queue = node->queue->next;
|
||||||
|
|
|
@ -39,6 +39,7 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S")
|
||||||
Find("LstMatch", ff_mask, fu_flags, "ListMatch");
|
Find("LstMatch", ff_mask, fu_flags, "ListMatch");
|
||||||
Find("DefineLstLoad", ff_mask, fu_flags, "DefineListLoad");
|
Find("DefineLstLoad", ff_mask, fu_flags, "DefineListLoad");
|
||||||
Find("ExtDft", ff_mask, fu_flags, "ExtDefault");
|
Find("ExtDft", ff_mask, fu_flags, "ExtDefault");
|
||||||
|
Find("ExtChg", ff_mask, fu_flags, "ExtChange");
|
||||||
Find("RegDft", ff_mask, fu_flags, "RegDefault");
|
Find("RegDft", ff_mask, fu_flags, "RegDefault");
|
||||||
Find("\"HC\"", ff_mask, fu_flags, "\"CC\"");
|
Find("\"HC\"", ff_mask, fu_flags, "\"CC\"");
|
||||||
Find("CDrv", ff_mask, fu_flags, "CDrive");
|
Find("CDrv", ff_mask, fu_flags, "CDrive");
|
||||||
|
@ -60,6 +61,7 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S")
|
||||||
Find("fp_draw_ms", ff_mask, fu_flags, "fp_draw_mouse");
|
Find("fp_draw_ms", ff_mask, fu_flags, "fp_draw_mouse");
|
||||||
Find("DrawStdMs", ff_mask, fu_flags, "DrawStdMouse");
|
Find("DrawStdMs", ff_mask, fu_flags, "DrawStdMouse");
|
||||||
Find("WIG_TASK_DFT", ff_mask, fu_flags, "WIG_TASK_DEFAULT");
|
Find("WIG_TASK_DFT", ff_mask, fu_flags, "WIG_TASK_DEFAULT");
|
||||||
|
Find("DirMk", ff_mask, fu_flags, "DirMake");
|
||||||
|
|
||||||
|
|
||||||
"\n$$BK,1$$$$LTRED$$Might want to go over these$$FG$$$$BK,0$$\n";
|
"\n$$BK,1$$$$LTRED$$Might want to go over these$$FG$$$$BK,0$$\n";
|
||||||
|
|
Loading…
Reference in a new issue