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