xtp-meaning freed memory usage fix
[xombrero.git] / about.c
blobe6d688ea469bee6d5c379c377801244b55165601
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, xtp_meaning;
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 xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
198 if (title) {
199 /* set xtp_meaning */
200 for (i = 0; i < LENGTH(about_list); i++)
201 if (!strcmp(title, about_list[i].name)) {
202 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 set_xtp_meaning(t, xtp_meaning);
218 t->progress_handle = g_signal_connect(t->wv,
219 "notify::progress", G_CALLBACK(webview_progress_changed_cb), t);
223 blank(struct tab *t, struct karg *args)
225 if (t == NULL)
226 show_oops(NULL, "blank invalid parameters");
228 load_webkit_string(t, "", XT_URI_ABOUT_BLANK, 0);
230 return (0);
234 help(struct tab *t, struct karg *args)
236 char *page, *head, *body;
238 if (t == NULL)
239 show_oops(NULL, "help invalid parameters");
241 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
242 "url=https://opensource.conformal.com/cgi-bin/man-cgi?xombrero\">"
243 "</head>\n";
244 body = "xombrero man page <a href=\"https://opensource.conformal.com/"
245 "cgi-bin/man-cgi?xombrero\">https://opensource.conformal.com/"
246 "cgi-bin/man-cgi?xombrero</a>";
248 page = get_html_page(XT_NAME, body, head, FALSE);
250 load_webkit_string(t, page, XT_URI_ABOUT_HELP, 0);
251 g_free(page);
253 return (0);
257 stats(struct tab *t, struct karg *args)
259 char *page, *body, *s, line[64 * 1024];
260 uint64_t line_count = 0;
261 FILE *r_cookie_f;
263 if (t == NULL)
264 show_oops(NULL, "stats invalid parameters");
266 line[0] = '\0';
267 if (save_rejected_cookies) {
268 if ((r_cookie_f = fopen(rc_fname, "r"))) {
269 for (;;) {
270 s = fgets(line, sizeof line, r_cookie_f);
271 if (s == NULL || feof(r_cookie_f) ||
272 ferror(r_cookie_f))
273 break;
274 line_count++;
276 fclose(r_cookie_f);
277 snprintf(line, sizeof line,
278 "<br/>Cookies blocked(*) total: %" PRIu64,
279 line_count);
280 } else
281 show_oops(t, "Can't open blocked cookies file: %s",
282 strerror(errno));
285 body = g_strdup_printf(
286 "Cookies blocked(*) this session: %" PRIu64
287 "%s"
288 "<p><small><b>*</b> results vary based on settings</small></p>",
289 blocked_cookies,
290 line);
292 page = get_html_page("Statistics", body, "", 0);
293 g_free(body);
295 load_webkit_string(t, page, XT_URI_ABOUT_STATS, 0);
296 g_free(page);
298 return (0);
301 void
302 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
303 size_t cert_count, char *title)
305 gnutls_datum_t *cinfo;
306 char *tmp, *body;
307 int i;
309 body = g_strdup("");
311 for (i = 0; i < cert_count; i++) {
312 cinfo = gnutls_malloc(sizeof *cinfo);
313 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
314 cinfo)) {
315 gnutls_free(cinfo);
316 g_free(body);
317 return;
320 tmp = body;
321 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
322 body, i, cinfo->data);
323 gnutls_free(cinfo);
324 g_free(tmp);
327 tmp = get_html_page(title, body, "", 0);
328 g_free(body);
330 load_webkit_string(t, tmp, XT_URI_ABOUT_CERTS, 0);
331 g_free(tmp);
335 ca_cmd(struct tab *t, struct karg *args)
337 FILE *f = NULL;
338 int rv = 1, certs_read;
339 unsigned int certs = 0;
340 struct stat sb;
341 gnutls_datum_t dt;
342 gnutls_x509_crt_t *c = NULL;
343 char *certs_buf = NULL, *s;
345 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
346 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
347 return (1);
350 if (fstat(fileno(f), &sb) == -1) {
351 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
352 goto done;
355 certs_buf = g_malloc(sb.st_size + 1);
356 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
357 show_oops(t, "Can't read CA file: %s", strerror(errno));
358 goto done;
360 certs_buf[sb.st_size] = '\0';
362 s = certs_buf;
363 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
364 certs++;
365 s += strlen("BEGIN CERTIFICATE");
368 bzero(&dt, sizeof dt);
369 dt.data = (unsigned char *)certs_buf;
370 dt.size = sb.st_size;
371 c = gnutls_malloc(sizeof(*c) * certs);
372 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
373 GNUTLS_X509_FMT_PEM, 0);
374 if (certs_read <= 0) {
375 show_oops(t, "No cert(s) available");
376 goto done;
378 show_certs(t, c, certs_read, "Certificate Authority Certificates");
379 done:
380 if (c)
381 gnutls_free(c);
382 if (certs_buf)
383 g_free(certs_buf);
384 if (f)
385 fclose(f);
387 return (rv);
391 cookie_show_wl(struct tab *t, struct karg *args)
393 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
394 wl_show(t, args, "Cookie White List", &c_wl);
396 return (0);
400 js_show_wl(struct tab *t, struct karg *args)
402 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
403 wl_show(t, args, "JavaScript White List", &js_wl);
405 return (0);
409 cookie_cmd(struct tab *t, struct karg *args)
411 if (args->i & XT_SHOW)
412 wl_show(t, args, "Cookie White List", &c_wl);
413 else if (args->i & XT_WL_TOGGLE) {
414 args->i |= XT_WL_RELOAD;
415 toggle_cwl(t, args);
416 } else if (args->i & XT_SAVE) {
417 args->i |= XT_WL_RELOAD;
418 wl_save(t, args, XT_WL_COOKIE);
419 } else if (args->i & XT_DELETE) {
420 remove_cookie_all();
421 update_cookie_tabs(NULL);
424 return (0);
428 js_cmd(struct tab *t, struct karg *args)
430 if (args->i & XT_SHOW)
431 wl_show(t, args, "JavaScript White List", &js_wl);
432 else if (args->i & XT_SAVE) {
433 args->i |= XT_WL_RELOAD;
434 wl_save(t, args, XT_WL_JAVASCRIPT);
435 } else if (args->i & XT_WL_TOGGLE) {
436 args->i |= XT_WL_RELOAD;
437 toggle_js(t, args);
438 } else if (args->i & XT_DELETE)
439 show_oops(t, "'js delete' currently unimplemented");
441 return (0);
445 pl_show_wl(struct tab *t, struct karg *args)
447 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
448 wl_show(t, args, "Plugin White List", &pl_wl);
450 return (0);
454 pl_cmd(struct tab *t, struct karg *args)
456 if (args->i & XT_SHOW)
457 wl_show(t, args, "Plugin White List", &pl_wl);
458 else if (args->i & XT_SAVE) {
459 args->i |= XT_WL_RELOAD;
460 wl_save(t, args, XT_WL_PLUGIN);
461 } else if (args->i & XT_WL_TOGGLE) {
462 args->i |= XT_WL_RELOAD;
463 toggle_pl(t, args);
464 } else if (args->i & XT_DELETE)
465 show_oops(t, "'plugin delete' currently unimplemented");
467 return (0);
471 https_show_wl(struct tab *t, struct karg *args)
473 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
474 wl_show(t, args, "HTTPS Force List", &force_https);
476 return (0);
480 https_cmd(struct tab *t, struct karg *args)
482 if (args->i & XT_SHOW)
483 wl_show(t, args, "HTTPS Force List", &force_https);
484 else if (args->i & XT_SAVE) {
485 args->i |= XT_WL_RELOAD;
486 wl_save(t, args, XT_WL_HTTPS);
487 } else if (args->i & XT_WL_TOGGLE) {
488 args->i |= XT_WL_RELOAD;
489 toggle_force_https(t, args);
490 } else if (args->i & XT_DELETE)
491 show_oops(t, "https delete' currently unimplemented");
493 return (0);
497 * cancel, remove, etc. downloads
499 void
500 xtp_handle_dl(struct tab *t, uint8_t cmd, int id, const char *query)
502 struct download find, *d = NULL;
503 #ifndef __MINGW32__
504 char *file = NULL;
505 const char *uri = NULL;
506 #endif
508 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
510 /* some commands require a valid download id */
511 if (cmd != XT_XTP_DL_LIST) {
512 /* lookup download in question */
513 find.id = id;
514 d = RB_FIND(download_list, &downloads, &find);
516 if (d == NULL) {
517 show_oops(t, "%s: no such download", __func__);
518 return;
522 /* decide what to do */
523 switch (cmd) {
524 case XT_XTP_DL_START:
525 /* our downloads always needs to be
526 * restarted if called from here
528 download_start(t, d, XT_DL_RESTART);
529 break;
530 case XT_XTP_DL_CANCEL:
531 webkit_download_cancel(d->download);
532 g_object_unref(d->download);
533 RB_REMOVE(download_list, &downloads, d);
534 break;
535 case XT_XTP_DL_UNLINK:
536 #ifdef __MINGW32__
537 /* XXX uri's aren't handled properly on windows? */
538 unlink(webkit_download_get_destination_uri(d->download));
539 #else
540 uri = webkit_download_get_destination_uri(d->download);
541 if ((file = g_filename_from_uri(uri, NULL, NULL)) != NULL) {
542 unlink(file);
543 g_free(file);
545 #endif
546 /* FALLTHROUGH */
547 case XT_XTP_DL_REMOVE:
548 webkit_download_cancel(d->download); /* just incase */
549 g_object_unref(d->download);
550 RB_REMOVE(download_list, &downloads, d);
551 break;
552 case XT_XTP_DL_LIST:
553 /* Nothing */
554 break;
555 default:
556 show_oops(t, "%s: unknown command", __func__);
557 break;
559 xtp_page_dl(t, NULL);
562 void
563 xtp_handle_hl(struct tab *t, uint8_t cmd, int id, const char *query)
565 struct history *h, *next, *ht;
566 int i = 1;
568 switch (cmd) {
569 case XT_XTP_HL_REMOVE:
570 /* walk backwards, as listed in reverse */
571 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
572 next = RB_PREV(history_list, &hl, h);
573 if (id == i) {
574 RB_REMOVE(history_list, &hl, h);
575 g_free((gpointer) h->title);
576 g_free((gpointer) h->uri);
577 g_free(h);
578 break;
580 i++;
582 break;
583 case XT_XTP_HL_REMOVE_ALL:
584 RB_FOREACH_SAFE(h, history_list, &hl, ht)
585 RB_REMOVE(history_list, &hl, h);
586 break;
587 case XT_XTP_HL_LIST:
588 /* Nothing - just xtp_page_hl() below */
589 break;
590 default:
591 show_oops(t, "%s: unknown command", __func__);
592 break;
595 xtp_page_hl(t, NULL);
598 /* remove a favorite */
599 void
600 remove_favorite(struct tab *t, int index)
602 char file[PATH_MAX], *title, *uri = NULL;
603 char *new_favs, *tmp;
604 FILE *f;
605 int i;
606 size_t len, lineno;
608 /* open favorites */
609 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
611 if ((f = fopen(file, "r")) == NULL) {
612 show_oops(t, "%s: can't open favorites: %s",
613 __func__, strerror(errno));
614 return;
617 /* build a string which will become the new favorites file */
618 new_favs = g_strdup("");
620 for (i = 1;;) {
621 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
622 if (feof(f) || ferror(f))
623 break;
624 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
625 if (len == 0) {
626 free(title);
627 title = NULL;
628 continue;
631 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
632 if (feof(f) || ferror(f)) {
633 show_oops(t, "%s: can't parse favorites %s",
634 __func__, strerror(errno));
635 goto clean;
639 /* as long as this isn't the one we are deleting add to file */
640 if (i != index) {
641 tmp = new_favs;
642 new_favs = g_strdup_printf("%s%s\n%s\n",
643 new_favs, title, uri);
644 g_free(tmp);
647 free(uri);
648 uri = NULL;
649 free(title);
650 title = NULL;
651 i++;
653 fclose(f);
655 /* write back new favorites file */
656 if ((f = fopen(file, "w")) == NULL) {
657 show_oops(t, "%s: can't open favorites: %s",
658 __func__, strerror(errno));
659 goto clean;
662 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
663 show_oops(t, "%s: can't fwrite", __func__);
664 fclose(f);
666 clean:
667 if (uri)
668 free(uri);
669 if (title)
670 free(title);
672 g_free(new_favs);
676 add_favorite(struct tab *t, struct karg *args)
678 char file[PATH_MAX];
679 FILE *f;
680 char *line = NULL;
681 size_t urilen, linelen;
682 gchar *argtitle = NULL;
683 const gchar *uri, *title;
685 if (t == NULL)
686 return (1);
688 /* don't allow adding of xtp pages to favorites */
689 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
690 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
691 return (1);
694 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
695 if ((f = fopen(file, "r+")) == NULL) {
696 show_oops(t, "Can't open favorites file: %s", strerror(errno));
697 return (1);
700 if (args->s && strlen(g_strstrip(args->s)))
701 argtitle = html_escape(g_strstrip(args->s));
703 title = argtitle ? argtitle : get_title(t, FALSE);
704 uri = get_uri(t);
706 if (title == NULL || uri == NULL) {
707 show_oops(t, "can't add page to favorites");
708 goto done;
711 urilen = strlen(uri);
713 for (;;) {
714 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL) {
715 if (feof(f))
716 break;
717 else {
718 show_oops(t, "Error reading favorites file: %s",
719 strerror(errno));
720 goto done;
724 if (linelen == urilen && !strcmp(line, uri))
725 goto done;
727 free(line);
728 line = NULL;
731 fprintf(f, "\n%s\n%s", title, uri);
732 done:
733 if (argtitle)
734 g_free(argtitle);
735 if (line)
736 free(line);
737 fclose(f);
739 update_favorite_tabs(NULL);
741 return (0);
744 char *
745 search_engine_add(char *body, const char *name, const char *url,
746 const char *key, int select)
748 char *b = body;
750 body = g_strdup_printf("%s<tr>"
751 "<td>%s</td>"
752 "<td>%s</td>"
753 "<td style='text-align: center'>"
754 "<a href='%s%d/%s/%d/%d'>[ Select ]</a></td>"
755 "</tr>\n",
756 body,
757 name,
758 url,
759 XT_XTP_STR, XT_XTP_SL, key, XT_XTP_SL_SET, select);
760 g_free(b);
761 return (body);
764 void
765 xtp_handle_ab(struct tab *t, uint8_t cmd, int arg, const char *query)
767 char config[PATH_MAX];
768 char *cmdstr;
769 char **sv;
771 switch (cmd) {
772 case XT_XTP_AB_EDIT_CONF:
773 if (external_editor == NULL || strlen(external_editor) == 0) {
774 show_oops(t, "external_editor is unset");
775 break;
778 snprintf(config, sizeof config, "%s" PS ".%s", pwd->pw_dir,
779 XT_CONF_FILE);
780 sv = g_strsplit(external_editor, "<file>", -1);
781 cmdstr = g_strjoinv(config, sv);
782 g_strfreev(sv);
783 sv = g_strsplit_set(cmdstr, " \t", -1);
785 if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
786 NULL, NULL))
787 show_oops(t, "%s: could not spawn process", __func__);
789 g_strfreev(sv);
790 g_free(cmdstr);
791 break;
792 default:
793 show_oops(t, "%s, invalid about command", __func__);
794 break;
796 xtp_page_ab(t, NULL);
798 void
799 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg, const char *query)
801 struct karg args = {0};
803 switch (cmd) {
804 case XT_XTP_FL_LIST:
805 /* nothing, just the below call to xtp_page_fl() */
806 break;
807 case XT_XTP_FL_REMOVE:
808 remove_favorite(t, arg);
809 args.i = XT_DELETE;
810 break;
811 default:
812 show_oops(t, "%s: invalid favorites command", __func__);
813 break;
816 xtp_page_fl(t, &args);
819 void
820 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg, const char *query)
822 switch (cmd) {
823 case XT_XTP_CL_LIST:
824 /* nothing, just xtp_page_cl() */
825 break;
826 case XT_XTP_CL_REMOVE:
827 remove_cookie(arg);
828 break;
829 case XT_XTP_CL_REMOVE_DOMAIN:
830 remove_cookie_domain(arg);
831 break;
832 case XT_XTP_CL_REMOVE_ALL:
833 remove_cookie_all();
834 break;
835 default:
836 show_oops(t, "%s: unknown cookie xtp command", __func__);
837 break;
840 xtp_page_cl(t, NULL);
843 void
844 xtp_handle_sl(struct tab *t, uint8_t cmd, int arg, const char *query)
846 const char *search;
847 char *enc_search, *uri;
848 char **sv;
850 switch (cmd) {
851 case XT_XTP_SL_SET:
852 set_search_string((char *)search_list[arg].url);
853 if (save_runtime_setting("search_string", search_list[arg].url))
854 show_oops(t, "could not set search_string in runtime");
855 break;
856 default:
857 show_oops(t, "%s: unknown search xtp command", __func__);
858 break;
861 search = gtk_entry_get_text(GTK_ENTRY(t->search_entry)); /* static */
862 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
863 sv = g_strsplit(search_string, "%s", 2);
864 uri = g_strjoinv(enc_search, sv);
865 load_uri(t, uri);
866 g_free(enc_search);
867 g_strfreev(sv);
868 g_free(uri);
871 void
872 xtp_handle_sv(struct tab *t, uint8_t cmd, int id, const char *query)
874 SoupURI *soupuri = NULL;
875 struct karg args = {0};
876 struct secviolation find, *sv;
878 find.xtp_arg = id;
879 if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
880 return;
882 args.ptr = (void *)sv->t;
883 args.s = sv->uri;
885 switch (cmd) {
886 case XT_XTP_SV_SHOW_NEW_CERT:
887 args.i = XT_SHOW;
888 if (cert_cmd(t, &args)) {
889 xtp_page_sv(t, &args);
890 return;
892 break;
893 case XT_XTP_SV_SHOW_CACHED_CERT:
894 args.i = XT_CACHE | XT_SHOW;
895 if (cert_cmd(t, &args)) {
896 xtp_page_sv(t, &args);
897 return;
899 break;
900 case XT_XTP_SV_ALLOW_SESSION:
901 soupuri = soup_uri_new(sv->uri);
902 wl_add(soupuri->host, &svil, 0);
903 load_uri(t, sv->uri);
904 focus_webview(t);
905 break;
906 case XT_XTP_SV_CACHE:
907 args.i = XT_CACHE;
908 if (cert_cmd(t, &args)) {
909 xtp_page_sv(t, &args);
910 return;
912 load_uri(t, sv->uri);
913 focus_webview(t);
914 break;
915 default:
916 show_oops(t, "%s: invalid secviolation command", __func__);
917 break;
920 g_free(sv->uri);
921 if (soupuri)
922 soup_uri_free(soupuri);
923 RB_REMOVE(secviolation_list, &svl, sv);
926 void
927 xtp_handle_rt(struct tab *t, uint8_t cmd, int id, const char *query)
929 struct set_reject *sr;
930 GHashTable *new_settings = NULL;
931 int modify;
932 char *val, *curval, *s;
933 int i = 0;
935 switch (cmd) {
936 case XT_XTP_RT_SAVE:
937 if (query == NULL)
938 break;
939 new_settings = soup_form_decode(query);
940 for (i = 0; i < get_settings_size(); ++i) {
941 if (!rs[i].activate)
942 continue;
943 val = (char *)g_hash_table_lookup(new_settings,
944 rs[i].name);
945 modify = 0;
946 switch (rs[i].type) {
947 case XT_S_INT: /* FALLTHROUGH */
948 case XT_S_BOOL:
949 if (atoi(val) != *rs[i].ival)
950 modify = 1;
951 break;
952 case XT_S_DOUBLE:
953 if (atof(val) < (*rs[i].dval - 0.0001) ||
954 atof(val) > (*rs[i].dval + 0.0001))
955 modify = 1;
956 break;
957 case XT_S_STR:
958 s = (rs[i].sval == NULL || *rs[i].sval == NULL)
959 ? "" : *rs[i].sval;
960 if (rs[i].sval && g_strcmp0(val, s))
961 modify = 1;
962 else if (rs[i].s && rs[i].s->get) {
963 curval = rs[i].s->get(NULL);
964 if (g_strcmp0(val, curval))
965 modify = 1;
966 g_free(curval);
968 break;
969 case XT_S_INVALID: /* FALLTHROUGH */
970 default:
971 break;
973 if (rs[i].activate(val)) {
974 sr = g_malloc(sizeof *sr);
975 sr->name = g_strdup(rs[i].name);
976 sr->value = g_strdup(val);
977 TAILQ_INSERT_TAIL(&srl, sr, entry);
978 continue;
980 if (modify)
981 if (save_runtime_setting(rs[i].name, val))
982 show_oops(t, "error");
984 break;
985 default:
986 show_oops(t, "%s: invalid set command", __func__);
987 break;
990 if (new_settings)
991 g_hash_table_destroy(new_settings);
993 xtp_page_rt(t, NULL);
996 /* link an XTP class to it's session key and handler function */
997 struct xtp_despatch {
998 uint8_t xtp_class;
999 void (*handle_func)(struct tab *, uint8_t, int,
1000 const char *query);
1003 struct xtp_despatch xtp_despatches[] = {
1004 { XT_XTP_DL, xtp_handle_dl },
1005 { XT_XTP_HL, xtp_handle_hl },
1006 { XT_XTP_FL, xtp_handle_fl },
1007 { XT_XTP_CL, xtp_handle_cl },
1008 { XT_XTP_SL, xtp_handle_sl },
1009 { XT_XTP_AB, xtp_handle_ab },
1010 { XT_XTP_SV, xtp_handle_sv },
1011 { XT_XTP_RT, xtp_handle_rt },
1012 { XT_XTP_INVALID, NULL }
1016 * generate a session key to secure xtp commands.
1017 * pass in a ptr to the key in question and it will
1018 * be modified in place.
1020 void
1021 generate_xtp_session_key(char **key)
1023 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1025 if (key == NULL)
1026 return;
1028 /* free old key */
1029 if (*key != NULL)
1030 g_free(*key);
1032 /* make a new one */
1033 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1034 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1035 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1036 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1038 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1042 * validate a xtp session key.
1043 * return (1) if OK
1046 validate_xtp_session_key(struct tab *t, char *key)
1048 if (t == NULL || t->session_key == NULL || key == NULL)
1049 return (0);
1051 if (strcmp(t->session_key, key) != 0) {
1052 show_oops(t, "%s: xtp session key mismatch possible spoof",
1053 __func__);
1054 return (0);
1057 return (1);
1061 * is the url xtp protocol? (xxxt://)
1062 * if so, parse and despatch correct bahvior
1065 parse_xtp_url(struct tab *t, const char *uri_str)
1067 SoupURI *uri = NULL;
1068 struct xtp_despatch *dsp, *dsp_match = NULL;
1069 int ret = FALSE;
1070 int class = 0;
1071 char **sv = NULL;
1074 * uri->host = class
1075 * sv[0] = session key
1076 * sv[1] = command
1077 * sv[2] = optional argument
1080 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, uri_str);
1082 if ((uri = soup_uri_new(uri_str)) == NULL)
1083 goto clean;
1084 if (strncmp(uri->scheme, XT_XTP_SCHEME, strlen(XT_XTP_SCHEME)))
1085 goto clean;
1086 if (uri->host == NULL || strlen(uri->host) == 0)
1087 goto clean;
1088 if ((sv = g_strsplit(uri->path + 1, "/", 3)) == NULL)
1089 goto clean;
1091 if (sv[0] == NULL || sv[1] == NULL)
1092 goto clean;
1094 dsp = xtp_despatches;
1095 class = atoi(uri->host);
1096 while (dsp->xtp_class) {
1097 if (dsp->xtp_class == class) {
1098 dsp_match = dsp;
1099 break;
1101 dsp++;
1104 /* did we find one atall? */
1105 if (dsp_match == NULL) {
1106 show_oops(t, "%s: no matching xtp despatch found", __func__);
1107 goto clean;
1110 /* check session key and call despatch function */
1111 if (validate_xtp_session_key(t, sv[0])) {
1112 ret = TRUE; /* all is well, this was a valid xtp request */
1113 if (sv[2])
1114 dsp_match->handle_func(t, atoi(sv[1]), atoi(sv[2]),
1115 uri->query);
1116 else
1117 dsp_match->handle_func(t, atoi(sv[1]), 0, uri->query);
1120 clean:
1121 if (uri)
1122 soup_uri_free(uri);
1123 if (sv)
1124 g_strfreev(sv);
1126 return (ret);
1130 * update all favorite tabs apart from one. Pass NULL if
1131 * you want to update all.
1133 void
1134 update_favorite_tabs(struct tab *apart_from)
1136 struct tab *t;
1138 if (!updating_fl_tabs) {
1139 updating_fl_tabs = 1; /* stop infinite recursion */
1140 TAILQ_FOREACH(t, &tabs, entry)
1141 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1142 && (t != apart_from))
1143 xtp_page_fl(t, NULL);
1144 updating_fl_tabs = 0;
1149 * update all download tabs apart from one. Pass NULL if
1150 * you want to update all.
1152 void
1153 update_download_tabs(struct tab *apart_from)
1155 struct tab *t;
1157 if (!updating_dl_tabs) {
1158 updating_dl_tabs = 1; /* stop infinite recursion */
1159 TAILQ_FOREACH(t, &tabs, entry)
1160 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
1161 && (t != apart_from))
1162 xtp_page_dl(t, NULL);
1163 updating_dl_tabs = 0;
1168 * update all cookie tabs apart from one. Pass NULL if
1169 * you want to update all.
1171 void
1172 update_cookie_tabs(struct tab *apart_from)
1174 struct tab *t;
1176 if (!updating_cl_tabs) {
1177 updating_cl_tabs = 1; /* stop infinite recursion */
1178 TAILQ_FOREACH(t, &tabs, entry)
1179 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
1180 && (t != apart_from))
1181 xtp_page_cl(t, NULL);
1182 updating_cl_tabs = 0;
1187 * update all history tabs apart from one. Pass NULL if
1188 * you want to update all.
1190 void
1191 update_history_tabs(struct tab *apart_from)
1193 struct tab *t;
1195 if (!updating_hl_tabs) {
1196 updating_hl_tabs = 1; /* stop infinite recursion */
1197 TAILQ_FOREACH(t, &tabs, entry)
1198 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
1199 && (t != apart_from))
1200 xtp_page_hl(t, NULL);
1201 updating_hl_tabs = 0;
1206 * update all search tabs apart from one. Pass NULL if
1207 * you want to update all.
1209 void
1210 update_search_tabs(struct tab *apart_from)
1212 struct tab *t;
1214 if (!updating_sl_tabs) {
1215 updating_sl_tabs = 1; /* stop infinite recursion */
1216 TAILQ_FOREACH(t, &tabs, entry)
1217 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SL)
1218 && (t != apart_from))
1219 xtp_page_sl(t, NULL);
1220 updating_sl_tabs = 0;
1225 xtp_page_ab(struct tab *t, struct karg *args)
1227 char *page, *body;
1229 if (t == NULL) {
1230 show_oops(NULL, "about invalid parameters");
1231 return (-1);
1234 generate_xtp_session_key(&t->session_key);
1236 body = g_strdup_printf("<b>Version: %s</b>"
1237 #ifdef XOMBRERO_BUILDSTR
1238 "<br><b>Build: %s</b>"
1239 #endif
1240 "<br><b>WebKit: %d.%d.%d</b>"
1241 "<br><b>User Agent: %d.%d</b>"
1242 #ifdef WEBKITGTK_API_VERSION
1243 "<br><b>WebKit API: %.1f</b>"
1244 #endif
1245 "<br><b>Configuration: %s" PS "<a href='%s%d/%s/%d'>.%s</a>"
1246 " (remember to restart the browser after any changes)</b>"
1247 "<p>"
1248 "Authors:"
1249 "<ul>"
1250 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1251 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1252 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
1253 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
1254 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
1255 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
1256 "<li>Josh Rickmar &lt;jrick@devio.us&gt;</li>"
1257 "<li>David Hill &lt;dhill@mindcry.org&gt;</li>"
1258 "</ul>"
1259 "Copyrights and licenses can be found on the xombrero "
1260 "<a href=\"https://opensource.conformal.com/wiki/xombrero\">website</a>"
1261 "</p>",
1262 #ifdef XOMBRERO_BUILDSTR
1263 version, XOMBRERO_BUILDSTR,
1264 #else
1265 version,
1266 #endif
1267 WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION,
1268 WEBKIT_USER_AGENT_MAJOR_VERSION, WEBKIT_USER_AGENT_MINOR_VERSION
1269 #ifdef WEBKITGTK_API_VERSION
1270 ,WEBKITGTK_API_VERSION
1271 #endif
1272 ,pwd->pw_dir,
1273 XT_XTP_STR,
1274 XT_XTP_AB,
1275 t->session_key ? t->session_key : "",
1276 XT_XTP_AB_EDIT_CONF,
1277 XT_CONF_FILE
1280 page = get_html_page("About", body, "", 0);
1281 g_free(body);
1283 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT, 0);
1285 g_free(page);
1287 return (0);
1290 /* show a list of favorites (bookmarks) */
1292 xtp_page_fl(struct tab *t, struct karg *args)
1294 char file[PATH_MAX];
1295 FILE *f;
1296 char *uri = NULL, *title = NULL;
1297 size_t len, lineno = 0;
1298 int i, failed = 0;
1299 char *body, *tmp, *page = NULL;
1300 const char delim[3] = {'\\', '\\', '\0'};
1302 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1304 if (t == NULL) {
1305 show_oops(NULL, "%s: bad param", __func__);
1306 return (-1);
1309 generate_xtp_session_key(&t->session_key);
1311 /* open favorites */
1312 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
1313 if ((f = fopen(file, "r")) == NULL) {
1314 show_oops(t, "Can't open favorites file: %s", strerror(errno));
1315 return (1);
1318 /* body */
1319 if (args && args->i & XT_DELETE)
1320 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1321 "<th style='width: 40px'>&#35;</th><th>Link</th>"
1322 "<th style='width: 40px'>Rm</th></tr>\n");
1323 else
1324 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1325 "<th style='width: 40px'>&#35;</th><th>Link</th></tr>\n");
1327 for (i = 1;;) {
1328 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1329 break;
1330 if (strlen(title) == 0) {
1331 free(title);
1332 title = NULL;
1333 continue;
1336 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1337 if (feof(f) || ferror(f)) {
1338 show_oops(t, "favorites file corrupt");
1339 failed = 1;
1340 break;
1343 tmp = body;
1344 if (args && args->i & XT_DELETE)
1345 body = g_strdup_printf("%s<tr>"
1346 "<td>%d</td>"
1347 "<td><a href='%s'>%s</a></td>"
1348 "<td style='text-align: center'>"
1349 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1350 "</tr>\n",
1351 body, i, uri, title,
1352 XT_XTP_STR, XT_XTP_FL,
1353 t->session_key ? t->session_key : "",
1354 XT_XTP_FL_REMOVE, i);
1355 else
1356 body = g_strdup_printf("%s<tr>"
1357 "<td>%d</td>"
1358 "<td><a href='%s'>%s</a></td>"
1359 "</tr>\n",
1360 body, i, uri, title);
1361 g_free(tmp);
1363 free(uri);
1364 uri = NULL;
1365 free(title);
1366 title = NULL;
1367 i++;
1369 fclose(f);
1371 /* if none, say so */
1372 if (i == 1) {
1373 tmp = body;
1374 body = g_strdup_printf("%s<tr>"
1375 "<td colspan='%d' style='text-align: center'>"
1376 "No favorites - To add one use the 'favadd' command."
1377 "</td></tr>", body, (args && args->i & XT_DELETE) ? 3 : 2);
1378 g_free(tmp);
1381 tmp = body;
1382 body = g_strdup_printf("%s</table>", body);
1383 g_free(tmp);
1385 if (uri)
1386 free(uri);
1387 if (title)
1388 free(title);
1390 /* render */
1391 if (!failed) {
1392 page = get_html_page("Favorites", body, "", 1);
1393 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES, 0);
1394 g_free(page);
1397 update_favorite_tabs(t);
1399 if (body)
1400 g_free(body);
1402 return (failed);
1406 * Return a new string with a download row (in html)
1407 * appended. Old string is freed.
1409 char *
1410 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1413 WebKitDownloadStatus stat;
1414 const gchar *destination;
1415 gchar *d;
1416 char *status_html = NULL, *cmd_html = NULL, *new_html;
1417 gdouble progress;
1418 char cur_sz[FMT_SCALED_STRSIZE];
1419 char tot_sz[FMT_SCALED_STRSIZE];
1420 char *xtp_prefix;
1422 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1424 /* All actions wil take this form:
1425 * xxxt://class/seskey
1427 xtp_prefix = g_strdup_printf("%s%d/%s/",
1428 XT_XTP_STR, XT_XTP_DL, t->session_key);
1430 stat = webkit_download_get_status(dl->download);
1432 switch (stat) {
1433 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1434 status_html = g_strdup_printf("Finished");
1435 cmd_html = g_strdup_printf(
1436 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1437 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1438 XT_XTP_DL_UNLINK, dl->id);
1439 break;
1440 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1441 /* gather size info */
1442 progress = 100 * webkit_download_get_progress(dl->download);
1444 fmt_scaled(
1445 webkit_download_get_current_size(dl->download), cur_sz);
1446 fmt_scaled(
1447 webkit_download_get_total_size(dl->download), tot_sz);
1449 status_html = g_strdup_printf(
1450 "<div style='width: 100%%' align='center'>"
1451 "<div class='progress-outer'>"
1452 "<div class='progress-inner' style='width: %.2f%%'>"
1453 "</div></div></div>"
1454 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1455 progress, cur_sz, tot_sz, progress);
1457 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1458 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1460 break;
1461 /* LLL */
1462 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1463 status_html = g_strdup_printf("Cancelled");
1464 cmd_html = g_strdup_printf(
1465 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1466 xtp_prefix, XT_XTP_DL_START, dl->id,
1467 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1468 XT_XTP_DL_UNLINK, dl->id);
1469 break;
1470 case WEBKIT_DOWNLOAD_STATUS_ERROR:
1471 status_html = g_strdup_printf("Error!");
1472 cmd_html = g_strdup_printf(
1473 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1474 xtp_prefix, XT_XTP_DL_START, dl->id,
1475 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1476 XT_XTP_DL_UNLINK, dl->id);
1477 break;
1478 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1479 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1480 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1481 XT_XTP_DL_CANCEL, dl->id);
1482 status_html = g_strdup_printf("Created");
1483 break;
1484 default:
1485 show_oops(t, "%s: unknown download status", __func__);
1488 destination = webkit_download_get_destination_uri(dl->download);
1489 /* we might not have a destination set yet */
1490 if (!destination)
1491 destination = webkit_download_get_suggested_filename(dl->download);
1492 d = g_strdup(destination); /* copy for basename */
1493 new_html = g_strdup_printf(
1494 "%s\n<tr><td>%s</td><td>%s</td>"
1495 "<td style='text-align:center'>%s</td></tr>\n",
1496 html, basename(d), status_html, cmd_html);
1497 g_free(d);
1498 g_free(html);
1500 if (status_html)
1501 g_free(status_html);
1503 if (cmd_html)
1504 g_free(cmd_html);
1506 g_free(xtp_prefix);
1508 return new_html;
1511 /* cookie management XTP page */
1513 xtp_page_cl(struct tab *t, struct karg *args)
1515 char *body, *page, *tmp;
1516 int i = 1; /* all ids start 1 */
1517 int domain_id = 0;
1518 GSList *sc, *pc, *pc_start;
1519 SoupCookie *c;
1520 char *type, *table_headers, *last_domain;
1522 DNPRINTF(XT_D_CMD, "%s", __func__);
1524 if (t == NULL) {
1525 show_oops(NULL, "%s invalid parameters", __func__);
1526 return (1);
1529 generate_xtp_session_key(&t->session_key);
1531 /* table headers */
1532 table_headers = g_strdup_printf("<table><tr>"
1533 "<th>Type</th>"
1534 "<th>Name</th>"
1535 "<th style='width:200px'>Value</th>"
1536 "<th>Path</th>"
1537 "<th>Expires</th>"
1538 "<th>Secure</th>"
1539 "<th>HTTP<br />only</th>"
1540 "<th style='width:40px'>Rm</th></tr>\n");
1542 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1543 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1544 pc_start = pc;
1546 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1547 "[ Remove All Cookies From All Domains ]</a></div>\n",
1548 XT_XTP_STR, XT_XTP_CL, t->session_key, XT_XTP_CL_REMOVE_ALL);
1550 last_domain = g_strdup("");
1551 for (; sc; sc = sc->next) {
1552 c = sc->data;
1554 if (strcmp(last_domain, c->domain) != 0) {
1555 /* new domain */
1556 domain_id ++;
1557 g_free(last_domain);
1558 last_domain = g_strdup(c->domain);
1560 if (body != NULL) {
1561 tmp = body;
1562 body = g_strdup_printf("%s</table>"
1563 "<h2>%s</h2><div align=\"center\">"
1564 "<a href='%s%d/%s/%d/%d'>"
1565 "[ Remove All From This Domain ]"
1566 "</a></div>%s\n",
1567 body, c->domain,
1568 XT_XTP_STR, XT_XTP_CL, t->session_key,
1569 XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1570 table_headers);
1571 g_free(tmp);
1572 } else {
1573 /* first domain */
1574 body = g_strdup_printf("<h2>%s</h2>"
1575 "<div align=\"center\">"
1576 "<a href='%s%d/%s/%d/%d'>"
1577 "[ Remove All From This Domain ]</a></div>%s\n",
1578 c->domain, XT_XTP_STR, XT_XTP_CL,
1579 t->session_key, XT_XTP_CL_REMOVE_DOMAIN,
1580 domain_id, table_headers);
1584 type = "Session";
1585 for (pc = pc_start; pc; pc = pc->next)
1586 if (soup_cookie_equal(pc->data, c)) {
1587 type = "Session + Persistent";
1588 break;
1591 tmp = body;
1592 body = g_strdup_printf(
1593 "%s\n<tr>"
1594 "<td>%s</td>"
1595 "<td style='word-wrap:normal'>%s</td>"
1596 "<td>"
1597 " <textarea rows='4'>%s</textarea>"
1598 "</td>"
1599 "<td>%s</td>"
1600 "<td>%s</td>"
1601 "<td>%d</td>"
1602 "<td>%d</td>"
1603 "<td style='text-align:center'>"
1604 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1605 body,
1606 type,
1607 c->name,
1608 c->value,
1609 c->path,
1610 c->expires ?
1611 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1612 c->secure,
1613 c->http_only,
1615 XT_XTP_STR,
1616 XT_XTP_CL,
1617 t->session_key,
1618 XT_XTP_CL_REMOVE,
1622 g_free(tmp);
1623 i++;
1626 soup_cookies_free(sc);
1627 soup_cookies_free(pc);
1629 /* small message if there are none */
1630 if (i == 1) {
1631 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1632 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1634 tmp = body;
1635 body = g_strdup_printf("%s</table>", body);
1636 g_free(tmp);
1638 page = get_html_page("Cookie Jar", body, "", TRUE);
1639 g_free(body);
1640 g_free(table_headers);
1641 g_free(last_domain);
1643 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR, 0);
1644 update_cookie_tabs(t);
1646 g_free(page);
1648 return (0);
1652 xtp_page_hl(struct tab *t, struct karg *args)
1654 char *body, *page, *tmp;
1655 struct history *h;
1656 int i = 1; /* all ids start 1 */
1658 DNPRINTF(XT_D_CMD, "%s", __func__);
1660 if (t == NULL) {
1661 show_oops(NULL, "%s invalid parameters", __func__);
1662 return (1);
1665 generate_xtp_session_key(&t->session_key);
1667 /* body */
1668 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1669 "[ Remove All ]</a></div>"
1670 "<table style='table-layout:fixed'><tr>"
1671 "<th>URI</th><th>Title</th><th>Last visited</th>"
1672 "<th style='width: 40px'>Rm</th></tr>\n",
1673 XT_XTP_STR, XT_XTP_HL, t->session_key, XT_XTP_HL_REMOVE_ALL);
1675 RB_FOREACH_REVERSE(h, history_list, &hl) {
1676 tmp = body;
1677 body = g_strdup_printf(
1678 "%s\n<tr>"
1679 "<td><a href='%s'>%s</a></td>"
1680 "<td>%s</td>"
1681 "<td>%s</td>"
1682 "<td style='text-align: center'>"
1683 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1684 body, h->uri, h->uri, h->title, ctime(&h->time),
1685 XT_XTP_STR, XT_XTP_HL, t->session_key,
1686 XT_XTP_HL_REMOVE, i);
1688 g_free(tmp);
1689 i++;
1692 /* small message if there are none */
1693 if (i == 1) {
1694 tmp = body;
1695 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1696 "colspan='4'>No History</td></tr>\n", body);
1697 g_free(tmp);
1700 tmp = body;
1701 body = g_strdup_printf("%s</table>", body);
1702 g_free(tmp);
1704 page = get_html_page("History", body, "", TRUE);
1705 g_free(body);
1708 * update all history manager tabs as the xtp session
1709 * key has now changed. No need to update the current tab.
1710 * Already did that above.
1712 update_history_tabs(t);
1714 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY, 0);
1715 g_free(page);
1717 return (0);
1721 * Generate a web page detailing the status of any downloads
1724 xtp_page_dl(struct tab *t, struct karg *args)
1726 struct download *dl;
1727 char *body, *page, *tmp;
1728 char *ref;
1729 int n_dl = 1;
1731 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1733 if (t == NULL) {
1734 show_oops(NULL, "%s invalid parameters", __func__);
1735 return (1);
1738 generate_xtp_session_key(&t->session_key);
1740 /* header - with refresh so as to update */
1741 if (refresh_interval >= 1)
1742 ref = g_strdup_printf(
1743 "<meta http-equiv='refresh' content='%u"
1744 ";url=%s%d/%s/%d' />\n",
1745 refresh_interval,
1746 XT_XTP_STR,
1747 XT_XTP_DL,
1748 t->session_key,
1749 XT_XTP_DL_LIST);
1750 else
1751 ref = g_strdup("");
1753 body = g_strdup_printf("<div align='center'>"
1754 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1755 "</p><table><tr><th style='width: 60%%'>"
1756 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1757 XT_XTP_STR, XT_XTP_DL, t->session_key, XT_XTP_DL_LIST);
1759 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1760 body = xtp_page_dl_row(t, body, dl);
1761 n_dl++;
1764 /* message if no downloads in list */
1765 if (n_dl == 1) {
1766 tmp = body;
1767 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1768 " style='text-align: center'>"
1769 "No downloads</td></tr>\n", body);
1770 g_free(tmp);
1773 tmp = body;
1774 body = g_strdup_printf("%s</table></div>", body);
1775 g_free(tmp);
1777 page = get_html_page("Downloads", body, ref, 1);
1778 g_free(ref);
1779 g_free(body);
1782 * update all download manager tabs as the xtp session
1783 * key has now changed. No need to update the current tab.
1784 * Already did that above.
1786 update_download_tabs(t);
1788 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS, 0);
1789 g_free(page);
1791 return (0);
1795 xtp_page_sl(struct tab *t, struct karg *args)
1797 int i;
1798 char *page, *body, *tmp;
1800 DNPRINTF(XT_D_SEARCH, "%s", __func__);
1802 generate_xtp_session_key(&t->session_key);
1804 if (t == NULL) {
1805 show_oops(NULL, "%s invalid parameters", __func__);
1806 return (1);
1809 body = g_strdup_printf("<p>The xombrero authors will not choose a "
1810 "default search engine for you. What follows is a list of search "
1811 "engines (in no particular order) you may be interested in. "
1812 "To permanently choose a search engine, click [ Select ] to save "
1813 "<tt>search_string</tt> as a runtime setting, or set "
1814 "<tt>search_string</tt> to the appropriate URL in your xombrero "
1815 "configuration.</p>");
1817 tmp = body;
1818 body = g_strdup_printf("%s\n<table style='table-layout:fixed'><tr>"
1819 "<th style='width: 200px'>Name</th><th>URL</th>"
1820 "<th style='width: 100px'>Select</th></tr>\n", body);
1821 g_free(tmp);
1823 for (i = 0; i < (sizeof search_list / sizeof (struct search_type)); ++i)
1824 body = search_engine_add(body, search_list[i].name,
1825 search_list[i].url, t->session_key, i);
1827 tmp = body;
1828 body = g_strdup_printf("%s</table>", body);
1829 g_free(tmp);
1831 page = get_html_page("Choose a search engine", body, "", 1);
1832 g_free(body);
1835 * update all search tabs as the xtp session key has now changed. No
1836 * need to update the current tab. Already did that above.
1838 update_search_tabs(t);
1840 load_webkit_string(t, page, XT_URI_ABOUT_SEARCH, 0);
1841 g_free(page);
1843 return (0);
1847 xtp_page_sv(struct tab *t, struct karg *args)
1849 SoupURI *soupuri;
1850 static int arg = 0;
1851 struct secviolation find, *sv;
1852 char *page, *body;
1854 if (t == NULL) {
1855 show_oops(NULL, "secviolation invalid parameters");
1856 return (-1);
1859 generate_xtp_session_key(&t->session_key);
1861 if (args == NULL) {
1862 find.xtp_arg = t->xtp_arg;
1863 sv = RB_FIND(secviolation_list, &svl, &find);
1864 if (sv == NULL)
1865 return (-1);
1866 } else {
1867 sv = g_malloc(sizeof(struct secviolation));
1868 sv->xtp_arg = ++arg;
1869 t->xtp_arg = arg;
1870 sv->t = t;
1871 sv->uri = args->s;
1872 RB_INSERT(secviolation_list, &svl, sv);
1875 if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
1876 return (-1);
1878 body = g_strdup_printf(
1879 "<p><b>You tried to access %s</b>."
1880 "<p><b>The site's security certificate has been modified.</b>"
1881 "<p>The domain of the page you have tried to access, <b>%s</b>, "
1882 "has a different remote certificate then the local cached version "
1883 "from a previous visit. As a security precaution to help prevent "
1884 "against man-in-the-middle attacks, please choose one of the "
1885 "following actions to continue, or disable the "
1886 "<tt>warn_cert_changes</tt> setting in your xombrero "
1887 "configuration."
1888 "<p><b>Choose an action:"
1889 "<br><a href='%s%d/%s/%d/%d'>Allow for this session</a>"
1890 "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>"
1891 "<br><a href='%s%d/%s/%d/%d'>Show cached certificate</a>"
1892 "<br><a href='%s%d/%s/%d/%d'>Show new certificate</a>",
1893 sv->uri,
1894 soupuri->host,
1895 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_ALLOW_SESSION,
1896 sv->xtp_arg,
1897 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_CACHE,
1898 sv->xtp_arg,
1899 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_CACHED_CERT,
1900 sv->xtp_arg,
1901 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_NEW_CERT,
1902 sv->xtp_arg);
1904 page = get_html_page("Security Violation", body, "", 0);
1905 g_free(body);
1907 load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION, 1);
1909 g_free(page);
1910 if (soupuri)
1911 soup_uri_free(soupuri);
1913 return (0);
1917 startpage(struct tab *t, struct karg *args)
1919 char *page, *body, *b;
1920 struct sp *s;
1922 if (t == NULL)
1923 show_oops(NULL, "startpage invalid parameters");
1925 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1927 TAILQ_FOREACH(s, &spl, entry) {
1928 b = body;
1929 body = g_strdup_printf("%s%s<br>", body, s->line);
1930 g_free(b);
1933 page = get_html_page("Startup Exception", body, "", 0);
1934 g_free(body);
1936 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE, 0);
1937 g_free(page);
1939 return (0);
1942 void
1943 startpage_add(const char *fmt, ...)
1945 va_list ap;
1946 char *msg;
1947 struct sp *s;
1949 if (fmt == NULL)
1950 return;
1952 va_start(ap, fmt);
1953 if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
1954 errx(1, "startpage_add failed");
1955 va_end(ap);
1957 s = g_malloc0(sizeof *s);
1958 s->line = msg;
1960 TAILQ_INSERT_TAIL(&spl, s, entry);
1962 gchar *show_g_object_settings(GObject *, char *, int);
1964 char *
1965 xt_g_object_serialize(GValue *value, const gchar *tname, char *str, int recurse)
1967 int typeno = 0;
1968 char *valstr, *tmpstr, *tmpsettings;
1969 GObject *object;
1971 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(value) );
1972 switch ( typeno ) {
1973 case G_TYPE_ENUM:
1974 valstr = g_strdup_printf("%d",
1975 g_value_get_enum(value));
1976 break;
1977 case G_TYPE_CHAR:
1978 valstr = g_strdup_printf("%c",
1979 #if GLIB_CHECK_VERSION(2, 32, 0)
1980 g_value_get_schar(value));
1981 #else
1982 g_value_get_char(value));
1983 #endif
1984 break;
1985 case G_TYPE_UCHAR:
1986 valstr = g_strdup_printf("%c",
1987 g_value_get_uchar(value));
1988 break;
1989 case G_TYPE_LONG:
1990 valstr = g_strdup_printf("%ld",
1991 g_value_get_long(value));
1992 break;
1993 case G_TYPE_ULONG:
1994 valstr = g_strdup_printf("%ld",
1995 g_value_get_ulong(value));
1996 break;
1997 case G_TYPE_INT:
1998 valstr = g_strdup_printf("%d",
1999 g_value_get_int(value));
2000 break;
2001 case G_TYPE_INT64:
2002 valstr = g_strdup_printf("%" PRIo64,
2003 (int64_t) g_value_get_int64(value));
2004 break;
2005 case G_TYPE_UINT:
2006 valstr = g_strdup_printf("%d",
2007 g_value_get_uint(value));
2008 break;
2009 case G_TYPE_UINT64:
2010 valstr = g_strdup_printf("%" PRIu64,
2011 (uint64_t) g_value_get_uint64(value));
2012 break;
2013 case G_TYPE_FLAGS:
2014 valstr = g_strdup_printf("0x%x",
2015 g_value_get_flags(value));
2016 break;
2017 case G_TYPE_BOOLEAN:
2018 valstr = g_strdup_printf("%s",
2019 g_value_get_boolean(value) ? "TRUE" : "FALSE");
2020 break;
2021 case G_TYPE_FLOAT:
2022 valstr = g_strdup_printf("%f",
2023 g_value_get_float(value));
2024 break;
2025 case G_TYPE_DOUBLE:
2026 valstr = g_strdup_printf("%f",
2027 g_value_get_double(value));
2028 break;
2029 case G_TYPE_STRING:
2030 valstr = g_strdup_printf("\"%s\"",
2031 g_value_get_string(value));
2032 break;
2033 case G_TYPE_POINTER:
2034 valstr = g_strdup_printf("%p",
2035 g_value_get_pointer(value));
2036 break;
2037 case G_TYPE_OBJECT:
2038 object = g_value_get_object(value);
2039 if (object != NULL) {
2040 if (recurse) {
2041 tmpstr = g_strdup_printf("%s ", str);
2042 tmpsettings = show_g_object_settings( object,
2043 tmpstr, recurse);
2044 g_free(tmpstr);
2046 if (strrchr(tmpsettings, '\n') != NULL) {
2047 valstr = g_strdup_printf("%s%s }",
2048 tmpsettings, str);
2049 g_free(tmpsettings);
2050 } else {
2051 valstr = tmpsettings;
2053 } else {
2054 valstr = g_strdup_printf("<...>");
2056 } else {
2057 valstr = g_strdup_printf("settings[] = NULL");
2059 break;
2060 default:
2061 valstr = g_strdup_printf("type %s unhandled", tname);
2063 return valstr;
2066 gchar *
2067 show_g_object_settings(GObject *o, char *str, int recurse)
2069 char *b, *p, *body, *valstr, *tmpstr;
2070 guint n_props = 0;
2071 int i, typeno = 0;
2072 GParamSpec *pspec;
2073 const gchar *tname;
2074 GValue value;
2075 GParamSpec **proplist;
2076 const gchar *name;
2078 if (!G_IS_OBJECT(o)) {
2079 fprintf(stderr, "%s is not a g_object\n", str);
2080 return g_strdup("");
2082 proplist = g_object_class_list_properties(
2083 G_OBJECT_GET_CLASS(o), &n_props);
2085 if (GTK_IS_WIDGET(o)) {
2086 name = gtk_widget_get_name(GTK_WIDGET(o));
2087 } else {
2088 name = "settings";
2090 if (n_props == 0) {
2091 body = g_strdup_printf("%s[0] = { }", name);
2092 goto end_show_g_objects;
2095 body = g_strdup_printf("%s[%d] = {\n", name, n_props);
2096 for (i=0; i < n_props; i++) {
2097 pspec = proplist[i];
2098 tname = G_OBJECT_TYPE_NAME(pspec);
2099 bzero(&value, sizeof value);
2100 valstr = NULL;
2102 if (!(pspec->flags & G_PARAM_READABLE))
2103 valstr = g_strdup_printf("not a readable property");
2104 else {
2105 g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
2106 g_object_get_property(G_OBJECT(o), pspec->name,
2107 &value);
2108 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(&value) );
2111 /* based on the type, recurse and display values */
2112 if (valstr == NULL) {
2113 valstr = xt_g_object_serialize(&value, tname, str,
2114 recurse);
2117 tmpstr = g_strdup_printf("%-13s %s%s%s,", tname, pspec->name,
2118 (typeno == G_TYPE_OBJECT) ? "." : " = ", valstr);
2119 b = body;
2121 #define XT_G_OBJECT_SPACING 50
2122 p = strrchr(tmpstr, '\n');
2123 if (p == NULL && strlen(tmpstr) > XT_G_OBJECT_SPACING) {
2124 body = g_strdup_printf(
2125 "%s%s %-50s\n%s %50s /* %3d flags=0x%08x */\n",
2126 body, str, tmpstr, str, "", i, pspec->flags);
2127 } else {
2128 char *fmt;
2129 int strspaces;
2130 if (p == NULL)
2131 strspaces = XT_G_OBJECT_SPACING;
2132 else
2133 strspaces = strlen(tmpstr) - (strlen(p) - strlen(str)) + XT_G_OBJECT_SPACING + 5;
2134 fmt = g_strdup_printf("%%s%%s %%-%ds /* %%3d flags=0x%%08x */\n", strspaces);
2135 body = g_strdup_printf(fmt, body, str, tmpstr, i, pspec->flags);
2136 g_free(fmt);
2138 g_free(tmpstr);
2139 g_free(b);
2140 g_free(valstr);
2142 end_show_g_objects:
2143 g_free(proplist);
2144 return (body);
2147 char *
2148 xt_append_settings(char *str, GObject *object, char *name, int recurse)
2150 char *newstr, *settings;
2152 settings = show_g_object_settings(object, name, recurse);
2153 if (str == NULL)
2154 str = g_strdup("");
2156 newstr = g_strdup_printf("%s%s %s%s };\n", str, name, settings, name);
2157 g_free(str);
2159 return newstr;
2163 about_webkit(struct tab *t, struct karg *arg)
2165 char *page, *body, *settingstr;
2167 settingstr = xt_append_settings(NULL, G_OBJECT(t->settings),
2168 "t->settings", 0);
2169 body = g_strdup_printf("<pre>%s</pre>\n", settingstr);
2170 g_free(settingstr);
2172 page = get_html_page("About Webkit", body, "", 0);
2173 g_free(body);
2175 load_webkit_string(t, page, XT_URI_ABOUT_WEBKIT, 0);
2176 g_free(page);
2178 return (0);
2181 static int toplevelcount = 0;
2183 void
2184 xt_append_toplevel(GtkWindow *w, char **body)
2186 char *n;
2188 n = g_strdup_printf("toplevel#%d", toplevelcount++);
2189 *body = xt_append_settings(*body, G_OBJECT(w), n, 0);
2190 g_free(n);
2194 allthethings(struct tab *t, struct karg *arg)
2196 GList *list;
2197 char *page, *body, *b;
2199 body = xt_append_settings(NULL, G_OBJECT(t->wv), "t->wv", 1);
2200 body = xt_append_settings(body, G_OBJECT(t->inspector),
2201 "t->inspector", 1);
2202 #if 0 /* not until warnings are gone */
2203 body = xt_append_settings(body, G_OBJECT(session),
2204 "session", 1);
2205 #endif
2206 toplevelcount = 0;
2207 list = gtk_window_list_toplevels();
2208 g_list_foreach(list, (GFunc)g_object_ref, NULL);
2209 g_list_foreach(list, (GFunc)xt_append_toplevel, &body);
2210 g_list_foreach(list, (GFunc)g_object_unref, NULL);
2211 g_list_free(list);
2213 b = body;
2214 body = g_strdup_printf("<pre>%scan paste clipboard = %d\n</pre>", body,
2215 webkit_web_view_can_paste_clipboard(t->wv));
2216 g_free(b);
2218 page = get_html_page("About All The Things _o/", body, "", 0);
2219 g_free(body);
2221 load_webkit_string(t, page, XT_URI_ABOUT_ALLTHETHINGS, 0);
2222 g_free(page);
2224 return (0);