AmendHub

Download:

jcs

/

detritus

/

amendments

/

12

gemini: Extract gemini protocol code from browser

This way we can plug in different protocols with the same text
rendering and UI.
 
I still need to figure out why the off-screen canvas drawing is not
working properly.

jcs made amendment 12 about 1 year ago
--- browser.c Tue Oct 22 17:37:20 2024 +++ browser.c Thu Oct 24 17:21:12 2024 @@ -18,19 +18,24 @@ #include <stdio.h> #include <string.h> -#include "gemino.h" -#include "browser.h" +#include "detritus.h" #include "focusable.h" #include "util.h" -#define PADDING 10 -#define BROWSER_FONT_SIZE 10 -#define BROWSER_FONT geneva +#define PADDING 6 +#define BROWSER_FONT geneva +#define BROWSER_FONT_SIZE 10 +#define BROWSER_STATUS_FONT geneva +#define BROWSER_STATUS_FONT_SIZE 9 static Rect zerorect = { 0, 0, 0, 0 }; static Pattern fill_pattern; -static BitMap shadow_cur_bits; +struct request_handler * request_handlers[] = { + &gemini_handler, +}; + +void browser_setup_shadow(struct browser *browser); bool browser_close(struct focusable *focusable); void browser_idle(struct focusable *focusable, EventRecord *event); void browser_update_menu(struct browser *browser); @@ -43,41 +48,24 @@ bool browser_handle_menu(struct focusable *focusable, short item); void browser_atexit(struct focusable *focusable); +void browser_go(struct browser *browser); +bool browser_load_url(struct browser *browser, char *uristr); +void browser_draw_status(struct browser *browser); void browser_cleanup(struct browser *browser); -void browser_connect(struct browser *browser); -void browser_shuffle_data(struct browser *browser); -void shuffle_read_tls_ciphertext(struct browser *browser); -void shuffle_read_tcp_ciphertext(struct browser *browser, short space); -void shuffle_tls_send_plaintext(struct browser *browser, short space); -void shuffle_tls_read_plaintext(struct browser *browser); -size_t browser_printf(struct browser *browser, const char *format, ...); -size_t browser_debugf(struct browser *browser, const char *format, ...); -void browser_parse_response(struct browser *browser); -bool browser_parse_header(struct browser *browser, char *str); -void browser_consume_response(struct browser *browser, size_t len); -void browser_click_link(struct browser *browser, - struct browser_link *link); void browser_idle(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; - size_t len; TEIdle(browser->uri_te); - if (!browser->req) - return; - - switch (browser->req->state) { - case REQ_STATE_DISCONNECTED: - break; - case REQ_STATE_IDLE: - break; - case REQ_STATE_CONNECTED: - case REQ_STATE_PARSING: - browser_shuffle_data(browser); - break; + if (browser->request && + !browser->handler->process_request(browser->request)) { + /* processing failed/finished */ + browser->handler->free_request(browser->request); + browser->handler = NULL; + browser->request = NULL; } } @@ -88,8 +76,7 @@ browser_init(void) struct browser *browser; struct focusable *focusable; Rect bounds, te_bounds, padding; - Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ - short n, width, height; + long width, height; browser = xmalloczero(sizeof(struct browser)); if (browser == NULL) @@ -99,21 +86,12 @@ browser_init(void) /* main window */ width = screenBits.bounds.right - screenBits.bounds.left - PADDING; - if (width > 540) - width = 540; + width = MIN(width, 500); height = screenBits.bounds.bottom - screenBits.bounds.top - PADDING - (GetMBarHeight() * 2); - if (height > 340) - height = 300; + height = MIN(height, 290); center_in_screen(width, height, true, &bounds); - browser->shadow.rowBytes = (((width - 1) / 16) + 1) * 2; - browser->shadow.baseAddr = - xmalloczero((long)browser->shadow.rowBytes * height); - if (browser->shadow.baseAddr == NULL) - panic("malloc(%ld) failed", - (long)(browser->shadow.rowBytes * height)); - snprintf(title, sizeof(title), "%s", PROGRAM_NAME); CtoPstr(title); browser->win = NewWindow(0L, &bounds, title, false, noGrowDocProc, @@ -143,17 +121,17 @@ browser_init(void) 1, 1, 1, pushButProc, 0L); /* output TV */ - bounds.left = PADDING; - bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING; + bounds.left = 0; + bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH + 1; bounds.top = bounds.bottom + PADDING; - bounds.bottom = browser->win->portRect.bottom - PADDING; - SetRect(&padding, 2, 2, 2, 2); + bounds.bottom = browser->win->portRect.bottom - SCROLLBAR_WIDTH + 1; + SetRect(&padding, 4, 4, 4, 4); browser->output_tv = TVNew(&bounds, &padding); if (browser->output_tv == NULL) panic("Out of memory allocating TV"); /* scrollbar for output text */ - bounds.right = browser->win->portRect.right - PADDING; + bounds.right = browser->win->portRect.right + 1; bounds.left = bounds.right - SCROLLBAR_WIDTH; bounds.bottom++; bounds.top--; @@ -163,8 +141,10 @@ browser_init(void) browser_update_menu(browser); TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller); - TESetText("gemini://geminiprotocol.net/", - strlen("gemini://geminiprotocol.net/"), browser->uri_te); + browser->status_rect.left = -1; + browser->status_rect.bottom = height + 1; + browser->status_rect.right = width - SCROLLBAR_WIDTH + 2; + browser->status_rect.top = height - SCROLLBAR_WIDTH + 1; focusable = xmalloczero(sizeof(struct focusable)); if (focusable == NULL) @@ -180,9 +160,88 @@ browser_init(void) focusable->atexit = browser_atexit; focusable_add(focusable); + DrawControls(browser->win); + browser_setup_shadow(browser); + + browser_statusf(browser, "Hello, cyberpals!"); + return browser; } +void +browser_setup_shadow(struct browser *browser) +{ + long width, height; + + width = browser->win->portRect.right - browser->win->portRect.left; + height = browser->win->portRect.bottom - browser->win->portRect.top; + + if (browser->shadow) { + ClosePort(browser->shadow); + if (browser->shadow->portBits.baseAddr) + xfree(&browser->shadow->portBits.baseAddr); + xfree(&browser->shadow); + } + + GetPort(&browser->shadow_old_port); + + browser->shadow = (GrafPtr)xmalloczero(sizeof(GrafPort)); + if (browser->shadow == NULL) + panic("Out of memory"); + OpenPort(browser->shadow); + browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2; + browser->shadow->portBits.baseAddr = + xmalloc((long)browser->shadow->portBits.rowBytes * height); + if (browser->shadow->portBits.baseAddr == NULL) + panic("Out of memory"); + browser->shadow->portBits.bounds.right = width; + browser->shadow->portBits.bounds.bottom = height; + browser->shadow->portBits.rowBytes = (((width - 1) / 16) + 1) * 2; + browser->shadow->portRect = browser->shadow->portBits.bounds; + + RectRgn(browser->shadow->clipRgn, &browser->shadow->portRect); + RectRgn(browser->shadow->visRgn, &browser->shadow->portRect); + + SetPort(browser->shadow_old_port); +} + +void +browser_use_shadow(struct browser *browser) +{ + if (++browser->shadow_refcnt != 1) + return; + + if (browser->shadow_refcnt == 0) + Debugger(); + + /* TODO: fix controls not drawing on the canvas */ + return; + + GetPort(&browser->shadow_old_port); + SetPort(browser->shadow); +} + +void +browser_reveal_shadow(struct browser *browser) +{ + if (browser->shadow_refcnt <= 0) + Debugger(); + + if (--browser->shadow_refcnt != 0) + return; + + /* TODO: fix controls not drawing on the canvas */ + return; + + SetPort(browser->shadow_old_port); + + CopyBits(&browser->shadow->portBits, + &browser->win->portBits, &browser->shadow->portRect, + &browser->win->portRect, srcCopy, NULL); + browser->shadow_old_port = NULL; + ValidRect(&browser->win->portRect); +} + bool browser_close(struct focusable *focusable) { @@ -206,27 +265,22 @@ browser_cleanup(struct browser *browser) { size_t n; - if (!browser->req) - return; - - if (browser->req->tcp_stream) - _TCPRelease(&browser->req->tcp_iopb, browser->req->tcp_stream, - NULL, NULL, false); - - if (browser->req->links) { - for (n = 0; n < browser->req->links_count; n++) { - if (browser->req->links[n].link) - xfree(&browser->req->links[n].link); - if (browser->req->links[n].title) - xfree(&browser->req->links[n].title); + if (browser->request) { + browser->handler->free_request(browser->request); + browser->handler = NULL; + browser->request = NULL; + } + + if (browser->links) { + for (n = 0; n < browser->links_count; n++) { + if (browser->links[n].link) + xfree(&browser->links[n].link); + if (browser->links[n].title) + xfree(&browser->links[n].title); } - xfree(&browser->req->links); + xfree(&browser->links); } - - scsi_tls_close(browser->req->tls_id); - - xfree(&browser->req); } void @@ -302,38 +356,17 @@ browser_update(struct focusable *focusable, EventRecor InsetRect(&r, -1, -1); FrameRect(&r); - UpdtControl(browser->win, browser->win->visRgn); - + DrawGrowIconOnly(browser->win); + DrawControls(browser->win); + browser_draw_status(browser); + browser_reveal_shadow(browser); browser_update_menu(browser); - break; } } void -browser_use_shadow(struct browser *browser) -{ - shadow_cur_bits = thePort->portBits; - - SetRect(&browser->shadow.bounds, 0, 0, - browser->win->portRect.right - browser->win->portRect.left, - browser->win->portRect.bottom - browser->win->portRect.top); - SetPortBits(&browser->shadow); - CopyBits(&browser->win->portBits, &browser->shadow, - &browser->shadow.bounds, &browser->shadow.bounds, srcCopy, nil); -} - -void -browser_reveal_shadow(struct browser *browser) -{ - SetPortBits(&shadow_cur_bits); - CopyBits(&browser->shadow, &browser->win->portBits, - &browser->shadow.bounds, &browser->shadow.bounds, srcCopy, nil); - ValidRect(&browser->win->portRect); -} - -void browser_mouse_down(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; @@ -364,16 +397,18 @@ browser_mouse_down(struct focusable *focusable, EventR TVClick(browser->output_tv, p, ((event->modifiers & shiftKey) != 0)); - if (browser->req && browser->req->links) { + if (browser->links) { off = TVGetOffset(browser->output_tv, p); - for (n = 0; n < browser->req->links_count; n++) { - link = &browser->req->links[n]; + for (n = 0; off != 0 && n < browser->links_count; n++) { + link = &browser->links[n]; if ((link->pos <= off) && (off < link->pos + link->len)) { - //if (event->modifiers & cmdKey) { - // browser_init(link->link); - // break; - //} - browser_click_link(browser, link); +#if 0 + if (event->modifiers & cmdKey) { + browser_init(link->link); + break; + } +#endif + browser_load_url(browser, link->link); break; } } @@ -386,7 +421,7 @@ browser_mouse_down(struct focusable *focusable, EventR switch (part = FindControl(p, browser->win, &control)) { case inButton: if (TrackControl(control, p, 0L) && control == browser->go_button) - browser_connect(browser); + browser_go(browser); break; case inUpButton: case inDownButton: @@ -421,7 +456,7 @@ browser_key_down(struct focusable *focusable, EventRec k = (event->message & charCodeMask); if (k == '\r') { - browser_connect(browser); + browser_go(browser); return; } @@ -451,434 +486,105 @@ browser_handle_menu(struct focusable *focusable, short } void -browser_connect(struct browser *browser) +browser_go(struct browser *browser) { - struct tls_init_request tls_req; - char ip_s[16]; - Rect r; TERec *te; - size_t len; - unsigned long time; - unsigned short ret; - ip_addr ip, local_ip; - tcp_port port, local_port; - short err, i, j, count; + short len; + char *uristr; - browser_cleanup(browser); + browser->handler = NULL; + browser->request = NULL; - browser->req = xmalloczero(sizeof(struct tcp_request)); - if (browser->req == NULL) - panic("Out of memory allocating request"); - - browser->req->state = REQ_STATE_DISCONNECTED; - browser->req->gem_state = GEM_STATE_HEADER; - HLock(browser->uri_te); te = *(browser->uri_te); HLock(te->hText); len = te->teLength; - if (len >= sizeof(browser->req->uri)) - len = sizeof(browser->req->uri) - 1; - memcpy(browser->req->uri, *(te->hText), len); - browser->req->uri[len] = '\0'; - HUnlock(te->hText); - HUnlock(browser->uri_te); - - /* TODO: support URIs with port? */ - browser->req->port = DEFAULT_GEMINI_PORT; - browser->req->uri_len = strlen(browser->req->uri); - - if (count = 0, sscanf(browser->req->uri, "gemini://%255[^/]/%*s%n", - &browser->req->hostname, &count) == 1 && count > 10) { - /* gemini://host/path */ - } - else if (count = 0, sscanf(browser->req->uri, "gemini://%255[^/]/%n", - &browser->req->hostname, &count) == 1 && count > 10) { - /* gemini://host/ */ - } - else if (count = 0, sscanf(browser->req->uri, "gemini://%255[^/]%n", - &browser->req->hostname, &count) == 1 && count > 10) { - /* gemini://host */ - browser->req->uri_len = strlcat(browser->req->uri, "/", - sizeof(browser->req->uri)); - } - else if (count = 0, sscanf(browser->req->uri, "%255[^/]/%*s%n", - &browser->req->hostname, &count) == 1 && count > 3) { - /* host/path */ - memmove(browser->req->uri + 9, browser->req->uri, - sizeof(browser->req->uri) - 9); - browser->req->uri[sizeof(browser->req->uri) - 1] = '\0'; - memcpy(browser->req->uri, "gemini://", 9); - browser->req->uri_len += 9; - } - else if (count = 0, sscanf(browser->req->uri, "%255[^/]%n", - &browser->req->hostname, &count) == 1 && count > 1) { - /* host */ - browser->req->uri_len = snprintf(browser->req->uri, - sizeof(browser->req->uri), "gemini://%s/", browser->req->hostname); - } - else { - warn("Invalid URI"); + uristr = xmalloc(len + 1); + if (uristr == NULL) { + warn("Out of memory"); return; } - - HLock(browser->uri_te); - r = (*(browser->uri_te))->viewRect; + memcpy(uristr, *(te->hText), len); + uristr[len] = '\0'; + HUnlock(te->hText); HUnlock(browser->uri_te); - TESetText(browser->req->uri, browser->req->uri_len, browser->uri_te); - TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); - TEUpdate(&r, browser->uri_te); - - browser->req->uri_len = strlen(browser->req->uri); - - memcpy(browser->req->message, browser->req->uri, browser->req->uri_len); - browser->req->message[browser->req->uri_len] = '\r'; - browser->req->message[browser->req->uri_len + 1] = '\n'; - browser->req->message_len = browser->req->uri_len + 2; - - if (browser->req->host_ip == 0) { - progress("Resolving \"%s\"...", browser->req->hostname); - - err = DNSResolveName(browser->req->hostname, - &browser->req->host_ip, NULL); - if (err) { - progress(NULL); - browser_printf(browser, "[Failed resolving: %d]\r", err); - return; - } - } - - long2ip(browser->req->host_ip, (char *)&ip_s); - progress("Connecting to %s port %d...", ip_s, browser->req->port); - - err = _TCPCreate(&browser->req->tcp_iopb, &browser->req->tcp_stream, - (Ptr)browser->req->tcp_buf, sizeof(browser->req->tcp_buf), NULL, NULL, - NULL, false); - if (err) { - progress(NULL); - browser_printf(browser, "[TCPCreate failed: %d]\r", err); - goto error; - } - err = _TCPActiveOpen(&browser->req->tcp_iopb, browser->req->tcp_stream, - browser->req->host_ip, browser->req->port, &local_ip, &local_port, - NULL, NULL, false); - if (err) { - progress(NULL); - browser_printf(browser, "[Failed connecting to %s (%s) port %d: %d]\r", - browser->req->hostname, ip_s, browser->req->port, err); - goto error; - } - - err = _TCPStatus(&browser->req->tcp_iopb, browser->req->tcp_stream, - &browser->req->tcp_status_pb, NULL, NULL, false); - if (err) { - progress(NULL); - browser_printf(browser, "[Failed TCPStatus on connection to %s (%s) " - "port %d: %d]\r", browser->req->hostname, ip_s, port, err); - goto error; - } - - memset(&tls_req, 0, sizeof(tls_req)); - strlcpy(tls_req.hostname, browser->req->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; - - browser->req->tls_id = 1; - - /* sad */ - tls_req.flags[1] = BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY; - - progress("Performing TLS handshake..."); - scsi_tls_init(browser->req->tls_id, browser->scsi_buf, - sizeof(browser->scsi_buf), &tls_req); - - browser->req->state = REQ_STATE_CONNECTED; - -error: - return; + browser_load_url(browser, uristr); + xfree(&uristr); } -void -browser_shuffle_data(struct browser *browser) +bool +browser_load_url(struct browser *browser, char *uristr) { - size_t len; - unsigned short slen; - short err, status; - short cipherspace, plainspace, error; - - if (browser->req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { - progress(NULL); - browser->req->state = REQ_STATE_DISCONNECTED; - browser_debugf(browser, "[TCP IO Result %d, disconnecting]\r", - browser->req->tcp_iopb.ioResult); - return; - } - - status = scsi_tls_status(browser->req->tls_id, browser->scsi_buf, - sizeof(browser->scsi_buf), &cipherspace, &plainspace, &error); - - while (status != 0) { - if (status & 0x1) { - /* closed */ - progress(NULL); - if (error != 0) - browser_printf(browser, "[TLS handshake failed: %d, 0x%x]\r", - error, status); - browser->req->state = REQ_STATE_DISCONNECTED; - break; - } - - if (status & 0x2) { - /* tls has ciphertext for tcp */ - shuffle_read_tls_ciphertext(browser); - status &= ~0x2; - } - - if (status & 0x10) { - /* tls has plaintext data for us */ - shuffle_tls_read_plaintext(browser); - status &= ~0x10; - } - - if (status & 0x8) { - /* tls can read plaintext from us */ - shuffle_tls_send_plaintext(browser, plainspace); - status &= ~0x8; - } - - if (status & 0x4) { - /* tls can read ciphertext from tcp */ - shuffle_read_tcp_ciphertext(browser, cipherspace); - status &= ~0x4; - } - - if (status) { - progress(NULL); - browser_printf(browser, "[Status is 0x%x?]\r", status); - return; - } - } -} - -void -shuffle_read_tls_ciphertext(struct browser *browser) -{ - size_t len; - short err; + struct request_handler *handler; + struct URI *uri; + Rect r; + TERec *te; + short n; - /* read ciphertext from TLS and send it out TCP */ - len = scsi_tls_read(browser->req->tls_id, browser->scsi_buf, - sizeof(browser->scsi_buf), true); - if (len == 0) { - browser_printf(browser, "[No ciphertext read from TLS]\r"); - return; - } + browser->handler = NULL; + browser->request = NULL; - browser_debugf(browser, "[Read %lu bytes of ciphertext from TLS, " - "forwarding to TCP]\r", len); - - if (browser->req->tcp_done_reading) - return; + for (n = 0; n < sizeof(request_handlers) / sizeof(request_handlers[0]); + n++) { + handler = request_handlers[n]; - memset(&browser->req->tcp_wds, 0, sizeof(browser->req->tcp_wds)); - browser->req->tcp_wds[0].ptr = (Ptr)browser->scsi_buf; - browser->req->tcp_wds[0].length = len; - - err = _TCPSend(&browser->req->tcp_iopb, browser->req->tcp_stream, - browser->req->tcp_wds, NULL, NULL, false); - if (err) { - progress(NULL); - browser_printf(browser, "[TCPSend failed: %d]\r", err); - browser->req->state = REQ_STATE_DISCONNECTED; - return; - } -} - -void -shuffle_read_tcp_ciphertext(struct browser *browser, short space) -{ - size_t len; - short err, rcverr; - unsigned short slen; - - /* read ciphertext from TCP and send it to TLS */ - if (browser->req->tcp_input_len < sizeof(browser->req->tcp_input) && - !browser->req->tcp_done_reading) { - err = _TCPStatus(&browser->req->tcp_iopb, browser->req->tcp_stream, - &browser->req->tcp_status_pb, NULL, NULL, false); - if (browser->req->tcp_status_pb.amtUnreadData > 0) { - slen = MIN(browser->req->tcp_status_pb.amtUnreadData, - sizeof(browser->req->tcp_input) - browser->req->tcp_input_len); - if (slen) { - rcverr = _TCPRcv(&browser->req->tcp_iopb, - browser->req->tcp_stream, - (Ptr)(browser->req->tcp_input + browser->req->tcp_input_len), - &slen, NULL, NULL, false); - if (rcverr) { - progress(NULL); - browser_printf(browser, "[Failed TCPRcv: %d]\r", rcverr); - browser->req->state = REQ_STATE_DISCONNECTED; - return; - } - browser->req->tcp_input_len += slen; - browser_debugf(browser, "[Read %d bytes of ciphertext from " - "TCP, forwarding to TLS]\r", slen); - } else { - browser_printf(browser, "[No buffer space available in " - "tcp_input]\r"); - } + if ((uri = handler->parse_uri(uristr)) != NULL) { + browser->handler = handler; + break; } - - if (err) { - browser->req->tcp_done_reading = true; - browser_printf(browser, "[Bad TCPStatus: %d]\r", err); - } else if (browser->req->tcp_status_pb.connectionState != - ConnectionStateEstablished) { - browser_debugf(browser, "[TCP connection closed (state %d)]\r", - browser->req->tcp_status_pb.connectionState); - browser->req->tcp_done_reading = true; - } } - if (browser->req->tcp_input_len && space) { - slen = browser->req->tcp_input_len; - if (slen > space) - slen = space; - browser_debugf(browser, "[Writing %d bytes of ciphertext to TLS]\r", - slen); - len = scsi_tls_write(browser->req->tls_id, browser->req->tcp_input, - slen, true); - if (len > 0) { - if (len == browser->req->tcp_input_len) - browser->req->tcp_input_len = 0; - else { - size_t n; - - memmove(browser->req->tcp_input, - browser->req->tcp_input + len, - browser->req->tcp_input_len - len); - - browser->req->tcp_input_len -= len; - browser_debugf(browser, "[Wrote %ld bytes of " - "ciphertext to TLS, %ld left]\r", len, - browser->req->tcp_input_len); - } - } else { - progress(NULL); - browser_printf(browser, "[Failed sending %d bytes of ciphertext " - "to TLS]\r", slen); - browser->req->state = REQ_STATE_DISCONNECTED; - return; - } + if (uri == NULL) { + warn("Could not parse URI"); + return false; } -} -void -shuffle_tls_send_plaintext(struct browser *browser, short space) -{ - size_t slen, len; - - /* send any plaintext from us to TLS */ - if (browser->req->message_len == 0) - return; - - slen = browser->req->message_len; - if (slen > space) - slen = space; + HLock(browser->uri_te); + r = (*(browser->uri_te))->viewRect; + HUnlock(browser->uri_te); + TESetText(uri->str, strlen(uri->str), browser->uri_te); + TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); + TEUpdate(&r, browser->uri_te); - len = scsi_tls_write(browser->req->tls_id, - (unsigned char *)browser->req->message, slen, false); - if (len) { - if (len == browser->req->message_len) { - browser->req->message_len = 0; - browser_debugf(browser, "[Sent %ld bytes of plaintext to TLS]\r", - len); - } else { - memmove(browser->req->message, browser->req->message + len, - browser->req->message_len - len); - browser->req->message_len -= len; - browser_debugf(browser, "[Wrote %ld bytes of plaintext to " - "TLS, %ld left]\r", len, browser->req->message_len); - } - } else { - progress(NULL); - browser_printf(browser, "[Failed sending %ld bytes of plaintext " - "to TLS]\r", slen); - browser->req->state = REQ_STATE_DISCONNECTED; - return; - } -} - -void -shuffle_tls_read_plaintext(struct browser *browser) -{ - size_t len; + browser->request = browser->handler->init_request(browser, uri); + if (browser->request) + return true; - if (browser->req->state == REQ_STATE_CONNECTED) { - browser_clear(browser); - progress(NULL); - browser->req->state = REQ_STATE_PARSING; - } - - /* read as much plaintext from TLS as we can buffer */ - len = sizeof(browser->req->response) - browser->req->response_len; - if (len > sizeof(browser->scsi_buf)) - len = sizeof(browser->scsi_buf); - if (len > 0) { - len = scsi_tls_read(browser->req->tls_id, browser->scsi_buf, len, - false); - if (len > 0) { - browser_debugf(browser, "[Read %ld bytes of plaintext from TLS]\r", len); - if (len > sizeof(browser->req->response) + - browser->req->response_len) - panic("response overflow!"); - memcpy(browser->req->response + browser->req->response_len, - browser->scsi_buf, len); - browser->req->response_len += len; - } - } - - if (browser->req->response_len) - browser_parse_response(browser); + return false; } size_t -browser_printf(struct browser *browser, const char *format, ...) +browser_statusf(struct browser *browser, const char *format, ...) { - static char fmt[1024]; va_list argptr; - size_t len; va_start(argptr, format); - len = vsnprintf(fmt, sizeof(fmt), format, argptr); - if (len > sizeof(fmt)) - len = sizeof(fmt); + browser->status_length = vsnprintf(browser->status_text, + sizeof(browser->status_text), format, argptr); + if (browser->status_length >= sizeof(browser->status_text)) + browser->status_length = sizeof(browser->status_text) - 1; va_end(argptr); - browser_print(browser, fmt, len); + browser_use_shadow(browser); + browser_draw_status(browser); + browser_reveal_shadow(browser); } -size_t -browser_debugf(struct browser *browser, const char *format, ...) +void +browser_draw_status(struct browser *browser) { -#if 0 - static char fmt[1024]; - va_list argptr; - size_t len; + EraseRect(&browser->status_rect); + FrameRect(&browser->status_rect); - va_start(argptr, format); - len = vsnprintf(fmt, sizeof(fmt), format, argptr); - if (len > sizeof(fmt)) - len = sizeof(fmt); - va_end(argptr); - - browser_print(browser, fmt, len); -#endif + if (!browser->status_length) + return; + + MoveTo(browser->status_rect.left + 5, browser->status_rect.bottom - 5); + TextFont(BROWSER_STATUS_FONT); + TextFace(0); + TextSize(BROWSER_STATUS_FONT_SIZE); + DrawText(browser->status_text, 0, browser->status_length); } size_t @@ -892,36 +598,36 @@ browser_print(struct browser *browser, const char *str style.size = BROWSER_FONT_SIZE; style.style = 0; - if (browser->req->style & STYLE_BOLD) + if (browser->style & STYLE_BOLD) style.style |= bold | condense; - if (browser->req->style & (STYLE_H1 | STYLE_H2 | STYLE_H3)) + if (browser->style & (STYLE_H1 | STYLE_H2 | STYLE_H3)) style.style |= bold; - if (browser->req->style & STYLE_ITALIC) + if (browser->style & STYLE_ITALIC) style.style |= italic; - if (browser->req->style & STYLE_LINK) + if (browser->style & STYLE_LINK) style.style |= underline; - if (browser->req->style & STYLE_H1) + if (browser->style & STYLE_H1) style.size += 8; - else if (browser->req->style & STYLE_H2) + else if (browser->style & STYLE_H2) style.size += 4; - else if (browser->req->style & STYLE_H3) + else if (browser->style & STYLE_H3) style.size += 2; - if (browser->req->style & STYLE_LINK) { - if (browser->req->links_count == browser->req->links_size) { - browser->req->links_size += BROWSER_LINKS_CHUNK_SIZE; - browser->req->links = xreallocarray(browser->req->links, - browser->req->links_size, sizeof(struct browser_link)); - if (browser->req->links == NULL) { + if (browser->style & STYLE_LINK) { + if (browser->links_count == browser->links_size) { + browser->links_size += BROWSER_LINKS_CHUNK_SIZE; + browser->links = xreallocarray(browser->links, + browser->links_size, sizeof(struct browser_link)); + if (browser->links == NULL) { warn("Out of memory allocating links"); return 0; } - memset(&browser->req->links[browser->req->links_count], 0, + memset(&browser->links[browser->links_count], 0, sizeof(struct browser_link) * BROWSER_LINKS_CHUNK_SIZE); } - link = &browser->req->links[browser->req->links_count++]; + link = &browser->links[browser->links_count++]; HLock(browser->output_tv); link->pos = (*(browser->output_tv))->text_length; @@ -983,11 +689,11 @@ browser_print(struct browser *browser, const char *str } else TVAppend(browser->output_tv, &style, (char *)str, len); - browser->req->style &= ~(STYLE_LINK); + browser->style &= ~(STYLE_LINK); } if (str[len - 1] == '\r' && - (browser->req->style & (STYLE_H1 | STYLE_H2 | STYLE_H3))) { + (browser->style & (STYLE_H1 | STYLE_H2 | STYLE_H3))) { /* print newlines in a small size */ TVAppend(browser->output_tv, &style, (char *)str, len - 1); style.font = BROWSER_FONT; @@ -1014,318 +720,4 @@ browser_clear(struct browser *browser) TVUpdateScrollbar(browser->output_tv, browser->output_tv_scroller); SetPort(win); -} - -void -browser_parse_response(struct browser *browser) -{ - size_t n, trail, skip, len; - char c; - -handle_state: - switch (browser->req->gem_state) { - case GEM_STATE_HEADER: { - short status; - - for (n = 0; n < browser->req->response_len; n++) { - c = browser->req->response[n]; - - if (!(c == '\n' && n && browser->req->response[n - 1] == '\r')) - continue; - - browser->req->response[n] = '\0'; - browser->req->response[n - 1] = '\0'; - - if (!browser_parse_header(browser, browser->req->response)) { - browser_debugf(browser, "[Header parsing failed, " - "disconnecting]\r"); - browser->req->state = REQ_STATE_DISCONNECTED; - return; - } - - if (strncmp(browser->req->mime_type, "text/gemini", 11) == 0) - browser->req->gem_state = GEM_STATE_GEMTEXT; - else - browser->req->gem_state = GEM_STATE_DOWNLOAD; - - browser_consume_response(browser, n + 1); - - /* avoid a round trip through idle handler */ - goto handle_state; - } - break; - } - case GEM_STATE_REDIRECT: - /* TODO */ - break; - case GEM_STATE_DOWNLOAD: - /* TODO */ - break; - case GEM_STATE_GEMTEXT: -restart_parse: - for (n = 0; n <= browser->req->response_len; n++) { - if (n == browser->req->response_len && n < 3) { - /* - * End of buffer with no newline, but not enough chars - * to determine what this line is, skip until we get more. - */ - break; - } - - if (!(n == browser->req->response_len || - browser->req->response[n] == '\n')) - continue; - - len = n + 1; - trail = 0; - skip = 0; - - if (n < browser->req->response_len && - browser->req->response[n] == '\n') { - browser->req->response[n] = '\r'; - if (n > 0 && browser->req->response[n - 1] == '\r') { - len--; - trail = 1; - } - } else if (browser->req->response_len < - sizeof(browser->req->response) && - !browser->req->tcp_done_reading) { - /* - * No newline found at the end of the buffer, but the - * buffer isn't full so wait for more data. - */ - break; - } - - /* - * If we didn't find a newline but we're at the end of the - * buffer, shift it down first to see if we can fit the whole - * line in the buffer at once */ - - if (browser->req->response[0] == '=' && - browser->req->response[1] == '>') { - /* link */ - browser->req->style = STYLE_LINK; - skip = 2; - len -= 2; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (browser->req->response[0] == '`' && - browser->req->response[1] == '`' && - browser->req->response[2] == '`') { - /* ``` toggle */ - browser->req->style = STYLE_PRE; - browser_consume_response(browser, len); - goto restart_parse; - } - - if (browser->req->response[0] == '#' && - browser->req->response[1] == '#' && - browser->req->response[2] == '#') { - /* ### h3 */ - browser->req->style = STYLE_H3; - skip = 3; - len -= 3; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (browser->req->response[0] == '#' && - browser->req->response[1] == '#') { - /* ## h2 */ - browser->req->style = STYLE_H2; - skip = 2; - len -= 2; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (browser->req->response[0] == '#') { - /* # h1 */ - browser->req->style = STYLE_H1; - skip = 1; - len--; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (browser->req->response[0] == '*') { - /* * list item */ - browser->req->style = STYLE_LIST; - skip = 1; - len--; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (browser->req->response[0] == '>') { - /* > quote text */ - browser->req->style = STYLE_QUOTE; - skip = 1; - len--; - if (browser->req->response[skip] == ' ') { - skip++; - len--; - } - browser_print(browser, browser->req->response + skip, len); - browser_consume_response(browser, skip + len + trail); - browser->req->style = STYLE_NONE; - goto restart_parse; - } - - if (n == browser->req->response_len) { - /* end of buffer with no start, probably a continuation */ - browser_print(browser, browser->req->response, n); - browser_consume_response(browser, n); - break; - } - - /* just plain text */ - browser_print(browser, browser->req->response, len - trail); - browser_consume_response(browser, len + trail); - goto restart_parse; - } - break; - } -} - -void -browser_consume_response(struct browser *browser, size_t len) -{ - if (len == browser->req->response_len) { - browser->req->response_len = 0; - memset(browser->req->response, 0, sizeof(browser->req->response)); - } else { - memmove(browser->req->response, browser->req->response + len, - sizeof(browser->req->response) - len); - browser->req->response_len -= len; - memset(browser->req->response + browser->req->response_len, 0, - sizeof(browser->req->response) - browser->req->response_len); - } -} - -bool -browser_parse_header(struct browser *browser, char *str) -{ - short status; - - browser_debugf(browser, "[Received header: %s]\r", str); - if (!(str[0] >= '0' && str[0] <= '9' && - str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) { - browser_printf(browser, "[Malformed response %s]\r", str); - return false; - } - - status = ((str[0] - '0') * 10) + (str[1] - '0'); - - if (status >= 10 && status <= 19) { - /* input, not supported */ - browser_printf(browser, "[Input not supported (%d)]\r", status); - return false; - } - if (status >= 20 && status <= 29) { - /* success */ - strlcpy(browser->req->mime_type, str + 3, - sizeof(browser->req->mime_type)); - return true; - } - if (status >= 30 && status <= 39) { - /* redirect */ - /* TODO */ - return false; - } - if (status >= 40 && status <= 49) { - /* temp fail */ - browser_printf(browser, "[Temporary server failure (%d)]\r", status); - return false; - } - if (status >= 50 && status <= 59) { - /* perm fail */ - browser_printf(browser, "[Permanent server failure (%d)]\r", status); - return false; - } - if (status >= 60 && status <= 69) { - /* auth, not supported */ - browser_printf(browser, "[Auth not supported (%d)]\r", status); - return false; - } - browser_printf(browser, "[Unsupported status %d]\r", status); - return false; -} - -void -browser_click_link(struct browser *browser, struct browser_link *link) -{ - static char new_uri[member_size(struct tcp_request, uri)]; - ssize_t n; - size_t len; - Rect r; - - if (strncmp(link->link, "gemini://", 9) == 0) - len = strlcpy(new_uri, link->link, sizeof(new_uri)); - else { - /* relative, remove any file from path */ - len = 0; - for (n = browser->req->uri_len - 1; n >= 0; n--) { - if (browser->req->uri[n] != '/') - continue; - - /* gemini://host/path -> gemini://host/ */ - len = n + 1; - memcpy(new_uri, browser->req->uri, len); - break; - } - if (len == 0) { - warn("failed making new uri from \"%s\" and \"%s\"", - browser->req->uri, link->link); - return; - } - - n = strlen(link->link); - memcpy(new_uri + len, link->link, n); - new_uri[len + n] = '\0'; - len = len + n; - } - - TESetText(new_uri, len, browser->uri_te); - TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); - - HLock(browser->uri_te); - r = (*(browser->uri_te))->viewRect; - HUnlock(browser->uri_te); - TEUpdate(&r, browser->uri_te); - - browser_connect(browser); -} \ No newline at end of file +} --- browser.h Tue Oct 22 15:29:10 2024 +++ browser.h Thu Oct 24 20:08:10 2024 @@ -22,20 +22,6 @@ #include "tcp.h" #include "util.h" -enum { - REQ_STATE_IDLE, - REQ_STATE_CONNECTED, - REQ_STATE_PARSING, - REQ_STATE_DISCONNECTED -}; - -enum { - GEM_STATE_HEADER, - GEM_STATE_GEMTEXT, - GEM_STATE_REDIRECT, - GEM_STATE_DOWNLOAD -}; - #define STYLE_NONE 0 #define STYLE_BOLD (1UL << 0) #define STYLE_ITALIC (1UL << 1) @@ -54,57 +40,34 @@ struct browser_link { unsigned short len; }; -struct tcp_request { - short tls_id; +struct browser { + WindowPtr win; + GrafPtr shadow; + GrafPtr shadow_old_port; + short shadow_refcnt; + TEHandle uri_te; + ControlHandle go_button; + TVHandle output_tv; + ControlHandle output_tv_scroller; + Rect status_rect; + unsigned short status_length; + char status_text[512]; - unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ - unsigned char tcp_input[2048]; - size_t tcp_input_len; - short state; + struct request_handler *handler; + void *request; - char hostname[256]; - ip_addr host_ip; - unsigned short port; - - TCPiopb tcp_iopb; - StreamPtr tcp_stream; - wdsEntry tcp_wds[2]; - TCPStatusPB tcp_status_pb; - bool tcp_done_reading; - - char uri[1024 + 1]; - size_t uri_len; - - char message[1024 + 3]; - size_t message_len; - - short gem_state; - char response[1024]; - size_t response_len; - - char mime_type[64]; unsigned long style; - size_t links_count; size_t links_size; struct browser_link *links; #define BROWSER_LINKS_CHUNK_SIZE 32 }; -struct browser { - WindowPtr win; - BitMap shadow; - TEHandle uri_te; - ControlHandle go_button; - TVHandle output_tv; - ControlHandle output_tv_scroller; - struct tcp_request *req; - - unsigned char scsi_buf[2048]; -}; - struct browser * browser_init(void); +size_t browser_statusf(struct browser *browser, const char *format, ...); size_t browser_print(struct browser *browser, const char *str, size_t len); +//#define BROWSER_DEBUGF(x) browser_statusf +#define BROWSER_DEBUGF(x) void browser_clear(struct browser *browser); #endif \ No newline at end of file --- gemini.c Thu Oct 24 20:19:40 2024 +++ gemini.c Thu Oct 24 20:19:40 2024 @@ -0,0 +1,769 @@ +/* + * 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" + +struct request_handler gemini_handler = { + gemini_parse_uri, + gemini_init_request, + gemini_process_request, + gemini_free, +}; + +bool shuffle_read_tls_ciphertext(struct gemini_request *req); +bool shuffle_read_tcp_ciphertext(struct gemini_request *req, short space); +bool shuffle_tls_send_plaintext(struct gemini_request *req, short space); +bool shuffle_tls_read_plaintext(struct gemini_request *req); +bool parse_response(struct gemini_request *req); +bool parse_header(struct gemini_request *req, char *str); +void consume_response(struct gemini_request *req, size_t len); + +struct URI * +gemini_parse_uri(char *uristr) +{ + struct URI *uri; + short count; + + uri = xmalloczero(sizeof(struct URI)); + if (uri == NULL) + return NULL; + + uri->port = GEMINI_PORT; + snprintf(uri->protocol, sizeof(uri->protocol), "gemini"); + + /* gemini://host/path */ + if (count = 0, sscanf(uristr, + "gemini://%" STR(URI_HOSTNAME_LEN) "[^/]/%" STR(URI_PATH_LEN) "s%n", + &uri->hostname, &uri->path + 1, &count) == 2 && count > 10) + goto parse_ok; + + /* gemini://host/ */ + if (count = 0, sscanf(uristr, + "gemini://%" STR(URI_HOSTNAME_LEN) "[^/]/%n", + &uri->hostname, &count) == 1 && count > 10) { + snprintf(uri->path, sizeof(uri->path), "/"); + goto parse_ok; + } + + /* gemini://host */ + if (count = 0, sscanf(uristr, + "gemini://%" STR(URI_HOSTNAME_LEN) "[^/]%n", + &uri->hostname, &count) == 1 && count > 10) { + snprintf(uri->path, sizeof(uri->path), "/"); + goto parse_ok; + } + + /* failed */ + xfree(&uri); + return NULL; + +parse_ok: + snprintf(uri->str, sizeof(uri->str), "%s://%s%s", + uri->protocol, uri->hostname, uri->path); + return uri; +} + +void * +gemini_init_request(struct browser *browser, struct URI *uri) +{ + 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; + + req = xmalloczero(sizeof(struct gemini_request)); + if (req == NULL) { + warn("Out of memory"); + goto fail; + } + + req->browser = browser; + req->uri = uri; + + browser_statusf(browser, "Resolving \"%s\"...", req->uri->hostname); + + err = DNSResolveName(req->uri->hostname, &ip, NULL); + if (err) { + browser_statusf(browser, "Error: Failed resolving: %d", err); + goto fail; + } + + long2ip(ip, (char *)&ip_s); + browser_statusf(browser, "Connecting to %s port %d...", ip_s, + req->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(browser, "Error: TCPCreate failed: %d", err); + goto fail; + } + + err = _TCPActiveOpen(&req->tcp_iopb, req->tcp_stream, ip, + req->uri->port, &local_ip, &local_port, NULL, NULL, false); + if (err) { + browser_statusf(browser, + "Error: Failed connecting to %s (%s) port %d: %d", + req->uri->hostname, ip_s, req->uri->port, err); + goto fail; + } + + err = _TCPStatus(&req->tcp_iopb, req->tcp_stream, &req->tcp_status_pb, + NULL, NULL, false); + if (err) { + browser_statusf(browser, + "Error: Failed TCPStatus on connection to %s (%s) port %d: %d", + req->uri->hostname, ip_s, req->uri->port, err); + goto fail; + } + + req->message_len = snprintf(req->message, sizeof(req->message), + "%s\r\n", req->uri->str); + + browser_statusf(browser, "Connected to %s", req->uri->hostname); + + memset(&tls_req, 0, sizeof(tls_req)); + strlcpy(tls_req.hostname, req->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; + + browser_statusf(browser, "Performing TLS handshake..."); + req->tls_id = scsi_tls_init(&tls_req); + req->state = REQ_STATE_NEGOTIATING; + + if (req->tls_id == 0) { + browser_statusf(browser, "TLS handshake failed!"); + goto fail; + } + + return req; + +fail: + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } + + if (req) + xfree(&req); + + return NULL; +} + +void +gemini_free(void *cookie) +{ + struct gemini_request *req = (struct gemini_request *)cookie; + + if (req->tcp_stream) { + _TCPAbort(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + _TCPRelease(&req->tcp_iopb, req->tcp_stream, NULL, NULL, false); + } + + if (req->tls_id) + scsi_tls_close(req->tls_id); + + xfree(&req->uri); + xfree(&req); +} + +bool +gemini_process_request(void *cookie) +{ + struct gemini_request *req = (struct gemini_request *)cookie; + size_t len; + unsigned short slen; + short err, status; + short cipherspace, plainspace, error; + + if (req->tcp_iopb.ioResult > 0 || CommandPeriodPressed()) { + BROWSER_DEBUGF((req->browser, "TCP I/O Result %d, disconnecting", + req->tcp_iopb.ioResult)); + return false; + } + + status = scsi_tls_status(req->tls_id, &cipherspace, &plainspace, + &error); + + while (status != 0) { + if ((status & 0x1) && !req->response_len) { + /* closed */ + if (error == 0) + browser_statusf(req->browser, + "Finished reading %ld bytes", req->total_response_len); + else + browser_statusf(req->browser, + "Error: TLS handshake failed: %d (TLS 0x%x)", + error, status); + return false; + } + + if (status & 0x2) { + /* tls has ciphertext for tcp */ + if (!shuffle_read_tls_ciphertext(req)) + return false; + status &= ~0x2; + } + + if ((status & 0x10) || req->response_len) { + /* tls has plaintext data for us */ + if (!shuffle_tls_read_plaintext(req)) + return false; + status &= ~0x10; + } + + if (status & 0x8) { + /* tls can read plaintext from us */ + if (!shuffle_tls_send_plaintext(req, plainspace)) + return false; + status &= ~0x8; + } + + if (status & 0x4) { + /* tls can read ciphertext from tcp */ + if (!shuffle_read_tcp_ciphertext(req, cipherspace)) + return false; + status &= ~0x4; + } + + if (status) { + browser_statusf(req->browser, "Status is 0x%x?", status); + return false; + } + } + + return true; +} + +bool +shuffle_read_tls_ciphertext(struct gemini_request *req) +{ + size_t len; + short err; + unsigned char *buf; + + /* read ciphertext from TLS and send it out TCP */ + + len = scsi_tls_read(req->tls_id, &buf, 0, true); + if (len == 0 || buf == NULL) { + browser_statusf(req->browser, + "Error: No ciphertext read from TLS when expected to"); + return false; + } + + BROWSER_DEBUGF((req->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(req->browser, "Error: TCPSend failed: %d", err); + return false; + } + + return true; +} + +bool +shuffle_read_tcp_ciphertext(struct gemini_request *req, short space) +{ + size_t len, n; + short err; + unsigned short slen; + + /* read ciphertext from TCP and send it to TLS */ + + 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(req->browser, "Error: Bad TCPStatus: %d", err); + goto forward_ciphertext; + } + if (req->tcp_status_pb.connectionState != ConnectionStateEstablished) { + BROWSER_DEBUGF((req->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(req->browser, + "Error: No buffer space available in tcp_input?"); + goto forward_ciphertext; + } + + err = _TCPRcv(&req->tcp_iopb, req->tcp_stream, + (Ptr)(req->tcp_input + req->tcp_input_len), &slen, NULL, NULL, + false); + if (err) { + browser_statusf(req->browser, "Error: Failed TCPRcv: %d", err); + goto forward_ciphertext; + } + req->tcp_input_len += slen; + BROWSER_DEBUGF((req->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((req->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(req->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((req->browser, + "Wrote %ld bytes of TCP ciphertext to TLS, %ld left", len, + req->tcp_input_len)); + return true; +} + +bool +shuffle_tls_send_plaintext(struct gemini_request *req, short space) +{ + size_t slen, len; + + /* send any plaintext from us to TLS */ + + if (req->message_len == 0) + return true; + + if (req->state == REQ_STATE_NEGOTIATING) { + browser_statusf(req->browser, "Sending request..."); + req->state = REQ_STATE_SENDING_REQUEST; + } else if (req->state != REQ_STATE_SENDING_REQUEST) { + browser_statusf(req->browser, "In bogus state (%d) instead of " + "SENDING_REQUEST, disconnecting", req->state); + 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(req->browser, + "Error: Failed sending %ld bytes of plaintext to TLS", slen); + return false; + } + + 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((req->browser, "Wrote %ld bytes of plaintext to TLS, " + "%ld left", len, req->message_len)); + return true; +} + +bool +shuffle_tls_read_plaintext(struct gemini_request *req) +{ + size_t len; + unsigned char *buf; + + /* read as much plaintext from TLS as we can buffer */ + + if (req->state == REQ_STATE_NEGOTIATING) { + browser_clear(req->browser); + req->state = REQ_STATE_PARSING_RESPONSE; + req->gem_state = GEM_STATE_HEADER; + } + + len = sizeof(req->response) - req->response_len; + if (len > 0) { + len = scsi_tls_read(req->tls_id, &buf, len, false); + if (len > 0) { + if (len > sizeof(req->response) + req->response_len) + panic("response overflow!"); + memcpy(req->response + req->response_len, buf, len); + req->response_len += len; + req->total_response_len += len; + + browser_statusf(req->browser, + "Read %ld bytes", req->total_response_len); + } + } + + if (req->response_len) + return parse_response(req); + + return true; +} + +bool +parse_response(struct gemini_request *req) +{ + size_t n, trail, skip, len; + char c; + +handle_state: + switch (req->gem_state) { + case GEM_STATE_HEADER: { + short status; + + for (n = 0; n < req->response_len; n++) { + c = req->response[n]; + + if (!(c == '\n' && n && req->response[n - 1] == '\r')) + continue; + + req->response[n] = '\0'; + req->response[n - 1] = '\0'; + + if (!parse_header(req, req->response)) { + BROWSER_DEBUGF((req->browser, + "Header parsing failed, disconnecting")); + return false; + } + + if (strncmp(req->mime_type, "text/gemini", 11) == 0) + req->gem_state = GEM_STATE_GEMTEXT; + else + req->gem_state = GEM_STATE_DOWNLOAD; + + consume_response(req, n + 1); + + /* avoid a round trip through idle handler */ + goto handle_state; + } + break; + } + case GEM_STATE_REDIRECT: + /* TODO */ + break; + case GEM_STATE_DOWNLOAD: + /* TODO */ + break; + case GEM_STATE_GEMTEXT: +restart_parse: + for (n = 0; n <= req->response_len; n++) { + if (n == req->response_len && n < 3) { + /* + * End of buffer with no newline, but not enough chars + * to determine what this line is, skip until we get more. + */ + break; + } + + if (!(n == req->response_len || req->response[n] == '\n')) + continue; + + len = n + 1; + trail = 0; + skip = 0; + + if (n < req->response_len && req->response[n] == '\n') { + req->response[n] = '\r'; + if (n > 0 && req->response[n - 1] == '\r') { + len--; + trail = 1; + } + } else if (req->response_len < sizeof(req->response) && + !req->tcp_done_reading) { + /* + * No newline found at the end of the buffer, but the + * buffer isn't full so wait for more data. + */ + break; + } + + /* + * If we didn't find a newline but we're at the end of the + * buffer, shift it down first to see if we can fit the whole + * line in the buffer at once */ + + if (req->response[0] == '=' && req->response[1] == '>') { + /* link */ + skip = 2; + len -= 2; + if (req->response[skip] == ' ') { + skip++; + len--; + } + req->browser->style = STYLE_LINK; + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (req->response[0] == '`' && req->response[1] == '`' && + req->response[2] == '`') { + /* ``` toggle */ + req->browser->style = STYLE_PRE; + consume_response(req, len); + goto restart_parse; + } + + if (req->response[0] == '#' && req->response[1] == '#' && + req->response[2] == '#') { + /* ### h3 */ + req->browser->style = STYLE_H3; + skip = 3; + len -= 3; + if (req->response[skip] == ' ') { + skip++; + len--; + } + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (req->response[0] == '#' && req->response[1] == '#') { + /* ## h2 */ + req->browser->style = STYLE_H2; + skip = 2; + len -= 2; + if (req->response[skip] == ' ') { + skip++; + len--; + } + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (req->response[0] == '#') { + /* # h1 */ + req->browser->style = STYLE_H1; + skip = 1; + len--; + if (req->response[skip] == ' ') { + skip++; + len--; + } + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (req->response[0] == '*') { + /* * list item */ + req->browser->style = STYLE_LIST; + skip = 1; + len--; + if (req->response[skip] == ' ') { + skip++; + len--; + } + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (req->response[0] == '>') { + /* > quote text */ + req->browser->style = STYLE_QUOTE; + skip = 1; + len--; + if (req->response[skip] == ' ') { + skip++; + len--; + } + browser_print(req->browser, req->response + skip, len); + consume_response(req, skip + len + trail); + req->browser->style = STYLE_NONE; + goto restart_parse; + } + + if (n == req->response_len) { + /* end of buffer with no start, probably a continuation */ + browser_print(req->browser, req->response, n); + consume_response(req, n); + break; + } + + /* just plain text */ + browser_print(req->browser, req->response, len - trail); + consume_response(req, len + trail); + goto restart_parse; + } + break; + } + + return true; +} + +void +consume_response(struct gemini_request *req, size_t len) +{ + if (len == req->response_len) { + req->response_len = 0; + memset(req->response, 0, sizeof(req->response)); + } else { + memmove(req->response, req->response + len, + sizeof(req->response) - len); + req->response_len -= len; + memset(req->response + req->response_len, 0, + sizeof(req->response) - req->response_len); + } +} + +bool +parse_header(struct gemini_request *req, char *str) +{ + short status; + + BROWSER_DEBUGF((req->browser, "Received header: %s", str)); + if (!(str[0] >= '0' && str[0] <= '9' && + str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) { + browser_statusf(req->browser, "Error: Malformed response %s", str); + return false; + } + + status = ((str[0] - '0') * 10) + (str[1] - '0'); + + if (status >= 10 && status <= 19) { + /* input, not supported */ + browser_statusf(req->browser, + "Error: Input not supported (%d)", status); + return false; + } + if (status >= 20 && status <= 29) { + /* success */ + strlcpy(req->mime_type, str + 3, sizeof(req->mime_type)); + return true; + } + if (status >= 30 && status <= 39) { + /* redirect */ + /* TODO */ + return false; + } + if (status >= 40 && status <= 49) { + /* temp fail */ + browser_statusf(req->browser, "Error: Server reported temporary " + "failure (%d)", status); + return false; + } + if (status >= 50 && status <= 59) { + /* perm fail */ + browser_statusf(req->browser, "Error: Server reported failure (%d)", + status); + return false; + } + if (status >= 60 && status <= 69) { + /* auth, not supported */ + browser_statusf(req->browser, "Error: Auth not supported (%d)", + status); + return false; + } + browser_statusf(req->browser, "Error: Unsupported status %d", status); + return false; +} + +#if 0 +void +browser_click_link(struct browser *browser, struct browser_link *link) +{ + static char new_uri[member_size(struct tcp_request, uri)]; + ssize_t n; + size_t len; + Rect r; + + if (strncmp(link->link, "gemini://", 9) == 0) + len = strlcpy(new_uri, link->link, sizeof(new_uri)); + else { + /* relative, remove any file from path */ + len = 0; + for (n = browser->req->uri_len - 1; n >= 0; n--) { + if (browser->req->uri[n] != '/') + continue; + + /* gemini://host/path -> gemini://host/ */ + len = n + 1; + memcpy(new_uri, browser->req->uri, len); + break; + } + if (len == 0) { + warn("failed making new uri from \"%s\" and \"%s\"", + browser->req->uri, link->link); + return; + } + + n = strlen(link->link); + memcpy(new_uri + len, link->link, n); + new_uri[len + n] = '\0'; + len = len + n; + } + + TESetText(new_uri, len, browser->uri_te); + TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te); + + HLock(browser->uri_te); + r = (*(browser->uri_te))->viewRect; + HUnlock(browser->uri_te); + TEUpdate(&r, browser->uri_te); + +bool +browser_load_url(struct browser *browser, char *uristr) + browser_connect(browser); +} + +#endif \ No newline at end of file --- gemini.h Thu Oct 24 16:48:34 2024 +++ gemini.h Thu Oct 24 16:48:34 2024 @@ -0,0 +1,76 @@ +/* + * 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" + +#ifndef __GEMINI_H__ +#define __GEMINI_H__ + +#define GEMINI_PORT 1965 + +extern struct request_handler gemini_handler; + +enum { + REQ_STATE_NEGOTIATING = 1, + REQ_STATE_SENDING_REQUEST, + REQ_STATE_PARSING_RESPONSE +}; + +enum { + GEM_STATE_HEADER, + GEM_STATE_GEMTEXT, + GEM_STATE_REDIRECT, + GEM_STATE_DOWNLOAD +}; + +struct gemini_request { + struct browser *browser; + struct URI *uri; + + uint8_t tls_id; + unsigned char tcp_buf[(4 * 1500) + 2048]; /* 4*MTU + tcp_input */ + unsigned char tcp_input[2048]; + size_t tcp_input_len; + short state; + + TCPiopb tcp_iopb; + StreamPtr tcp_stream; + wdsEntry tcp_wds[2]; + TCPStatusPB tcp_status_pb; + bool tcp_done_reading; + + char message[1024 + 3]; + size_t message_len; + + short gem_state; + char response[1024]; + size_t response_len; + size_t total_response_len; + + char mime_type[64]; + unsigned long style; +}; + +struct URI * gemini_parse_uri(char *uristr); +void * gemini_init_request(struct browser *browser, struct URI *uri); +bool gemini_process_request(void *cookie); +void gemini_free(void *cookie); + +#endif \ No newline at end of file