jcs
/detritus
/amendments
/7
browser: Rename from client, add shadow buffer
This name was left over from TCP Client and browser makes more sense
jcs made amendment 7 about 1 year ago
--- browser.c Tue Oct 1 20:15:34 2024
+++ browser.c Tue Oct 1 20:15:34 2024
@@ -0,0 +1,1419 @@
+/*
+ * 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 "gemino.h"
+#include "browser.h"
+#include "focusable.h"
+#include "util.h"
+
+#define PADDING 10
+#define BROWSER_FONT_SIZE 10
+#define BROWSER_FONT geneva
+
+static Rect zerorect = { 0, 0, 0, 0 };
+static Pattern fill_pattern;
+static BitMap shadow_cur_bits;
+
+bool browser_close(struct focusable *focusable);
+void browser_idle(struct focusable *focusable, EventRecord *event);
+void browser_update_menu(struct browser *browser);
+void browser_update(struct focusable *focusable, EventRecord *event);
+void browser_use_shadow(struct browser *browser);
+void browser_reveal_shadow(struct browser *browser);
+void browser_key_down(struct focusable *focusable, EventRecord *event);
+void browser_mouse_down(struct focusable *focusable, EventRecord *event);
+bool browser_handle_menu(struct focusable *focusable, short menu,
+ short item);
+void browser_atexit(struct focusable *focusable);
+bool browser_avoid_te_overflow(struct browser *browser, TEHandle te,
+ short line_height);
+
+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->active_te);
+
+ if (!browser->req)
+ return;
+
+ switch (browser->req->state) {
+ case REQ_STATE_IDLE:
+ break;
+ case REQ_STATE_CONNECTED:
+ case REQ_STATE_PARSING:
+ browser_shuffle_data(browser);
+ break;
+ }
+}
+
+struct browser *
+browser_init(void)
+{
+ char title[64];
+ struct browser *browser;
+ struct focusable *focusable;
+ Rect bounds = { 0 }, te_bounds = { 0 };
+ Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
+ Point cell_size = { 0, 0 };
+ Cell cell = { 0 };
+ short n, width, height;
+
+ browser = xmalloczero(sizeof(struct browser));
+ if (browser == NULL)
+ panic("Out of memory allocating browser");
+
+ GetIndPattern(&fill_pattern, sysPatListID, 10);
+
+ /* main window */
+ width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
+ if (width > 540)
+ width = 540;
+ height = screenBits.bounds.bottom - screenBits.bounds.top -
+ PADDING - (GetMBarHeight() * 2);
+ if (height > 340)
+ height = 300;
+ 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,
+ (WindowPtr)-1L, true, 0);
+ if (!browser->win)
+ panic("Can't create window");
+ SetPort(browser->win);
+
+ /* uri TE */
+ bounds.top = PADDING;
+ bounds.left = PADDING;
+ bounds.right = browser->win->portRect.right - PADDING - 50;
+ bounds.bottom = bounds.top + 16;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ TextFont(geneva);
+ TextSize(10);
+ browser->uri_te = TENew(&te_bounds, &bounds);
+ if (browser->uri_te == NULL)
+ panic("Out of memory allocating TE");
+ TEAutoView(false, browser->uri_te);
+ TEActivate(browser->uri_te);
+ browser->active_te = browser->uri_te;
+
+ bounds.left = bounds.right + PADDING;
+ bounds.right = browser->win->portRect.right - PADDING;
+ browser->go_button = NewControl(browser->win, &bounds, "\pGo", true,
+ 1, 1, 1, pushButProc, 0L);
+
+ /* output TE */
+ bounds.left = PADDING;
+ bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
+ bounds.top = bounds.bottom + PADDING;
+ bounds.bottom = browser->win->portRect.bottom - PADDING;
+ te_bounds = bounds;
+ InsetRect(&te_bounds, 2, 2);
+ browser->output_te = TEStylNew(&te_bounds, &bounds);
+ if (browser->output_te == NULL)
+ panic("Out of memory allocating TE");
+ TEAutoView(false, browser->output_te);
+ (*(browser->output_te))->caretHook = NullCaretHook;
+
+ /* scrollbar for output text */
+ bounds.right = browser->win->portRect.right - PADDING;
+ bounds.left = bounds.right - SCROLLBAR_WIDTH;
+ bounds.bottom++;
+ bounds.top--;
+ browser->output_te_scroller = NewControl(browser->win, &bounds, "\p",
+ true, 1, 1, 1, scrollBarProc, 0L);
+
+ browser_update_menu(browser);
+ UpdateScrollbarForTE(browser->win, browser->output_te_scroller,
+ browser->output_te, true);
+
+ TESetText("geminiprotocol.net", strlen("geminiprotocol.net"),
+ browser->uri_te);
+
+ focusable = xmalloczero(sizeof(struct focusable));
+ if (focusable == NULL)
+ panic("Out of memory!");
+ focusable->cookie = browser;
+ focusable->win = browser->win;
+ focusable->idle = browser_idle;
+ focusable->update = browser_update;
+ focusable->mouse_down = browser_mouse_down;
+ focusable->key_down = browser_key_down;
+ focusable->menu = browser_handle_menu;
+ focusable->close = browser_close;
+ focusable->atexit = browser_atexit;
+ focusable_add(focusable);
+
+ return browser;
+}
+
+bool
+browser_close(struct focusable *focusable)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+
+ TEDispose(browser->uri_te);
+ TEDispose(browser->output_te);
+ DisposeWindow(browser->win);
+
+ browser_cleanup(browser);
+ xfree(&browser);
+ focusable->cookie = NULL;
+
+ scsi_cleanup();
+
+ return true;
+}
+
+void
+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);
+ }
+
+ xfree(&browser->req->links);
+ }
+
+ scsi_tls_close(browser->req->tls_id);
+
+ xfree(&browser->req);
+}
+
+void
+browser_atexit(struct focusable *focusable)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+
+ if (browser)
+ browser_cleanup(browser);
+
+ scsi_cleanup();
+}
+
+void
+browser_update_menu(struct browser *browser)
+{
+ size_t vlines;
+ TERec *te;
+ Cell cell = { 0, 0 };
+
+ TextFont(systemFont);
+ TextSize(12);
+
+#if 0
+ te = *(browser->te);
+
+ DisableItem(edit_menu, EDIT_MENU_CUT_ID);
+
+ if (te->selStart == te->selEnd)
+ DisableItem(edit_menu, EDIT_MENU_COPY_ID);
+ else
+ EnableItem(edit_menu, EDIT_MENU_COPY_ID);
+
+ DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
+
+ if (te->nLines == 0) {
+ DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ } else {
+ EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
+ }
+#endif
+}
+
+void
+browser_update(struct focusable *focusable, EventRecord *event)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+ Rect r;
+ short what = -1;
+
+ if (event != NULL)
+ what = event->what;
+
+ switch (what) {
+ case -1:
+ case updateEvt:
+ browser_use_shadow(browser);
+
+ FillRect(&browser->win->portRect, fill_pattern);
+
+ HLock(browser->uri_te);
+ r = (*(browser->uri_te))->viewRect;
+ HUnlock(browser->uri_te);
+ FillRect(&r, white);
+ TEUpdate(&r, browser->uri_te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ HLock(browser->output_te);
+ r = (*(browser->output_te))->viewRect;
+ HUnlock(browser->output_te);
+ FillRect(&r, white);
+ TEUpdate(&r, browser->output_te);
+ InsetRect(&r, -1, -1);
+ FrameRect(&r);
+
+ UpdtControl(browser->win, browser->win->visRgn);
+
+ 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;
+ struct browser_link *link;
+ Cell selected = { 0 };
+ Point p;
+ ControlHandle control;
+ Rect r;
+ short val, adj, page, len, part, off;
+ size_t n;
+
+ p = event->where;
+ GlobalToLocal(&p);
+
+ HLock(browser->uri_te);
+ r = (*(browser->uri_te))->viewRect;
+ HUnlock(browser->uri_te);
+ if (PtInRect(p, &r)) {
+ if (browser->active_te != browser->uri_te) {
+ TEDeactivate(browser->active_te);
+ browser->active_te = browser->uri_te;
+ TEActivate(browser->uri_te);
+ }
+ TEClick(p, ((event->modifiers & shiftKey) != 0), browser->uri_te);
+ browser_update_menu(browser);
+ return;
+ }
+
+ HLock(browser->output_te);
+ r = (*(browser->output_te))->viewRect;
+ HUnlock(browser->output_te);
+ if (PtInRect(p, &r)) {
+ TEClick(p, ((event->modifiers & shiftKey) != 0),
+ browser->output_te);
+
+ if (browser->req && browser->req->links) {
+ off = TEGetOffset(p, browser->output_te);
+ for (n = 0; n < browser->req->links_count; n++) {
+ link = &browser->req->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);
+ break;
+ }
+ }
+ }
+
+ browser_update_menu(browser);
+ return;
+ }
+
+ switch (part = FindControl(p, browser->win, &control)) {
+ case inButton:
+ if (TrackControl(control, p, 0L) && control == browser->go_button)
+ browser_connect(browser);
+ break;
+ case inUpButton:
+ case inDownButton:
+ case inPageUp:
+ case inPageDown:
+ if (control != browser->output_te_scroller)
+ break;
+ SetTrackControlTE(browser->output_te);
+ TrackControl(control, p, TrackMouseDownInControl);
+ break;
+ case inThumb:
+ val = GetCtlValue(control);
+ if (TrackControl(control, p, 0L) == 0)
+ break;
+ adj = val - GetCtlValue(control);
+ if (adj != 0) {
+ val -= adj;
+ if (control == browser->output_te_scroller) {
+ TEScroll(0, adj * TEGetHeight(0, 0,
+ browser->output_te), browser->output_te);
+ }
+ SetCtlValue(control, val);
+ }
+ break;
+ }
+}
+
+void
+browser_key_down(struct focusable *focusable, EventRecord *event)
+{
+ struct browser *browser = (struct browser *)(focusable->cookie);
+ char k;
+
+ k = (event->message & charCodeMask);
+
+ if (k == '\r') {
+ browser_connect(browser);
+ return;
+ }
+
+ TEKey(k, browser->uri_te);
+ TESelView(browser->uri_te);
+}
+
+bool
+browser_handle_menu(struct focusable *focusable, short menu, short item)
+{
+ struct browser *browser = (struct browser *)focusable->cookie;
+
+ switch (menu) {
+ case EDIT_MENU_ID:
+ switch (item) {
+ case EDIT_MENU_COPY_ID:
+ TECopy(browser->active_te);
+ return true;
+ case EDIT_MENU_SELECT_ALL_ID:
+ TESetSelect(0, 1024 * 32, browser->active_te);
+ return true;
+ }
+ break;
+ }
+
+ return false;
+}
+
+void
+browser_connect(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;
+
+ browser_cleanup(browser);
+
+ 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;
+
+ 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");
+ return;
+ }
+
+ TESetText(browser->req->uri, browser->req->uri_len, browser->uri_te);
+ HLock(browser->uri_te);
+ r = (*(browser->uri_te))->viewRect;
+ HUnlock(browser->uri_te);
+ TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
+ TEUpdate(&r, browser->uri_te);
+ TEDeactivate(browser->uri_te);
+ browser->active_te = browser->output_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] = '\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;
+}
+
+void
+browser_shuffle_data(struct browser *browser)
+{
+ size_t len;
+ unsigned short slen;
+ short err, status;
+ short cipherspace, plainspace, error;
+
+ if (browser->req->tcp_iopb.ioResult > 0) {
+ progress(NULL);
+ browser->req->state = REQ_STATE_DISCONNECTED;
+ 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);
+ 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;
+
+ /* 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_debugf(browser, "[Read %lu bytes of ciphertext from TLS, "
+ "forwarding to TCP]\r", len);
+
+ 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)) {
+ 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 (err) {
+ progress(NULL);
+ browser_printf(browser, "[Failed TCPStatus: %d]\r", err);
+ browser->req->state = REQ_STATE_DISCONNECTED;
+ return;
+ }
+ }
+
+ 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;
+
+ /* TODO: why does memmove fail? */
+ //memmove(browser->req->readbuf, browser->req->readbuf + len,
+ // browser->req->readbuflen - len);
+ for (n = 0; n < browser->req->tcp_input_len - len; n++)
+ browser->req->tcp_input[n] =
+ browser->req->tcp_input[len + n];
+
+ 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;
+ }
+ }
+}
+
+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;
+
+ 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;
+
+ 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);
+}
+
+size_t
+browser_printf(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);
+ va_end(argptr);
+
+ browser_print(browser, fmt, len);
+}
+
+size_t
+browser_debugf(struct browser *browser, const char *format, ...)
+{
+#if 0
+ 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);
+ va_end(argptr);
+
+ browser_print(browser, fmt, len);
+#endif
+}
+
+size_t
+browser_print(struct browser *browser, const char *str, size_t len)
+{
+ StScrpRec *scrp_rec;
+ ScrpSTElement *scrp_ele;
+ struct browser_link *link;
+ short line_height;
+ size_t n;
+ unsigned long style = STYLE_NONE;
+
+ if (browser->req && browser->req->style)
+ style = browser->req->style;
+
+ line_height = BROWSER_FONT_SIZE + 3;
+
+ browser_avoid_te_overflow(browser, browser->output_te, line_height);
+
+ if (browser->scrp_rec_h == NULL) {
+ browser->scrp_rec_h = xNewHandle(sizeof(short) +
+ (sizeof(ScrpSTElement) * BROWSER_SCRAP_ELEMENTS));
+ HLock(browser->scrp_rec_h);
+ memset(*(browser->scrp_rec_h), 0,
+ GetHandleSize(browser->scrp_rec_h));
+ } else {
+ HLock(browser->scrp_rec_h);
+ }
+
+ scrp_rec = (StScrpRec *)(*(browser->scrp_rec_h));
+ scrp_rec->scrpNStyles = 1;
+ scrp_ele = &scrp_rec->scrpStyleTab[0];
+ scrp_ele->scrpHeight = line_height;
+ scrp_ele->scrpAscent = BROWSER_FONT_SIZE;
+ scrp_ele->scrpFont = BROWSER_FONT;
+ scrp_ele->scrpSize = BROWSER_FONT_SIZE;
+ scrp_ele->scrpFace = 0;
+
+ if (style & STYLE_BOLD)
+ scrp_ele->scrpFace |= bold | condense;
+ if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))
+ scrp_ele->scrpFace |= bold;
+ if (style & STYLE_ITALIC)
+ scrp_ele->scrpFace |= italic;
+ if (style & STYLE_LINK)
+ scrp_ele->scrpFace |= underline;
+ if (style & STYLE_H1) {
+ scrp_ele->scrpSize += 8;
+ scrp_ele->scrpHeight += 10;
+ scrp_ele->scrpAscent += 8;
+ } else if (style & STYLE_H2) {
+ scrp_ele->scrpSize += 4;
+ scrp_ele->scrpHeight += 6;
+ scrp_ele->scrpAscent += 4;
+ } else if (style & STYLE_H3) {
+ scrp_ele->scrpSize += 2;
+ scrp_ele->scrpHeight += 4;
+ scrp_ele->scrpAscent += 2;
+ }
+
+ TESetSelect(SHRT_MAX, SHRT_MAX, browser->output_te);
+
+ if (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) {
+ warn("Out of memory allocating links");
+ return 0;
+ }
+ memset(&browser->req->links[browser->req->links_count], 0,
+ sizeof(struct browser_link) * BROWSER_LINKS_CHUNK_SIZE);
+ }
+
+ link = &browser->req->links[browser->req->links_count++];
+
+ HLock(browser->output_te);
+ link->pos = (*(browser->output_te))->teLength;
+ HUnlock(browser->output_te);
+
+ /* [<whitespace>]<URL>[<whitespace><title>] */
+
+ /* eat leading whitespace */
+ while (len && (str[0] == ' ' || str[0] == '\t')) {
+ str++;
+ len--;
+ }
+
+ /* url, up to whitespace or end of str */
+ for (n = 0; n <= len; n++) {
+ if (!(n == len || str[n] == ' ' || str[n] == '\t' ||
+ str[n] == '\r'))
+ continue;
+
+ if (n == len)
+ n--;
+
+ link->link = xmalloc(n + 1);
+ if (link->link == NULL) {
+ warn("Out of memory allocating link");
+ return 0;
+ }
+ memcpy(link->link, str, n);
+ link->link[n] = '\0';
+ link->len = n;
+ str += n;
+ len -= n;
+ break;
+ }
+
+ /* eat separating white space */
+ while (len && (str[0] == ' ' || str[0] == '\t')) {
+ str++;
+ len--;
+ }
+
+ /* optional title */
+ if (len > 1) {
+ /* len will include trailing \r */
+
+ link->title = xmalloc(len);
+ if (link->title == NULL) {
+ warn("Out of memory allocating link title");
+ return 0;
+ }
+ memcpy(link->title, str, len - 1);
+ link->title[len - 1] = '\0';
+ link->len = len - 1;
+
+ TEStylInsert(link->title, len - 1, browser->scrp_rec_h,
+ browser->output_te);
+
+ str += len - 1;
+ len = 1;
+ } else
+ TEStylInsert(link->link, len, browser->scrp_rec_h,
+ browser->output_te);
+
+ style &= ~(STYLE_LINK);
+ }
+
+ if (str[len - 1] == '\r' &&
+ (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))) {
+ /* print newlines in a small size */
+ TEStylInsert(str, len - 1, browser->scrp_rec_h, browser->output_te);
+ scrp_ele->scrpHeight = line_height;
+ scrp_ele->scrpAscent = BROWSER_FONT_SIZE;
+ scrp_ele->scrpFont = BROWSER_FONT;
+ scrp_ele->scrpSize = BROWSER_FONT_SIZE;
+ TEStylInsert("\r", 1, browser->scrp_rec_h, browser->output_te);
+ } else
+ TEStylInsert(str, len, browser->scrp_rec_h, browser->output_te);
+
+ HUnlock(browser->scrp_rec_h);
+
+// if (was_len == 0) {
+// SetCtlValue(browser->output_te_scroller,
+// GetCtlMin(browser->output_te_scroller));
+// }
+ UpdateScrollbarForTE(browser->win, browser->output_te_scroller,
+ browser->output_te, false);
+
+ HUnlock(browser->output_te);
+
+ return len;
+}
+
+bool
+browser_avoid_te_overflow(struct browser *browser, TEHandle te,
+ short line_height)
+{
+ RgnHandle savergn;
+ Rect zerorect = { 0, 0, 0, 0 };
+
+ HLock(te);
+
+ /* too many lines */
+ if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10))
+ goto te_overflow;
+
+ /* too many characters */
+ if ((*te)->teLength >= (SHRT_MAX - 500))
+ goto te_overflow;
+
+ /* rect of all lines is too tall */
+ if ((*te)->nLines * line_height >= (SHRT_MAX - 100))
+ goto te_overflow;
+
+ HUnlock(te);
+
+ return false;
+
+te_overflow:
+ savergn = NewRgn();
+ GetClip(savergn);
+ /* create an empty clip region so all TE updates are hidden */
+ ClipRect(&zerorect);
+
+ /* select some lines at the start, delete them */
+ TESetSelect(0, (*te)->lineStarts[5], te);
+ TEDelete(te);
+
+ /* scroll up, causing a repaint */
+ TEPinScroll(0, INT_MAX, te);
+
+ /* then scroll back down to what it looked like before we did anything */
+ TEPinScroll(0, -INT_MAX, te);
+
+ /* resume normal drawing */
+ SetClip(savergn);
+ DisposeRgn(savergn);
+
+ HUnlock(te);
+
+ return true;
+}
+
+void
+browser_clear(struct browser *browser)
+{
+ WindowPtr win;
+
+ GetPort(&win);
+ SetPort(browser->win);
+
+ EraseRect(&(*(browser->output_te))->viewRect);
+ TESetText("", 0, browser->output_te);
+
+ UpdateScrollbarForTE(browser->win, browser->output_te_scroller,
+ browser->output_te, true);
+
+ 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->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;
+
+ 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;
+ }
+ }
+
+ 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;
+ else {
+ memmove(browser->req->response, browser->req->response + len,
+ sizeof(browser->req->response) - len);
+ browser->req->response_len -= len;
+ }
+}
+
+bool
+browser_parse_header(struct browser *browser, char *str)
+{
+ short status;
+
+ browser_debugf(browser, "[Received header: %d]\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);
+
+ HLock(browser->uri_te);
+ r = (*(browser->uri_te))->viewRect;
+ HUnlock(browser->uri_te);
+ TESetSelect(SHRT_MAX, SHRT_MAX, browser->uri_te);
+ TEUpdate(&r, browser->uri_te);
+
+ //browser_connect(browser);
+}
\ No newline at end of file
--- browser.h Tue Oct 1 20:08:53 2024
+++ browser.h Tue Oct 1 20:08:53 2024
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2021-2024 joshua stein <jcs@jcs.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef __BROWSER_H__
+#define __BROWSER_H__
+
+#include <stdlib.h>
+#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)
+#define STYLE_H1 (1UL << 2)
+#define STYLE_H2 (1UL << 3)
+#define STYLE_H3 (1UL << 4)
+#define STYLE_LINK (1UL << 5)
+#define STYLE_PRE (1UL << 6)
+#define STYLE_LIST (1UL << 7)
+#define STYLE_QUOTE (1UL << 8)
+
+struct browser_link {
+ char *link;
+ char *title;
+ unsigned short pos;
+ unsigned short len;
+};
+
+struct tcp_request {
+ short 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;
+
+ char hostname[256];
+ ip_addr host_ip;
+ unsigned short port;
+
+ TCPiopb tcp_iopb;
+ StreamPtr tcp_stream;
+ wdsEntry tcp_wds[2];
+ TCPStatusPB tcp_status_pb;
+
+ 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;
+ TEHandle output_te;
+ ControlHandle output_te_scroller;
+ Handle scrp_rec_h;
+#define BROWSER_SCRAP_ELEMENTS 20
+ TEHandle active_te;
+ struct tcp_request *req;
+
+ unsigned char scsi_buf[2048];
+};
+
+struct browser * browser_init(void);
+size_t browser_print(struct browser *browser, const char *str, size_t len);
+void browser_clear(struct browser *browser);
+
+#endif
\ No newline at end of file
--- client.c Tue Oct 1 12:13:41 2024
+++ client.c Tue Oct 1 20:08:53 2024
@@ -1,1249 +0,0 @@
-/*
- * Copyright (c) 2021-2022 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 "gemino.h"
-#include "client.h"
-#include "focusable.h"
-#include "util.h"
-
-#define PADDING 10
-#define CLIENT_FONT_SIZE 10
-#define CLIENT_FONT geneva
-
-bool client_close(struct focusable *focusable);
-void client_idle(struct focusable *focusable, EventRecord *event);
-void client_update_menu(struct client *client);
-void client_update(struct focusable *focusable, EventRecord *event);
-void client_key_down(struct focusable *focusable, EventRecord *event);
-void client_mouse_down(struct focusable *focusable, EventRecord *event);
-bool client_handle_menu(struct focusable *focusable, short menu,
- short item);
-void client_atexit(struct focusable *focusable);
-bool client_avoid_te_overflow(struct client *client, TEHandle te,
- short line_height);
-
-void client_cleanup(struct client *client);
-void client_connect(struct client *client);
-void client_shuffle_data(struct client *client);
-void shuffle_read_tls_ciphertext(struct client *client);
-void shuffle_read_tcp_ciphertext(struct client *client, short space);
-void shuffle_tls_send_plaintext(struct client *client, short space);
-void shuffle_tls_read_plaintext(struct client *client);
-
-size_t client_printf(struct client *client, const char *format, ...);
-size_t client_debugf(struct client *client, const char *format, ...);
-void client_parse_response(struct client *client);
-bool client_parse_header(struct client *client, char *str);
-void client_consume_input(struct client *client, size_t len);
-
-Pattern fill_pattern;
-
-void
-client_idle(struct focusable *focusable, EventRecord *event)
-{
- struct client *client = (struct client *)focusable->cookie;
- size_t len;
-
- TEIdle(client->active_te);
-
- if (!client->req)
- return;
-
- switch (client->req->state) {
- case REQ_STATE_IDLE:
- break;
- case REQ_STATE_CONNECTED:
- client_shuffle_data(client);
- break;
- }
-}
-
-struct client *
-client_init(void)
-{
- char title[64];
- struct client *client;
- struct focusable *focusable;
- Rect bounds = { 0 }, te_bounds = { 0 };
- Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
- Point cell_size = { 0, 0 };
- Cell cell = { 0 };
- short n, width, height;
-
- client = xmalloczero(sizeof(struct client));
- if (client == NULL)
- panic("Out of memory allocating client");
-
- GetIndPattern(&fill_pattern, sysPatListID, 10);
-
- /* main window */
- width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
- if (width > 540)
- width = 540;
- height = screenBits.bounds.bottom - screenBits.bounds.top -
- PADDING - (GetMBarHeight() * 2);
- if (height > 340)
- height = 300;
- center_in_screen(width, height, true, &bounds);
-
- snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
- CtoPstr(title);
- client->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
- (WindowPtr)-1L, true, 0);
- if (!client->win)
- panic("Can't create window");
- SetPort(client->win);
-
- /* uri TE */
- bounds.top = PADDING;
- bounds.left = PADDING;
- bounds.right = client->win->portRect.right - PADDING - 50;
- bounds.bottom = bounds.top + 16;
- te_bounds = bounds;
- InsetRect(&te_bounds, 2, 2);
- TextFont(geneva);
- TextSize(10);
- client->uri_te = TENew(&te_bounds, &bounds);
- if (client->uri_te == NULL)
- panic("Out of memory allocating TE");
- TEAutoView(false, client->uri_te);
- TEActivate(client->uri_te);
- client->active_te = client->uri_te;
-
- bounds.left = bounds.right + PADDING;
- bounds.right = client->win->portRect.right - PADDING;
- client->go_button = NewControl(client->win, &bounds, "\pGo", true,
- 1, 1, 1, pushButProc, 0L);
-
- /* output TE */
- bounds.left = PADDING;
- bounds.right = client->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
- bounds.top = bounds.bottom + PADDING;
- bounds.bottom = client->win->portRect.bottom - PADDING;
- te_bounds = bounds;
- InsetRect(&te_bounds, 2, 2);
- client->output_te = TEStylNew(&te_bounds, &bounds);
- if (client->output_te == NULL)
- panic("Out of memory allocating TE");
- TEAutoView(false, client->output_te);
- (*(client->output_te))->caretHook = NullCaretHook;
-
- /* scrollbar for output text */
- bounds.right = client->win->portRect.right - PADDING;
- bounds.left = bounds.right - SCROLLBAR_WIDTH;
- bounds.bottom++;
- bounds.top--;
- client->output_te_scroller = NewControl(client->win, &bounds, "\p",
- true, 1, 1, 1, scrollBarProc, 0L);
-
- client_update_menu(client);
- UpdateScrollbarForTE(client->win, client->output_te_scroller,
- client->output_te, true);
-
- TESetText("geminiprotocol.net", strlen("geminiprotocol.net"),
- client->uri_te);
-
- focusable = xmalloczero(sizeof(struct focusable));
- if (focusable == NULL)
- panic("Out of memory!");
- focusable->cookie = client;
- focusable->win = client->win;
- focusable->idle = client_idle;
- focusable->update = client_update;
- focusable->mouse_down = client_mouse_down;
- focusable->key_down = client_key_down;
- focusable->menu = client_handle_menu;
- focusable->close = client_close;
- focusable->atexit = client_atexit;
- focusable_add(focusable);
-
- return client;
-}
-
-bool
-client_close(struct focusable *focusable)
-{
- struct client *client = (struct client *)focusable->cookie;
-
- TEDispose(client->uri_te);
- TEDispose(client->output_te);
- DisposeWindow(client->win);
-
- client_cleanup(client);
- xfree(&client);
- focusable->cookie = NULL;
-
- scsi_cleanup();
-
- return true;
-}
-
-void
-client_cleanup(struct client *client)
-{
- if (!client->req)
- return;
-
- if (client->req->tcp_stream)
- _TCPAbort(&client->req->tcp_iopb, client->req->tcp_stream,
- NULL, NULL, false);
-
- if (client->req->links)
- xfree(&client->req->links);
-
- xfree(&client->req);
-}
-
-void
-client_atexit(struct focusable *focusable)
-{
- struct client *client = (struct client *)focusable->cookie;
-
- if (client)
- client_cleanup(client);
-
- scsi_cleanup();
-}
-
-void
-client_update_menu(struct client *client)
-{
- size_t vlines;
- TERec *te;
- Cell cell = { 0, 0 };
-
- TextFont(systemFont);
- TextSize(12);
-
-#if 0
- te = *(client->te);
-
- DisableItem(edit_menu, EDIT_MENU_CUT_ID);
-
- if (te->selStart == te->selEnd)
- DisableItem(edit_menu, EDIT_MENU_COPY_ID);
- else
- EnableItem(edit_menu, EDIT_MENU_COPY_ID);
-
- DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
-
- if (te->nLines == 0) {
- DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
- } else {
- EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
- }
-#endif
-}
-
-void
-client_update(struct focusable *focusable, EventRecord *event)
-{
- struct client *client = (struct client *)focusable->cookie;
- Rect r;
- short what = -1;
-
- if (event != NULL)
- what = event->what;
-
- switch (what) {
- case -1:
- case updateEvt:
- FillRect(&client->win->portRect, fill_pattern);
-
- HLock(client->uri_te);
- r = (*(client->uri_te))->viewRect;
- HUnlock(client->uri_te);
- FillRect(&r, white);
- TEUpdate(&r, client->uri_te);
- InsetRect(&r, -1, -1);
- FrameRect(&r);
-
- HLock(client->output_te);
- r = (*(client->output_te))->viewRect;
- HUnlock(client->output_te);
- FillRect(&r, white);
- TEUpdate(&r, client->output_te);
- InsetRect(&r, -1, -1);
- FrameRect(&r);
-
- UpdtControl(client->win, client->win->visRgn);
-
- client_update_menu(client);
-
- break;
- }
-}
-
-void
-client_mouse_down(struct focusable *focusable, EventRecord *event)
-{
- struct client *client = (struct client *)focusable->cookie;
- struct client_link *link;
- Cell selected = { 0 };
- Point p;
- ControlHandle control;
- Rect r;
- short val, adj, page, len, part, off;
- size_t n;
-
- p = event->where;
- GlobalToLocal(&p);
-
- HLock(client->uri_te);
- r = (*(client->uri_te))->viewRect;
- HUnlock(client->uri_te);
- if (PtInRect(p, &r)) {
- if (client->active_te != client->uri_te) {
- TEDeactivate(client->active_te);
- client->active_te = client->uri_te;
- TEActivate(client->uri_te);
- }
- TEClick(p, ((event->modifiers & shiftKey) != 0), client->uri_te);
- client_update_menu(client);
- return;
- }
-
- switch (part = FindControl(p, client->win, &control)) {
- case inButton:
- if (TrackControl(control, p, 0L) && control == client->go_button)
- client_connect(client);
- break;
- case inUpButton:
- case inDownButton:
- case inPageUp:
- case inPageDown:
- if (control != client->output_te_scroller)
- break;
- SetTrackControlTE(client->output_te);
- TrackControl(control, p, TrackMouseDownInControl);
- break;
- case inThumb:
- val = GetCtlValue(control);
- if (TrackControl(control, p, 0L) == 0)
- break;
- adj = val - GetCtlValue(control);
- if (adj != 0) {
- val -= adj;
- if (control == client->output_te_scroller) {
- TEScroll(0, adj * TEGetHeight(0, 0,
- client->output_te), client->output_te);
- }
- SetCtlValue(control, val);
- }
- break;
- }
-}
-
-void
-client_key_down(struct focusable *focusable, EventRecord *event)
-{
- struct client *client = (struct client *)(focusable->cookie);
- char k;
-
- k = (event->message & charCodeMask);
-
- if (k == '\r') {
- client_connect(client);
- return;
- }
-
- TEKey(k, client->uri_te);
- TESelView(client->uri_te);
-}
-
-bool
-client_handle_menu(struct focusable *focusable, short menu, short item)
-{
- struct client *client = (struct client *)focusable->cookie;
-
- switch (menu) {
- case EDIT_MENU_ID:
- switch (item) {
- case EDIT_MENU_COPY_ID:
- TECopy(client->active_te);
- return true;
- case EDIT_MENU_SELECT_ALL_ID:
- TESetSelect(0, 1024 * 32, client->active_te);
- return true;
- }
- break;
- }
-
- return false;
-}
-
-void
-client_connect(struct client *client)
-{
- 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;
-
- client_cleanup(client);
-
- client->req = xmalloczero(sizeof(struct tcp_request));
- if (client->req == NULL)
- panic("Out of memory allocating request");
-
- client->req->state = REQ_STATE_DISCONNECTED;
- client->req->gem_state = GEM_STATE_HEADER;
-
- HLock(client->uri_te);
- te = *(client->uri_te);
- HLock(te->hText);
- len = te->teLength;
- if (len >= sizeof(client->req->uri))
- len = sizeof(client->req->uri) - 1;
- memcpy(client->req->uri, *(te->hText), len);
- client->req->uri[len] = '\0';
- HUnlock(te->hText);
- HUnlock(client->uri_te);
- TEDeactivate(client->uri_te);
-
- /* TODO: support URIs with port? */
- client->req->port = DEFAULT_GEMINI_PORT;
-
- if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]/%*s%n",
- &client->req->hostname, &count) == 1 && count > 10) {
- /* gemini://host/path */
- }
- else if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]%n",
- &client->req->hostname, &count) == 1 && count > 10) {
- /* gemini://host */
- client->req->uri_len = strlcat(client->req->uri, "/",
- sizeof(client->req->uri));
- }
- else if (count = 0, sscanf(client->req->uri, "%255[^/]/%*s%n",
- &client->req->hostname, &count) == 1 && count > 3) {
- /* host/path */
- memmove(client->req->uri + 9, client->req->uri,
- sizeof(client->req->uri) - 9);
- client->req->uri[sizeof(client->req->uri) - 1] = '\0';
- memcpy(client->req->uri, "gemini://", 9);
- client->req->uri_len += 9;
- }
- else if (count = 0, sscanf(client->req->uri, "%255[^/]%n",
- &client->req->hostname, &count) == 1 && count > 1) {
- /* host */
- client->req->uri_len = snprintf(client->req->uri,
- sizeof(client->req->uri), "gemini://%s/", client->req->hostname);
- }
- else {
- warn("Invalid URI");
- return;
- }
-
- TESetText(client->req->uri, strlen(client->req->uri), client->uri_te);
- HLock(client->uri_te);
- r = (*(client->uri_te))->viewRect;
- HUnlock(client->uri_te);
- TEUpdate(&r, client->uri_te);
-
- client->req->uri_len = strlcat(client->req->uri, "\r\n",
- sizeof(client->req->uri));
-
- if (client->req->host_ip == 0) {
- progress("Resolving \"%s\"...", client->req->hostname);
-
- err = DNSResolveName(client->req->hostname,
- &client->req->host_ip, NULL);
- if (err) {
- progress(NULL);
- client_printf(client, "[Failed resolving: %d]\r", err);
- return;
- }
- }
-
- long2ip(client->req->host_ip, (char *)&ip_s);
- progress("Connecting to %s port %d...", ip_s, client->req->port);
-
- err = _TCPCreate(&client->req->tcp_iopb, &client->req->tcp_stream,
- (Ptr)client->req->tcp_buf, sizeof(client->req->tcp_buf), NULL, NULL,
- NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[TCPCreate failed: %d]\r", err);
- goto error;
- }
-
- err = _TCPActiveOpen(&client->req->tcp_iopb, client->req->tcp_stream,
- client->req->host_ip, client->req->port, &local_ip, &local_port,
- NULL, NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[Failed connecting to %s (%s) port %d: %d]\r",
- client->req->hostname, ip_s, client->req->port, err);
- goto error;
- }
-
- err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
- &client->req->tcp_status_pb, NULL, NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[Failed TCPStatus on connection to %s (%s) "
- "port %d: %d]\r", client->req->hostname, ip_s, port, err);
- goto error;
- }
-
- memset(&tls_req, 0, sizeof(tls_req));
- strlcpy(tls_req.hostname, client->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;
-
- client->req->tls_id = 1;
-
- progress("Performing TLS handshake...");
- scsi_tls_init(client->req->tls_id, client->scsi_buf,
- sizeof(client->scsi_buf), &tls_req);
-
- client->req->state = REQ_STATE_CONNECTED;
-
-error:
- return;
-}
-
-void
-client_shuffle_data(struct client *client)
-{
- size_t len;
- unsigned short slen;
- short err, status;
- short cipherspace, plainspace, error;
-
- if (client->req->tcp_iopb.ioResult > 0) {
- progress(NULL);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
-
- status = scsi_tls_status(client->req->tls_id, client->scsi_buf,
- sizeof(client->scsi_buf), &cipherspace, &plainspace, &error);
-
- while (status != 0) {
- if (status & 0x1) {
- /* closed */
- progress(NULL);
- client_printf(client, "[TLS handshake failed: %d, 0x%x]\r",
- error, status);
- client->req->state = REQ_STATE_DISCONNECTED;
- break;
- }
-
- if (status & 0x2) {
- /* tls has ciphertext for tcp */
- shuffle_read_tls_ciphertext(client);
- status &= ~0x2;
- }
-
- if (status & 0x10) {
- /* tls has plaintext data for us */
- progress(NULL);
- TEActivate(client->uri_te);
- shuffle_tls_read_plaintext(client);
- status &= ~0x10;
- }
-
- if (status & 0x8) {
- /* tls can read plaintext from us */
- shuffle_tls_send_plaintext(client, plainspace);
- status &= ~0x8;
- }
-
- if (status & 0x4) {
- /* tls can read ciphertext from tcp */
- shuffle_read_tcp_ciphertext(client, cipherspace);
- status &= ~0x4;
- }
-
- if (status) {
- progress(NULL);
- client_printf(client, "[Status is 0x%x?]\r", status);
- return;
- }
- }
-}
-
-void
-shuffle_read_tls_ciphertext(struct client *client)
-{
- size_t len;
- short err;
-
- /* read ciphertext from TLS and send it out TCP */
- len = scsi_tls_read(client->req->tls_id, client->scsi_buf,
- sizeof(client->scsi_buf), true);
- if (len == 0) {
- client_printf(client, "[No ciphertext read from TLS]\r");
- return;
- }
-
- client_debugf(client, "[Read %lu bytes of ciphertext from TLS, "
- "forwarding to TCP]\r", len);
-
- memset(&client->req->tcp_wds, 0, sizeof(client->req->tcp_wds));
- client->req->tcp_wds[0].ptr = (Ptr)client->scsi_buf;
- client->req->tcp_wds[0].length = len;
-
- err = _TCPSend(&client->req->tcp_iopb, client->req->tcp_stream,
- client->req->tcp_wds, NULL, NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[TCPSend failed: %d]\r", err);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
-}
-
-void
-shuffle_read_tcp_ciphertext(struct client *client, short space)
-{
- size_t len;
- short err;
- unsigned short slen;
-
- /* read ciphertext from TCP and send it to TLS */
- if (client->req->tcp_input_len < sizeof(client->req->tcp_input)) {
- err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
- &client->req->tcp_status_pb, NULL, NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[Failed TCPStatus: %d]\r", err);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
-
- if (client->req->tcp_status_pb.amtUnreadData > 0) {
- slen = MIN(client->req->tcp_status_pb.amtUnreadData,
- sizeof(client->req->tcp_input) - client->req->tcp_input_len);
- if (slen) {
- err = _TCPRcv(&client->req->tcp_iopb,
- client->req->tcp_stream,
- (Ptr)(client->req->tcp_input + client->req->tcp_input_len),
- &slen, NULL, NULL, false);
- if (err) {
- progress(NULL);
- client_printf(client, "[Failed TCPRcv: %d]\r", err);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
- client->req->tcp_input_len += slen;
- client_debugf(client, "[Read %d bytes of ciphertext from "
- "TCP, forwarding to TLS]\r", slen);
- } else {
- client_printf(client, "[No buffer space available in "
- "tcp_input]\r");
- }
- }
- }
-
- if (client->req->tcp_input_len && space) {
- slen = client->req->tcp_input_len;
- if (slen > space)
- slen = space;
- client_debugf(client, "[Writing %d bytes of ciphertext to TLS]\r",
- slen);
- len = scsi_tls_write(client->req->tls_id, client->req->tcp_input,
- slen, true);
- if (len > 0) {
- if (len == client->req->tcp_input_len)
- client->req->tcp_input_len = 0;
- else {
- size_t n;
-
- /* TODO: why does memmove fail? */
- //memmove(client->req->readbuf, client->req->readbuf + len,
- // client->req->readbuflen - len);
- for (n = 0; n < client->req->tcp_input_len - len; n++)
- client->req->tcp_input[n] =
- client->req->tcp_input[len + n];
-
- client->req->tcp_input_len -= len;
- client_debugf(client, "[Wrote %ld bytes of "
- "ciphertext to TLS, %ld left]\r", len,
- client->req->tcp_input_len);
- }
- } else {
- progress(NULL);
- client_printf(client, "[Failed sending %d bytes of ciphertext "
- "to TLS]\r", slen);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
- }
-}
-
-void
-shuffle_tls_send_plaintext(struct client *client, short space)
-{
- size_t slen, len;
-
- /* send any plaintext from us to TLS */
- if (client->req->uri_len == 0)
- return;
-
- slen = client->req->uri_len;
- if (slen > space)
- slen = space;
-
- len = scsi_tls_write(client->req->tls_id,
- (unsigned char *)client->req->uri, slen, false);
- if (len) {
- if (len == client->req->uri_len) {
- client->req->uri_len = 0;
- client_debugf(client, "[Sent %ld bytes of plaintext to TLS]\r",
- len);
- } else {
- memmove(client->req->uri, client->req->uri + len,
- client->req->uri_len - len);
- client->req->uri_len -= len;
- client_debugf(client, "[Wrote %ld bytes of plaintext to "
- "TLS, %ld left]\r", len, client->req->uri_len);
- }
- } else {
- progress(NULL);
- client_printf(client, "[Failed sending %ld bytes of plaintext "
- "to TLS]\r", slen);
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
-}
-
-void
-shuffle_tls_read_plaintext(struct client *client)
-{
- size_t len;
-
- /* read as much plaintext from TLS as we can buffer */
- len = sizeof(client->req->input) - client->req->input_len;
- if (len > sizeof(client->scsi_buf))
- len = sizeof(client->scsi_buf);
- if (len > 0) {
- len = scsi_tls_read(client->req->tls_id, client->scsi_buf, len,
- false);
- if (len > 0) {
- client_debugf(client, "[Read %ld bytes of plaintext from TLS]\r", len);
- if (len > sizeof(client->req->input) + client->req->input_len)
- panic("input overflow!");
- memcpy(client->req->input + client->req->input_len,
- client->scsi_buf, len);
- client->req->input_len += len;
- }
- }
-
- if (client->req->input_len)
- client_parse_response(client);
-}
-
-size_t
-client_printf(struct client *client, 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);
- va_end(argptr);
-
- client_print(client, fmt, len);
-}
-
-size_t
-client_debugf(struct client *client, const char *format, ...)
-{
-#if 0
- 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);
- va_end(argptr);
-
- client_print(client, fmt, len);
-#endif
-}
-
-size_t
-client_print(struct client *client, const char *str, size_t len)
-{
- StScrpRec *scrp_rec;
- ScrpSTElement *scrp_ele;
- struct client_link *link;
- short line_height;
- size_t n;
- unsigned long style = STYLE_NONE;
-
- if (client->req && client->req->style)
- style = client->req->style;
-
- line_height = CLIENT_FONT_SIZE + 3;
-
- client_avoid_te_overflow(client, client->output_te, line_height);
-
- if (client->scrp_rec_h == NULL) {
- client->scrp_rec_h = xNewHandle(sizeof(short) +
- (sizeof(ScrpSTElement) * CLIENT_SCRAP_ELEMENTS));
- HLock(client->scrp_rec_h);
- memset(*(client->scrp_rec_h), 0,
- GetHandleSize(client->scrp_rec_h));
- } else {
- HLock(client->scrp_rec_h);
- }
-
- scrp_rec = (StScrpRec *)(*(client->scrp_rec_h));
- scrp_rec->scrpNStyles = 1;
- scrp_ele = &scrp_rec->scrpStyleTab[0];
- scrp_ele->scrpHeight = line_height;
- scrp_ele->scrpAscent = CLIENT_FONT_SIZE;
- scrp_ele->scrpFont = CLIENT_FONT;
- scrp_ele->scrpSize = CLIENT_FONT_SIZE;
- scrp_ele->scrpFace = 0;
-
- if (style & STYLE_BOLD)
- scrp_ele->scrpFace |= bold | condense;
- if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))
- scrp_ele->scrpFace |= bold;
- if (style & STYLE_ITALIC)
- scrp_ele->scrpFace |= italic;
- if (style & STYLE_LINK)
- scrp_ele->scrpFace |= underline;
- if (style & STYLE_H1) {
- scrp_ele->scrpSize += 8;
- scrp_ele->scrpHeight += 10;
- scrp_ele->scrpAscent += 8;
- } else if (style & STYLE_H2) {
- scrp_ele->scrpSize += 4;
- scrp_ele->scrpHeight += 6;
- scrp_ele->scrpAscent += 4;
- } else if (style & STYLE_H3) {
- scrp_ele->scrpSize += 2;
- scrp_ele->scrpHeight += 4;
- scrp_ele->scrpAscent += 2;
- }
-
- TESetSelect(SHRT_MAX, SHRT_MAX, client->output_te);
-
- if (style & STYLE_LINK) {
- if (client->req->links_count == client->req->links_size) {
- client->req->links_size += 256;
- client->req->links = xreallocarray(client->req->links,
- client->req->links_size, sizeof(struct client_link));
- if (client->req->links == NULL) {
- warn("Out of memory allocating links");
- return 0;
- }
- memset(&client->req->links[client->req->links_count], 0,
- sizeof(struct client_link) * 256);
- }
-
- link = &client->req->links[client->req->links_count++];
-
- HLock(client->output_te);
- link->pos = (*(client->output_te))->teLength;
- HUnlock(client->output_te);
-
- /* [<whitespace>]<URL>[<whitespace><title>] */
-
- /* eat leading whitespace */
- while (len && (str[0] == ' ' || str[0] == '\t')) {
- str++;
- len--;
- }
-
- /* url, up to whitespace or end of str */
- for (n = 0; n <= len; n++) {
- if (!(n == len || str[n] == ' ' || str[n] == '\t' ||
- str[n] == '\r'))
- continue;
-
- if (n == len)
- n--;
-
- link->link = xmalloc(n + 1);
- if (link->link == NULL) {
- warn("Out of memory allocating link");
- return 0;
- }
- memcpy(link->link, str, n);
- link->link[n] = '\0';
- link->len = n;
- str += n;
- len -= n;
- break;
- }
-
- /* eat separating white space */
- while (len && (str[0] == ' ' || str[0] == '\t')) {
- str++;
- len--;
- }
-
- /* optional title */
- if (len > 1) {
- /* len will include trailing \r */
-
- link->title = xmalloc(len);
- if (link->title == NULL) {
- warn("Out of memory allocating link title");
- return 0;
- }
- memcpy(link->title, str, len - 1);
- link->title[len - 1] = '\0';
- link->len = len - 1;
-
- TEStylInsert(link->title, len - 1, client->scrp_rec_h,
- client->output_te);
-
- str += len - 1;
- len = 1;
- } else
- TEStylInsert(link->link, len, client->scrp_rec_h,
- client->output_te);
-
- style &= ~(STYLE_LINK);
- }
-
- if (str[len - 1] == '\r' &&
- (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))) {
- /* print newlines in a small size */
- TEStylInsert(str, len - 1, client->scrp_rec_h, client->output_te);
- scrp_ele->scrpHeight = line_height;
- scrp_ele->scrpAscent = CLIENT_FONT_SIZE;
- scrp_ele->scrpFont = CLIENT_FONT;
- scrp_ele->scrpSize = CLIENT_FONT_SIZE;
- TEStylInsert("\r", 1, client->scrp_rec_h, client->output_te);
- } else
- TEStylInsert(str, len, client->scrp_rec_h, client->output_te);
-
- HUnlock(client->scrp_rec_h);
-
-// if (was_len == 0) {
-// SetCtlValue(client->output_te_scroller,
-// GetCtlMin(client->output_te_scroller));
-// }
- UpdateScrollbarForTE(client->win, client->output_te_scroller,
- client->output_te, false);
-
- HUnlock(client->output_te);
-
- return len;
-}
-
-bool
-client_avoid_te_overflow(struct client *client, TEHandle te,
- short line_height)
-{
- RgnHandle savergn;
- Rect zerorect = { 0, 0, 0, 0 };
-
- HLock(te);
-
- /* too many lines */
- if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10))
- goto te_overflow;
-
- /* too many characters */
- if ((*te)->teLength >= (SHRT_MAX - 500))
- goto te_overflow;
-
- /* rect of all lines is too tall */
- if ((*te)->nLines * line_height >= (SHRT_MAX - 100))
- goto te_overflow;
-
- HUnlock(te);
-
- return false;
-
-te_overflow:
- savergn = NewRgn();
- GetClip(savergn);
- /* create an empty clip region so all TE updates are hidden */
- ClipRect(&zerorect);
-
- /* select some lines at the start, delete them */
- TESetSelect(0, (*te)->lineStarts[5], te);
- TEDelete(te);
-
- /* scroll up, causing a repaint */
- TEPinScroll(0, INT_MAX, te);
-
- /* then scroll back down to what it looked like before we did anything */
- TEPinScroll(0, -INT_MAX, te);
-
- /* resume normal drawing */
- SetClip(savergn);
- DisposeRgn(savergn);
-
- HUnlock(te);
-
- return true;
-}
-
-void
-client_clear(struct client *client)
-{
- size_t n;
-
- TEPinScroll(0, -SHRT_MAX, client->uri_te);
- TESetText("", 0, client->uri_te);
-
- TEPinScroll(0, -SHRT_MAX, client->output_te);
- TESetText("", 0, client->output_te);
- UpdateScrollbarForTE(client->win, client->output_te_scroller,
- client->output_te, true);
-}
-
-void
-client_parse_response(struct client *client)
-{
- size_t n, trail, skip;
- char c;
-
-handle_state:
- switch (client->req->gem_state) {
- case GEM_STATE_HEADER: {
- short status;
-
- for (n = 0; n < client->req->input_len; n++) {
- c = client->req->input[n];
-
- if (!(c == '\n' && n && client->req->input[n - 1] == '\r'))
- continue;
-
- client->req->input[n] = '\0';
- client->req->input[n - 1] = '\0';
-
- if (!client_parse_header(client, client->req->input)) {
- client->req->state = REQ_STATE_DISCONNECTED;
- return;
- }
-
- if (strncmp(client->req->mime_type, "text/gemini", 11) == 0)
- client->req->gem_state = GEM_STATE_GEMTEXT;
- else
- client->req->gem_state = GEM_STATE_DOWNLOAD;
-
- client_consume_input(client, 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:
- trail = 0;
- for (n = 0; n <= client->req->input_len; n++) {
- if (n == client->req->input_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 == client->req->input_len ||
- client->req->input[n] == '\n'))
- continue;
-
- if (n < client->req->input_len &&
- client->req->input[n] == '\n') {
- client->req->input[n] = '\r';
- if (client->req->input[n - 1] == '\r')
- trail = 1;
- }
-
- if (client->req->input[0] == '=' &&
- client->req->input[1] == '>') {
- /* link */
- client->req->style = STYLE_LINK;
- skip = !!(client->req->input[2] == ' ');
- client_print(client, client->req->input + 2 + skip,
- n - trail - skip);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (client->req->input[0] == '`' &&
- client->req->input[1] == '`' &&
- client->req->input[2] == '`') {
- /* ``` toggle */
- client->req->style = STYLE_PRE;
- client_consume_input(client, n + 1);
- goto restart_parse;
- }
-
- if (client->req->input[0] == '#' &&
- client->req->input[1] == '#' &&
- client->req->input[2] == '#') {
- /* ### h3 */
- client->req->style = STYLE_H3;
- skip = !!(client->req->input[3] == ' ');
- client_print(client, client->req->input + 3 + skip,
- n - 2 - skip - trail);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (client->req->input[0] == '#' &&
- client->req->input[1] == '#') {
- /* ## h2 */
- client->req->style = STYLE_H2;
- skip = !!(client->req->input[2] == ' ');
- client_print(client, client->req->input + 2 + skip,
- n - 1 - skip - trail);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (client->req->input[0] == '#') {
- /* # h1 */
- client->req->style = STYLE_H1;
- skip = !!(client->req->input[1] == ' ');
- client_print(client, client->req->input + 1 + skip,
- n - skip - trail);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (client->req->input[0] == '*') {
- /* * list item */
- client->req->style = STYLE_LIST;
- skip = !!(client->req->input[1] == ' ');
- client_print(client, client->req->input + 1 + skip,
- n - skip - trail);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (client->req->input[0] == '>') {
- /* > quote text */
- client->req->style = STYLE_QUOTE;
- skip = !!(client->req->input[1] == ' ');
- client_print(client, client->req->input + 1 + skip,
- n - skip - trail);
- client_consume_input(client, n + 1);
- client->req->style = STYLE_NONE;
- goto restart_parse;
- }
-
- if (n == client->req->input_len) {
- /* end of buffer with no start, probably a continuation */
- client_print(client, client->req->input, n);
- client_consume_input(client, n);
- break;
- }
-
- /* just plain text */
- client_print(client, client->req->input, n + 1 - trail);
- client_consume_input(client, n + 1);
- goto restart_parse;
- }
- break;
- }
-}
-
-void
-client_consume_input(struct client *client, size_t len)
-{
- if (len == client->req->input_len)
- client->req->input_len = 0;
- else {
- memmove(client->req->input, client->req->input + len,
- sizeof(client->req->input) - len);
- client->req->input_len -= len;
- }
-}
-
-bool
-client_parse_header(struct client *client, char *str)
-{
- short status;
-
- client_debugf(client, "[Received header: %d]\r", str);
- if (!(str[0] >= '0' && str[0] <= '9' &&
- str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) {
- client_printf(client, "[Malformed response %s]\r", str);
- return false;
- }
-
- status = ((str[0] - '0') * 10) + (str[1] - '0');
-
- if (status >= 10 && status <= 19) {
- /* input, not supported */
- client_printf(client, "[Input not supported (%d)]\r", status);
- return false;
- }
- if (status >= 20 && status <= 29) {
- /* success */
- strlcpy(client->req->mime_type, str + 3,
- sizeof(client->req->mime_type));
- return true;
- }
- if (status >= 30 && status <= 39) {
- /* redirect */
- /* TODO */
- return false;
- }
- if (status >= 40 && status <= 49) {
- /* temp fail */
- client_printf(client, "[Temporary server failure (%d)]\r", status);
- return false;
- }
- if (status >= 50 && status <= 59) {
- /* perm fail */
- client_printf(client, "[Permanent server failure (%d)]\r", status);
- return false;
- }
- if (status >= 60 && status <= 69) {
- /* auth, not supported */
- client_printf(client, "[Auth not supported (%d)]\r", status);
- return false;
- }
- client_printf(client, "[Unsupported status %d]\r", status);
- return false;
-}
--- client.h Tue Oct 1 11:41:30 2024
+++ client.h Tue Oct 1 20:08:53 2024
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
- *
- * Permission to use, copy, modify, and distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef __BROWSER_H__
-#define __BROWSER_H__
-
-#include <stdlib.h>
-#include "tcp.h"
-#include "util.h"
-
-enum {
- REQ_STATE_IDLE,
- REQ_STATE_CONNECTED,
- 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)
-#define STYLE_H1 (1UL << 2)
-#define STYLE_H2 (1UL << 3)
-#define STYLE_H3 (1UL << 4)
-#define STYLE_LINK (1UL << 5)
-#define STYLE_PRE (1UL << 6)
-#define STYLE_LIST (1UL << 7)
-#define STYLE_QUOTE (1UL << 8)
-
-struct client_link {
- char *link;
- char *title;
- unsigned short pos;
- unsigned short len;
-};
-
-struct tcp_request {
- short 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;
-
- char hostname[256];
- ip_addr host_ip;
- unsigned short port;
-
- TCPiopb tcp_iopb;
- StreamPtr tcp_stream;
- wdsEntry tcp_wds[2];
- TCPStatusPB tcp_status_pb;
-
- char uri[1024 + 2];
- size_t uri_len;
-
- short gem_state;
- char input[1024];
- size_t input_len;
-
- char mime_type[64];
- unsigned long style;
-
- size_t links_count;
- size_t links_size;
- struct client_link *links;
-};
-
-struct client {
- WindowPtr win;
- TEHandle uri_te;
- ControlHandle go_button;
- TEHandle output_te;
- ControlHandle output_te_scroller;
- Handle scrp_rec_h;
-#define CLIENT_SCRAP_ELEMENTS 20
- TEHandle active_te;
- struct tcp_request *req;
-
- unsigned char scsi_buf[2048];
-};
-
-struct client * client_init(void);
-size_t client_print(struct client *client, const char *str, size_t len);
-void client_clear(struct client *client);
-
-#endif
\ No newline at end of file
--- gemino.h Mon Sep 30 13:51:05 2024
+++ gemino.h Tue Oct 1 20:07:23 2024
@@ -14,15 +14,15 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef __TCPUTILITY_H__
-#define __TCPUTILITY_H__
+#ifndef __GEMINO_H__
+#define __GEMINO_H__
-#include "client.h"
+#include "browser.h"
#include "util.h"
-#define PROGRAM_NAME "Gemino"
+#define PROGRAM_NAME "Gemino"
-#define MBAR_ID 128
+#define MBAR_ID 128
#define APPLE_MENU_ID 128
#define APPLE_MENU_ABOUT_ID 1
@@ -40,7 +40,7 @@
struct tls_init_request {
uint8_t flags[2];
-#define TLS_INIT_REQUEST_FLAG_NO_VERIFY (1 << 0)
+#define BLUESCSI_TLS_INIT_REQUEST_FLAG_NO_VERIFY (1 << 0)
uint8_t unix_time[4];
char hostname[256];
};
@@ -52,7 +52,7 @@ void menu_defaults(void);
short scsi_find_tls(void);
bool scsi_tls_init(char tls_id, unsigned char *buf, size_t buf_size,
struct tls_init_request *req);
-bool scsi_tls_close(char tls_id, unsigned char *buf, size_t buf_size);
+bool scsi_tls_close(char tls_id);
short scsi_tls_status(char tls_id, unsigned char *buf, size_t buf_size,
short *cipherspace, short *plainspace, short *error);
size_t scsi_tls_read(char tls_id, unsigned char *buf, size_t buf_size,
--- main.c Mon Sep 30 10:29:36 2024
+++ main.c Tue Oct 1 20:06:37 2024
@@ -18,7 +18,7 @@
#include <string.h>
#include "gemino.h"
-#include "client.h"
+#include "browser.h"
#include "focusable.h"
#include "tcp.h"
#include "util.h"
@@ -72,7 +72,7 @@ main(void)
if (_TCPInit() != 0)
panic("Failed initializing MacTCP");
- client_init();
+ browser_init();
while (!quitting) {
WaitNextEvent(everyEvent, &event, 5L, 0L);
--- scsi.c Mon Sep 30 20:46:09 2024
+++ scsi.c Tue Oct 1 15:13:41 2024
@@ -138,7 +138,7 @@ scsi_tls_init(char tls_id, unsigned char *buf, size_t
}
bool
-scsi_tls_close(char tls_id, unsigned char *buf, size_t buf_size)
+scsi_tls_close(char tls_id)
{
unsigned char cdb[6];
--- tcp.c Mon Sep 23 15:19:30 2024
+++ tcp.c Tue Oct 1 15:09:07 2024
@@ -65,8 +65,8 @@ _TCPAtexit(void)
for (n = 0; n < (sizeof(_TCPStreams) / sizeof(_TCPStreams[0])); n++) {
if (_TCPStreams[n] != 0) {
- _TCPAbort(&pb, _TCPStreams[n], nil, nil, false);
- _TCPRelease(&pb, _TCPStreams[n], nil, nil, false);
+ _TCPAbort(&pb, _TCPStreams[n], NULL, NULL, false);
+ _TCPRelease(&pb, _TCPStreams[n], NULL, NULL, false);
_TCPStreams[n] = 0;
}
}