AmendHub

Download

jcs

/

detritus

/

client.c

 

(View History)

jcs   browser: Rename from client, add shadow buffer Latest amendment: 7 on 2024-10-01

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "gemino.h"
22 #include "client.h"
23 #include "focusable.h"
24 #include "util.h"
25
26 #define PADDING 10
27 #define CLIENT_FONT_SIZE 10
28 #define CLIENT_FONT geneva
29
30 bool client_close(struct focusable *focusable);
31 void client_idle(struct focusable *focusable, EventRecord *event);
32 void client_update_menu(struct client *client);
33 void client_update(struct focusable *focusable, EventRecord *event);
34 void client_key_down(struct focusable *focusable, EventRecord *event);
35 void client_mouse_down(struct focusable *focusable, EventRecord *event);
36 bool client_handle_menu(struct focusable *focusable, short menu,
37 short item);
38 void client_atexit(struct focusable *focusable);
39 bool client_avoid_te_overflow(struct client *client, TEHandle te,
40 short line_height);
41
42 void client_cleanup(struct client *client);
43 void client_connect(struct client *client);
44 void client_shuffle_data(struct client *client);
45 void shuffle_read_tls_ciphertext(struct client *client);
46 void shuffle_read_tcp_ciphertext(struct client *client, short space);
47 void shuffle_tls_send_plaintext(struct client *client, short space);
48 void shuffle_tls_read_plaintext(struct client *client);
49
50 size_t client_printf(struct client *client, const char *format, ...);
51 size_t client_debugf(struct client *client, const char *format, ...);
52 void client_parse_response(struct client *client);
53 bool client_parse_header(struct client *client, char *str);
54 void client_consume_input(struct client *client, size_t len);
55
56 Pattern fill_pattern;
57
58 void
59 client_idle(struct focusable *focusable, EventRecord *event)
60 {
61 struct client *client = (struct client *)focusable->cookie;
62 size_t len;
63
64 TEIdle(client->active_te);
65
66 if (!client->req)
67 return;
68
69 switch (client->req->state) {
70 case REQ_STATE_IDLE:
71 break;
72 case REQ_STATE_CONNECTED:
73 client_shuffle_data(client);
74 break;
75 }
76 }
77
78 struct client *
79 client_init(void)
80 {
81 char title[64];
82 struct client *client;
83 struct focusable *focusable;
84 Rect bounds = { 0 }, te_bounds = { 0 };
85 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
86 Point cell_size = { 0, 0 };
87 Cell cell = { 0 };
88 short n, width, height;
89
90 client = xmalloczero(sizeof(struct client));
91 if (client == NULL)
92 panic("Out of memory allocating client");
93
94 GetIndPattern(&fill_pattern, sysPatListID, 10);
95
96 /* main window */
97 width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
98 if (width > 540)
99 width = 540;
100 height = screenBits.bounds.bottom - screenBits.bounds.top -
101 PADDING - (GetMBarHeight() * 2);
102 if (height > 340)
103 height = 300;
104 center_in_screen(width, height, true, &bounds);
105
106 snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
107 CtoPstr(title);
108 client->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
109 (WindowPtr)-1L, true, 0);
110 if (!client->win)
111 panic("Can't create window");
112 SetPort(client->win);
113
114 /* uri TE */
115 bounds.top = PADDING;
116 bounds.left = PADDING;
117 bounds.right = client->win->portRect.right - PADDING - 50;
118 bounds.bottom = bounds.top + 16;
119 te_bounds = bounds;
120 InsetRect(&te_bounds, 2, 2);
121 TextFont(geneva);
122 TextSize(10);
123 client->uri_te = TENew(&te_bounds, &bounds);
124 if (client->uri_te == NULL)
125 panic("Out of memory allocating TE");
126 TEAutoView(false, client->uri_te);
127 TEActivate(client->uri_te);
128 client->active_te = client->uri_te;
129
130 bounds.left = bounds.right + PADDING;
131 bounds.right = client->win->portRect.right - PADDING;
132 client->go_button = NewControl(client->win, &bounds, "\pGo", true,
133 1, 1, 1, pushButProc, 0L);
134
135 /* output TE */
136 bounds.left = PADDING;
137 bounds.right = client->win->portRect.right - SCROLLBAR_WIDTH - PADDING;
138 bounds.top = bounds.bottom + PADDING;
139 bounds.bottom = client->win->portRect.bottom - PADDING;
140 te_bounds = bounds;
141 InsetRect(&te_bounds, 2, 2);
142 client->output_te = TEStylNew(&te_bounds, &bounds);
143 if (client->output_te == NULL)
144 panic("Out of memory allocating TE");
145 TEAutoView(false, client->output_te);
146 (*(client->output_te))->caretHook = NullCaretHook;
147
148 /* scrollbar for output text */
149 bounds.right = client->win->portRect.right - PADDING;
150 bounds.left = bounds.right - SCROLLBAR_WIDTH;
151 bounds.bottom++;
152 bounds.top--;
153 client->output_te_scroller = NewControl(client->win, &bounds, "\p",
154 true, 1, 1, 1, scrollBarProc, 0L);
155
156 client_update_menu(client);
157 UpdateScrollbarForTE(client->win, client->output_te_scroller,
158 client->output_te, true);
159
160 TESetText("geminiprotocol.net", strlen("geminiprotocol.net"),
161 client->uri_te);
162
163 focusable = xmalloczero(sizeof(struct focusable));
164 if (focusable == NULL)
165 panic("Out of memory!");
166 focusable->cookie = client;
167 focusable->win = client->win;
168 focusable->idle = client_idle;
169 focusable->update = client_update;
170 focusable->mouse_down = client_mouse_down;
171 focusable->key_down = client_key_down;
172 focusable->menu = client_handle_menu;
173 focusable->close = client_close;
174 focusable->atexit = client_atexit;
175 focusable_add(focusable);
176
177 return client;
178 }
179
180 bool
181 client_close(struct focusable *focusable)
182 {
183 struct client *client = (struct client *)focusable->cookie;
184
185 TEDispose(client->uri_te);
186 TEDispose(client->output_te);
187 DisposeWindow(client->win);
188
189 client_cleanup(client);
190 xfree(&client);
191 focusable->cookie = NULL;
192
193 scsi_cleanup();
194
195 return true;
196 }
197
198 void
199 client_cleanup(struct client *client)
200 {
201 if (!client->req)
202 return;
203
204 if (client->req->tcp_stream)
205 _TCPAbort(&client->req->tcp_iopb, client->req->tcp_stream,
206 NULL, NULL, false);
207
208 if (client->req->links)
209 xfree(&client->req->links);
210
211 xfree(&client->req);
212 }
213
214 void
215 client_atexit(struct focusable *focusable)
216 {
217 struct client *client = (struct client *)focusable->cookie;
218
219 if (client)
220 client_cleanup(client);
221
222 scsi_cleanup();
223 }
224
225 void
226 client_update_menu(struct client *client)
227 {
228 size_t vlines;
229 TERec *te;
230 Cell cell = { 0, 0 };
231
232 TextFont(systemFont);
233 TextSize(12);
234
235 #if 0
236 te = *(client->te);
237
238 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
239
240 if (te->selStart == te->selEnd)
241 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
242 else
243 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
244
245 DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
246
247 if (te->nLines == 0) {
248 DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
249 } else {
250 EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
251 }
252 #endif
253 }
254
255 void
256 client_update(struct focusable *focusable, EventRecord *event)
257 {
258 struct client *client = (struct client *)focusable->cookie;
259 Rect r;
260 short what = -1;
261
262 if (event != NULL)
263 what = event->what;
264
265 switch (what) {
266 case -1:
267 case updateEvt:
268 FillRect(&client->win->portRect, fill_pattern);
269
270 HLock(client->uri_te);
271 r = (*(client->uri_te))->viewRect;
272 HUnlock(client->uri_te);
273 FillRect(&r, white);
274 TEUpdate(&r, client->uri_te);
275 InsetRect(&r, -1, -1);
276 FrameRect(&r);
277
278 HLock(client->output_te);
279 r = (*(client->output_te))->viewRect;
280 HUnlock(client->output_te);
281 FillRect(&r, white);
282 TEUpdate(&r, client->output_te);
283 InsetRect(&r, -1, -1);
284 FrameRect(&r);
285
286 UpdtControl(client->win, client->win->visRgn);
287
288 client_update_menu(client);
289
290 break;
291 }
292 }
293
294 void
295 client_mouse_down(struct focusable *focusable, EventRecord *event)
296 {
297 struct client *client = (struct client *)focusable->cookie;
298 struct client_link *link;
299 Cell selected = { 0 };
300 Point p;
301 ControlHandle control;
302 Rect r;
303 short val, adj, page, len, part, off;
304 size_t n;
305
306 p = event->where;
307 GlobalToLocal(&p);
308
309 HLock(client->uri_te);
310 r = (*(client->uri_te))->viewRect;
311 HUnlock(client->uri_te);
312 if (PtInRect(p, &r)) {
313 if (client->active_te != client->uri_te) {
314 TEDeactivate(client->active_te);
315 client->active_te = client->uri_te;
316 TEActivate(client->uri_te);
317 }
318 TEClick(p, ((event->modifiers & shiftKey) != 0), client->uri_te);
319 client_update_menu(client);
320 return;
321 }
322
323 switch (part = FindControl(p, client->win, &control)) {
324 case inButton:
325 if (TrackControl(control, p, 0L) && control == client->go_button)
326 client_connect(client);
327 break;
328 case inUpButton:
329 case inDownButton:
330 case inPageUp:
331 case inPageDown:
332 if (control != client->output_te_scroller)
333 break;
334 SetTrackControlTE(client->output_te);
335 TrackControl(control, p, TrackMouseDownInControl);
336 break;
337 case inThumb:
338 val = GetCtlValue(control);
339 if (TrackControl(control, p, 0L) == 0)
340 break;
341 adj = val - GetCtlValue(control);
342 if (adj != 0) {
343 val -= adj;
344 if (control == client->output_te_scroller) {
345 TEScroll(0, adj * TEGetHeight(0, 0,
346 client->output_te), client->output_te);
347 }
348 SetCtlValue(control, val);
349 }
350 break;
351 }
352 }
353
354 void
355 client_key_down(struct focusable *focusable, EventRecord *event)
356 {
357 struct client *client = (struct client *)(focusable->cookie);
358 char k;
359
360 k = (event->message & charCodeMask);
361
362 if (k == '\r') {
363 client_connect(client);
364 return;
365 }
366
367 TEKey(k, client->uri_te);
368 TESelView(client->uri_te);
369 }
370
371 bool
372 client_handle_menu(struct focusable *focusable, short menu, short item)
373 {
374 struct client *client = (struct client *)focusable->cookie;
375
376 switch (menu) {
377 case EDIT_MENU_ID:
378 switch (item) {
379 case EDIT_MENU_COPY_ID:
380 TECopy(client->active_te);
381 return true;
382 case EDIT_MENU_SELECT_ALL_ID:
383 TESetSelect(0, 1024 * 32, client->active_te);
384 return true;
385 }
386 break;
387 }
388
389 return false;
390 }
391
392 void
393 client_connect(struct client *client)
394 {
395 struct tls_init_request tls_req;
396 char ip_s[16];
397 Rect r;
398 TERec *te;
399 size_t len;
400 unsigned long time;
401 unsigned short ret;
402 ip_addr ip, local_ip;
403 tcp_port port, local_port;
404 short err, i, j, count;
405
406 client_cleanup(client);
407
408 client->req = xmalloczero(sizeof(struct tcp_request));
409 if (client->req == NULL)
410 panic("Out of memory allocating request");
411
412 client->req->state = REQ_STATE_DISCONNECTED;
413 client->req->gem_state = GEM_STATE_HEADER;
414
415 HLock(client->uri_te);
416 te = *(client->uri_te);
417 HLock(te->hText);
418 len = te->teLength;
419 if (len >= sizeof(client->req->uri))
420 len = sizeof(client->req->uri) - 1;
421 memcpy(client->req->uri, *(te->hText), len);
422 client->req->uri[len] = '\0';
423 HUnlock(te->hText);
424 HUnlock(client->uri_te);
425 TEDeactivate(client->uri_te);
426
427 /* TODO: support URIs with port? */
428 client->req->port = DEFAULT_GEMINI_PORT;
429
430 if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]/%*s%n",
431 &client->req->hostname, &count) == 1 && count > 10) {
432 /* gemini://host/path */
433 }
434 else if (count = 0, sscanf(client->req->uri, "gemini://%255[^/]%n",
435 &client->req->hostname, &count) == 1 && count > 10) {
436 /* gemini://host */
437 client->req->uri_len = strlcat(client->req->uri, "/",
438 sizeof(client->req->uri));
439 }
440 else if (count = 0, sscanf(client->req->uri, "%255[^/]/%*s%n",
441 &client->req->hostname, &count) == 1 && count > 3) {
442 /* host/path */
443 memmove(client->req->uri + 9, client->req->uri,
444 sizeof(client->req->uri) - 9);
445 client->req->uri[sizeof(client->req->uri) - 1] = '\0';
446 memcpy(client->req->uri, "gemini://", 9);
447 client->req->uri_len += 9;
448 }
449 else if (count = 0, sscanf(client->req->uri, "%255[^/]%n",
450 &client->req->hostname, &count) == 1 && count > 1) {
451 /* host */
452 client->req->uri_len = snprintf(client->req->uri,
453 sizeof(client->req->uri), "gemini://%s/", client->req->hostname);
454 }
455 else {
456 warn("Invalid URI");
457 return;
458 }
459
460 TESetText(client->req->uri, strlen(client->req->uri), client->uri_te);
461 HLock(client->uri_te);
462 r = (*(client->uri_te))->viewRect;
463 HUnlock(client->uri_te);
464 TEUpdate(&r, client->uri_te);
465
466 client->req->uri_len = strlcat(client->req->uri, "\r\n",
467 sizeof(client->req->uri));
468
469 if (client->req->host_ip == 0) {
470 progress("Resolving \"%s\"...", client->req->hostname);
471
472 err = DNSResolveName(client->req->hostname,
473 &client->req->host_ip, NULL);
474 if (err) {
475 progress(NULL);
476 client_printf(client, "[Failed resolving: %d]\r", err);
477 return;
478 }
479 }
480
481 long2ip(client->req->host_ip, (char *)&ip_s);
482 progress("Connecting to %s port %d...", ip_s, client->req->port);
483
484 err = _TCPCreate(&client->req->tcp_iopb, &client->req->tcp_stream,
485 (Ptr)client->req->tcp_buf, sizeof(client->req->tcp_buf), NULL, NULL,
486 NULL, false);
487 if (err) {
488 progress(NULL);
489 client_printf(client, "[TCPCreate failed: %d]\r", err);
490 goto error;
491 }
492
493 err = _TCPActiveOpen(&client->req->tcp_iopb, client->req->tcp_stream,
494 client->req->host_ip, client->req->port, &local_ip, &local_port,
495 NULL, NULL, false);
496 if (err) {
497 progress(NULL);
498 client_printf(client, "[Failed connecting to %s (%s) port %d: %d]\r",
499 client->req->hostname, ip_s, client->req->port, err);
500 goto error;
501 }
502
503 err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
504 &client->req->tcp_status_pb, NULL, NULL, false);
505 if (err) {
506 progress(NULL);
507 client_printf(client, "[Failed TCPStatus on connection to %s (%s) "
508 "port %d: %d]\r", client->req->hostname, ip_s, port, err);
509 goto error;
510 }
511
512 memset(&tls_req, 0, sizeof(tls_req));
513 strlcpy(tls_req.hostname, client->req->hostname,
514 sizeof(tls_req.hostname));
515 time = MAC_TO_UNIX_TIME(Time);
516 tls_req.unix_time[0] = (time >> 24) & 0xff;
517 tls_req.unix_time[1] = (time >> 16) & 0xff;
518 tls_req.unix_time[2] = (time >> 8) & 0xff;
519 tls_req.unix_time[3] = (time) & 0xff;
520
521 client->req->tls_id = 1;
522
523 progress("Performing TLS handshake...");
524 scsi_tls_init(client->req->tls_id, client->scsi_buf,
525 sizeof(client->scsi_buf), &tls_req);
526
527 client->req->state = REQ_STATE_CONNECTED;
528
529 error:
530 return;
531 }
532
533 void
534 client_shuffle_data(struct client *client)
535 {
536 size_t len;
537 unsigned short slen;
538 short err, status;
539 short cipherspace, plainspace, error;
540
541 if (client->req->tcp_iopb.ioResult > 0) {
542 progress(NULL);
543 client->req->state = REQ_STATE_DISCONNECTED;
544 return;
545 }
546
547 status = scsi_tls_status(client->req->tls_id, client->scsi_buf,
548 sizeof(client->scsi_buf), &cipherspace, &plainspace, &error);
549
550 while (status != 0) {
551 if (status & 0x1) {
552 /* closed */
553 progress(NULL);
554 client_printf(client, "[TLS handshake failed: %d, 0x%x]\r",
555 error, status);
556 client->req->state = REQ_STATE_DISCONNECTED;
557 break;
558 }
559
560 if (status & 0x2) {
561 /* tls has ciphertext for tcp */
562 shuffle_read_tls_ciphertext(client);
563 status &= ~0x2;
564 }
565
566 if (status & 0x10) {
567 /* tls has plaintext data for us */
568 progress(NULL);
569 TEActivate(client->uri_te);
570 shuffle_tls_read_plaintext(client);
571 status &= ~0x10;
572 }
573
574 if (status & 0x8) {
575 /* tls can read plaintext from us */
576 shuffle_tls_send_plaintext(client, plainspace);
577 status &= ~0x8;
578 }
579
580 if (status & 0x4) {
581 /* tls can read ciphertext from tcp */
582 shuffle_read_tcp_ciphertext(client, cipherspace);
583 status &= ~0x4;
584 }
585
586 if (status) {
587 progress(NULL);
588 client_printf(client, "[Status is 0x%x?]\r", status);
589 return;
590 }
591 }
592 }
593
594 void
595 shuffle_read_tls_ciphertext(struct client *client)
596 {
597 size_t len;
598 short err;
599
600 /* read ciphertext from TLS and send it out TCP */
601 len = scsi_tls_read(client->req->tls_id, client->scsi_buf,
602 sizeof(client->scsi_buf), true);
603 if (len == 0) {
604 client_printf(client, "[No ciphertext read from TLS]\r");
605 return;
606 }
607
608 client_debugf(client, "[Read %lu bytes of ciphertext from TLS, "
609 "forwarding to TCP]\r", len);
610
611 memset(&client->req->tcp_wds, 0, sizeof(client->req->tcp_wds));
612 client->req->tcp_wds[0].ptr = (Ptr)client->scsi_buf;
613 client->req->tcp_wds[0].length = len;
614
615 err = _TCPSend(&client->req->tcp_iopb, client->req->tcp_stream,
616 client->req->tcp_wds, NULL, NULL, false);
617 if (err) {
618 progress(NULL);
619 client_printf(client, "[TCPSend failed: %d]\r", err);
620 client->req->state = REQ_STATE_DISCONNECTED;
621 return;
622 }
623 }
624
625 void
626 shuffle_read_tcp_ciphertext(struct client *client, short space)
627 {
628 size_t len;
629 short err;
630 unsigned short slen;
631
632 /* read ciphertext from TCP and send it to TLS */
633 if (client->req->tcp_input_len < sizeof(client->req->tcp_input)) {
634 err = _TCPStatus(&client->req->tcp_iopb, client->req->tcp_stream,
635 &client->req->tcp_status_pb, NULL, NULL, false);
636 if (err) {
637 progress(NULL);
638 client_printf(client, "[Failed TCPStatus: %d]\r", err);
639 client->req->state = REQ_STATE_DISCONNECTED;
640 return;
641 }
642
643 if (client->req->tcp_status_pb.amtUnreadData > 0) {
644 slen = MIN(client->req->tcp_status_pb.amtUnreadData,
645 sizeof(client->req->tcp_input) - client->req->tcp_input_len);
646 if (slen) {
647 err = _TCPRcv(&client->req->tcp_iopb,
648 client->req->tcp_stream,
649 (Ptr)(client->req->tcp_input + client->req->tcp_input_len),
650 &slen, NULL, NULL, false);
651 if (err) {
652 progress(NULL);
653 client_printf(client, "[Failed TCPRcv: %d]\r", err);
654 client->req->state = REQ_STATE_DISCONNECTED;
655 return;
656 }
657 client->req->tcp_input_len += slen;
658 client_debugf(client, "[Read %d bytes of ciphertext from "
659 "TCP, forwarding to TLS]\r", slen);
660 } else {
661 client_printf(client, "[No buffer space available in "
662 "tcp_input]\r");
663 }
664 }
665 }
666
667 if (client->req->tcp_input_len && space) {
668 slen = client->req->tcp_input_len;
669 if (slen > space)
670 slen = space;
671 client_debugf(client, "[Writing %d bytes of ciphertext to TLS]\r",
672 slen);
673 len = scsi_tls_write(client->req->tls_id, client->req->tcp_input,
674 slen, true);
675 if (len > 0) {
676 if (len == client->req->tcp_input_len)
677 client->req->tcp_input_len = 0;
678 else {
679 size_t n;
680
681 /* TODO: why does memmove fail? */
682 //memmove(client->req->readbuf, client->req->readbuf + len,
683 // client->req->readbuflen - len);
684 for (n = 0; n < client->req->tcp_input_len - len; n++)
685 client->req->tcp_input[n] =
686 client->req->tcp_input[len + n];
687
688 client->req->tcp_input_len -= len;
689 client_debugf(client, "[Wrote %ld bytes of "
690 "ciphertext to TLS, %ld left]\r", len,
691 client->req->tcp_input_len);
692 }
693 } else {
694 progress(NULL);
695 client_printf(client, "[Failed sending %d bytes of ciphertext "
696 "to TLS]\r", slen);
697 client->req->state = REQ_STATE_DISCONNECTED;
698 return;
699 }
700 }
701 }
702
703 void
704 shuffle_tls_send_plaintext(struct client *client, short space)
705 {
706 size_t slen, len;
707
708 /* send any plaintext from us to TLS */
709 if (client->req->uri_len == 0)
710 return;
711
712 slen = client->req->uri_len;
713 if (slen > space)
714 slen = space;
715
716 len = scsi_tls_write(client->req->tls_id,
717 (unsigned char *)client->req->uri, slen, false);
718 if (len) {
719 if (len == client->req->uri_len) {
720 client->req->uri_len = 0;
721 client_debugf(client, "[Sent %ld bytes of plaintext to TLS]\r",
722 len);
723 } else {
724 memmove(client->req->uri, client->req->uri + len,
725 client->req->uri_len - len);
726 client->req->uri_len -= len;
727 client_debugf(client, "[Wrote %ld bytes of plaintext to "
728 "TLS, %ld left]\r", len, client->req->uri_len);
729 }
730 } else {
731 progress(NULL);
732 client_printf(client, "[Failed sending %ld bytes of plaintext "
733 "to TLS]\r", slen);
734 client->req->state = REQ_STATE_DISCONNECTED;
735 return;
736 }
737 }
738
739 void
740 shuffle_tls_read_plaintext(struct client *client)
741 {
742 size_t len;
743
744 /* read as much plaintext from TLS as we can buffer */
745 len = sizeof(client->req->input) - client->req->input_len;
746 if (len > sizeof(client->scsi_buf))
747 len = sizeof(client->scsi_buf);
748 if (len > 0) {
749 len = scsi_tls_read(client->req->tls_id, client->scsi_buf, len,
750 false);
751 if (len > 0) {
752 client_debugf(client, "[Read %ld bytes of plaintext from TLS]\r", len);
753 if (len > sizeof(client->req->input) + client->req->input_len)
754 panic("input overflow!");
755 memcpy(client->req->input + client->req->input_len,
756 client->scsi_buf, len);
757 client->req->input_len += len;
758 }
759 }
760
761 if (client->req->input_len)
762 client_parse_response(client);
763 }
764
765 size_t
766 client_printf(struct client *client, const char *format, ...)
767 {
768 static char fmt[1024];
769 va_list argptr;
770 size_t len;
771
772 va_start(argptr, format);
773 len = vsnprintf(fmt, sizeof(fmt), format, argptr);
774 if (len > sizeof(fmt))
775 len = sizeof(fmt);
776 va_end(argptr);
777
778 client_print(client, fmt, len);
779 }
780
781 size_t
782 client_debugf(struct client *client, const char *format, ...)
783 {
784 #if 0
785 static char fmt[1024];
786 va_list argptr;
787 size_t len;
788
789 va_start(argptr, format);
790 len = vsnprintf(fmt, sizeof(fmt), format, argptr);
791 if (len > sizeof(fmt))
792 len = sizeof(fmt);
793 va_end(argptr);
794
795 client_print(client, fmt, len);
796 #endif
797 }
798
799 size_t
800 client_print(struct client *client, const char *str, size_t len)
801 {
802 StScrpRec *scrp_rec;
803 ScrpSTElement *scrp_ele;
804 struct client_link *link;
805 short line_height;
806 size_t n;
807 unsigned long style = STYLE_NONE;
808
809 if (client->req && client->req->style)
810 style = client->req->style;
811
812 line_height = CLIENT_FONT_SIZE + 3;
813
814 client_avoid_te_overflow(client, client->output_te, line_height);
815
816 if (client->scrp_rec_h == NULL) {
817 client->scrp_rec_h = xNewHandle(sizeof(short) +
818 (sizeof(ScrpSTElement) * CLIENT_SCRAP_ELEMENTS));
819 HLock(client->scrp_rec_h);
820 memset(*(client->scrp_rec_h), 0,
821 GetHandleSize(client->scrp_rec_h));
822 } else {
823 HLock(client->scrp_rec_h);
824 }
825
826 scrp_rec = (StScrpRec *)(*(client->scrp_rec_h));
827 scrp_rec->scrpNStyles = 1;
828 scrp_ele = &scrp_rec->scrpStyleTab[0];
829 scrp_ele->scrpHeight = line_height;
830 scrp_ele->scrpAscent = CLIENT_FONT_SIZE;
831 scrp_ele->scrpFont = CLIENT_FONT;
832 scrp_ele->scrpSize = CLIENT_FONT_SIZE;
833 scrp_ele->scrpFace = 0;
834
835 if (style & STYLE_BOLD)
836 scrp_ele->scrpFace |= bold | condense;
837 if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))
838 scrp_ele->scrpFace |= bold;
839 if (style & STYLE_ITALIC)
840 scrp_ele->scrpFace |= italic;
841 if (style & STYLE_LINK)
842 scrp_ele->scrpFace |= underline;
843 if (style & STYLE_H1) {
844 scrp_ele->scrpSize += 8;
845 scrp_ele->scrpHeight += 10;
846 scrp_ele->scrpAscent += 8;
847 } else if (style & STYLE_H2) {
848 scrp_ele->scrpSize += 4;
849 scrp_ele->scrpHeight += 6;
850 scrp_ele->scrpAscent += 4;
851 } else if (style & STYLE_H3) {
852 scrp_ele->scrpSize += 2;
853 scrp_ele->scrpHeight += 4;
854 scrp_ele->scrpAscent += 2;
855 }
856
857 TESetSelect(SHRT_MAX, SHRT_MAX, client->output_te);
858
859 if (style & STYLE_LINK) {
860 if (client->req->links_count == client->req->links_size) {
861 client->req->links_size += 256;
862 client->req->links = xreallocarray(client->req->links,
863 client->req->links_size, sizeof(struct client_link));
864 if (client->req->links == NULL) {
865 warn("Out of memory allocating links");
866 return 0;
867 }
868 memset(&client->req->links[client->req->links_count], 0,
869 sizeof(struct client_link) * 256);
870 }
871
872 link = &client->req->links[client->req->links_count++];
873
874 HLock(client->output_te);
875 link->pos = (*(client->output_te))->teLength;
876 HUnlock(client->output_te);
877
878 /* [<whitespace>]<URL>[<whitespace><title>] */
879
880 /* eat leading whitespace */
881 while (len && (str[0] == ' ' || str[0] == '\t')) {
882 str++;
883 len--;
884 }
885
886 /* url, up to whitespace or end of str */
887 for (n = 0; n <= len; n++) {
888 if (!(n == len || str[n] == ' ' || str[n] == '\t' ||
889 str[n] == '\r'))
890 continue;
891
892 if (n == len)
893 n--;
894
895 link->link = xmalloc(n + 1);
896 if (link->link == NULL) {
897 warn("Out of memory allocating link");
898 return 0;
899 }
900 memcpy(link->link, str, n);
901 link->link[n] = '\0';
902 link->len = n;
903 str += n;
904 len -= n;
905 break;
906 }
907
908 /* eat separating white space */
909 while (len && (str[0] == ' ' || str[0] == '\t')) {
910 str++;
911 len--;
912 }
913
914 /* optional title */
915 if (len > 1) {
916 /* len will include trailing \r */
917
918 link->title = xmalloc(len);
919 if (link->title == NULL) {
920 warn("Out of memory allocating link title");
921 return 0;
922 }
923 memcpy(link->title, str, len - 1);
924 link->title[len - 1] = '\0';
925 link->len = len - 1;
926
927 TEStylInsert(link->title, len - 1, client->scrp_rec_h,
928 client->output_te);
929
930 str += len - 1;
931 len = 1;
932 } else
933 TEStylInsert(link->link, len, client->scrp_rec_h,
934 client->output_te);
935
936 style &= ~(STYLE_LINK);
937 }
938
939 if (str[len - 1] == '\r' &&
940 (style & (STYLE_H1 | STYLE_H2 | STYLE_H3))) {
941 /* print newlines in a small size */
942 TEStylInsert(str, len - 1, client->scrp_rec_h, client->output_te);
943 scrp_ele->scrpHeight = line_height;
944 scrp_ele->scrpAscent = CLIENT_FONT_SIZE;
945 scrp_ele->scrpFont = CLIENT_FONT;
946 scrp_ele->scrpSize = CLIENT_FONT_SIZE;
947 TEStylInsert("\r", 1, client->scrp_rec_h, client->output_te);
948 } else
949 TEStylInsert(str, len, client->scrp_rec_h, client->output_te);
950
951 HUnlock(client->scrp_rec_h);
952
953 // if (was_len == 0) {
954 // SetCtlValue(client->output_te_scroller,
955 // GetCtlMin(client->output_te_scroller));
956 // }
957 UpdateScrollbarForTE(client->win, client->output_te_scroller,
958 client->output_te, false);
959
960 HUnlock(client->output_te);
961
962 return len;
963 }
964
965 bool
966 client_avoid_te_overflow(struct client *client, TEHandle te,
967 short line_height)
968 {
969 RgnHandle savergn;
970 Rect zerorect = { 0, 0, 0, 0 };
971
972 HLock(te);
973
974 /* too many lines */
975 if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10))
976 goto te_overflow;
977
978 /* too many characters */
979 if ((*te)->teLength >= (SHRT_MAX - 500))
980 goto te_overflow;
981
982 /* rect of all lines is too tall */
983 if ((*te)->nLines * line_height >= (SHRT_MAX - 100))
984 goto te_overflow;
985
986 HUnlock(te);
987
988 return false;
989
990 te_overflow:
991 savergn = NewRgn();
992 GetClip(savergn);
993 /* create an empty clip region so all TE updates are hidden */
994 ClipRect(&zerorect);
995
996 /* select some lines at the start, delete them */
997 TESetSelect(0, (*te)->lineStarts[5], te);
998 TEDelete(te);
999
1000 /* scroll up, causing a repaint */
1001 TEPinScroll(0, INT_MAX, te);
1002
1003 /* then scroll back down to what it looked like before we did anything */
1004 TEPinScroll(0, -INT_MAX, te);
1005
1006 /* resume normal drawing */
1007 SetClip(savergn);
1008 DisposeRgn(savergn);
1009
1010 HUnlock(te);
1011
1012 return true;
1013 }
1014
1015 void
1016 client_clear(struct client *client)
1017 {
1018 size_t n;
1019
1020 TEPinScroll(0, -SHRT_MAX, client->uri_te);
1021 TESetText("", 0, client->uri_te);
1022
1023 TEPinScroll(0, -SHRT_MAX, client->output_te);
1024 TESetText("", 0, client->output_te);
1025 UpdateScrollbarForTE(client->win, client->output_te_scroller,
1026 client->output_te, true);
1027 }
1028
1029 void
1030 client_parse_response(struct client *client)
1031 {
1032 size_t n, trail, skip;
1033 char c;
1034
1035 handle_state:
1036 switch (client->req->gem_state) {
1037 case GEM_STATE_HEADER: {
1038 short status;
1039
1040 for (n = 0; n < client->req->input_len; n++) {
1041 c = client->req->input[n];
1042
1043 if (!(c == '\n' && n && client->req->input[n - 1] == '\r'))
1044 continue;
1045
1046 client->req->input[n] = '\0';
1047 client->req->input[n - 1] = '\0';
1048
1049 if (!client_parse_header(client, client->req->input)) {
1050 client->req->state = REQ_STATE_DISCONNECTED;
1051 return;
1052 }
1053
1054 if (strncmp(client->req->mime_type, "text/gemini", 11) == 0)
1055 client->req->gem_state = GEM_STATE_GEMTEXT;
1056 else
1057 client->req->gem_state = GEM_STATE_DOWNLOAD;
1058
1059 client_consume_input(client, n + 1);
1060
1061 /* avoid a round trip through idle handler */
1062 goto handle_state;
1063 }
1064 break;
1065 }
1066 case GEM_STATE_REDIRECT:
1067 /* TODO */
1068 break;
1069 case GEM_STATE_DOWNLOAD:
1070 /* TODO */
1071 break;
1072 case GEM_STATE_GEMTEXT:
1073 restart_parse:
1074 trail = 0;
1075 for (n = 0; n <= client->req->input_len; n++) {
1076 if (n == client->req->input_len && n < 3) {
1077 /*
1078 * End of buffer with no newline, but not enough chars
1079 * to determine what this line is, skip until we get more.
1080 */
1081 break;
1082 }
1083
1084 if (!(n == client->req->input_len ||
1085 client->req->input[n] == '\n'))
1086 continue;
1087
1088 if (n < client->req->input_len &&
1089 client->req->input[n] == '\n') {
1090 client->req->input[n] = '\r';
1091 if (client->req->input[n - 1] == '\r')
1092 trail = 1;
1093 }
1094
1095 if (client->req->input[0] == '=' &&
1096 client->req->input[1] == '>') {
1097 /* link */
1098 client->req->style = STYLE_LINK;
1099 skip = !!(client->req->input[2] == ' ');
1100 client_print(client, client->req->input + 2 + skip,
1101 n - trail - skip);
1102 client_consume_input(client, n + 1);
1103 client->req->style = STYLE_NONE;
1104 goto restart_parse;
1105 }
1106
1107 if (client->req->input[0] == '`' &&
1108 client->req->input[1] == '`' &&
1109 client->req->input[2] == '`') {
1110 /* ``` toggle */
1111 client->req->style = STYLE_PRE;
1112 client_consume_input(client, n + 1);
1113 goto restart_parse;
1114 }
1115
1116 if (client->req->input[0] == '#' &&
1117 client->req->input[1] == '#' &&
1118 client->req->input[2] == '#') {
1119 /* ### h3 */
1120 client->req->style = STYLE_H3;
1121 skip = !!(client->req->input[3] == ' ');
1122 client_print(client, client->req->input + 3 + skip,
1123 n - 2 - skip - trail);
1124 client_consume_input(client, n + 1);
1125 client->req->style = STYLE_NONE;
1126 goto restart_parse;
1127 }
1128
1129 if (client->req->input[0] == '#' &&
1130 client->req->input[1] == '#') {
1131 /* ## h2 */
1132 client->req->style = STYLE_H2;
1133 skip = !!(client->req->input[2] == ' ');
1134 client_print(client, client->req->input + 2 + skip,
1135 n - 1 - skip - trail);
1136 client_consume_input(client, n + 1);
1137 client->req->style = STYLE_NONE;
1138 goto restart_parse;
1139 }
1140
1141 if (client->req->input[0] == '#') {
1142 /* # h1 */
1143 client->req->style = STYLE_H1;
1144 skip = !!(client->req->input[1] == ' ');
1145 client_print(client, client->req->input + 1 + skip,
1146 n - skip - trail);
1147 client_consume_input(client, n + 1);
1148 client->req->style = STYLE_NONE;
1149 goto restart_parse;
1150 }
1151
1152 if (client->req->input[0] == '*') {
1153 /* * list item */
1154 client->req->style = STYLE_LIST;
1155 skip = !!(client->req->input[1] == ' ');
1156 client_print(client, client->req->input + 1 + skip,
1157 n - skip - trail);
1158 client_consume_input(client, n + 1);
1159 client->req->style = STYLE_NONE;
1160 goto restart_parse;
1161 }
1162
1163 if (client->req->input[0] == '>') {
1164 /* > quote text */
1165 client->req->style = STYLE_QUOTE;
1166 skip = !!(client->req->input[1] == ' ');
1167 client_print(client, client->req->input + 1 + skip,
1168 n - skip - trail);
1169 client_consume_input(client, n + 1);
1170 client->req->style = STYLE_NONE;
1171 goto restart_parse;
1172 }
1173
1174 if (n == client->req->input_len) {
1175 /* end of buffer with no start, probably a continuation */
1176 client_print(client, client->req->input, n);
1177 client_consume_input(client, n);
1178 break;
1179 }
1180
1181 /* just plain text */
1182 client_print(client, client->req->input, n + 1 - trail);
1183 client_consume_input(client, n + 1);
1184 goto restart_parse;
1185 }
1186 break;
1187 }
1188 }
1189
1190 void
1191 client_consume_input(struct client *client, size_t len)
1192 {
1193 if (len == client->req->input_len)
1194 client->req->input_len = 0;
1195 else {
1196 memmove(client->req->input, client->req->input + len,
1197 sizeof(client->req->input) - len);
1198 client->req->input_len -= len;
1199 }
1200 }
1201
1202 bool
1203 client_parse_header(struct client *client, char *str)
1204 {
1205 short status;
1206
1207 client_debugf(client, "[Received header: %d]\r", str);
1208 if (!(str[0] >= '0' && str[0] <= '9' &&
1209 str[1] >= '0' && str[1] <= '9' && str[2] == ' ')) {
1210 client_printf(client, "[Malformed response %s]\r", str);
1211 return false;
1212 }
1213
1214 status = ((str[0] - '0') * 10) + (str[1] - '0');
1215
1216 if (status >= 10 && status <= 19) {
1217 /* input, not supported */
1218 client_printf(client, "[Input not supported (%d)]\r", status);
1219 return false;
1220 }
1221 if (status >= 20 && status <= 29) {
1222 /* success */
1223 strlcpy(client->req->mime_type, str + 3,
1224 sizeof(client->req->mime_type));
1225 return true;
1226 }
1227 if (status >= 30 && status <= 39) {
1228 /* redirect */
1229 /* TODO */
1230 return false;
1231 }
1232 if (status >= 40 && status <= 49) {
1233 /* temp fail */
1234 client_printf(client, "[Temporary server failure (%d)]\r", status);
1235 return false;
1236 }
1237 if (status >= 50 && status <= 59) {
1238 /* perm fail */
1239 client_printf(client, "[Permanent server failure (%d)]\r", status);
1240 return false;
1241 }
1242 if (status >= 60 && status <= 69) {
1243 /* auth, not supported */
1244 client_printf(client, "[Auth not supported (%d)]\r", status);
1245 return false;
1246 }
1247 client_printf(client, "[Unsupported status %d]\r", status);
1248 return false;
1249 }