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