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