Be sure not to malloc nothing
[xombrero.git] / about.c
blobb6095678f24bcd32833c01993fa417fd18d5e741
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))
720 break;
721 else {
722 show_oops(t, "Error reading favorites file: %s",
723 strerror(errno));
724 goto done;
728 if (linelen == urilen && !strcmp(line, uri))
729 goto done;
731 free(line);
732 line = NULL;
735 fprintf(f, "\n%s\n%s", title, uri);
736 done:
737 if (argtitle)
738 g_free(argtitle);
739 if (line)
740 free(line);
741 fclose(f);
743 update_favorite_tabs(NULL);
745 return (0);
748 char *
749 search_engine_add(char *body, const char *name, const char *url,
750 const char *key, int select)
752 char *b = body;
754 body = g_strdup_printf("%s<tr>"
755 "<td>%s</td>"
756 "<td>%s</td>"
757 "<td style='text-align: center'>"
758 "<a href='%s%d/%s/%d/%d'>[ Select ]</a></td>"
759 "</tr>\n",
760 body,
761 name,
762 url,
763 XT_XTP_STR, XT_XTP_SL, key, XT_XTP_SL_SET, select);
764 g_free(b);
765 return (body);
768 void
769 xtp_handle_ab(struct tab *t, uint8_t cmd, int arg, const char *query)
771 char config[PATH_MAX];
772 char *cmdstr;
773 char **sv;
775 switch (cmd) {
776 case XT_XTP_AB_EDIT_CONF:
777 if (external_editor == NULL || strlen(external_editor) == 0) {
778 show_oops(t, "external_editor is unset");
779 break;
782 snprintf(config, sizeof config, "%s" PS ".%s", pwd->pw_dir,
783 XT_CONF_FILE);
784 sv = g_strsplit(external_editor, "<file>", -1);
785 cmdstr = g_strjoinv(config, sv);
786 g_strfreev(sv);
787 sv = g_strsplit_set(cmdstr, " \t", -1);
789 if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
790 NULL, NULL))
791 show_oops(t, "%s: could not spawn process", __func__);
793 g_strfreev(sv);
794 g_free(cmdstr);
795 break;
796 default:
797 show_oops(t, "%s, invalid about command", __func__);
798 break;
800 xtp_page_ab(t, NULL);
802 void
803 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg, const char *query)
805 struct karg args = {0};
807 switch (cmd) {
808 case XT_XTP_FL_LIST:
809 /* nothing, just the below call to xtp_page_fl() */
810 break;
811 case XT_XTP_FL_REMOVE:
812 remove_favorite(t, arg);
813 args.i = XT_DELETE;
814 break;
815 default:
816 show_oops(t, "%s: invalid favorites command", __func__);
817 break;
820 xtp_page_fl(t, &args);
823 void
824 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg, const char *query)
826 switch (cmd) {
827 case XT_XTP_CL_LIST:
828 /* nothing, just xtp_page_cl() */
829 break;
830 case XT_XTP_CL_REMOVE:
831 remove_cookie(arg);
832 break;
833 case XT_XTP_CL_REMOVE_DOMAIN:
834 remove_cookie_domain(arg);
835 break;
836 case XT_XTP_CL_REMOVE_ALL:
837 remove_cookie_all();
838 break;
839 default:
840 show_oops(t, "%s: unknown cookie xtp command", __func__);
841 break;
844 xtp_page_cl(t, NULL);
847 void
848 xtp_handle_sl(struct tab *t, uint8_t cmd, int arg, const char *query)
850 const char *search;
851 char *enc_search, *uri;
852 char **sv;
854 switch (cmd) {
855 case XT_XTP_SL_SET:
856 set_search_string((char *)search_list[arg].url);
857 if (save_runtime_setting("search_string", search_list[arg].url))
858 show_oops(t, "could not set search_string in runtime");
859 break;
860 default:
861 show_oops(t, "%s: unknown search xtp command", __func__);
862 break;
865 search = gtk_entry_get_text(GTK_ENTRY(t->search_entry)); /* static */
866 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
867 sv = g_strsplit(search_string, "%s", 2);
868 uri = g_strjoinv(enc_search, sv);
869 load_uri(t, uri);
870 g_free(enc_search);
871 g_strfreev(sv);
872 g_free(uri);
875 void
876 xtp_handle_sv(struct tab *t, uint8_t cmd, int id, const char *query)
878 SoupURI *soupuri = NULL;
879 struct karg args = {0};
880 struct secviolation find, *sv;
882 find.xtp_arg = id;
883 if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
884 return;
886 args.ptr = (void *)sv->t;
887 args.s = sv->uri;
889 switch (cmd) {
890 case XT_XTP_SV_SHOW_NEW_CERT:
891 args.i = XT_SHOW;
892 if (cert_cmd(t, &args)) {
893 xtp_page_sv(t, &args);
894 return;
896 break;
897 case XT_XTP_SV_SHOW_CACHED_CERT:
898 args.i = XT_CACHE | XT_SHOW;
899 if (cert_cmd(t, &args)) {
900 xtp_page_sv(t, &args);
901 return;
903 break;
904 case XT_XTP_SV_ALLOW_SESSION:
905 soupuri = soup_uri_new(sv->uri);
906 wl_add(soupuri->host, &svil, 0);
907 load_uri(t, sv->uri);
908 focus_webview(t);
909 break;
910 case XT_XTP_SV_CACHE:
911 args.i = XT_CACHE;
912 if (cert_cmd(t, &args)) {
913 xtp_page_sv(t, &args);
914 return;
916 load_uri(t, sv->uri);
917 focus_webview(t);
918 break;
919 default:
920 show_oops(t, "%s: invalid secviolation command", __func__);
921 break;
924 g_free(sv->uri);
925 if (soupuri)
926 soup_uri_free(soupuri);
927 RB_REMOVE(secviolation_list, &svl, sv);
930 void
931 xtp_handle_rt(struct tab *t, uint8_t cmd, int id, const char *query)
933 struct set_reject *sr;
934 GHashTable *new_settings = NULL;
935 int modify;
936 char *val, *curval, *s;
937 int i = 0;
939 switch (cmd) {
940 case XT_XTP_RT_SAVE:
941 if (query == NULL)
942 break;
943 new_settings = soup_form_decode(query);
944 for (i = 0; i < get_settings_size(); ++i) {
945 if (!rs[i].activate)
946 continue;
947 val = (char *)g_hash_table_lookup(new_settings,
948 rs[i].name);
949 modify = 0;
950 switch (rs[i].type) {
951 case XT_S_INT: /* FALLTHROUGH */
952 case XT_S_BOOL:
953 if (atoi(val) != *rs[i].ival)
954 modify = 1;
955 break;
956 case XT_S_FLOAT:
957 if (atof(val) != *rs[i].fval)
958 modify = 1;
959 break;
960 case XT_S_STR:
961 s = (rs[i].sval == NULL || *rs[i].sval == NULL)
962 ? "" : *rs[i].sval;
963 if (rs[i].sval && g_strcmp0(val, s))
964 modify = 1;
965 else if (rs[i].s && rs[i].s->get) {
966 curval = rs[i].s->get(NULL);
967 if (g_strcmp0(val, curval))
968 modify = 1;
969 g_free(curval);
971 break;
972 case XT_S_INVALID: /* FALLTHROUGH */
973 default:
974 break;
976 if (rs[i].activate(val)) {
977 sr = g_malloc(sizeof *sr);
978 sr->name = g_strdup(rs[i].name);
979 sr->value = g_strdup(val);
980 TAILQ_INSERT_TAIL(&srl, sr, entry);
981 continue;
983 if (modify)
984 if (save_runtime_setting(rs[i].name, val))
985 show_oops(t, "error");
987 break;
988 default:
989 show_oops(t, "%s: invalid set command", __func__);
990 break;
993 if (new_settings)
994 g_hash_table_destroy(new_settings);
996 xtp_page_rt(t, NULL);
999 /* link an XTP class to it's session key and handler function */
1000 struct xtp_despatch {
1001 uint8_t xtp_class;
1002 void (*handle_func)(struct tab *, uint8_t, int,
1003 const char *query);
1006 struct xtp_despatch xtp_despatches[] = {
1007 { XT_XTP_DL, xtp_handle_dl },
1008 { XT_XTP_HL, xtp_handle_hl },
1009 { XT_XTP_FL, xtp_handle_fl },
1010 { XT_XTP_CL, xtp_handle_cl },
1011 { XT_XTP_SL, xtp_handle_sl },
1012 { XT_XTP_AB, xtp_handle_ab },
1013 { XT_XTP_SV, xtp_handle_sv },
1014 { XT_XTP_RT, xtp_handle_rt },
1015 { XT_XTP_INVALID, NULL }
1019 * generate a session key to secure xtp commands.
1020 * pass in a ptr to the key in question and it will
1021 * be modified in place.
1023 void
1024 generate_xtp_session_key(char **key)
1026 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1028 if (key == NULL)
1029 return;
1031 /* free old key */
1032 if (*key != NULL)
1033 g_free(*key);
1035 /* make a new one */
1036 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1037 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1038 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1039 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1041 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1045 * validate a xtp session key.
1046 * return (1) if OK
1049 validate_xtp_session_key(struct tab *t, char *key)
1051 if (t == NULL || t->session_key == NULL || key == NULL)
1052 return (0);
1054 if (strcmp(t->session_key, key) != 0) {
1055 show_oops(t, "%s: xtp session key mismatch possible spoof",
1056 __func__);
1057 return (0);
1060 return (1);
1064 * is the url xtp protocol? (xxxt://)
1065 * if so, parse and despatch correct bahvior
1068 parse_xtp_url(struct tab *t, const char *uri_str)
1070 SoupURI *uri = NULL;
1071 struct xtp_despatch *dsp, *dsp_match = NULL;
1072 int ret = FALSE;
1073 int class = 0;
1074 char **sv = NULL;
1077 * uri->host = class
1078 * sv[0] = session key
1079 * sv[1] = command
1080 * sv[2] = optional argument
1083 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, uri_str);
1085 if ((uri = soup_uri_new(uri_str)) == NULL)
1086 goto clean;
1087 if (strncmp(uri->scheme, XT_XTP_SCHEME, strlen(XT_XTP_SCHEME)))
1088 goto clean;
1089 if (uri->host == NULL || strlen(uri->host) == 0)
1090 goto clean;
1091 if ((sv = g_strsplit(uri->path + 1, "/", 3)) == NULL)
1092 goto clean;
1094 if (sv[0] == NULL || sv[1] == NULL)
1095 goto clean;
1097 dsp = xtp_despatches;
1098 class = atoi(uri->host);
1099 while (dsp->xtp_class) {
1100 if (dsp->xtp_class == class) {
1101 dsp_match = dsp;
1102 break;
1104 dsp++;
1107 /* did we find one atall? */
1108 if (dsp_match == NULL) {
1109 show_oops(t, "%s: no matching xtp despatch found", __func__);
1110 goto clean;
1113 /* check session key and call despatch function */
1114 if (validate_xtp_session_key(t, sv[0])) {
1115 ret = TRUE; /* all is well, this was a valid xtp request */
1116 if (sv[2])
1117 dsp_match->handle_func(t, atoi(sv[1]), atoi(sv[2]),
1118 uri->query);
1119 else
1120 dsp_match->handle_func(t, atoi(sv[1]), 0, uri->query);
1123 clean:
1124 if (uri)
1125 soup_uri_free(uri);
1126 if (sv)
1127 g_strfreev(sv);
1129 return (ret);
1133 * update all favorite tabs apart from one. Pass NULL if
1134 * you want to update all.
1136 void
1137 update_favorite_tabs(struct tab *apart_from)
1139 struct tab *t;
1141 if (!updating_fl_tabs) {
1142 updating_fl_tabs = 1; /* stop infinite recursion */
1143 TAILQ_FOREACH(t, &tabs, entry)
1144 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1145 && (t != apart_from))
1146 xtp_page_fl(t, NULL);
1147 updating_fl_tabs = 0;
1152 * update all download tabs apart from one. Pass NULL if
1153 * you want to update all.
1155 void
1156 update_download_tabs(struct tab *apart_from)
1158 struct tab *t;
1160 if (!updating_dl_tabs) {
1161 updating_dl_tabs = 1; /* stop infinite recursion */
1162 TAILQ_FOREACH(t, &tabs, entry)
1163 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
1164 && (t != apart_from))
1165 xtp_page_dl(t, NULL);
1166 updating_dl_tabs = 0;
1171 * update all cookie tabs apart from one. Pass NULL if
1172 * you want to update all.
1174 void
1175 update_cookie_tabs(struct tab *apart_from)
1177 struct tab *t;
1179 if (!updating_cl_tabs) {
1180 updating_cl_tabs = 1; /* stop infinite recursion */
1181 TAILQ_FOREACH(t, &tabs, entry)
1182 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
1183 && (t != apart_from))
1184 xtp_page_cl(t, NULL);
1185 updating_cl_tabs = 0;
1190 * update all history tabs apart from one. Pass NULL if
1191 * you want to update all.
1193 void
1194 update_history_tabs(struct tab *apart_from)
1196 struct tab *t;
1198 if (!updating_hl_tabs) {
1199 updating_hl_tabs = 1; /* stop infinite recursion */
1200 TAILQ_FOREACH(t, &tabs, entry)
1201 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
1202 && (t != apart_from))
1203 xtp_page_hl(t, NULL);
1204 updating_hl_tabs = 0;
1209 * update all search tabs apart from one. Pass NULL if
1210 * you want to update all.
1212 void
1213 update_search_tabs(struct tab *apart_from)
1215 struct tab *t;
1217 if (!updating_sl_tabs) {
1218 updating_sl_tabs = 1; /* stop infinite recursion */
1219 TAILQ_FOREACH(t, &tabs, entry)
1220 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SL)
1221 && (t != apart_from))
1222 xtp_page_sl(t, NULL);
1223 updating_sl_tabs = 0;
1228 xtp_page_ab(struct tab *t, struct karg *args)
1230 char *page, *body;
1232 if (t == NULL) {
1233 show_oops(NULL, "about invalid parameters");
1234 return (-1);
1237 generate_xtp_session_key(&t->session_key);
1239 body = g_strdup_printf("<b>Version: %s</b>"
1240 #ifdef XOMBRERO_BUILDSTR
1241 "<br><b>Build: %s</b>"
1242 #endif
1243 "<br><b>WebKit: %d.%d.%d</b>"
1244 "<br><b>User Agent: %d.%d</b>"
1245 #ifdef WEBKITGTK_API_VERSION
1246 "<br><b>WebKit API: %.1f</b>"
1247 #endif
1248 "<br><b>Configuration: %s" PS "<a href='%s%d/%s/%d'>.%s</a>"
1249 " (remember to restart the browser after any changes)</b>"
1250 "<p>"
1251 "Authors:"
1252 "<ul>"
1253 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1254 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1255 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
1256 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
1257 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
1258 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
1259 "<li>Josh Rickmar &lt;jrick@devio.us&gt;</li>"
1260 "<li>David Hill &lt;dhill@mindcry.org&gt;</li>"
1261 "</ul>"
1262 "Copyrights and licenses can be found on the xombrero "
1263 "<a href=\"https://opensource.conformal.com/wiki/xombrero\">website</a>"
1264 "</p>",
1265 #ifdef XOMBRERO_BUILDSTR
1266 version, XOMBRERO_BUILDSTR,
1267 #else
1268 version,
1269 #endif
1270 WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION,
1271 WEBKIT_USER_AGENT_MAJOR_VERSION, WEBKIT_USER_AGENT_MINOR_VERSION
1272 #ifdef WEBKITGTK_API_VERSION
1273 ,WEBKITGTK_API_VERSION
1274 #endif
1275 ,pwd->pw_dir,
1276 XT_XTP_STR,
1277 XT_XTP_AB,
1278 t->session_key ? t->session_key : "",
1279 XT_XTP_AB_EDIT_CONF,
1280 XT_CONF_FILE
1283 page = get_html_page("About", body, "", 0);
1284 g_free(body);
1286 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT, 0);
1288 g_free(page);
1290 return (0);
1293 /* show a list of favorites (bookmarks) */
1295 xtp_page_fl(struct tab *t, struct karg *args)
1297 char file[PATH_MAX];
1298 FILE *f;
1299 char *uri = NULL, *title = NULL;
1300 size_t len, lineno = 0;
1301 int i, failed = 0;
1302 char *body, *tmp, *page = NULL;
1303 const char delim[3] = {'\\', '\\', '\0'};
1305 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1307 if (t == NULL) {
1308 show_oops(NULL, "%s: bad param", __func__);
1309 return (-1);
1312 generate_xtp_session_key(&t->session_key);
1314 /* open favorites */
1315 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
1316 if ((f = fopen(file, "r")) == NULL) {
1317 show_oops(t, "Can't open favorites file: %s", strerror(errno));
1318 return (1);
1321 /* body */
1322 if (args && args->i & XT_DELETE)
1323 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1324 "<th style='width: 40px'>&#35;</th><th>Link</th>"
1325 "<th style='width: 40px'>Rm</th></tr>\n");
1326 else
1327 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1328 "<th style='width: 40px'>&#35;</th><th>Link</th></tr>\n");
1330 for (i = 1;;) {
1331 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1332 break;
1333 if (strlen(title) == 0) {
1334 free(title);
1335 title = NULL;
1336 continue;
1339 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1340 if (feof(f) || ferror(f)) {
1341 show_oops(t, "favorites file corrupt");
1342 failed = 1;
1343 break;
1346 tmp = body;
1347 if (args && args->i & XT_DELETE)
1348 body = g_strdup_printf("%s<tr>"
1349 "<td>%d</td>"
1350 "<td><a href='%s'>%s</a></td>"
1351 "<td style='text-align: center'>"
1352 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1353 "</tr>\n",
1354 body, i, uri, title,
1355 XT_XTP_STR, XT_XTP_FL,
1356 t->session_key ? t->session_key : "",
1357 XT_XTP_FL_REMOVE, i);
1358 else
1359 body = g_strdup_printf("%s<tr>"
1360 "<td>%d</td>"
1361 "<td><a href='%s'>%s</a></td>"
1362 "</tr>\n",
1363 body, i, uri, title);
1364 g_free(tmp);
1366 free(uri);
1367 uri = NULL;
1368 free(title);
1369 title = NULL;
1370 i++;
1372 fclose(f);
1374 /* if none, say so */
1375 if (i == 1) {
1376 tmp = body;
1377 body = g_strdup_printf("%s<tr>"
1378 "<td colspan='%d' style='text-align: center'>"
1379 "No favorites - To add one use the 'favadd' command."
1380 "</td></tr>", body, (args && args->i & XT_DELETE) ? 3 : 2);
1381 g_free(tmp);
1384 tmp = body;
1385 body = g_strdup_printf("%s</table>", body);
1386 g_free(tmp);
1388 if (uri)
1389 free(uri);
1390 if (title)
1391 free(title);
1393 /* render */
1394 if (!failed) {
1395 page = get_html_page("Favorites", body, "", 1);
1396 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES, 0);
1397 g_free(page);
1400 update_favorite_tabs(t);
1402 if (body)
1403 g_free(body);
1405 return (failed);
1409 * Return a new string with a download row (in html)
1410 * appended. Old string is freed.
1412 char *
1413 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1416 WebKitDownloadStatus stat;
1417 const gchar *destination;
1418 gchar *d;
1419 char *status_html = NULL, *cmd_html = NULL, *new_html;
1420 gdouble progress;
1421 char cur_sz[FMT_SCALED_STRSIZE];
1422 char tot_sz[FMT_SCALED_STRSIZE];
1423 char *xtp_prefix;
1425 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1427 /* All actions wil take this form:
1428 * xxxt://class/seskey
1430 xtp_prefix = g_strdup_printf("%s%d/%s/",
1431 XT_XTP_STR, XT_XTP_DL, t->session_key);
1433 stat = webkit_download_get_status(dl->download);
1435 switch (stat) {
1436 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1437 status_html = g_strdup_printf("Finished");
1438 cmd_html = g_strdup_printf(
1439 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1440 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1441 XT_XTP_DL_UNLINK, dl->id);
1442 break;
1443 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1444 /* gather size info */
1445 progress = 100 * webkit_download_get_progress(dl->download);
1447 fmt_scaled(
1448 webkit_download_get_current_size(dl->download), cur_sz);
1449 fmt_scaled(
1450 webkit_download_get_total_size(dl->download), tot_sz);
1452 status_html = g_strdup_printf(
1453 "<div style='width: 100%%' align='center'>"
1454 "<div class='progress-outer'>"
1455 "<div class='progress-inner' style='width: %.2f%%'>"
1456 "</div></div></div>"
1457 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1458 progress, cur_sz, tot_sz, progress);
1460 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1461 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1463 break;
1464 /* LLL */
1465 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1466 status_html = g_strdup_printf("Cancelled");
1467 cmd_html = g_strdup_printf(
1468 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1469 xtp_prefix, XT_XTP_DL_START, dl->id,
1470 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1471 XT_XTP_DL_UNLINK, dl->id);
1472 break;
1473 case WEBKIT_DOWNLOAD_STATUS_ERROR:
1474 status_html = g_strdup_printf("Error!");
1475 cmd_html = g_strdup_printf(
1476 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1477 xtp_prefix, XT_XTP_DL_START, dl->id,
1478 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1479 XT_XTP_DL_UNLINK, dl->id);
1480 break;
1481 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1482 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1483 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1484 XT_XTP_DL_CANCEL, dl->id);
1485 status_html = g_strdup_printf("Created");
1486 break;
1487 default:
1488 show_oops(t, "%s: unknown download status", __func__);
1491 destination = webkit_download_get_destination_uri(dl->download);
1492 /* we might not have a destination set yet */
1493 if (!destination)
1494 destination = webkit_download_get_suggested_filename(dl->download);
1495 d = g_strdup(destination); /* copy for basename */
1496 new_html = g_strdup_printf(
1497 "%s\n<tr><td>%s</td><td>%s</td>"
1498 "<td style='text-align:center'>%s</td></tr>\n",
1499 html, basename(d), status_html, cmd_html);
1500 g_free(d);
1501 g_free(html);
1503 if (status_html)
1504 g_free(status_html);
1506 if (cmd_html)
1507 g_free(cmd_html);
1509 g_free(xtp_prefix);
1511 return new_html;
1514 /* cookie management XTP page */
1516 xtp_page_cl(struct tab *t, struct karg *args)
1518 char *body, *page, *tmp;
1519 int i = 1; /* all ids start 1 */
1520 int domain_id = 0;
1521 GSList *sc, *pc, *pc_start;
1522 SoupCookie *c;
1523 char *type, *table_headers, *last_domain;
1525 DNPRINTF(XT_D_CMD, "%s", __func__);
1527 if (t == NULL) {
1528 show_oops(NULL, "%s invalid parameters", __func__);
1529 return (1);
1532 generate_xtp_session_key(&t->session_key);
1534 /* table headers */
1535 table_headers = g_strdup_printf("<table><tr>"
1536 "<th>Type</th>"
1537 "<th>Name</th>"
1538 "<th style='width:200px'>Value</th>"
1539 "<th>Path</th>"
1540 "<th>Expires</th>"
1541 "<th>Secure</th>"
1542 "<th>HTTP<br />only</th>"
1543 "<th style='width:40px'>Rm</th></tr>\n");
1545 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1546 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1547 pc_start = pc;
1549 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1550 "[ Remove All Cookies From All Domains ]</a></div>\n",
1551 XT_XTP_STR, XT_XTP_CL, t->session_key, XT_XTP_CL_REMOVE_ALL);
1553 last_domain = g_strdup("");
1554 for (; sc; sc = sc->next) {
1555 c = sc->data;
1557 if (strcmp(last_domain, c->domain) != 0) {
1558 /* new domain */
1559 domain_id ++;
1560 g_free(last_domain);
1561 last_domain = g_strdup(c->domain);
1563 if (body != NULL) {
1564 tmp = body;
1565 body = g_strdup_printf("%s</table>"
1566 "<h2>%s</h2><div align=\"center\">"
1567 "<a href='%s%d/%s/%d/%d'>"
1568 "[ Remove All From This Domain ]"
1569 "</a></div>%s\n",
1570 body, c->domain,
1571 XT_XTP_STR, XT_XTP_CL, t->session_key,
1572 XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1573 table_headers);
1574 g_free(tmp);
1575 } else {
1576 /* first domain */
1577 body = g_strdup_printf("<h2>%s</h2>"
1578 "<div align=\"center\">"
1579 "<a href='%s%d/%s/%d/%d'>"
1580 "[ Remove All From This Domain ]</a></div>%s\n",
1581 c->domain, XT_XTP_STR, XT_XTP_CL,
1582 t->session_key, XT_XTP_CL_REMOVE_DOMAIN,
1583 domain_id, table_headers);
1587 type = "Session";
1588 for (pc = pc_start; pc; pc = pc->next)
1589 if (soup_cookie_equal(pc->data, c)) {
1590 type = "Session + Persistent";
1591 break;
1594 tmp = body;
1595 body = g_strdup_printf(
1596 "%s\n<tr>"
1597 "<td>%s</td>"
1598 "<td style='word-wrap:normal'>%s</td>"
1599 "<td>"
1600 " <textarea rows='4'>%s</textarea>"
1601 "</td>"
1602 "<td>%s</td>"
1603 "<td>%s</td>"
1604 "<td>%d</td>"
1605 "<td>%d</td>"
1606 "<td style='text-align:center'>"
1607 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1608 body,
1609 type,
1610 c->name,
1611 c->value,
1612 c->path,
1613 c->expires ?
1614 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1615 c->secure,
1616 c->http_only,
1618 XT_XTP_STR,
1619 XT_XTP_CL,
1620 t->session_key,
1621 XT_XTP_CL_REMOVE,
1625 g_free(tmp);
1626 i++;
1629 soup_cookies_free(sc);
1630 soup_cookies_free(pc);
1632 /* small message if there are none */
1633 if (i == 1) {
1634 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1635 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1637 tmp = body;
1638 body = g_strdup_printf("%s</table>", body);
1639 g_free(tmp);
1641 page = get_html_page("Cookie Jar", body, "", TRUE);
1642 g_free(body);
1643 g_free(table_headers);
1644 g_free(last_domain);
1646 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR, 0);
1647 update_cookie_tabs(t);
1649 g_free(page);
1651 return (0);
1655 xtp_page_hl(struct tab *t, struct karg *args)
1657 char *body, *page, *tmp;
1658 struct history *h;
1659 int i = 1; /* all ids start 1 */
1661 DNPRINTF(XT_D_CMD, "%s", __func__);
1663 if (t == NULL) {
1664 show_oops(NULL, "%s invalid parameters", __func__);
1665 return (1);
1668 generate_xtp_session_key(&t->session_key);
1670 /* body */
1671 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1672 "[ Remove All ]</a></div>"
1673 "<table style='table-layout:fixed'><tr>"
1674 "<th>URI</th><th>Title</th><th>Last visited</th>"
1675 "<th style='width: 40px'>Rm</th></tr>\n",
1676 XT_XTP_STR, XT_XTP_HL, t->session_key, XT_XTP_HL_REMOVE_ALL);
1678 RB_FOREACH_REVERSE(h, history_list, &hl) {
1679 tmp = body;
1680 body = g_strdup_printf(
1681 "%s\n<tr>"
1682 "<td><a href='%s'>%s</a></td>"
1683 "<td>%s</td>"
1684 "<td>%s</td>"
1685 "<td style='text-align: center'>"
1686 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1687 body, h->uri, h->uri, h->title, ctime(&h->time),
1688 XT_XTP_STR, XT_XTP_HL, t->session_key,
1689 XT_XTP_HL_REMOVE, i);
1691 g_free(tmp);
1692 i++;
1695 /* small message if there are none */
1696 if (i == 1) {
1697 tmp = body;
1698 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1699 "colspan='4'>No History</td></tr>\n", body);
1700 g_free(tmp);
1703 tmp = body;
1704 body = g_strdup_printf("%s</table>", body);
1705 g_free(tmp);
1707 page = get_html_page("History", body, "", TRUE);
1708 g_free(body);
1711 * update all history manager tabs as the xtp session
1712 * key has now changed. No need to update the current tab.
1713 * Already did that above.
1715 update_history_tabs(t);
1717 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY, 0);
1718 g_free(page);
1720 return (0);
1724 * Generate a web page detailing the status of any downloads
1727 xtp_page_dl(struct tab *t, struct karg *args)
1729 struct download *dl;
1730 char *body, *page, *tmp;
1731 char *ref;
1732 int n_dl = 1;
1734 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1736 if (t == NULL) {
1737 show_oops(NULL, "%s invalid parameters", __func__);
1738 return (1);
1741 generate_xtp_session_key(&t->session_key);
1743 /* header - with refresh so as to update */
1744 if (refresh_interval >= 1)
1745 ref = g_strdup_printf(
1746 "<meta http-equiv='refresh' content='%u"
1747 ";url=%s%d/%s/%d' />\n",
1748 refresh_interval,
1749 XT_XTP_STR,
1750 XT_XTP_DL,
1751 t->session_key,
1752 XT_XTP_DL_LIST);
1753 else
1754 ref = g_strdup("");
1756 body = g_strdup_printf("<div align='center'>"
1757 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1758 "</p><table><tr><th style='width: 60%%'>"
1759 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1760 XT_XTP_STR, XT_XTP_DL, t->session_key, XT_XTP_DL_LIST);
1762 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1763 body = xtp_page_dl_row(t, body, dl);
1764 n_dl++;
1767 /* message if no downloads in list */
1768 if (n_dl == 1) {
1769 tmp = body;
1770 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1771 " style='text-align: center'>"
1772 "No downloads</td></tr>\n", body);
1773 g_free(tmp);
1776 tmp = body;
1777 body = g_strdup_printf("%s</table></div>", body);
1778 g_free(tmp);
1780 page = get_html_page("Downloads", body, ref, 1);
1781 g_free(ref);
1782 g_free(body);
1785 * update all download manager tabs as the xtp session
1786 * key has now changed. No need to update the current tab.
1787 * Already did that above.
1789 update_download_tabs(t);
1791 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS, 0);
1792 g_free(page);
1794 return (0);
1798 xtp_page_sl(struct tab *t, struct karg *args)
1800 int i;
1801 char *page, *body, *tmp;
1803 DNPRINTF(XT_D_SEARCH, "%s", __func__);
1805 generate_xtp_session_key(&t->session_key);
1807 if (t == NULL) {
1808 show_oops(NULL, "%s invalid parameters", __func__);
1809 return (1);
1812 body = g_strdup_printf("<p>The xombrero authors will not choose a "
1813 "default search engine for you. What follows is a list of search "
1814 "engines (in no particular order) you may be interested in. "
1815 "To permanently choose a search engine, click [ Select ] to save "
1816 "<tt>search_string</tt> as a runtime setting, or set "
1817 "<tt>search_string</tt> to the appropriate URL in your xombrero "
1818 "configuration.</p>");
1820 tmp = body;
1821 body = g_strdup_printf("%s\n<table style='table-layout:fixed'><tr>"
1822 "<th style='width: 200px'>Name</th><th>URL</th>"
1823 "<th style='width: 100px'>Select</th></tr>\n", body);
1824 g_free(tmp);
1826 for (i = 0; i < (sizeof search_list / sizeof (struct search_type)); ++i)
1827 body = search_engine_add(body, search_list[i].name,
1828 search_list[i].url, t->session_key, i);
1830 tmp = body;
1831 body = g_strdup_printf("%s</table>", body);
1832 g_free(tmp);
1834 page = get_html_page("Choose a search engine", body, "", 1);
1835 g_free(body);
1838 * update all search tabs as the xtp session key has now changed. No
1839 * need to update the current tab. Already did that above.
1841 update_search_tabs(t);
1843 load_webkit_string(t, page, XT_URI_ABOUT_SEARCH, 0);
1844 g_free(page);
1846 return (0);
1850 xtp_page_sv(struct tab *t, struct karg *args)
1852 SoupURI *soupuri;
1853 static int arg = 0;
1854 struct secviolation find, *sv;
1855 char *page, *body;
1857 if (t == NULL) {
1858 show_oops(NULL, "secviolation invalid parameters");
1859 return (-1);
1862 generate_xtp_session_key(&t->session_key);
1864 if (args == NULL) {
1865 find.xtp_arg = t->xtp_arg;
1866 sv = RB_FIND(secviolation_list, &svl, &find);
1867 if (sv == NULL)
1868 return (-1);
1869 } else {
1870 sv = g_malloc(sizeof(struct secviolation));
1871 sv->xtp_arg = ++arg;
1872 t->xtp_arg = arg;
1873 sv->t = t;
1874 sv->uri = args->s;
1875 RB_INSERT(secviolation_list, &svl, sv);
1878 if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
1879 return (-1);
1881 body = g_strdup_printf(
1882 "<p><b>You tried to access %s</b>."
1883 "<p><b>The site's security certificate has been modified.</b>"
1884 "<p>The domain of the page you have tried to access, <b>%s</b>, "
1885 "has a different remote certificate then the local cached version "
1886 "from a previous visit. As a security precaution to help prevent "
1887 "against man-in-the-middle attacks, please choose one of the "
1888 "following actions to continue, or disable the "
1889 "<tt>warn_cert_changes</tt> setting in your xombrero "
1890 "configuration."
1891 "<p><b>Choose an action:"
1892 "<br><a href='%s%d/%s/%d/%d'>Allow for this session</a>"
1893 "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>"
1894 "<br><a href='%s%d/%s/%d/%d'>Show cached certificate</a>"
1895 "<br><a href='%s%d/%s/%d/%d'>Show new certificate</a>",
1896 sv->uri,
1897 soupuri->host,
1898 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_ALLOW_SESSION,
1899 sv->xtp_arg,
1900 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_CACHE,
1901 sv->xtp_arg,
1902 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_CACHED_CERT,
1903 sv->xtp_arg,
1904 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_NEW_CERT,
1905 sv->xtp_arg);
1907 page = get_html_page("Security Violation", body, "", 0);
1908 g_free(body);
1910 load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION, 1);
1912 g_free(page);
1913 if (soupuri)
1914 soup_uri_free(soupuri);
1916 return (0);
1920 startpage(struct tab *t, struct karg *args)
1922 char *page, *body, *b;
1923 struct sp *s;
1925 if (t == NULL)
1926 show_oops(NULL, "startpage invalid parameters");
1928 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1930 TAILQ_FOREACH(s, &spl, entry) {
1931 b = body;
1932 body = g_strdup_printf("%s%s<br>", body, s->line);
1933 g_free(b);
1936 page = get_html_page("Startup Exception", body, "", 0);
1937 g_free(body);
1939 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE, 0);
1940 g_free(page);
1942 return (0);
1945 void
1946 startpage_add(const char *fmt, ...)
1948 va_list ap;
1949 char *msg;
1950 struct sp *s;
1952 if (fmt == NULL)
1953 return;
1955 va_start(ap, fmt);
1956 if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
1957 errx(1, "startpage_add failed");
1958 va_end(ap);
1960 s = g_malloc0(sizeof *s);
1961 s->line = msg;
1963 TAILQ_INSERT_TAIL(&spl, s, entry);
1965 gchar *show_g_object_settings(GObject *, char *, int);
1967 char *
1968 xt_g_object_serialize(GValue *value, const gchar *tname, char *str, int recurse)
1970 int typeno = 0;
1971 char *valstr, *tmpstr, *tmpsettings;
1972 GObject *object;
1974 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(value) );
1975 switch ( typeno ) {
1976 case G_TYPE_ENUM:
1977 valstr = g_strdup_printf("%d",
1978 g_value_get_enum(value));
1979 break;
1980 case G_TYPE_CHAR:
1981 valstr = g_strdup_printf("%c",
1982 #if GLIB_CHECK_VERSION(2, 32, 0)
1983 g_value_get_schar(value));
1984 #else
1985 g_value_get_char(value));
1986 #endif
1987 break;
1988 case G_TYPE_UCHAR:
1989 valstr = g_strdup_printf("%c",
1990 g_value_get_uchar(value));
1991 break;
1992 case G_TYPE_LONG:
1993 valstr = g_strdup_printf("%ld",
1994 g_value_get_long(value));
1995 break;
1996 case G_TYPE_ULONG:
1997 valstr = g_strdup_printf("%ld",
1998 g_value_get_ulong(value));
1999 break;
2000 case G_TYPE_INT:
2001 valstr = g_strdup_printf("%d",
2002 g_value_get_int(value));
2003 break;
2004 case G_TYPE_INT64:
2005 valstr = g_strdup_printf("%" PRIo64,
2006 (int64_t) g_value_get_int64(value));
2007 break;
2008 case G_TYPE_UINT:
2009 valstr = g_strdup_printf("%d",
2010 g_value_get_uint(value));
2011 break;
2012 case G_TYPE_UINT64:
2013 valstr = g_strdup_printf("%" PRIu64,
2014 (uint64_t) g_value_get_uint64(value));
2015 break;
2016 case G_TYPE_FLAGS:
2017 valstr = g_strdup_printf("0x%x",
2018 g_value_get_flags(value));
2019 break;
2020 case G_TYPE_BOOLEAN:
2021 valstr = g_strdup_printf("%s",
2022 g_value_get_boolean(value) ? "TRUE" : "FALSE");
2023 break;
2024 case G_TYPE_FLOAT:
2025 valstr = g_strdup_printf("%f",
2026 g_value_get_float(value));
2027 break;
2028 case G_TYPE_DOUBLE:
2029 valstr = g_strdup_printf("%f",
2030 g_value_get_double(value));
2031 break;
2032 case G_TYPE_STRING:
2033 valstr = g_strdup_printf("\"%s\"",
2034 g_value_get_string(value));
2035 break;
2036 case G_TYPE_POINTER:
2037 valstr = g_strdup_printf("%p",
2038 g_value_get_pointer(value));
2039 break;
2040 case G_TYPE_OBJECT:
2041 object = g_value_get_object(value);
2042 if (object != NULL) {
2043 if (recurse) {
2044 tmpstr = g_strdup_printf("%s ", str);
2045 tmpsettings = show_g_object_settings( object,
2046 tmpstr, recurse);
2047 g_free(tmpstr);
2049 if (strrchr(tmpsettings, '\n') != NULL) {
2050 valstr = g_strdup_printf("%s%s }",
2051 tmpsettings, str);
2052 g_free(tmpsettings);
2053 } else {
2054 valstr = tmpsettings;
2056 } else {
2057 valstr = g_strdup_printf("<...>");
2059 } else {
2060 valstr = g_strdup_printf("settings[] = NULL");
2062 break;
2063 default:
2064 valstr = g_strdup_printf("type %s unhandled", tname);
2066 return valstr;
2069 gchar *
2070 show_g_object_settings(GObject *o, char *str, int recurse)
2072 char *b, *p, *body, *valstr, *tmpstr;
2073 guint n_props = 0;
2074 int i, typeno = 0;
2075 GParamSpec *pspec;
2076 const gchar *tname;
2077 GValue value;
2078 GParamSpec **proplist;
2079 const gchar *name;
2081 if (!G_IS_OBJECT(o)) {
2082 fprintf(stderr, "%s is not a g_object\n", str);
2083 return g_strdup("");
2085 proplist = g_object_class_list_properties(
2086 G_OBJECT_GET_CLASS(o), &n_props);
2088 if (GTK_IS_WIDGET(o)) {
2089 name = gtk_widget_get_name(GTK_WIDGET(o));
2090 } else {
2091 name = "settings";
2093 if (n_props == 0) {
2094 body = g_strdup_printf("%s[0] = { }", name);
2095 goto end_show_g_objects;
2098 body = g_strdup_printf("%s[%d] = {\n", name, n_props);
2099 for (i=0; i < n_props; i++) {
2100 pspec = proplist[i];
2101 tname = G_OBJECT_TYPE_NAME(pspec);
2102 bzero(&value, sizeof value);
2103 valstr = NULL;
2105 if (!(pspec->flags & G_PARAM_READABLE))
2106 valstr = g_strdup_printf("not a readable property");
2107 else {
2108 g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
2109 g_object_get_property(G_OBJECT(o), pspec->name,
2110 &value);
2111 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(&value) );
2114 /* based on the type, recurse and display values */
2115 if (valstr == NULL) {
2116 valstr = xt_g_object_serialize(&value, tname, str,
2117 recurse);
2120 tmpstr = g_strdup_printf("%-13s %s%s%s,", tname, pspec->name,
2121 (typeno == G_TYPE_OBJECT) ? "." : " = ", valstr);
2122 b = body;
2124 #define XT_G_OBJECT_SPACING 50
2125 p = strrchr(tmpstr, '\n');
2126 if (p == NULL && strlen(tmpstr) > XT_G_OBJECT_SPACING) {
2127 body = g_strdup_printf(
2128 "%s%s %-50s\n%s %50s /* %3d flags=0x%08x */\n",
2129 body, str, tmpstr, str, "", i, pspec->flags);
2130 } else {
2131 char *fmt;
2132 int strspaces;
2133 if (p == NULL)
2134 strspaces = XT_G_OBJECT_SPACING;
2135 else
2136 strspaces = strlen(tmpstr) - (strlen(p) - strlen(str)) + XT_G_OBJECT_SPACING + 5;
2137 fmt = g_strdup_printf("%%s%%s %%-%ds /* %%3d flags=0x%%08x */\n", strspaces);
2138 body = g_strdup_printf(fmt, body, str, tmpstr, i, pspec->flags);
2139 g_free(fmt);
2141 g_free(tmpstr);
2142 g_free(b);
2143 g_free(valstr);
2145 end_show_g_objects:
2146 g_free(proplist);
2147 return (body);
2150 char *
2151 xt_append_settings(char *str, GObject *object, char *name, int recurse)
2153 char *newstr, *settings;
2155 settings = show_g_object_settings(object, name, recurse);
2156 if (str == NULL)
2157 str = g_strdup("");
2159 newstr = g_strdup_printf("%s%s %s%s };\n", str, name, settings, name);
2160 g_free(str);
2162 return newstr;
2166 about_webkit(struct tab *t, struct karg *arg)
2168 char *page, *body, *settingstr;
2170 settingstr = xt_append_settings(NULL, G_OBJECT(t->settings),
2171 "t->settings", 0);
2172 body = g_strdup_printf("<pre>%s</pre>\n", settingstr);
2173 g_free(settingstr);
2175 page = get_html_page("About Webkit", body, "", 0);
2176 g_free(body);
2178 load_webkit_string(t, page, XT_URI_ABOUT_WEBKIT, 0);
2179 g_free(page);
2181 return (0);
2184 static int toplevelcount = 0;
2186 void
2187 xt_append_toplevel(GtkWindow *w, char **body)
2189 char *n;
2191 n = g_strdup_printf("toplevel#%d", toplevelcount++);
2192 *body = xt_append_settings(*body, G_OBJECT(w), n, 0);
2193 g_free(n);
2197 allthethings(struct tab *t, struct karg *arg)
2199 GList *list;
2200 char *page, *body, *b;
2202 body = xt_append_settings(NULL, G_OBJECT(t->wv), "t->wv", 1);
2203 body = xt_append_settings(body, G_OBJECT(t->inspector),
2204 "t->inspector", 1);
2205 #if 0 /* not until warnings are gone */
2206 body = xt_append_settings(body, G_OBJECT(session),
2207 "session", 1);
2208 #endif
2209 toplevelcount = 0;
2210 list = gtk_window_list_toplevels();
2211 g_list_foreach(list, (GFunc)g_object_ref, NULL);
2212 g_list_foreach(list, (GFunc)xt_append_toplevel, &body);
2213 g_list_foreach(list, (GFunc)g_object_unref, NULL);
2214 g_list_free(list);
2216 b = body;
2217 body = g_strdup_printf("<pre>%scan paste clipboard = %d\n</pre>", body,
2218 webkit_web_view_can_paste_clipboard(t->wv));
2219 g_free(b);
2221 page = get_html_page("About All The Things _o/", body, "", 0);
2222 g_free(body);
2224 load_webkit_string(t, page, XT_URI_ABOUT_ALLTHETHINGS, 0);
2225 g_free(page);
2227 return (0);