💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Vgmi › files › 8e11c777cf661d37c99885ddde015… captured on 2023-09-08 at 16:22:47. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 /* See LICENSE file for copyright and license details. */
1 #ifdef __linux__
2 #define _GNU_SOURCE
3 #endif
4 #ifdef sun
5 #include <port.h>
6 #endif
7 #include <pthread.h>
8 #include <netinet/in.h>
9 #include <stdint.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <strings.h>
13 #include <unistd.h>
14 #include <sys/socket.h>
15 #include <string.h>
16 #include <netdb.h>
17 #include <tls.h>
18 #include <poll.h>
19 #include <fcntl.h>
20 #include <errno.h>
21 #include <termbox.h>
22 #include <ctype.h>
23 #include <time.h>
24 #include "gemini.h"
25 #include "cert.h"
26 #include "wcwidth.h"
27 #include "display.h"
28 #include "input.h"
29 #include "sandbox.h"
30 #include "str.h"
31 #include "url.h"
32 #include "util.h"
33
34 #define MAX_CACHE 10
35 #define TIMEOUT 8
36 struct timespec timeout = {0, 10000000};
37
38 struct tls_config* config;
39 struct tls_config* config_empty;
40 struct gmi_client client;
41
42 void tb_colorline(int x, int y, uintattr_t color);
43 void* gmi_request_thread(struct gmi_tab* tab);
44
45 void fatal() {
46 tb_shutdown();
47 printf("Failed to allocate memory, terminating\n");
48 exit(0);
49 }
50
51 int fatalI() {
52 fatal();
53 return -1;
54 }
55
56 void* fatalP() {
57 fatal();
58 return NULL;
59 }
60
61 void memory_failure(struct gmi_tab* tab) {
62 if (!tab) return;
63 tab->show_error = 1;
64 snprintf(tab->error, sizeof(tab->error),
65 "memory allocation failure");
66 }
67
68 int gmi_goto(struct gmi_tab* tab, int id) {
69 id--;
70 struct gmi_page* page = &tab->page;
71 if (id < 0 || id >= page->links_count) {
72 snprintf(tab->error, sizeof(tab->error),
73 "Invalid link number, %d/%d", id, page->links_count);
74 tab->show_error = 1;
75 client.input.mode = 0;
76 return -1;
77 }
78 gmi_cleanforward(tab);
79 int ret = gmi_nextlink(tab, tab->url, page->links[id]);
80 return ret;
81 }
82
83 int gmi_goto_new(struct gmi_tab* tab, int id) {
84 id--;
85 struct gmi_page* page = &tab->page;
86 if (id < 0 || id >= page->links_count) {
87 snprintf(tab->error, sizeof(tab->error),
88 "Invalid link number, %d/%d", id, page->links_count);
89 tab->show_error = 1;
90 client.input.mode = 0;
91 return -1;
92 }
93 struct gmi_tab* old_tab = client.tab;
94 client.tab = gmi_newtab_url(NULL);
95 int ret = gmi_nextlink(client.tab, old_tab->url,
96 old_tab->page.links[id]);
97 if (ret) gmi_freetab(client.tab);
98
99 return ret;
100 }
101
102 int gmi_nextlink(struct gmi_tab* tab, char* url, char* link) {
103 int url_len = strnlen(url, MAX_URL);
104 if (!strcmp(link, "about:home")) {
105 gmi_gohome(tab, 1);
106 return 0;
107 } else if (link[0] == '/' && link[1] == '/') {
108 char buf[1024];
109 strlcpy(buf, &link[2], MAX_URL - 2);
110 int len = STRNLEN(buf);
111 if (len < MAX_URL - 1 && buf[len - 1] != '/') {
112 buf[len] = '/';
113 buf[len + 1] = '\0';
114 }
115 int ret = gmi_request(tab, buf, 1);
116 if (ret < 1) return ret;
117 return ret;
118 } else if (link[0] == '/') {
119 if (url_len > GMI && strstr(url, "gemini://")) {
120 char* ptr = strchr(&url[GMI], '/');
121 if (ptr) *ptr = '\0';
122 }
123 char urlbuf[MAX_URL];
124 size_t l = STRLCPY(urlbuf, url);
125 if (l >= sizeof(urlbuf))
126 goto nextlink_overflow;
127 if (strlcpy(urlbuf + l, link, sizeof(urlbuf) - l) >=
128 sizeof(urlbuf) - l)
129 goto nextlink_overflow;
130 int ret = gmi_request(tab, urlbuf, 1);
131 return ret;
132 } else if (strstr(link, "https://") == link ||
133 strstr(link, "http://") == link ||
134 strstr(link, "gopher://") == link) {
135 #ifndef DISABLE_XDG
136 if (client.xdg && !xdg_open(link)) return -1;
137 #endif
138 tab->show_error = 1;
139 snprintf(tab->error, sizeof(tab->error),
140 "Unable to open the link");
141 return -1;
142 } else if (strstr(link, "gemini://") == link) {
143 int ret = gmi_request(tab, link, 1);
144 return ret;
145 } else {
146 char* ptr = strrchr(&url[GMI], '/');
147 if (ptr) *ptr = '\0';
148 char urlbuf[MAX_URL];
149 size_t l = STRLCPY(urlbuf, url);
150 if (l >= sizeof(urlbuf))
151 goto nextlink_overflow;
152 if (urlbuf[l-1] != '/') {
153 size_t l2 = strlcpy(urlbuf + l, "/",
154 sizeof(urlbuf) - l);
155 if (l2 >= sizeof(urlbuf) - l)
156 goto nextlink_overflow;
157 l += l2;
158 }
159 size_t l2 = strlcpy(urlbuf + l, link, sizeof(urlbuf) - l);
160 if (l2 >= sizeof(urlbuf) - l)
161 goto nextlink_overflow;
162 int ret = gmi_request(tab, urlbuf, 1);
163 return ret;
164 }
165 nextlink_overflow:
166 tab->show_error = 1;
167 snprintf(tab->error, sizeof(tab->error),
168 "Link too long, above 1024 characters");
169 return -1;
170 }
171
172 void gmi_load(struct gmi_page* page) {
173 for (int i=0; i < page->links_count; i++)
174 free(page->links[i]);
175 free(page->links);
176 page->links = NULL;
177 page->links_count = 0;
178 page->lines = 0;
179 if (!STARTWITH(page->meta, "text/gemini")) {
180 page->links = NULL;
181 page->links_count = 0;
182 page->lines = 0;
183 return;
184 }
185 int x = 0;
186 for (int c = 0; c < page->data_len; c++) {
187 if (x == 0 && page->data[c] == '=' &&
188 page->data[c + 1] == '>') {
189 c += 2;
190 for (; c < page->data_len && (page->data[c] == ' ' ||
191 page->data[c] == '\t');
192 c++) ;
193 char* url = (char*)&page->data[c];
194 c += parse_link(&page->data[c], page->data_len - c);
195 if (page->data[c - 1] == 127)
196 c--;
197 char save = page->data[c];
198 page->data[c] = '\0';
199 void *ptr = NULL;
200 while (!ptr) {
201 ptr = page->links ? realloc(page->links,
202 sizeof(char*) * (page->links_count+1)):
203 malloc(sizeof(char*));
204 if (!ptr) sleep(1);
205 }
206 page->links = ptr;
207
208 if (url[0] == '\0') {
209 page->links[page->links_count] = NULL;
210 page->data[c] = save;
211 continue;
212 }
213 int len = strnlen(url, MAX_URL);
214 ptr = NULL;
215 while (!ptr) {
216 ptr = malloc(len+2);
217 if (!ptr) sleep(1);
218 }
219 page->links[page->links_count] = ptr;
220 memcpy(page->links[page->links_count], url, len+1);
221 page->links_count++;
222 page->data[c] = save;
223 }
224 if (page->data[c] == '\n') {
225 page->lines++;
226 x = 0;
227 continue;
228 }
229 x++;
230 }
231 }
232
233 int gmi_render(struct gmi_tab* tab) {
234 pthread_mutex_lock(&tab->render_mutex);
235 #include "img.h"
236 #ifdef TERMINAL_IMG_VIEWER
237 if (STARTWITH(tab->page.meta, "image/")) {
238 if (!tab->page.img.tried) {
239 char* ptr = strchr(tab->page.data, '\n');
240 if (!ptr) {
241 tb_printf(2, -tab->scroll,
242 TB_DEFAULT, TB_DEFAULT,
243 "Invalid data: new line not found");
244 tab->page.img.tried = 1;
245 pthread_mutex_unlock(&tab->render_mutex);
246 return 1;
247 }
248 ptr++;
249 if (tab->page.img.data) {
250 stbi_image_free(tab->page.img.data);
251 tab->page.img.data = NULL;
252 }
253 tab->page.img.data =
254 stbi_load_from_memory((unsigned char*)ptr,
255 tab->page.data_len -
256 (int)(ptr-tab->page.data),
257 &tab->page.img.w,
258 &tab->page.img.h,
259 &tab->page.img.channels, 3);
260 if (!tab->page.img.data) {
261 tb_printf(2, -tab->scroll,
262 TB_DEFAULT, TB_DEFAULT,
263 "Failed to decode image: %s;",
264 tab->page.meta);
265 tab->page.img.tried = 1;
266 pthread_mutex_unlock(&tab->render_mutex);
267 return 1;
268 }
269
270 tab->page.img.tried = 1;
271 }
272 if (tab->page.img.data) {
273 img_display(tab->page.img.data,
274 tab->page.img.w, tab->page.img.h,
275 client.tabs_count>1);
276 pthread_mutex_unlock(&tab->render_mutex);
277 return 1;
278 }
279 }
280 #endif
281 int text = 0;
282 if (!STARTWITH(tab->page.meta, "text/gemini")) {
283 if (!STARTWITH(tab->page.meta, "text/")) {
284 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,
285 "Unable to render format : %s",
286 tab->page.meta);
287 pthread_mutex_unlock(&tab->render_mutex);
288 return 1;
289 }
290 text = 1;
291 }
292 int line = 0;
293 int x = 0;
294 int links = 0;
295 uintattr_t color = TB_DEFAULT;
296 int start = 1;
297 int ignore = 0;
298 int h = tb_height() - 2 - (client.tabs_count > 1);
299 char* search = client.input.field[0] == '/' ?
300 &client.input.field[1] : 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);
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 (!STARTWITH(page->meta, "text/gemini")) {
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 sanitize(page->title, len);
770 return;
771 }
772 char* str = strrchr(url, '/');
773 if (!str) {
774 len = STRLCPY(page->title, url);
775 goto sanitize;
776 }
777 if (str[1] != '\0')
778 str++;
779 len = STRLCPY(page->title, str);
780 sanitize:
781 for (size_t i = 0; i < len; i++)
782 if (page->title[i] == '?')
783 page->title[i] = 0;
784 }
785
786 int gmi_removebookmark(int index) {
787 index--;
788 if (index < 0) return -1;
789 int fail = -1;
790 for (int i = 0; client.bookmarks[i]; i++) {
791 if (i == index) {
792 free(client.bookmarks[i]);
793 fail = 0;
794 }
795 if (!fail)
796 client.bookmarks[i] = client.bookmarks[i+1];
797 }
798 return fail;
799 }
800
801 void gmi_addbookmark(struct gmi_tab* tab, char* url, char* title) {
802 if (!strcmp(url, "about:home")) {
803 snprintf(tab->error, sizeof(tab->error),
804 "Cannot add the new tab page to the bookmarks");
805 tab->show_error = 1;
806 return;
807 }
808 int title_len = 0;
809 if (!title) {
810 gmi_gettitle(&tab->page, tab->url);
811 title = tab->page.title;
812 title_len = STRNLEN(tab->page.title);
813 if (!title_len) {
814 title_len = sizeof("no title");
815 title = "no title";
816 }
817 } else title_len = strnlen(title, 128);
818 long n = 0;
819 while (client.bookmarks[n]) n++;
820 void* ptr = realloc(client.bookmarks, sizeof(char*) * (n + 2));
821 if (!ptr) {
822 memory_failure(tab);
823 return;
824 }
825 client.bookmarks = ptr;
826 int len = strnlen(url, MAX_URL) + title_len + 2;
827 client.bookmarks[n] = malloc(len);
828 if (!client.bookmarks[n]) {
829 memory_failure(tab);
830 return;
831 }
832 if (title)
833 snprintf(client.bookmarks[n], len, "%s %s", url, title);
834 else
835 snprintf(client.bookmarks[n], len, "%s", url);
836 client.bookmarks[n + 1] = NULL;
837 gmi_savebookmarks();
838 }
839
840 int gmi_savebookmarks() {
841 #ifdef SANDBOX_SUN
842 int fd = wr_pair[1];
843 if (send(fd, &WR_BOOKMARKS, sizeof(SBC), 0) != sizeof(SBC))
844 return -1;
845 #else
846 int fd = openat(config_fd, "bookmarks.txt",
847 O_CREAT|O_WRONLY|O_CLOEXEC|O_TRUNC, 0600);
848 if (fd < 0) {
849 printf("Failed to write bookmarks, %s\n", strerror(errno));
850 return -1;
851 }
852 #ifdef SANDBOX_FREEBSD
853 if (makefd_writeonly(fd)) {
854 close(fd);
855 return -1;
856 }
857 #endif
858 #endif
859 for (int i = 0; client.bookmarks[i]; i++) {
860 write(fd, client.bookmarks[i], strlen(client.bookmarks[i]));
861 char c = '\n';
862 write(fd, &c, 1);
863 }
864 #ifdef SANDBOX_SUN
865 send(fd, &WR_END, sizeof(SBC), 0);
866 #else
867 close(fd);
868 #endif
869 return 0;
870 }
871
872 char* gmi_getbookmarks(int* len) {
873 char* data = NULL;
874 int n = 0;
875 for (int i = 0; client.bookmarks[i]; i++) {
876 char line[2048];
877 long length = snprintf(line, sizeof(line), "=>%s\n ",
878 client.bookmarks[i]);
879 void *ptr = realloc(data, n+length+1);
880 if (!ptr) {
881 *len = n;
882 return data;
883 }
884 data = ptr;
885 strlcpy(&data[n], line, length);
886 n += length-1;
887 }
888 *len = n;
889 return data;
890 }
891
892 void gmi_gohome(struct gmi_tab* tab, int add) {
893 STRLCPY(tab->url, "about:home");
894 int bm = 0;
895 char* data = gmi_getbookmarks(&bm);
896 pthread_mutex_lock(&tab->render_mutex);
897 tab->request.data = malloc(sizeof(home_page) + bm);
898 if (!tab->request.data) {
899 memory_failure(tab);
900 pthread_mutex_unlock(&tab->render_mutex);
901 return;
902 }
903 bzero(tab->request.data, sizeof(home_page) + bm);
904
905 tab->request.recv = snprintf(tab->request.data, sizeof(home_page) + bm,
906 home_page, data ? data : "");
907 free(data);
908
909 STRLCPY(tab->request.meta, "text/gemini");
910
911 if (!add)
912 gmi_freepage(&tab->page);
913 bzero(&tab->page, sizeof(struct gmi_page));
914 tab->page.data = tab->request.data;
915 tab->page.data_len = tab->request.recv;
916 tab->page.code = 20;
917 STRLCPY(tab->page.meta, tab->request.meta);
918 gmi_load(&tab->page);
919 if (add)
920 gmi_addtohistory(tab);
921 else if (tab->history) {
922 tab->history->page = tab->page;
923 tab->history->cached = 1;
924 }
925 tab->scroll = -1;
926 tab->request.data = NULL;
927 pthread_mutex_unlock(&tab->render_mutex);
928 }
929
930 struct gmi_tab* gmi_newtab() {
931 return gmi_newtab_url("about:home");
932 }
933
934 struct gmi_tab* gmi_newtab_url(const char* url) {
935
936 if (url && strcmp(url, "about:home")) {
937 struct gmi_tab tmp;
938 int proto = parse_url(url,
939 tmp.request.host, sizeof(tmp.request.host),
940 tmp.request.url, sizeof(tmp.request.url),
941 &tmp.request.port);
942 switch (proto) {
943 case PROTO_GEMINI:
944 break;
945 case PROTO_HTTP:
946 case PROTO_HTTPS:
947 case PROTO_GOPHER:
948 xdg_request(url);
949 /* fallthrough */
950 default:
951 return client.tab;
952 }
953 }
954
955 client.tabs_count++;
956 if (client.tab) {
957 struct gmi_tab* next = client.tab->next;
958 client.tab->next = malloc(sizeof(struct gmi_tab));
959 if (!client.tab->next) {
960 memory_failure(client.tab);
961 return client.tab;
962 }
963 bzero(client.tab->next, sizeof(struct gmi_tab));
964 client.tab->next->next = next;
965 client.tab->next->prev = client.tab;
966 client.tab = client.tab->next;
967 if (next)
968 next->prev = client.tab;
969 } else {
970 client.tab = malloc(sizeof(struct gmi_tab));
971 if (!client.tab) return fatalP();
972 bzero(client.tab, sizeof(struct gmi_tab));
973 }
974 pthread_mutex_init(&client.tab->render_mutex, NULL);
975
976 if (socketpair(AF_UNIX, SOCK_STREAM, 0, client.tab->thread.pair))
977 return client.tab;
978 pthread_create(&client.tab->thread.thread, NULL,
979 (void *(*)(void *))gmi_request_thread,
980 (void*)client.tab);
981 client.tab->thread.started = 1;
982 if (url)
983 gmi_request(client.tab, url, 1);
984
985 client.tab->scroll = -1;
986 return client.tab;
987 }
988
989 int gmi_request_init(struct gmi_tab* tab, const char* url, int add) {
990 if (!tab) return -1;
991 if (!strcmp(url, "about:home")) {
992 gmi_gohome(tab, add);
993 return tab->page.data_len;
994 }
995 tab->show_error = 0;
996 if (tab->history) tab->history->scroll = tab->scroll;
997 tab->selected = 0;
998 tab->request.host[0] = '\0';
999 int proto = parse_url(url,
1000 tab->request.host, sizeof(tab->request.host),
1001 tab->request.url, sizeof(tab->request.url),
1002 &tab->request.port);
1003
1004 if (proto == -2) {
1005 tab->show_error = 1;
1006 snprintf(tab->error, sizeof(tab->error),
1007 "Link too long, above 1024 characters");
1008 return -1;
1009 }
1010 if (proto == PROTO_FILE) {
1011 return gmi_loadfile(tab, &tab->request.url[P_FILE]);
1012 }
1013 if (proto != PROTO_GEMINI) {
1014 #ifndef DISABLE_XDG
1015 if (client.xdg && !xdg_open((char*)url)) return 1;
1016 #endif
1017 tab->show_error = 1;
1018 snprintf(tab->error, sizeof(tab->error),
1019 "Unable to open the link");
1020 return -1;
1021 }
1022 if (tab->request.tls)
1023 tls_reset(tab->request.tls);
1024 else
1025 tab->request.tls = tls_client();
1026 if (!tab->request.tls) {
1027 snprintf(tab->error, sizeof(tab->error),
1028 "Failed to initialize TLS");
1029 tab->show_error = 1;
1030 return -1;
1031 }
1032
1033 int cert = cert_getcert(tab->request.host, 0);
1034 if (cert >= 0 &&
1035 tls_config_set_keypair_mem(config,
1036 (unsigned char*)client.certs[cert].crt,
1037 client.certs[cert].crt_len,
1038 (unsigned char*)client.certs[cert].key,
1039 client.certs[cert].key_len))
1040 {
1041 tab->show_error = 1;
1042 snprintf(tab->error, sizeof(tab->error), "tls error: %s",
1043 tls_config_error(config));
1044 return -1;
1045 }
1046
1047 if (tls_configure(tab->request.tls, cert>=0?config:config_empty)) {
1048 snprintf(tab->error, sizeof(tab->error),
1049 "Failed to configure TLS");
1050 tab->show_error = 1;
1051 return -1;
1052 }
1053 tab->request.state = STATE_REQUESTED;
1054 return 0;
1055 }
1056
1057 #ifdef __linux
1058 void dns_async(union sigval sv) {
1059 struct gmi_tab* tab = sv.sival_ptr;
1060 tab->request.resolved = 1;
1061 }
1062 #endif
1063
1064 int gmi_request_dns(struct gmi_tab* tab) {
1065 char host[1024];
1066 if (idn_to_ascii(tab->request.host, sizeof(tab->request.host),
1067 host, sizeof(host))) {
1068 snprintf(tab->error, sizeof(tab->error),
1069 "Invalid domain name: %s", tab->request.host);
1070 tab->show_error = 1;
1071 return -1;
1072 }
1073 STRLCPY(tab->request.host, host);
1074 #if defined(__linux__) && !defined(__MUSL__)
1075 tab->request.gaicb_ptr = malloc(sizeof(struct gaicb));
1076 if (!tab->request.gaicb_ptr) {
1077 memory_failure(tab);
1078 return -1;
1079 }
1080 bzero(tab->request.gaicb_ptr, sizeof(struct gaicb));
1081 tab->request.gaicb_ptr->ar_name = tab->request.host;
1082 tab->request.resolved = 0;
1083 struct sigevent sevp;
1084 bzero(&sevp, sizeof(sevp));
1085 sevp.sigev_notify = SIGEV_THREAD;
1086 sevp.sigev_notify_function = dns_async;
1087 sevp.sigev_value.sival_ptr = tab;
1088 int ret = getaddrinfo_a(GAI_NOWAIT, &tab->request.gaicb_ptr, 1, &sevp);
1089 if (ret) {
1090 snprintf(tab->error, sizeof(tab->error),
1091 "Unable request domain name: %s", tab->request.host);
1092 tab->show_error = 1;
1093 return -1;
1094 }
1095
1096 long start = time(NULL);
1097 for (int i=0; !tab->request.resolved; i++) {
1098 if (tab->request.state == STATE_CANCEL) break;
1099 if (time(NULL) - start > TIMEOUT) break;
1100 nanosleep(&timeout, NULL);
1101 }
1102
1103 if (tab->request.resolved != 1 ||
1104 tab->request.gaicb_ptr->ar_result == NULL) {
1105 gai_cancel(tab->request.gaicb_ptr);
1106 free(tab->request.gaicb_ptr);
1107 snprintf(tab->error, sizeof(tab->error),
1108 "Unknown domain name: %s", tab->request.host);
1109 tab->show_error = 1;
1110 return -1;
1111 }
1112 struct addrinfo *result = tab->request.gaicb_ptr->ar_result;
1113 #else
1114 struct addrinfo hints, *result;
1115 bzero(&hints, sizeof(hints));
1116 hints.ai_family = AF_INET;
1117 hints.ai_socktype = SOCK_STREAM;
1118 hints.ai_flags |= AI_CANONNAME;
1119 errno = 0;
1120
1121 if ((getaddrinfo(tab->request.host, NULL, &hints, &result))) {
1122 snprintf(tab->error, sizeof(tab->error),
1123 "Unknown domain name: %s, %s",
1124 tab->request.host, strerror(errno));
1125 tab->show_error = 1;
1126 return -1;
1127 }
1128 #endif
1129
1130 struct sockaddr_in addr4;
1131 struct sockaddr_in6 addr6;
1132 bzero(&addr4, sizeof(addr4));
1133 bzero(&addr6, sizeof(addr6));
1134
1135 int error = 0;
1136 if (result->ai_family == AF_INET) {
1137 addr4.sin_addr =
1138 ((struct sockaddr_in*)result->ai_addr)->sin_addr;
1139 addr4.sin_family = AF_INET;
1140 addr4.sin_port = htons(tab->request.port);
1141 tab->request._addr.addr4 = addr4;
1142 tab->request.addr =
1143 (struct sockaddr*)&tab->request._addr.addr4;
1144 }
1145 else if (result->ai_family == AF_INET6) {
1146 addr6.sin6_addr =
1147 ((struct sockaddr_in6*)result->ai_addr)->sin6_addr;
1148 addr6.sin6_family = AF_INET6;
1149 addr6.sin6_port = htons(tab->request.port);
1150 tab->request._addr.addr6 = addr6;
1151 tab->request.addr =
1152 (struct sockaddr*)&tab->request._addr.addr6;
1153 } else {
1154 snprintf(tab->error, sizeof(tab->error),
1155 "Unexpected error, invalid address family %s",
1156 tab->request.host);
1157 error = 1;
1158 }
1159 tab->request.family = result->ai_family;
1160 freeaddrinfo(result);
1161 #ifdef __linux__
1162 free(tab->request.gaicb_ptr);
1163 #endif
1164 if (error) {
1165 tab->request.state = STATE_CANCEL;
1166 return -1;
1167 }
1168
1169 tab->request.state = STATE_DNS;
1170 return 0;
1171 }
1172
1173 int gmi_request_connect(struct gmi_tab* tab) {
1174
1175 tab->request.socket = socket(tab->request.family, SOCK_STREAM, 0);
1176 if (tab->request.socket == -1) {
1177 snprintf(tab->error, sizeof(tab->error),
1178 "Failed to create socket");
1179 return -1;
1180 }
1181 int flags = fcntl(tab->request.socket, F_GETFL);
1182 if (flags == -1 ||
1183 fcntl(tab->request.socket, F_SETFL, flags|O_NONBLOCK) == -1) {
1184 snprintf(tab->error, sizeof(tab->error),
1185 "Failed to create non-blocking socket");
1186 return -1;
1187 }
1188
1189 int addr_size = (tab->request.family == AF_INET)?
1190 sizeof(struct sockaddr_in):
1191 sizeof(struct sockaddr_in6);
1192
1193 int connected = 0;
1194 int failed = connect(tab->request.socket,
1195 tab->request.addr, addr_size);
1196 failed = failed?(errno != EAGAIN && errno != EWOULDBLOCK &&
1197 errno != EINPROGRESS && errno != EALREADY &&
1198 errno != 0):failed;
1199 while (!failed) {
1200 struct pollfd fds[2];
1201 fds[0].fd = tab->thread.pair[0];
1202 fds[0].events = POLLIN;
1203 fds[1].fd = tab->request.socket;
1204 fds[1].events = POLLOUT;
1205 int count = poll(fds, 2, TIMEOUT * 1000);
1206 if (count < 1 || fds[1].revents != POLLOUT ||
1207 tab->request.state == STATE_CANCEL) break;
1208 int value;
1209 socklen_t len = sizeof(value);
1210 int ret = getsockopt(tab->request.socket, SOL_SOCKET,
1211 SO_ERROR, &value, &len);
1212 connected = (value == 0 && ret == 0);
1213 break;
1214 }
1215
1216 if (!connected) {
1217 snprintf(tab->error, sizeof(tab->error),
1218 "Connection to %s timed out : %s",
1219 tab->request.host, strerror(errno));
1220 return -1;
1221 }
1222
1223 if (tls_connect_socket(tab->request.tls, tab->request.socket,
1224 tab->request.host)) {
1225 snprintf(tab->error, sizeof(tab->error),
1226 "Unable to connect to: %s : %s",
1227 tab->request.host, tls_error(tab->request.tls));
1228 return -1;
1229 }
1230 return 0;
1231 }
1232
1233 int gmi_request_handshake(struct gmi_tab* tab) {
1234 if (tls_connect_socket(tab->request.tls,
1235 tab->request.socket,
1236 tab->request.host)) {
1237 snprintf(tab->error, sizeof(tab->error),
1238 "Unable to connect to: %s : %s",
1239 tab->request.host, tls_error(tab->request.tls));
1240 return -1;
1241 }
1242 if (tab->request.state == STATE_CANCEL) return -1;
1243 int ret = 0;
1244 time_t start = time(0);
1245 while ((ret = tls_handshake(tab->request.tls))) {
1246 if (time(NULL) - start > TIMEOUT ||
1247 tab->request.state == STATE_CANCEL ||
1248 (ret < 0 &&
1249 ret != TLS_WANT_POLLIN &&
1250 ret != TLS_WANT_POLLOUT))
1251 break;
1252 if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT)
1253 nanosleep(&timeout, NULL);
1254 }
1255 if (ret) {
1256 snprintf(tab->error, sizeof(tab->error),
1257 "Failed to handshake: %s (%s)",
1258 tls_error(tab->request.tls),
1259 tab->request.host);
1260 return -1;
1261 }
1262 if (tab->request.state == STATE_CANCEL) return -1;
1263 ret = cert_verify(tab->request.host,
1264 tls_peer_cert_hash(tab->request.tls),
1265 tls_peer_cert_notbefore(tab->request.tls),
1266 tls_peer_cert_notafter(tab->request.tls));
1267 switch (ret) {
1268 case 0: // success
1269 break;
1270 case -1: // invalid certificate
1271 snprintf(tab->error, sizeof(tab->error),
1272 "Failed to verify server certificate for %s" \
1273 "(The certificate changed)",
1274 tab->request.host);
1275 return -1;
1276 case -3: // sandbox error
1277 snprintf(tab->error, sizeof(tab->error),
1278 "Sandbox error when verifying server certificate " \
1279 "for %s", tab->request.host);
1280 return -1;
1281 case -5: // expired
1282 if (cert_should_ignore(tab->request.host)) break;
1283 snprintf(tab->error, sizeof(tab->error),
1284 "Expired certificate, " \
1285 "the certificate for %s has expired",
1286 tab->request.host);
1287 return -1;
1288 case -6: // certificate changed
1289 snprintf(tab->error, sizeof(tab->error),
1290 "Invalid certificate, enter \":forget %s\"" \
1291 " to forget the old certificate.",
1292 tab->request.host);
1293 return -1;
1294 default: // failed to write certificate
1295 snprintf(tab->error, sizeof(tab->error),
1296 "Failed to write %s certificate information : %s [%d]",
1297 tab->request.host, strerror(errno), ret);
1298 return -1;
1299 }
1300
1301 if (tab->request.state == STATE_CANCEL) return -1;
1302 char buf[MAX_URL];
1303 STRLCPY(buf, "gemini://");
1304 char toascii[1024];
1305 if (idn_to_ascii(&tab->request.url[GMI],
1306 sizeof(tab->request.url) - GMI,
1307 toascii, sizeof(toascii))) {
1308 snprintf(tab->error, sizeof(tab->error),
1309 "Failed to parse url: %s", &tab->request.url[GMI]);
1310 return -1;
1311 }
1312 ssize_t len = strlcpy(&buf[GMI], toascii, sizeof(buf) - GMI) + GMI;
1313 char url[1024];
1314 if (len < MAX_URL)
1315 len = parse_query(buf, MAX_URL, url, MAX_URL);
1316 if (len >= MAX_URL ||
1317 (len += strlcpy(&url[len], "\r\n", MAX_URL - len)) >= MAX_URL) {
1318 snprintf(tab->error, sizeof(tab->error),
1319 "Url too long: %s", url);
1320 return -1;
1321 }
1322
1323 if (tls_write(tab->request.tls, url, len) < len) {
1324 snprintf(tab->error, sizeof(tab->error),
1325 "Failed to send data to: %s", tab->request.host);
1326 return -1;
1327 }
1328
1329 if (tab->request.state == STATE_CANCEL) return -1;
1330 tab->request.state = STATE_CONNECTED;
1331 return 0;
1332 }
1333
1334 int gmi_request_header(struct gmi_tab* tab) {
1335 time_t now = time(0);
1336 char buf[1024];
1337 int recv = 0;
1338 while (1) {
1339 if (tab->request.state == STATE_CANCEL) break;
1340 if (time(0) - now >= TIMEOUT) {
1341 snprintf(tab->error, sizeof(tab->error),
1342 "Connection to %s timed out (sent no data)",
1343 tab->request.host);
1344 return -1;
1345 }
1346 int n = tls_read(tab->request.tls, &buf[recv],
1347 sizeof(buf) - recv - 1);
1348 if (n == TLS_WANT_POLLIN) {
1349 nanosleep(&timeout, NULL);
1350 continue;
1351 }
1352 if (n < 1) {
1353 recv = -1;
1354 break;
1355 }
1356 recv += n;
1357 buf[recv] = '\0';
1358 if (strstr(buf, "\r\n")) break;
1359 }
1360 if (tab->request.state == STATE_CANCEL) return -1;
1361
1362 if (recv <= 0) {
1363 snprintf(tab->error, sizeof(tab->error),
1364 "[%d] Invalid data from: %s",
1365 recv, tab->request.host);
1366 return -1;
1367 }
1368 if (!strstr(buf, "\r\n")) {
1369 snprintf(tab->error, sizeof(tab->error),
1370 "Invalid data from: %s (no CRLF)", tab->request.host);
1371 return -1;
1372 }
1373 char* ptr = strchr(buf, ' ');
1374 if (!ptr) {
1375 snprintf(tab->error, sizeof(tab->error),
1376 "Invalid data from: %s (no metadata)",
1377 tab->request.host);
1378 return -1;
1379 }
1380 STRLCPY(tab->request.error, ptr + 1);
1381
1382 int previous_code = tab->page.code;
1383 char c = buf[2];
1384 buf[2] = '\0';
1385 tab->page.code = atoi(buf);
1386 buf[2] = c;
1387
1388 if (tab->request.state == STATE_CANCEL) return -1;
1389 switch (tab->page.code) {
1390 case 10:
1391 case 11:
1392 ptr++;
1393 STRLCPY(client.input.label, ptr);
1394 ptr = strchr(client.input.label, '\n');
1395 if (ptr) *ptr = '\0';
1396 ptr = strchr(client.input.label, '\r');
1397 if (ptr) *ptr = '\0';
1398 tab->request.state = STATE_RECV_HEADER;
1399 tab->request.recv = recv;
1400 return 2; // input
1401 case 20:
1402 {
1403 #ifdef TERMINAL_IMG_VIEWER
1404 tab->page.img.tried = 0;
1405 #endif
1406 char* meta = strchr(++ptr, '\r');
1407 if (!meta) {
1408 snprintf(tab->error, sizeof(tab->error),
1409 "Invalid data from: %s", tab->request.host);
1410 return -1;
1411 }
1412 *meta = '\0';
1413 STRLCPY(tab->request.meta, ptr);
1414 *meta = '\r';
1415 if ((strcasestr(tab->request.meta, "charset=") &&
1416 !strcasestr(tab->request.meta, "charset=utf-8"))
1417 || ((!STARTWITH(tab->request.meta, "text/"))
1418 #ifdef TERMINAL_IMG_VIEWER
1419 && (!STARTWITH(tab->request.meta, "image/"))
1420 #endif
1421 )) {
1422 if (tab->history)
1423 STRLCPY(tab->url, tab->history->url);
1424 else
1425 STRLCPY(tab->url, "about:home");
1426 tab->request.ask = 2;
1427 snprintf(tab->request.info, sizeof(tab->request.info),
1428 "# Non-renderable meta-data : %s",
1429 tab->request.meta);
1430 STRLCPY(tab->request.action, "download");
1431 tb_interupt();
1432 while (tab->request.ask == 2)
1433 nanosleep(&timeout, NULL);
1434 tab->request.download = tab->request.ask;
1435 if (!tab->request.download) {
1436 tab->request.recv = -1;
1437 return 1; // download
1438 }
1439 }
1440 char* ptr_meta = strchr(tab->page.meta, ';');
1441 if (ptr_meta) *ptr_meta = '\0';
1442 }
1443 break;
1444 case 30:
1445 snprintf(tab->error, sizeof(tab->error),
1446 "Redirect temporary");
1447 break;
1448 case 31:
1449 snprintf(tab->error, sizeof(tab->error),
1450 "Redirect permanent");
1451 break;
1452 case 40:
1453 snprintf(tab->error, sizeof(tab->error),
1454 "Temporary failure");
1455 return -2;
1456 case 41:
1457 snprintf(tab->error, sizeof(tab->error),
1458 "Server unavailable");
1459 return -2;
1460 case 42:
1461 snprintf(tab->error, sizeof(tab->error),
1462 "CGI error");
1463 return -2;
1464 case 43:
1465 snprintf(tab->error, sizeof(tab->error),
1466 "Proxy error");
1467 return -2;
1468 case 44:
1469 snprintf(tab->error, sizeof(tab->error),
1470 "Slow down: server above rate limit");
1471 return -2;
1472 case 50:
1473 snprintf(tab->error, sizeof(tab->error),
1474 "Permanent failure");
1475 return -2;
1476 case 51:
1477 snprintf(tab->error, sizeof(tab->error),
1478 "Not found %s", tab->request.url);
1479 return -2;
1480 case 52:
1481 snprintf(tab->error, sizeof(tab->error),
1482 "Resource gone");
1483 return -2;
1484 case 53:
1485 snprintf(tab->error, sizeof(tab->error),
1486 "Proxy request refused");
1487 return -2;
1488 case 59:
1489 snprintf(tab->error, sizeof(tab->error),
1490 "Bad request");
1491 return -2;
1492 case 60:
1493 snprintf(tab->error, sizeof(tab->error),
1494 "Client certificate required");
1495 return -2;
1496 case 61:
1497 snprintf(tab->error, sizeof(tab->error),
1498 "Client not authorised");
1499 return -2;
1500 case 62:
1501 snprintf(tab->error, sizeof(tab->error),
1502 "Client certificate not valid");
1503 return -2;
1504 default:
1505 snprintf(tab->error, sizeof(tab->error),
1506 "Unknown status code: %d", tab->page.code);
1507 tab->page.code = previous_code;
1508 return -2;
1509 }
1510 if (tab->request.state == STATE_CANCEL) return -1;
1511 tab->request.state = STATE_RECV_HEADER;
1512 tab->request.recv = recv;
1513 tab->request.data = malloc(tab->request.recv + 1);
1514 if (!tab->request.data) {
1515 memory_failure(tab);
1516 return -1;
1517 }
1518 memcpy(tab->request.data, buf, recv);
1519 return 0;
1520 }
1521
1522 int gmi_request_body(struct gmi_tab* tab, int fd) {
1523 time_t now = time(0);
1524 char buf[1024];
1525 int isText = STARTWITH(tab->request.meta, "text/");
1526 while (tab->request.state != STATE_CANCEL) {
1527 if (time(0) - now >= TIMEOUT) {
1528 snprintf(tab->error, sizeof(tab->error),
1529 "Server %s stopped responding",
1530 tab->request.host);
1531 return -1;
1532 }
1533 int bytes = tls_read(tab->request.tls, buf, sizeof(buf));
1534 if (tab->request.state == STATE_CANCEL) return -1;
1535 if (bytes == 0) break;
1536 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) {
1537 nanosleep(&timeout, NULL);
1538 continue;
1539 }
1540 if (bytes < 1) {
1541 snprintf(tab->error, sizeof(tab->error),
1542 "Invalid data from %s: %s",
1543 tab->request.host,
1544 tls_error(tab->request.tls));
1545 return -1;
1546 }
1547 if (fd > 0) {
1548 if (write(fd, buf, bytes) != bytes) {
1549 snprintf(tab->error, sizeof(tab->error),
1550 "Failed to download file: %s",
1551 strerror(errno));
1552 return -1;
1553 }
1554 now = time(0);
1555 continue;
1556 }
1557 /* allocate addional memory in case of reading an incomplete
1558 * unicode character at the end of the page */
1559 void *ptr = realloc(tab->request.data,
1560 tab->request.recv + bytes + 32);
1561 if (!ptr) {
1562 free(tab->request.data);
1563 tab->request.data = NULL;
1564 memory_failure(tab);
1565 return -1;
1566 }
1567 tab->request.data = ptr;
1568 memcpy(&tab->request.data[tab->request.recv], buf, bytes);
1569 memset(&tab->request.data[tab->request.recv + bytes], 0, 32);
1570 tab->request.recv += bytes;
1571 /* prevent the server from filling up memory */
1572 if ((isText && tab->request.recv > MAX_TEXT_SIZE) ||
1573 (!isText && tab->request.recv > MAX_IMAGE_SIZE)) {
1574 snprintf(tab->error, sizeof(tab->error),
1575 "The server sent a too large response");
1576 return -1;
1577 }
1578 now = time(0);
1579 }
1580 if (tab->request.state == STATE_CANCEL) return -1;
1581 tab->request.state = STATE_RECV_BODY;
1582 return 0;
1583 }
1584
1585 int gmi_getfd(struct gmi_tab* tab, char* path, size_t path_len) {
1586 int len = STRNLEN(tab->request.url);
1587 if (tab->request.url[len-1] == '/')
1588 tab->request.url[len-1] = '\0';
1589 char* ptr = strrchr(tab->request.url, '/');
1590 #ifndef SANDBOX_SUN
1591 int fd = getdownloadfd();
1592 if (fd < 0) {
1593 snprintf(tab->error, sizeof(tab->error),
1594 "Unable to open download folder");
1595 tab->show_error = 1;
1596 return -1;
1597 }
1598 #endif
1599 if (ptr)
1600 strlcpy(path, ptr + 1, path_len);
1601 else {
1602 #ifdef __OpenBSD__
1603 char format[] = "output_%lld.dat";
1604 #else
1605 char format[] = "output_%ld.dat";
1606 #endif
1607
1608 snprintf(path, path_len, format, time(NULL));
1609 }
1610 for (unsigned int i = 0; i < path_len && path[i] && ptr; i++) {
1611 char c = path[i];
1612 if ((path[i] == '.' && path[i+1] == '.') ||
1613 !(c == '.' ||
1614 (c >= 'A' && c <= 'Z') ||
1615 (c >= 'a' && c <= 'z') ||
1616 (c >= '0' && c <= '9')
1617 ))
1618 path[i] = '_';
1619 }
1620 #ifdef SANDBOX_SUN
1621 if (sandbox_download(tab, path))
1622 return -1;
1623 int dfd = wr_pair[1];
1624 sandbox_dl_length(-1);
1625 #else
1626 int dfd = openat(fd, path,
1627 O_CREAT|O_EXCL|O_WRONLY, 0600);
1628 if (dfd < 0 && errno == EEXIST) {
1629 char buf[1024];
1630 #ifdef __OpenBSD__
1631 char format[] = "%lld_%s";
1632 #else
1633 char format[] = "%ld_%s";
1634 #endif
1635 snprintf(buf, sizeof(buf), format, time(NULL), path);
1636 strlcpy(path, buf, path_len);
1637 dfd = openat(fd, path,
1638 O_CREAT|O_EXCL|O_WRONLY, 0600);
1639 if (dfd < 0) {
1640 snprintf(tab->error, sizeof(tab->error),
1641 "Failed to write to the download folder");
1642 tab->show_error = 1;
1643 return -1;
1644 }
1645 }
1646 #ifdef SANDBOX_FREEBSD
1647 if (makefd_writeonly(dfd)) {
1648 client.shutdown = 1;
1649 return -1;
1650 }
1651 #endif
1652 #endif
1653 return dfd;
1654 }
1655
1656 int gmi_postdownload(struct gmi_tab* tab, int fd, const char* path) {
1657
1658 #ifdef SANDBOX_SUN
1659 if (wr_pair[1] != fd)
1660 return -1;
1661 #else
1662 close(fd);
1663 #endif
1664 #ifndef DISABLE_XDG
1665 int fail = 0;
1666 if (client.xdg) {
1667 tab->request.ask = 2;
1668 snprintf(tab->request.info,
1669 sizeof(tab->request.info),
1670 "# %s download",
1671 tab->request.meta);
1672 STRLCPY(tab->request.action, "open");
1673 tb_interupt();
1674 while (tab->request.ask == 2)
1675 nanosleep(&timeout, NULL);
1676 if (tab->request.ask)
1677 fail = xdg_open(path);
1678 }
1679 if (fail) {
1680 tab->show_error = 1;
1681 snprintf(tab->error, sizeof(tab->info),
1682 "Failed to open %s", path);
1683 } else
1684 #endif
1685 {
1686 tab->show_info = 1;
1687 snprintf(tab->info, sizeof(tab->info),
1688 "File downloaded to %s", path);
1689 }
1690 return fail;
1691 }
1692
1693 void* gmi_request_thread(struct gmi_tab* tab) {
1694 unsigned int signal = 0;
1695 while (!client.shutdown) {
1696 tab->selected = 0;
1697 tab->request.state = STATE_DONE;
1698 if (tab->page.code != 30 && tab->page.code != 31)
1699 tb_interupt();
1700 if (recv(tab->thread.pair[0], &signal, 4, 0) != 4 ||
1701 client.shutdown || signal == 0xFFFFFFFF) break;
1702 bzero(&tab->request, sizeof(tab->request));
1703 tab->request.state = STATE_STARTED;
1704 int ret = gmi_request_init(tab, tab->thread.url,
1705 tab->thread.add);
1706 if (tab->request.state == STATE_CANCEL ||
1707 ret || gmi_request_dns(tab)) {
1708 if (tab->request.tls) {
1709 tls_close(tab->request.tls);
1710 tls_free(tab->request.tls);
1711 tab->request.tls = NULL;
1712 }
1713 if (ret == -1)
1714 tab->show_error = 1;
1715 tab->request.recv = ret;
1716 continue;
1717 }
1718 if (gmi_request_connect(tab) ||
1719 tab->request.state == STATE_CANCEL) {
1720 close(tab->request.socket);
1721 if (tab->request.tls) {
1722 tls_close(tab->request.tls);
1723 tls_free(tab->request.tls);
1724 tab->request.tls = NULL;
1725 }
1726 tab->show_error = 1;
1727 tab->request.recv = -1;
1728 continue;
1729 }
1730 ret = -1;
1731 if (!gmi_request_handshake(tab) &&
1732 tab->request.state != STATE_CANCEL) {
1733 ret = gmi_request_header(tab);
1734 if (!ret || ret == 2) {
1735 char out[1024];
1736 int fd = -1;
1737 if (tab->request.download)
1738 fd = gmi_getfd(tab, out, sizeof(out));
1739 if (gmi_request_body(tab, fd)) {
1740 close(fd);
1741 tab->show_error = 1;
1742 free(tab->request.data);
1743 continue;
1744 }
1745 if (fd > 0)
1746 gmi_postdownload(tab, fd, out);
1747 }
1748 }
1749
1750 if (ret == -2) {
1751 char* cr = strchr(tab->request.error, '\r');
1752 if (cr) {
1753 *cr = '\0';
1754 char buf[256];
1755 snprintf(buf, sizeof(buf),
1756 "%s (%d : %s)", tab->request.error,
1757 tab->page.code, tab->error);
1758 STRLCPY(tab->error, buf);
1759 *cr = '\r';
1760 }
1761 }
1762 if (ret == -1 || ret == -2) {
1763 free(tab->request.data);
1764 tab->request.data = NULL;
1765 if (tab->history)
1766 STRLCPY(tab->url, tab->history->url);
1767 else
1768 STRLCPY(tab->url, "about:home");
1769 tab->show_error = 1;
1770 tab->request.recv = -1;
1771 tab->page.code = 20;
1772 }
1773
1774 if (tab->request.socket != -1)
1775 close(tab->request.socket);
1776 if (tab->request.tls) {
1777 tls_close(tab->request.tls);
1778 tls_free(tab->request.tls);
1779 tab->request.tls = NULL;
1780 }
1781 if (tab->request.recv > 0 &&
1782 (tab->page.code == 11 || tab->page.code == 10)) {
1783 free(tab->request.data);
1784 tab->request.data = NULL;
1785 STRLCPY(tab->url, tab->request.url);
1786 continue;
1787 }
1788 if (tab->request.recv > 0 &&
1789 (tab->page.code == 31 || tab->page.code == 30)) {
1790 tab->request.data[tab->request.recv] = '\0';
1791 char* ptr = strchr(tab->request.data, ' ');
1792 if (!ptr) {
1793 free(tab->request.data);
1794 continue;
1795 }
1796 char* ln = strchr(ptr + 1, ' ');
1797 if (ln) *ln = '\0';
1798 ln = strchr(ptr, '\n');
1799 if (ln) *ln = '\0';
1800 ln = strchr(ptr, '\r');
1801 if (ln) *ln = '\0';
1802 STRLCPY(tab->url, tab->request.url);
1803 int r = gmi_nextlink(tab, tab->url, ptr + 1);
1804 if (r < 1 || tab->page.code != 20) {
1805 free(tab->request.data);
1806 continue;
1807 }
1808 tab->page.data_len = r;
1809 free(tab->request.data);
1810 tab->request.data = NULL;
1811 gmi_load(&tab->page);
1812 tab->history->page = tab->page;
1813 tab->history->cached = 1;
1814 tab->request.recv = r;
1815 continue;
1816 }
1817 if (!tab->request.download &&
1818 tab->request.recv > 0 &&
1819 tab->page.code == 20) {
1820 tab->search.entry[0] = '\0';
1821 struct gmi_link* link_ptr = tab->history?
1822 tab->history->prev:NULL;
1823 for (int i = 0; i < MAX_CACHE; i++) {
1824 if (!link_ptr) break;
1825 link_ptr = link_ptr->prev;
1826 }
1827 while (link_ptr) {
1828 if (link_ptr->cached) {
1829 gmi_freepage(&link_ptr->page);
1830 link_ptr->cached = 0;
1831 }
1832 link_ptr = link_ptr->prev;
1833 }
1834 pthread_mutex_lock(&tab->render_mutex);
1835 if (!tab->thread.add)
1836 gmi_freepage(&tab->page);
1837 bzero(&tab->page, sizeof(struct gmi_page));
1838 tab->page.code = 20;
1839 STRLCPY(tab->page.meta, tab->request.meta);
1840 STRLCPY(tab->url, tab->request.url);
1841 tab->page.data_len = tab->request.recv;
1842 tab->page.data = tab->request.data;
1843 gmi_load(&tab->page);
1844 if (tab->thread.add)
1845 gmi_addtohistory(tab);
1846 else if (tab->history) {
1847 tab->history->page = tab->page;
1848 tab->history->cached = 1;
1849 }
1850 tab->scroll = -1;
1851 pthread_mutex_unlock(&tab->render_mutex);
1852 }
1853 }
1854 return NULL;
1855 }
1856
1857 int gmi_request(struct gmi_tab *tab, const char *url, int add) {
1858 if (tab->request.state != STATE_DONE)
1859 tab->request.state = STATE_CANCEL;
1860 for (int i = 0; i < 5 && tab->request.state == STATE_CANCEL; i++)
1861 nanosleep(&timeout, NULL);
1862 tab->thread.add = add;
1863 STRLCPY(tab->thread.url, url);
1864 int signal = 0;
1865 if (send(tab->thread.pair[1], &signal, sizeof(signal), 0) !=
1866 sizeof(signal))
1867 return -1;
1868 return 0;
1869 }
1870
1871 int gmi_loadfile(struct gmi_tab* tab, char* path) {
1872 FILE* f = fopen(path, "rb");
1873 if (!f) {
1874 snprintf(tab->error, sizeof(tab->error),
1875 "Failed to open %s", path);
1876 tab->show_error = 1;
1877 return -1;
1878 }
1879 #ifdef SANDBOX_FREEBSD
1880 if (make_readonly(f)) {
1881 fclose(f);
1882 client.shutdown = 1;
1883 return -1;
1884 }
1885 #endif
1886 fseek(f, 0, SEEK_END);
1887 size_t len = ftell(f);
1888 fseek(f, 0, SEEK_SET);
1889 char* data = malloc(len);
1890 if (!data) {
1891 fclose(f);
1892 memory_failure(tab);
1893 return -1;
1894 }
1895 if (len != fread(data, 1, len, f)) {
1896 fclose(f);
1897 free(data);
1898 return -1;
1899 }
1900 fclose(f);
1901 pthread_mutex_lock(&tab->render_mutex);
1902 tab->page.code = 20;
1903 tab->page.data_len = len;
1904 if (tab->page.data) free(tab->page.data);
1905 tab->page.data = data;
1906 snprintf(tab->url, sizeof(tab->url), "file://%s/", path);
1907 int i = strnlen(path, PATH_MAX);
1908 int gmi = 0;
1909 if (i > 4 && path[i - 1] == '/') i--;
1910 if (i > 4 && path[i - 4] == '.' && path[i - 3] == 'g' &&
1911 path[i - 2] == 'm' && path[i - 1] == 'i')
1912 gmi = 1;
1913 if (gmi)
1914 STRLCPY(tab->page.meta, "text/gemini");
1915 else
1916 STRLCPY(tab->page.meta, "text/text");
1917 tab->page.no_header = 1;
1918 gmi_load(&tab->page);
1919 gmi_addtohistory(tab);
1920 pthread_mutex_unlock(&tab->render_mutex);
1921 return len;
1922 }
1923
1924 int gmi_init() {
1925 if (tls_init()) {
1926 tb_shutdown();
1927 printf("Failed to initialize TLS\n");
1928 return -1;
1929 }
1930
1931 config = tls_config_new();
1932 if (!config) {
1933 tb_shutdown();
1934 printf("Failed to initialize TLS config\n");
1935 return -1;
1936 }
1937
1938 config_empty = tls_config_new();
1939 if (!config_empty) {
1940 tb_shutdown();
1941 printf("Failed to initialize TLS config\n");
1942 return -1;
1943 }
1944
1945 tls_config_insecure_noverifycert(config);
1946 tls_config_insecure_noverifycert(config_empty);
1947 #ifndef DISABLE_XDG
1948 int xdg = client.xdg;
1949 #endif
1950 #ifdef SANDBOX_SUN
1951 char** bookmarks = client.bookmarks;
1952 #endif
1953 bzero(&client, sizeof(client));
1954 #ifdef SANDBOX_SUN
1955 client.bookmarks = bookmarks;
1956 #endif
1957 #ifndef DISABLE_XDG
1958 client.xdg = xdg;
1959 #endif
1960
1961 int fd = getconfigfd();
1962 if (fd < 0) {
1963 tb_shutdown();
1964 printf("Failed to open config folder\n");
1965 return -1;
1966 }
1967
1968 #ifndef SANDBOX_SUN
1969 if (gmi_loadbookmarks()) {
1970 gmi_newbookmarks();
1971 }
1972
1973 if (cert_load()) {
1974 tb_shutdown();
1975 printf("Failed to load known hosts\n");
1976 return -1;
1977 }
1978 #endif
1979
1980 return 0;
1981 }
1982
1983 void gmi_free() {
1984 while (client.tab)
1985 gmi_freetab(client.tab);
1986 gmi_savebookmarks();
1987 for (int i = 0; client.bookmarks[i]; i++)
1988 free(client.bookmarks[i]);
1989 free(client.bookmarks);
1990 cert_free();
1991 }
1992