From 9ac4b35c29f6a011a691c202894a2b8aa718e6d9 Mon Sep 17 00:00:00 2001 From: TomAwezome Date: Tue, 2 Nov 2021 02:01:48 -0400 Subject: [PATCH] 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. --- src/Home/Net/Load.CC | 5 +- src/Home/Net/Programs/Gopher.CC | 290 +++++++++++++++++++++++++++ src/Home/Net/Protocols/TCP/TCP.CC | 81 ++++++++ src/System/Utils/ConversionScript.CC | 2 + 4 files changed, 376 insertions(+), 2 deletions(-) create mode 100755 src/Home/Net/Programs/Gopher.CC diff --git a/src/Home/Net/Load.CC b/src/Home/Net/Load.CC index 890456bc..98ad8f58 100755 --- a/src/Home/Net/Load.CC +++ b/src/Home/Net/Load.CC @@ -17,9 +17,10 @@ Cd(__DIR__);; #include "Utilities/BST" #include "Protocols/UDP/MakeUDP" -#include "Protocols/TCP/MakeTCP" - #include "Protocols/DNS" + +#include "Protocols/TCP/MakeTCP" + #include "Protocols/DHCP" #include "Utilities/NetHandler" // needs IPV4, UDP, ICMP \ No newline at end of file diff --git a/src/Home/Net/Programs/Gopher.CC b/src/Home/Net/Programs/Gopher.CC new file mode 100755 index 00000000..9dd12bf8 --- /dev/null +++ b/src/Home/Net/Programs/Gopher.CC @@ -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"; diff --git a/src/Home/Net/Protocols/TCP/TCP.CC b/src/Home/Net/Protocols/TCP/TCP.CC index 97a91ec2..ed15fb2d 100755 --- a/src/Home/Net/Protocols/TCP/TCP.CC +++ b/src/Home/Net/Protocols/TCP/TCP.CC @@ -1076,7 +1076,10 @@ I64 TCPSocketConnect(CTCPSocket *tcp_socket, CSocketAddressStorage *address) } if (tcp_socket->state != TCP_STATE_ESTABLISHED) + { + NetErr("TCP SOCKET CONNECT: Failed to establish TCP Socket connection."); return -1; + } switch (tcp_socket->socket->state) { @@ -1303,6 +1306,28 @@ I64 TCPSocketReceive(CTCPSocket *tcp_socket, U8 *buffer, I64 length) 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 sent_total = 0; @@ -1399,6 +1424,62 @@ I64 TCPSocketSendString(CTCPSocket *tcp_socket, U8 *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) { CTCPTreeQueue *queue = node->queue->next; diff --git a/src/System/Utils/ConversionScript.CC b/src/System/Utils/ConversionScript.CC index d62962aa..b6f2adae 100755 --- a/src/System/Utils/ConversionScript.CC +++ b/src/System/Utils/ConversionScript.CC @@ -39,6 +39,7 @@ U0 Cvt(U8 *ff_mask="*", U8 *fu_flags="+r+l-i+S") Find("LstMatch", ff_mask, fu_flags, "ListMatch"); Find("DefineLstLoad", ff_mask, fu_flags, "DefineListLoad"); Find("ExtDft", ff_mask, fu_flags, "ExtDefault"); + Find("ExtChg", ff_mask, fu_flags, "ExtChange"); Find("RegDft", ff_mask, fu_flags, "RegDefault"); Find("\"HC\"", ff_mask, fu_flags, "\"CC\""); 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("DrawStdMs", ff_mask, fu_flags, "DrawStdMouse"); 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";