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