/* * Copyright (c) 2021-2022 joshua stein * * 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 #include #include #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); /* [][] */ /* 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; }