💾 Archived View for gemini.rmf-dev.com › repo › Vaati › VgmiTL › files › 3e3c9f3f82451e808762ab52b28… captured on 2022-07-16 at 17:13:10. Gemini links have been rewritten to link to archived content
-=-=-=-=-=-=-
0 /* See LICENSE file for copyright and license details. */
1 #include <tinyscheme/scheme.h>
2 #ifdef __linux__
3 #define _GNU_SOURCE
4 #endif
5 #include <string.h>
6 #include <netinet/in.h>
7 #include <stdint.h>
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <strings.h>
11 #include <unistd.h>
12 #include <netdb.h>
13 #include <tls.h>
14 #include <poll.h>
15 #include <fcntl.h>
16 #include <errno.h>
17 #include <termbox.h>
18 #include <ctype.h>
19 #include <time.h>
20 #include "gemini.h"
21 #include "scheme.h"
22 #ifdef __linux__
23 #include <bsd/string.h>
24 char *strcasestr(const char *haystack, const char *needle);
25 #endif
26 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
27 #include <pthread.h>
28 #include <sys/socket.h>
29 #endif
30 #include "cert.h"
31 #include "wcwidth.h"
32 #include "display.h"
33
34 #define TIMEOUT 3
35 struct timespec timeout = {0, 50000000};
36
37 struct tls_config* config;
38 struct tls_config* config_empty;
39 struct tls* ctx;
40 struct gmi_client client;
41
42 void tb_colorline(int x, int y, uintattr_t color);
43
44 void fatal() {
45 tb_shutdown();
46 printf("Failed to allocate memory, terminating\n");
47 exit(0);
48 }
49
50 int fatalI() {
51 fatal();
52 return -1;
53 }
54
55 void* fatalP() {
56 fatal();
57 return NULL;
58 }
59
60 int getbookmark(char* path, size_t len) {
61 int length = getcachefolder(path, len);
62 const char bookmark[] = "/bookmarks.txt";
63 if (length + sizeof(bookmark) >= len) return -1;
64 return length + strlcpy(&path[length], bookmark, len - length);
65 }
66
67 int gmi_parseuri(const char* url, int len, char* buf, int llen) {
68 int j = 0;
69 int inquery = 0;
70 for (int i=0; j < llen && i < len && url[i]; i++) {
71 if (url[i] == '/') inquery = 0;
72 if ((url[i] >= 'a' && url[i] <= 'z') ||
73 (url[i] >= 'A' && url[i] <= 'Z') ||
74 (url[i] >= '0' && url[i] <= '9') ||
75 (url[i] == '?' && !inquery) ||
76 url[i] == '.' || url[i] == '/' ||
77 url[i] == ':' || url[i] == '-') {
78 if (url[i] == '?') inquery = 1;
79 buf[j] = url[i];
80 j++;
81 } else {
82 char format[8];
83 snprintf(format, sizeof(format), "%%!x(MISSING)", (unsigned char)url[i]);
84 buf[j] = '\0';
85 j = strlcat(buf, format, llen);
86 }
87 }
88 if (j >= llen) j = llen - 1;
89 buf[j] = '\0';
90 return j;
91 }
92
93 int gmi_goto(int id) {
94 id--;
95 struct gmi_tab* tab = &client.tabs[client.tab];
96 struct gmi_page* page = &tab->page;
97 if (id < 0 || id >= page->links_count) {
98 snprintf(tab->error, sizeof(tab->error),
99 "Invalid link number, %!d(MISSING)/%!d(MISSING)", id, page->links_count);
100 client.input.error = 1;
101 return -1;
102 }
103 gmi_cleanforward(&client.tabs[client.tab]);
104 int ret = gmi_nextlink(client.tabs[client.tab].url, page->links[id]);
105 if (ret < 1) return ret;
106 client.tabs[client.tab].page.data_len = ret;
107 gmi_load(page);
108 client.tabs[client.tab].scroll = -1;
109 return ret;
110 }
111
112 int gmi_goto_new(int id) {
113 id--;
114 struct gmi_tab* tab = &client.tabs[client.tab];
115 struct gmi_page* page = &tab->page;
116 if (id < 0 || id >= page->links_count) {
117 snprintf(tab->error, sizeof(tab->error),
118 "Invalid link number, %!d(MISSING)/%!d(MISSING)", id, page->links_count);
119 client.input.error = 1;
120 return -1;
121 }
122 int old_tab = client.tab;
123 gmi_newtab();
124 client.tab = client.tabs_count - 1;
125 page = &client.tabs[old_tab].page;
126 int ret = gmi_nextlink(client.tabs[old_tab].url, page->links[id]);
127 if (ret < 1) return ret;
128 client.tabs[client.tab].page.data_len = ret;
129 gmi_load(&client.tabs[client.tab].page);
130 return ret;
131 }
132
133 int gmi_nextlink(char* url, char* link) {
134 int url_len = strnlen(url, MAX_URL);
135 if (link[0] == '(' && link[strnlen(link, MAX_URL)-1] == ')') {
136 scheme_load_string(client.tabs[client.tab].ctx, link);
137 return 0;
138 }
139 if (link[0] == '/' && link[1] == '/') {
140 int ret = gmi_request(&link[2]);
141 if (ret < 1) return ret;
142 return ret;
143 } else if (link[0] == '/') {
144 if (url_len > GMI && strstr(url, "gemini://")) {
145 char* ptr = strchr(&url[GMI], '/');
146 if (ptr) *ptr = '\0';
147 }
148 char urlbuf[MAX_URL];
149 size_t l = strlcpy(urlbuf, url, sizeof(urlbuf));
150 if (l >= sizeof(urlbuf))
151 goto nextlink_overflow;
152 if (strlcpy(urlbuf + l, link, sizeof(urlbuf) - l) >= sizeof(urlbuf) - l)
153 goto nextlink_overflow;
154 int ret = gmi_request(urlbuf);
155 return ret;
156 } else if (strstr(link, "https://") ||
157 strstr(link, "http://") ||
158 strstr(link, "gopher://")) {
159 #ifndef DISABLE_XDG
160 if (client.xdg) {
161 char buf[1048];
162 snprintf(buf, sizeof(buf), "xdg-open %!s(MISSING) > /dev/null 2>&1", link);
163 #ifdef __OpenBSD__
164 if (fork() == 0) {
165 setsid();
166 char* argv[] = {"/bin/sh", "-c", buf, NULL};
167 execvp(argv[0], argv);
168 exit(0);
169 }
170 #else
171 system(buf);
172 #endif
173 return -1;
174 }
175 #endif
176 client.input.error = 1;
177 snprintf(client.tabs[client.tab].error,
178 sizeof(client.tabs[client.tab].error),
179 "Can't open web link");
180 return -1;
181 } else if (strstr(link, "gemini://")) {
182 int ret = gmi_request(link);
183 return ret;
184 } else {
185 char* ptr = strrchr(&url[GMI], '/');
186 if (ptr) *ptr = '\0';
187 char urlbuf[MAX_URL];
188 size_t l = strlcpy(urlbuf, url, sizeof(urlbuf));
189 if (l >= sizeof(urlbuf))
190 goto nextlink_overflow;
191 if (urlbuf[l-1] != '/') {
192 size_t l2 = strlcpy(urlbuf + l, "/", sizeof(urlbuf) - l);
193 if (l2 >= sizeof(urlbuf) - l)
194 goto nextlink_overflow;
195 l += l2;
196 }
197 size_t l2 = strlcpy(urlbuf + l, link, sizeof(urlbuf) - l);
198 if (l2 >= sizeof(urlbuf) - l)
199 goto nextlink_overflow;
200 l += l2;
201 if (urlbuf[l-1] == '/') urlbuf[l-1] = '\0';
202 int ret = gmi_request(urlbuf);
203 return ret;
204 }
205 nextlink_overflow:
206 client.input.error = 1;
207 struct gmi_tab* tab = &client.tabs[client.tab];
208 snprintf(tab->error, sizeof(tab->error),
209 "Link too long, above 1024 characters");
210 return -1;
211 }
212
213 void gmi_load(struct gmi_page* page) {
214 for (int i=0; i<page->links_count; i++)
215 free(page->links[i]);
216 free(page->links);
217 page->links = NULL;
218 page->links_count = 0;
219 page->lines = 0;
220 int x = 0;
221 page->objs_count = 0;
222 page->data[page->data_len] = '\0';
223 for (int c = 0; c < page->data_len; c++) {
224 if (page->data[c] == '%!'(MISSING) &&
225 page->data[c+1] == '%!'(MISSING) &&
226 page->data[c+2] == ':'
227 ) {
228 page->data_len = c-1;
229 page->script_start = c+3;
230 break;
231 }
232 if (page->data[c] == '%!'(MISSING) && page->data[c+1] == '{') {
233 int end = 0;
234 int space = 1;
235 char buf[64];
236 int pos = 0;
237 for (int i = c+2; i < page->data_len; i++) {
238 if (space &&
239 (page->data[i] == ' ' ||
240 page->data[i] == '\t'))
241 continue;
242 if (page->data[i] == '\n') break;
243 if (page->data[i] == '}') {
244 end = i;
245 break;
246 }
247 buf[pos] = page->data[i];
248 pos++;
249 }
250 buf[pos] = '\0';
251 if (end) {
252 struct gmi_obj obj;
253 strlcpy(obj.id, buf, sizeof(obj.id));
254 obj.value = NULL;
255 obj.start = c;
256 obj.end = end;
257 page->objs_count++;
258 page->objs = realloc(page->objs,
259 sizeof(obj)*page->objs_count);
260 memcpy(&page->objs[page->objs_count-1], &obj, sizeof(obj));
261 c = end;
262 }
263 }
264 if (x == 0 && page->data[c] == '=' && page->data[c+1] == '>') {
265 c += 2;
266 int nospace = c;
267 for (; page->data[c]==' ' || page->data[c]=='\t'; c++) {
268 if (page->data[c] == '\n' || page->data[c] == '\0') {
269 c = nospace;
270 break;
271 }
272 }
273 char* url = (char*)&page->data[c];
274 for (; page->data[c]!=' ' && page->data[c]!='\t'; c++) {
275 if (page->data[c] == '\n' || page->data[c] == '\0') {
276 break;
277 }
278 }
279 char save = page->data[c];
280 page->data[c] = '\0';
281 if (page->links)
282 page->links = realloc(page->links, sizeof(char*)
283 * (page->links_count+1));
284 else
285 page->links = malloc(sizeof(char*));
286 if (!page->links) {
287 fatal();
288 return;
289 }
290 if (url[0] == '\0') {
291 page->links[page->links_count] = NULL;
292 page->data[c] = save;
293 continue;
294 }
295 int len = strnlen(url, MAX_URL);
296 page->links[page->links_count] = malloc(len+2);
297 if (!page->links[page->links_count]) {
298 fatal();
299 return;
300 }
301 memcpy(page->links[page->links_count], url, len+1);
302 page->links_count++;
303 page->data[c] = save;
304 }
305 if (page->data[c] == '\n') {
306 page->lines++;
307 x = 0;
308 continue;
309 }
310 x++;
311 }
312 }
313
314 int gmi_render(struct gmi_tab* tab) {
315 if (!tab->page.script_exec) {
316 //tb_shutdown();
317 scheme_load_string(tab->ctx, &tab->page.data[tab->page.script_start]);
318 //tb_init();
319 tab->page.script_exec = 1;
320 }
321 #include "img.h"
322 #ifdef TERMINAL_IMG_VIEWER
323 if (strncmp(tab->page.meta, "image/", 6) == 0) {
324 if (!tab->page.img.tried) {
325 char* ptr = strchr(tab->page.data, '\n');
326 if (!ptr) {
327 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,
328 "Invalid data: new line not found");
329 tab->page.img.tried = 1;
330 return 1;
331 }
332 ptr++;
333 if (tab->page.img.data) {
334 stbi_image_free(tab->page.img.data);
335 tab->page.img.data = NULL;
336 }
337 tab->page.img.data =
338 stbi_load_from_memory((unsigned char*)ptr,
339 tab->page.data_len-(int)(ptr-tab->page.data),
340 &tab->page.img.w, &tab->page.img.h,
341 &tab->page.img.channels, 3);
342 if (!tab->page.img.data) {
343 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,
344 "Failed to decode image: %!s(MISSING);", tab->page.meta);
345 tab->page.img.tried = 1;
346 return 1;
347 }
348
349 tab->page.img.tried = 1;
350 }
351 if (tab->page.img.data) {
352 img_display(tab->page.img.data,
353 tab->page.img.w, tab->page.img.h,
354 client.tabs_count>1);
355 return 1;
356 }
357 }
358 #endif
359
360 int text = 0;
361 if (strncmp(tab->page.meta, "text/gemini", 11)) {
362 if (strncmp(tab->page.meta, "text/", 5)) {
363 tb_printf(2, -tab->scroll, TB_DEFAULT, TB_DEFAULT,
364 "Unable to render format : %!s(MISSING)", tab->page.meta);
365 return 1;
366 }
367 text = 1;
368 }
369 int line = 0;
370 int x = 0;
371 int links = 0;
372 uintattr_t color = TB_DEFAULT;
373 int start = 1;
374 int ignore = 0;
375 int h = tb_height() - 2 - (client.tabs_count>1);
376 for (int c = 0; c < tab->page.data_len; c++) {
377 if (x == 0 && tab->page.data[c] == '\n') {
378 if (c+1 != tab->page.data_len)
379 line++;
380 continue;
381 }
382 if (tab->page.data[c] == '\t') {
383 x+=4;
384 continue;
385 }
386 if (tab->page.data[c] == '\r') continue;
387 int atobj = 0;
388 for (int i = 0; i < tab->page.objs_count; i++) {
389 if (tab->page.objs[i].start == c) {
390 atobj = 1;
391 c = tab->page.objs[i].end;
392 if (tab->page.objs[i].value) {
393 tb_printf(x+2, line-1-tab->scroll,
394 TB_DEFAULT, TB_DEFAULT,
395 "%!s(MISSING)", tab->page.objs[i].value);
396 }
397 break;
398 }
399 }
400 if (atobj) continue;
401 if (!text && start && tab->page.data[c] == '`' &&
402 tab->page.data[c+1] == '`' && tab->page.data[c+2] == '`')
403 ignore = !ignore;
404 if (!ignore && !text) {
405 for (int i=0; start && tab->page.data[c+i] == '#' && i<3; i++) {
406 if (tab->page.data[c+i+1] == ' ') {
407 color = TB_RED + i;
408 break;
409 }
410 }
411 if (start && tab->page.data[c] == '*' && tab->page.data[c+1] == ' ') {
412 color = TB_ITALIC|TB_CYAN;
413 }
414 if (start && tab->page.data[c] == '>' && tab->page.data[c+1] == ' ') {
415 color = TB_ITALIC|TB_MAGENTA;
416 }
417 if (start && tab->page.data[c] == '=' && tab->page.data[c+1] == '>') {
418 if (line-1>=(tab->scroll>=0?tab->scroll:0) &&
419 line-tab->scroll <= tb_height()-2) {
420 char buf[32];
421 snprintf(buf, sizeof(buf), "[%!d(MISSING)]", links+1);
422 tb_print(x+2, line-1-tab->scroll,
423 links+1 == tab->selected?TB_RED:TB_BLUE,
424 TB_DEFAULT, buf);
425 x += strnlen(buf, sizeof(buf));
426 }
427 c += 2;
428
429 while (
430 (tab->page.data[c]==' ' || tab->page.data[c]=='\t') &&
431 tab->page.data[c]!='\n' &&
432 tab->page.data[c]!='\0') c++;
433
434 int initial = c;
435 while (tab->page.data[c]!=' ' &&
436 tab->page.data[c]!='\t' &&
437 tab->page.data[c]!='\n' &&
438 tab->page.data[c]!='\0') c++;
439
440 while ((tab->page.data[c]==' ' || tab->page.data[c]=='\t') &&
441 tab->page.data[c]!='\n' &&
442 tab->page.data[c]!='\0') c++;
443
444 if (tab->page.data[c]=='\n' || tab->page.data[c]=='\0')
445 c = initial;
446 x+=3;
447 if ((links+1)/10) x--;
448 if ((links+1)/100) x--;
449 if ((links+1)/1000) x--;
450 links++;
451 }
452 }
453
454 if (tab->page.data[c] == '\n' || tab->page.data[c] == ' ' ||
455 x+4 >= tb_width()) {
456 int end = 0;
457 if (x+4>=tb_width()) {
458 end = 1;
459 }
460 int newline = (tab->page.data[c] == '\n' || x+4 >= tb_width());
461 for (int i=1; 1; i++) {
462 if (x+i == tb_width() - 1) break;
463 if (i > tb_width() - 4) {
464 newline = 0;
465 break;
466 }
467 if (tab->page.data[c+i] == ' ' || tab->page.data[c+i] == '\n' ||
468 tab->page.data[c+i] == '\0' || c+i >= tab->page.data_len)
469 break;
470 if (tb_width()-4<=x+i) newline = 1;
471 }
472 if (newline) {
473 if (c+1 != tab->page.data_len)
474 line++;
475 if (tab->page.data[c+1] == ' ') c++;
476 if (tab->page.data[c] == '\n') {
477 color = TB_DEFAULT;
478 start = 1;
479 }
480 x = 0;
481 continue;
482 } else if (end) {
483 c++;
484 x++;
485 }
486 }
487 uint32_t ch = 0;
488 int size = tb_utf8_char_to_unicode(&ch, &tab->page.data[c])-1;
489 if (size > 0)
490 c += tb_utf8_char_to_unicode(&ch, &tab->page.data[c])-1;
491
492 int wc = mk_wcwidth(ch);
493 if (wc < 0) wc = 0;
494 if (line-1>=(tab->scroll>=0?tab->scroll:0) &&
495 (line-tab->scroll <= tb_height() - 2) && ch != '\t') {
496 if (wc == 1)
497 tb_set_cell(x+2, line-1-tab->scroll, ch, color, TB_DEFAULT);
498 else
499 tb_set_cell_ex(x+2, line-1-tab->scroll, &ch,
500 wc, color, TB_DEFAULT);
501 }
502
503 x += wc;
504 start = 0;
505 }
506 line++;
507 h += (client.tabs_count>1);
508 if (h > line) return line;
509 int size = (h==line?(h-1):(h/(line-h)));
510 if (size == h) size--;
511 if (size < 1) size = 1;
512 int H = (line-h+1+(client.tabs_count>1));
513 int pos = (tab->scroll+(client.tabs_count<1))*h/(H?H:1)
514 + (client.tabs_count>1) ;
515 if (pos >= h) pos = h - size;
516 if (pos < 0) pos = 0;
517 if (tab->scroll+h == line) pos = h - size;
518 int w = tb_width();
519 for (int y = (client.tabs_count>1); y < h+(client.tabs_count>1); y++)
520 if (y >= pos && y < pos + size)
521 tb_set_cell(w-1, y, ' ', TB_DEFAULT, client.c256?246:TB_CYAN);
522 else
523 tb_set_cell(w-1, y, ' ', TB_DEFAULT, client.c256?233:TB_BLACK);
524 return line;
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 free(link);
535 link = ptr;
536 }
537 tab->history->next = NULL;
538 }
539
540
541 void gmi_freetab(struct gmi_tab* tab) {
542 if (tab->history) {
543 struct gmi_link* link = tab->history->next;
544 while (link) {
545 struct gmi_link* ptr = link->next;
546 bzero(link->url, sizeof(link->url));
547 free(link);
548 link = ptr;
549 }
550 link = tab->history->prev;
551 while (link) {
552 struct gmi_link* ptr = link->prev;
553 bzero(link->url, sizeof(link->url));
554 free(link);
555 link = ptr;
556 }
557 bzero(tab->history->url, sizeof(tab->history->url));
558 free(tab->history);
559 }
560 for (int i=0; i < tab->page.links_count; i++)
561 free(tab->page.links[i]);
562 free(tab->page.links);
563 free(tab->page.data);
564 #ifdef TERMINAL_IMG_VIEWER
565 stbi_image_free(tab->page.img.data);
566 #endif
567 }
568
569
570 char home_page[] =
571 "20 text/gemini\r\n# Vgmi\n\n" \
572 "A Gemini client written in C with vim-like keybindings\n\n" \
573 "## Bookmarks\n\n" \
574 "%!s(MISSING)\n" \
575 "## Keybindings\n\n" \
576 "* k - Scroll up\n" \
577 "* j - Scroll down\n" \
578 "* h - Switch to the previous tab\n" \
579 "* l - Switch to the next tab\n" \
580 "* H - Go back in the history\n" \
581 "* L - Go forward in the history\n" \
582 "* gg - Go at the top of the page\n" \
583 "* G - Go at the bottom of the page\n" \
584 "* : - Open input mode\n" \
585 "* u - Open input mode with the current url\n" \
586 "* f - Show the history\n" \
587 "* r - Reload the page\n" \
588 "* [number]Tab - Select a link\n" \
589 "* Tab - Follow the selected link\n" \
590 "* Shift+Tab - Open the selected link in a new tab\n" \
591 "* Del - Delete the selected link from the bookmarks\n" \
592 "\nYou can prefix a movement key with a number to repeat it.\n\n" \
593 "## Commands\n\n" \
594 "* :q - Close the current tab\n" \
595 "* :qa - Close all tabs, exit the program\n" \
596 "* :o [url] - Open an url\n" \
597 "* :s [search] - Search the Geminispace using geminispace.info\n" \
598 "* :nt [url] - Open a new tab, the url is optional\n" \
599 "* :add [name] - Add the current url to the bookmarks, the name is optional\n" \
600 "* :[number] - Follow the link\n" \
601 "* :gencert - Generate a certificate for the current capsule"
602 ;
603
604 void gmi_newbookmarks() {
605 int len;
606 const char geminispace[] = "gemini://geminispace.info Geminispace";
607 const char gemigit[] = "gemini://gemini.rmf-dev.com Gemigit";
608 client.bookmarks = malloc(sizeof(char*) * 3);
609 if (!client.bookmarks) goto fail_malloc;
610
611 len = sizeof(geminispace);
612 client.bookmarks[0] = malloc(len);
613 if (!client.bookmarks[0]) goto fail_malloc;
614 strlcpy(client.bookmarks[0], geminispace, len);
615
616 len = sizeof(gemigit);
617 client.bookmarks[1] = malloc(len);
618 if (!client.bookmarks[1]) goto fail_malloc;
619 strlcpy(client.bookmarks[1], gemigit, len);
620
621 client.bookmarks[2] = NULL;
622 return;
623 fail_malloc:
624 fatal();
625 }
626
627 int gmi_loadbookmarks() {
628 char path[1024];
629 if (getbookmark(path, sizeof(path)) < 1) return -1;
630 FILE* f = fopen(path, "rb");
631 if (!f)
632 return -1;
633 fseek(f, 0, SEEK_END);
634 size_t len = ftell(f);
635 fseek(f, 0, SEEK_SET);
636 char* data = malloc(len);
637 if (len != fread(data, 1, len, f)) {
638 fclose(f);
639 return -1;
640 }
641 fclose(f);
642 char* ptr = data;
643 long n = 0;
644 while (++ptr && ptr < data+len) if (*ptr == '\n') n++;
645 client.bookmarks = malloc(sizeof(char*) * (n + 1));
646 client.bookmarks[n] = NULL;
647 n = 0;
648 ptr = data;
649 char* str = data;
650 while (++ptr && ptr < data+len) {
651 if (*ptr == '\n') {
652 *ptr = '\0';
653 client.bookmarks[n] = malloc(ptr-str+1);
654 strlcpy(client.bookmarks[n], str, ptr-str+1);
655 n++;
656 str = ptr+1;
657 }
658 }
659 free(data);
660 return 0;
661 }
662
663 char* gmi_gettitle(struct gmi_page page, int* len) {
664 int start = -1;
665 int end = -1;
666 for (int i=0; i < page.data_len; i++) {
667 if (start == -1 && page.data[i] == '#') {
668 for (int j = i+1; j < page.data_len; j++) {
669 if (j && page.data[j-1] == '#' && page.data[j] == '#')
670 break;
671 if (page.data[j] != ' ') {
672 start = j;
673 break;
674 }
675 }
676 }
677 if (start != -1 && page.data[i] == '\n') {
678 end = i;
679 break;
680 }
681 }
682 if (start == -1 || end == -1) return NULL;
683 char* title = malloc(end - start + 1);
684 strlcpy(title, &page.data[start], end - start + 1);
685 title[end - start] = '\0';
686 *len = end - start + 1;
687 return title;
688 }
689
690 int gmi_removebookmark(int index) {
691 index--;
692 if (index < 0) return -1;
693 int fail = -1;
694 for (int i = 0; client.bookmarks[i]; i++) {
695 if (i == index) {
696 free(client.bookmarks[i]);
697 fail = 0;
698 }
699 if (!fail)
700 client.bookmarks[i] = client.bookmarks[i+1];
701 }
702 return fail;
703 }
704
705 void gmi_addbookmark(char* url, char* title) {
706 struct gmi_tab* tab = &client.tabs[client.tab];
707 if (!strcmp(url, "about:home")) {
708 snprintf(tab->error,
709 sizeof(client.tabs[client.tab].error),
710 "Cannot add the new tab page to the bookmarks");
711 client.input.error = 1;
712 return;
713 }
714 int title_len = 0;
715 char* tmp_title = NULL;
716 if (!title) tmp_title = title = gmi_gettitle(tab->page, &title_len);
717 else title_len = strnlen(title, 128);
718 long n = 0;
719 while (client.bookmarks[n]) n++;
720 client.bookmarks = realloc(client.bookmarks, sizeof(char*) * (n + 2));
721 int len = strnlen(url, MAX_URL) + title_len + 2;
722 client.bookmarks[n] = malloc(len);
723 if (title)
724 snprintf(client.bookmarks[n], len, "%!s(MISSING) %!s(MISSING)", url, title);
725 else
726 snprintf(client.bookmarks[n], len, "%!s(MISSING)", url);
727 client.bookmarks[n+1] = NULL;
728 free(tmp_title);
729 }
730
731 int gmi_savebookmarks() {
732 char path[1024];
733 if (getbookmark(path, sizeof(path)) < 1) return -1;
734 FILE* f = fopen(path, "wb");
735 if (!f) {
736 return -1;
737 }
738 for (int i = 0; client.bookmarks[i]; i++)
739 fprintf(f, "%!s(MISSING)\n", client.bookmarks[i]);
740 fclose(f);
741 return 0;
742
743 }
744
745 char* gmi_getbookmarks(int* len) {
746 char* data = NULL;
747 int n = 0;
748 for (int i = 0; client.bookmarks[i]; i++) {
749 char line[2048];
750 long length = snprintf(line, sizeof(line), "=>%!s(MISSING)\n ", client.bookmarks[i]);
751 data = realloc(data, n+length+1);
752 if (!data) return fatalP();
753 strlcpy(&data[n], line, length);
754 n += length-1;
755 }
756 *len = n;
757 return data;
758 }
759
760 void gmi_gohome(struct gmi_tab* tab) {
761 bzero(tab, sizeof(struct gmi_tab));
762 tab->scroll = -1;
763 strlcpy(tab->url, "about:home", sizeof(tab->url));
764 tab->page.code = 20;
765 int bm;
766 char* data = gmi_getbookmarks(&bm);
767 tab->page.data = malloc(sizeof(home_page) + bm);
768 if (!tab->page.data) {
769 fatal();
770 return;
771 }
772
773 tab->page.data_len =
774 snprintf(tab->page.data, sizeof(home_page) + bm, home_page,
775 data?data:"") + 1;
776 free(data);
777
778 strlcpy(tab->page.meta, "text/gemini",
779 sizeof(tab->page.meta));
780
781 gmi_load(&tab->page);
782 }
783
784 void gmi_newtab() {
785 int tab = client.tabs_count;
786 client.tabs_count++;
787 if (client.tabs)
788 client.tabs = realloc(client.tabs, sizeof(struct gmi_tab) * client.tabs_count);
789 else
790 client.tabs = malloc(sizeof(struct gmi_tab));
791 if (!client.tabs) {
792 fatal();
793 return;
794 }
795 gmi_gohome(&client.tabs[tab]);
796 scm_init_tab(&client.tabs[tab]);
797 }
798
799 #define PROTO_GEMINI 0
800 #define PROTO_HTTP 1
801 #define PROTO_HTTPS 2
802 #define PROTO_GOPHER 3
803 #define PROTO_FILE 4
804
805 int gmi_parseurl(const char* url, char* host, int host_len, char* urlbuf,
806 int url_len, unsigned short* port) {
807 int proto = PROTO_GEMINI;
808 char* proto_ptr = strstr(url, "://");
809 char* ptr = (char*)url;
810 if (!proto_ptr) {
811 goto skip_proto;
812 }
813 char proto_buf[16];
814 for(; proto_ptr!=ptr; ptr++) {
815 if (!((*ptr > 'a' && *ptr < 'z') || (*ptr > 'A' && *ptr < 'Z'))) goto skip_proto;
816 if (ptr - url >= (signed)sizeof(proto_buf)) goto skip_proto;
817 proto_buf[ptr-url] = tolower(*ptr);
818 }
819 proto_buf[ptr-url] = '\0';
820 ptr+=3;
821 proto_ptr+=3;
822 if (!strcmp(proto_buf,"gemini")) goto skip_proto;
823 else if (!strcmp(proto_buf,"http")) proto = PROTO_HTTP;
824 else if (!strcmp(proto_buf,"https")) proto = PROTO_HTTPS;
825 else if (!strcmp(proto_buf,"gopher")) proto = PROTO_GOPHER;
826 else if (!strcmp(proto_buf,"file")) proto = PROTO_FILE;
827 else {
828 return -1; // unknown protocol
829 }
830 skip_proto:;
831 if (port && proto == PROTO_GEMINI) *port = 1965;
832 if (!proto_ptr) proto_ptr = ptr;
833 char* host_ptr = strchr(ptr, '/');
834 if (!host_ptr) host_ptr = ptr+strnlen(ptr, MAX_URL);
835 char* port_ptr = strchr(ptr, ':');
836 if (port_ptr && port_ptr < host_ptr) {
837 port_ptr++;
838 char c = *host_ptr;
839 *host_ptr = '\0';
840 if (port) {
841 *port = atoi(port_ptr);
842 if (*port < 1) {
843 return -1; // invalid port
844 }
845 }
846 *host_ptr = c;
847 host_ptr = port_ptr - 1;
848 }
849 for(; host_ptr!=ptr; ptr++) {
850 if (host_len <= host_ptr-ptr) {
851 return -1;
852 }
853 if (!((*ptr >= 'a' && *ptr <= 'z') || (*ptr >= 'A' && *ptr <= 'Z')
854 || (*ptr >= '0' && *ptr <= '9') || *ptr == '.' || *ptr == '-')) {
855 return -1;
856 }
857 host[ptr-proto_ptr] = *ptr;
858 }
859 host[ptr-proto_ptr] = '\0';
860 if (!urlbuf) return proto;
861 if (url_len < 16) return -1; // buffer too small
862 int len = 0;
863 switch (proto) {
864 case PROTO_GEMINI:
865 len = strlcpy(urlbuf, "gemini://", url_len);
866 break;
867 case PROTO_HTTP:
868 len = strlcpy(urlbuf, "http://", url_len);
869 break;
870 case PROTO_HTTPS:
871 len = strlcpy(urlbuf, "http://", url_len);
872 break;
873 case PROTO_GOPHER:
874 len = strlcpy(urlbuf, "gopher://", url_len);
875 break;
876 case PROTO_FILE:
877 len = strlcpy(urlbuf, "file://", url_len);
878 break;
879 default:
880 return -1;
881 }
882 size_t l = strlcpy(urlbuf + len, host, sizeof(urlbuf) - len);
883 if (l >= sizeof(urlbuf) - len) {
884 goto parseurl_overflow;
885 }
886 len += l;
887 if (host_ptr &&
888 strlcpy(urlbuf + len, host_ptr, sizeof(urlbuf) - len) >=
889 sizeof(urlbuf) - len)
890 goto parseurl_overflow;
891 return proto;
892 parseurl_overflow:
893 client.input.error = 1;
894 struct gmi_tab* tab = &client.tabs[client.tab];
895 snprintf(tab->error, sizeof(tab->error),
896 "Link too long, above 1024 characters");
897 return -1;
898 }
899
900 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
901 struct conn {
902 int connected;
903 int socket;
904 struct sockaddr* addr;
905 int family;
906 struct sockaddr_in addr4;
907 struct sockaddr_in6 addr6;
908 pthread_t tid;
909 pthread_cond_t cond;
910 pthread_mutex_t mutex;
911 };
912 struct conn conn;
913
914 void conn_thread() {
915 if (pthread_mutex_init(&conn.mutex, NULL)) {
916 struct gmi_tab* tab = &client.tabs[client.tab];
917 snprintf(tab->error, sizeof(tab->error),
918 "Failed to initialize connection mutex\n");
919 return;
920 }
921 pthread_mutex_lock(&conn.mutex);
922 while (1) {
923 pthread_cond_wait(&conn.cond, &conn.mutex);
924 if (connect(conn.socket, conn.addr,
925 (conn.family == AF_INET)?sizeof(conn.addr4):sizeof(conn.addr6)) != 0) {
926 conn.connected = -1;
927 continue;
928 }
929 conn.connected = 1;
930 }
931 }
932
933 struct dnsquery {
934 struct addrinfo* result;
935 int resolved;
936 char* host;
937 pthread_t tid;
938 pthread_cond_t cond;
939 pthread_mutex_t mutex;
940 };
941 struct dnsquery dnsquery;
942
943 void dnsquery_thread(int signal) {
944 if (signal != 0 && signal != SIGUSR1) return;
945 if (pthread_mutex_init(&dnsquery.mutex, NULL)) {
946 struct gmi_tab* tab = &client.tabs[client.tab];
947 snprintf(tab->error, sizeof(tab->error),
948 "Failed to initialize connection mutex\n");
949 return;
950 }
951 pthread_mutex_lock(&dnsquery.mutex);
952 while (1) {
953 pthread_cond_wait(&dnsquery.cond, &dnsquery.mutex);
954 struct addrinfo hints;
955 memset (&hints, 0, sizeof (hints));
956 hints.ai_family = PF_UNSPEC;
957 hints.ai_socktype = SOCK_STREAM;
958 hints.ai_flags |= AI_CANONNAME;
959 if (getaddrinfo(dnsquery.host, NULL, &hints, &dnsquery.result)) {
960 dnsquery.resolved = -1;
961 continue;
962 }
963 dnsquery.resolved = 1;
964 }
965 }
966
967 void signal_cb() {
968 pthread_t thread = pthread_self();
969 if (thread == conn.tid)
970 pthread_mutex_destroy(&conn.mutex);
971 if (thread == dnsquery.tid)
972 pthread_mutex_destroy(&dnsquery.mutex);
973 pthread_exit(NULL);
974 }
975
976 #endif
977
978 int gmi_request(const char* url) {
979 client.input.error = 0;
980 char* data_buf = NULL;
981 char meta_buf[1024];
982 char url_buf[1024];
983 int download = 0;
984 struct gmi_tab* tab = &client.tabs[client.tab];
985 if (tab->history) tab->history->scroll = tab->scroll;
986 tab->selected = 0;
987 int recv = TLS_WANT_POLLIN;
988 int sockfd = -1;
989 char gmi_host[256];
990 gmi_host[0] = '\0';
991 unsigned short port;
992 int proto = gmi_parseurl(url, gmi_host, sizeof(gmi_host),
993 url_buf, sizeof(url_buf), &port);
994 if (proto == PROTO_FILE) {
995 if (gmi_loadfile(&url_buf[P_FILE]) > 0)
996 gmi_load(&tab->page);
997 return 0;
998 }
999 if (proto != PROTO_GEMINI) {
1000 snprintf(tab->error, sizeof(tab->error), "Invalid url: %!s(MISSING)", url);
1001 client.input.error = 1;
1002 return -1;
1003 }
1004 if (ctx) {
1005 tls_close(ctx);
1006 ctx = NULL;
1007 }
1008 ctx = tls_client();
1009 if (!ctx) {
1010 snprintf(tab->error, sizeof(tab->error), "Failed to initialize TLS");
1011 client.input.error = 1;
1012 return -1;
1013 }
1014
1015 int cert = 0;
1016 char crt[1024];
1017 char key[1024];
1018 if (cert_getpath(gmi_host, crt, sizeof(crt), key, sizeof(key)) == -1) {
1019 client.input.error = 1;
1020 cert = -1;
1021 }
1022
1023 if (cert == 0) {
1024 FILE* f = fopen(crt, "rb");
1025 if (f) {
1026 cert = 1;
1027 fclose(f);
1028 }
1029 }
1030
1031 if (cert == 1 && tls_config_set_keypair_file(config, crt, key)) {
1032 client.input.error = 1;
1033 snprintf(tab->error, sizeof(tab->error), "%!s(MISSING)",
1034 tls_config_error(config));
1035 return -1;
1036 }
1037
1038 if (tls_configure(ctx, cert?config:config_empty)) {
1039 snprintf(tab->error, sizeof(tab->error),
1040 "Failed to configure TLS");
1041 client.input.error = 1;
1042 return -1;
1043 }
1044
1045 // Manually create socket to set a timeout
1046 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
1047 dnsquery.host = gmi_host;
1048 dnsquery.resolved = 0;
1049 pthread_cond_signal(&dnsquery.cond);
1050 for (int i=0; i < TIMEOUT * 20 && !dnsquery.resolved; i++)
1051 nanosleep(&timeout, NULL);
1052
1053 if (dnsquery.resolved != 1 || dnsquery.result == NULL) {
1054 if (!dnsquery.resolved) {
1055 pthread_cancel(dnsquery.tid);
1056 pthread_kill(dnsquery.tid, SIGUSR1);
1057 pthread_join(dnsquery.tid, NULL);
1058 pthread_create(&dnsquery.tid, NULL,
1059 (void *(*)(void *))dnsquery_thread, NULL);
1060 }
1061 snprintf(tab->error, sizeof(tab->error),
1062 "Unknown domain name: %!s(MISSING)", gmi_host);
1063 goto request_error;
1064 }
1065 struct addrinfo *result = dnsquery.result;
1066 #else
1067 struct addrinfo hints, *result;
1068 memset (&hints, 0, sizeof (hints));
1069 hints.ai_family = PF_UNSPEC;
1070 hints.ai_socktype = SOCK_STREAM;
1071 hints.ai_flags |= AI_CANONNAME;
1072 if (getaddrinfo(gmi_host, NULL, &hints, &result)) {
1073 snprintf(tab->error, sizeof(tab->error),
1074 "Unknown domain name: %!s(MISSING)", gmi_host);
1075 goto request_error;
1076 }
1077 #endif
1078
1079 struct sockaddr_in addr4;
1080 struct sockaddr_in6 addr6;
1081 bzero(&addr4, sizeof(addr4));
1082 bzero(&addr6, sizeof(addr6));
1083 sockfd = socket(AF_INET, SOCK_STREAM, 0);
1084 struct timeval tv;
1085 tv.tv_sec = TIMEOUT;
1086 tv.tv_usec = 0;
1087 setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof tv);
1088 setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv);
1089 struct sockaddr* addr = NULL;
1090 if (result->ai_family == AF_INET) {
1091 addr4.sin_addr = ((struct sockaddr_in*) result->ai_addr)->sin_addr;
1092 addr4.sin_family = AF_INET;
1093 addr4.sin_port = htons(port);
1094 addr = (struct sockaddr*)&addr4;
1095 }
1096 else if (result->ai_family == AF_INET6) {
1097 addr6.sin6_addr = ((struct sockaddr_in6*) result->ai_addr)->sin6_addr;
1098 addr6.sin6_family = AF_INET6;
1099 addr6.sin6_port = htons(port);
1100 addr = (struct sockaddr*)&addr6;
1101 } else {
1102 snprintf(tab->error, sizeof(tab->error),
1103 "Unexpected error, invalid address family %!s(MISSING)", gmi_host);
1104 goto request_error;
1105 }
1106 int family = result->ai_family;
1107 freeaddrinfo(result);
1108
1109 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
1110 conn.connected = 0;
1111 conn.addr = addr;
1112 conn.addr4 = addr4;
1113 conn.addr6 = addr6;
1114 conn.family = family;
1115 conn.socket = sockfd;
1116 pthread_cond_signal(&conn.cond);
1117 for (int i=0; i < TIMEOUT * 20 && !conn.connected; i++)
1118 nanosleep(&timeout, NULL);
1119 if (conn.connected != 1) {
1120 if (!conn.connected) {
1121 pthread_cancel(conn.tid);
1122 pthread_kill(conn.tid, SIGUSR1);
1123 pthread_join(conn.tid, NULL);
1124 pthread_create(&conn.tid, NULL, (void *(*)(void *))conn_thread, NULL);
1125 }
1126 #else
1127 int addr_size = (family == AF_INET)?sizeof(addr4):sizeof(addr6);
1128 if (connect(sockfd, addr, addr_size) != 0) {
1129 #endif
1130 snprintf(tab->error, sizeof(tab->error),
1131 "Connection to %!s(MISSING) timed out", gmi_host);
1132 goto request_error;
1133 }
1134
1135 if (tls_connect_socket(ctx, sockfd, gmi_host)) {
1136 snprintf(tab->error, sizeof(tab->error),
1137 "Unable to connect to: %!s(MISSING)", gmi_host);
1138 goto request_error;
1139 }
1140 if (tls_handshake(ctx)) {
1141 snprintf(tab->error, sizeof(tab->error),
1142 "Failed to handshake: %!s(MISSING)", gmi_host);
1143 goto request_error;
1144 }
1145 char buf[MAX_URL];
1146 ssize_t len = gmi_parseuri(url_buf, MAX_URL, buf, MAX_URL);
1147 if (len >= MAX_URL ||
1148 (len += strlcpy(&buf[len], "\r\n", MAX_URL - len)) >= MAX_URL) {
1149 snprintf(tab->error, sizeof(tab->error),
1150 "Url too long: %!s(MISSING)", buf);
1151 goto request_error;
1152 }
1153 if (tls_write(ctx, buf, len) < len) {
1154 snprintf(tab->error, sizeof(tab->error),
1155 "Failed to send data to: %!s(MISSING)", gmi_host);
1156 goto request_error;
1157 }
1158 time_t now = time(0);
1159 while (recv==TLS_WANT_POLLIN || recv==TLS_WANT_POLLOUT) {
1160 if (time(0) - now >= TIMEOUT) {
1161 snprintf(tab->error, sizeof(tab->error),
1162 "Connection to %!s(MISSING) timed out (sent no data)", gmi_host);
1163 goto request_error;
1164 }
1165 recv = tls_read(ctx, buf, sizeof(buf));
1166 }
1167
1168 if (recv <= 0) {
1169 snprintf(tab->error, sizeof(tab->error),
1170 "[%!d(MISSING)] Invalid data from: %!s(MISSING)", recv, gmi_host);
1171 goto request_error;
1172 }
1173 if (!strstr(buf, "\r\n")) {
1174 snprintf(tab->error, sizeof(tab->error),
1175 "Invalid data from: %!s(MISSING) (no CRLF)", gmi_host);
1176 goto request_error;
1177 }
1178 char* ptr = strchr(buf, ' ');
1179 if (!ptr) {
1180 snprintf(tab->error, sizeof(tab->error),
1181 "Invalid data from: %!s(MISSING)", gmi_host);
1182 goto request_error;
1183 }
1184 int previous_code = tab->page.code;
1185 tab->page.code = atoi(buf);
1186
1187 switch (tab->page.code) {
1188 case 10:
1189 case 11:
1190 ptr++;
1191 strlcpy(client.input.label, ptr, sizeof(client.input.label));
1192 ptr = strchr(client.input.label, '\n');
1193 if (ptr) *ptr = '\0';
1194 ptr = strchr(client.input.label, '\r');
1195 if (ptr) *ptr = '\0';
1196 goto exit;
1197 case 20:
1198 {
1199 tab->page.img.tried = 0;
1200 char* meta = strchr(++ptr, '\r');
1201 if (!meta) {
1202 snprintf(tab->error, sizeof(tab->error),
1203 "Invalid data from: %!s(MISSING)", gmi_host);
1204 goto request_error;
1205 }
1206 *meta = '\0';
1207 strlcpy(meta_buf, ptr, MAX_META);
1208 *meta = '\r';
1209 if ((strcasestr(meta_buf, "charset=") &&
1210 !strcasestr(meta_buf, "charset=utf-8"))
1211 || ((strncmp(meta_buf, "text/", 5))
1212 #ifdef TERMINAL_IMG_VIEWER
1213 && (strncmp(meta_buf, "image/", 6))
1214 #endif
1215 )) {
1216 if (tab->history) {
1217 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
1218 } else {
1219 strlcpy(tab->url, "about:home", sizeof(tab->url));
1220 }
1221 download = display_download(meta_buf);
1222 if (!download) {
1223 recv = -1;
1224 goto exit_download;
1225 }
1226 }
1227 char* ptr_meta = strchr(tab->page.meta, ';');
1228 if (ptr_meta) *ptr_meta = '\0';
1229 }
1230 break;
1231 case 30:
1232 snprintf(tab->error, sizeof(tab->error),
1233 "Redirect temporary");
1234 break;
1235 case 31:
1236 snprintf(tab->error, sizeof(tab->error),
1237 "Redirect permanent");
1238 break;
1239 case 40:
1240 snprintf(tab->error, sizeof(tab->error),
1241 "Temporary failure");
1242 goto request_error_msg;
1243 case 41:
1244 snprintf(tab->error, sizeof(tab->error),
1245 "Server unavailable");
1246 goto request_error_msg;
1247 case 42:
1248 snprintf(tab->error, sizeof(tab->error),
1249 "CGI error");
1250 goto request_error_msg;
1251 case 43:
1252 snprintf(tab->error, sizeof(tab->error),
1253 "Proxy error");
1254 goto request_error_msg;
1255 case 44:
1256 snprintf(tab->error, sizeof(tab->error),
1257 "Slow down: server above rate limit");
1258 goto request_error_msg;
1259 case 50:
1260 snprintf(tab->error, sizeof(tab->error),
1261 "Permanent failure");
1262 goto request_error_msg;
1263 case 51:
1264 snprintf(tab->error, sizeof(tab->error),
1265 "Not found %!s(MISSING)", url);
1266 goto request_error_msg;
1267 case 52:
1268 snprintf(tab->error, sizeof(tab->error),
1269 "Resource gone");
1270 goto request_error_msg;
1271 case 53:
1272 snprintf(tab->error, sizeof(tab->error),
1273 "Proxy request refused");
1274 goto request_error_msg;
1275 case 59:
1276 snprintf(tab->error, sizeof(tab->error),
1277 "Bad request");
1278 goto request_error_msg;
1279 case 60:
1280 snprintf(tab->error, sizeof(tab->error),
1281 "Client certificate required");
1282 goto request_error_msg;
1283 case 61:
1284 snprintf(tab->error, sizeof(tab->error),
1285 "Client not authorised");
1286 goto request_error_msg;
1287 case 62:
1288 snprintf(tab->error, sizeof(tab->error),
1289 "Client certificate not valid");
1290 goto request_error_msg;
1291 default:
1292 snprintf(tab->error, sizeof(tab->error),
1293 "Unknown status code: %!d(MISSING)", tab->page.code);
1294 tab->page.code = previous_code;
1295 goto request_error;
1296 }
1297 data_buf = malloc(recv+1);
1298 if (!data_buf) return fatalI();
1299 memcpy(data_buf, buf, recv);
1300 now = time(0);
1301 while (1) {
1302 if (time(0) - now >= TIMEOUT) {
1303 snprintf(tab->error, sizeof(tab->error),
1304 "Server %!s(MISSING) stopped responding", gmi_host);
1305 goto request_error;
1306 }
1307 int bytes = tls_read(ctx, buf, sizeof(buf));
1308 if (bytes == 0) break;
1309 if (bytes == TLS_WANT_POLLIN || bytes == TLS_WANT_POLLOUT) continue;
1310 if (bytes < 1) {
1311 snprintf(tab->error, sizeof(tab->error),
1312 "Invalid data from %!s(MISSING): %!s(MISSING)",
1313 gmi_host, tls_error(ctx));
1314 goto request_error;
1315 }
1316 data_buf = realloc(data_buf, recv+bytes+1);
1317 memcpy(&data_buf[recv], buf, bytes);
1318 recv += bytes;
1319 now = time(0);
1320 }
1321 exit:
1322 if (!download && recv > 0 && tab->page.code == 20 &&
1323 (!tab->history || (tab->history && !tab->history->next))) {
1324 gmi_cleanforward(tab);
1325 struct gmi_link* link = malloc(sizeof(struct gmi_link));
1326 if (!link) return fatalI();
1327 link->next = NULL;
1328 link->prev = tab->history;
1329 link->scroll = tab->scroll;
1330 strlcpy(link->url, url_buf, sizeof(link->url));
1331 tab->history = link;
1332 if (link->prev)
1333 link->prev->next = tab->history;
1334 }
1335 if (0) {
1336 request_error_msg:;
1337 char* cr = strchr(ptr+1, '\r');
1338 if (cr) {
1339 *cr = '\0';
1340 char buf[256];
1341 snprintf(buf, sizeof(buf),
1342 "%!s(MISSING) (%!d(MISSING) : %!s(MISSING))", ptr+1, tab->page.code, tab->error);
1343 strlcpy(tab->error, buf, sizeof(tab->error));
1344 *cr = '\r';
1345 }
1346 request_error:
1347 free(data_buf);
1348 if (tab->history) {
1349 strlcpy(tab->url, tab->history->url, sizeof(tab->url));
1350 } else {
1351 strlcpy(tab->url, "about:home", sizeof(tab->url));
1352 }
1353 client.input.error = 1;
1354 recv = -1;
1355 }
1356 exit_download:
1357 if (sockfd != -1)
1358 close(sockfd);
1359 if (ctx) {
1360 tls_close(ctx);
1361 tls_free(ctx);
1362 ctx = NULL;
1363 }
1364 if (recv > 0 && (tab->page.code == 11 || tab->page.code == 10)) {
1365 free(data_buf);
1366 strlcpy(tab->url, url_buf, sizeof(tab->url));
1367 return tab->page.data_len;
1368 }
1369 if (recv > 0 && (tab->page.code == 31 || tab->page.code == 30)) {
1370 char* ptr = strchr(data_buf, ' ');
1371 if (!ptr) {
1372 free(data_buf);
1373 return -1;
1374 }
1375 char* ln = strchr(ptr+1, ' ');
1376 if (ln) *ln = '\0';
1377 ln = strchr(ptr, '\n');
1378 if (ln) *ln = '\0';
1379 ln = strchr(ptr, '\r');
1380 if (ln) *ln = '\0';
1381 strlcpy(tab->url, url_buf, sizeof(tab->url));
1382 int r = gmi_nextlink(tab->url, ptr+1);
1383 if (r < 1 || tab->page.code != 20) {
1384 free(data_buf);
1385 return tab->page.data_len;
1386 }
1387 tab->page.data_len = r;
1388 free(data_buf);
1389 data_buf = NULL;
1390 gmi_load(&tab->page);
1391 tab->scroll = -1;
1392 return r;
1393 }
1394 if (!download && recv > 0 && tab->page.code == 20) {
1395 strlcpy(tab->page.meta, meta_buf, sizeof(tab->page.meta));
1396 strlcpy(tab->url, url_buf, sizeof(tab->url));
1397 tab->page.data_len = recv;
1398 free(tab->page.data); // should be cached
1399 tab->page.data = data_buf;
1400 gmi_load(&tab->page);
1401 }
1402 if (download && recv > 0 && tab->page.code == 20) {
1403 int len = strnlen(url_buf, sizeof(url_buf));
1404 if (url_buf[len-1] == '/')
1405 url_buf[len-1] = '\0';
1406 char* ptr = strrchr(url_buf, '/');
1407 FILE* f;
1408 char path[1024];
1409 int plen = getdownloadfolder(path, sizeof(path));
1410 if (plen < 0) plen = 0;
1411 else {
1412 path[plen] = '/';
1413 plen++;
1414 }
1415 if (ptr)
1416 strlcpy(&path[plen], ptr+1, sizeof(path)-plen);
1417 else
1418 strlcpy(&path[plen], "output.dat", sizeof(path)-plen);
1419 f = fopen(path, "wb");
1420 if (!f) {
1421 snprintf(tab->error, sizeof(tab->error),
1422 "Failed to write the downloaded file");
1423 client.input.error = 1;
1424 } else {
1425 char* ptr = strchr(data_buf, '\n')+1;
1426 if (ptr) {
1427 fwrite(ptr, 1, recv - (ptr-data_buf), f);
1428 fclose(f);
1429 #ifndef DISABLE_XDG
1430 if (client.xdg && display_open(path)) {
1431 char buf[1048];
1432 snprintf(buf, sizeof(buf), "xdg-open %!s(MISSING)", path);
1433 system(buf);
1434 }
1435 #endif
1436 client.input.info = 1;
1437 snprintf(tab->info, sizeof(tab->info),
1438 "File downloaded to %!s(MISSING)", path);
1439
1440 } else {
1441 client.input.error = 1;
1442 snprintf(tab->error, sizeof(tab->error),
1443 "Server sent invalid data");
1444 }
1445 }
1446 free(data_buf);
1447 recv = tab->page.data_len;
1448 }
1449 return recv;
1450 }
1451
1452 int gmi_loadfile(char* path) {
1453 struct gmi_tab* tab = &client.tabs[client.tab];
1454 FILE* f = fopen(path, "rb");
1455 if (!f) {
1456 snprintf(tab->error, sizeof(tab->error),
1457 "Failed to open %!s(MISSING)", path);
1458 client.input.error = 1;
1459 return -1;
1460 }
1461 fseek(f, 0, SEEK_END);
1462 size_t len = ftell(f);
1463 fseek(f, 0, SEEK_SET);
1464 char* data = malloc(len+1);
1465 if (!data) return fatalI();
1466 data[0] = '\n';
1467 if (len != fread(&data[1], 1, len, f)) {
1468 fclose(f);
1469 free(data);
1470 return -1;
1471 }
1472 fclose(f);
1473 tab->page.code = 20;
1474 tab->page.data_len = len;
1475 if (tab->page.data) free(tab->page.data);
1476 tab->page.data = data;
1477 snprintf(tab->url, sizeof(tab->url), "file://%!s(MISSING)/", path);
1478 return len;
1479 }
1480
1481 int gmi_init() {
1482 if (tls_init()) {
1483 printf("Failed to initialize TLS\n");
1484 return -1;
1485 }
1486
1487 config = tls_config_new();
1488 if (!config) {
1489 printf("Failed to initialize TLS config\n");
1490 return -1;
1491 }
1492
1493 config_empty = tls_config_new();
1494 if (!config_empty) {
1495 printf("Failed to initialize TLS config\n");
1496 return -1;
1497 }
1498
1499 tls_config_insecure_noverifycert(config);
1500 tls_config_insecure_noverifycert(config_empty);
1501 ctx = NULL;
1502 bzero(&client, sizeof(client));
1503
1504 #ifndef DISABLE_XDG
1505 if (!system("which xdg-open > /dev/null 2>&1"))
1506 client.xdg = 1;
1507 #endif
1508
1509 if (gmi_loadbookmarks()) {
1510 gmi_newbookmarks();
1511 }
1512
1513 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
1514 bzero(&conn, sizeof(conn));
1515 if (pthread_cond_init(&conn.cond, NULL)) {
1516 printf("Failed to initialize pthread condition\n");
1517 return -1;
1518 }
1519 if (pthread_create(&conn.tid, NULL, (void *(*)(void *))conn_thread, NULL)) {
1520 printf("Failed to initialize connection thread\n");
1521 return -1;
1522 }
1523 bzero(&dnsquery, sizeof(dnsquery));
1524 if (pthread_cond_init(&dnsquery.cond, NULL)) {
1525 printf("Failed to initialize thread condition\n");
1526 return -1;
1527 }
1528 if (pthread_create(&dnsquery.tid, NULL, (void *(*)(void *))dnsquery_thread, NULL)) {
1529 printf("Failed to initialize dns query thread\n");
1530 return -1;
1531 }
1532 signal(SIGUSR1, signal_cb);
1533 #endif
1534
1535 scm_init();
1536
1537 return 0;
1538 }
1539
1540 void gmi_free() {
1541 for (int i=0; i < client.tabs_count; i++)
1542 gmi_freetab(&client.tabs[i]);
1543 gmi_savebookmarks();
1544 for (int i = 0; client.bookmarks[i]; i++)
1545 free(client.bookmarks[i]);
1546 free(client.bookmarks);
1547 free(client.tabs);
1548 #if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__)
1549 pthread_kill(conn.tid, SIGUSR1);
1550 pthread_join(conn.tid, NULL);
1551 pthread_kill(dnsquery.tid, SIGUSR1);
1552 pthread_join(dnsquery.tid, NULL);
1553 #endif
1554 client.shutdown = 1;
1555 scm_free();
1556 }
1557
1558