AmendHub

Download:

jcs

/

detritus

/

amendments

/

26

*: Abstract TCP and TLS ops to request.c


jcs made amendment 26 about 1 year ago
--- browser.c Wed Nov 6 12:52:48 2024 +++ browser.c Wed Nov 6 22:33:34 2024 @@ -19,9 +19,7 @@ #include <string.h> #include "detritus.h" -#include "browser.h" #include "focusable.h" -#include "util.h" #define PADDING 6 #define BROWSER_FONT geneva @@ -235,9 +233,8 @@ browser_init(void) focusable->atexit = browser_atexit; focusable_add(focusable); - snprintf(browser->status_text, sizeof(browser->status_text), - "Hello, cyberpals!"); browser_update(focusable, NULL); + browser_statusf(browser, "Hello, cyberpals!"); return browser; } @@ -690,10 +687,7 @@ browser_go(struct browser *browser, short dir) browser->loading_page = opage; browser_update_buttons(browser); - if ((*opage)->handler->init(*opage)) { - HUnlock(opage); - browser_commit_to_loading_page(browser); - } else { + if (!(*opage)->handler->init(*opage)) { (*opage)->handler->cleanup(*opage); HUnlock(opage); browser->loading_page = NULL; --- browser.h Tue Nov 5 21:28:05 2024 +++ browser.h Wed Nov 6 22:31:48 2024 @@ -18,9 +18,9 @@ #define __BROWSER_H__ #include <stdlib.h> + #include "detritus.h" #include "textview.h" -#include "tcp.h" #include "util.h" //#define BROWSER_DEBUGF(x) browser_statusf @@ -45,6 +45,28 @@ struct browser_link { struct browser_link *next_link; }; +typedef struct page **page_handle; + +struct page { + struct URI *uri; + struct browser *browser; + page_handle back_page; + page_handle fwd_page; + size_t content_size; +#define PAGE_CONTENT_CHUNK_SIZE 1024 + size_t content_len; + size_t content_pos; + struct uri_handler *handler; + void *fetch_cookie; + char type[32]; + short state; + short parse_state; + struct URI *redir_to; + short download_frefnum; + size_t download_len; + char content[]; +}; + struct browser { WindowPtr win; RgnHandle header; @@ -66,6 +88,23 @@ struct browser { unsigned long style; struct browser_link *first_link; struct browser_link *last_link; +}; + +struct uri_handler { + /* return a URI if this handler can process this string */ + struct URI * (*parse_uri)(char *uristr); + + /* do any post-init on a new page, like doing a request if no content */ + bool (*init)(struct page *page); + + /* continue fetching content */ + bool (*fetch)(struct page *page); + + /* parse existing content */ + bool (*process)(struct page *page); + + /* cleanup when done loading, such as freeing request */ + void (*cleanup)(struct page *page); }; struct browser * browser_init(void); --- detritus.h Wed Nov 6 09:45:19 2024 +++ detritus.h Wed Nov 6 22:25:22 2024 @@ -17,6 +17,8 @@ #ifndef __DETRITUS_H__ #define __DETRITUS_H__ +#include "browser.h" +#include "request.h" #include "util.h" #define PROGRAM_NAME "Detritus" @@ -39,82 +41,8 @@ #define BOOKMARKS_MENU_ID 132 -extern uint8_t tls_req_last_id; - -struct tls_init_request { - uint8_t flags[2]; -#define BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY (1 << 0) - uint8_t unix_time[4]; - char hostname[256]; -}; - -struct URI { -#define URI_MAX_PROTOCOL_LEN 20 - char *protocol; -#define URI_MAX_HOSTNAME_LEN 255 - char *hostname; -#define URI_MAX_PATH_LEN 512 - char *path; -#define URI_MAX_STR_LEN (URI_MAX_PROTOCOL_LEN + 3 + URI_MAX_HOSTNAME_LEN + \ - URI_MAX_PATH_LEN) - char *str; - unsigned short port; -}; - -typedef struct page **page_handle; -struct page { - struct URI *uri; - struct browser *browser; - page_handle back_page; - page_handle fwd_page; - size_t content_size; -#define PAGE_CONTENT_CHUNK_SIZE 1024 - size_t content_len; - size_t content_pos; - struct uri_handler *handler; - void *fetch_cookie; - char type[32]; - short state; - short parse_state; - struct URI *redir_to; - short download_frefnum; - size_t download_len; - char content[]; -}; - -struct uri_handler { - /* return a URI if this handler can process this string */ - struct URI * (*parse_uri)(char *uristr); - - /* do any post-init on a new page, like doing a request if no content */ - bool (*init)(struct page *page); - - /* continue fetching content */ - bool (*fetch)(struct page *page); - - /* parse existing content */ - bool (*process)(struct page *page); - - /* cleanup when done loading, such as freeing request */ - void (*cleanup)(struct page *page); -}; - extern MenuHandle file_menu, edit_menu, bookmarks_menu; void menu_defaults(void); -struct URI * parse_uri(char *uristr, char *restrict_protocol); -struct URI * build_relative_uri(struct URI *uri, char *relative, size_t len); - -short scsi_find_tls(void); -uint8_t scsi_tls_init(struct tls_init_request *req); -bool scsi_tls_close(uint8_t tls_id); -short scsi_tls_status(uint8_t tls_id, short *cipherspace, - short *plainspace, short *error); -size_t scsi_tls_read(uint8_t tls_id, unsigned char **buf, size_t max_size, - bool cipher); -size_t scsi_tls_write(uint8_t tls_id, unsigned char *buf, size_t buf_size, - bool cipher); -void scsi_cleanup(void); -bool scsi_can_do_tls(void); #endif \ No newline at end of file --- finger.c Wed Nov 6 12:57:34 2024 +++ finger.c Wed Nov 6 22:07:25 2024 @@ -19,18 +19,12 @@ #include <string.h> #include "detritus.h" -#include "browser.h" #define FINGER_PORT 79 struct finger_request { - unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ + struct tcp_request tcp; - TCPiopb tcp_iopb; - StreamPtr tcp_stream; - wdsEntry tcp_wds[2]; - TCPStatusPB tcp_status_pb; - char query[128]; size_t query_len; }; @@ -58,21 +52,17 @@ finger_parse_uri(char *uristr) bool finger_init_request(struct page *page) { - struct finger_request *req = NULL; - size_t len; - char ip_s[12 + 3 + 1]; - short err; - ip_addr ip, local_ip; - tcp_port port, local_port; + struct finger_request *finger = NULL; if (page->content_len) { /* already fetched, nothing to do here but reset */ page->content_pos = 0; + browser_commit_to_loading_page(page->browser); return true; } - req = xmalloczero(sizeof(struct finger_request)); - if (req == NULL) { + finger = xmalloczero(sizeof(struct finger_request)); + if (finger == NULL) { warn("Out of memory"); return false; } @@ -80,137 +70,78 @@ finger_init_request(struct page *page) if (page->uri->port == 0) page->uri->port = FINGER_PORT; - browser_statusf(page->browser, "Resolving \"%s\"...", - page->uri->hostname); - - err = DNSResolveName(page->uri->hostname, &ip, NULL); - if (err) { - browser_statusf(page->browser, "Error: Failed resolving: %d", err); + if (!request_tcp_connect(&finger->tcp, page->browser, + page->uri->hostname, page->uri->port)) { + xfree(&finger); return false; } - long2ip(ip, (char *)&ip_s); - browser_statusf(page->browser, "Connecting to %s port %d...", ip_s, - page->uri->port); - - err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf, - sizeof(req->tcp_buf), NULL, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPCreate failed: %d", err); - return false; - } + browser_statusf(page->browser, "Connected to %s, sending request...", + page->uri->hostname); - err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, - page->uri->port, &local_ip, &local_port, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed connecting to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - return false; - } - - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - return false; - } - if (page->uri->path[0] == '\0' || (page->uri->path[0] == '/' && page->uri->path[1] == '\0')) - req->query_len = snprintf(req->query, sizeof(req->query), - "\r\n", page->uri->hostname); + finger->query_len = snprintf(finger->query, sizeof(finger->query), + "\r\n"); else - req->query_len = snprintf(req->query, sizeof(req->query), + finger->query_len = snprintf(finger->query, sizeof(finger->query), "%s\r\n", page->uri->path + 1); - browser_statusf(page->browser, "Connected to %s, sending request...", - page->uri->hostname); - - memset(&req->tcp_wds, 0, sizeof(req->tcp_wds)); - req->tcp_wds[0].ptr = (Ptr)req->query; - req->tcp_wds[0].length = req->query_len; - - err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL, - NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPSend failed: %d", err); + if (!request_tcp_send(&finger->tcp, page->browser, finger->query, + finger->query_len)) { + request_tcp_cleanup(&finger->tcp); + xfree(&finger); return false; } + + page->fetch_cookie = finger; + browser_commit_to_loading_page(page->browser); - page->fetch_cookie = req; return true; } void finger_cleanup(struct page *page) { - struct finger_request *req; + struct finger_request *finger; - if (page->fetch_cookie == NULL) + finger = (struct finger_request *)(page->fetch_cookie); + if (finger == NULL) return; - - req = (struct finger_request *)(page->fetch_cookie); - if (req->tcp_stream) - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - + request_tcp_cleanup(&finger->tcp); xfree(&page->fetch_cookie); } bool finger_fetch(struct page *page) { - struct finger_request *req; - short err; - unsigned short slen; + struct finger_request *finger; + ssize_t len; - if (page->fetch_cookie == NULL) + finger = (struct finger_request *)(page->fetch_cookie); + if (finger == NULL) return false; - req = (struct finger_request *)(page->fetch_cookie); - - if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting", - req->tcp_iopb.ioResult)); + len = request_tcp_avail(&finger->tcp, page->browser); + if (len < -1) return false; - } - - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err); - return false; - } - - if (req->tcp_status_pb.connectionState != - ConnectionStateEstablished) { - BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)", - req->tcp_status_pb.connectionState)); - return false; - } - - slen = req->tcp_status_pb.amtUnreadData; - if (slen == 0) + if (len == 0) return true; - if (page->content_len + slen >= page->content_size) { - page = browser_grow_page_content(page, slen); + if (page->content_len + len >= page->content_size) { + page = browser_grow_page_content(page, len); if (page == NULL) return false; - req = (struct finger_request *)(page->fetch_cookie); + finger = (struct finger_request *)(page->fetch_cookie); } - err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, - (Ptr)(page->content + page->content_len), &slen, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err); + len = request_tcp_read(&finger->tcp, page->browser, + page->content + page->content_len, len); + if (len < 0) return false; - } - page->content_len += slen; + page->content_len += len; browser_statusf(page->browser, "Read %ld bytes", page->content_len); return true; --- gemini.c Wed Nov 6 13:06:39 2024 +++ gemini.c Wed Nov 6 22:07:32 2024 @@ -19,37 +19,20 @@ #include <string.h> #include "detritus.h" -#include "browser.h" #define GEMINI_PORT 1965 enum { - REQ_STATE_NEGOTIATING = 1, - REQ_STATE_SENDING_REQUEST, - REQ_STATE_PARSING_RESPONSE -}; - -enum { PARSE_STATE_HEADER, PARSE_STATE_GEMTEXT, PARSE_STATE_DOWNLOAD }; struct gemini_request { - uint8_t tls_id; - unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ - unsigned char tcp_input[2048]; - unsigned long tcp_input_len; + struct tls_request tls; - TCPiopb tcp_iopb; - StreamPtr tcp_stream; - wdsEntry tcp_wds[2]; - TCPStatusPB tcp_status_pb; - bool tcp_done_reading; - char message[URI_MAX_STR_LEN + 1]; size_t message_len; - short state; }; @@ -59,10 +42,6 @@ bool gemini_fetch(struct page *page); bool gemini_process(struct page *page); void gemini_cleanup(struct page *page); -static bool shuffle_read_tls_ciphertext(struct page *page); -static bool shuffle_read_tcp_ciphertext(struct page *page, short space); -static bool shuffle_tls_send_plaintext(struct page *page, short space); -static bool shuffle_tls_read_plaintext(struct page *page); static bool parse_header(struct page *page, char *str, size_t len); struct uri_handler gemini_handler = { @@ -82,13 +61,7 @@ gemini_parse_uri(char *uristr) bool gemini_init_request(struct page *page) { - struct gemini_request *req = NULL; - struct tls_init_request tls_req; - char ip_s[12 + 3 + 1]; - unsigned long time; - short err; - ip_addr ip, local_ip; - tcp_port port, local_port; + struct gemini_request *gemini = NULL; if (page->content_len) { /* already fetched, nothing to do here but reset */ @@ -97,13 +70,8 @@ gemini_init_request(struct page *page) return true; } - if (!scsi_can_do_tls()) { - warn("No TLS accelerator found, cannot make Gemini requests"); - return false; - } - - req = xmalloczero(sizeof(struct gemini_request)); - if (req == NULL) { + gemini = xmalloczero(sizeof(struct gemini_request)); + if (gemini == NULL) { warn("Out of memory"); return false; } @@ -111,376 +79,132 @@ gemini_init_request(struct page *page) if (page->uri->port == 0) page->uri->port = GEMINI_PORT; - browser_statusf(page->browser, "Resolving \"%s\"...", - page->uri->hostname); - - err = DNSResolveName(page->uri->hostname, &ip, NULL); - if (err) { - browser_statusf(page->browser, "Error: Failed resolving: %d", err); - goto fail; + if (!request_tls_connect(&gemini->tls, page->browser, + page->uri->hostname, page->uri->port, + BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY)) { + xfree(&gemini); + return false; } - - long2ip(ip, (char *)&ip_s); - browser_statusf(page->browser, "Connecting to %s port %d...", ip_s, - page->uri->port); - - err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf, - sizeof(req->tcp_buf), NULL, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPCreate failed: %d", err); - goto fail; - } - err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, - page->uri->port, &local_ip, &local_port, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed connecting to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - goto fail; - } + gemini->message_len = snprintf(gemini->message, + sizeof(gemini->message), "%s\r\n", page->uri->str); - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - goto fail; - } - - req->message_len = snprintf(req->message, sizeof(req->message), - "%s\r\n", page->uri->str); - - browser_statusf(page->browser, - "Connected to %s, performing TLS negotiation...", - page->uri->hostname); - - memset(&tls_req, 0, sizeof(tls_req)); - strlcpy(tls_req.hostname, page->uri->hostname, - sizeof(tls_req.hostname)); - time = MAC_TO_UNIX_TIME(Time); - tls_req.unix_time[0] = (time >> 24) & 0xff; - tls_req.unix_time[1] = (time >> 16) & 0xff; - tls_req.unix_time[2] = (time >> 8) & 0xff; - tls_req.unix_time[3] = (time) & 0xff; - /* sad */ - tls_req.flags[1] = BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY; + page->fetch_cookie = gemini; - req->tls_id = scsi_tls_init(&tls_req); - req->state = REQ_STATE_NEGOTIATING; - - if (req->tls_id == 0) { - browser_statusf(page->browser, "TLS handshake failed!"); - goto fail; - } - - page->fetch_cookie = req; return true; - -fail: - if (req) { - if (req->tcp_stream) - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - - xfree(&req); - page->fetch_cookie = NULL; - } - - return false; } void gemini_cleanup(struct page *page) { - struct gemini_request *req; + struct gemini_request *gemini; - if (page->fetch_cookie == NULL) + gemini = (struct gemini_request *)(page->fetch_cookie); + if (gemini == NULL) return; - req = (struct gemini_request *)(page->fetch_cookie); - - if (req->tcp_stream) - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - - if (req->tls_id) - scsi_tls_close(req->tls_id); - + request_tls_cleanup(&gemini->tls); xfree(&page->fetch_cookie); } -bool -gemini_fetch(struct page *page) +static bool +gemini_fetch_tls_write_callback(struct tls_request *request, + struct browser *browser, void *cookie, bool wrote, char **buf, + size_t *len) { + struct gemini_request *gemini; page_handle pageh; - struct gemini_request *req; - size_t len; - unsigned short slen; - short err, status; - short cipherspace, plainspace, error; - bool ret; + struct page *page; + size_t tlen; - if (page->fetch_cookie == NULL) + if (cookie == NULL) return false; + + pageh = (page_handle)cookie; + HLock(pageh); + page = *pageh; + gemini = (struct gemini_request *)page->fetch_cookie; - req = (struct gemini_request *)(page->fetch_cookie); - - if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting", - req->tcp_iopb.ioResult)); + if (page == NULL) { + HUnlock(pageh); return false; } - - status = scsi_tls_status(req->tls_id, &cipherspace, &plainspace, - &error); - pageh = (page_handle)RecoverHandle(page); - - while (status != 0) { - if ((status & 0x1) && page->content_pos == page->content_len) { - /* closed */ - if (error != 0) - browser_statusf(page->browser, - "Error: TLS handshake failed: %d (TLS status 0x%x)", - error, status); - return false; + if (wrote) { + if (*len == gemini->message_len) { + browser_statusf(browser, "Sent request, waiting for reply..."); + gemini->message_len = 0; + } else { + memmove(gemini->message, gemini->message + *len, + gemini->message_len - *len); + gemini->message_len -= *len; } - - if (status & 0x2) { - /* tls has ciphertext for tcp */ - if (!shuffle_read_tls_ciphertext(page)) - return false; - status &= ~0x2; - } - - if ((status & 0x10) || page->content_pos < page->content_len) { - /* tls has plaintext data for us */ - if (!shuffle_tls_read_plaintext(page)) - return false; - /* handle may have grown */ - HLock(pageh); - page = *pageh; - status &= ~0x10; - } - - if (status & 0x8) { - /* tls can read plaintext from us */ - if (!shuffle_tls_send_plaintext(page, plainspace)) - return false; - status &= ~0x8; - } - - if (status & 0x4) { - /* tls can read ciphertext from tcp */ - if (!shuffle_read_tcp_ciphertext(page, cipherspace)) - return false; - status &= ~0x4; - } - - if (status) { - browser_statusf(page->browser, "TLS status is 0x%x?", status); - return false; - } + } else { + tlen = MIN(gemini->message_len, *len); + *buf = gemini->message; + *len = tlen; } + HUnlock(pageh); + return true; } static bool -shuffle_read_tls_ciphertext(struct page *page) +gemini_fetch_tls_read_callback(struct tls_request *request, + struct browser *browser, void *cookie, char *buf, size_t len) { - struct gemini_request *req; - size_t len; - short err; - unsigned char *buf; + page_handle pageh; + struct page *page; + size_t tlen; - /* read ciphertext from TLS and send it out TCP */ - - req = (struct gemini_request *)(page->fetch_cookie); - - len = scsi_tls_read(req->tls_id, &buf, 0, true); - if (len == 0 || buf == NULL) { - browser_statusf(page->browser, - "Error: No ciphertext read from TLS when expected to"); + if (cookie == NULL) return false; - } - - BROWSER_DEBUGF((page->browser, - "Read %lu bytes of TLS ciphertext, forwarding to TCP", len)); - - if (req->tcp_done_reading) - return true; - memset(&req->tcp_wds, 0, sizeof(req->tcp_wds)); - req->tcp_wds[0].ptr = (Ptr)buf; - req->tcp_wds[0].length = len; - - err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL, - NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPSend failed: %d", err); + pageh = (page_handle)cookie; + HLock(pageh); + page = *pageh; + + if (page == NULL) { + HUnlock(pageh); return false; } - return true; -} - -static bool -shuffle_read_tcp_ciphertext(struct page *page, short space) -{ - struct gemini_request *req; - size_t len, n; - short err; - unsigned short slen; - - /* read ciphertext from TCP and send it to TLS */ - - req = (struct gemini_request *)(page->fetch_cookie); - - if (req->tcp_input_len == sizeof(req->tcp_input) || - req->tcp_done_reading) - goto forward_ciphertext; - - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - req->tcp_done_reading = true; - browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err); - goto forward_ciphertext; + if (page->content_len + len >= page->content_size) { + page = browser_grow_page_content(page, len); + if (page == NULL) + return false; } - if (req->tcp_status_pb.connectionState != ConnectionStateEstablished) { - BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)", - req->tcp_status_pb.connectionState)); - req->tcp_done_reading = true; - goto forward_ciphertext; - } - if (req->tcp_status_pb.amtUnreadData == 0) - goto forward_ciphertext; - - slen = MIN(req->tcp_status_pb.amtUnreadData, - sizeof(req->tcp_input) - req->tcp_input_len); - if (!slen) { - browser_statusf(page->browser, - "Error: No buffer space available in tcp_input?"); - goto forward_ciphertext; - } + memcpy(page->content + page->content_len, buf, len); + page->content_len += len; + browser_statusf(browser, "Read %ld bytes", page->content_len); + HUnlock(pageh); - err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, - (Ptr)(req->tcp_input + req->tcp_input_len), &slen, NULL, NULL, - false); - if (err) { - browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err); - goto forward_ciphertext; - } - req->tcp_input_len += slen; - BROWSER_DEBUGF((page->browser, - "Read %d bytes of TCP ciphertext, forwarding to TLS", slen)); - -forward_ciphertext: - if (!req->tcp_input_len || !space) - return true; - - slen = req->tcp_input_len; - if (slen > space) - slen = space; - BROWSER_DEBUGF((page->browser, - "Forwarding %d bytes of TCP ciphertext to TLS", slen)); - len = scsi_tls_write(req->tls_id, req->tcp_input, slen, true); - if (len == 0) { - browser_statusf(page->browser, - "Error: Failed forwarding %d bytes of ciphertext to TLS", slen); - return false; - } - - if (len == req->tcp_input_len) - req->tcp_input_len = 0; - else { - memmove(req->tcp_input, req->tcp_input + len, - req->tcp_input_len - len); - req->tcp_input_len -= len; - } - BROWSER_DEBUGF((page->browser, - "Wrote %ld bytes of TCP ciphertext to TLS, %ld left", len, - req->tcp_input_len)); return true; } -static bool -shuffle_tls_send_plaintext(struct page *page, short space) +bool +gemini_fetch(struct page *page) { page_handle pageh; - struct gemini_request *req; - size_t slen, len; + struct gemini_request *gemini; + short n; + bool ret; - /* send any plaintext from us to TLS */ - - req = (struct gemini_request *)(page->fetch_cookie); - - if (req->message_len == 0) - return true; - - if (req->state == REQ_STATE_NEGOTIATING) { - browser_statusf(page->browser, "Negotiated, sending request..."); - req->state = REQ_STATE_SENDING_REQUEST; - } else if (req->state != REQ_STATE_SENDING_REQUEST) { - browser_statusf(page->browser, "In bogus state (%d) instead of " - "SENDING_REQUEST, disconnecting", req->state); + gemini = (struct gemini_request *)(page->fetch_cookie); + if (gemini == NULL) return false; - } - slen = req->message_len; - if (slen > space) - slen = space; - len = scsi_tls_write(req->tls_id, (unsigned char *)req->message, slen, - false); - if (!len) { - browser_statusf(page->browser, - "Error: Failed sending %ld bytes of plaintext to TLS", slen); - return false; - } + pageh = (page_handle)RecoverHandle(page); - if (len == req->message_len) - req->message_len = 0; - else { - memmove(req->message, req->message + len, - req->message_len - len); - req->message_len -= len; - } - BROWSER_DEBUGF((page->browser, "Wrote %ld bytes of plaintext to TLS, " - "%ld left", len, req->message_len)); - return true; -} - -static bool -shuffle_tls_read_plaintext(struct page *page) -{ - page_handle pageh; - struct gemini_request *req; - size_t len; - unsigned char *buf; + ret = request_tls_shuffle(&gemini->tls, page->browser, + gemini_fetch_tls_read_callback, pageh, + gemini_fetch_tls_write_callback, pageh); - /* read as much plaintext from TLS as we can buffer */ - - req = (struct gemini_request *)(page->fetch_cookie); - - len = scsi_tls_read(req->tls_id, &buf, PAGE_CONTENT_CHUNK_SIZE, false); - if (len == 0) - return true; - - if (page->content_len + len >= page->content_size) { - page = browser_grow_page_content(page, len); - if (page == NULL) - return false; - req = (struct gemini_request *)(page->fetch_cookie); - } - - memcpy(page->content + page->content_len, buf, len); - page->content_len += len; + HUnlock(pageh); - browser_statusf(page->browser, "Read %ld bytes", page->content_len); - - return true; + return ret; } static bool @@ -489,11 +213,16 @@ gemini_process(struct page *page) page_handle pageh; size_t n, trail, skip, len; char c; - bool newline; + bool newline, ret; pageh = (page_handle)RecoverHandle(page); + if (page->content_pos == page->content_len) + return (page->fetch_cookie != NULL); + handle_state: + ret = true; + switch (page->parse_state) { case PARSE_STATE_HEADER: { short status; @@ -537,8 +266,9 @@ handle_state: case PARSE_STATE_GEMTEXT: TVAutoCalc(page->browser->output_tv, false); - for (n = page->content_pos; n <= page->content_len; n++) { - if (!(n == page->content_len || page->content[n] == '\n')) + for (n = page->content_pos; n < page->content_len; n++) { + if (page->content[n] != '\n' && + !(n == page->content_len - 1 && page->fetch_cookie == NULL)) continue; len = n - page->content_pos + 1; @@ -546,7 +276,7 @@ handle_state: skip = 0; newline = false; - if (n < page->content_len && page->content[n] == '\n') { + if (page->content[n] == '\n') { len--; trail = 1; newline = true; @@ -720,14 +450,23 @@ print_line: if (len) browser_print(page->browser, page->content + page->content_pos + skip, len); - + page->content_pos += skip + len + trail; + if (!(page->browser->style & STYLE_PRE)) page->browser->style = STYLE_NONE; if (newline) browser_print(page->browser, "\r", 1); } + if (page->fetch_cookie == NULL && + page->content_pos < page->content_len) { + browser_print(page->browser, page->content + page->content_pos, + page->content_len - page->content_pos); + page->content_pos = page->content_len; + ret = false; + } + TVAutoCalc(page->browser->output_tv, true); TVCalcLines(page->browser->output_tv); TVUpdate(page->browser->output_tv); @@ -736,7 +475,7 @@ print_line: break; } - return true; + return ret; } static bool --- gopher.c Wed Nov 6 12:57:26 2024 +++ gopher.c Wed Nov 6 22:07:39 2024 @@ -19,20 +19,14 @@ #include <string.h> #include "detritus.h" -#include "browser.h" #define GOPHER_PORT 70 static const char showable_types[] = "013i"; struct gopher_request { - unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ + struct tcp_request tcp; - TCPiopb tcp_iopb; - StreamPtr tcp_stream; - wdsEntry tcp_wds[2]; - TCPStatusPB tcp_status_pb; - char selector[URI_MAX_PATH_LEN + 3]; size_t selector_len; }; @@ -62,21 +56,18 @@ gopher_parse_uri(char *uristr) bool gopher_init_request(struct page *page) { - struct gopher_request *req = NULL; - size_t len; - char ip_s[12 + 3 + 1], *filename; - short err; - ip_addr ip, local_ip; - tcp_port port, local_port; + struct gopher_request *gopher = NULL; + char *filename; if (page->content_len) { /* already fetched, nothing to do here but reset */ page->content_pos = 0; + browser_commit_to_loading_page(page->browser); return true; } - req = xmalloczero(sizeof(struct gopher_request)); - if (req == NULL) { + gopher = xmalloczero(sizeof(struct gopher_request)); + if (gopher == NULL) { warn("Out of memory"); return false; } @@ -84,44 +75,12 @@ gopher_init_request(struct page *page) if (page->uri->port == 0) page->uri->port = GOPHER_PORT; - browser_statusf(page->browser, "Resolving \"%s\"...", - page->uri->hostname); - - err = DNSResolveName(page->uri->hostname, &ip, NULL); - if (err) { - browser_statusf(page->browser, "Error: Failed resolving: %d", err); + if (!request_tcp_connect(&gopher->tcp, page->browser, + page->uri->hostname, page->uri->port)) { + xfree(&gopher); return false; } - long2ip(ip, (char *)&ip_s); - browser_statusf(page->browser, "Connecting to %s port %d...", ip_s, - page->uri->port); - - err = _TCPCreate(&req->tcp_iopb, &req->tcp_stream, (Ptr)req->tcp_buf, - sizeof(req->tcp_buf), NULL, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPCreate failed: %d", err); - return false; - } - - err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, - page->uri->port, &local_ip, &local_port, NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed connecting to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - return false; - } - - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - browser_statusf(page->browser, - "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", - page->uri->hostname, ip_s, page->uri->port, err); - return false; - } - /* * If we have a path other than /, assume the first character is the * type and should not be sent, per RFC 4266. @@ -129,41 +88,37 @@ gopher_init_request(struct page *page) if (page->uri->path[0] == '\0' || (page->uri->path[0] == '/' && page->uri->path[1] == '\0')) { page->type[0] = '1'; - req->selector_len = snprintf(req->selector, sizeof(req->selector), - "\r\n"); + gopher->selector_len = snprintf(gopher->selector, + sizeof(gopher->selector), "\r\n"); } else { /* /0/blah -> blah */ page->type[0] = page->uri->path[1]; - req->selector_len = snprintf(req->selector, sizeof(req->selector), - "%s\r\n", page->uri->path + 2); + gopher->selector_len = snprintf(gopher->selector, + sizeof(gopher->selector), "%s\r\n", page->uri->path + 2); } browser_statusf(page->browser, "Connected to %s, sending request...", page->uri->hostname); - memset(&req->tcp_wds, 0, sizeof(req->tcp_wds)); - req->tcp_wds[0].ptr = (Ptr)req->selector; - req->tcp_wds[0].length = req->selector_len; - - err = _TCPSend(&req->tcp_iopb, req->tcp_stream, req->tcp_wds, NULL, - NULL, false); - if (err) { - browser_statusf(page->browser, "Error: TCPSend failed: %d", err); + if (!request_tcp_send(&gopher->tcp, page->browser, gopher->selector, + gopher->selector_len)) { + request_tcp_cleanup(&gopher->tcp); + xfree(&gopher); return false; } + + page->fetch_cookie = gopher; - page->fetch_cookie = req; - if (strchr(showable_types, page->type[0]) == NULL) { - if (req->selector_len == 2) + if (gopher->selector_len == 2) filename = NULL; else { filename = strrchr(page->uri->path + 2, '/'); if (filename && filename[0] == '/') filename++; - if (!browser_start_download(page->browser, filename)) - return false; } + if (!browser_start_download(page->browser, filename)) + return false; } else browser_commit_to_loading_page(page->browser); @@ -173,91 +128,66 @@ gopher_init_request(struct page *page) void gopher_cleanup(struct page *page) { - struct gopher_request *req; + struct gopher_request *gopher; - if (page->fetch_cookie == NULL) + gopher = (struct gopher_request *)(page->fetch_cookie); + if (gopher == NULL) return; - - req = (struct gopher_request *)(page->fetch_cookie); - if (req->tcp_stream) - _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); - + request_tcp_cleanup(&gopher->tcp); xfree(&page->fetch_cookie); } bool gopher_fetch(struct page *page) { - struct gopher_request *req; + struct gopher_request *gopher; + ssize_t len; + size_t wlen; + char *buf; short err; - unsigned short slen; - unsigned long llen, ollen; - Ptr rcvptr; - if (page->fetch_cookie == NULL) + gopher = (struct gopher_request *)(page->fetch_cookie); + if (gopher == NULL) return false; - req = (struct gopher_request *)(page->fetch_cookie); - - if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - BROWSER_DEBUGF((page->browser, "TCP I/O Result %d, disconnecting", - req->tcp_iopb.ioResult)); + len = request_tcp_avail(&gopher->tcp, page->browser); + if (len < -1) return false; - } - - err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, - NULL, NULL, false); - if (err) { - browser_statusf(page->browser, "Error: Bad TCPStatus: %d", err); - return false; - } - - if (req->tcp_status_pb.connectionState != - ConnectionStateEstablished) { - BROWSER_DEBUGF((page->browser, "TCP connection closed (state %d)", - req->tcp_status_pb.connectionState)); - return false; - } - - slen = req->tcp_status_pb.amtUnreadData; - if (slen == 0) + if (len == 0) return true; if (page->download_frefnum) { - rcvptr = (Ptr)(page->content); - if (slen > page->content_size) - slen = page->content_size; + if (len > page->content_size) + len = page->content_size; + buf = page->content; } else { - if (page->content_len + slen >= page->content_size) { - page = browser_grow_page_content(page, slen); + if (page->content_len + len >= page->content_size) { + page = browser_grow_page_content(page, len); if (page == NULL) return false; - req = (struct gopher_request *)(page->fetch_cookie); + gopher = (struct gopher_request *)(page->fetch_cookie); } - rcvptr = (Ptr)(page->content + page->content_len); + buf = page->content + page->content_len; } - err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, rcvptr, &slen, NULL, - NULL, false); - if (err) { - browser_statusf(page->browser, "Error: Failed TCPRcv: %d", err); + len = request_tcp_read(&gopher->tcp, page->browser, buf, len); + if (len < 0) return false; - } - + if (page->download_frefnum) { - ollen = llen = slen; - err = FSWrite(page->download_frefnum, &llen, page->content); - if (err || ollen != llen) { - warn("Failed to write: %d", err); + wlen = len; + err = FSWrite(page->download_frefnum, &wlen, page->content); + if (err || wlen != len) { + warn("Failed to write %ld bytes: %d", len, err); return false; } - page->download_len += llen; + page->download_len += wlen; browser_statusf(page->browser, "Wrote %ld bytes", page->download_len); } else { - page->content_len += slen; + page->content_len += len; browser_statusf(page->browser, "Read %ld bytes", page->content_len); if (strchr(showable_types, page->type[0]) && --- main.c Tue Nov 5 20:47:53 2024 +++ main.c Wed Nov 6 22:06:06 2024 @@ -18,7 +18,6 @@ #include <string.h> #include "detritus.h" -#include "browser.h" #include "focusable.h" #include "tcp.h" #include "util.h" @@ -242,172 +241,4 @@ void handle_exit(void) { focusables_quit(); -} - -struct URI * -parse_uri(char *uristr, char *restrict_protocol) -{ - static char protocol[URI_MAX_PROTOCOL_LEN + 1]; - static char hostname[URI_MAX_HOSTNAME_LEN + 1]; - static char path[URI_MAX_PATH_LEN + 1]; - static char str[URI_MAX_STR_LEN + 1]; - struct URI *uri; - char *data; - size_t protocol_len, hostname_len, path_len, str_len, size; - short count; - unsigned short port; - - /* protocol://host/path */ - if (count = 0, sscanf(uristr, - "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" - STR(URI_MAX_HOSTNAME_LEN) "[^/]%" - STR(URI_MAX_PATH_LEN) "[ -~]%n", /* %s stops at whitespace, use all vis */ - &protocol, &hostname, &path, &count) == 3 && count > 10) - goto parse_ok; - - /* protocol://host/ */ - if (count = 0, sscanf(uristr, - "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" - STR(URI_MAX_HOSTNAME_LEN) "[^/]/%n", - &protocol, &hostname, &count) == 2 && count > 10) { - path[0] = '/'; - path[1] = '\0'; - goto parse_ok; - } - - /* gemini://host */ - if (count = 0, sscanf(uristr, - "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" - STR(URI_MAX_HOSTNAME_LEN) "[^/]%n", - &protocol, &hostname, &count) == 2 && count > 10) { - path[0] = '/'; - path[1] = '\0'; - goto parse_ok; - } - - /* failed */ - return NULL; - -parse_ok: - if (restrict_protocol != NULL && - strcasecmp(restrict_protocol, protocol) != 0) - return NULL; - - protocol_len = strlen(protocol); - hostname_len = strlen(hostname); - path_len = strlen(path); - str_len = protocol_len + 3 + hostname_len + path_len; - - size = sizeof(struct URI) + protocol_len + 1 + hostname_len + 1 + - path_len + 1 + str_len + 1; - uri = xmalloc(size); - if (uri == NULL) - return NULL; - - uri->port = 0; - - data = (char *)uri + sizeof(struct URI); - - uri->protocol = data; - memcpy(uri->protocol, protocol, protocol_len); - uri->protocol[protocol_len] = '\0'; - data += protocol_len + 1; - - uri->hostname = data; - memcpy(uri->hostname, hostname, hostname_len); - uri->hostname[hostname_len] = '\0'; - data += hostname_len + 1; - - /* TODO: cut at ? and put that in query */ - uri->path = data; - memcpy(uri->path, path, path_len); - uri->path[path_len] = '\0'; - data += path_len + 1; - - uri->str = data; - data += snprintf(uri->str, str_len + 1, "%s://%s%s", - uri->protocol, uri->hostname, uri->path); - data++; - - if (data > (char *)uri + size) - panic("URI overflow"); - - return uri; -} - -struct URI * -build_relative_uri(struct URI *uri, char *relative, size_t len) -{ - static char path[URI_MAX_PATH_LEN + 1]; - static char str[URI_MAX_STR_LEN + 1]; - size_t slen, plen, n; - - /* http://a.b/c + //d/e -> http://d/e */ - if (relative[0] == '/' && relative[1] == '/') { - /* retain protocol, new host and path */ - slen = snprintf(str, sizeof(str), "%s://", uri->protocol); - while (slen < sizeof(str) - 1 && len) { - str[slen - 1] = *relative++; - slen++; - } - str[slen] = '\0'; - - return parse_uri(str, NULL); - } - - /* http://a.b/c + /d/e -> http://a.b/d/e */ - if (relative[0] == '/') { - /* retain protocol and host, new path */ - slen = snprintf(str, sizeof(str), "%s://%s", uri->protocol, - uri->hostname); - while (slen < sizeof(str) - 1 && len) { - str[slen] = *relative++; - slen++; - } - str[slen] = '\0'; - - return parse_uri(str, NULL); - } - - for (n = 0; n <= len; n++) { - /* http://a.b/c + gemini://d/e -> gemini://d/e */ - if (n < len - 2 && relative[n] == ':' && relative[n + 1] == '/' && - relative[n + 2] == '/') { - /* protocol found, this isn't relative */ - if (len >= sizeof(str)) - len = sizeof(str) - 1; - memcpy(str, relative, len); - str[len] = '\0'; - return parse_uri(str, NULL); - } - - /* http://a.b/c/d.html + e/f.html -> http://a.b/c/e/f.html */ - if (relative[n] == '/' || n == len) { - /* remove last component in uri path up to slash */ - plen = strlcpy(path, uri->path, sizeof(path)); - while (plen && path[plen - 1] != '/') { - path[plen - 1] = '\0'; - plen--; - } - - if (plen == 0) { - path[0] = '/'; - path[1] = '\0'; - plen = 1; - } - - if (plen >= sizeof(path) - 1) - plen = sizeof(path) - 2; - - memcpy(path + plen, relative, len); - path[plen + len] = '\0'; - snprintf(str, sizeof(str), "%s://%s%s", uri->protocol, - uri->hostname, path); - return parse_uri(str, NULL); - } - } - - /* what the heck is this? */ - Debugger(); - return parse_uri(relative, NULL); } --- request.c Wed Nov 6 20:15:15 2024 +++ request.c Wed Nov 6 22:07:45 2024 @@ -0,0 +1,617 @@ +/* + * Copyright (c) 2021-2024 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <stdarg.h> +#include <stdio.h> +#include <string.h> + +#include "detritus.h" + +enum { + REQ_STATE_NEGOTIATING = 1, + REQ_STATE_SENDING_REQUEST, + REQ_STATE_PARSING_RESPONSE +}; + +static bool request_tls_read_tls_ciphertext(struct tls_request *request, + struct browser *browser); +static bool request_tls_read_tcp_ciphertext(struct tls_request *request, + struct browser *browser, short space); +static bool request_tls_send_plaintext(struct tls_request *request, + struct browser *browser, size_t space, request_tls_writer writer, + void *cookie); +static bool request_tls_read_plaintext(struct tls_request *request, + struct browser *browser, request_tls_reader reader, void *cookie); + +struct URI * +parse_uri(char *uristr, char *restrict_protocol) +{ + static char protocol[URI_MAX_PROTOCOL_LEN + 1]; + static char hostname[URI_MAX_HOSTNAME_LEN + 1]; + static char path[URI_MAX_PATH_LEN + 1]; + static char str[URI_MAX_STR_LEN + 1]; + struct URI *uri; + char *data; + size_t protocol_len, hostname_len, path_len, str_len, size; + short count; + unsigned short port; + + /* protocol://host/path */ + if (count = 0, sscanf(uristr, + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]%" + STR(URI_MAX_PATH_LEN) "[ -~]%n", /* %s stops at whitespace, use all vis */ + &protocol, &hostname, &path, &count) == 3 && count > 10) + goto parse_ok; + + /* protocol://host/ */ + if (count = 0, sscanf(uristr, + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]/%n", + &protocol, &hostname, &count) == 2 && count > 10) { + path[0] = '/'; + path[1] = '\0'; + goto parse_ok; + } + + /* gemini://host */ + if (count = 0, sscanf(uristr, + "%" STR(URI_MAX_PROTOCOL_LEN) "[^:]://%" + STR(URI_MAX_HOSTNAME_LEN) "[^/]%n", + &protocol, &hostname, &count) == 2 && count > 10) { + path[0] = '/'; + path[1] = '\0'; + goto parse_ok; + } + + /* failed */ + return NULL; + +parse_ok: + if (restrict_protocol != NULL && + strcasecmp(restrict_protocol, protocol) != 0) + return NULL; + + protocol_len = strlen(protocol); + hostname_len = strlen(hostname); + path_len = strlen(path); + str_len = protocol_len + 3 + hostname_len + path_len; + + size = sizeof(struct URI) + protocol_len + 1 + hostname_len + 1 + + path_len + 1 + str_len + 1; + uri = xmalloc(size); + if (uri == NULL) + return NULL; + + uri->port = 0; + + data = (char *)uri + sizeof(struct URI); + + uri->protocol = data; + memcpy(uri->protocol, protocol, protocol_len); + uri->protocol[protocol_len] = '\0'; + data += protocol_len + 1; + + uri->hostname = data; + memcpy(uri->hostname, hostname, hostname_len); + uri->hostname[hostname_len] = '\0'; + data += hostname_len + 1; + + /* TODO: cut at ? and put that in query */ + uri->path = data; + memcpy(uri->path, path, path_len); + uri->path[path_len] = '\0'; + data += path_len + 1; + + uri->str = data; + data += snprintf(uri->str, str_len + 1, "%s://%s%s", + uri->protocol, uri->hostname, uri->path); + data++; + + if (data > (char *)uri + size) + panic("URI overflow"); + + return uri; +} + +struct URI * +build_relative_uri(struct URI *uri, char *relative, size_t len) +{ + static char path[URI_MAX_PATH_LEN + 1]; + static char str[URI_MAX_STR_LEN + 1]; + size_t slen, plen, n; + + /* http://a.b/c + //d/e -> http://d/e */ + if (relative[0] == '/' && relative[1] == '/') { + /* retain protocol, new host and path */ + slen = snprintf(str, sizeof(str), "%s://", uri->protocol); + while (slen < sizeof(str) - 1 && len) { + str[slen - 1] = *relative++; + slen++; + } + str[slen] = '\0'; + + return parse_uri(str, NULL); + } + + /* http://a.b/c + /d/e -> http://a.b/d/e */ + if (relative[0] == '/') { + /* retain protocol and host, new path */ + slen = snprintf(str, sizeof(str), "%s://%s", uri->protocol, + uri->hostname); + while (slen < sizeof(str) - 1 && len) { + str[slen] = *relative++; + slen++; + } + str[slen] = '\0'; + + return parse_uri(str, NULL); + } + + for (n = 0; n <= len; n++) { + /* http://a.b/c + gemini://d/e -> gemini://d/e */ + if (n < len - 2 && relative[n] == ':' && relative[n + 1] == '/' && + relative[n + 2] == '/') { + /* protocol found, this isn't relative */ + if (len >= sizeof(str)) + len = sizeof(str) - 1; + memcpy(str, relative, len); + str[len] = '\0'; + return parse_uri(str, NULL); + } + + /* http://a.b/c/d.html + e/f.html -> http://a.b/c/e/f.html */ + if (relative[n] == '/' || n == len) { + /* remove last component in uri path up to slash */ + plen = strlcpy(path, uri->path, sizeof(path)); + while (plen && path[plen - 1] != '/') { + path[plen - 1] = '\0'; + plen--; + } + + if (plen == 0) { + path[0] = '/'; + path[1] = '\0'; + plen = 1; + } + + if (plen >= sizeof(path) - 1) + plen = sizeof(path) - 2; + + memcpy(path + plen, relative, len); + path[plen + len] = '\0'; + snprintf(str, sizeof(str), "%s://%s%s", uri->protocol, + uri->hostname, path); + return parse_uri(str, NULL); + } + } + + /* what the heck is this? */ + Debugger(); + return parse_uri(relative, NULL); +} + +bool +request_tcp_connect(struct tcp_request *request, struct browser *browser, + char *hostname, unsigned short port) +{ + size_t len; + char ip_s[12 + 3 + 1]; + short err; + ip_addr ip, local_ip; + tcp_port local_port; + + browser_statusf(browser, "Resolving \"%s\"...", hostname); + + err = DNSResolveName(hostname, &ip, NULL); + if (err) { + browser_statusf(browser, "Error: Failed resolving: %d", err); + request_tcp_cleanup(request); + return false; + } + + long2ip(ip, (char *)&ip_s); + browser_statusf(browser, "Connecting to %s port %d...", ip_s, port); + + err = _TCPCreate(&request->iopb, &request->stream, (Ptr)request->buf, + sizeof(request->buf), NULL, NULL, NULL, false); + if (err) { + browser_statusf(browser, "Error: TCPCreate failed: %d", err); + request_tcp_cleanup(request); + return false; + } + + err = _TCPActiveOpen(&request->iopb, request->stream, ip, port, + &local_ip, &local_port, NULL, NULL, false); + if (err) { + browser_statusf(browser, + "Error: Failed connecting to %s (%s) port %d: %d", + hostname, ip_s, port, err); + request_tcp_cleanup(request); + return false; + } + + err = _TCPStatus(&request->iopb, request->stream, &request->status_pb, + NULL, NULL, false); + if (err) { + browser_statusf(browser, + "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", + hostname, ip_s, port, err); + request_tcp_cleanup(request); + return false; + } + + return true; +} + +void +request_tcp_cleanup(struct tcp_request *request) +{ + if (request->stream) { + _TCPRelease(&request->iopb, request->stream, NULL, NULL, false); + request->stream = 0; + } +} + +bool +request_tcp_send(struct tcp_request *request, struct browser *browser, + char *data, size_t len) +{ + short err; + + memset(&request->wds, 0, sizeof(request->wds)); + request->wds[0].ptr = (Ptr)data; + request->wds[0].length = len; + + err = _TCPSend(&request->iopb, request->stream, request->wds, NULL, + NULL, false); + if (err) { + browser_statusf(browser, "Error: TCPSend failed: %d", err); + return false; + } + + return true; +} + +ssize_t +request_tcp_avail(struct tcp_request *request, struct browser *browser) +{ + short err; + + if (request->iopb.ioResult > 0 || CommandPeriodPressed()) { + BROWSER_DEBUGF((browser, "TCP I/O Result %d, disconnecting", + request->iopb.ioResult)); + return -1; + } + + err = _TCPStatus(&request->iopb, request->stream, &request->status_pb, + NULL, NULL, false); + if (err) { + browser_statusf(browser, "Error: Bad TCPStatus: %d", err); + return -1; + } + + if (request->status_pb.connectionState != ConnectionStateEstablished) { + BROWSER_DEBUGF((browser, "TCP connection closed (state %d)", + request->status_pb.connectionState)); + return -1; + } + + return request->status_pb.amtUnreadData; +} + +ssize_t +request_tcp_read(struct tcp_request *request, struct browser *browser, + char *buf, size_t len) +{ + short err; + unsigned short slen; + + slen = len; + err = _TCPRcv(&request->iopb, request->stream, (Ptr)buf, &slen, NULL, + NULL, false); + if (err) { + browser_statusf(browser, "Error: Failed TCPRcv: %d", err); + return -1; + } + + return slen; +} + +/* + * TLS over TCP using BlueSCSI TLS Accelerator + */ + +bool +request_tls_connect(struct tls_request *request, struct browser *browser, + char *hostname, unsigned short port, short flags) +{ + struct tls_init_request tls_req; + unsigned long time; + + if (!scsi_can_do_tls()) { + warn("No TLS accelerator found, cannot make TLS requests"); + return false; + } + + if (!request_tcp_connect(&request->tcp, browser, hostname, port)) + return false; + + browser_statusf(browser, + "Connected to %s, performing TLS negotiation...", hostname); + + memset(&tls_req, 0, sizeof(tls_req)); + strlcpy(tls_req.hostname, hostname, sizeof(tls_req.hostname)); + + time = MAC_TO_UNIX_TIME(Time); + tls_req.unix_time[0] = (time >> 24) & 0xff; + tls_req.unix_time[1] = (time >> 16) & 0xff; + tls_req.unix_time[2] = (time >> 8) & 0xff; + tls_req.unix_time[3] = (time) & 0xff; + + tls_req.flags[1] = flags; + + request->tls_state = REQ_STATE_NEGOTIATING; + request->tls_id = scsi_tls_init(&tls_req); + + if (request->tls_id == 0) { + browser_statusf(browser, "TLS handshake failed!"); + request_tcp_cleanup(&request->tcp); + return false; + } + + return true; +} + +void +request_tls_cleanup(struct tls_request *request) +{ + if (request->tls_id) + scsi_tls_close(request->tls_id); + + request_tcp_cleanup(&request->tcp); +} + +bool +request_tls_shuffle(struct tls_request *request, struct browser *browser, + request_tls_reader reader, void *reader_cookie, + request_tls_writer writer, void *writer_cookie) +{ + size_t len; + short status, cipherspace, plainspace, tls_error; + bool final; + + status = scsi_tls_status(request->tls_id, &cipherspace, &plainspace, + &tls_error); + + final = false; + while (status != 0) { + if (status & 0x1) { + /* closed */ + if (final) { + if (tls_error != 0) + browser_statusf(browser, + "Error: TLS handshake failed: %d (TLS status 0x%x)", + tls_error, status); + return false; + } + final = true; + } + + if ((status & 0x10) || final) { + /* tls has plaintext data for us */ + if (!request_tls_read_plaintext(request, browser, reader, + reader_cookie)) + return false; + status &= ~0x10; + } + + if (status & 0x2) { + /* tls has ciphertext for tcp */ + if (!request_tls_read_tls_ciphertext(request, browser)) + return false; + status &= ~0x2; + } + + if (status & 0x8) { + /* tls can read plaintext from us */ + if (!request_tls_send_plaintext(request, browser, plainspace, + writer, writer_cookie)) + return false; + status &= ~0x8; + } + + if (status & 0x4) { + /* tls can read ciphertext from tcp */ + if (!request_tls_read_tcp_ciphertext(request, browser, + cipherspace)) + return false; + status &= ~0x4; + } + + if (final) + continue; + + if (status) { + browser_statusf(browser, "TLS status is 0x%x?", status); + return false; + } + } + + return true; +} + +static bool +request_tls_read_tls_ciphertext(struct tls_request *request, + struct browser *browser) +{ + size_t len; + unsigned char *buf; + + /* read ciphertext from TLS and send it out TCP */ + + if (request->tcp_done_reading) + return false; + + if (request_tcp_avail(&request->tcp, browser) == -1) { + request->tcp_done_reading = true; + return false; + } + + /* this will point buf to scsi's static buffer */ + len = scsi_tls_read(request->tls_id, &buf, 0, true); + if (len == 0 || buf == NULL) { + browser_statusf(browser, + "Error: No ciphertext read from TLS when expected to"); + return false; + } + + BROWSER_DEBUGF((browser, + "Read %lu bytes of TLS ciphertext, forwarding to TCP", len)); + + /* result ignored? */ + request_tcp_send(&request->tcp, browser, (char *)buf, len); + + return true; +} + +static bool +request_tls_read_tcp_ciphertext(struct tls_request *request, + struct browser *browser, short space) +{ + size_t len, n; + ssize_t slen; + short err; + + /* read ciphertext from TCP and send it to TLS */ + + if (request->tcp.input_len == sizeof(request->tcp.input) || + request->tcp_done_reading) + goto forward_ciphertext; + + slen = request_tcp_avail(&request->tcp, browser); + if (slen < 0) + request->tcp_done_reading = true; + if (slen <= 0) + goto forward_ciphertext; + + slen = MIN(slen, sizeof(request->tcp.input) - request->tcp.input_len); + if (!slen) { + browser_statusf(browser, + "Error: No buffer space available in TCP input?"); + goto forward_ciphertext; + } + + slen = request_tcp_read(&request->tcp, browser, + (char *)(request->tcp.input + request->tcp.input_len), slen); + if (slen < 0) + goto forward_ciphertext; + + request->tcp.input_len += slen; + BROWSER_DEBUGF((browser, + "Read %ld bytes of TCP ciphertext, forwarding to TLS", slen)); + +forward_ciphertext: + if (!request->tcp.input_len || !space) + return true; + + slen = MIN(request->tcp.input_len, space); + BROWSER_DEBUGF((page->browser, + "Forwarding %ld bytes of TCP ciphertext to TLS", slen)); + + len = scsi_tls_write(request->tls_id, request->tcp.input, slen, true); + if (len == 0) { + browser_statusf(browser, + "Error: Failed forwarding %ld bytes of ciphertext to TLS", slen); + return false; + } + + if (len == request->tcp.input_len) + request->tcp.input_len = 0; + else { + memmove(request->tcp.input, request->tcp.input + len, + request->tcp.input_len - len); + request->tcp.input_len -= len; + } + BROWSER_DEBUGF((browser, + "Wrote %ld bytes of TCP ciphertext to TLS, %ld left", len, + request->tcp.input_len)); + return true; +} + +static bool +request_tls_send_plaintext(struct tls_request *request, + struct browser *browser, size_t space, request_tls_writer writer, + void *cookie) +{ + size_t olen; + char *data; + size_t len; + + /* send any plaintext from us to TLS */ + + /* writer will set data and len to message content */ + len = space; + if (!writer(request, browser, cookie, false, &data, &len)) + return false; + if (len == 0 || data == NULL) + return true; + + if (request->tls_state == REQ_STATE_NEGOTIATING) { + browser_statusf(browser, "Negotiated, sending request..."); + request->tls_state = REQ_STATE_SENDING_REQUEST; + } else if (request->tls_state != REQ_STATE_SENDING_REQUEST) { + browser_statusf(browser, "In bogus state (%d) instead of " + "SENDING_REQUEST, disconnecting", request->tls_state); + return false; + } + + olen = MIN(len, space); + len = scsi_tls_write(request->tls_id, (unsigned char *)data, olen, + false); + if (!len) { + browser_statusf(browser, + "Error: Failed sending %ld bytes of plaintext to TLS", olen); + return false; + } + + /* inform that we wrote len bytes */ + writer(request, browser, cookie, true, NULL, &len); + + BROWSER_DEBUGF((page->browser, "Wrote %ld bytes of plaintext to TLS", + len)); + return true; +} + +static bool +request_tls_read_plaintext(struct tls_request *request, + struct browser *browser, request_tls_reader reader, void *cookie) +{ + size_t len; + unsigned char *buf; + + /* read as much plaintext from TLS as we can buffer */ + + /* this will point buf to scsi's static buffer */ + len = scsi_tls_read(request->tls_id, &buf, 0, false); + if (len == 0) + return true; + + if (!reader(request, browser, cookie, (char *)buf, len)) + return false; + + return true; +} --- request.h Wed Nov 6 17:55:41 2024 +++ request.h Wed Nov 6 22:02:37 2024 @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2021-2024 joshua stein <jcs@jcs.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef __REQUEST_H__ +#define __REQUEST_H__ + +#include <stdarg.h> + +#include "detritus.h" +/* TODO: don't pass browser for statusf but return specific error codes? */ +#include "browser.h" +#include "tcp.h" + +extern uint8_t tls_req_last_id; + +struct tls_init_request { + uint8_t flags[2]; +#define BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY (1 << 0) + uint8_t unix_time[4]; + char hostname[256]; +}; + +struct URI { +#define URI_MAX_PROTOCOL_LEN 20 + char *protocol; +#define URI_MAX_HOSTNAME_LEN 255 + char *hostname; +#define URI_MAX_PATH_LEN 512 + char *path; +#define URI_MAX_STR_LEN (URI_MAX_PROTOCOL_LEN + 3 + URI_MAX_HOSTNAME_LEN + \ + URI_MAX_PATH_LEN) + char *str; + unsigned short port; +}; + +struct tcp_request { + unsigned char buf[(4 * 1500) + 2048]; /* 4*MTU + input */ + unsigned char input[1500]; + unsigned long input_len; + + TCPiopb iopb; + StreamPtr stream; + wdsEntry wds[2]; + TCPStatusPB status_pb; +}; + +struct tls_request { + uint8_t tls_id; + short tls_state; + struct tcp_request tcp; + bool tcp_done_reading; +}; + +struct URI * parse_uri(char *uristr, char *restrict_protocol); +struct URI * build_relative_uri(struct URI *uri, char *relative, size_t len); + +/* tcp functions */ +bool request_tcp_connect(struct tcp_request *request, + struct browser *browser, char *hostname, unsigned short port); +void request_tcp_cleanup(struct tcp_request *request); +bool request_tcp_send(struct tcp_request *request, struct browser *browser, + char *data, size_t len); +ssize_t request_tcp_avail(struct tcp_request *request, + struct browser *browser); +ssize_t request_tcp_read(struct tcp_request *request, + struct browser *browser, char *buf, size_t len); + +/* tls functions */ +short scsi_find_tls(void); +uint8_t scsi_tls_init(struct tls_init_request *req); +bool scsi_tls_close(uint8_t tls_id); +short scsi_tls_status(uint8_t tls_id, short *cipherspace, + short *plainspace, short *error); +size_t scsi_tls_read(uint8_t tls_id, unsigned char **buf, size_t max_size, + bool cipher); +size_t scsi_tls_write(uint8_t tls_id, unsigned char *buf, size_t buf_size, + bool cipher); +void scsi_cleanup(void); +bool scsi_can_do_tls(void); + +bool request_tls_connect(struct tls_request *request, + struct browser *browser, char *hostname, unsigned short port, short flags); +void request_tls_cleanup(struct tls_request *request); + +/* + * writer is called to set buf and len, then it's written, then it's called + * again with wrote set and size set to the actual bytes written + */ +typedef bool (*request_tls_writer)(struct tls_request *request, + struct browser *browser, void *cookie, bool wrote, char **buf, + size_t *len); + +/* reader consumes len bytes from buf */ +typedef bool (*request_tls_reader)(struct tls_request *request, + struct browser *browser, void *cookie, char *buf, size_t len); + +bool request_tls_shuffle(struct tls_request *request, + struct browser *browser, request_tls_reader reader, void *reader_cookie, + request_tls_writer writer, void *writer_cookie); + +#endif \ No newline at end of file --- scsi.c Mon Nov 4 11:11:57 2024 +++ scsi.c Wed Nov 6 22:01:36 2024 @@ -17,6 +17,7 @@ #include <SCSI.h> #include <string.h> #include <stdio.h> + #include "detritus.h" struct scsi_inquiry { --- tcp.c Tue Oct 1 15:09:07 2024 +++ tcp.c Wed Nov 6 22:06:36 2024 @@ -18,6 +18,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> + #include "tcp.h" #define RCV_BUFFER_SIZE 1024 --- tcp.h Fri Aug 30 09:56:02 2024 +++ tcp.h Wed Nov 6 22:07:06 2024 @@ -1,12 +1,12 @@ /* * Copyright (c) 1990-1992 by the University of Illinois Board of Trustees */ - -#include "MacTCP.h" -#include "dnr.h" #ifndef __TCP_H__ #define __TCP_H__ + +#include "MacTCP.h" +#include "dnr.h" typedef struct {