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