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