Fix for FS#205 - use same number for hints with same url
[xombrero.git] / about.c
blob7b7696bbcd03f130238501ce0d8beec561b421c4
1 /*
2 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
3 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
4 * Copyright (c) 2010, 2011 Edd Barrett <vext01@gmail.com>
5 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
6 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
7 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 #include "xxxterm.h"
25 * xxxterm "protocol" (xtp)
26 * We use this for managing stuff like downloads and favorites. They
27 * make magical HTML pages in memory which have xxxt:// links in order
28 * to communicate with xxxterm's internals. These links take the format:
29 * xxxt://class/session_key/action/arg
31 * Don't begin xtp class/actions as 0. atoi returns that on error.
33 * Typically we have not put addition of items in this framework, as
34 * adding items is either done via an ex-command or via a keybinding instead.
37 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
38 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
39 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
40 "td{overflow: hidden;" \
41 " padding: 2px 2px 2px 2px;" \
42 " border: 1px solid black;" \
43 " vertical-align:top;" \
44 " word-wrap: break-word}\n" \
45 "tr:hover{background: #ffff99}\n" \
46 "th{background-color: #cccccc;" \
47 " border: 1px solid black}\n" \
48 "table{width: 100%%;" \
49 " border: 1px black solid;" \
50 " border-collapse:collapse}\n" \
51 ".progress-outer{" \
52 "border: 1px solid black;" \
53 " height: 8px;" \
54 " width: 90%%}\n" \
55 ".progress-inner{float: left;" \
56 " height: 8px;" \
57 " background: green}\n" \
58 ".dlstatus{font-size: small;" \
59 " text-align: center}\n" \
60 "</style>\n"
62 /* XTP classes (xxxt://<class>) */
63 #define XT_XTP_INVALID (0) /* invalid */
64 #define XT_XTP_DL (1) /* downloads */
65 #define XT_XTP_HL (2) /* history */
66 #define XT_XTP_CL (3) /* cookies */
67 #define XT_XTP_FL (4) /* favorites */
69 /* XTP download actions */
70 #define XT_XTP_DL_LIST (1)
71 #define XT_XTP_DL_CANCEL (2)
72 #define XT_XTP_DL_REMOVE (3)
73 #define XT_XTP_DL_UNLINK (4)
74 #define XT_XTP_DL_START (5)
76 /* XTP history actions */
77 #define XT_XTP_HL_LIST (1)
78 #define XT_XTP_HL_REMOVE (2)
80 /* XTP cookie actions */
81 #define XT_XTP_CL_LIST (1)
82 #define XT_XTP_CL_REMOVE (2)
83 #define XT_XTP_CL_REMOVE_DOMAIN (3)
85 /* XTP cookie actions */
86 #define XT_XTP_FL_LIST (1)
87 #define XT_XTP_FL_REMOVE (2)
89 int js_show_wl(struct tab *, struct karg *);
90 int pl_show_wl(struct tab *, struct karg *);
91 int set(struct tab *, struct karg *);
92 int marco(struct tab *, struct karg *);
93 int startpage(struct tab *, struct karg *);
94 const char * marco_message(int *);
96 struct about_type about_list[] = {
97 { XT_URI_ABOUT_ABOUT, about },
98 { XT_URI_ABOUT_BLANK, blank },
99 { XT_URI_ABOUT_CERTS, ca_cmd },
100 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
101 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
102 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
103 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
104 { XT_URI_ABOUT_HELP, help },
105 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
106 { XT_URI_ABOUT_JSWL, js_show_wl },
107 { XT_URI_ABOUT_SET, set },
108 { XT_URI_ABOUT_STATS, stats },
109 { XT_URI_ABOUT_MARCO, marco },
110 { XT_URI_ABOUT_STARTPAGE, startpage },
111 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
115 * Session IDs.
116 * We use these to prevent people putting xxxt:// URLs on
117 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
119 #define XT_XTP_SES_KEY_SZ 8
120 #define XT_XTP_SES_KEY_HEX_FMT \
121 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
122 char *dl_session_key; /* downloads */
123 char *hl_session_key; /* history list */
124 char *cl_session_key; /* cookie list */
125 char *fl_session_key; /* favorites list */
127 int updating_fl_tabs = 0;
128 int updating_dl_tabs = 0;
129 int updating_hl_tabs = 0;
130 int updating_cl_tabs = 0;
131 struct download_list downloads;
133 size_t
134 about_list_size(void)
136 return (LENGTH(about_list));
139 gchar *
140 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
142 gchar *r;
144 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
145 "<head>\n"
146 "<title>%s</title>\n"
147 "%s"
148 "%s"
149 "</head>\n"
150 "<body>\n"
151 "<h1>%s</h1>\n"
152 "%s\n</body>\n"
153 "</html>",
154 title,
155 addstyles ? XT_PAGE_STYLE : "",
156 head,
157 title,
158 body);
160 return (r);
164 * Display a web page from a HTML string in memory, rather than from a URL
166 void
167 load_webkit_string(struct tab *t, const char *str, gchar *title)
169 char file[PATH_MAX];
170 int i;
172 /* we set this to indicate we want to manually do navaction */
173 if (t->bfl)
174 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
176 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
177 if (title) {
178 /* set t->xtp_meaning */
179 for (i = 0; i < LENGTH(about_list); i++)
180 if (!strcmp(title, about_list[i].name)) {
181 t->xtp_meaning = i;
182 break;
185 webkit_web_view_load_string(t->wv, str, NULL, encoding,
186 "file://");
187 #if GTK_CHECK_VERSION(2, 20, 0)
188 gtk_spinner_stop(GTK_SPINNER(t->spinner));
189 gtk_widget_hide(t->spinner);
190 #endif
191 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[0]);
192 xt_icon_from_file(t, file);
197 blank(struct tab *t, struct karg *args)
199 if (t == NULL)
200 show_oops(NULL, "blank invalid parameters");
202 load_webkit_string(t, "", XT_URI_ABOUT_BLANK);
204 return (0);
208 about(struct tab *t, struct karg *args)
210 char *page, *body;
212 if (t == NULL)
213 show_oops(NULL, "about invalid parameters");
215 body = g_strdup_printf("<b>Version: %s</b>"
216 #ifdef XXXTERM_BUILDSTR
217 "<br><b>Build: %s</b>"
218 #endif
219 "<p>"
220 "Authors:"
221 "<ul>"
222 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
223 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
224 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
225 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
226 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
227 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
228 "</ul>"
229 "Copyrights and licenses can be found on the XXXTerm "
230 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
231 "</p>",
232 #ifdef XXXTERM_BUILDSTR
233 version, XXXTERM_BUILDSTR
234 #else
235 version
236 #endif
239 page = get_html_page("About", body, "", 0);
240 g_free(body);
242 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT);
243 g_free(page);
245 return (0);
249 help(struct tab *t, struct karg *args)
251 char *page, *head, *body;
253 if (t == NULL)
254 show_oops(NULL, "help invalid parameters");
256 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
257 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
258 "</head>\n";
259 body = "XXXTerm man page <a href=\"http://opensource.conformal.com/"
260 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
261 "cgi-bin/man-cgi?xxxterm</a>";
263 page = get_html_page(XT_NAME, body, head, FALSE);
265 load_webkit_string(t, page, XT_URI_ABOUT_HELP);
266 g_free(page);
268 return (0);
272 stats(struct tab *t, struct karg *args)
274 char *page, *body, *s, line[64 * 1024];
275 long long unsigned int line_count = 0;
276 FILE *r_cookie_f;
278 if (t == NULL)
279 show_oops(NULL, "stats invalid parameters");
281 line[0] = '\0';
282 if (save_rejected_cookies) {
283 if ((r_cookie_f = fopen(rc_fname, "r"))) {
284 for (;;) {
285 s = fgets(line, sizeof line, r_cookie_f);
286 if (s == NULL || feof(r_cookie_f) ||
287 ferror(r_cookie_f))
288 break;
289 line_count++;
291 fclose(r_cookie_f);
292 snprintf(line, sizeof line,
293 "<br/>Cookies blocked(*) total: %llu", line_count);
294 } else
295 show_oops(t, "Can't open blocked cookies file: %s",
296 strerror(errno));
299 body = g_strdup_printf(
300 "Cookies blocked(*) this session: %llu"
301 "%s"
302 "<p><small><b>*</b> results vary based on settings</small></p>",
303 blocked_cookies,
304 line);
306 page = get_html_page("Statistics", body, "", 0);
307 g_free(body);
309 load_webkit_string(t, page, XT_URI_ABOUT_STATS);
310 g_free(page);
312 return (0);
315 void
316 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
317 size_t cert_count, char *title)
319 gnutls_datum_t cinfo;
320 char *tmp, *body;
321 int i;
323 body = g_strdup("");
325 for (i = 0; i < cert_count; i++) {
326 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
327 &cinfo))
328 return;
330 tmp = body;
331 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
332 body, i, cinfo.data);
333 gnutls_free(cinfo.data);
334 g_free(tmp);
337 tmp = get_html_page(title, body, "", 0);
338 g_free(body);
340 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS);
341 g_free(tmp);
345 ca_cmd(struct tab *t, struct karg *args)
347 FILE *f = NULL;
348 int rv = 1, certs = 0, certs_read;
349 struct stat sb;
350 gnutls_datum_t dt;
351 gnutls_x509_crt_t *c = NULL;
352 char *certs_buf = NULL, *s;
354 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
355 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
356 return (1);
359 if (fstat(fileno(f), &sb) == -1) {
360 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
361 goto done;
364 certs_buf = g_malloc(sb.st_size + 1);
365 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
366 show_oops(t, "Can't read CA file: %s", strerror(errno));
367 goto done;
369 certs_buf[sb.st_size] = '\0';
371 s = certs_buf;
372 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
373 certs++;
374 s += strlen("BEGIN CERTIFICATE");
377 bzero(&dt, sizeof dt);
378 dt.data = (unsigned char *)certs_buf;
379 dt.size = sb.st_size;
380 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
381 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
382 GNUTLS_X509_FMT_PEM, 0);
383 if (certs_read <= 0) {
384 show_oops(t, "No cert(s) available");
385 goto done;
387 show_certs(t, c, certs_read, "Certificate Authority Certificates");
388 done:
389 if (c)
390 g_free(c);
391 if (certs_buf)
392 g_free(certs_buf);
393 if (f)
394 fclose(f);
396 return (rv);
400 cookie_show_wl(struct tab *t, struct karg *args)
402 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
403 wl_show(t, args, "Cookie White List", &c_wl);
405 return (0);
409 js_show_wl(struct tab *t, struct karg *args)
411 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
412 wl_show(t, args, "JavaScript White List", &js_wl);
414 return (0);
418 cookie_cmd(struct tab *t, struct karg *args)
420 if (args->i & XT_SHOW)
421 wl_show(t, args, "Cookie White List", &c_wl);
422 else if (args->i & XT_WL_TOGGLE) {
423 args->i |= XT_WL_RELOAD;
424 toggle_cwl(t, args);
425 } else if (args->i & XT_SAVE) {
426 args->i |= XT_WL_RELOAD;
427 wl_save(t, args, XT_WL_COOKIE);
428 } else if (args->i & XT_DELETE)
429 show_oops(t, "'cookie delete' currently unimplemented");
431 return (0);
435 js_cmd(struct tab *t, struct karg *args)
437 if (args->i & XT_SHOW)
438 wl_show(t, args, "JavaScript White List", &js_wl);
439 else if (args->i & XT_SAVE) {
440 args->i |= XT_WL_RELOAD;
441 wl_save(t, args, XT_WL_JAVASCRIPT);
442 } else if (args->i & XT_WL_TOGGLE) {
443 args->i |= XT_WL_RELOAD;
444 toggle_js(t, args);
445 } else if (args->i & XT_DELETE)
446 show_oops(t, "'js delete' currently unimplemented");
448 return (0);
452 pl_show_wl(struct tab *t, struct karg *args)
454 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
455 wl_show(t, args, "Plugin White List", &pl_wl);
457 return (0);
461 pl_cmd(struct tab *t, struct karg *args)
463 if (args->i & XT_SHOW)
464 wl_show(t, args, "Plugin White List", &pl_wl);
465 else if (args->i & XT_SAVE) {
466 args->i |= XT_WL_RELOAD;
467 wl_save(t, args, XT_WL_PLUGIN);
468 } else if (args->i & XT_WL_TOGGLE) {
469 args->i |= XT_WL_RELOAD;
470 toggle_pl(t, args);
471 } else if (args->i & XT_DELETE)
472 show_oops(t, "'plugin delete' currently unimplemented");
474 return (0);
478 * cancel, remove, etc. downloads
480 void
481 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
483 struct download find, *d = NULL;
485 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
487 /* some commands require a valid download id */
488 if (cmd != XT_XTP_DL_LIST) {
489 /* lookup download in question */
490 find.id = id;
491 d = RB_FIND(download_list, &downloads, &find);
493 if (d == NULL) {
494 show_oops(t, "%s: no such download", __func__);
495 return;
499 /* decide what to do */
500 switch (cmd) {
501 case XT_XTP_DL_START:
502 /* our downloads always needs to be
503 * restarted if called from here
505 download_start(t, d, XT_DL_RESTART);
506 break;
507 case XT_XTP_DL_CANCEL:
508 webkit_download_cancel(d->download);
509 g_object_unref(d->download);
510 RB_REMOVE(download_list, &downloads, d);
511 break;
512 case XT_XTP_DL_UNLINK:
513 unlink(webkit_download_get_destination_uri(d->download) +
514 strlen("file://"));
515 /* FALLTHROUGH */
516 case XT_XTP_DL_REMOVE:
517 webkit_download_cancel(d->download); /* just incase */
518 g_object_unref(d->download);
519 RB_REMOVE(download_list, &downloads, d);
520 break;
521 case XT_XTP_DL_LIST:
522 /* Nothing */
523 break;
524 default:
525 show_oops(t, "%s: unknown command", __func__);
526 break;
528 xtp_page_dl(t, NULL);
532 * Actions on history, only does one thing for now, but
533 * we provide the function for future actions
535 void
536 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
538 struct history *h, *next;
539 int i = 1;
541 switch (cmd) {
542 case XT_XTP_HL_REMOVE:
543 /* walk backwards, as listed in reverse */
544 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
545 next = RB_PREV(history_list, &hl, h);
546 if (id == i) {
547 RB_REMOVE(history_list, &hl, h);
548 g_free((gpointer) h->title);
549 g_free((gpointer) h->uri);
550 g_free(h);
551 break;
553 i++;
555 break;
556 case XT_XTP_HL_LIST:
557 /* Nothing - just xtp_page_hl() below */
558 break;
559 default:
560 show_oops(t, "%s: unknown command", __func__);
561 break;
564 xtp_page_hl(t, NULL);
567 /* remove a favorite */
568 void
569 remove_favorite(struct tab *t, int index)
571 char file[PATH_MAX], *title, *uri = NULL;
572 char *new_favs, *tmp;
573 FILE *f;
574 int i;
575 size_t len, lineno;
577 /* open favorites */
578 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
580 if ((f = fopen(file, "r")) == NULL) {
581 show_oops(t, "%s: can't open favorites: %s",
582 __func__, strerror(errno));
583 return;
586 /* build a string which will become the new favroites file */
587 new_favs = g_strdup("");
589 for (i = 1;;) {
590 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
591 if (feof(f) || ferror(f))
592 break;
593 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
594 if (len == 0) {
595 free(title);
596 title = NULL;
597 continue;
600 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
601 if (feof(f) || ferror(f)) {
602 show_oops(t, "%s: can't parse favorites %s",
603 __func__, strerror(errno));
604 goto clean;
608 /* as long as this isn't the one we are deleting add to file */
609 if (i != index) {
610 tmp = new_favs;
611 new_favs = g_strdup_printf("%s%s\n%s\n",
612 new_favs, title, uri);
613 g_free(tmp);
616 free(uri);
617 uri = NULL;
618 free(title);
619 title = NULL;
620 i++;
622 fclose(f);
624 /* write back new favorites file */
625 if ((f = fopen(file, "w")) == NULL) {
626 show_oops(t, "%s: can't open favorites: %s",
627 __func__, strerror(errno));
628 goto clean;
631 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
632 show_oops(t, "%s: can't fwrite", __func__);
633 fclose(f);
635 clean:
636 if (uri)
637 free(uri);
638 if (title)
639 free(title);
641 g_free(new_favs);
645 add_favorite(struct tab *t, struct karg *args)
647 char file[PATH_MAX];
648 FILE *f;
649 char *line = NULL;
650 size_t urilen, linelen;
651 const gchar *uri, *title;
653 if (t == NULL)
654 return (1);
656 /* don't allow adding of xtp pages to favorites */
657 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
658 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
659 return (1);
662 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
663 if ((f = fopen(file, "r+")) == NULL) {
664 show_oops(t, "Can't open favorites file: %s", strerror(errno));
665 return (1);
668 title = get_title(t, FALSE);
669 uri = get_uri(t);
671 if (title == NULL || uri == NULL) {
672 show_oops(t, "can't add page to favorites");
673 goto done;
676 urilen = strlen(uri);
678 for (;;) {
679 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
680 if (feof(f) || ferror(f))
681 break;
683 if (linelen == urilen && !strcmp(line, uri))
684 goto done;
686 free(line);
687 line = NULL;
690 fprintf(f, "\n%s\n%s", title, uri);
691 done:
692 if (line)
693 free(line);
694 fclose(f);
696 update_favorite_tabs(NULL);
698 return (0);
701 void
702 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
704 switch (cmd) {
705 case XT_XTP_FL_LIST:
706 /* nothing, just the below call to xtp_page_fl() */
707 break;
708 case XT_XTP_FL_REMOVE:
709 remove_favorite(t, arg);
710 break;
711 default:
712 show_oops(t, "%s: invalid favorites command", __func__);
713 break;
716 xtp_page_fl(t, NULL);
719 void
720 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
722 switch (cmd) {
723 case XT_XTP_CL_LIST:
724 /* nothing, just xtp_page_cl() */
725 break;
726 case XT_XTP_CL_REMOVE:
727 remove_cookie(arg);
728 break;
729 case XT_XTP_CL_REMOVE_DOMAIN:
730 remove_cookie_domain(arg);
731 break;
732 default:
733 show_oops(t, "%s: unknown cookie xtp command", __func__);
734 break;
737 xtp_page_cl(t, NULL);
740 /* link an XTP class to it's session key and handler function */
741 struct xtp_despatch {
742 uint8_t xtp_class;
743 char **session_key;
744 void (*handle_func)(struct tab *, uint8_t, int);
747 struct xtp_despatch xtp_despatches[] = {
748 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
749 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
750 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
751 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
752 { XT_XTP_INVALID, NULL, NULL }
756 * generate a session key to secure xtp commands.
757 * pass in a ptr to the key in question and it will
758 * be modified in place.
760 void
761 generate_xtp_session_key(char **key)
763 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
765 /* free old key */
766 if (*key)
767 g_free(*key);
769 /* make a new one */
770 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
771 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
772 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
773 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
775 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
778 void
779 xtp_generate_keys(void)
781 /* generate session keys for xtp pages */
782 generate_xtp_session_key(&dl_session_key);
783 generate_xtp_session_key(&hl_session_key);
784 generate_xtp_session_key(&cl_session_key);
785 generate_xtp_session_key(&fl_session_key);
789 * validate a xtp session key.
790 * return (1) if OK
793 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
795 if (strcmp(trusted, untrusted) != 0) {
796 show_oops(t, "%s: xtp session key mismatch possible spoof",
797 __func__);
798 return (0);
801 return (1);
805 * is the url xtp protocol? (xxxt://)
806 * if so, parse and despatch correct bahvior
809 parse_xtp_url(struct tab *t, const char *url)
811 char *dup = NULL, *p, *last = NULL;
812 uint8_t n_tokens = 0;
813 char *tokens[4] = {NULL, NULL, NULL, ""};
814 struct xtp_despatch *dsp, *dsp_match = NULL;
815 uint8_t req_class;
816 int ret = FALSE;
819 * tokens array meaning:
820 * tokens[0] = class
821 * tokens[1] = session key
822 * tokens[2] = action
823 * tokens[3] = optional argument
826 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
828 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
829 goto clean;
831 dup = g_strdup(url + strlen(XT_XTP_STR));
833 /* split out the url */
834 for ((p = strtok_r(dup, "/", &last)); p;
835 (p = strtok_r(NULL, "/", &last))) {
836 if (n_tokens < 4)
837 tokens[n_tokens++] = p;
840 /* should be atleast three fields 'class/seskey/command/arg' */
841 if (n_tokens < 3)
842 goto clean;
844 dsp = xtp_despatches;
845 req_class = atoi(tokens[0]);
846 while (dsp->xtp_class) {
847 if (dsp->xtp_class == req_class) {
848 dsp_match = dsp;
849 break;
851 dsp++;
854 /* did we find one atall? */
855 if (dsp_match == NULL) {
856 show_oops(t, "%s: no matching xtp despatch found", __func__);
857 goto clean;
860 /* check session key and call despatch function */
861 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
862 ret = TRUE; /* all is well, this was a valid xtp request */
863 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
866 clean:
867 if (dup)
868 g_free(dup);
870 return (ret);
874 * update all favorite tabs apart from one. Pass NULL if
875 * you want to update all.
877 void
878 update_favorite_tabs(struct tab *apart_from)
880 struct tab *t;
881 if (!updating_fl_tabs) {
882 updating_fl_tabs = 1; /* stop infinite recursion */
883 TAILQ_FOREACH(t, &tabs, entry)
884 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
885 && (t != apart_from))
886 xtp_page_fl(t, NULL);
887 updating_fl_tabs = 0;
892 * update all download tabs apart from one. Pass NULL if
893 * you want to update all.
895 void
896 update_download_tabs(struct tab *apart_from)
898 struct tab *t;
899 if (!updating_dl_tabs) {
900 updating_dl_tabs = 1; /* stop infinite recursion */
901 TAILQ_FOREACH(t, &tabs, entry)
902 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
903 && (t != apart_from))
904 xtp_page_dl(t, NULL);
905 updating_dl_tabs = 0;
910 * update all cookie tabs apart from one. Pass NULL if
911 * you want to update all.
913 void
914 update_cookie_tabs(struct tab *apart_from)
916 struct tab *t;
917 if (!updating_cl_tabs) {
918 updating_cl_tabs = 1; /* stop infinite recursion */
919 TAILQ_FOREACH(t, &tabs, entry)
920 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
921 && (t != apart_from))
922 xtp_page_cl(t, NULL);
923 updating_cl_tabs = 0;
928 * update all history tabs apart from one. Pass NULL if
929 * you want to update all.
931 void
932 update_history_tabs(struct tab *apart_from)
934 struct tab *t;
936 if (!updating_hl_tabs) {
937 updating_hl_tabs = 1; /* stop infinite recursion */
938 TAILQ_FOREACH(t, &tabs, entry)
939 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
940 && (t != apart_from))
941 xtp_page_hl(t, NULL);
942 updating_hl_tabs = 0;
946 /* show a list of favorites (bookmarks) */
948 xtp_page_fl(struct tab *t, struct karg *args)
950 char file[PATH_MAX];
951 FILE *f;
952 char *uri = NULL, *title = NULL;
953 size_t len, lineno = 0;
954 int i, failed = 0;
955 char *body, *tmp, *page = NULL;
956 const char delim[3] = {'\\', '\\', '\0'};
958 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
960 if (t == NULL)
961 warn("%s: bad param", __func__);
963 /* new session key */
964 if (!updating_fl_tabs)
965 generate_xtp_session_key(&fl_session_key);
967 /* open favorites */
968 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
969 if ((f = fopen(file, "r")) == NULL) {
970 show_oops(t, "Can't open favorites file: %s", strerror(errno));
971 return (1);
974 /* body */
975 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
976 "<th style='width: 40px'>&#35;</th><th>Link</th>"
977 "<th style='width: 40px'>Rm</th></tr>\n");
979 for (i = 1;;) {
980 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
981 break;
982 if (strlen(title) == 0) {
983 free(title);
984 title = NULL;
985 continue;
988 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
989 if (feof(f) || ferror(f)) {
990 show_oops(t, "favorites file corrupt");
991 failed = 1;
992 break;
995 tmp = body;
996 body = g_strdup_printf("%s<tr>"
997 "<td>%d</td>"
998 "<td><a href='%s'>%s</a></td>"
999 "<td style='text-align: center'>"
1000 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1001 "</tr>\n",
1002 body, i, uri, title,
1003 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
1005 g_free(tmp);
1007 free(uri);
1008 uri = NULL;
1009 free(title);
1010 title = NULL;
1011 i++;
1013 fclose(f);
1015 /* if none, say so */
1016 if (i == 1) {
1017 tmp = body;
1018 body = g_strdup_printf("%s<tr>"
1019 "<td colspan='3' style='text-align: center'>"
1020 "No favorites - To add one use the 'favadd' command."
1021 "</td></tr>", body);
1022 g_free(tmp);
1025 tmp = body;
1026 body = g_strdup_printf("%s</table>", body);
1027 g_free(tmp);
1029 if (uri)
1030 free(uri);
1031 if (title)
1032 free(title);
1034 /* render */
1035 if (!failed) {
1036 page = get_html_page("Favorites", body, "", 1);
1037 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES);
1038 g_free(page);
1041 update_favorite_tabs(t);
1043 if (body)
1044 g_free(body);
1046 return (failed);
1050 * Return a new string with a download row (in html)
1051 * appended. Old string is freed.
1053 char *
1054 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1057 WebKitDownloadStatus stat;
1058 const gchar *destination;
1059 char *status_html = NULL, *cmd_html = NULL, *new_html;
1060 gdouble progress;
1061 char cur_sz[FMT_SCALED_STRSIZE];
1062 char tot_sz[FMT_SCALED_STRSIZE];
1063 char *xtp_prefix;
1065 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1067 /* All actions wil take this form:
1068 * xxxt://class/seskey
1070 xtp_prefix = g_strdup_printf("%s%d/%s/",
1071 XT_XTP_STR, XT_XTP_DL, dl_session_key);
1073 stat = webkit_download_get_status(dl->download);
1075 switch (stat) {
1076 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1077 status_html = g_strdup_printf("Finished");
1078 cmd_html = g_strdup_printf(
1079 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1080 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1081 XT_XTP_DL_UNLINK, dl->id);
1082 break;
1083 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1084 /* gather size info */
1085 progress = 100 * webkit_download_get_progress(dl->download);
1087 fmt_scaled(
1088 webkit_download_get_current_size(dl->download), cur_sz);
1089 fmt_scaled(
1090 webkit_download_get_total_size(dl->download), tot_sz);
1092 status_html = g_strdup_printf(
1093 "<div style='width: 100%%' align='center'>"
1094 "<div class='progress-outer'>"
1095 "<div class='progress-inner' style='width: %.2f%%'>"
1096 "</div></div></div>"
1097 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1098 progress, cur_sz, tot_sz, progress);
1100 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1101 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1103 break;
1104 /* LLL */
1105 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1106 status_html = g_strdup_printf("Cancelled");
1107 cmd_html = g_strdup_printf(
1108 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1109 xtp_prefix, XT_XTP_DL_START, dl->id,
1110 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1111 XT_XTP_DL_UNLINK, dl->id);
1112 break;
1113 case WEBKIT_DOWNLOAD_STATUS_ERROR:
1114 status_html = g_strdup_printf("Error!");
1115 cmd_html = g_strdup_printf(
1116 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1117 xtp_prefix, XT_XTP_DL_START, dl->id,
1118 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1119 XT_XTP_DL_UNLINK, dl->id);
1120 break;
1121 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1122 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1123 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1124 XT_XTP_DL_CANCEL, dl->id);
1125 status_html = g_strdup_printf("Created");
1126 break;
1127 default:
1128 show_oops(t, "%s: unknown download status", __func__);
1131 destination = webkit_download_get_destination_uri(dl->download);
1132 /* we might not have a destination set yet */
1133 if (!destination)
1134 destination = webkit_download_get_suggested_filename(dl->download);
1135 new_html = g_strdup_printf(
1136 "%s\n<tr><td>%s</td><td>%s</td>"
1137 "<td style='text-align:center'>%s</td></tr>\n",
1138 html, basename((char *)destination),
1139 status_html, cmd_html);
1140 g_free(html);
1142 if (status_html)
1143 g_free(status_html);
1145 if (cmd_html)
1146 g_free(cmd_html);
1148 g_free(xtp_prefix);
1150 return new_html;
1153 /* cookie management XTP page */
1155 xtp_page_cl(struct tab *t, struct karg *args)
1157 char *body, *page, *tmp;
1158 int i = 1; /* all ids start 1 */
1159 int domain_id = 0;
1160 GSList *sc, *pc, *pc_start;
1161 SoupCookie *c;
1162 char *type, *table_headers, *last_domain;
1164 DNPRINTF(XT_D_CMD, "%s", __func__);
1166 if (t == NULL) {
1167 show_oops(NULL, "%s invalid parameters", __func__);
1168 return (1);
1171 /* Generate a new session key */
1172 if (!updating_cl_tabs)
1173 generate_xtp_session_key(&cl_session_key);
1175 /* table headers */
1176 table_headers = g_strdup_printf("<table><tr>"
1177 "<th>Type</th>"
1178 "<th>Name</th>"
1179 "<th style='width:200px'>Value</th>"
1180 "<th>Path</th>"
1181 "<th>Expires</th>"
1182 "<th>Secure</th>"
1183 "<th>HTTP<br />only</th>"
1184 "<th style='width:40px'>Rm</th></tr>\n");
1186 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1187 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1188 pc_start = pc;
1190 body = NULL;
1191 last_domain = strdup("");
1192 for (; sc; sc = sc->next) {
1193 c = sc->data;
1195 if (strcmp(last_domain, c->domain) != 0) {
1196 /* new domain */
1197 domain_id ++;
1198 free(last_domain);
1199 last_domain = strdup(c->domain);
1201 if (body != NULL) {
1202 tmp = body;
1203 body = g_strdup_printf("%s</table>"
1204 "<h2>%s</h2>"
1205 "<a href='%s%d/%s/%d/%d'>remove all</a>%s\n",
1206 body, c->domain,
1207 XT_XTP_STR, XT_XTP_CL,
1208 cl_session_key, XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1209 table_headers);
1210 g_free(tmp);
1211 } else {
1212 /* first domain */
1213 body = g_strdup_printf("<h2>%s</h2>"
1214 "<a href='%s%d/%s/%d/%d'>remove all</a>%s\n",
1215 c->domain,
1216 XT_XTP_STR, XT_XTP_CL,
1217 cl_session_key, XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1218 table_headers);
1222 type = "Session";
1223 for (pc = pc_start; pc; pc = pc->next)
1224 if (soup_cookie_equal(pc->data, c)) {
1225 type = "Session + Persistent";
1226 break;
1229 tmp = body;
1230 body = g_strdup_printf(
1231 "%s\n<tr>"
1232 "<td>%s</td>"
1233 "<td style='word-wrap:normal'>%s</td>"
1234 "<td>"
1235 " <textarea rows='4'>%s</textarea>"
1236 "</td>"
1237 "<td>%s</td>"
1238 "<td>%s</td>"
1239 "<td>%d</td>"
1240 "<td>%d</td>"
1241 "<td style='text-align:center'>"
1242 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1243 body,
1244 type,
1245 c->name,
1246 c->value,
1247 c->path,
1248 c->expires ?
1249 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1250 c->secure,
1251 c->http_only,
1253 XT_XTP_STR,
1254 XT_XTP_CL,
1255 cl_session_key,
1256 XT_XTP_CL_REMOVE,
1260 g_free(tmp);
1261 i++;
1264 soup_cookies_free(sc);
1265 soup_cookies_free(pc);
1267 /* small message if there are none */
1268 if (i == 1) {
1269 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1270 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1272 tmp = body;
1273 body = g_strdup_printf("%s</table>", body);
1274 g_free(tmp);
1276 page = get_html_page("Cookie Jar", body, "", TRUE);
1277 g_free(body);
1278 g_free(table_headers);
1279 g_free(last_domain);
1281 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR);
1282 update_cookie_tabs(t);
1284 g_free(page);
1286 return (0);
1290 xtp_page_hl(struct tab *t, struct karg *args)
1292 char *body, *page, *tmp;
1293 struct history *h;
1294 int i = 1; /* all ids start 1 */
1296 DNPRINTF(XT_D_CMD, "%s", __func__);
1298 if (t == NULL) {
1299 show_oops(NULL, "%s invalid parameters", __func__);
1300 return (1);
1303 /* Generate a new session key */
1304 if (!updating_hl_tabs)
1305 generate_xtp_session_key(&hl_session_key);
1307 /* body */
1308 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1309 "<th>URI</th><th>Title</th><th>Last visited</th><th style='width: 40px'>Rm</th></tr>\n");
1311 RB_FOREACH_REVERSE(h, history_list, &hl) {
1312 tmp = body;
1313 body = g_strdup_printf(
1314 "%s\n<tr>"
1315 "<td><a href='%s'>%s</a></td>"
1316 "<td>%s</td>"
1317 "<td>%s</td>"
1318 "<td style='text-align: center'>"
1319 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1320 body, h->uri, h->uri, h->title, ctime(&h->time),
1321 XT_XTP_STR, XT_XTP_HL, hl_session_key,
1322 XT_XTP_HL_REMOVE, i);
1324 g_free(tmp);
1325 i++;
1328 /* small message if there are none */
1329 if (i == 1) {
1330 tmp = body;
1331 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1332 "colspan='3'>No History</td></tr>\n", body);
1333 g_free(tmp);
1336 tmp = body;
1337 body = g_strdup_printf("%s</table>", body);
1338 g_free(tmp);
1340 page = get_html_page("History", body, "", TRUE);
1341 g_free(body);
1344 * update all history manager tabs as the xtp session
1345 * key has now changed. No need to update the current tab.
1346 * Already did that above.
1348 update_history_tabs(t);
1350 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY);
1351 g_free(page);
1353 return (0);
1357 * Generate a web page detailing the status of any downloads
1360 xtp_page_dl(struct tab *t, struct karg *args)
1362 struct download *dl;
1363 char *body, *page, *tmp;
1364 char *ref;
1365 int n_dl = 1;
1367 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1369 if (t == NULL) {
1370 show_oops(NULL, "%s invalid parameters", __func__);
1371 return (1);
1375 * Generate a new session key for next page instance.
1376 * This only happens for the top level call to xtp_page_dl()
1377 * in which case updating_dl_tabs is 0.
1379 if (!updating_dl_tabs)
1380 generate_xtp_session_key(&dl_session_key);
1382 /* header - with refresh so as to update */
1383 if (refresh_interval >= 1)
1384 ref = g_strdup_printf(
1385 "<meta http-equiv='refresh' content='%u"
1386 ";url=%s%d/%s/%d' />\n",
1387 refresh_interval,
1388 XT_XTP_STR,
1389 XT_XTP_DL,
1390 dl_session_key,
1391 XT_XTP_DL_LIST);
1392 else
1393 ref = g_strdup("");
1395 body = g_strdup_printf("<div align='center'>"
1396 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1397 "</p><table><tr><th style='width: 60%%'>"
1398 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1399 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
1401 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1402 body = xtp_page_dl_row(t, body, dl);
1403 n_dl++;
1406 /* message if no downloads in list */
1407 if (n_dl == 1) {
1408 tmp = body;
1409 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1410 " style='text-align: center'>"
1411 "No downloads</td></tr>\n", body);
1412 g_free(tmp);
1415 tmp = body;
1416 body = g_strdup_printf("%s</table></div>", body);
1417 g_free(tmp);
1419 page = get_html_page("Downloads", body, ref, 1);
1420 g_free(ref);
1421 g_free(body);
1424 * update all download manager tabs as the xtp session
1425 * key has now changed. No need to update the current tab.
1426 * Already did that above.
1428 update_download_tabs(t);
1430 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS);
1431 g_free(page);
1433 return (0);
1437 startpage(struct tab *t, struct karg *args)
1439 char *page, *body, *b;
1440 struct sp *s;
1442 if (t == NULL)
1443 show_oops(NULL, "startpage invalid parameters");
1445 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1447 TAILQ_FOREACH(s, &spl, entry) {
1448 b = body;
1449 body = g_strdup_printf("%s%s<br>", body, s->line);
1450 g_free(b);
1453 page = get_html_page("Startup Exception", body, "", 0);
1454 g_free(body);
1456 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE);
1457 g_free(page);
1459 return (0);
1462 void
1463 startpage_add(const char *fmt, ...)
1465 va_list ap;
1466 char *msg;
1467 struct sp *s;
1469 if (fmt == NULL)
1470 return;
1472 va_start(ap, fmt);
1473 if (vasprintf(&msg, fmt, ap) == -1)
1474 errx(1, "startpage_add failed");
1475 va_end(ap);
1477 s = g_malloc0(sizeof *s);
1478 s->line = msg;
1480 TAILQ_INSERT_TAIL(&spl, s, entry);