AmendHub

Download:

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; } }