💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 04004a65b0805493b16d2abca9402… captured on 2023-09-28 at 16:18:59. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 /* See LICENSE file for copyright and license details. */
1 #ifdef __linux__
2 #define _GNU_SOURCE
3 #endif
4 #ifdef sun
5 #include <port.h>
6 #endif
7 #include <pthread.h>
8 #include <netinet/in.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <strings.h>
13 #include <unistd.h>
14 #include <sys/socket.h>
15 #include <string.h>
16 #include <netdb.h>
17 #include <tls.h>
18 #include <poll.h>
19 #include <fcntl.h>
20 #include <errno.h>
21 #include <termbox.h>
22 #include <ctype.h>
23 #include <time.h>
24 #include "gemini.h"
25 #include "cert.h"
26 #include "wcwidth.h"
27 #include "display.h"
28 #include "input.h"
29 #include "sandbox.h"
30 #include "str.h"
31 #include "url.h"
32 #include "util.h"
33
34 #define MAX_CACHE 10
35 #define TIMEOUT 8
36 struct timespec timeout = {0, 10000000};
37
38 struct tls_config* config;
39 struct tls_config* config_empty;
40 struct gmi_client client;
41
42 void tb_colorline(int x, int y, uintattr_t color);
43 void* gmi_request_thread(struct gmi_tab* tab);
44
45 void fatal() {
46 tb_shutdown();
47 printf("Failed to allocate memory, terminating\n");
48 exit(0);
49 }
50
51 int fatalI() {
52 fatal();
53 return -1;
54 }
55
56 void* fatalP() {
57 fatal();
58 return NULL;
59 }
60
61 void memory_failure(struct gmi_tab* tab) {
62 if (!tab) return;
63 tab->show_error = 1;
64 snprintf(tab->error, sizeof(tab->error),
65 "memory allocation failure");
66 }
67
68 int gmi_goto(struct gmi_tab* tab, int id) {
69 id--;
70 struct gmi_page* page = &tab->page;
71 if (id < 0 || id >= page->links_count) {
72 snprintf(tab->error, sizeof(tab->error),
73 "Invalid link number, %d/%d", id, page->links_count);
74 tab->show_error = 1;
75 client.input.mode = 0;
76 return -1;
77 }
78 gmi_cleanforward(tab);
79 int ret = gmi_nextlink(tab, tab->url, page->links[id]);
80 return ret;
81 }
82
83 int gmi_goto_new(struct gmi_tab* tab, int id) {
84 id--;
85 struct gmi_page* page = &tab->page;
86 if (id < 0 || id >= page->links_count) {
87 snprintf(tab->error, sizeof(tab->error),
88 "Invalid link number, %d/%d", id, page->links_count);
89 tab->show_error = 1;
90 client.input.mode = 0;
91 return -1;
92 }
93 struct gmi_tab* old_tab = client.tab;
94 client.tab = gmi_newtab_url(NULL);
95 int ret = gmi_nextlink(client.tab, old_tab->url,
96 old_tab->page.links[id]);
97 if (ret) gmi_freetab(client.tab);
98
99 return ret;
100 }
101
102 int gmi_nextlink(struct gmi_tab* tab, char* url, char* link) {
103 int url_len = strnlen(url, MAX_URL);
104
105 if (!*link) {
106 return 0;
107 }
108 if (STARTWITH(link, "about:")) {
109 if (!strcmp(link, "about:home")) {
110 gmi_gohome(tab, 1);
111 return 0;
112 }
113 tab->show_error = 1;
114 snprintf(tab->error, sizeof(tab->error), "Invalid url");
115 return -1;
116 }
117 if (link[0] == '/' && link[1] == '/') {
118 char buf[1024];
119 strlcpy(buf, &link[2], MAX_URL - 2);
120 int len = STRNLEN(buf);
121 if (len < MAX_URL - 1 && buf[len - 1] != '/') {
122 buf[len] = '/';
123 buf[len + 1] = '\0';
124 }
125 int ret = gmi_request(tab, buf, 1);
126 if (ret < 1) return ret;
127 return ret;
128 }
129 if (link[0] == '/') {
130 if (url_len > GMI && strstr(url, "gemini://")) {
131 char* ptr = strchr(&url[GMI], '/');
132 if (ptr) *ptr = '\0';
133 }
134 char urlbuf[MAX_URL];
135 size_t l = STRLCPY(urlbuf, url);
136 if (l >= sizeof(urlbuf))
137 goto nextlink_overflow;
138 if (strlcpy(urlbuf + l, link, sizeof(urlbuf) - l) >=
139 sizeof(urlbuf) - l)
140 goto nextlink_overflow;
141 int ret = gmi_request(tab, urlbuf, 1);
142 return ret;
143 }
144 if (strstr(link, "https://") == link ||
145 strstr(link, "http://") == link ||
146 strstr(link, "gopher://") == link) {
147 #ifndef DISABLE_XDG
148 if (client.xdg && !xdg_open(link)) return -1;
149 #endif
150 tab->show_error = 1;
151 snprintf(tab->error, sizeof(tab->error),
152 "Unable to open the link");
153 return -1;
154 }
155 if (strstr(link, "gemini://") == link) {
156 int ret = gmi_request(tab, link, 1);
157 return ret;
158 }
159 char* ptr = strrchr(&url[GMI], '/');
160 if (ptr) *ptr = '\0';
161 char urlbuf[MAX_URL];
162 size_t l = STRLCPY(urlbuf, url);
163 if (l >= sizeof(urlbuf))
164 goto nextlink_overflow;
165 if (urlbuf[l-1] != '/') {
166 size_t l2 = strlcpy(urlbuf + l, "/",
167 sizeof(urlbuf) - l);
168 if (l2 >= sizeof(urlbuf) - l)
169 goto nextlink_overflow;
170 l += l2;
171 }
172 size_t l2 = strlcpy(urlbuf + l, link, sizeof(urlbuf) - l);
173 if (l2 >= sizeof(urlbuf) - l)
174 goto nextlink_overflow;
175 int ret = gmi_request(tab, urlbuf, 1);
176 return ret;
177 nextlink_overflow:
178 tab->show_error = 1;
179 snprintf(tab->error, sizeof(tab->error),
180 "Link too long, above 1024 characters");
181 return -1;
182 }
183
184 void gmi_load(struct gmi_page* page) {
185 for (int i=0; i < page->links_count; i++)
186 free(page->links[i]);
187 free(page->links);
188 page->links = NULL;
189 page->links_count = 0;
190 page->lines = 0;
191 if (!STARTWITH(page->meta, "text/gemini")) {
192 page->links = NULL;
193 page->links_count = 0;
194 page->lines = 0;
195 return;
196 }
197 int x = 0;
198 for (int c = 0; c < page->data_len; c++) {
199 if (x == 0 && page->data[c] == '=' &&
200 page->data[c + 1] == '>') {
201 c += 2;
202 for (; c < page->data_len && (page->data[c] == ' ' ||
203 page->data[c] == '\t');
204 c++) ;
205 char* url = (char*)&page->data[c];
206 c += parse_link(&page->data[c], page->data_len - c);
207 if (page->data[c - 1] == 127)
208 c--;
209 char save = page->data[c];
210 page->data[c] = '\0';
211 void *ptr = NULL;
212 while (!ptr) {
213 ptr = page->links ? realloc(page->links,
214 sizeof(char*) * (page->links_count+1)):
215 malloc(sizeof(char*));
216 if (!ptr) sleep(1);
217 }
218 page->links = ptr;
219
220 if (url[0] == '\0') {
221 page->links[page->links_count] = NULL;
222 page->data[c] = save;
223 continue;
224 }
225 int len = strnlen(url, MAX_URL);
226 ptr = NULL;
227 while (!ptr) {
228 ptr = malloc(len+2);
229 if (!ptr) sleep(1);
230 }
231 page->links[page->links_count] = ptr;
232 memcpy(page->links[page->links_count], url, len+1);
233 for (int i = 0; page->links[page->links_count][i] &&
234 i < len; ) {
235 uint32_t ch;
236 int len;
237 len = tb_utf8_char_to_unicode(&ch,
238 &page->links[page->links_count][i]);
239 if (ch < ' ')
240 page->links[page->links_count][i] = 0;
241 i += len;
242 }
243 page->links_count++;
244 page->data[c] = save;
245 }
246 if (page->data[c] == '\n') {
247 page->lines++;
248 x = 0;
249 continue;
250 }
251 x++;
252 }
253 }
254
255 int gmi_render(struct gmi_tab* tab) {
256 pthread_mutex_lock(&tab->render_mutex);
257 #include "img.h"
258 #ifdef TERMINAL_IMG_VIEWER
259 if (STARTWITH(tab->page.meta, "image/")) {
260 if (!tab->page.img.tried) {
261 char* ptr = strchr(tab->page.data, '\n');
262 if (!ptr) {
263 tb_printf(2, -tab->scroll,
264 TB_DEFAULT, TB_DEFAULT,
265 "Invalid data: new line not found");
266 tab->page.img.tried = 1;
267 pthread_mutex_unlock(&tab->render_mutex);
268 return 1;
269 }
270 ptr++;
271 if (tab->page.img.data) {
272 stbi_image_free(tab->page.img.data);
273 tab->page.img.data = NULL;
274 }
275 tab->page.img.data =
276 stbi_load_from_memory((unsigned char*)ptr,
277 tab->page.data_len -
278 (int)(ptr-tab->page.data),
279 &tab->page.img.w,
280 &tab->page.img.h,
281 &tab->page.img.channels, 3);
282 if (!tab->page.img.data) {
283 tb_printf(2, -tab->scroll,
284 TB_DEFAULT, TB_DEFAULT,
285 "Failed to decode image: %s;",
286 tab->page.meta);
287 tab->page.img.tried = 1;
288 pthread_mutex_unlock(&tab->render_mutex);
289 return 1;
290 }
291
292 tab->page.img.tried = 1;
293 }
294 if (tab->page.img.data) {
295 img_display(tab->page.img.data,
296 tab->page.img.w, tab->page.img.h,
297 client.tabs_count>1);
298 pthread_mutex_unlock(&tab->render_mutex);
299 return 1;
300 }
301 }
302 #endif
303 int text = 0;
304 if (!STARTWITH(tab->page.meta, "text/gemini")) {
305 if (!STARTWITH(tab->page.meta, "text/")) {
306 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,
307 "Unable to render format : %s",
308 tab->page.meta);
309 pthread_mutex_unlock(&tab->render_mutex);
310 return 1;
311 }
312 text = 1;
313 }
314 int line = 0;
315 int x = 0;
316 int links = 0;
317 uintattr_t color = TB_DEFAULT;
318 int start = 1;
319 int ignore = 0;
320 int h = tb_height() - 2 - (client.tabs_count > 1);
321 char* search = client.input.field[0] == '/' ?
322 &client.input.field[1] : tab->search.entry;
323 int search_len = search ?
324 strnlen(search, sizeof(client.input.field) - 1) : 0;
325 if (!search_len) search = NULL;
326 int highlight = 0;
327 int hlcolor = YELLOW;
328 if (tab->search.cursor >= tab->search.count)
329 tab->search.cursor = 0;
330 else if (tab->search.cursor < 0)
331 tab->search.cursor = tab->search.count - 1;
332 int previous_count = tab->search.count;
333 tab->search.count = 0;
334 char* ptr = tab->page.no_header?NULL:strstr(tab->page.data, "\r\n");
335 if (ptr && ptr > strstr(tab->page.data, "\n")) ptr = NULL;
336 line++;
337 int w = tb_width();
338 for (int c = ptr?ptr-tab->page.data+2:0; c < tab->page.data_len; c++) {
339 if (x == 0 && tab->page.data[c] == '\n') {
340 if (c+1 != tab->page.data_len)
341 line += 1;
342 continue;
343 }
344 if (tab->page.data[c] == '\t') {
345 x += 8 - x%8;
346 continue;
347 }
348 if (tab->page.data[c] == '\r') continue;
349 if (!text && start &&
350 tab->page.data[c] == '`' &&
351 tab->page.data[c + 1] == '`' &&
352 tab->page.data[c + 2] == '`') {
353 ignore = !ignore;
354 c += 3;
355 continue;
356 }
357 if (ignore)
358 color = ORANGE;
359
360 if (!ignore && !text) {
361 for (int i=0;
362 start && tab->page.data[c + i] == '#' && i<3;
363 i++) {
364 if (tab->page.data[c + i + 1] != '#') {
365 color = RED + i;
366 break;
367 }
368 }
369 if (start && tab->page.data[c] == '*' &&
370 tab->page.data[c + 1] == ' ') {
371 color = ITALIC|CYAN;
372 }
373 if (start && tab->page.data[c] == '>') {
374 color = ITALIC|MAGENTA;
375 }
376 if (start && tab->page.data[c] == '=' &&
377 tab->page.data[c + 1] == '>') {
378 char buf[32];
379 int len = snprintf(buf, sizeof(buf),
380 "[%d]", links + 1);
381 if (line-1>=(tab->scroll>=0?tab->scroll:0) &&
382 line-tab->scroll <= tb_height()-2) {
383 tb_print(x+2, line-1-tab->scroll,
384 links+1 ==
385 tab->selected?RED:BLUE,
386 TB_DEFAULT, buf);
387 }
388 x += len;
389 c += 2;
390
391 while (
392 (tab->page.data[c]==' ' ||
393 tab->page.data[c]=='\t') &&
394 tab->page.data[c]!='\n' &&
395 tab->page.data[c]!='\0') c++;
396
397 int initial = c;
398 while (tab->page.data[c]!=' ' &&
399 tab->page.data[c]!='\t' &&
400 tab->page.data[c]!='\n' &&
401 tab->page.data[c]!='\0') c++;
402
403 while (
404 (tab->page.data[c]==' ' ||
405 tab->page.data[c]=='\t') &&
406 tab->page.data[c]!='\n' &&
407 tab->page.data[c]!='\0') c++;
408
409 if (tab->page.data[c]=='\n' ||
410 tab->page.data[c]=='\0')
411 c = initial;
412 x += 3;
413 if ((links + 1) / 10) x--;
414 if ((links + 1) / 100) x--;
415 if ((links + 1) / 1000) x--;
416 links++;
417 }
418 }
419
420 if (search &&
421 !strncasecmp(&tab->page.data[c], search, search_len)) {
422 if (tab->search.count == (tab->search.cursor?
423 (tab->search.cursor - 1):
424 (previous_count - 1)))
425 tab->search.pos[0] = line;
426 if (tab->search.count == tab->search.cursor)
427 hlcolor++;
428 if (tab->search.cursor == previous_count - 1 &&
429 tab->search.count == 0)
430 tab->search.pos[1] = line;
431 if (tab->search.count == tab->search.cursor + 1) {
432 hlcolor--;
433 tab->search.pos[1] = line;
434 }
435 highlight += search_len;
436 tab->search.count++;
437 }
438 if (tab->page.data[c] == '\n' || tab->page.data[c] == ' ' ||
439 x+4 >= w) {
440 int end = 0;
441 if (x+4 >= w)
442 end = 1;
443 int newline = (tab->page.data[c] == '\n' || x+4 >= w);
444 for (int i = 1; ; i++) {
445 if (x + i == w - 1) break;
446 if (i > w - 4) {
447 newline = 0;
448 break;
449 }
450 if (c + i >= tab->page.data_len ||
451 tab->page.data[c+i] == ' ' ||
452 tab->page.data[c+i] == '\n' ||
453 tab->page.data[c+i] == '\0')
454 break;
455 if (w - 4 <= x + i) newline = 1;
456 }
457 if (newline) {
458 if (c != tab->page.data_len)
459 line += 1 + (x + 1)/w;
460 if (tab->page.data[c] == '\n') {
461 color = TB_DEFAULT;
462 start = 1;
463 } else c--;
464 if (c + 1 >= tab->page.data_len) continue;
465 if (tab->page.data[c + 1] == ' ') c++;
466 x = 0;
467 continue;
468 } else if (end) {
469 c++;
470 x++;
471 }
472 }
473 uint32_t ch = 0;
474 int size = tb_utf8_char_to_unicode(&ch,
475 &tab->page.data[c]) - 1;
476 if (size > 0)
477 c += tb_utf8_char_to_unicode(&ch,
478 &tab->page.data[c]) - 1;
479 else if (ch < 32) ch = '?';
480
481 int wc = mk_wcwidth(ch);
482 if (wc < 0) wc = 0;
483 if (line - 1 >= tab->scroll &&
484 (line - tab->scroll <= tb_height() - 2) && ch != '\t') {
485 if (wc == 1)
486 tb_set_cell(x + 2, line - 1 - tab->scroll,
487 ch, color,
488 highlight?hlcolor:TB_DEFAULT);
489 else
490 tb_set_cell_ex(x + 2, line - 1 - tab->scroll,
491 &ch, wc, color,
492 highlight?hlcolor:TB_DEFAULT);
493 }
494 if (highlight > 0)
495 highlight--;
496
497 x += wc;
498 start = 0;
499 }
500 line++;
501 h += (client.tabs_count > 1);
502 if (h > line) {
503 pthread_mutex_unlock(&tab->render_mutex);
504 return line;
505 }
506 int size = (h == line?(h - 1):(h / (line - h)));
507 if (size == h) size--;
508 if (size < 1) size = 1;
509 int H = (line - h + 1 + (client.tabs_count > 1));
510 int pos = (tab->scroll + (client.tabs_count < 1)) * h / (H?H:1)
511 + (client.tabs_count>1);
512 if (pos >= h) pos = h - size;
513 if (pos < 0) pos = 0;
514 if (tab->scroll+h == line) pos = h - size;
515 for (int y = (client.tabs_count > 1);
516 y < h + (client.tabs_count > 1); y++) {
517 if (y >= pos && y < pos + size)
518 tb_set_cell(w - 1, y, ' ', TB_DEFAULT, CYAN);
519 else
520 tb_set_cell(w - 1, y, ' ', TB_DEFAULT, BLACK);
521 }
522 pthread_mutex_unlock(&tab->render_mutex);
523 return line;
524 }
525
526 void gmi_addtohistory(struct gmi_tab* tab) {
527 if (!(!tab->history || (tab->history && !tab->history->next)))
528 return;
529 gmi_cleanforward(tab);
530 struct gmi_link* link = malloc(sizeof(struct gmi_link));
531 if (!link) {
532 memory_failure(tab);
533 return;
534 }
535 link->next = NULL;
536 link->prev = tab->history;
537 if (link->prev)
538 link->prev->scroll = tab->scroll;
539 link->page = tab->page;
540 link->cached = 1;
541 STRLCPY(link->url, tab->url);
542 tab->history = link;
543 if (link->prev)
544 link->prev->next = tab->history;
545 }
546
547 void gmi_cleanforward(struct gmi_tab* tab) {
548 if (!tab->history)
549 return;
550 struct gmi_link* link = tab->history->next;
551 while (link) {
552 struct gmi_link* ptr = link->next;
553 bzero(link->url, sizeof(link->url));
554 if (link->cached) {
555 gmi_freepage(&link->page);
556 link->cached = 0;
557 }
558 free(link);
559 link = ptr;
560 }
561 tab->history->next = NULL;
562 }
563
564 void gmi_freepage(struct gmi_page* page) {
565 if (!page) return;
566 #ifdef TERMINAL_IMG_VIEWER
567 if (page->img.data)
568 stbi_image_free(page->img.data);
569 #endif
570 free(page->data);
571 for (int i=0; i < page->links_count; i++)
572 free(page->links[i]);
573 free(page->links);
574 bzero(page, sizeof(struct gmi_page));
575 }
576
577 void gmi_freetab(struct gmi_tab* tab) {
578 if (!tab) return;
579 tab->request.state = STATE_CANCEL;
580 int signal = 0xFFFFFFFF;
581 send(tab->thread.pair[1], &signal, sizeof(signal), 0);
582 close(tab->thread.pair[0]);
583 close(tab->thread.pair[1]);
584 if (tab->history) {
585 struct gmi_link* link = tab->history->next;
586 while (link) {
587 struct gmi_link* ptr = link->next;
588 if (link->cached) {
589 gmi_freepage(&link->page);
590 link->cached = 0;
591 }
592 bzero(link->url, sizeof(link->url));
593 free(link);
594 link = ptr;
595 }
596 link = tab->history;
597 while (link) {
598 struct gmi_link* ptr = link->prev;
599 if (link->cached) {
600 gmi_freepage(&link->page);
601 link->cached = 0;
602 }
603 bzero(link->url, sizeof(link->url));
604 free(link);
605 link = ptr;
606 }
607 }
608 if (tab->prev) tab->prev->next = tab->next;
609 if (tab->next) tab->next->prev = tab->prev;
610 struct gmi_tab* prev = tab->prev;
611 if (!prev) prev = tab->next;
612 pthread_mutex_destroy(&tab->render_mutex);
613 if ((signed)tab->thread.started)
614 pthread_join(tab->thread.thread, NULL);
615 #ifdef TERMINAL_IMG_VIEWER
616 if (tab->page.img.data)
617 free(tab->page.img.data);
618 #endif
619 free(tab);
620 client.tab = prev;
621 client.tabs_count--;
622 }
623
624 char home_page[] =
625 "20 text/gemini\r\n# Vgmi - " VERSION "\n\n" \
626 "A Gemini client written in C with vim-like keybindings\n\n" \
627 "## Bookmarks\n\n" \
628 "%s\n" \
629 "## Keybindings\n\n" \
630 "* k - Scroll up\n" \
631 "* j - Scroll down\n" \
632 "* gT - Switch to the previous tab\n" \
633 "* gt - Switch to the next tab\n" \
634 "* H - Go back in the history\n" \
635 "* L - Go forward in the history\n" \
636 "* gg - Go at the top of the page\n" \
637 "* G - Go at the bottom of the page\n" \
638 "* / - Open search mode\n" \
639 "* : - Open input mode\n" \
640 "* u - Open input mode with the current url\n" \
641 "* f - Show the history\n" \
642 "* r - Reload the page\n" \
643 "* [number]Tab - Select a link\n" \
644 "* Tab - Follow the selected link\n" \
645 "* Shift+Tab - Open the selected link in a new tab\n" \
646 "* Del - Delete the selected link from the bookmarks\n" \
647 "\nYou can prefix a movement key with a number to repeat it.\n\n" \
648 "## Commands\n\n" \
649 "* :q - Close the current tab\n" \
650 "* :qa - Close all tabs, exit the program\n" \
651 "* :o [url] - Open an url\n" \
652 "* :s [search] - Search the Geminispace using geminispace.info\n" \
653 "* :nt [url] - Open a new tab, the url is optional\n" \
654 "* :add [name] - Add the current url to the bookmarks, the name is optional\n"\
655 "* :[number] - Scroll to the line number\n" \
656 "* :gencert - Generate a certificate for the current capsule\n" \
657 "* :forget <host> - Forget the certificate for an host\n" \
658 "* :ignore <host> - Ignore expiration for the host certificate\n" \
659 "* :download [name] - Download the current page, the name is optional\n"
660 "* :exec - Open the last downloaded file";
661
662 void gmi_newbookmarks() {
663 int len;
664 const char geminispace[] = "gemini://geminispace.info Geminispace";
665 const char gemigit[] = "gemini://gemini.rmf-dev.com Gemigit";
666 client.bookmarks = malloc(sizeof(char*) * 3);
667 if (!client.bookmarks) goto fail_malloc;
668
669 len = sizeof(geminispace);
670 client.bookmarks[0] = malloc(len);
671 if (!client.bookmarks[0]) goto fail_malloc;
672 strlcpy(client.bookmarks[0], geminispace, len);
673
674 len = sizeof(gemigit);
675 client.bookmarks[1] = malloc(len);
676 if (!client.bookmarks[1]) goto fail_malloc;
677 strlcpy(client.bookmarks[1], gemigit, len);
678
679 client.bookmarks[2] = NULL;
680 return;
681 fail_malloc:
682 fatal();
683 }
684
685 int gmi_loadbookmarks() {
686 int fd = openat(config_fd, "bookmarks.txt", O_RDONLY);
687 if (fd < 0)
688 return -1;
689 FILE* f = fdopen(fd, "rb");
690 if (!f)
691 return -1;
692 #ifdef SANDBOX_FREEBSD
693 if (makefd_readonly(fd)) {
694 fclose(f);
695 return -1;
696 }
697 #endif
698 fseek(f, 0, SEEK_END);
699 size_t len = ftell(f);
700 fseek(f, 0, SEEK_SET);
701 char* data = malloc(len);
702 if (!data) {
703 memory_failure(client.tab);
704 fclose(f);
705 return -1;
706 }
707 if (len != fread(data, 1, len, f)) {
708 fclose(f);
709 return -1;
710 }
711 fclose(f);
712 char* ptr = data;
713 long n = 0;
714 while (++ptr && ptr < data+len) if (*ptr == '\n') n++;
715 client.bookmarks = malloc(sizeof(char*) * (n + 1));
716 if (!client.bookmarks) {
717 memory_failure(client.tab);
718 return -1;
719 }
720 client.bookmarks[n] = NULL;
721 n = 0;
722 ptr = data;
723 char* str = data;
724 while (++ptr && ptr < data+len) {
725 if (*ptr != '\n') continue;
726 *ptr = '\0';
727 client.bookmarks[n] = malloc(ptr-str+1);
728 if (!client.bookmarks[n]) {
729 memory_failure(client.tab);
730 break;
731 }
732 strlcpy(client.bookmarks[n], str, ptr - str + 1);
733 n++;
734 str = ptr + 1;
735 }
736 free(data);
737 return 0;
738 }
739
740 void sanitize(char* str, size_t len) {
741 int n = 0;
742 for (size_t i = 0; i < len; i += n) {
743 n = tb_utf8_char_length(str[i]);
744 if (n > 1 || str[i] >= 32) continue;
745 str[i] = '\0';
746 break;
747 }
748 }
749
750 void gmi_gettitle(struct gmi_page* page, const char* url) {
751 if (page->title_cached) return;
752 page->title[0] = '\0';
753 page->title_cached = 1;
754 if (!STARTWITH(page->meta, "text/gemini")) {
755 goto use_url;
756 }
757 int start = -1;
758 int end = -1;
759 int line_start = 1;
760 for (int i = 0; i < page->data_len; i++) {
761 if (line_start && start == -1 && page->data[i] == '#') {
762 for (int j = i+1; j < page->data_len; j++) {
763 if (j && page->data[j-1] == '#' &&
764 page->data[j] == '#')
765 break;
766 if (page->data[j] != ' ') {
767 start = j;
768 break;
769 }
770 }
771 }
772 line_start = 0;
773 if (page->data[i] == '\n')
774 line_start = 1;
775 if (start != -1 && page->data[i] == '\n') {
776 end = i;
777 break;
778 }
779 }
780 if (start == -1 || end == -1)
781 goto use_url;
782 size_t len = end - start + 1;
783 len = strlcpy(page->title, &page->data[start],
784 len < sizeof(page->title)?
785 len : sizeof(page->title));
786 sanitize(page->title, len);
787 return;
788 use_url:
789 if (!url) {
790 size_t len = STRLCPY(page->title, page->meta);
791 sanitize(page->title, len);
792 return;
793 }
794 char* str = strrchr(url, '/');
795 if (!str) {
796 len = STRLCPY(page->title, url);
797 goto sanitize;
798 }
799 if (str[1] != '\0')
800 str++;
801 len = STRLCPY(page->title, str);
802 sanitize:
803 for (size_t i = 0; i < len; i++)
804 if (page->title[i] == '?')
805 page->title[i] = 0;
806 }
807
808 int gmi_removebookmark(int index) {
809 index--;
810 if (index < 0) return -1;
811 int fail = -1;
812 for (int i = 0; client.bookmarks[i]; i++) {
813 if (i == index) {
814 free(client.bookmarks[i]);
815 fail = 0;
816 }
817 if (!fail)
818 client.bookmarks[i] = client.bookmarks[i+1];
819 }
820 return fail;
821 }
822
823 void gmi_addbookmark(struct gmi_tab* tab, char* url, char* title) {
824 if (!strcmp(url, "about:home")) {
825 snprintf(tab->error, sizeof(tab->error),
826 "Cannot add the new tab page to the bookmarks");
827 tab->show_error = 1;
828 return;
829 }
830 int title_len = 0;
831 if (!title) {
832 gmi_gettitle(&tab->page, tab->url);
833 title = tab->page.title;
834 title_len = STRNLEN(tab->page.title);
835 if (!title_len) {
836 title_len = sizeof("no title");
837 title = "no title";
838 }
839 } else title_len = strnlen(title, 128);
840 long n = 0;
841 while (client.bookmarks[n]) n++;
842 void* ptr = realloc(client.bookmarks, sizeof(char*) * (n + 2));
843 if (!ptr) {
844 memory_failure(tab);
845 return;
846 }
847 client.bookmarks = ptr;
848 int len = strnlen(url, MAX_URL) + title_len + 2;
849 client.bookmarks[n] = malloc(len);
850 if (!client.bookmarks[n]) {
851 memory_failure(tab);
852 return;
853 }
854 if (title)
855 snprintf(client.bookmarks[n], len, "%s %s", url, title);
856 else
857 snprintf(client.bookmarks[n], len, "%s", url);
858 client.bookmarks[n + 1] = NULL;
859 gmi_savebookmarks();
860 }
861
862 int gmi_savebookmarks() {
863 #ifdef SANDBOX_SUN
864 int fd = wr_pair[1];
865 if (send(fd, &WR_BOOKMARKS, sizeof(SBC), 0) != sizeof(SBC))
866 return -1;
867 #else
868 int fd = openat(config_fd, "bookmarks.txt",
869 O_CREAT|O_WRONLY|O_CLOEXEC|O_TRUNC, 0600);
870 if (fd < 0) {
871 printf("Failed to write bookmarks, %s\n", strerror(errno));
872 return -1;
873 }
874 #ifdef SANDBOX_FREEBSD
875 if (makefd_writeonly(fd)) {
876 close(fd);
877 return -1;
878 }
879 #endif
880 #endif
881 for (int i = 0; client.bookmarks[i]; i++) {
882 write(fd, client.bookmarks[i], strlen(client.bookmarks[i]));
883 char c = '\n';
884 write(fd, &c, 1);
885 }
886 #ifdef SANDBOX_SUN
887 send(fd, &WR_END, sizeof(SBC), 0);
888 #else
889 close(fd);
890 #endif
891 return 0;
892 }
893
894 char* gmi_getbookmarks(int* len) {
895 char* data = NULL;
896 int n = 0;
897 for (int i = 0; client.bookmarks[i]; i++) {
898 char line[2048];
899 long length = snprintf(line, sizeof(line), "=>%s\n ",
900 client.bookmarks[i]);
901 void *ptr = realloc(data, n+length+1);
902 if (!ptr) {
903 *len = n;
904 return data;
905 }
906 data = ptr;
907 strlcpy(&data[n], line, length);
908 n += length-1;
909 }
910 *len = n;
911 return data;
912 }
913
914 void gmi_gohome(struct gmi_tab* tab, int add) {
915 STRLCPY(tab->url, "about:home");
916 int bm = 0;
917 char* data = gmi_getbookmarks(&bm);
918 pthread_mutex_lock(&tab->render_mutex);
919 tab->request.data = malloc(sizeof(home_page) + bm);
920 if (!tab->request.data) {
921 memory_failure(tab);
922 pthread_mutex_unlock(&tab->render_mutex);
923 return;
924 }
925 bzero(tab->request.data, sizeof(home_page) + bm);
926
927 tab->request.recv = snprintf(tab->request.data, sizeof(home_page) + bm,
928 home_page, data ? data : "");
929 free(data);
930
931 STRLCPY(tab->request.meta, "text/gemini");
932
933 if (!add)
934 gmi_freepage(&tab->page);
935 bzero(&tab->page, sizeof(struct gmi_page));
936 tab->page.data = tab->request.data;
937 tab->page.data_len = tab->request.recv;
938 tab->page.code = 20;
939 STRLCPY(tab->page.meta, tab->request.meta);
940 gmi_load(&tab->page);
941 if (add)
942 gmi_addtohistory(tab);
943 else if (tab->history) {
944 tab->history->page = tab->page;
945 tab->history->cached = 1;
946 }
947 tab->scroll = -1;
948 tab->request.data = NULL;
949 pthread_mutex_unlock(&tab->render_mutex);
950 }
951
952 struct gmi_tab* gmi_newtab() {
953 return gmi_newtab_url("about:home");
954 }
955
956 struct gmi_tab* gmi_newtab_url(const char* url) {
957
958 if (url && strcmp(url, "about:home")) {
959 struct gmi_tab tmp;
960 int proto = parse_url(url,
961 tmp.request.host, sizeof(tmp.request.host),
962 tmp.request.url, sizeof(tmp.request.url),
963 &tmp.request.port);
964 switch (proto) {
965 case PROTO_GEMINI:
966 break;
967 case PROTO_HTTP:
968 case PROTO_HTTPS:
969 case PROTO_GOPHER:
970 xdg_request(url);
971 /* fallthrough */
972 default:
973 return client.tab;
974 }
975 }
976
977 client.tabs_count++;
978 if (client.tab) {
979 struct gmi_tab* next = client.tab->next;
980 client.tab->next = malloc(sizeof(struct gmi_tab));
981 if (!client.tab->next) {
982 memory_failure(client.tab);
983 return client.tab;
984 }
985 bzero(client.tab->next, sizeof(struct gmi_tab));
986 client.tab->next->next = next;
987 client.tab->next->prev = client.tab;
988 client.tab = client.tab->next;
989 if (next)
990 next->prev = client.tab;
991 } else {
992 client.tab = malloc(sizeof(struct gmi_tab));
993 if (!client.tab) return fatalP();
994 bzero(client.tab, sizeof(struct gmi_tab));
995 }
996 pthread_mutex_init(&client.tab->render_mutex, NULL);
997
998 if (socketpair(AF_UNIX, SOCK_STREAM, 0, client.tab->thread.pair))
999 return client.tab;
1000 pthread_create(&client.tab->thread.thread, NULL,
1001 (void *(*)(void *))gmi_request_thread,
1002 (void*)client.tab);
1003 client.tab->thread.started = 1;
1004 if (url)
1005 gmi_request(client.tab, url, 1);
1006
1007 client.tab->scroll = -1;
1008 return client.tab;
1009 }
1010
1011 int gmi_request_init(struct gmi_tab* tab, const char* url, int add) {
1012 if (!tab) return -1;
1013 if (!strcmp(url, "about:home")) {
1014 gmi_gohome(tab, add);
1015 return tab->page.data_len;
1016 }
1017 tab->show_error = 0;
1018 if (tab->history) tab->history->scroll = tab->scroll;
1019 tab->selected = 0;
1020 tab->request.host[0] = '\0';
1021 int proto = parse_url(url,
1022 tab->request.host, sizeof(tab->request.host),
1023 tab->request.url, sizeof(tab->request.url),
1024 &tab->request.port);
1025
1026 if (proto == -2) {
1027 tab->show_error = 1;
1028 snprintf(tab->error, sizeof(tab->error),
1029 "Link too long, above 1024 characters");
1030 return -1;
1031 }
1032 if (proto == PROTO_FILE) {
1033 return gmi_loadfile(tab, &tab->request.url[P_FILE]);
1034 }
1035 if (proto != PROTO_GEMINI) {
1036 #ifndef DISABLE_XDG
1037 if (client.xdg && !xdg_open((char*)url)) return 1;
1038 #endif
1039 tab->show_error = 1;
1040 snprintf(tab->error, sizeof(tab->error),
1041 "Unable to open the link");
1042 return -1;
1043 }
1044 if (tab->request.tls)
1045 tls_reset(tab->request.tls);
1046 else
1047 tab->request.tls = tls_client();
1048 if (!tab->request.tls) {
1049 snprintf(tab->error, sizeof(tab->error),
1050 "Failed to initialize TLS");
1051 tab->show_error = 1;
1052 return -1;
1053 }
1054
1055 int cert = cert_getcert(tab->request.host, 0);
1056 if (cert >= 0 &&
1057 tls_config_set_keypair_mem(config,
1058 (unsigned char*)client.certs[cert].crt,
1059 client.certs[cert].crt_len,
1060 (unsigned char*)client.certs[cert].key,
1061 client.certs[cert].key_len))
1062 {
1063 tab->show_error = 1;
1064 snprintf(tab->error, sizeof(tab->error), "tls error: %s",
1065 tls_config_error(config));
1066 return -1;
1067 }
1068
1069 if (tls_configure(tab->request.tls, cert>=0?config:config_empty)) {
1070 snprintf(tab->error, sizeof(tab->error),
1071 "Failed to configure TLS");
1072 tab->show_error = 1;
1073 return -1;
1074 }
1075 tab->request.state = STATE_REQUESTED;
1076 return 0;
1077 }
1078
1079 #ifdef __linux
1080 void dns_async(union sigval sv) {
1081 struct gmi_tab* tab = sv.sival_ptr;
1082 tab->request.resolved = 1;
1083 }
1084 #endif
1085
1086 int gmi_request_dns(struct gmi_tab* tab) {
1087 char host[1024];
1088 if (idn_to_ascii(tab->request.host, sizeof(tab->request.host),
1089 host, sizeof(host))) {
1090 snprintf(tab->error, sizeof(tab->error),
1091 "Invalid domain name: %s", tab->request.host);
1092 tab->show_error = 1;
1093 return -1;
1094 }
1095 STRLCPY(tab->request.host, host);
1096 #if defined(__linux__) && !defined(__MUSL__)
1097 tab->request.gaicb_ptr = malloc(sizeof(struct gaicb));
1098 if (!tab->request.gaicb_ptr) {
1099 memory_failure(tab);
1100 return -1;
1101 }
1102 bzero(tab->request.gaicb_ptr, sizeof(struct gaicb));
1103 tab->request.gaicb_ptr->ar_name = tab->request.host;
1104 tab->request.resolved = 0;
1105 struct sigevent sevp;
1106 bzero(&sevp, sizeof(sevp));
1107 sevp.sigev_notify = SIGEV_THREAD;
1108 sevp.sigev_notify_function = dns_async;
1109 sevp.sigev_value.sival_ptr = tab;
1110 int ret = getaddrinfo_a(GAI_NOWAIT, &tab->request.gaicb_ptr, 1, &sevp);
1111 if (ret) {
1112 snprintf(tab->error, sizeof(tab->error),
1113 "Unable request domain name: %s", tab->request.host);
1114 tab->show_error = 1;
1115 return -1;
1116 }
1117
1118 long start = time(NULL);
1119 for (int i=0; !tab->request.resolved; i++) {
1120 if (tab->request.state == STATE_CANCEL) break;
1121 if (time(NULL) - start > TIMEOUT) break;
1122 nanosleep(&timeout, NULL);
1123 }
1124
1125 if (tab->request.resolved != 1 ||
1126 tab->request.gaicb_ptr->ar_result == NULL) {
1127 gai_cancel(tab->request.gaicb_ptr);
1128 free(tab->request.gaicb_ptr);
1129 snprintf(tab->error, sizeof(tab->error),
1130 "Unknown domain name: %s", tab->request.host);
1131 tab->show_error = 1;
1132 return -1;
1133 }
1134 struct addrinfo *result = tab->request.gaicb_ptr->ar_result;
1135 #else
1136 struct addrinfo hints, *result;
1137 bzero(&hints, sizeof(hints));
1138 hints.ai_family = AF_INET;
1139 hints.ai_socktype = SOCK_STREAM;
1140 hints.ai_flags |= AI_CANONNAME;
1141 errno = 0;
1142
1143 if ((getaddrinfo(tab->request.host, NULL, &hints, &result))) {
1144 snprintf(tab->error, sizeof(tab->error),
1145 "Unknown domain name: %s, %s",
1146 tab->request.host, strerror(errno));
1147 tab->show_error = 1;
1148 return -1;
1149 }
1150 #endif
1151
1152 struct sockaddr_in addr4;
1153 struct sockaddr_in6 addr6;
1154 bzero(&addr4, sizeof(addr4));
1155 bzero(&addr6, sizeof(addr6));
1156
1157 int error = 0;
1158 if (result->ai_family == AF_INET) {
1159 addr4.sin_addr =
1160 ((struct sockaddr_in*)result->ai_addr)->sin_addr;
1161 addr4.sin_family = AF_INET;
1162 addr4.sin_port = htons(tab->request.port);
1163 tab->request._addr.addr4 = addr4;
1164 tab->request.addr =
1165 (struct sockaddr*)&tab->request._addr.addr4;
1166 }
1167 else if (result->ai_family == AF_INET6) {
1168 addr6.sin6_addr =
1169 ((struct sockaddr_in6*)result->ai_addr)->sin6_addr;
1170 addr6.sin6_family = AF_INET6;
1171 addr6.sin6_port = htons(tab->request.port);
1172 tab->request._addr.addr6 = addr6;
1173 tab->request.addr =
1174 (struct sockaddr*)&tab->request._addr.addr6;
1175 } else {
1176 snprintf(tab->error, sizeof(tab->error),
1177 "Unexpected error, invalid address family %s",
1178 tab->request.host);
1179 error = 1;
1180 }
1181 tab->request.family = result->ai_family;
1182 freeaddrinfo(result);
1183 #ifdef __linux__
1184 free(tab->request.gaicb_ptr);
1185 #endif
1186 if (error) {
1187 tab->request.state = STATE_CANCEL;
1188 return -1;
1189 }
1190
1191 tab->request.state = STATE_DNS;
1192 return 0;
1193 }
1194
1195 int gmi_request_connect(struct gmi_tab* tab) {
1196
1197 tab->request.socket = socket(tab->request.family, SOCK_STREAM, 0);
1198 if (tab->request.socket == -1) {
1199 snprintf(tab->error, sizeof(tab->error),
1200 "Failed to create socket");
1201 return -1;
1202 }
1203 int flags = fcntl(tab->request.socket, F_GETFL);
1204 if (flags == -1 ||
1205 fcntl(tab->request.socket, F_SETFL, flags|O_NONBLOCK) == -1) {
1206 snprintf(tab->error, sizeof(tab->error),
1207 "Failed to create non-blocking socket");
1208 return -1;
1209 }
1210
1211 int addr_size = (tab->request.family == AF_INET)?
1212 sizeof(struct sockaddr_in):
1213 sizeof(struct sockaddr_in6);
1214
1215 int connected = 0;
1216 int failed = connect(tab->request.socket,
1217 tab->request.addr, addr_size);
1218 failed = failed?(errno != EAGAIN && errno != EWOULDBLOCK &&
1219 errno != EINPROGRESS && errno != EALREADY &&
1220 errno != 0):failed;
1221 while (!failed) {
1222 struct pollfd fds[2];
1223 fds[0].fd = tab->thread.pair[0];
1224 fds[0].events = POLLIN;
1225 fds[1].fd = tab->request.socket;
1226 fds[1].events = POLLOUT;
1227 int count = poll(fds, 2, TIMEOUT * 1000);
1228 if (count < 1 || fds[1].revents != POLLOUT ||
1229 tab->request.state == STATE_CANCEL) break;
1230 int value;
1231 socklen_t len = sizeof(value);
1232 int ret = getsockopt(tab->request.socket, SOL_SOCKET,
1233 SO_ERROR, &value, &len);
1234 connected = (value == 0 && ret == 0);
1235 break;
1236 }
1237
1238 if (!connected) {
1239 snprintf(tab->error, sizeof(tab->error),
1240 "Connection to %s timed out : %s",
1241 tab->request.host, strerror(errno));
1242 return -1;
1243 }
1244
1245 if (tls_connect_socket(tab->request.tls, tab->request.socket,
1246 tab->request.host)) {
1247 snprintf(tab->error, sizeof(tab->error),
1248 "Unable to connect to: %s : %s",
1249 tab->request.host, tls_error(tab->request.tls));
1250 return -1;
1251 }
1252 return 0;
1253 }
1254
1255 int gmi_request_handshake(struct gmi_tab* tab) {
1256 if (tls_connect_socket(tab->request.tls,
1257 tab->request.socket,
1258 tab->request.host)) {
1259 snprintf(tab->error, sizeof(tab->error),
1260 "Unable to connect to: %s : %s",
1261 tab->request.host, tls_error(tab->request.tls));
1262 return -1;
1263 }
1264 if (tab->request.state == STATE_CANCEL) return -1;
1265 int ret = 0;
1266 time_t start = time(0);
1267 while ((ret = tls_handshake(tab->request.tls))) {
1268 if (time(NULL) - start > TIMEOUT ||
1269 tab->request.state == STATE_CANCEL ||
1270 (ret < 0 &&
1271 ret != TLS_WANT_POLLIN &&
1272 ret != TLS_WANT_POLLOUT))
1273 break;
1274 if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
1275 nanosleep(&timeout, NULL);
1276 }
1277 if (ret) {
1278 snprintf(tab->error, sizeof(tab->error),
1279 "Failed to handshake: %s (%s)",
1280 tls_error(tab->request.tls),
1281 tab->request.host);
1282 return -1;
1283 }
1284 if (tab->request.state == STATE_CANCEL) return -1;
1285 ret = cert_verify(tab->request.host,
1286 tls_peer_cert_hash(tab->request.tls),
1287 tls_peer_cert_notbefore(tab->request.tls),
1288 tls_peer_cert_notafter(tab->request.tls));
1289 switch (ret) {
1290 case 0: // success
1291 break;
1292 case -1: // invalid certificate
1293 snprintf(tab->error, sizeof(tab->error),
1294 "Failed to verify server certificate for %s" \
1295 "(The certificate changed)",
1296 tab->request.host);
1297 return -1;
1298 case -3: // sandbox error
1299 snprintf(tab->error, sizeof(tab->error),
1300 "Sandbox error when verifying server certificate " \
1301 "for %s", tab->request.host);
1302 return -1;
1303 case -5: // expired
1304 if (cert_should_ignore(tab->request.host)) break;
1305 snprintf(tab->error, sizeof(tab->error),
1306 "Expired certificate, " \
1307 "the certificate for %s has expired",
1308 tab->request.host);
1309 return -1;
1310 case -6: // certificate changed
1311 snprintf(tab->error, sizeof(tab->error),
1312 "Invalid certificate, enter \":forget %s\"" \
1313 " to forget the old certificate.",
1314 tab->request.host);
1315 return -1;
1316 default: // failed to write certificate
1317 snprintf(tab->error, sizeof(tab->error),
1318 "Failed to write %s certificate information : %s [%d]",
1319 tab->request.host, strerror(errno), ret);
1320 return -1;
1321 }
1322
1323 if (tab->request.state == STATE_CANCEL) return -1;
1324 char buf[MAX_URL];
1325 STRLCPY(buf, "gemini://");
1326 char toascii[1024];
1327 if (idn_to_ascii(&tab->request.url[GMI],
1328 sizeof(tab->request.url) - GMI,
1329 toascii, sizeof(toascii))) {
1330 snprintf(tab->error, sizeof(tab->error),
1331 "Failed to parse url: %s", &tab->request.url[GMI]);
1332 return -1;
1333 }
1334 ssize_t len = strlcpy(&buf[GMI], toascii, sizeof(buf) - GMI) + GMI;
1335 char url[1024];
1336 if (len < MAX_URL)
1337 len = parse_query(buf, MAX_URL, url, MAX_URL);
1338 if (len >= MAX_URL ||
1339 (len += strlcpy(&url[len], "\r\n", MAX_URL - len)) >= MAX_URL) {
1340 snprintf(tab->error, sizeof(tab->error),
1341 "Url too long: %s", url);
1342 return -1;
1343 }
1344
1345 if (tls_write(tab->request.tls, url, len) < len) {
1346 snprintf(tab->error, sizeof(tab->error),
1347 "Failed to send data to: %s", tab->request.host);
1348 return -1;
1349 }
1350
1351 if (tab->request.state == STATE_CANCEL) return -1;
1352 tab->request.state = STATE_CONNECTED;
1353 return 0;
1354 }
1355
1356 int gmi_request_header(struct gmi_tab* tab) {
1357 time_t now = time(0);
1358 char buf[1024];
1359 int recv = 0;
1360 while (1) {
1361 if (tab->request.state == STATE_CANCEL) break;
1362 if (time(0) - now >= TIMEOUT) {
1363 snprintf(tab->error, sizeof(tab->error),
1364 "Connection to %s timed out (sent no data)",
1365 tab->request.host);
1366 return -1;
1367 }
1368 int n = tls_read(tab->request.tls, &buf[recv],
1369 sizeof(buf) - recv - 1);
1370 if (n == TLS_WANT_POLLIN) {
1371 nanosleep(&timeout, NULL);
1372 continue;
1373 }
1374 if (n < 1) {
1375 recv = -1;
1376 break;
1377 }
1378 recv += n;
1379 buf[recv] = '\0';
1380 if (strstr(buf, "\r\n")) break;
1381 }
1382 if (tab->request.state == STATE_CANCEL) return -1;
1383
1384 if (recv <= 0) {
1385 snprintf(tab->error, sizeof(tab->error),
1386 "[%d] Invalid data from: %s",
1387 recv, tab->request.host);
1388 return -1;
1389 }
1390 if (!strstr(buf, "\r\n")) {
1391 snprintf(tab->error, sizeof(tab->error),
1392 "Invalid data from: %s (no CRLF)", tab->request.host);
1393 return -1;
1394 }
1395 char* ptr = strchr(buf, ' ');
1396 if (!ptr) {
1397 snprintf(tab->error, sizeof(tab->error),
1398 "Invalid data from: %s (no metadata)",
1399 tab->request.host);
1400 return -1;
1401 }
1402 STRLCPY(tab->request.error, ptr + 1);
1403
1404 int previous_code = tab->page.code;
1405 char c = buf[2];
1406 buf[2] = '\0';
1407 tab->page.code = atoi(buf);
1408 buf[2] = c;
1409
1410 if (tab->request.state == STATE_CANCEL) return -1;
1411 if (tab->page.code != 30 && tab->page.code != 31) {
1412 tab->redirects = 0;
1413 }
1414 switch (tab->page.code) {
1415 case 10:
1416 case 11:
1417 ptr++;
1418 STRLCPY(client.input.label, ptr);
1419 ptr = strchr(client.input.label, '\n');
1420 if (ptr) *ptr = '\0';
1421 ptr = strchr(client.input.label, '\r');
1422 if (ptr) *ptr = '\0';
1423 tab->request.state = STATE_RECV_HEADER;
1424 tab->request.recv = recv;
1425 return 2; // input
1426 case 20:
1427 {
1428 #ifdef TERMINAL_IMG_VIEWER
1429 tab->page.img.tried = 0;
1430 #endif
1431 char* meta = strchr(++ptr, '\r');
1432 if (!meta) {
1433 snprintf(tab->error, sizeof(tab->error),
1434 "Invalid data from: %s", tab->request.host);
1435 return -1;
1436 }
1437 *meta = '\0';
1438 STRLCPY(tab->request.meta, ptr);
1439 *meta = '\r';
1440 if ((strcasestr(tab->request.meta, "charset=") &&
1441 !strcasestr(tab->request.meta, "charset=utf-8"))
1442 || ((!STARTWITH(tab->request.meta, "text/"))
1443 #ifdef TERMINAL_IMG_VIEWER
1444 && (!STARTWITH(tab->request.meta, "image/"))
1445 #endif
1446 )) {
1447 if (tab->history)
1448 STRLCPY(tab->url, tab->history->url);
1449 else
1450 STRLCPY(tab->url, "about:home");
1451 tab->request.ask = 2;
1452 snprintf(tab->request.info, sizeof(tab->request.info),
1453 "# Non-renderable meta-data : %s",
1454 tab->request.meta);
1455 STRLCPY(tab->request.action, "download");
1456 tb_interupt();
1457 while (tab->request.ask == 2)
1458 nanosleep(&timeout, NULL);
1459 tab->request.download = tab->request.ask;
1460 if (!tab->request.download) {
1461 tab->request.recv = -1;
1462 return 1; // download
1463 }
1464 }
1465 char* ptr_meta = strchr(tab->page.meta, ';');
1466 if (ptr_meta) *ptr_meta = '\0';
1467 }
1468 break;
1469 case 30:
1470 case 31:
1471 tab->redirects++;
1472 if (tab->redirects > MAX_REDIRECT) {
1473 snprintf(tab->error, sizeof(tab->error),
1474 "Too many redirects");
1475 return -2;
1476 }
1477 snprintf(tab->error, sizeof(tab->error), "Redirect %s",
1478 tab->page.code == 30 ? "temporary" : "permanent");
1479 break;
1480 case 40:
1481 snprintf(tab->error, sizeof(tab->error),
1482 "Temporary failure");
1483 return -2;
1484 case 41:
1485 snprintf(tab->error, sizeof(tab->error),
1486 "Server unavailable");
1487 return -2;
1488 case 42:
1489 snprintf(tab->error, sizeof(tab->error),
1490 "CGI error");
1491 return -2;
1492 case 43:
1493 snprintf(tab->error, sizeof(tab->error),
1494 "Proxy error");
1495 return -2;
1496 case 44:
1497 snprintf(tab->error, sizeof(tab->error),
1498 "Slow down: server above rate limit");
1499 return -2;
1500 case 50:
1501 snprintf(tab->error, sizeof(tab->error),
1502 "Permanent failure");
1503 return -2;
1504 case 51:
1505 snprintf(tab->error, sizeof(tab->error),
1506 "Not found %s", tab->request.url);
1507 return -2;
1508 case 52:
1509 snprintf(tab->error, sizeof(tab->error),
1510 "Resource gone");
1511 return -2;
1512 case 53:
1513 snprintf(tab->error, sizeof(tab->error),
1514 "Proxy request refused");
1515 return -2;
1516 case 59:
1517 snprintf(tab->error, sizeof(tab->error),
1518 "Bad request");
1519 return -2;
1520 case 60:
1521 snprintf(tab->error, sizeof(tab->error),
1522 "Client certificate required");
1523 return -2;
1524 case 61:
1525 snprintf(tab->error, sizeof(tab->error),
1526 "Client not authorised");
1527 return -2;
1528 case 62:
1529 snprintf(tab->error, sizeof(tab->error),
1530 "Client certificate not valid");
1531 return -2;
1532 default:
1533 snprintf(tab->error, sizeof(tab->error),
1534 "Unknown status code: %d", tab->page.code);
1535 tab->page.code = previous_code;
1536 return -2;
1537 }
1538 if (tab->request.state == STATE_CANCEL) return -1;
1539 tab->request.state = STATE_RECV_HEADER;
1540 tab->request.recv = recv;
1541 tab->request.data = malloc(tab->request.recv + 1);
1542 if (!tab->request.data) {
1543 memory_failure(tab);
1544 return -1;
1545 }
1546 memcpy(tab->request.data, buf, recv);
1547 return 0;
1548 }
1549
1550 int gmi_request_body(struct gmi_tab* tab, int fd) {
1551 time_t now = time(0);
1552 char buf[1024];
1553 int isText = STARTWITH(tab->request.meta, "text/");
1554 while (tab->request.state != STATE_CANCEL) {
1555 if (time(0) - now >= TIMEOUT) {
1556 snprintf(tab->error, sizeof(tab->error),
1557 "Server %s stopped responding",
1558 tab->request.host);
1559 return -1;
1560 }
1561 int bytes = tls_read(tab->request.tls, buf, sizeof(buf));
1562 if (tab->request.state == STATE_CANCEL) return -1;
1563 if (bytes == 0) break;
1564 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) {
1565 nanosleep(&timeout, NULL);
1566 continue;
1567 }
1568 if (bytes < 1) {
1569 snprintf(tab->error, sizeof(tab->error),
1570 "Invalid data from %s: %s",
1571 tab->request.host,
1572 tls_error(tab->request.tls));
1573 return -1;
1574 }
1575 if (fd > 0) {
1576 if (write(fd, buf, bytes) != bytes) {
1577 snprintf(tab->error, sizeof(tab->error),
1578 "Failed to download file: %s",
1579 strerror(errno));
1580 return -1;
1581 }
1582 now = time(0);
1583 continue;
1584 }
1585 /* allocate addional memory in case of reading an incomplete
1586 * unicode character at the end of the page */
1587 void *ptr = realloc(tab->request.data,
1588 tab->request.recv + bytes + 32);
1589 if (!ptr) {
1590 free(tab->request.data);
1591 tab->request.data = NULL;
1592 memory_failure(tab);
1593 return -1;
1594 }
1595 tab->request.data = ptr;
1596 memcpy(&tab->request.data[tab->request.recv], buf, bytes);
1597 memset(&tab->request.data[tab->request.recv + bytes], 0, 32);
1598 tab->request.recv += bytes;
1599 /* prevent the server from filling up memory */
1600 if ((isText && tab->request.recv > MAX_TEXT_SIZE) ||
1601 (!isText && tab->request.recv > MAX_IMAGE_SIZE)) {
1602 snprintf(tab->error, sizeof(tab->error),
1603 "The server sent a too large response");
1604 return -1;
1605 }
1606 now = time(0);
1607 }
1608 if (tab->request.state == STATE_CANCEL) return -1;
1609 tab->request.state = STATE_RECV_BODY;
1610 return 0;
1611 }
1612
1613 int gmi_getfd(struct gmi_tab* tab, char* path, size_t path_len) {
1614 int len = STRNLEN(tab->request.url);
1615 if (tab->request.url[len-1] == '/')
1616 tab->request.url[len-1] = '\0';
1617 char* ptr = strrchr(tab->request.url, '/');
1618 #ifndef SANDBOX_SUN
1619 int fd = getdownloadfd();
1620 if (fd < 0) {
1621 snprintf(tab->error, sizeof(tab->error),
1622 "Unable to open download folder");
1623 tab->show_error = 1;
1624 return -1;
1625 }
1626 #endif
1627 if (ptr)
1628 strlcpy(path, ptr + 1, path_len);
1629 else {
1630 #ifdef __OpenBSD__
1631 char format[] = "output_%lld.dat";
1632 #else
1633 char format[] = "output_%ld.dat";
1634 #endif
1635
1636 snprintf(path, path_len, format, time(NULL));
1637 }
1638 for (unsigned int i = 0; i < path_len && path[i] && ptr; i++) {
1639 char c = path[i];
1640 if ((path[i] == '.' && path[i+1] == '.') ||
1641 !(c == '.' ||
1642 (c >= 'A' && c <= 'Z') ||
1643 (c >= 'a' && c <= 'z') ||
1644 (c >= '0' && c <= '9')
1645 ))
1646 path[i] = '_';
1647 }
1648 #ifdef SANDBOX_SUN
1649 if (sandbox_download(tab, path))
1650 return -1;
1651 int dfd = wr_pair[1];
1652 sandbox_dl_length(-1);
1653 #else
1654 int dfd = openat(fd, path,
1655 O_CREAT|O_EXCL|O_WRONLY, 0600);
1656 if (dfd < 0 && errno == EEXIST) {
1657 char buf[1024];
1658 #ifdef __OpenBSD__
1659 char format[] = "%lld_%s";
1660 #else
1661 char format[] = "%ld_%s";
1662 #endif
1663 snprintf(buf, sizeof(buf), format, time(NULL), path);
1664 strlcpy(path, buf, path_len);
1665 dfd = openat(fd, path,
1666 O_CREAT|O_EXCL|O_WRONLY, 0600);
1667 if (dfd < 0) {
1668 snprintf(tab->error, sizeof(tab->error),
1669 "Failed to write to the download folder");
1670 tab->show_error = 1;
1671 return -1;
1672 }
1673 }
1674 #ifdef SANDBOX_FREEBSD
1675 if (makefd_writeonly(dfd)) {
1676 client.shutdown = 1;
1677 return -1;
1678 }
1679 #endif
1680 #endif
1681 return dfd;
1682 }
1683
1684 int gmi_postdownload(struct gmi_tab* tab, int fd, const char* path) {
1685
1686 #ifdef SANDBOX_SUN
1687 if (wr_pair[1] != fd)
1688 return -1;
1689 #else
1690 close(fd);
1691 #endif
1692 #ifndef DISABLE_XDG
1693 int fail = 0;
1694 if (client.xdg) {
1695 tab->request.ask = 2;
1696 snprintf(tab->request.info,
1697 sizeof(tab->request.info),
1698 "# %s download",
1699 tab->request.meta);
1700 STRLCPY(tab->request.action, "open");
1701 tb_interupt();
1702 while (tab->request.ask == 2)
1703 nanosleep(&timeout, NULL);
1704 if (tab->request.ask)
1705 fail = xdg_open(path);
1706 }
1707 if (fail) {
1708 tab->show_error = 1;
1709 snprintf(tab->error, sizeof(tab->info),
1710 "Failed to open %s", path);
1711 } else
1712 #endif
1713 {
1714 tab->show_info = 1;
1715 snprintf(tab->info, sizeof(tab->info),
1716 "File downloaded to %s", path);
1717 }
1718 return fail;
1719 }
1720
1721 void* gmi_request_thread(struct gmi_tab* tab) {
1722 unsigned int signal = 0;
1723 while (!client.shutdown) {
1724 tab->selected = 0;
1725 tab->request.state = STATE_DONE;
1726 if (tab->page.code != 30 && tab->page.code != 31)
1727 tb_interupt();
1728 if (recv(tab->thread.pair[0], &signal, 4, 0) != 4 ||
1729 client.shutdown || signal == 0xFFFFFFFF) break;
1730 bzero(&tab->request, sizeof(tab->request));
1731 tab->request.state = STATE_STARTED;
1732 int ret = gmi_request_init(tab, tab->thread.url,
1733 tab->thread.add);
1734 if (tab->request.state == STATE_CANCEL ||
1735 ret || gmi_request_dns(tab)) {
1736 if (tab->request.tls) {
1737 tls_close(tab->request.tls);
1738 tls_free(tab->request.tls);
1739 tab->request.tls = NULL;
1740 }
1741 if (ret == -1)
1742 tab->show_error = 1;
1743 tab->request.recv = ret;
1744 continue;
1745 }
1746 if (gmi_request_connect(tab) ||
1747 tab->request.state == STATE_CANCEL) {
1748 close(tab->request.socket);
1749 if (tab->request.tls) {
1750 tls_close(tab->request.tls);
1751 tls_free(tab->request.tls);
1752 tab->request.tls = NULL;
1753 }
1754 tab->show_error = 1;
1755 tab->request.recv = -1;
1756 continue;
1757 }
1758 ret = -1;
1759 if (!gmi_request_handshake(tab) &&
1760 tab->request.state != STATE_CANCEL) {
1761 ret = gmi_request_header(tab);
1762 if (!ret || ret == 2) {
1763 char out[1024];
1764 int fd = -1;
1765 if (tab->request.download)
1766 fd = gmi_getfd(tab, out, sizeof(out));
1767 if (gmi_request_body(tab, fd)) {
1768 close(fd);
1769 tab->show_error = 1;
1770 free(tab->request.data);
1771 continue;
1772 }
1773 if (fd > 0)
1774 gmi_postdownload(tab, fd, out);
1775 }
1776 }
1777
1778 if (ret == -2) {
1779 char* cr = strchr(tab->request.error, '\r');
1780 if (cr) {
1781 *cr = '\0';
1782 char buf[256];
1783 snprintf(buf, sizeof(buf),
1784 "%s (%d : %s)", tab->request.error,
1785 tab->page.code, tab->error);
1786 STRLCPY(tab->error, buf);
1787 *cr = '\r';
1788 }
1789 }
1790 if (ret == -1 || ret == -2) {
1791 free(tab->request.data);
1792 tab->request.data = NULL;
1793 if (tab->history)
1794 STRLCPY(tab->url, tab->history->url);
1795 else
1796 STRLCPY(tab->url, "about:home");
1797 tab->show_error = 1;
1798 tab->request.recv = -1;
1799 tab->page.code = 20;
1800 }
1801
1802 if (tab->request.socket != -1)
1803 close(tab->request.socket);
1804 if (tab->request.tls) {
1805 tls_close(tab->request.tls);
1806 tls_free(tab->request.tls);
1807 tab->request.tls = NULL;
1808 }
1809 if (tab->request.recv > 0 &&
1810 (tab->page.code == 11 || tab->page.code == 10)) {
1811 free(tab->request.data);
1812 tab->request.data = NULL;
1813 STRLCPY(tab->url, tab->request.url);
1814 continue;
1815 }
1816 if (tab->request.recv > 0 &&
1817 (tab->page.code == 31 || tab->page.code == 30)) {
1818 tab->request.data[tab->request.recv] = '\0';
1819 char* ptr = strchr(tab->request.data, ' ');
1820 if (!ptr) {
1821 free(tab->request.data);
1822 continue;
1823 }
1824 char* ln = strchr(ptr + 1, ' ');
1825 if (ln) *ln = '\0';
1826 ln = strchr(ptr, '\n');
1827 if (ln) *ln = '\0';
1828 ln = strchr(ptr, '\r');
1829 if (ln) *ln = '\0';
1830 STRLCPY(tab->url, tab->request.url);
1831 int r = gmi_nextlink(tab, tab->url, ptr + 1);
1832 if (r < 1 || tab->page.code != 20) {
1833 free(tab->request.data);
1834 continue;
1835 }
1836 tab->page.data_len = r;
1837 free(tab->request.data);
1838 tab->request.data = NULL;
1839 gmi_load(&tab->page);
1840 tab->history->page = tab->page;
1841 tab->history->cached = 1;
1842 tab->request.recv = r;
1843 continue;
1844 }
1845 if (!tab->request.download &&
1846 tab->request.recv > 0 &&
1847 tab->page.code == 20) {
1848 tab->search.entry[0] = '\0';
1849 struct gmi_link* link_ptr = tab->history?
1850 tab->history->prev:NULL;
1851 for (int i = 0; i < MAX_CACHE; i++) {
1852 if (!link_ptr) break;
1853 link_ptr = link_ptr->prev;
1854 }
1855 while (link_ptr) {
1856 if (link_ptr->cached) {
1857 gmi_freepage(&link_ptr->page);
1858 link_ptr->cached = 0;
1859 }
1860 link_ptr = link_ptr->prev;
1861 }
1862 pthread_mutex_lock(&tab->render_mutex);
1863 if (!tab->thread.add)
1864 gmi_freepage(&tab->page);
1865 bzero(&tab->page, sizeof(struct gmi_page));
1866 tab->page.code = 20;
1867 STRLCPY(tab->page.meta, tab->request.meta);
1868 STRLCPY(tab->url, tab->request.url);
1869 tab->page.data_len = tab->request.recv;
1870 tab->page.data = tab->request.data;
1871 gmi_load(&tab->page);
1872 if (tab->thread.add)
1873 gmi_addtohistory(tab);
1874 else if (tab->history) {
1875 tab->history->page = tab->page;
1876 tab->history->cached = 1;
1877 }
1878 tab->scroll = -1;
1879 pthread_mutex_unlock(&tab->render_mutex);
1880 }
1881 }
1882 return NULL;
1883 }
1884
1885 int gmi_request(struct gmi_tab *tab, const char *url, int add) {
1886 if (tab->request.state != STATE_DONE)
1887 tab->request.state = STATE_CANCEL;
1888 for (int i = 0; i < 5 && tab->request.state == STATE_CANCEL; i++)
1889 nanosleep(&timeout, NULL);
1890 tab->thread.add = add;
1891 STRLCPY(tab->thread.url, url);
1892 int signal = 0;
1893 if (send(tab->thread.pair[1], &signal, sizeof(signal), 0) !=
1894 sizeof(signal))
1895 return -1;
1896 return 0;
1897 }
1898
1899 int gmi_loadfile(struct gmi_tab* tab, char* path) {
1900 FILE* f = fopen(path, "rb");
1901 if (!f) {
1902 snprintf(tab->error, sizeof(tab->error),
1903 "Failed to open %s", path);
1904 tab->show_error = 1;
1905 return -1;
1906 }
1907 #ifdef SANDBOX_FREEBSD
1908 if (make_readonly(f)) {
1909 fclose(f);
1910 client.shutdown = 1;
1911 return -1;
1912 }
1913 #endif
1914 fseek(f, 0, SEEK_END);
1915 size_t len = ftell(f);
1916 fseek(f, 0, SEEK_SET);
1917 char* data = malloc(len);
1918 if (!data) {
1919 fclose(f);
1920 memory_failure(tab);
1921 return -1;
1922 }
1923 if (len != fread(data, 1, len, f)) {
1924 fclose(f);
1925 free(data);
1926 return -1;
1927 }
1928 fclose(f);
1929 pthread_mutex_lock(&tab->render_mutex);
1930 tab->page.code = 20;
1931 tab->page.data_len = len;
1932 if (tab->page.data) free(tab->page.data);
1933 tab->page.data = data;
1934 snprintf(tab->url, sizeof(tab->url), "file://%s/", path);
1935 int i = strnlen(path, PATH_MAX);
1936 int gmi = 0;
1937 if (i > 4 && path[i - 1] == '/') i--;
1938 if (i > 4 && path[i - 4] == '.' && path[i - 3] == 'g' &&
1939 path[i - 2] == 'm' && path[i - 1] == 'i')
1940 gmi = 1;
1941 if (gmi)
1942 STRLCPY(tab->page.meta, "text/gemini");
1943 else
1944 STRLCPY(tab->page.meta, "text/text");
1945 tab->page.no_header = 1;
1946 gmi_load(&tab->page);
1947 gmi_addtohistory(tab);
1948 pthread_mutex_unlock(&tab->render_mutex);
1949 return len;
1950 }
1951
1952 int gmi_init() {
1953 if (tls_init()) {
1954 tb_shutdown();
1955 printf("Failed to initialize TLS\n");
1956 return -1;
1957 }
1958
1959 config = tls_config_new();
1960 if (!config) {
1961 tb_shutdown();
1962 printf("Failed to initialize TLS config\n");
1963 return -1;
1964 }
1965
1966 config_empty = tls_config_new();
1967 if (!config_empty) {
1968 tb_shutdown();
1969 printf("Failed to initialize TLS config\n");
1970 return -1;
1971 }
1972
1973 tls_config_insecure_noverifycert(config);
1974 tls_config_insecure_noverifycert(config_empty);
1975 #ifndef DISABLE_XDG
1976 int xdg = client.xdg;
1977 #endif
1978 #ifdef SANDBOX_SUN
1979 char** bookmarks = client.bookmarks;
1980 #endif
1981 bzero(&client, sizeof(client));
1982 #ifdef SANDBOX_SUN
1983 client.bookmarks = bookmarks;
1984 #endif
1985 #ifndef DISABLE_XDG
1986 client.xdg = xdg;
1987 #endif
1988
1989 int fd = getconfigfd();
1990 if (fd < 0) {
1991 tb_shutdown();
1992 printf("Failed to open config folder\n");
1993 return -1;
1994 }
1995
1996 #ifndef SANDBOX_SUN
1997 if (gmi_loadbookmarks()) {
1998 gmi_newbookmarks();
1999 }
2000
2001 if (cert_load()) {
2002 tb_shutdown();
2003 printf("Failed to load known hosts\n");
2004 return -1;
2005 }
2006 #endif
2007
2008 return 0;
2009 }
2010
2011 void gmi_free() {
2012 while (client.tab)
2013 gmi_freetab(client.tab);
2014 gmi_savebookmarks();
2015 for (int i = 0; client.bookmarks[i]; i++)
2016 free(client.bookmarks[i]);
2017 free(client.bookmarks);
2018 cert_free();
2019 }
2020