fix build on NetBSD. From Arnaud Degroote
[xombrero.git] / about.c
blobfd4c1c05c754fa3e7ac06659b0db2c874328c69f
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" },
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=https://opensource.conformal.com/cgi-bin/man-cgi?xombrero\">"
247 "</head>\n";
248 body = "xombrero man page <a href=\"https://opensource.conformal.com/"
249 "cgi-bin/man-cgi?xombrero\">https://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 favorites 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 gchar *argtitle = NULL;
687 const gchar *uri, *title;
689 if (t == NULL)
690 return (1);
692 /* don't allow adding of xtp pages to favorites */
693 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
694 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
695 return (1);
698 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
699 if ((f = fopen(file, "r+")) == NULL) {
700 show_oops(t, "Can't open favorites file: %s", strerror(errno));
701 return (1);
704 if (args->s && strlen(g_strstrip(args->s)))
705 argtitle = html_escape(g_strstrip(args->s));
707 title = argtitle ? argtitle : get_title(t, FALSE);
708 uri = get_uri(t);
710 if (title == NULL || uri == NULL) {
711 show_oops(t, "can't add page to favorites");
712 goto done;
715 urilen = strlen(uri);
717 for (;;) {
718 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
719 if (feof(f) || ferror(f))
720 break;
722 if (linelen == urilen && !strcmp(line, uri))
723 goto done;
725 free(line);
726 line = NULL;
729 fprintf(f, "\n%s\n%s", title, uri);
730 done:
731 if (argtitle)
732 g_free(argtitle);
733 if (line)
734 free(line);
735 fclose(f);
737 update_favorite_tabs(NULL);
739 return (0);
742 char *
743 search_engine_add(char *body, const char *name, const char *url,
744 const char *key, int select)
746 char *b = body;
748 body = g_strdup_printf("%s<tr>"
749 "<td>%s</td>"
750 "<td>%s</td>"
751 "<td style='text-align: center'>"
752 "<a href='%s%d/%s/%d/%d'>[ Select ]</a></td>"
753 "</tr>\n",
754 body,
755 name,
756 url,
757 XT_XTP_STR, XT_XTP_SL, key, XT_XTP_SL_SET, select);
758 g_free(b);
759 return (body);
762 void
763 xtp_handle_ab(struct tab *t, uint8_t cmd, int arg, const char *query)
765 char config[PATH_MAX];
766 char *cmdstr;
767 char **sv;
769 switch (cmd) {
770 case XT_XTP_AB_EDIT_CONF:
771 if (external_editor == NULL || strlen(external_editor) == 0) {
772 show_oops(t, "external_editor is unset");
773 break;
776 snprintf(config, sizeof config, "%s" PS ".%s", pwd->pw_dir,
777 XT_CONF_FILE);
778 sv = g_strsplit(external_editor, "<file>", -1);
779 cmdstr = g_strjoinv(config, sv);
780 g_strfreev(sv);
781 sv = g_strsplit_set(cmdstr, " \t", -1);
783 if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
784 NULL, NULL))
785 show_oops(t, "%s: could not spawn process", __func__);
787 g_strfreev(sv);
788 g_free(cmdstr);
789 break;
790 default:
791 show_oops(t, "%s, invalid about command", __func__);
792 break;
794 xtp_page_ab(t, NULL);
796 void
797 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg, const char *query)
799 struct karg args = {0};
801 switch (cmd) {
802 case XT_XTP_FL_LIST:
803 /* nothing, just the below call to xtp_page_fl() */
804 break;
805 case XT_XTP_FL_REMOVE:
806 remove_favorite(t, arg);
807 args.i = XT_DELETE;
808 break;
809 default:
810 show_oops(t, "%s: invalid favorites command", __func__);
811 break;
814 xtp_page_fl(t, &args);
817 void
818 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg, const char *query)
820 switch (cmd) {
821 case XT_XTP_CL_LIST:
822 /* nothing, just xtp_page_cl() */
823 break;
824 case XT_XTP_CL_REMOVE:
825 remove_cookie(arg);
826 break;
827 case XT_XTP_CL_REMOVE_DOMAIN:
828 remove_cookie_domain(arg);
829 break;
830 case XT_XTP_CL_REMOVE_ALL:
831 remove_cookie_all();
832 break;
833 default:
834 show_oops(t, "%s: unknown cookie xtp command", __func__);
835 break;
838 xtp_page_cl(t, NULL);
841 void
842 xtp_handle_sl(struct tab *t, uint8_t cmd, int arg, const char *query)
844 const char *search;
845 char *enc_search, *uri;
846 char **sv;
848 switch (cmd) {
849 case XT_XTP_SL_SET:
850 set_search_string((char *)search_list[arg].url);
851 if (save_runtime_setting("search_string", search_list[arg].url))
852 show_oops(t, "could not set search_string in runtime");
853 break;
854 default:
855 show_oops(t, "%s: unknown search xtp command", __func__);
856 break;
859 search = gtk_entry_get_text(GTK_ENTRY(t->search_entry)); /* static */
860 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
861 sv = g_strsplit(search_string, "%s", 2);
862 uri = g_strjoinv(enc_search, sv);
863 load_uri(t, uri);
864 g_free(enc_search);
865 g_strfreev(sv);
866 g_free(uri);
869 void
870 xtp_handle_sv(struct tab *t, uint8_t cmd, int id, const char *query)
872 SoupURI *soupuri = NULL;
873 struct karg args = {0};
874 struct secviolation find, *sv;
876 find.xtp_arg = id;
877 if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
878 return;
880 args.ptr = (void *)sv->t;
881 args.s = sv->uri;
883 switch (cmd) {
884 case XT_XTP_SV_SHOW_NEW_CERT:
885 args.i = XT_SHOW;
886 if (cert_cmd(t, &args)) {
887 xtp_page_sv(t, &args);
888 return;
890 break;
891 case XT_XTP_SV_SHOW_CACHED_CERT:
892 args.i = XT_CACHE | XT_SHOW;
893 if (cert_cmd(t, &args)) {
894 xtp_page_sv(t, &args);
895 return;
897 break;
898 case XT_XTP_SV_ALLOW_SESSION:
899 soupuri = soup_uri_new(sv->uri);
900 wl_add(soupuri->host, &svil, 0);
901 load_uri(t, sv->uri);
902 focus_webview(t);
903 break;
904 case XT_XTP_SV_CACHE:
905 args.i = XT_CACHE;
906 if (cert_cmd(t, &args)) {
907 xtp_page_sv(t, &args);
908 return;
910 load_uri(t, sv->uri);
911 focus_webview(t);
912 break;
913 default:
914 show_oops(t, "%s: invalid secviolation command", __func__);
915 break;
918 g_free(sv->uri);
919 if (soupuri)
920 soup_uri_free(soupuri);
921 RB_REMOVE(secviolation_list, &svl, sv);
924 void
925 xtp_handle_rt(struct tab *t, uint8_t cmd, int id, const char *query)
927 struct set_reject *sr;
928 GHashTable *new_settings = NULL;
929 int modify;
930 char *val, *curval, *s;
931 int i = 0;
933 switch (cmd) {
934 case XT_XTP_RT_SAVE:
935 if (query == NULL)
936 break;
937 new_settings = soup_form_decode(query);
938 for (i = 0; i < get_settings_size(); ++i) {
939 if (!rs[i].activate)
940 continue;
941 val = (char *)g_hash_table_lookup(new_settings,
942 rs[i].name);
943 modify = 0;
944 switch (rs[i].type) {
945 case XT_S_INT: /* FALLTHROUGH */
946 case XT_S_BOOL:
947 if (atoi(val) != *rs[i].ival)
948 modify = 1;
949 break;
950 case XT_S_FLOAT:
951 if (atof(val) != *rs[i].fval)
952 modify = 1;
953 break;
954 case XT_S_STR:
955 s = (rs[i].sval == NULL || *rs[i].sval == NULL)
956 ? "" : *rs[i].sval;
957 if (rs[i].sval && g_strcmp0(val, s))
958 modify = 1;
959 else if (rs[i].s && rs[i].s->get) {
960 curval = rs[i].s->get(NULL);
961 if (g_strcmp0(val, curval))
962 modify = 1;
963 g_free(curval);
965 break;
966 case XT_S_INVALID: /* FALLTHROUGH */
967 default:
968 break;
970 if (rs[i].activate(val)) {
971 sr = g_malloc(sizeof *sr);
972 sr->name = g_strdup(rs[i].name);
973 sr->value = g_strdup(val);
974 TAILQ_INSERT_TAIL(&srl, sr, entry);
975 continue;
977 if (modify)
978 if (save_runtime_setting(rs[i].name, val))
979 show_oops(t, "error");
981 break;
982 default:
983 show_oops(t, "%s: invalid set command", __func__);
984 break;
987 if (new_settings)
988 g_hash_table_destroy(new_settings);
990 xtp_page_rt(t, NULL);
993 /* link an XTP class to it's session key and handler function */
994 struct xtp_despatch {
995 uint8_t xtp_class;
996 void (*handle_func)(struct tab *, uint8_t, int,
997 const char *query);
1000 struct xtp_despatch xtp_despatches[] = {
1001 { XT_XTP_DL, xtp_handle_dl },
1002 { XT_XTP_HL, xtp_handle_hl },
1003 { XT_XTP_FL, xtp_handle_fl },
1004 { XT_XTP_CL, xtp_handle_cl },
1005 { XT_XTP_SL, xtp_handle_sl },
1006 { XT_XTP_AB, xtp_handle_ab },
1007 { XT_XTP_SV, xtp_handle_sv },
1008 { XT_XTP_RT, xtp_handle_rt },
1009 { XT_XTP_INVALID, NULL }
1013 * generate a session key to secure xtp commands.
1014 * pass in a ptr to the key in question and it will
1015 * be modified in place.
1017 void
1018 generate_xtp_session_key(char **key)
1020 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1022 /* free old key */
1023 if (*key)
1024 g_free(*key);
1026 /* make a new one */
1027 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1028 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1029 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1030 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1032 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1036 * validate a xtp session key.
1037 * return (1) if OK
1040 validate_xtp_session_key(struct tab *t, char *key)
1042 if (t == NULL || t->session_key == NULL || key == NULL)
1043 return (0);
1045 if (strcmp(t->session_key, key) != 0) {
1046 show_oops(t, "%s: xtp session key mismatch possible spoof",
1047 __func__);
1048 return (0);
1051 return (1);
1055 * is the url xtp protocol? (xxxt://)
1056 * if so, parse and despatch correct bahvior
1059 parse_xtp_url(struct tab *t, const char *uri_str)
1061 SoupURI *uri = NULL;
1062 struct xtp_despatch *dsp, *dsp_match = NULL;
1063 int ret = FALSE;
1064 int class = 0;
1065 char **sv = NULL;
1068 * uri->host = class
1069 * sv[0] = session key
1070 * sv[1] = command
1071 * sv[2] = optional argument
1074 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, uri_str);
1076 if ((uri = soup_uri_new(uri_str)) == NULL)
1077 goto clean;
1078 if (strncmp(uri->scheme, XT_XTP_SCHEME, strlen(XT_XTP_SCHEME)))
1079 goto clean;
1080 if (uri->host == NULL || strlen(uri->host) == 0)
1081 goto clean;
1082 else
1083 class = atoi(uri->host);
1084 if ((sv = g_strsplit(uri->path + 1, "/", 3)) == NULL)
1085 goto clean;
1087 if (sv[0] == NULL || sv[1] == NULL)
1088 goto clean;
1090 dsp = xtp_despatches;
1091 class = atoi(uri->host);
1092 while (dsp->xtp_class) {
1093 if (dsp->xtp_class == class) {
1094 dsp_match = dsp;
1095 break;
1097 dsp++;
1100 /* did we find one atall? */
1101 if (dsp_match == NULL) {
1102 show_oops(t, "%s: no matching xtp despatch found", __func__);
1103 goto clean;
1106 /* check session key and call despatch function */
1107 if (validate_xtp_session_key(t, sv[0])) {
1108 ret = TRUE; /* all is well, this was a valid xtp request */
1109 if (sv[2])
1110 dsp_match->handle_func(t, atoi(sv[1]), atoi(sv[2]),
1111 uri->query);
1112 else
1113 dsp_match->handle_func(t, atoi(sv[1]), 0, uri->query);
1116 clean:
1117 if (uri)
1118 soup_uri_free(uri);
1119 if (sv)
1120 g_strfreev(sv);
1122 return (ret);
1126 * update all favorite tabs apart from one. Pass NULL if
1127 * you want to update all.
1129 void
1130 update_favorite_tabs(struct tab *apart_from)
1132 struct tab *t;
1134 if (!updating_fl_tabs) {
1135 updating_fl_tabs = 1; /* stop infinite recursion */
1136 TAILQ_FOREACH(t, &tabs, entry)
1137 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1138 && (t != apart_from))
1139 xtp_page_fl(t, NULL);
1140 updating_fl_tabs = 0;
1145 * update all download tabs apart from one. Pass NULL if
1146 * you want to update all.
1148 void
1149 update_download_tabs(struct tab *apart_from)
1151 struct tab *t;
1153 if (!updating_dl_tabs) {
1154 updating_dl_tabs = 1; /* stop infinite recursion */
1155 TAILQ_FOREACH(t, &tabs, entry)
1156 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
1157 && (t != apart_from))
1158 xtp_page_dl(t, NULL);
1159 updating_dl_tabs = 0;
1164 * update all cookie tabs apart from one. Pass NULL if
1165 * you want to update all.
1167 void
1168 update_cookie_tabs(struct tab *apart_from)
1170 struct tab *t;
1172 if (!updating_cl_tabs) {
1173 updating_cl_tabs = 1; /* stop infinite recursion */
1174 TAILQ_FOREACH(t, &tabs, entry)
1175 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
1176 && (t != apart_from))
1177 xtp_page_cl(t, NULL);
1178 updating_cl_tabs = 0;
1183 * update all history tabs apart from one. Pass NULL if
1184 * you want to update all.
1186 void
1187 update_history_tabs(struct tab *apart_from)
1189 struct tab *t;
1191 if (!updating_hl_tabs) {
1192 updating_hl_tabs = 1; /* stop infinite recursion */
1193 TAILQ_FOREACH(t, &tabs, entry)
1194 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
1195 && (t != apart_from))
1196 xtp_page_hl(t, NULL);
1197 updating_hl_tabs = 0;
1202 * update all search tabs apart from one. Pass NULL if
1203 * you want to update all.
1205 void
1206 update_search_tabs(struct tab *apart_from)
1208 struct tab *t;
1210 if (!updating_sl_tabs) {
1211 updating_sl_tabs = 1; /* stop infinite recursion */
1212 TAILQ_FOREACH(t, &tabs, entry)
1213 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SL)
1214 && (t != apart_from))
1215 xtp_page_sl(t, NULL);
1216 updating_sl_tabs = 0;
1221 xtp_page_ab(struct tab *t, struct karg *args)
1223 char *page, *body;
1225 if (t == NULL)
1226 show_oops(NULL, "about invalid parameters");
1228 generate_xtp_session_key(&t->session_key);
1230 body = g_strdup_printf("<b>Version: %s</b>"
1231 #ifdef XOMBRERO_BUILDSTR
1232 "<br><b>Build: %s</b>"
1233 #endif
1234 "<br><b>WebKit: %d.%d.%d</b>"
1235 "<br><b>User Agent: %d.%d</b>"
1236 #ifdef WEBKITGTK_API_VERSION
1237 "<br><b>WebKit API: %.1f</b>"
1238 #endif
1239 "<br><b>Configuration: %s" PS "<a href='%s%d/%s/%d'>.%s</a>"
1240 " (remember to restart the browser after any changes)</b>"
1241 "<p>"
1242 "Authors:"
1243 "<ul>"
1244 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1245 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1246 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
1247 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
1248 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
1249 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
1250 "<li>Josh Rickmar &lt;jrick@devio.us&gt;</li>"
1251 "<li>David Hill &lt;dhill@mindcry.org&gt;</li>"
1252 "</ul>"
1253 "Copyrights and licenses can be found on the xombrero "
1254 "<a href=\"https://opensource.conformal.com/wiki/xombrero\">website</a>"
1255 "</p>",
1256 #ifdef XOMBRERO_BUILDSTR
1257 version, XOMBRERO_BUILDSTR,
1258 #else
1259 version,
1260 #endif
1261 WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION,
1262 WEBKIT_USER_AGENT_MAJOR_VERSION, WEBKIT_USER_AGENT_MINOR_VERSION
1263 #ifdef WEBKITGTK_API_VERSION
1264 ,WEBKITGTK_API_VERSION
1265 #endif
1266 ,pwd->pw_dir,
1267 XT_XTP_STR,
1268 XT_XTP_AB,
1269 t->session_key,
1270 XT_XTP_AB_EDIT_CONF,
1271 XT_CONF_FILE
1274 page = get_html_page("About", body, "", 0);
1275 g_free(body);
1277 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT, 0);
1279 g_free(page);
1281 return (0);
1284 /* show a list of favorites (bookmarks) */
1286 xtp_page_fl(struct tab *t, struct karg *args)
1288 char file[PATH_MAX];
1289 FILE *f;
1290 char *uri = NULL, *title = NULL;
1291 size_t len, lineno = 0;
1292 int i, failed = 0;
1293 char *body, *tmp, *page = NULL;
1294 const char delim[3] = {'\\', '\\', '\0'};
1296 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1298 if (t == NULL)
1299 warn("%s: bad param", __func__);
1301 generate_xtp_session_key(&t->session_key);
1303 /* open favorites */
1304 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
1305 if ((f = fopen(file, "r")) == NULL) {
1306 show_oops(t, "Can't open favorites file: %s", strerror(errno));
1307 return (1);
1310 /* body */
1311 if (args && args->i & XT_DELETE)
1312 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1313 "<th style='width: 40px'>&#35;</th><th>Link</th>"
1314 "<th style='width: 40px'>Rm</th></tr>\n");
1315 else
1316 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1317 "<th style='width: 40px'>&#35;</th><th>Link</th></tr>\n");
1319 for (i = 1;;) {
1320 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1321 break;
1322 if (strlen(title) == 0) {
1323 free(title);
1324 title = NULL;
1325 continue;
1328 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1329 if (feof(f) || ferror(f)) {
1330 show_oops(t, "favorites file corrupt");
1331 failed = 1;
1332 break;
1335 tmp = body;
1336 if (args && args->i & XT_DELETE)
1337 body = g_strdup_printf("%s<tr>"
1338 "<td>%d</td>"
1339 "<td><a href='%s'>%s</a></td>"
1340 "<td style='text-align: center'>"
1341 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1342 "</tr>\n",
1343 body, i, uri, title,
1344 XT_XTP_STR, XT_XTP_FL, t->session_key,
1345 XT_XTP_FL_REMOVE, i);
1346 else
1347 body = g_strdup_printf("%s<tr>"
1348 "<td>%d</td>"
1349 "<td><a href='%s'>%s</a></td>"
1350 "</tr>\n",
1351 body, i, uri, title);
1352 g_free(tmp);
1354 free(uri);
1355 uri = NULL;
1356 free(title);
1357 title = NULL;
1358 i++;
1360 fclose(f);
1362 /* if none, say so */
1363 if (i == 1) {
1364 tmp = body;
1365 body = g_strdup_printf("%s<tr>"
1366 "<td colspan='%d' style='text-align: center'>"
1367 "No favorites - To add one use the 'favadd' command."
1368 "</td></tr>", body, (args && args->i & XT_DELETE) ? 3 : 2);
1369 g_free(tmp);
1372 tmp = body;
1373 body = g_strdup_printf("%s</table>", body);
1374 g_free(tmp);
1376 if (uri)
1377 free(uri);
1378 if (title)
1379 free(title);
1381 /* render */
1382 if (!failed) {
1383 page = get_html_page("Favorites", body, "", 1);
1384 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES, 0);
1385 g_free(page);
1388 update_favorite_tabs(t);
1390 if (body)
1391 g_free(body);
1393 return (failed);
1397 * Return a new string with a download row (in html)
1398 * appended. Old string is freed.
1400 char *
1401 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1404 WebKitDownloadStatus stat;
1405 const gchar *destination;
1406 gchar *d;
1407 char *status_html = NULL, *cmd_html = NULL, *new_html;
1408 gdouble progress;
1409 char cur_sz[FMT_SCALED_STRSIZE];
1410 char tot_sz[FMT_SCALED_STRSIZE];
1411 char *xtp_prefix;
1413 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1415 /* All actions wil take this form:
1416 * xxxt://class/seskey
1418 xtp_prefix = g_strdup_printf("%s%d/%s/",
1419 XT_XTP_STR, XT_XTP_DL, t->session_key);
1421 stat = webkit_download_get_status(dl->download);
1423 switch (stat) {
1424 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1425 status_html = g_strdup_printf("Finished");
1426 cmd_html = g_strdup_printf(
1427 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1428 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1429 XT_XTP_DL_UNLINK, dl->id);
1430 break;
1431 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1432 /* gather size info */
1433 progress = 100 * webkit_download_get_progress(dl->download);
1435 fmt_scaled(
1436 webkit_download_get_current_size(dl->download), cur_sz);
1437 fmt_scaled(
1438 webkit_download_get_total_size(dl->download), tot_sz);
1440 status_html = g_strdup_printf(
1441 "<div style='width: 100%%' align='center'>"
1442 "<div class='progress-outer'>"
1443 "<div class='progress-inner' style='width: %.2f%%'>"
1444 "</div></div></div>"
1445 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1446 progress, cur_sz, tot_sz, progress);
1448 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1449 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1451 break;
1452 /* LLL */
1453 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1454 status_html = g_strdup_printf("Cancelled");
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_ERROR:
1462 status_html = g_strdup_printf("Error!");
1463 cmd_html = g_strdup_printf(
1464 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1465 xtp_prefix, XT_XTP_DL_START, dl->id,
1466 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1467 XT_XTP_DL_UNLINK, dl->id);
1468 break;
1469 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1470 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1471 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1472 XT_XTP_DL_CANCEL, dl->id);
1473 status_html = g_strdup_printf("Created");
1474 break;
1475 default:
1476 show_oops(t, "%s: unknown download status", __func__);
1479 destination = webkit_download_get_destination_uri(dl->download);
1480 /* we might not have a destination set yet */
1481 if (!destination)
1482 destination = webkit_download_get_suggested_filename(dl->download);
1483 d = g_strdup(destination); /* copy for basename */
1484 new_html = g_strdup_printf(
1485 "%s\n<tr><td>%s</td><td>%s</td>"
1486 "<td style='text-align:center'>%s</td></tr>\n",
1487 html, basename(d), status_html, cmd_html);
1488 g_free(d);
1489 g_free(html);
1491 if (status_html)
1492 g_free(status_html);
1494 if (cmd_html)
1495 g_free(cmd_html);
1497 g_free(xtp_prefix);
1499 return new_html;
1502 /* cookie management XTP page */
1504 xtp_page_cl(struct tab *t, struct karg *args)
1506 char *body, *page, *tmp;
1507 int i = 1; /* all ids start 1 */
1508 int domain_id = 0;
1509 GSList *sc, *pc, *pc_start;
1510 SoupCookie *c;
1511 char *type, *table_headers, *last_domain;
1513 DNPRINTF(XT_D_CMD, "%s", __func__);
1515 if (t == NULL) {
1516 show_oops(NULL, "%s invalid parameters", __func__);
1517 return (1);
1520 generate_xtp_session_key(&t->session_key);
1522 /* table headers */
1523 table_headers = g_strdup_printf("<table><tr>"
1524 "<th>Type</th>"
1525 "<th>Name</th>"
1526 "<th style='width:200px'>Value</th>"
1527 "<th>Path</th>"
1528 "<th>Expires</th>"
1529 "<th>Secure</th>"
1530 "<th>HTTP<br />only</th>"
1531 "<th style='width:40px'>Rm</th></tr>\n");
1533 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1534 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1535 pc_start = pc;
1537 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1538 "[ Remove All Cookies From All Domains ]</a></div>\n",
1539 XT_XTP_STR, XT_XTP_CL, t->session_key, XT_XTP_CL_REMOVE_ALL);
1541 last_domain = g_strdup("");
1542 for (; sc; sc = sc->next) {
1543 c = sc->data;
1545 if (strcmp(last_domain, c->domain) != 0) {
1546 /* new domain */
1547 domain_id ++;
1548 g_free(last_domain);
1549 last_domain = g_strdup(c->domain);
1551 if (body != NULL) {
1552 tmp = body;
1553 body = g_strdup_printf("%s</table>"
1554 "<h2>%s</h2><div align=\"center\">"
1555 "<a href='%s%d/%s/%d/%d'>"
1556 "[ Remove All From This Domain ]"
1557 "</a></div>%s\n",
1558 body, c->domain,
1559 XT_XTP_STR, XT_XTP_CL, t->session_key,
1560 XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1561 table_headers);
1562 g_free(tmp);
1563 } else {
1564 /* first domain */
1565 body = g_strdup_printf("<h2>%s</h2>"
1566 "<div align=\"center\">"
1567 "<a href='%s%d/%s/%d/%d'>"
1568 "[ Remove All From This Domain ]</a></div>%s\n",
1569 c->domain, XT_XTP_STR, XT_XTP_CL,
1570 t->session_key, XT_XTP_CL_REMOVE_DOMAIN,
1571 domain_id, table_headers);
1575 type = "Session";
1576 for (pc = pc_start; pc; pc = pc->next)
1577 if (soup_cookie_equal(pc->data, c)) {
1578 type = "Session + Persistent";
1579 break;
1582 tmp = body;
1583 body = g_strdup_printf(
1584 "%s\n<tr>"
1585 "<td>%s</td>"
1586 "<td style='word-wrap:normal'>%s</td>"
1587 "<td>"
1588 " <textarea rows='4'>%s</textarea>"
1589 "</td>"
1590 "<td>%s</td>"
1591 "<td>%s</td>"
1592 "<td>%d</td>"
1593 "<td>%d</td>"
1594 "<td style='text-align:center'>"
1595 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1596 body,
1597 type,
1598 c->name,
1599 c->value,
1600 c->path,
1601 c->expires ?
1602 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1603 c->secure,
1604 c->http_only,
1606 XT_XTP_STR,
1607 XT_XTP_CL,
1608 t->session_key,
1609 XT_XTP_CL_REMOVE,
1613 g_free(tmp);
1614 i++;
1617 soup_cookies_free(sc);
1618 soup_cookies_free(pc);
1620 /* small message if there are none */
1621 if (i == 1) {
1622 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1623 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1625 tmp = body;
1626 body = g_strdup_printf("%s</table>", body);
1627 g_free(tmp);
1629 page = get_html_page("Cookie Jar", body, "", TRUE);
1630 g_free(body);
1631 g_free(table_headers);
1632 g_free(last_domain);
1634 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR, 0);
1635 update_cookie_tabs(t);
1637 g_free(page);
1639 return (0);
1643 xtp_page_hl(struct tab *t, struct karg *args)
1645 char *body, *page, *tmp;
1646 struct history *h;
1647 int i = 1; /* all ids start 1 */
1649 DNPRINTF(XT_D_CMD, "%s", __func__);
1651 if (t == NULL) {
1652 show_oops(NULL, "%s invalid parameters", __func__);
1653 return (1);
1656 generate_xtp_session_key(&t->session_key);
1658 /* body */
1659 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1660 "[ Remove All ]</a></div>"
1661 "<table style='table-layout:fixed'><tr>"
1662 "<th>URI</th><th>Title</th><th>Last visited</th>"
1663 "<th style='width: 40px'>Rm</th></tr>\n",
1664 XT_XTP_STR, XT_XTP_HL, t->session_key, XT_XTP_HL_REMOVE_ALL);
1666 RB_FOREACH_REVERSE(h, history_list, &hl) {
1667 tmp = body;
1668 body = g_strdup_printf(
1669 "%s\n<tr>"
1670 "<td><a href='%s'>%s</a></td>"
1671 "<td>%s</td>"
1672 "<td>%s</td>"
1673 "<td style='text-align: center'>"
1674 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1675 body, h->uri, h->uri, h->title, ctime(&h->time),
1676 XT_XTP_STR, XT_XTP_HL, t->session_key,
1677 XT_XTP_HL_REMOVE, i);
1679 g_free(tmp);
1680 i++;
1683 /* small message if there are none */
1684 if (i == 1) {
1685 tmp = body;
1686 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1687 "colspan='4'>No History</td></tr>\n", body);
1688 g_free(tmp);
1691 tmp = body;
1692 body = g_strdup_printf("%s</table>", body);
1693 g_free(tmp);
1695 page = get_html_page("History", body, "", TRUE);
1696 g_free(body);
1699 * update all history manager tabs as the xtp session
1700 * key has now changed. No need to update the current tab.
1701 * Already did that above.
1703 update_history_tabs(t);
1705 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY, 0);
1706 g_free(page);
1708 return (0);
1712 * Generate a web page detailing the status of any downloads
1715 xtp_page_dl(struct tab *t, struct karg *args)
1717 struct download *dl;
1718 char *body, *page, *tmp;
1719 char *ref;
1720 int n_dl = 1;
1722 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1724 if (t == NULL) {
1725 show_oops(NULL, "%s invalid parameters", __func__);
1726 return (1);
1729 generate_xtp_session_key(&t->session_key);
1731 /* header - with refresh so as to update */
1732 if (refresh_interval >= 1)
1733 ref = g_strdup_printf(
1734 "<meta http-equiv='refresh' content='%u"
1735 ";url=%s%d/%s/%d' />\n",
1736 refresh_interval,
1737 XT_XTP_STR,
1738 XT_XTP_DL,
1739 t->session_key,
1740 XT_XTP_DL_LIST);
1741 else
1742 ref = g_strdup("");
1744 body = g_strdup_printf("<div align='center'>"
1745 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1746 "</p><table><tr><th style='width: 60%%'>"
1747 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1748 XT_XTP_STR, XT_XTP_DL, t->session_key, XT_XTP_DL_LIST);
1750 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1751 body = xtp_page_dl_row(t, body, dl);
1752 n_dl++;
1755 /* message if no downloads in list */
1756 if (n_dl == 1) {
1757 tmp = body;
1758 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1759 " style='text-align: center'>"
1760 "No downloads</td></tr>\n", body);
1761 g_free(tmp);
1764 tmp = body;
1765 body = g_strdup_printf("%s</table></div>", body);
1766 g_free(tmp);
1768 page = get_html_page("Downloads", body, ref, 1);
1769 g_free(ref);
1770 g_free(body);
1773 * update all download manager tabs as the xtp session
1774 * key has now changed. No need to update the current tab.
1775 * Already did that above.
1777 update_download_tabs(t);
1779 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS, 0);
1780 g_free(page);
1782 return (0);
1786 xtp_page_sl(struct tab *t, struct karg *args)
1788 int i;
1789 char *page, *body, *tmp;
1791 DNPRINTF(XT_D_SEARCH, "%s", __func__);
1793 generate_xtp_session_key(&t->session_key);
1795 if (t == NULL) {
1796 show_oops(NULL, "%s invalid parameters", __func__);
1797 return (1);
1800 body = g_strdup_printf("<p>The xombrero authors will not choose a "
1801 "default search engine for you. What follows is a list of search "
1802 "engines (in no particular order) you may be interested in. "
1803 "To permanently choose a search engine, click [ Select ] to save "
1804 "<tt>search_string</tt> as a runtime setting, or set "
1805 "<tt>search_string</tt> to the appropriate URL in your xombrero "
1806 "configuration.</p>");
1808 tmp = body;
1809 body = g_strdup_printf("%s\n<table style='table-layout:fixed'><tr>"
1810 "<th style='width: 200px'>Name</th><th>URL</th>"
1811 "<th style='width: 100px'>Select</th></tr>\n", body);
1812 g_free(tmp);
1814 for (i = 0; i < (sizeof search_list / sizeof (struct search_type)); ++i)
1815 body = search_engine_add(body, search_list[i].name,
1816 search_list[i].url, t->session_key, i);
1818 tmp = body;
1819 body = g_strdup_printf("%s</table>", body);
1820 g_free(tmp);
1822 page = get_html_page("Choose a search engine", body, "", 1);
1823 g_free(body);
1826 * update all search tabs as the xtp session key has now changed. No
1827 * need to update the current tab. Already did that above.
1829 update_search_tabs(t);
1831 load_webkit_string(t, page, XT_URI_ABOUT_SEARCH, 0);
1832 g_free(page);
1834 return (0);
1838 xtp_page_sv(struct tab *t, struct karg *args)
1840 SoupURI *soupuri;
1841 static int arg = 0;
1842 struct secviolation find, *sv;
1843 char *page, *body;
1845 if (t == NULL)
1846 show_oops(NULL, "secviolation invalid parameters");
1848 generate_xtp_session_key(&t->session_key);
1850 if (args == NULL) {
1851 find.xtp_arg = t->xtp_arg;
1852 sv = RB_FIND(secviolation_list, &svl, &find);
1853 if (sv == NULL)
1854 return (-1);
1855 } else {
1856 sv = g_malloc(sizeof(struct secviolation));
1857 sv->xtp_arg = ++arg;
1858 t->xtp_arg = arg;
1859 sv->t = t;
1860 sv->uri = args->s;
1861 RB_INSERT(secviolation_list, &svl, sv);
1864 if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
1865 return (-1);
1867 body = g_strdup_printf(
1868 "<p><b>You tried to access %s</b>."
1869 "<p><b>The site's security certificate has been modified.</b>"
1870 "<p>The domain of the page you have tried to access, <b>%s</b>, "
1871 "has a different remote certificate then the local cached version "
1872 "from a previous visit. As a security precaution to help prevent "
1873 "against man-in-the-middle attacks, please choose one of the "
1874 "following actions to continue, or disable the "
1875 "<tt>warn_cert_changes</tt> setting in your xombrero "
1876 "configuration."
1877 "<p><b>Choose an action:"
1878 "<br><a href='%s%d/%s/%d/%d'>Allow for this session</a>"
1879 "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>"
1880 "<br><a href='%s%d/%s/%d/%d'>Show cached certificate</a>"
1881 "<br><a href='%s%d/%s/%d/%d'>Show new certificate</a>",
1882 sv->uri,
1883 soupuri->host,
1884 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_ALLOW_SESSION,
1885 sv->xtp_arg,
1886 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_CACHE,
1887 sv->xtp_arg,
1888 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_CACHED_CERT,
1889 sv->xtp_arg,
1890 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_NEW_CERT,
1891 sv->xtp_arg);
1893 page = get_html_page("Security Violation", body, "", 0);
1894 g_free(body);
1896 load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION, 1);
1898 g_free(page);
1899 if (soupuri)
1900 soup_uri_free(soupuri);
1902 return (0);
1906 startpage(struct tab *t, struct karg *args)
1908 char *page, *body, *b;
1909 struct sp *s;
1911 if (t == NULL)
1912 show_oops(NULL, "startpage invalid parameters");
1914 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1916 TAILQ_FOREACH(s, &spl, entry) {
1917 b = body;
1918 body = g_strdup_printf("%s%s<br>", body, s->line);
1919 g_free(b);
1922 page = get_html_page("Startup Exception", body, "", 0);
1923 g_free(body);
1925 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE, 0);
1926 g_free(page);
1928 return (0);
1931 void
1932 startpage_add(const char *fmt, ...)
1934 va_list ap;
1935 char *msg;
1936 struct sp *s;
1938 if (fmt == NULL)
1939 return;
1941 va_start(ap, fmt);
1942 if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
1943 errx(1, "startpage_add failed");
1944 va_end(ap);
1946 s = g_malloc0(sizeof *s);
1947 s->line = msg;
1949 TAILQ_INSERT_TAIL(&spl, s, entry);
1951 gchar *show_g_object_settings(GObject *, char *, int);
1953 char *
1954 xt_g_object_serialize(GValue *value, const gchar *tname, char *str, int recurse)
1956 int typeno = 0;
1957 char *valstr, *tmpstr, *tmpsettings;
1958 GObject *object;
1960 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(value) );
1961 switch ( typeno ) {
1962 case G_TYPE_ENUM:
1963 valstr = g_strdup_printf("%d",
1964 g_value_get_enum(value));
1965 break;
1966 case G_TYPE_CHAR:
1967 valstr = g_strdup_printf("%c",
1968 #if GLIB_CHECK_VERSION(2, 32, 0)
1969 g_value_get_schar(value));
1970 #else
1971 g_value_get_char(value));
1972 #endif
1973 break;
1974 case G_TYPE_UCHAR:
1975 valstr = g_strdup_printf("%c",
1976 g_value_get_uchar(value));
1977 break;
1978 case G_TYPE_LONG:
1979 valstr = g_strdup_printf("%ld",
1980 g_value_get_long(value));
1981 break;
1982 case G_TYPE_ULONG:
1983 valstr = g_strdup_printf("%ld",
1984 g_value_get_ulong(value));
1985 break;
1986 case G_TYPE_INT:
1987 valstr = g_strdup_printf("%d",
1988 g_value_get_int(value));
1989 break;
1990 case G_TYPE_INT64:
1991 valstr = g_strdup_printf("%" PRIo64,
1992 (int64_t) g_value_get_int64(value));
1993 break;
1994 case G_TYPE_UINT:
1995 valstr = g_strdup_printf("%d",
1996 g_value_get_uint(value));
1997 break;
1998 case G_TYPE_UINT64:
1999 valstr = g_strdup_printf("%" PRIu64,
2000 (uint64_t) g_value_get_uint64(value));
2001 break;
2002 case G_TYPE_FLAGS:
2003 valstr = g_strdup_printf("0x%x",
2004 g_value_get_flags(value));
2005 break;
2006 case G_TYPE_BOOLEAN:
2007 valstr = g_strdup_printf("%s",
2008 g_value_get_boolean(value) ? "TRUE" : "FALSE");
2009 break;
2010 case G_TYPE_FLOAT:
2011 valstr = g_strdup_printf("%f",
2012 g_value_get_float(value));
2013 break;
2014 case G_TYPE_DOUBLE:
2015 valstr = g_strdup_printf("%f",
2016 g_value_get_double(value));
2017 break;
2018 case G_TYPE_STRING:
2019 valstr = g_strdup_printf("\"%s\"",
2020 g_value_get_string(value));
2021 break;
2022 case G_TYPE_POINTER:
2023 valstr = g_strdup_printf("%p",
2024 g_value_get_pointer(value));
2025 break;
2026 case G_TYPE_OBJECT:
2027 object = g_value_get_object(value);
2028 if (object != NULL) {
2029 if (recurse) {
2030 tmpstr = g_strdup_printf("%s ", str);
2031 tmpsettings = show_g_object_settings( object,
2032 tmpstr, recurse);
2033 g_free(tmpstr);
2035 if (strrchr(tmpsettings, '\n') != NULL) {
2036 valstr = g_strdup_printf("%s%s }",
2037 tmpsettings, str);
2038 g_free(tmpsettings);
2039 } else {
2040 valstr = tmpsettings;
2042 } else {
2043 valstr = g_strdup_printf("<...>");
2045 } else {
2046 valstr = g_strdup_printf("settings[] = NULL");
2048 break;
2049 default:
2050 valstr = g_strdup_printf("type %s unhandled", tname);
2052 return valstr;
2055 gchar *
2056 show_g_object_settings(GObject *o, char *str, int recurse)
2058 char *b, *p, *body, *valstr, *tmpstr;
2059 guint n_props = 0;
2060 int i, typeno = 0;
2061 GParamSpec *pspec;
2062 const gchar *tname;
2063 GValue value;
2064 GParamSpec **proplist;
2065 const gchar *name;
2067 if (!G_IS_OBJECT(o)) {
2068 fprintf(stderr, "%s is not a g_object\n", str);
2069 return g_strdup("");
2071 proplist = g_object_class_list_properties(
2072 G_OBJECT_GET_CLASS(o), &n_props);
2074 if (GTK_IS_WIDGET(o)) {
2075 name = gtk_widget_get_name(GTK_WIDGET(o));
2076 } else {
2077 name = "settings";
2079 if (n_props == 0) {
2080 body = g_strdup_printf("%s[0] = { }", name);
2081 goto end_show_g_objects;
2084 body = g_strdup_printf("%s[%d] = {\n", name, n_props);
2085 for (i=0; i < n_props; i++) {
2086 pspec = proplist[i];
2087 tname = G_OBJECT_TYPE_NAME(pspec);
2088 bzero(&value, sizeof value);
2089 valstr = NULL;
2091 if (!(pspec->flags & G_PARAM_READABLE))
2092 valstr = g_strdup_printf("not a readable property");
2093 else {
2094 g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
2095 g_object_get_property(G_OBJECT(o), pspec->name,
2096 &value);
2097 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(&value) );
2100 /* based on the type, recurse and display values */
2101 if (valstr == NULL) {
2102 valstr = xt_g_object_serialize(&value, tname, str,
2103 recurse);
2106 tmpstr = g_strdup_printf("%-13s %s%s%s,", tname, pspec->name,
2107 (typeno == G_TYPE_OBJECT) ? "." : " = ", valstr);
2108 b = body;
2110 #define XT_G_OBJECT_SPACING 50
2111 p = strrchr(tmpstr, '\n');
2112 if (p == NULL && strlen(tmpstr) > XT_G_OBJECT_SPACING) {
2113 body = g_strdup_printf(
2114 "%s%s %-50s\n%s %50s /* %3d flags=0x%08x */\n",
2115 body, str, tmpstr, str, "", i, pspec->flags);
2116 } else {
2117 char *fmt;
2118 int strspaces;
2119 if (p == NULL)
2120 strspaces = XT_G_OBJECT_SPACING;
2121 else
2122 strspaces = strlen(tmpstr) - (strlen(p) - strlen(str)) + XT_G_OBJECT_SPACING + 5;
2123 fmt = g_strdup_printf("%%s%%s %%-%ds /* %%3d flags=0x%%08x */\n", strspaces);
2124 body = g_strdup_printf(fmt, body, str, tmpstr, i, pspec->flags);
2125 g_free(fmt);
2127 g_free(tmpstr);
2128 g_free(b);
2129 g_free(valstr);
2131 end_show_g_objects:
2132 g_free(proplist);
2133 return (body);
2136 char *
2137 xt_append_settings(char *str, GObject *object, char *name, int recurse)
2139 char *newstr, *settings;
2141 settings = show_g_object_settings(object, name, recurse);
2142 if (str == NULL)
2143 str = g_strdup("");
2145 newstr = g_strdup_printf("%s%s %s%s };\n", str, name, settings, name);
2146 g_free(str);
2148 return newstr;
2152 about_webkit(struct tab *t, struct karg *arg)
2154 char *page, *body, *settingstr;
2156 settingstr = xt_append_settings(NULL, G_OBJECT(t->settings),
2157 "t->settings", 0);
2158 body = g_strdup_printf("<pre>%s</pre>\n", settingstr);
2159 g_free(settingstr);
2161 page = get_html_page("About Webkit", body, "", 0);
2162 g_free(body);
2164 load_webkit_string(t, page, XT_URI_ABOUT_WEBKIT, 0);
2165 g_free(page);
2167 return (0);
2170 static int toplevelcount = 0;
2172 void
2173 xt_append_toplevel(GtkWindow *w, char **body)
2175 char *n;
2177 n = g_strdup_printf("toplevel#%d", toplevelcount++);
2178 *body = xt_append_settings(*body, G_OBJECT(w), n, 0);
2179 g_free(n);
2183 allthethings(struct tab *t, struct karg *arg)
2185 GList *list;
2186 char *page, *body, *b;
2188 body = xt_append_settings(NULL, G_OBJECT(t->wv), "t->wv", 1);
2189 body = xt_append_settings(body, G_OBJECT(t->inspector),
2190 "t->inspector", 1);
2191 #if 0 /* not until warnings are gone */
2192 body = xt_append_settings(body, G_OBJECT(session),
2193 "session", 1);
2194 #endif
2195 toplevelcount = 0;
2196 list = gtk_window_list_toplevels();
2197 g_list_foreach(list, (GFunc)g_object_ref, NULL);
2198 g_list_foreach(list, (GFunc)xt_append_toplevel, &body);
2199 g_list_foreach(list, (GFunc)g_object_unref, NULL);
2200 g_list_free(list);
2202 b = body;
2203 body = g_strdup_printf("<pre>%scan paste clipboard = %d\n</pre>", body,
2204 webkit_web_view_can_paste_clipboard(t->wv));
2205 g_free(b);
2207 page = get_html_page("About All The Things _o/", body, "", 0);
2208 g_free(body);
2210 load_webkit_string(t, page, XT_URI_ABOUT_ALLTHETHINGS, 0);
2211 g_free(page);
2213 return (0);