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 += 8 - x%8;
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 close(fd);
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 if (len < MAX_URL)
1256 len = parse_query(buf, MAX_URL, url, MAX_URL);
1257 if (len >= MAX_URL ||
1258 (len += strlcpy(&url[len], "\r\n", MAX_URL - len)) >= MAX_URL) {
1259 snprintf(tab->error, sizeof(tab->error),
1260 "Url too long: %s", url);
1261 return -1;
1262 }
1263
1264 if (tls_write(tab->request.tls, url, len) < len) {
1265 snprintf(tab->error, sizeof(tab->error),
1266 "Failed to send data to: %s", tab->request.host);
1267 return -1;
1268 }
1269
1270 if (tab->request.state == STATE_CANCEL) return -1;
1271 tab->request.state = STATE_CONNECTED;
1272 return 0;
1273 }
1274
1275 int gmi_request_header(struct gmi_tab* tab) {
1276 time_t now = time(0);
1277 char buf[1024];
1278 int recv = 0;
1279 while (1) {
1280 if (tab->request.state == STATE_CANCEL) break;
1281 if (time(0) - now >= TIMEOUT) {
1282 snprintf(tab->error, sizeof(tab->error),
1283 "Connection to %s timed out (sent no data)",
1284 tab->request.host);
1285 return -1;
1286 }
1287 int n = tls_read(tab->request.tls, &buf[recv],
1288 sizeof(buf) - recv - 1);
1289 if (n == TLS_WANT_POLLIN) {
1290 nanosleep(&timeout, NULL);
1291 continue;
1292 }
1293 if (n < 1) {
1294 recv = -1;
1295 break;
1296 }
1297 recv += n;
1298 buf[recv] = '\0';
1299 if (strstr(buf, "\r\n")) break;
1300 }
1301 if (tab->request.state == STATE_CANCEL) return -1;
1302
1303 if (recv <= 0) {
1304 snprintf(tab->error, sizeof(tab->error),
1305 "[%d] Invalid data from: %s",
1306 recv, tab->request.host);
1307 return -1;
1308 }
1309 if (!strstr(buf, "\r\n")) {
1310 snprintf(tab->error, sizeof(tab->error),
1311 "Invalid data from: %s (no CRLF)", tab->request.host);
1312 return -1;
1313 }
1314 char* ptr = strchr(buf, ' ');
1315 if (!ptr) {
1316 snprintf(tab->error, sizeof(tab->error),
1317 "Invalid data from: %s (no metadata)",
1318 tab->request.host);
1319 return -1;
1320 }
1321 strlcpy(tab->request.error, ptr +1, sizeof(tab->request.error));
1322
1323 int previous_code = tab->page.code;
1324 char c = buf[2];
1325 buf[2] = '\0';
1326 tab->page.code = atoi(buf);
1327 buf[2] = c;
1328
1329 if (tab->request.state == STATE_CANCEL) return -1;
1330 switch (tab->page.code) {
1331 case 10:
1332 case 11:
1333 ptr++;
1334 strlcpy(client.input.label, ptr, sizeof(client.input.label));
1335 ptr = strchr(client.input.label, '\n');
1336 if (ptr) *ptr = '\0';
1337 ptr = strchr(client.input.label, '\r');
1338 if (ptr) *ptr = '\0';
1339 tab->request.state = STATE_RECV_HEADER;
1340 tab->request.recv = recv;
1341 return 2; // input
1342 case 20:
1343 {
1344 #ifdef TERMINAL_IMG_VIEWER
1345 tab->page.img.tried = 0;
1346 #endif
1347 char* meta = strchr(++ptr, '\r');
1348 if (!meta) {
1349 snprintf(tab->error, sizeof(tab->error),
1350 "Invalid data from: %s", tab->request.host);
1351 return -1;
1352 }
1353 *meta = '\0';
1354 strlcpy(tab->request.meta, ptr, MAX_META);
1355 *meta = '\r';
1356 if ((strcasestr(tab->request.meta, "charset=") &&
1357 !strcasestr(tab->request.meta, "charset=utf-8"))
1358 || ((strncmp(tab->request.meta, "text/", 5))
1359 #ifdef TERMINAL_IMG_VIEWER
1360 && (strncmp(tab->request.meta, "image/", 6))
1361 #endif
1362 )) {
1363 if (tab->history)
1364 strlcpy(tab->url, tab->history->url,
1365 sizeof(tab->url));
1366 else
1367 strlcpy(tab->url, "about:home",
1368 sizeof(tab->url));
1369 tab->request.ask = 2;
1370 snprintf(tab->request.info, sizeof(tab->request.info),
1371 "# Non-renderable meta-data : %s",
1372 tab->request.meta);
1373 strlcpy(tab->request.action, "download",
1374 sizeof(tab->request.action));
1375 tb_interupt();
1376 while (tab->request.ask == 2)
1377 nanosleep(&timeout, NULL);
1378 tab->request.download = tab->request.ask;
1379 if (!tab->request.download) {
1380 tab->request.recv = -1;
1381 return 1; // download
1382 }
1383 }
1384 char* ptr_meta = strchr(tab->page.meta, ';');
1385 if (ptr_meta) *ptr_meta = '\0';
1386 }
1387 break;
1388 case 30:
1389 snprintf(tab->error, sizeof(tab->error),
1390 "Redirect temporary");
1391 break;
1392 case 31:
1393 snprintf(tab->error, sizeof(tab->error),
1394 "Redirect permanent");
1395 break;
1396 case 40:
1397 snprintf(tab->error, sizeof(tab->error),
1398 "Temporary failure");
1399 return -2;
1400 case 41:
1401 snprintf(tab->error, sizeof(tab->error),
1402 "Server unavailable");
1403 return -2;
1404 case 42:
1405 snprintf(tab->error, sizeof(tab->error),
1406 "CGI error");
1407 return -2;
1408 case 43:
1409 snprintf(tab->error, sizeof(tab->error),
1410 "Proxy error");
1411 return -2;
1412 case 44:
1413 snprintf(tab->error, sizeof(tab->error),
1414 "Slow down: server above rate limit");
1415 return -2;
1416 case 50:
1417 snprintf(tab->error, sizeof(tab->error),
1418 "Permanent failure");
1419 return -2;
1420 case 51:
1421 snprintf(tab->error, sizeof(tab->error),
1422 "Not found %s", tab->request.url);
1423 return -2;
1424 case 52:
1425 snprintf(tab->error, sizeof(tab->error),
1426 "Resource gone");
1427 return -2;
1428 case 53:
1429 snprintf(tab->error, sizeof(tab->error),
1430 "Proxy request refused");
1431 return -2;
1432 case 59:
1433 snprintf(tab->error, sizeof(tab->error),
1434 "Bad request");
1435 return -2;
1436 case 60:
1437 snprintf(tab->error, sizeof(tab->error),
1438 "Client certificate required");
1439 return -2;
1440 case 61:
1441 snprintf(tab->error, sizeof(tab->error),
1442 "Client not authorised");
1443 return -2;
1444 case 62:
1445 snprintf(tab->error, sizeof(tab->error),
1446 "Client certificate not valid");
1447 return -2;
1448 default:
1449 snprintf(tab->error, sizeof(tab->error),
1450 "Unknown status code: %d", tab->page.code);
1451 tab->page.code = previous_code;
1452 return -2;
1453 }
1454 if (tab->request.state == STATE_CANCEL) return -1;
1455 tab->request.state = STATE_RECV_HEADER;
1456 tab->request.recv = recv;
1457 tab->request.data = malloc(tab->request.recv + 1);
1458 if (!tab->request.data) return fatalI();
1459 memcpy(tab->request.data, buf, recv);
1460 return 0;
1461 }
1462
1463 int gmi_request_body(struct gmi_tab* tab) {
1464 time_t now = time(0);
1465 char buf[1024];
1466 while (tab->request.state != STATE_CANCEL) {
1467 if (time(0) - now >= TIMEOUT) {
1468 snprintf(tab->error, sizeof(tab->error),
1469 "Server %s stopped responding",
1470 tab->request.host);
1471 return -1;
1472 }
1473 int bytes = tls_read(tab->request.tls, buf, sizeof(buf));
1474 if (tab->request.state == STATE_CANCEL) return -1;
1475 if (bytes == 0) break;
1476 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) {
1477 nanosleep(&timeout, NULL);
1478 continue;
1479 }
1480 if (bytes < 1) {
1481 snprintf(tab->error, sizeof(tab->error),
1482 "Invalid data from %s: %s",
1483 tab->request.host,
1484 tls_error(tab->request.tls));
1485 return -1;
1486 }
1487 tab->request.data = realloc(tab->request.data,
1488 tab->request.recv + bytes + 1);
1489 memcpy(&tab->request.data[tab->request.recv], buf, bytes);
1490 tab->request.data[tab->request.recv + bytes] = '\0';
1491 tab->request.recv += bytes;
1492 now = time(0);
1493 }
1494 if (tab->request.state == STATE_CANCEL) return -1;
1495 tab->request.state = STATE_RECV_BODY;
1496 return 0;
1497 }
1498
1499 void* gmi_request_thread(struct gmi_tab* tab) {
1500 unsigned int signal = 0;
1501 while (!client.shutdown) {
1502 tab->selected = 0;
1503 tab->request.state = STATE_DONE;
1504 if (tab->page.code != 30 && tab->page.code != 31)
1505 tb_interupt();
1506 if (recv(tab->thread.pair[0], &signal, 4, 0) != 4 ||
1507 client.shutdown || signal == 0xFFFFFFFF) break;
1508 bzero(&tab->request, sizeof(tab->request));
1509 tab->request.state = STATE_STARTED;
1510 int ret = gmi_request_init(tab, tab->thread.url,
1511 tab->thread.add);
1512 if (tab->request.state == STATE_CANCEL ||
1513 ret || gmi_request_dns(tab)) {
1514 if (tab->request.tls) {
1515 tls_close(tab->request.tls);
1516 tls_free(tab->request.tls);
1517 tab->request.tls = NULL;
1518 }
1519 if (ret == -1)
1520 tab->show_error = 1;
1521 tab->request.recv = ret;
1522 continue;
1523 }
1524 if (gmi_request_connect(tab) ||
1525 tab->request.state == STATE_CANCEL) {
1526 close(tab->request.socket);
1527 if (tab->request.tls) {
1528 tls_close(tab->request.tls);
1529 tls_free(tab->request.tls);
1530 tab->request.tls = NULL;
1531 }
1532 tab->show_error = 1;
1533 tab->request.recv = -1;
1534 continue;
1535 }
1536 ret = -1;
1537 if (!gmi_request_handshake(tab) &&
1538 tab->request.state != STATE_CANCEL) {
1539 ret = gmi_request_header(tab);
1540 if (!ret || ret == 2) gmi_request_body(tab);
1541 }
1542
1543 if (ret == -2) {
1544 char* cr = strchr(tab->request.error, '\r');
1545 if (cr) {
1546 *cr = '\0';
1547 char buf[256];
1548 snprintf(buf, sizeof(buf),
1549 "%s (%d : %s)", tab->request.error,
1550 tab->page.code, tab->error);
1551 strlcpy(tab->error, buf, sizeof(tab->error));
1552 *cr = '\r';
1553 }
1554 }
1555 if (ret == -1 || ret == -2) {
1556 free(tab->request.data);
1557 tab->request.data = NULL;
1558 if (tab->history)
1559 strlcpy(tab->url, tab->history->url,
1560 sizeof(tab->url));
1561 else
1562 strlcpy(tab->url, "about:home",
1563 sizeof(tab->url));
1564 tab->show_error = 1;
1565 tab->request.recv = -1;
1566 tab->page.code = 20;
1567 }
1568
1569 if (tab->request.socket != -1)
1570 close(tab->request.socket);
1571 if (tab->request.tls) {
1572 tls_close(tab->request.tls);
1573 tls_free(tab->request.tls);
1574 tab->request.tls = NULL;
1575 }
1576 if (tab->request.recv > 0 &&
1577 (tab->page.code == 11 || tab->page.code == 10)) {
1578 free(tab->request.data);
1579 tab->request.data = NULL;
1580 strlcpy(tab->url, tab->request.url, sizeof(tab->url));
1581 continue;
1582 }
1583 if (tab->request.recv > 0 &&
1584 (tab->page.code == 31 || tab->page.code == 30)) {
1585 tab->request.data[tab->request.recv] = '\0';
1586 char* ptr = strchr(tab->request.data, ' ');
1587 if (!ptr) {
1588 free(tab->request.data);
1589 continue;
1590 }
1591 char* ln = strchr(ptr + 1, ' ');
1592 if (ln) *ln = '\0';
1593 ln = strchr(ptr, '\n');
1594 if (ln) *ln = '\0';
1595 ln = strchr(ptr, '\r');
1596 if (ln) *ln = '\0';
1597 strlcpy(tab->url, tab->request.url, sizeof(tab->url));
1598 int r = gmi_nextlink(tab, tab->url, ptr + 1);
1599 if (r < 1 || tab->page.code != 20) {
1600 free(tab->request.data);
1601 continue;
1602 }
1603 tab->page.data_len = r;
1604 free(tab->request.data);
1605 tab->request.data = NULL;
1606 gmi_load(&tab->page);
1607 tab->history->page = tab->page;
1608 tab->history->cached = 1;
1609 tab->request.recv = r;
1610 continue;
1611 }
1612 if (!tab->request.download &&
1613 tab->request.recv > 0 &&
1614 tab->page.code == 20) {
1615 tab->search.entry[0] = '\0';
1616 struct gmi_link* link_ptr = tab->history?
1617 tab->history->prev:NULL;
1618 for (int i = 0; i < MAX_CACHE; i++) {
1619 if (!link_ptr) break;
1620 link_ptr = link_ptr->prev;
1621 }
1622 while (link_ptr) {
1623 if (link_ptr->cached) {
1624 gmi_freepage(&link_ptr->page);
1625 link_ptr->cached = 0;
1626 }
1627 link_ptr = link_ptr->prev;
1628 }
1629 pthread_mutex_lock(&tab->render_mutex);
1630 if (!tab->thread.add)
1631 gmi_freepage(&tab->page);
1632 bzero(&tab->page, sizeof(struct gmi_page));
1633 tab->page.code = 20;
1634 strlcpy(tab->page.meta, tab->request.meta,
1635 sizeof(tab->page.meta));
1636 strlcpy(tab->url, tab->request.url,
1637 sizeof(tab->url));
1638 tab->page.data_len = tab->request.recv;
1639 tab->page.data = tab->request.data;
1640 gmi_load(&tab->page);
1641 if (tab->thread.add)
1642 gmi_addtohistory(tab);
1643 else if (tab->history) {
1644 tab->history->page = tab->page;
1645 tab->history->cached = 1;
1646 }
1647 tab->scroll = -1;
1648 pthread_mutex_unlock(&tab->render_mutex);
1649 }
1650 if (tab->request.download &&
1651 tab->request.recv > 0 &&
1652 tab->page.code == 20) {
1653 int len = strnlen(tab->request.url,
1654 sizeof(tab->request.url));
1655 if (tab->request.url[len-1] == '/')
1656 tab->request.url[len-1] = '\0';
1657 char* ptr = strrchr(tab->request.url, '/');
1658 #ifndef SANDBOX_SUN
1659 int fd = getdownloadfd();
1660 if (fd < 0) {
1661 snprintf(tab->error, sizeof(tab->error),
1662 "Unable to open download folder");
1663 tab->show_error = 1;
1664 goto request_end;
1665 }
1666 #endif
1667 char path[1024];
1668 if (ptr)
1669 strlcpy(path, ptr + 1, sizeof(path));
1670 else {
1671 #ifdef __OpenBSD__
1672 char format[] = "output_%lld.dat";
1673 #else
1674 char format[] = "output_%ld.dat";
1675 #endif
1676
1677 snprintf(path, sizeof(path),
1678 format, time(NULL));
1679 }
1680 for (unsigned int i = 0;
1681 i < sizeof(path) && path[i] && ptr; i++) {
1682 char c = path[i];
1683 if ((path[i] == '.' && path[i+1] == '.') ||
1684 !(c == '.' ||
1685 (c >= 'A' && c <= 'Z') ||
1686 (c >= 'a' && c <= 'z') ||
1687 (c >= '0' && c <= '9')
1688 ))
1689 path[i] = '_';
1690 }
1691 #ifdef SANDBOX_SUN
1692 if (sandbox_download(tab, path))
1693 goto request_end;
1694 int dfd = wr_pair[1];
1695 #else
1696 int dfd = openat(fd, path,
1697 O_CREAT|O_EXCL|O_WRONLY, 0600);
1698 if (dfd < 0 && errno == EEXIST) {
1699 char buf[1024];
1700 #ifdef __OpenBSD__
1701 char format[] = "%lld_%s";
1702 #else
1703 char format[] = "%ld_%s";
1704 #endif
1705 snprintf(buf, sizeof(buf), format,
1706 time(NULL), path);
1707 strlcpy(path, buf, sizeof(path));
1708 dfd = openat(fd, path,
1709 O_CREAT|O_EXCL|O_WRONLY, 0600);
1710 if (dfd < 0) {
1711 snprintf(tab->error,
1712 sizeof(tab->error),
1713 "Failed to write to " \
1714 "the download folder");
1715 tab->show_error = 1;
1716 goto request_end;
1717 }
1718 }
1719 #ifdef SANDBOX_FREEBSD
1720 if (makefd_writeonly(dfd)) {
1721 client.shutdown = 1;
1722 break;
1723 }
1724 #endif
1725 #endif
1726 ptr = strchr(tab->request.data, '\n') + 1;
1727 if (!ptr) {
1728 tab->show_error = 1;
1729 snprintf(tab->error, sizeof(tab->error),
1730 "Server sent invalid data");
1731 goto request_end;
1732 }
1733 #ifdef SANDBOX_SUN
1734 sandbox_dl_length(tab->request.recv -
1735 (ptr-tab->request.data));
1736 #endif
1737 write(dfd, ptr,
1738 tab->request.recv - (ptr-tab->request.data));
1739 #ifndef SANDBOX_SUN
1740 close(dfd);
1741 #endif
1742 #ifndef DISABLE_XDG
1743 int fail = 0;
1744 if (client.xdg) {
1745 tab->request.ask = 2;
1746 snprintf(tab->request.info,
1747 sizeof(tab->request.info),
1748 "# %s download",
1749 tab->request.meta);
1750 strlcpy(tab->request.action, "open",
1751 sizeof(tab->request.action));
1752 tb_interupt();
1753 while (tab->request.ask == 2)
1754 nanosleep(&timeout, NULL);
1755 if (tab->request.ask)
1756 fail = xdg_open(path);
1757 }
1758 if (fail) {
1759 tab->show_error = 1;
1760 snprintf(tab->error, sizeof(tab->info),
1761 "Failed to open %s", path);
1762 } else
1763 #endif
1764 {
1765 tab->show_info = 1;
1766 snprintf(tab->info, sizeof(tab->info),
1767 "File downloaded to %s", path);
1768 }
1769 request_end:
1770 free(tab->request.data);
1771 tab->request.recv = tab->page.data_len;
1772 }
1773 }
1774 return NULL;
1775 }
1776
1777 int gmi_request(struct gmi_tab *tab, const char *url, int add) {
1778 if (tab->request.state != STATE_DONE)
1779 tab->request.state = STATE_CANCEL;
1780 for (int i = 0; i < 5 && tab->request.state == STATE_CANCEL; i++)
1781 nanosleep(&timeout, NULL);
1782 tab->thread.add = add;
1783 strlcpy(tab->thread.url, url, sizeof(tab->thread.url));
1784 int signal = 0;
1785 if (send(tab->thread.pair[1], &signal, sizeof(signal), 0) !=
1786 sizeof(signal))
1787 return -1;
1788 return 0;
1789 }
1790
1791 int gmi_loadfile(struct gmi_tab* tab, char* path) {
1792 FILE* f = fopen(path, "rb");
1793 if (!f) {
1794 snprintf(tab->error, sizeof(tab->error),
1795 "Failed to open %s", path);
1796 tab->show_error = 1;
1797 return -1;
1798 }
1799 #ifdef SANDBOX_FREEBSD
1800 if (make_readonly(f)) {
1801 fclose(f);
1802 client.shutdown = 1;
1803 return -1;
1804 }
1805 #endif
1806 fseek(f, 0, SEEK_END);
1807 size_t len = ftell(f);
1808 fseek(f, 0, SEEK_SET);
1809 char* data = malloc(len);
1810 if (!data) return fatalI();
1811 if (len != fread(data, 1, len, f)) {
1812 fclose(f);
1813 free(data);
1814 return -1;
1815 }
1816 fclose(f);
1817 pthread_mutex_lock(&tab->render_mutex);
1818 tab->page.code = 20;
1819 tab->page.data_len = len;
1820 if (tab->page.data) free(tab->page.data);
1821 tab->page.data = data;
1822 snprintf(tab->url, sizeof(tab->url), "file://%s/", path);
1823 int i = strnlen(path, 1024);
1824 int gmi = 0;
1825 if (i > 4 && path[i - 1] == '/') i--;
1826 if (i > 4 && path[i - 4] == '.' &&
1827 path[i - 3] == 'g' &&
1828 path[i - 2] == 'm' &&
1829 path[i - 1] == 'i')
1830 gmi = 1;
1831 if (gmi)
1832 strlcpy(tab->page.meta, "text/gemini", sizeof(tab->page.meta));
1833 else
1834 strlcpy(tab->page.meta, "text/text", sizeof(tab->page.meta));
1835 tab->page.no_header = 1;
1836 gmi_load(&tab->page);
1837 gmi_addtohistory(tab);
1838 pthread_mutex_unlock(&tab->render_mutex);
1839 return len;
1840 }
1841
1842 int gmi_init() {
1843 if (tls_init()) {
1844 tb_shutdown();
1845 printf("Failed to initialize TLS\n");
1846 return -1;
1847 }
1848
1849 config = tls_config_new();
1850 if (!config) {
1851 tb_shutdown();
1852 printf("Failed to initialize TLS config\n");
1853 return -1;
1854 }
1855
1856 config_empty = tls_config_new();
1857 if (!config_empty) {
1858 tb_shutdown();
1859 printf("Failed to initialize TLS config\n");
1860 return -1;
1861 }
1862
1863 tls_config_insecure_noverifycert(config);
1864 tls_config_insecure_noverifycert(config_empty);
1865 #ifndef DISABLE_XDG
1866 int xdg = client.xdg;
1867 #endif
1868 #ifdef SANDBOX_SUN
1869 char** bookmarks = client.bookmarks;
1870 #endif
1871 bzero(&client, sizeof(client));
1872 #ifdef SANDBOX_SUN
1873 client.bookmarks = bookmarks;
1874 #endif
1875 #ifndef DISABLE_XDG
1876 client.xdg = xdg;
1877 #endif
1878
1879 int fd = getconfigfd();
1880 if (fd < 0) {
1881 tb_shutdown();
1882 printf("Failed to open config folder\n");
1883 return -1;
1884 }
1885
1886 #ifndef SANDBOX_SUN
1887 if (gmi_loadbookmarks()) {
1888 gmi_newbookmarks();
1889 }
1890
1891 if (cert_load()) {
1892 tb_shutdown();
1893 printf("Failed to load known hosts\n");
1894 return -1;
1895 }
1896 #endif
1897
1898 return 0;
1899 }
1900
1901 void gmi_free() {
1902 while (client.tab)
1903 gmi_freetab(client.tab);
1904 gmi_savebookmarks();
1905 for (int i = 0; client.bookmarks[i]; i++)
1906 free(client.bookmarks[i]);
1907 free(client.bookmarks);
1908 cert_free();
1909 }
1910