Don't color bar when warning of cached cert mismatch
[xombrero.git] / about.c
blob46d091881abfc2e038751d8b15b0d148ef8a071d
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, 2012 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>
8 * Copyright (c) 2012 Josh Rickmar <jrick@devio.us>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 #include <xombrero.h>
26 * xombrero "protocol" (xtp)
27 * We use this for managing stuff like downloads and favorites. They
28 * make magical HTML pages in memory which have xxxt:// links in order
29 * to communicate with xombrero's internals. These links take the format:
30 * xxxt://class/session_key/action/arg
32 * Don't begin xtp class/actions as 0. atoi returns that on error.
34 * Typically we have not put addition of items in this framework, as
35 * adding items is either done via an ex-command or via a keybinding instead.
38 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
39 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
40 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
41 "td{overflow: hidden;" \
42 " padding: 2px 2px 2px 2px;" \
43 " border: 1px solid black;" \
44 " vertical-align:top;" \
45 " word-wrap: break-word}\n" \
46 "tr:hover{background: #ffff99}\n" \
47 "th{background-color: #cccccc;" \
48 " border: 1px solid black}\n" \
49 "table{width: 100%%;" \
50 " border: 1px black solid;" \
51 " border-collapse:collapse}\n" \
52 ".progress-outer{" \
53 "border: 1px solid black;" \
54 " height: 8px;" \
55 " width: 90%%}\n" \
56 ".progress-inner{float: left;" \
57 " height: 8px;" \
58 " background: green}\n" \
59 ".dlstatus{font-size: small;" \
60 " text-align: center}\n" \
61 "table#settings{background-color: #eee;"\
62 " border: 0px;" \
63 " margin: 15px;}\n" \
64 "table#settings td{border: 0px;}\n" \
65 "table#settings th{border: 0px;}\n" \
66 "table#settings tr{" \
67 " background: #f6f6f6;}\n" \
68 "table#settings tr:nth-child(even){" \
69 " background: #eeeeee;}\n" \
70 "table#settings tr#modified{" \
71 " background: #FFFFBA;}\n" \
72 "table#settings tr#modified:nth-child(even){" \
73 " background: #ffffA0;}\n" \
74 "</style>\n"
76 int js_show_wl(struct tab *, struct karg *);
77 int pl_show_wl(struct tab *, struct karg *);
78 int https_show_wl(struct tab *, struct karg *);
79 int xtp_page_set(struct tab *, struct karg *);
80 int xtp_page_rt(struct tab *, struct karg *);
81 int marco(struct tab *, struct karg *);
82 int startpage(struct tab *, struct karg *);
83 const char * marco_message(int *);
84 void update_cookie_tabs(struct tab *apart_from);
85 int about_webkit(struct tab *, struct karg *);
86 int allthethings(struct tab *, struct karg *);
89 * If you change the index of any of these, correct the
90 * XT_XTP_TAB_MEANING_* macros in xombrero.h!
92 struct about_type about_list[] = {
93 { XT_URI_ABOUT_ABOUT, xtp_page_ab },
94 { XT_URI_ABOUT_ALLTHETHINGS, allthethings },
95 { XT_URI_ABOUT_BLANK, blank },
96 { XT_URI_ABOUT_CERTS, ca_cmd },
97 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
98 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
99 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
100 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
101 { XT_URI_ABOUT_HELP, help },
102 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
103 { XT_URI_ABOUT_JSWL, js_show_wl },
104 { XT_URI_ABOUT_SET, xtp_page_set },
105 { XT_URI_ABOUT_STATS, stats },
106 { XT_URI_ABOUT_MARCO, marco },
107 { XT_URI_ABOUT_STARTPAGE, startpage },
108 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
109 { XT_URI_ABOUT_HTTPS, https_show_wl },
110 { XT_URI_ABOUT_WEBKIT, about_webkit },
111 { XT_URI_ABOUT_SEARCH, xtp_page_sl },
112 { XT_URI_ABOUT_RUNTIME, xtp_page_rt },
113 { XT_URI_ABOUT_SECVIOLATION, NULL },
116 struct search_type {
117 const char *name;
118 const char *url;
119 } search_list[] = {
120 { "Google (SSL)", "https://encrypted.google.com/search?q=%s&&client=xombrero" },
121 { "Bing", "http://www.bing.com/search?q=%s" },
122 { "Yahoo", "http://search.yahoo.com/search?p=%s" },
123 { "DuckDuckGo", "https://duckduckgo.com/?q=%s" },
124 { "DuckDuckGo (HTML)", "https://duckduckgo.com/html?q=%s" },
125 { "DuckDuckGo (Lite)", "https://duckduckgo.com/lite?q=%s" },
126 { "Ixquick", "https://ixquick.com/do/search?q=%s" },
127 { "Startpage", "https://startpage.com/do/search?q=%s" },
131 * Session IDs.
132 * We use these to prevent people putting xxxt:// URLs on
133 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
135 #define XT_XTP_SES_KEY_SZ 8
136 #define XT_XTP_SES_KEY_HEX_FMT \
137 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8
139 int updating_fl_tabs = 0;
140 int updating_dl_tabs = 0;
141 int updating_hl_tabs = 0;
142 int updating_cl_tabs = 0;
143 int updating_sl_tabs = 0;
144 int updating_sv_tabs = 0;
145 int updating_set_tabs = 0;
146 struct download_list downloads;
148 size_t
149 about_list_size(void)
151 return (LENGTH(about_list));
154 gchar *
155 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
157 gchar *r;
159 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
160 "<head>\n"
161 "<title>%s</title>\n"
162 "%s"
163 "%s"
164 "</head>\n"
165 "<body>\n"
166 "<h1>%s</h1>\n"
167 "%s\n</body>\n"
168 "</html>",
169 title,
170 addstyles ? XT_PAGE_STYLE : "",
171 head,
172 title,
173 body);
175 return (r);
179 * Display a web page from a HTML string in memory, rather than from a URL
181 void
182 load_webkit_string(struct tab *t, const char *str, gchar *title, int nohist)
184 char file[PATH_MAX];
185 int i;
187 if (g_signal_handler_is_connected(t->wv, t->progress_handle))
188 g_signal_handler_disconnect(t->wv, t->progress_handle);
190 /* we set this to indicate we want to manually do navaction */
191 if (t->bfl && !nohist) {
192 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
193 if (t->item)
194 g_object_ref(t->item);
197 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
198 if (title) {
199 /* set t->xtp_meaning */
200 for (i = 0; i < LENGTH(about_list); i++)
201 if (!strcmp(title, about_list[i].name)) {
202 t->xtp_meaning = i;
203 break;
206 webkit_web_view_load_string(t->wv, str, NULL, encoding,
207 XT_XTP_STR);
208 #if GTK_CHECK_VERSION(2, 20, 0)
209 gtk_spinner_stop(GTK_SPINNER(t->spinner));
210 gtk_widget_hide(t->spinner);
211 #endif
212 snprintf(file, sizeof file, "%s" PS "%s", resource_dir, icons[0]);
213 xt_icon_from_file(t, file);
216 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL &&
217 t->session_key != NULL) {
218 g_free(t->session_key);
219 t->session_key = NULL;
222 t->progress_handle = g_signal_connect(t->wv,
223 "notify::progress", G_CALLBACK(webview_progress_changed_cb), t);
227 blank(struct tab *t, struct karg *args)
229 if (t == NULL)
230 show_oops(NULL, "blank invalid parameters");
232 load_webkit_string(t, "", XT_URI_ABOUT_BLANK, 0);
234 return (0);
238 help(struct tab *t, struct karg *args)
240 char *page, *head, *body;
242 if (t == NULL)
243 show_oops(NULL, "help invalid parameters");
245 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
246 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xombrero\">"
247 "</head>\n";
248 body = "xombrero man page <a href=\"http://opensource.conformal.com/"
249 "cgi-bin/man-cgi?xombrero\">http://opensource.conformal.com/"
250 "cgi-bin/man-cgi?xombrero</a>";
252 page = get_html_page(XT_NAME, body, head, FALSE);
254 load_webkit_string(t, page, XT_URI_ABOUT_HELP, 0);
255 g_free(page);
257 return (0);
261 stats(struct tab *t, struct karg *args)
263 char *page, *body, *s, line[64 * 1024];
264 uint64_t line_count = 0;
265 FILE *r_cookie_f;
267 if (t == NULL)
268 show_oops(NULL, "stats invalid parameters");
270 line[0] = '\0';
271 if (save_rejected_cookies) {
272 if ((r_cookie_f = fopen(rc_fname, "r"))) {
273 for (;;) {
274 s = fgets(line, sizeof line, r_cookie_f);
275 if (s == NULL || feof(r_cookie_f) ||
276 ferror(r_cookie_f))
277 break;
278 line_count++;
280 fclose(r_cookie_f);
281 snprintf(line, sizeof line,
282 "<br/>Cookies blocked(*) total: %" PRIu64,
283 line_count);
284 } else
285 show_oops(t, "Can't open blocked cookies file: %s",
286 strerror(errno));
289 body = g_strdup_printf(
290 "Cookies blocked(*) this session: %" PRIu64
291 "%s"
292 "<p><small><b>*</b> results vary based on settings</small></p>",
293 blocked_cookies,
294 line);
296 page = get_html_page("Statistics", body, "", 0);
297 g_free(body);
299 load_webkit_string(t, page, XT_URI_ABOUT_STATS, 0);
300 g_free(page);
302 return (0);
305 void
306 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
307 size_t cert_count, char *title)
309 gnutls_datum_t *cinfo;
310 char *tmp, *body;
311 int i;
313 body = g_strdup("");
315 for (i = 0; i < cert_count; i++) {
316 cinfo = gnutls_malloc(sizeof *cinfo);
317 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
318 cinfo)) {
319 gnutls_free(cinfo);
320 g_free(body);
321 return;
324 tmp = body;
325 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
326 body, i, cinfo->data);
327 gnutls_free(cinfo);
328 g_free(tmp);
331 tmp = get_html_page(title, body, "", 0);
332 g_free(body);
334 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS, 0);
335 g_free(tmp);
339 ca_cmd(struct tab *t, struct karg *args)
341 FILE *f = NULL;
342 int rv = 1, certs_read;
343 unsigned int certs = 0;
344 struct stat sb;
345 gnutls_datum_t dt;
346 gnutls_x509_crt_t *c = NULL;
347 char *certs_buf = NULL, *s;
349 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
350 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
351 return (1);
354 if (fstat(fileno(f), &sb) == -1) {
355 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
356 goto done;
359 certs_buf = g_malloc(sb.st_size + 1);
360 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
361 show_oops(t, "Can't read CA file: %s", strerror(errno));
362 goto done;
364 certs_buf[sb.st_size] = '\0';
366 s = certs_buf;
367 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
368 certs++;
369 s += strlen("BEGIN CERTIFICATE");
372 bzero(&dt, sizeof dt);
373 dt.data = (unsigned char *)certs_buf;
374 dt.size = sb.st_size;
375 c = gnutls_malloc(sizeof(*c) * certs);
376 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
377 GNUTLS_X509_FMT_PEM, 0);
378 if (certs_read <= 0) {
379 show_oops(t, "No cert(s) available");
380 goto done;
382 show_certs(t, c, certs_read, "Certificate Authority Certificates");
383 done:
384 if (c)
385 gnutls_free(c);
386 if (certs_buf)
387 g_free(certs_buf);
388 if (f)
389 fclose(f);
391 return (rv);
395 cookie_show_wl(struct tab *t, struct karg *args)
397 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
398 wl_show(t, args, "Cookie White List", &c_wl);
400 return (0);
404 js_show_wl(struct tab *t, struct karg *args)
406 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
407 wl_show(t, args, "JavaScript White List", &js_wl);
409 return (0);
413 cookie_cmd(struct tab *t, struct karg *args)
415 if (args->i & XT_SHOW)
416 wl_show(t, args, "Cookie White List", &c_wl);
417 else if (args->i & XT_WL_TOGGLE) {
418 args->i |= XT_WL_RELOAD;
419 toggle_cwl(t, args);
420 } else if (args->i & XT_SAVE) {
421 args->i |= XT_WL_RELOAD;
422 wl_save(t, args, XT_WL_COOKIE);
423 } else if (args->i & XT_DELETE) {
424 remove_cookie_all();
425 update_cookie_tabs(NULL);
428 return (0);
432 js_cmd(struct tab *t, struct karg *args)
434 if (args->i & XT_SHOW)
435 wl_show(t, args, "JavaScript White List", &js_wl);
436 else if (args->i & XT_SAVE) {
437 args->i |= XT_WL_RELOAD;
438 wl_save(t, args, XT_WL_JAVASCRIPT);
439 } else if (args->i & XT_WL_TOGGLE) {
440 args->i |= XT_WL_RELOAD;
441 toggle_js(t, args);
442 } else if (args->i & XT_DELETE)
443 show_oops(t, "'js delete' currently unimplemented");
445 return (0);
449 pl_show_wl(struct tab *t, struct karg *args)
451 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
452 wl_show(t, args, "Plugin White List", &pl_wl);
454 return (0);
458 pl_cmd(struct tab *t, struct karg *args)
460 if (args->i & XT_SHOW)
461 wl_show(t, args, "Plugin White List", &pl_wl);
462 else if (args->i & XT_SAVE) {
463 args->i |= XT_WL_RELOAD;
464 wl_save(t, args, XT_WL_PLUGIN);
465 } else if (args->i & XT_WL_TOGGLE) {
466 args->i |= XT_WL_RELOAD;
467 toggle_pl(t, args);
468 } else if (args->i & XT_DELETE)
469 show_oops(t, "'plugin delete' currently unimplemented");
471 return (0);
475 https_show_wl(struct tab *t, struct karg *args)
477 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
478 wl_show(t, args, "HTTPS Force List", &force_https);
480 return (0);
484 https_cmd(struct tab *t, struct karg *args)
486 if (args->i & XT_SHOW)
487 wl_show(t, args, "HTTPS Force List", &force_https);
488 else if (args->i & XT_SAVE) {
489 args->i |= XT_WL_RELOAD;
490 wl_save(t, args, XT_WL_HTTPS);
491 } else if (args->i & XT_WL_TOGGLE) {
492 args->i |= XT_WL_RELOAD;
493 toggle_force_https(t, args);
494 } else if (args->i & XT_DELETE)
495 show_oops(t, "https delete' currently unimplemented");
497 return (0);
501 * cancel, remove, etc. downloads
503 void
504 xtp_handle_dl(struct tab *t, uint8_t cmd, int id, const char *query)
506 struct download find, *d = NULL;
507 #ifndef __MINGW32__
508 char *file = NULL;
509 const char *uri = NULL;
510 #endif
512 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
514 /* some commands require a valid download id */
515 if (cmd != XT_XTP_DL_LIST) {
516 /* lookup download in question */
517 find.id = id;
518 d = RB_FIND(download_list, &downloads, &find);
520 if (d == NULL) {
521 show_oops(t, "%s: no such download", __func__);
522 return;
526 /* decide what to do */
527 switch (cmd) {
528 case XT_XTP_DL_START:
529 /* our downloads always needs to be
530 * restarted if called from here
532 download_start(t, d, XT_DL_RESTART);
533 break;
534 case XT_XTP_DL_CANCEL:
535 webkit_download_cancel(d->download);
536 g_object_unref(d->download);
537 RB_REMOVE(download_list, &downloads, d);
538 break;
539 case XT_XTP_DL_UNLINK:
540 #ifdef __MINGW32__
541 /* XXX uri's aren't handled properly on windows? */
542 unlink(webkit_download_get_destination_uri(d->download));
543 #else
544 uri = webkit_download_get_destination_uri(d->download);
545 if ((file = g_filename_from_uri(uri, NULL, NULL)) != NULL) {
546 unlink(file);
547 g_free(file);
549 #endif
550 /* FALLTHROUGH */
551 case XT_XTP_DL_REMOVE:
552 webkit_download_cancel(d->download); /* just incase */
553 g_object_unref(d->download);
554 RB_REMOVE(download_list, &downloads, d);
555 break;
556 case XT_XTP_DL_LIST:
557 /* Nothing */
558 break;
559 default:
560 show_oops(t, "%s: unknown command", __func__);
561 break;
563 xtp_page_dl(t, NULL);
566 void
567 xtp_handle_hl(struct tab *t, uint8_t cmd, int id, const char *query)
569 struct history *h, *next, *ht;
570 int i = 1;
572 switch (cmd) {
573 case XT_XTP_HL_REMOVE:
574 /* walk backwards, as listed in reverse */
575 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
576 next = RB_PREV(history_list, &hl, h);
577 if (id == i) {
578 RB_REMOVE(history_list, &hl, h);
579 g_free((gpointer) h->title);
580 g_free((gpointer) h->uri);
581 g_free(h);
582 break;
584 i++;
586 break;
587 case XT_XTP_HL_REMOVE_ALL:
588 RB_FOREACH_SAFE(h, history_list, &hl, ht)
589 RB_REMOVE(history_list, &hl, h);
590 break;
591 case XT_XTP_HL_LIST:
592 /* Nothing - just xtp_page_hl() below */
593 break;
594 default:
595 show_oops(t, "%s: unknown command", __func__);
596 break;
599 xtp_page_hl(t, NULL);
602 /* remove a favorite */
603 void
604 remove_favorite(struct tab *t, int index)
606 char file[PATH_MAX], *title, *uri = NULL;
607 char *new_favs, *tmp;
608 FILE *f;
609 int i;
610 size_t len, lineno;
612 /* open favorites */
613 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
615 if ((f = fopen(file, "r")) == NULL) {
616 show_oops(t, "%s: can't open favorites: %s",
617 __func__, strerror(errno));
618 return;
621 /* build a string which will become the new favroites file */
622 new_favs = g_strdup("");
624 for (i = 1;;) {
625 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
626 if (feof(f) || ferror(f))
627 break;
628 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
629 if (len == 0) {
630 free(title);
631 title = NULL;
632 continue;
635 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
636 if (feof(f) || ferror(f)) {
637 show_oops(t, "%s: can't parse favorites %s",
638 __func__, strerror(errno));
639 goto clean;
643 /* as long as this isn't the one we are deleting add to file */
644 if (i != index) {
645 tmp = new_favs;
646 new_favs = g_strdup_printf("%s%s\n%s\n",
647 new_favs, title, uri);
648 g_free(tmp);
651 free(uri);
652 uri = NULL;
653 free(title);
654 title = NULL;
655 i++;
657 fclose(f);
659 /* write back new favorites file */
660 if ((f = fopen(file, "w")) == NULL) {
661 show_oops(t, "%s: can't open favorites: %s",
662 __func__, strerror(errno));
663 goto clean;
666 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
667 show_oops(t, "%s: can't fwrite", __func__);
668 fclose(f);
670 clean:
671 if (uri)
672 free(uri);
673 if (title)
674 free(title);
676 g_free(new_favs);
680 add_favorite(struct tab *t, struct karg *args)
682 char file[PATH_MAX];
683 FILE *f;
684 char *line = NULL;
685 size_t urilen, linelen;
686 const gchar *uri, *title;
688 if (t == NULL)
689 return (1);
691 /* don't allow adding of xtp pages to favorites */
692 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
693 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
694 return (1);
697 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
698 if ((f = fopen(file, "r+")) == NULL) {
699 show_oops(t, "Can't open favorites file: %s", strerror(errno));
700 return (1);
703 title = get_title(t, FALSE);
704 uri = get_uri(t);
706 if (title == NULL || uri == NULL) {
707 show_oops(t, "can't add page to favorites");
708 goto done;
711 urilen = strlen(uri);
713 for (;;) {
714 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
715 if (feof(f) || ferror(f))
716 break;
718 if (linelen == urilen && !strcmp(line, uri))
719 goto done;
721 free(line);
722 line = NULL;
725 fprintf(f, "\n%s\n%s", title, uri);
726 done:
727 if (line)
728 free(line);
729 fclose(f);
731 update_favorite_tabs(NULL);
733 return (0);
736 char *
737 search_engine_add(char *body, const char *name, const char *url,
738 const char *key, int select)
740 char *b = body;
742 body = g_strdup_printf("%s<tr>"
743 "<td>%s</td>"
744 "<td>%s</td>"
745 "<td style='text-align: center'>"
746 "<a href='%s%d/%s/%d/%d'>[ Select ]</a></td>"
747 "</tr>\n",
748 body,
749 name,
750 url,
751 XT_XTP_STR, XT_XTP_SL, key, XT_XTP_SL_SET, select);
752 g_free(b);
753 return (body);
756 void
757 xtp_handle_ab(struct tab *t, uint8_t cmd, int arg, const char *query)
759 char config[PATH_MAX];
760 char *cmdstr;
761 char **sv;
763 switch (cmd) {
764 case XT_XTP_AB_EDIT_CONF:
765 if (external_editor == NULL || strlen(external_editor) == 0) {
766 show_oops(t, "external_editor is unset");
767 break;
770 snprintf(config, sizeof config, "%s" PS ".%s", pwd->pw_dir,
771 XT_CONF_FILE);
772 sv = g_strsplit(external_editor, "<file>", -1);
773 cmdstr = g_strjoinv(config, sv);
774 g_strfreev(sv);
775 sv = g_strsplit_set(cmdstr, " \t", -1);
777 if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
778 NULL, NULL))
779 show_oops(t, "%s: could not spawn process", __func__);
781 g_strfreev(sv);
782 g_free(cmdstr);
783 break;
784 default:
785 show_oops(t, "%s, invalid about command", __func__);
786 break;
788 xtp_page_ab(t, NULL);
790 void
791 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg, const char *query)
793 struct karg args = {0};
795 switch (cmd) {
796 case XT_XTP_FL_LIST:
797 /* nothing, just the below call to xtp_page_fl() */
798 break;
799 case XT_XTP_FL_REMOVE:
800 remove_favorite(t, arg);
801 args.i = XT_DELETE;
802 break;
803 default:
804 show_oops(t, "%s: invalid favorites command", __func__);
805 break;
808 xtp_page_fl(t, &args);
811 void
812 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg, const char *query)
814 switch (cmd) {
815 case XT_XTP_CL_LIST:
816 /* nothing, just xtp_page_cl() */
817 break;
818 case XT_XTP_CL_REMOVE:
819 remove_cookie(arg);
820 break;
821 case XT_XTP_CL_REMOVE_DOMAIN:
822 remove_cookie_domain(arg);
823 break;
824 case XT_XTP_CL_REMOVE_ALL:
825 remove_cookie_all();
826 break;
827 default:
828 show_oops(t, "%s: unknown cookie xtp command", __func__);
829 break;
832 xtp_page_cl(t, NULL);
835 void
836 xtp_handle_sl(struct tab *t, uint8_t cmd, int arg, const char *query)
838 const char *search;
839 char *enc_search, *uri;
840 char **sv;
842 switch (cmd) {
843 case XT_XTP_SL_SET:
844 set_search_string((char *)search_list[arg].url);
845 if (save_runtime_setting("search_string", search_list[arg].url))
846 show_oops(t, "could not set search_string in runtime");
847 break;
848 default:
849 show_oops(t, "%s: unknown search xtp command", __func__);
850 break;
853 search = gtk_entry_get_text(GTK_ENTRY(t->search_entry)); /* static */
854 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
855 sv = g_strsplit(search_string, "%s", 2);
856 uri = g_strjoinv(enc_search, sv);
857 load_uri(t, uri);
858 g_free(enc_search);
859 g_strfreev(sv);
860 g_free(uri);
863 void
864 xtp_handle_sv(struct tab *t, uint8_t cmd, int id, const char *query)
866 SoupURI *soupuri = NULL;
867 struct karg args = {0};
868 struct secviolation find, *sv;
870 find.xtp_arg = id;
871 if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
872 return;
874 args.ptr = (void *)sv->t;
875 args.s = sv->uri;
877 switch (cmd) {
878 case XT_XTP_SV_SHOW_NEW_CERT:
879 args.i = XT_SHOW;
880 if (cert_cmd(t, &args)) {
881 xtp_page_sv(t, &args);
882 return;
884 break;
885 case XT_XTP_SV_SHOW_CACHED_CERT:
886 args.i = XT_CACHE | XT_SHOW;
887 if (cert_cmd(t, &args)) {
888 xtp_page_sv(t, &args);
889 return;
891 break;
892 case XT_XTP_SV_ALLOW_SESSION:
893 soupuri = soup_uri_new(sv->uri);
894 wl_add(soupuri->host, &svil, 0);
895 load_uri(t, sv->uri);
896 focus_webview(t);
897 break;
898 case XT_XTP_SV_CACHE:
899 args.i = XT_CACHE;
900 if (cert_cmd(t, &args)) {
901 xtp_page_sv(t, &args);
902 return;
904 load_uri(t, sv->uri);
905 focus_webview(t);
906 break;
907 default:
908 show_oops(t, "%s: invalid secviolation command", __func__);
909 break;
912 g_free(sv->uri);
913 if (soupuri)
914 soup_uri_free(soupuri);
915 RB_REMOVE(secviolation_list, &svl, sv);
918 void
919 xtp_handle_rt(struct tab *t, uint8_t cmd, int id, const char *query)
921 struct set_reject *sr;
922 GHashTable *new_settings = NULL;
923 int modify;
924 char *val, *curval, *s;
925 int i = 0;
927 switch (cmd) {
928 case XT_XTP_RT_SAVE:
929 if (query == NULL)
930 break;
931 new_settings = soup_form_decode(query);
932 for (i = 0; i < get_settings_size(); ++i) {
933 if (!rs[i].activate)
934 continue;
935 sr = g_malloc(sizeof *sr);
936 val = (char *)g_hash_table_lookup(new_settings,
937 rs[i].name);
938 modify = 0;
939 switch (rs[i].type) {
940 case XT_S_INT: /* FALLTHROUGH */
941 case XT_S_BOOL:
942 if (atoi(val) != *rs[i].ival)
943 modify = 1;
944 break;
945 case XT_S_FLOAT:
946 if (atof(val) != *rs[i].fval)
947 modify = 1;
948 break;
949 case XT_S_STR:
950 s = (rs[i].sval == NULL || *rs[i].sval == NULL)
951 ? "" : *rs[i].sval;
952 if (rs[i].sval && g_strcmp0(val, s))
953 modify = 1;
954 else if (rs[i].s && rs[i].s->get) {
955 curval = rs[i].s->get(NULL);
956 if (g_strcmp0(val, curval))
957 modify = 1;
958 g_free(curval);
960 break;
961 case XT_S_INVALID: /* FALLTHROUGH */
962 default:
963 break;
965 if (rs[i].activate(val)) {
966 sr->name = g_strdup(rs[i].name);
967 sr->value = g_strdup(val);
968 TAILQ_INSERT_TAIL(&srl, sr, entry);
969 continue;
971 if (modify)
972 if (save_runtime_setting(rs[i].name, val))
973 show_oops(t, "error");
975 break;
976 default:
977 show_oops(t, "%s: invalid set command", __func__);
978 break;
981 if (new_settings)
982 g_hash_table_destroy(new_settings);
984 xtp_page_rt(t, NULL);
987 /* link an XTP class to it's session key and handler function */
988 struct xtp_despatch {
989 uint8_t xtp_class;
990 void (*handle_func)(struct tab *, uint8_t, int,
991 const char *query);
994 struct xtp_despatch xtp_despatches[] = {
995 { XT_XTP_DL, xtp_handle_dl },
996 { XT_XTP_HL, xtp_handle_hl },
997 { XT_XTP_FL, xtp_handle_fl },
998 { XT_XTP_CL, xtp_handle_cl },
999 { XT_XTP_SL, xtp_handle_sl },
1000 { XT_XTP_AB, xtp_handle_ab },
1001 { XT_XTP_SV, xtp_handle_sv },
1002 { XT_XTP_RT, xtp_handle_rt },
1003 { XT_XTP_INVALID, NULL }
1007 * generate a session key to secure xtp commands.
1008 * pass in a ptr to the key in question and it will
1009 * be modified in place.
1011 void
1012 generate_xtp_session_key(char **key)
1014 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1016 /* free old key */
1017 if (*key)
1018 g_free(*key);
1020 /* make a new one */
1021 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1022 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1023 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1024 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1026 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1030 * validate a xtp session key.
1031 * return (1) if OK
1034 validate_xtp_session_key(struct tab *t, char *key)
1036 if (t == NULL || t->session_key == NULL || key == NULL)
1037 return (0);
1039 if (strcmp(t->session_key, key) != 0) {
1040 show_oops(t, "%s: xtp session key mismatch possible spoof",
1041 __func__);
1042 return (0);
1045 return (1);
1049 * is the url xtp protocol? (xxxt://)
1050 * if so, parse and despatch correct bahvior
1053 parse_xtp_url(struct tab *t, const char *uri_str)
1055 SoupURI *uri = NULL;
1056 struct xtp_despatch *dsp, *dsp_match = NULL;
1057 int ret = FALSE;
1058 int class = 0;
1059 char **sv = NULL;
1062 * uri->host = class
1063 * sv[0] = session key
1064 * sv[1] = command
1065 * sv[2] = optional argument
1068 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, uri_str);
1070 if ((uri = soup_uri_new(uri_str)) == NULL)
1071 goto clean;
1072 if (strncmp(uri->scheme, XT_XTP_SCHEME, strlen(XT_XTP_SCHEME)))
1073 goto clean;
1074 if (uri->host == NULL || strlen(uri->host) == 0)
1075 goto clean;
1076 else
1077 class = atoi(uri->host);
1078 if ((sv = g_strsplit(uri->path + 1, "/", 3)) == NULL)
1079 goto clean;
1081 if (sv[0] == NULL || sv[1] == NULL)
1082 goto clean;
1084 dsp = xtp_despatches;
1085 class = atoi(uri->host);
1086 while (dsp->xtp_class) {
1087 if (dsp->xtp_class == class) {
1088 dsp_match = dsp;
1089 break;
1091 dsp++;
1094 /* did we find one atall? */
1095 if (dsp_match == NULL) {
1096 show_oops(t, "%s: no matching xtp despatch found", __func__);
1097 goto clean;
1100 /* check session key and call despatch function */
1101 if (validate_xtp_session_key(t, sv[0])) {
1102 ret = TRUE; /* all is well, this was a valid xtp request */
1103 if (sv[2])
1104 dsp_match->handle_func(t, atoi(sv[1]), atoi(sv[2]),
1105 uri->query);
1106 else
1107 dsp_match->handle_func(t, atoi(sv[1]), 0, uri->query);
1110 clean:
1111 if (uri)
1112 soup_uri_free(uri);
1113 if (sv)
1114 g_strfreev(sv);
1116 return (ret);
1120 * update all favorite tabs apart from one. Pass NULL if
1121 * you want to update all.
1123 void
1124 update_favorite_tabs(struct tab *apart_from)
1126 struct tab *t;
1128 if (!updating_fl_tabs) {
1129 updating_fl_tabs = 1; /* stop infinite recursion */
1130 TAILQ_FOREACH(t, &tabs, entry)
1131 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1132 && (t != apart_from))
1133 xtp_page_fl(t, NULL);
1134 updating_fl_tabs = 0;
1139 * update all download tabs apart from one. Pass NULL if
1140 * you want to update all.
1142 void
1143 update_download_tabs(struct tab *apart_from)
1145 struct tab *t;
1147 if (!updating_dl_tabs) {
1148 updating_dl_tabs = 1; /* stop infinite recursion */
1149 TAILQ_FOREACH(t, &tabs, entry)
1150 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
1151 && (t != apart_from))
1152 xtp_page_dl(t, NULL);
1153 updating_dl_tabs = 0;
1158 * update all cookie tabs apart from one. Pass NULL if
1159 * you want to update all.
1161 void
1162 update_cookie_tabs(struct tab *apart_from)
1164 struct tab *t;
1166 if (!updating_cl_tabs) {
1167 updating_cl_tabs = 1; /* stop infinite recursion */
1168 TAILQ_FOREACH(t, &tabs, entry)
1169 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
1170 && (t != apart_from))
1171 xtp_page_cl(t, NULL);
1172 updating_cl_tabs = 0;
1177 * update all history tabs apart from one. Pass NULL if
1178 * you want to update all.
1180 void
1181 update_history_tabs(struct tab *apart_from)
1183 struct tab *t;
1185 if (!updating_hl_tabs) {
1186 updating_hl_tabs = 1; /* stop infinite recursion */
1187 TAILQ_FOREACH(t, &tabs, entry)
1188 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
1189 && (t != apart_from))
1190 xtp_page_hl(t, NULL);
1191 updating_hl_tabs = 0;
1196 * update all search tabs apart from one. Pass NULL if
1197 * you want to update all.
1199 void
1200 update_search_tabs(struct tab *apart_from)
1202 struct tab *t;
1204 if (!updating_sl_tabs) {
1205 updating_sl_tabs = 1; /* stop infinite recursion */
1206 TAILQ_FOREACH(t, &tabs, entry)
1207 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SL)
1208 && (t != apart_from))
1209 xtp_page_sl(t, NULL);
1210 updating_sl_tabs = 0;
1215 xtp_page_ab(struct tab *t, struct karg *args)
1217 char *page, *body;
1219 if (t == NULL)
1220 show_oops(NULL, "about invalid parameters");
1222 generate_xtp_session_key(&t->session_key);
1224 body = g_strdup_printf("<b>Version: %s</b>"
1225 #ifdef XOMBRERO_BUILDSTR
1226 "<br><b>Build: %s</b>"
1227 #endif
1228 "<br><b>WebKit: %d.%d.%d</b>"
1229 "<br><b>User Agent: %d.%d</b>"
1230 #ifdef WEBKITGTK_API_VERSION
1231 "<br><b>WebKit API: %.1f</b>"
1232 #endif
1233 "<br><b>Configuration: %s" PS "<a href='%s%d/%s/%d'>.%s</a>"
1234 " (remember to restart the browser after any changes)</b>"
1235 "<p>"
1236 "Authors:"
1237 "<ul>"
1238 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1239 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1240 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
1241 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
1242 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
1243 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
1244 "<li>Josh Rickmar &lt;jrick@devio.us&gt;</li>"
1245 "</ul>"
1246 "Copyrights and licenses can be found on the xombrero "
1247 "<a href=\"http://opensource.conformal.com/wiki/xombrero\">website</a>"
1248 "</p>",
1249 #ifdef XOMBRERO_BUILDSTR
1250 version, XOMBRERO_BUILDSTR,
1251 #else
1252 version,
1253 #endif
1254 WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION,
1255 WEBKIT_USER_AGENT_MAJOR_VERSION, WEBKIT_USER_AGENT_MINOR_VERSION
1256 #ifdef WEBKITGTK_API_VERSION
1257 ,WEBKITGTK_API_VERSION
1258 #endif
1259 ,pwd->pw_dir,
1260 XT_XTP_STR,
1261 XT_XTP_AB,
1262 t->session_key,
1263 XT_XTP_AB_EDIT_CONF,
1264 XT_CONF_FILE
1267 page = get_html_page("About", body, "", 0);
1268 g_free(body);
1270 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT, 0);
1272 g_free(page);
1274 return (0);
1277 /* show a list of favorites (bookmarks) */
1279 xtp_page_fl(struct tab *t, struct karg *args)
1281 char file[PATH_MAX];
1282 FILE *f;
1283 char *uri = NULL, *title = NULL;
1284 size_t len, lineno = 0;
1285 int i, failed = 0;
1286 char *body, *tmp, *page = NULL;
1287 const char delim[3] = {'\\', '\\', '\0'};
1289 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1291 if (t == NULL)
1292 warn("%s: bad param", __func__);
1294 generate_xtp_session_key(&t->session_key);
1296 /* open favorites */
1297 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
1298 if ((f = fopen(file, "r")) == NULL) {
1299 show_oops(t, "Can't open favorites file: %s", strerror(errno));
1300 return (1);
1303 /* body */
1304 if (args && args->i & XT_DELETE)
1305 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1306 "<th style='width: 40px'>&#35;</th><th>Link</th>"
1307 "<th style='width: 40px'>Rm</th></tr>\n");
1308 else
1309 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1310 "<th style='width: 40px'>&#35;</th><th>Link</th></tr>\n");
1312 for (i = 1;;) {
1313 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1314 break;
1315 if (strlen(title) == 0) {
1316 free(title);
1317 title = NULL;
1318 continue;
1321 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1322 if (feof(f) || ferror(f)) {
1323 show_oops(t, "favorites file corrupt");
1324 failed = 1;
1325 break;
1328 tmp = body;
1329 if (args && args->i & XT_DELETE)
1330 body = g_strdup_printf("%s<tr>"
1331 "<td>%d</td>"
1332 "<td><a href='%s'>%s</a></td>"
1333 "<td style='text-align: center'>"
1334 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1335 "</tr>\n",
1336 body, i, uri, title,
1337 XT_XTP_STR, XT_XTP_FL, t->session_key,
1338 XT_XTP_FL_REMOVE, i);
1339 else
1340 body = g_strdup_printf("%s<tr>"
1341 "<td>%d</td>"
1342 "<td><a href='%s'>%s</a></td>"
1343 "</tr>\n",
1344 body, i, uri, title);
1345 g_free(tmp);
1347 free(uri);
1348 uri = NULL;
1349 free(title);
1350 title = NULL;
1351 i++;
1353 fclose(f);
1355 /* if none, say so */
1356 if (i == 1) {
1357 tmp = body;
1358 body = g_strdup_printf("%s<tr>"
1359 "<td colspan='%d' style='text-align: center'>"
1360 "No favorites - To add one use the 'favadd' command."
1361 "</td></tr>", body, (args && args->i & XT_DELETE) ? 3 : 2);
1362 g_free(tmp);
1365 tmp = body;
1366 body = g_strdup_printf("%s</table>", body);
1367 g_free(tmp);
1369 if (uri)
1370 free(uri);
1371 if (title)
1372 free(title);
1374 /* render */
1375 if (!failed) {
1376 page = get_html_page("Favorites", body, "", 1);
1377 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES, 0);
1378 g_free(page);
1381 update_favorite_tabs(t);
1383 if (body)
1384 g_free(body);
1386 return (failed);
1390 * Return a new string with a download row (in html)
1391 * appended. Old string is freed.
1393 char *
1394 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1397 WebKitDownloadStatus stat;
1398 const gchar *destination;
1399 char *status_html = NULL, *cmd_html = NULL, *new_html;
1400 gdouble progress;
1401 char cur_sz[FMT_SCALED_STRSIZE];
1402 char tot_sz[FMT_SCALED_STRSIZE];
1403 char *xtp_prefix;
1405 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1407 /* All actions wil take this form:
1408 * xxxt://class/seskey
1410 xtp_prefix = g_strdup_printf("%s%d/%s/",
1411 XT_XTP_STR, XT_XTP_DL, t->session_key);
1413 stat = webkit_download_get_status(dl->download);
1415 switch (stat) {
1416 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1417 status_html = g_strdup_printf("Finished");
1418 cmd_html = g_strdup_printf(
1419 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1420 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1421 XT_XTP_DL_UNLINK, dl->id);
1422 break;
1423 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1424 /* gather size info */
1425 progress = 100 * webkit_download_get_progress(dl->download);
1427 fmt_scaled(
1428 webkit_download_get_current_size(dl->download), cur_sz);
1429 fmt_scaled(
1430 webkit_download_get_total_size(dl->download), tot_sz);
1432 status_html = g_strdup_printf(
1433 "<div style='width: 100%%' align='center'>"
1434 "<div class='progress-outer'>"
1435 "<div class='progress-inner' style='width: %.2f%%'>"
1436 "</div></div></div>"
1437 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1438 progress, cur_sz, tot_sz, progress);
1440 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1441 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1443 break;
1444 /* LLL */
1445 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1446 status_html = g_strdup_printf("Cancelled");
1447 cmd_html = g_strdup_printf(
1448 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1449 xtp_prefix, XT_XTP_DL_START, dl->id,
1450 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1451 XT_XTP_DL_UNLINK, dl->id);
1452 break;
1453 case WEBKIT_DOWNLOAD_STATUS_ERROR:
1454 status_html = g_strdup_printf("Error!");
1455 cmd_html = g_strdup_printf(
1456 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1457 xtp_prefix, XT_XTP_DL_START, dl->id,
1458 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1459 XT_XTP_DL_UNLINK, dl->id);
1460 break;
1461 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1462 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1463 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1464 XT_XTP_DL_CANCEL, dl->id);
1465 status_html = g_strdup_printf("Created");
1466 break;
1467 default:
1468 show_oops(t, "%s: unknown download status", __func__);
1471 destination = webkit_download_get_destination_uri(dl->download);
1472 /* we might not have a destination set yet */
1473 if (!destination)
1474 destination = webkit_download_get_suggested_filename(dl->download);
1475 new_html = g_strdup_printf(
1476 "%s\n<tr><td>%s</td><td>%s</td>"
1477 "<td style='text-align:center'>%s</td></tr>\n",
1478 html, basename((char *)destination),
1479 status_html, cmd_html);
1480 g_free(html);
1482 if (status_html)
1483 g_free(status_html);
1485 if (cmd_html)
1486 g_free(cmd_html);
1488 g_free(xtp_prefix);
1490 return new_html;
1493 /* cookie management XTP page */
1495 xtp_page_cl(struct tab *t, struct karg *args)
1497 char *body, *page, *tmp;
1498 int i = 1; /* all ids start 1 */
1499 int domain_id = 0;
1500 GSList *sc, *pc, *pc_start;
1501 SoupCookie *c;
1502 char *type, *table_headers, *last_domain;
1504 DNPRINTF(XT_D_CMD, "%s", __func__);
1506 if (t == NULL) {
1507 show_oops(NULL, "%s invalid parameters", __func__);
1508 return (1);
1511 generate_xtp_session_key(&t->session_key);
1513 /* table headers */
1514 table_headers = g_strdup_printf("<table><tr>"
1515 "<th>Type</th>"
1516 "<th>Name</th>"
1517 "<th style='width:200px'>Value</th>"
1518 "<th>Path</th>"
1519 "<th>Expires</th>"
1520 "<th>Secure</th>"
1521 "<th>HTTP<br />only</th>"
1522 "<th style='width:40px'>Rm</th></tr>\n");
1524 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1525 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1526 pc_start = pc;
1528 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1529 "[ Remove All Cookies From All Domains ]</a></div>\n",
1530 XT_XTP_STR, XT_XTP_CL, t->session_key, XT_XTP_CL_REMOVE_ALL);
1532 last_domain = g_strdup("");
1533 for (; sc; sc = sc->next) {
1534 c = sc->data;
1536 if (strcmp(last_domain, c->domain) != 0) {
1537 /* new domain */
1538 domain_id ++;
1539 g_free(last_domain);
1540 last_domain = g_strdup(c->domain);
1542 if (body != NULL) {
1543 tmp = body;
1544 body = g_strdup_printf("%s</table>"
1545 "<h2>%s</h2><div align=\"center\">"
1546 "<a href='%s%d/%s/%d/%d'>"
1547 "[ Remove All From This Domain ]"
1548 "</a></div>%s\n",
1549 body, c->domain,
1550 XT_XTP_STR, XT_XTP_CL, t->session_key,
1551 XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1552 table_headers);
1553 g_free(tmp);
1554 } else {
1555 /* first domain */
1556 body = g_strdup_printf("<h2>%s</h2>"
1557 "<div align=\"center\">"
1558 "<a href='%s%d/%s/%d/%d'>"
1559 "[ Remove All From This Domain ]</a></div>%s\n",
1560 c->domain, XT_XTP_STR, XT_XTP_CL,
1561 t->session_key, XT_XTP_CL_REMOVE_DOMAIN,
1562 domain_id, table_headers);
1566 type = "Session";
1567 for (pc = pc_start; pc; pc = pc->next)
1568 if (soup_cookie_equal(pc->data, c)) {
1569 type = "Session + Persistent";
1570 break;
1573 tmp = body;
1574 body = g_strdup_printf(
1575 "%s\n<tr>"
1576 "<td>%s</td>"
1577 "<td style='word-wrap:normal'>%s</td>"
1578 "<td>"
1579 " <textarea rows='4'>%s</textarea>"
1580 "</td>"
1581 "<td>%s</td>"
1582 "<td>%s</td>"
1583 "<td>%d</td>"
1584 "<td>%d</td>"
1585 "<td style='text-align:center'>"
1586 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1587 body,
1588 type,
1589 c->name,
1590 c->value,
1591 c->path,
1592 c->expires ?
1593 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1594 c->secure,
1595 c->http_only,
1597 XT_XTP_STR,
1598 XT_XTP_CL,
1599 t->session_key,
1600 XT_XTP_CL_REMOVE,
1604 g_free(tmp);
1605 i++;
1608 soup_cookies_free(sc);
1609 soup_cookies_free(pc);
1611 /* small message if there are none */
1612 if (i == 1) {
1613 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1614 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1616 tmp = body;
1617 body = g_strdup_printf("%s</table>", body);
1618 g_free(tmp);
1620 page = get_html_page("Cookie Jar", body, "", TRUE);
1621 g_free(body);
1622 g_free(table_headers);
1623 g_free(last_domain);
1625 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR, 0);
1626 update_cookie_tabs(t);
1628 g_free(page);
1630 return (0);
1634 xtp_page_hl(struct tab *t, struct karg *args)
1636 char *body, *page, *tmp;
1637 struct history *h;
1638 int i = 1; /* all ids start 1 */
1640 DNPRINTF(XT_D_CMD, "%s", __func__);
1642 if (t == NULL) {
1643 show_oops(NULL, "%s invalid parameters", __func__);
1644 return (1);
1647 generate_xtp_session_key(&t->session_key);
1649 /* body */
1650 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1651 "[ Remove All ]</a></div>"
1652 "<table style='table-layout:fixed'><tr>"
1653 "<th>URI</th><th>Title</th><th>Last visited</th>"
1654 "<th style='width: 40px'>Rm</th></tr>\n",
1655 XT_XTP_STR, XT_XTP_HL, t->session_key, XT_XTP_HL_REMOVE_ALL);
1657 RB_FOREACH_REVERSE(h, history_list, &hl) {
1658 tmp = body;
1659 body = g_strdup_printf(
1660 "%s\n<tr>"
1661 "<td><a href='%s'>%s</a></td>"
1662 "<td>%s</td>"
1663 "<td>%s</td>"
1664 "<td style='text-align: center'>"
1665 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1666 body, h->uri, h->uri, h->title, ctime(&h->time),
1667 XT_XTP_STR, XT_XTP_HL, t->session_key,
1668 XT_XTP_HL_REMOVE, i);
1670 g_free(tmp);
1671 i++;
1674 /* small message if there are none */
1675 if (i == 1) {
1676 tmp = body;
1677 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1678 "colspan='4'>No History</td></tr>\n", body);
1679 g_free(tmp);
1682 tmp = body;
1683 body = g_strdup_printf("%s</table>", body);
1684 g_free(tmp);
1686 page = get_html_page("History", body, "", TRUE);
1687 g_free(body);
1690 * update all history manager tabs as the xtp session
1691 * key has now changed. No need to update the current tab.
1692 * Already did that above.
1694 update_history_tabs(t);
1696 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY, 0);
1697 g_free(page);
1699 return (0);
1703 * Generate a web page detailing the status of any downloads
1706 xtp_page_dl(struct tab *t, struct karg *args)
1708 struct download *dl;
1709 char *body, *page, *tmp;
1710 char *ref;
1711 int n_dl = 1;
1713 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1715 if (t == NULL) {
1716 show_oops(NULL, "%s invalid parameters", __func__);
1717 return (1);
1720 generate_xtp_session_key(&t->session_key);
1722 /* header - with refresh so as to update */
1723 if (refresh_interval >= 1)
1724 ref = g_strdup_printf(
1725 "<meta http-equiv='refresh' content='%u"
1726 ";url=%s%d/%s/%d' />\n",
1727 refresh_interval,
1728 XT_XTP_STR,
1729 XT_XTP_DL,
1730 t->session_key,
1731 XT_XTP_DL_LIST);
1732 else
1733 ref = g_strdup("");
1735 body = g_strdup_printf("<div align='center'>"
1736 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1737 "</p><table><tr><th style='width: 60%%'>"
1738 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1739 XT_XTP_STR, XT_XTP_DL, t->session_key, XT_XTP_DL_LIST);
1741 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1742 body = xtp_page_dl_row(t, body, dl);
1743 n_dl++;
1746 /* message if no downloads in list */
1747 if (n_dl == 1) {
1748 tmp = body;
1749 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1750 " style='text-align: center'>"
1751 "No downloads</td></tr>\n", body);
1752 g_free(tmp);
1755 tmp = body;
1756 body = g_strdup_printf("%s</table></div>", body);
1757 g_free(tmp);
1759 page = get_html_page("Downloads", body, ref, 1);
1760 g_free(ref);
1761 g_free(body);
1764 * update all download manager tabs as the xtp session
1765 * key has now changed. No need to update the current tab.
1766 * Already did that above.
1768 update_download_tabs(t);
1770 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS, 0);
1771 g_free(page);
1773 return (0);
1777 xtp_page_sl(struct tab *t, struct karg *args)
1779 int i;
1780 char *page, *body, *tmp;
1782 DNPRINTF(XT_D_SEARCH, "%s", __func__);
1784 generate_xtp_session_key(&t->session_key);
1786 if (t == NULL) {
1787 show_oops(NULL, "%s invalid parameters", __func__);
1788 return (1);
1791 body = g_strdup_printf("<p>The xombrero authors will not choose a "
1792 "default search engine for you. What follows is a list of search "
1793 "engines (in no particular order) you may be interested in. "
1794 "To permanently choose a search engine, click [ Select ] to save "
1795 "<tt>search_string</tt> as a runtime setting, or set "
1796 "<tt>search_string</tt> to the appropriate URL in your xombrero "
1797 "configuration.</p>");
1799 tmp = body;
1800 body = g_strdup_printf("%s\n<table style='table-layout:fixed'><tr>"
1801 "<th style='width: 200px'>Name</th><th>URL</th>"
1802 "<th style='width: 100px'>Select</th></tr>\n", body);
1803 g_free(tmp);
1805 for (i = 0; i < (sizeof search_list / sizeof (struct search_type)); ++i)
1806 body = search_engine_add(body, search_list[i].name,
1807 search_list[i].url, t->session_key, i);
1809 tmp = body;
1810 body = g_strdup_printf("%s</table>", body);
1811 g_free(tmp);
1813 page = get_html_page("Choose a search engine", body, "", 1);
1814 g_free(body);
1817 * update all search tabs as the xtp session key has now changed. No
1818 * need to update the current tab. Already did that above.
1820 update_search_tabs(t);
1822 load_webkit_string(t, page, XT_URI_ABOUT_SEARCH, 0);
1823 g_free(page);
1825 return (0);
1829 xtp_page_sv(struct tab *t, struct karg *args)
1831 SoupURI *soupuri;
1832 static int arg = 0;
1833 struct secviolation find, *sv;
1834 char *page, *body;
1836 if (t == NULL)
1837 show_oops(NULL, "secviolation invalid parameters");
1839 generate_xtp_session_key(&t->session_key);
1841 if (args == NULL) {
1842 find.xtp_arg = t->xtp_arg;
1843 sv = RB_FIND(secviolation_list, &svl, &find);
1844 if (sv == NULL)
1845 return (-1);
1846 } else {
1847 sv = g_malloc(sizeof(struct secviolation));
1848 sv->xtp_arg = ++arg;
1849 t->xtp_arg = arg;
1850 sv->t = t;
1851 sv->uri = args->s;
1852 RB_INSERT(secviolation_list, &svl, sv);
1855 if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
1856 return (-1);
1858 body = g_strdup_printf(
1859 "<p><b>You tried to access %s</b>."
1860 "<p><b>The site's security certificate has been modified.</b>"
1861 "<p>The domain of the page you have tried to access, <b>%s</b>, "
1862 "has a different remote certificate then the local cached version "
1863 "from a previous visit. As a security precaution to help prevent "
1864 "against man-in-the-middle attacks, please choose one of the "
1865 "following actions to continue, or disable the "
1866 "<tt>warn_cert_changes</tt> setting in your xombrero "
1867 "configuration."
1868 "<p><b>Choose an action:"
1869 "<br><a href='%s%d/%s/%d/%d'>Allow for this session</a>"
1870 "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>"
1871 "<br><a href='%s%d/%s/%d/%d'>Show cached certificate</a>"
1872 "<br><a href='%s%d/%s/%d/%d'>Show new certificate</a>",
1873 sv->uri,
1874 soupuri->host,
1875 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_ALLOW_SESSION,
1876 sv->xtp_arg,
1877 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_CACHE,
1878 sv->xtp_arg,
1879 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_CACHED_CERT,
1880 sv->xtp_arg,
1881 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_NEW_CERT,
1882 sv->xtp_arg);
1884 page = get_html_page("Security Violation", body, "", 0);
1885 g_free(body);
1887 load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION, 1);
1889 g_free(page);
1890 if (soupuri)
1891 soup_uri_free(soupuri);
1893 return (0);
1897 startpage(struct tab *t, struct karg *args)
1899 char *page, *body, *b;
1900 struct sp *s;
1902 if (t == NULL)
1903 show_oops(NULL, "startpage invalid parameters");
1905 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1907 TAILQ_FOREACH(s, &spl, entry) {
1908 b = body;
1909 body = g_strdup_printf("%s%s<br>", body, s->line);
1910 g_free(b);
1913 page = get_html_page("Startup Exception", body, "", 0);
1914 g_free(body);
1916 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE, 0);
1917 g_free(page);
1919 return (0);
1922 void
1923 startpage_add(const char *fmt, ...)
1925 va_list ap;
1926 char *msg;
1927 struct sp *s;
1929 if (fmt == NULL)
1930 return;
1932 va_start(ap, fmt);
1933 if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
1934 errx(1, "startpage_add failed");
1935 va_end(ap);
1937 s = g_malloc0(sizeof *s);
1938 s->line = msg;
1940 TAILQ_INSERT_TAIL(&spl, s, entry);
1942 gchar *show_g_object_settings(GObject *, char *, int);
1944 char *
1945 xt_g_object_serialize(GValue *value, const gchar *tname, char *str, int recurse)
1947 int typeno = 0;
1948 char *valstr, *tmpstr, *tmpsettings;
1949 GObject *object;
1951 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(value) );
1952 switch ( typeno ) {
1953 case G_TYPE_ENUM:
1954 valstr = g_strdup_printf("%d",
1955 g_value_get_enum(value));
1956 break;
1957 case G_TYPE_CHAR:
1958 valstr = g_strdup_printf("%c",
1959 #if GLIB_CHECK_VERSION(2, 32, 0)
1960 g_value_get_schar(value));
1961 #else
1962 g_value_get_char(value));
1963 #endif
1964 break;
1965 case G_TYPE_UCHAR:
1966 valstr = g_strdup_printf("%c",
1967 g_value_get_uchar(value));
1968 break;
1969 case G_TYPE_LONG:
1970 valstr = g_strdup_printf("%ld",
1971 g_value_get_long(value));
1972 break;
1973 case G_TYPE_ULONG:
1974 valstr = g_strdup_printf("%ld",
1975 g_value_get_ulong(value));
1976 break;
1977 case G_TYPE_INT:
1978 valstr = g_strdup_printf("%d",
1979 g_value_get_int(value));
1980 break;
1981 case G_TYPE_INT64:
1982 valstr = g_strdup_printf("%" PRIo64,
1983 (int64_t) g_value_get_int64(value));
1984 break;
1985 case G_TYPE_UINT:
1986 valstr = g_strdup_printf("%d",
1987 g_value_get_uint(value));
1988 break;
1989 case G_TYPE_UINT64:
1990 valstr = g_strdup_printf("%" PRIu64,
1991 (uint64_t) g_value_get_uint64(value));
1992 break;
1993 case G_TYPE_FLAGS:
1994 valstr = g_strdup_printf("0x%x",
1995 g_value_get_flags(value));
1996 break;
1997 case G_TYPE_BOOLEAN:
1998 valstr = g_strdup_printf("%s",
1999 g_value_get_boolean(value) ? "TRUE" : "FALSE");
2000 break;
2001 case G_TYPE_FLOAT:
2002 valstr = g_strdup_printf("%f",
2003 g_value_get_float(value));
2004 break;
2005 case G_TYPE_DOUBLE:
2006 valstr = g_strdup_printf("%f",
2007 g_value_get_double(value));
2008 break;
2009 case G_TYPE_STRING:
2010 valstr = g_strdup_printf("\"%s\"",
2011 g_value_get_string(value));
2012 break;
2013 case G_TYPE_POINTER:
2014 valstr = g_strdup_printf("%p",
2015 g_value_get_pointer(value));
2016 break;
2017 case G_TYPE_OBJECT:
2018 object = g_value_get_object(value);
2019 if (object != NULL) {
2020 if (recurse) {
2021 tmpstr = g_strdup_printf("%s ", str);
2022 tmpsettings = show_g_object_settings( object,
2023 tmpstr, recurse);
2024 g_free(tmpstr);
2026 if (strrchr(tmpsettings, '\n') != NULL) {
2027 valstr = g_strdup_printf("%s%s }",
2028 tmpsettings, str);
2029 g_free(tmpsettings);
2030 } else {
2031 valstr = tmpsettings;
2033 } else {
2034 valstr = g_strdup_printf("<...>");
2036 } else {
2037 valstr = g_strdup_printf("settings[] = NULL");
2039 break;
2040 default:
2041 valstr = g_strdup_printf("type %s unhandled", tname);
2043 return valstr;
2046 gchar *
2047 show_g_object_settings(GObject *o, char *str, int recurse)
2049 char *b, *p, *body, *valstr, *tmpstr;
2050 guint n_props = 0;
2051 int i, typeno = 0;
2052 GParamSpec *pspec;
2053 const gchar *tname;
2054 GValue value;
2055 GParamSpec **proplist;
2056 const gchar *name;
2058 if (!G_IS_OBJECT(o)) {
2059 fprintf(stderr, "%s is not a g_object\n", str);
2060 return g_strdup("");
2062 proplist = g_object_class_list_properties(
2063 G_OBJECT_GET_CLASS(o), &n_props);
2065 if (GTK_IS_WIDGET(o)) {
2066 name = gtk_widget_get_name(GTK_WIDGET(o));
2067 } else {
2068 name = "settings";
2070 if (n_props == 0) {
2071 body = g_strdup_printf("%s[0] = { }", name);
2072 goto end_show_g_objects;
2075 body = g_strdup_printf("%s[%d] = {\n", name, n_props);
2076 for (i=0; i < n_props; i++) {
2077 pspec = proplist[i];
2078 tname = G_OBJECT_TYPE_NAME(pspec);
2079 bzero(&value, sizeof value);
2080 valstr = NULL;
2082 if (!(pspec->flags & G_PARAM_READABLE))
2083 valstr = g_strdup_printf("not a readable property");
2084 else {
2085 g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
2086 g_object_get_property(G_OBJECT(o), pspec->name,
2087 &value);
2088 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(&value) );
2091 /* based on the type, recurse and display values */
2092 if (valstr == NULL) {
2093 valstr = xt_g_object_serialize(&value, tname, str,
2094 recurse);
2097 tmpstr = g_strdup_printf("%-13s %s%s%s,", tname, pspec->name,
2098 (typeno == G_TYPE_OBJECT) ? "." : " = ", valstr);
2099 b = body;
2101 #define XT_G_OBJECT_SPACING 50
2102 p = strrchr(tmpstr, '\n');
2103 if (p == NULL && strlen(tmpstr) > XT_G_OBJECT_SPACING) {
2104 body = g_strdup_printf(
2105 "%s%s %-50s\n%s %50s /* %3d flags=0x%08x */\n",
2106 body, str, tmpstr, str, "", i, pspec->flags);
2107 } else {
2108 char *fmt;
2109 int strspaces;
2110 if (p == NULL)
2111 strspaces = XT_G_OBJECT_SPACING;
2112 else
2113 strspaces = strlen(tmpstr) - (strlen(p) - strlen(str)) + XT_G_OBJECT_SPACING + 5;
2114 fmt = g_strdup_printf("%%s%%s %%-%ds /* %%3d flags=0x%%08x */\n", strspaces);
2115 body = g_strdup_printf(fmt, body, str, tmpstr, i, pspec->flags);
2116 g_free(fmt);
2118 g_free(tmpstr);
2119 g_free(b);
2120 g_free(valstr);
2122 end_show_g_objects:
2123 g_free(proplist);
2124 return (body);
2127 char *
2128 xt_append_settings(char *str, GObject *object, char *name, int recurse)
2130 char *newstr, *settings;
2132 settings = show_g_object_settings(object, name, recurse);
2133 if (str == NULL)
2134 str = g_strdup("");
2136 newstr = g_strdup_printf("%s%s %s%s };\n", str, name, settings, name);
2137 g_free(str);
2139 return newstr;
2143 about_webkit(struct tab *t, struct karg *arg)
2145 char *page, *body, *settingstr;
2147 settingstr = xt_append_settings(NULL, G_OBJECT(t->settings),
2148 "t->settings", 0);
2149 body = g_strdup_printf("<pre>%s</pre>\n", settingstr);
2150 g_free(settingstr);
2152 page = get_html_page("About Webkit", body, "", 0);
2153 g_free(body);
2155 load_webkit_string(t, page, XT_URI_ABOUT_WEBKIT, 0);
2156 g_free(page);
2158 return (0);
2161 static int toplevelcount = 0;
2163 void
2164 xt_append_toplevel(GtkWindow *w, char **body)
2166 char *n;
2168 n = g_strdup_printf("toplevel#%d", toplevelcount++);
2169 *body = xt_append_settings(*body, G_OBJECT(w), n, 0);
2170 g_free(n);
2174 allthethings(struct tab *t, struct karg *arg)
2176 GList *list;
2177 char *page, *body, *b;
2179 body = xt_append_settings(NULL, G_OBJECT(t->wv), "t->wv", 1);
2180 body = xt_append_settings(body, G_OBJECT(t->inspector),
2181 "t->inspector", 1);
2182 #if 0 /* not until warnings are gone */
2183 body = xt_append_settings(body, G_OBJECT(session),
2184 "session", 1);
2185 #endif
2186 toplevelcount = 0;
2187 list = gtk_window_list_toplevels();
2188 g_list_foreach(list, (GFunc)g_object_ref, NULL);
2189 g_list_foreach(list, (GFunc)xt_append_toplevel, &body);
2190 g_list_foreach(list, (GFunc)g_object_unref, NULL);
2191 g_list_free(list);
2193 b = body;
2194 body = g_strdup_printf("<pre>%scan paste clipboard = %d\n</pre>", body,
2195 webkit_web_view_can_paste_clipboard(t->wv));
2196 g_free(b);
2198 page = get_html_page("About All The Things _o/", body, "", 0);
2199 g_free(body);
2201 load_webkit_string(t, page, XT_URI_ABOUT_ALLTHETHINGS, 0);
2202 g_free(page);
2204 return (0);