Kill whitespace in aliases. Fixes FS240.
[xombrero.git] / about.c
blob35d7716281fe3e5ae75604cfc0a452b23a9c694e
1 /*
2 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
3 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
4 * Copyright (c) 2010, 2011, 2012 Edd Barrett <vext01@gmail.com>
5 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
6 * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
7 * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
8 * Copyright (c) 2012 Josh Rickmar <jrick@devio.us>
10 * Permission to use, copy, modify, and distribute this software for any
11 * purpose with or without fee is hereby granted, provided that the above
12 * copyright notice and this permission notice appear in all copies.
14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23 #include <xombrero.h>
26 * xombrero "protocol" (xtp)
27 * We use this for managing stuff like downloads and favorites. They
28 * make magical HTML pages in memory which have xxxt:// links in order
29 * to communicate with xombrero's internals. These links take the format:
30 * xxxt://class/session_key/action/arg
32 * Don't begin xtp class/actions as 0. atoi returns that on error.
34 * Typically we have not put addition of items in this framework, as
35 * adding items is either done via an ex-command or via a keybinding instead.
38 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>\n"
39 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>\n"
40 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
41 "td{overflow: hidden;" \
42 " padding: 2px 2px 2px 2px;" \
43 " border: 1px solid black;" \
44 " vertical-align:top;" \
45 " word-wrap: break-word}\n" \
46 "tr:hover{background: #ffff99}\n" \
47 "th{background-color: #cccccc;" \
48 " border: 1px solid black}\n" \
49 "table{width: 100%%;" \
50 " border: 1px black solid;" \
51 " border-collapse:collapse}\n" \
52 ".progress-outer{" \
53 "border: 1px solid black;" \
54 " height: 8px;" \
55 " width: 90%%}\n" \
56 ".progress-inner{float: left;" \
57 " height: 8px;" \
58 " background: green}\n" \
59 ".dlstatus{font-size: small;" \
60 " text-align: center}\n" \
61 "table#settings{background-color: #eee;"\
62 " border: 0px;" \
63 " margin: 15px;}\n" \
64 "table#settings td{border: 0px;}\n" \
65 "table#settings th{border: 0px;}\n" \
66 "table#settings tr{" \
67 " background: #f6f6f6;}\n" \
68 "table#settings tr:nth-child(even){" \
69 " background: #eeeeee;}\n" \
70 "table#settings tr#modified{" \
71 " background: #FFFFBA;}\n" \
72 "table#settings tr#modified:nth-child(even){" \
73 " background: #ffffA0;}\n" \
74 "</style>\n"
76 int js_show_wl(struct tab *, struct karg *);
77 int pl_show_wl(struct tab *, struct karg *);
78 int https_show_wl(struct tab *, struct karg *);
79 int xtp_page_set(struct tab *, struct karg *);
80 int xtp_page_rt(struct tab *, struct karg *);
81 int marco(struct tab *, struct karg *);
82 int startpage(struct tab *, struct karg *);
83 const char * marco_message(int *);
84 void update_cookie_tabs(struct tab *apart_from);
85 int about_webkit(struct tab *, struct karg *);
86 int allthethings(struct tab *, struct karg *);
89 * If you change the index of any of these, correct the
90 * XT_XTP_TAB_MEANING_* macros in xombrero.h!
92 struct about_type about_list[] = {
93 { XT_URI_ABOUT_ABOUT, xtp_page_ab },
94 { XT_URI_ABOUT_ALLTHETHINGS, allthethings },
95 { XT_URI_ABOUT_BLANK, blank },
96 { XT_URI_ABOUT_CERTS, ca_cmd },
97 { XT_URI_ABOUT_COOKIEWL, cookie_show_wl },
98 { XT_URI_ABOUT_COOKIEJAR, xtp_page_cl },
99 { XT_URI_ABOUT_DOWNLOADS, xtp_page_dl },
100 { XT_URI_ABOUT_FAVORITES, xtp_page_fl },
101 { XT_URI_ABOUT_HELP, help },
102 { XT_URI_ABOUT_HISTORY, xtp_page_hl },
103 { XT_URI_ABOUT_JSWL, js_show_wl },
104 { XT_URI_ABOUT_SET, xtp_page_set },
105 { XT_URI_ABOUT_STATS, stats },
106 { XT_URI_ABOUT_MARCO, marco },
107 { XT_URI_ABOUT_STARTPAGE, startpage },
108 { XT_URI_ABOUT_PLUGINWL, pl_show_wl },
109 { XT_URI_ABOUT_HTTPS, https_show_wl },
110 { XT_URI_ABOUT_WEBKIT, about_webkit },
111 { XT_URI_ABOUT_SEARCH, xtp_page_sl },
112 { XT_URI_ABOUT_RUNTIME, xtp_page_rt },
113 { XT_URI_ABOUT_SECVIOLATION, NULL },
116 struct search_type {
117 const char *name;
118 const char *url;
119 } search_list[] = {
120 { "Google (SSL)", "https://encrypted.google.com/search?q=%s&&client=xombrero" },
121 { "Bing", "http://www.bing.com/search?q=%s" },
122 { "Yahoo", "http://search.yahoo.com/search?p=%s" },
123 { "DuckDuckGo", "https://duckduckgo.com/?q=%s" },
124 { "DuckDuckGo (HTML)", "https://duckduckgo.com/html?q=%s" },
125 { "DuckDuckGo (Lite)", "https://duckduckgo.com/lite?q=%s" },
126 { "Ixquick", "https://ixquick.com/do/search?q=%s" },
127 { "Startpage", "https://startpage.com/do/search?q=%s" },
131 * Session IDs.
132 * We use these to prevent people putting xxxt:// URLs on
133 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
135 #define XT_XTP_SES_KEY_SZ 8
136 #define XT_XTP_SES_KEY_HEX_FMT \
137 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8 "%02" PRIx8
139 int updating_fl_tabs = 0;
140 int updating_dl_tabs = 0;
141 int updating_hl_tabs = 0;
142 int updating_cl_tabs = 0;
143 int updating_sl_tabs = 0;
144 int updating_sv_tabs = 0;
145 int updating_set_tabs = 0;
146 struct download_list downloads;
148 size_t
149 about_list_size(void)
151 return (LENGTH(about_list));
154 gchar *
155 get_html_page(gchar *title, gchar *body, gchar *head, bool addstyles)
157 gchar *r;
159 r = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
160 "<head>\n"
161 "<title>%s</title>\n"
162 "%s"
163 "%s"
164 "</head>\n"
165 "<body>\n"
166 "<h1>%s</h1>\n"
167 "%s\n</body>\n"
168 "</html>",
169 title,
170 addstyles ? XT_PAGE_STYLE : "",
171 head,
172 title,
173 body);
175 return (r);
179 * Display a web page from a HTML string in memory, rather than from a URL
181 void
182 load_webkit_string(struct tab *t, const char *str, gchar *title, int nohist)
184 char file[PATH_MAX];
185 int i;
187 if (g_signal_handler_is_connected(t->wv, t->progress_handle))
188 g_signal_handler_disconnect(t->wv, t->progress_handle);
190 /* we set this to indicate we want to manually do navaction */
191 if (t->bfl && !nohist) {
192 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
193 if (t->item)
194 g_object_ref(t->item);
197 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
198 if (title) {
199 /* set t->xtp_meaning */
200 for (i = 0; i < LENGTH(about_list); i++)
201 if (!strcmp(title, about_list[i].name)) {
202 t->xtp_meaning = i;
203 break;
206 webkit_web_view_load_string(t->wv, str, NULL, encoding,
207 XT_XTP_STR);
208 #if GTK_CHECK_VERSION(2, 20, 0)
209 gtk_spinner_stop(GTK_SPINNER(t->spinner));
210 gtk_widget_hide(t->spinner);
211 #endif
212 snprintf(file, sizeof file, "%s" PS "%s", resource_dir, icons[0]);
213 xt_icon_from_file(t, file);
216 if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL &&
217 t->session_key != NULL) {
218 g_free(t->session_key);
219 t->session_key = NULL;
222 t->progress_handle = g_signal_connect(t->wv,
223 "notify::progress", G_CALLBACK(webview_progress_changed_cb), t);
227 blank(struct tab *t, struct karg *args)
229 if (t == NULL)
230 show_oops(NULL, "blank invalid parameters");
232 load_webkit_string(t, "", XT_URI_ABOUT_BLANK, 0);
234 return (0);
238 help(struct tab *t, struct karg *args)
240 char *page, *head, *body;
242 if (t == NULL)
243 show_oops(NULL, "help invalid parameters");
245 head = "<meta http-equiv=\"REFRESH\" content=\"0;"
246 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xombrero\">"
247 "</head>\n";
248 body = "xombrero man page <a href=\"http://opensource.conformal.com/"
249 "cgi-bin/man-cgi?xombrero\">http://opensource.conformal.com/"
250 "cgi-bin/man-cgi?xombrero</a>";
252 page = get_html_page(XT_NAME, body, head, FALSE);
254 load_webkit_string(t, page, XT_URI_ABOUT_HELP, 0);
255 g_free(page);
257 return (0);
261 stats(struct tab *t, struct karg *args)
263 char *page, *body, *s, line[64 * 1024];
264 uint64_t line_count = 0;
265 FILE *r_cookie_f;
267 if (t == NULL)
268 show_oops(NULL, "stats invalid parameters");
270 line[0] = '\0';
271 if (save_rejected_cookies) {
272 if ((r_cookie_f = fopen(rc_fname, "r"))) {
273 for (;;) {
274 s = fgets(line, sizeof line, r_cookie_f);
275 if (s == NULL || feof(r_cookie_f) ||
276 ferror(r_cookie_f))
277 break;
278 line_count++;
280 fclose(r_cookie_f);
281 snprintf(line, sizeof line,
282 "<br/>Cookies blocked(*) total: %" PRIu64,
283 line_count);
284 } else
285 show_oops(t, "Can't open blocked cookies file: %s",
286 strerror(errno));
289 body = g_strdup_printf(
290 "Cookies blocked(*) this session: %" PRIu64
291 "%s"
292 "<p><small><b>*</b> results vary based on settings</small></p>",
293 blocked_cookies,
294 line);
296 page = get_html_page("Statistics", body, "", 0);
297 g_free(body);
299 load_webkit_string(t, page, XT_URI_ABOUT_STATS, 0);
300 g_free(page);
302 return (0);
305 void
306 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
307 size_t cert_count, char *title)
309 gnutls_datum_t cinfo;
310 char *tmp, *body;
311 int i;
313 body = g_strdup("");
315 for (i = 0; i < cert_count; i++) {
316 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
317 &cinfo))
318 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.data);
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 = 0, certs_read;
339 struct stat sb;
340 gnutls_datum_t dt;
341 gnutls_x509_crt_t *c = NULL;
342 char *certs_buf = NULL, *s;
344 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
345 show_oops(t, "Can't open CA file: %s", ssl_ca_file);
346 return (1);
349 if (fstat(fileno(f), &sb) == -1) {
350 show_oops(t, "Can't stat CA file: %s", ssl_ca_file);
351 goto done;
354 certs_buf = g_malloc(sb.st_size + 1);
355 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
356 show_oops(t, "Can't read CA file: %s", strerror(errno));
357 goto done;
359 certs_buf[sb.st_size] = '\0';
361 s = certs_buf;
362 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
363 certs++;
364 s += strlen("BEGIN CERTIFICATE");
367 bzero(&dt, sizeof dt);
368 dt.data = (unsigned char *)certs_buf;
369 dt.size = sb.st_size;
370 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
371 certs_read = gnutls_x509_crt_list_import(c, (unsigned int *)&certs, &dt,
372 GNUTLS_X509_FMT_PEM, 0);
373 if (certs_read <= 0) {
374 show_oops(t, "No cert(s) available");
375 goto done;
377 show_certs(t, c, certs_read, "Certificate Authority Certificates");
378 done:
379 if (c)
380 g_free(c);
381 if (certs_buf)
382 g_free(certs_buf);
383 if (f)
384 fclose(f);
386 return (rv);
390 cookie_show_wl(struct tab *t, struct karg *args)
392 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
393 wl_show(t, args, "Cookie White List", &c_wl);
395 return (0);
399 js_show_wl(struct tab *t, struct karg *args)
401 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
402 wl_show(t, args, "JavaScript White List", &js_wl);
404 return (0);
408 cookie_cmd(struct tab *t, struct karg *args)
410 if (args->i & XT_SHOW)
411 wl_show(t, args, "Cookie White List", &c_wl);
412 else if (args->i & XT_WL_TOGGLE) {
413 args->i |= XT_WL_RELOAD;
414 toggle_cwl(t, args);
415 } else if (args->i & XT_SAVE) {
416 args->i |= XT_WL_RELOAD;
417 wl_save(t, args, XT_WL_COOKIE);
418 } else if (args->i & XT_DELETE) {
419 remove_cookie_all();
420 update_cookie_tabs(NULL);
423 return (0);
427 js_cmd(struct tab *t, struct karg *args)
429 if (args->i & XT_SHOW)
430 wl_show(t, args, "JavaScript White List", &js_wl);
431 else if (args->i & XT_SAVE) {
432 args->i |= XT_WL_RELOAD;
433 wl_save(t, args, XT_WL_JAVASCRIPT);
434 } else if (args->i & XT_WL_TOGGLE) {
435 args->i |= XT_WL_RELOAD;
436 toggle_js(t, args);
437 } else if (args->i & XT_DELETE)
438 show_oops(t, "'js delete' currently unimplemented");
440 return (0);
444 pl_show_wl(struct tab *t, struct karg *args)
446 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
447 wl_show(t, args, "Plugin White List", &pl_wl);
449 return (0);
453 pl_cmd(struct tab *t, struct karg *args)
455 if (args->i & XT_SHOW)
456 wl_show(t, args, "Plugin White List", &pl_wl);
457 else if (args->i & XT_SAVE) {
458 args->i |= XT_WL_RELOAD;
459 wl_save(t, args, XT_WL_PLUGIN);
460 } else if (args->i & XT_WL_TOGGLE) {
461 args->i |= XT_WL_RELOAD;
462 toggle_pl(t, args);
463 } else if (args->i & XT_DELETE)
464 show_oops(t, "'plugin delete' currently unimplemented");
466 return (0);
470 https_show_wl(struct tab *t, struct karg *args)
472 args->i = XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION;
473 wl_show(t, args, "HTTPS Force List", &force_https);
475 return (0);
479 https_cmd(struct tab *t, struct karg *args)
481 if (args->i & XT_SHOW)
482 wl_show(t, args, "HTTPS Force List", &force_https);
483 else if (args->i & XT_SAVE) {
484 args->i |= XT_WL_RELOAD;
485 wl_save(t, args, XT_WL_HTTPS);
486 } else if (args->i & XT_WL_TOGGLE) {
487 args->i |= XT_WL_RELOAD;
488 toggle_force_https(t, args);
489 } else if (args->i & XT_DELETE)
490 show_oops(t, "https delete' currently unimplemented");
492 return (0);
496 * cancel, remove, etc. downloads
498 void
499 xtp_handle_dl(struct tab *t, uint8_t cmd, int id, const char *query)
501 struct download find, *d = NULL;
502 #ifndef __MINGW32__
503 char *file = NULL;
504 const char *uri = NULL;
505 #endif
507 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
509 /* some commands require a valid download id */
510 if (cmd != XT_XTP_DL_LIST) {
511 /* lookup download in question */
512 find.id = id;
513 d = RB_FIND(download_list, &downloads, &find);
515 if (d == NULL) {
516 show_oops(t, "%s: no such download", __func__);
517 return;
521 /* decide what to do */
522 switch (cmd) {
523 case XT_XTP_DL_START:
524 /* our downloads always needs to be
525 * restarted if called from here
527 download_start(t, d, XT_DL_RESTART);
528 break;
529 case XT_XTP_DL_CANCEL:
530 webkit_download_cancel(d->download);
531 g_object_unref(d->download);
532 RB_REMOVE(download_list, &downloads, d);
533 break;
534 case XT_XTP_DL_UNLINK:
535 #ifdef __MINGW32__
536 /* XXX uri's aren't handled properly on windows? */
537 unlink(webkit_download_get_destination_uri(d->download));
538 #else
539 uri = webkit_download_get_destination_uri(d->download);
540 if ((file = g_filename_from_uri(uri, NULL, NULL)) != NULL) {
541 unlink(file);
542 g_free(file);
544 #endif
545 /* FALLTHROUGH */
546 case XT_XTP_DL_REMOVE:
547 webkit_download_cancel(d->download); /* just incase */
548 g_object_unref(d->download);
549 RB_REMOVE(download_list, &downloads, d);
550 break;
551 case XT_XTP_DL_LIST:
552 /* Nothing */
553 break;
554 default:
555 show_oops(t, "%s: unknown command", __func__);
556 break;
558 xtp_page_dl(t, NULL);
561 void
562 xtp_handle_hl(struct tab *t, uint8_t cmd, int id, const char *query)
564 struct history *h, *next, *ht;
565 int i = 1;
567 switch (cmd) {
568 case XT_XTP_HL_REMOVE:
569 /* walk backwards, as listed in reverse */
570 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
571 next = RB_PREV(history_list, &hl, h);
572 if (id == i) {
573 RB_REMOVE(history_list, &hl, h);
574 g_free((gpointer) h->title);
575 g_free((gpointer) h->uri);
576 g_free(h);
577 break;
579 i++;
581 break;
582 case XT_XTP_HL_REMOVE_ALL:
583 RB_FOREACH_SAFE(h, history_list, &hl, ht)
584 RB_REMOVE(history_list, &hl, h);
585 break;
586 case XT_XTP_HL_LIST:
587 /* Nothing - just xtp_page_hl() below */
588 break;
589 default:
590 show_oops(t, "%s: unknown command", __func__);
591 break;
594 xtp_page_hl(t, NULL);
597 /* remove a favorite */
598 void
599 remove_favorite(struct tab *t, int index)
601 char file[PATH_MAX], *title, *uri = NULL;
602 char *new_favs, *tmp;
603 FILE *f;
604 int i;
605 size_t len, lineno;
607 /* open favorites */
608 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
610 if ((f = fopen(file, "r")) == NULL) {
611 show_oops(t, "%s: can't open favorites: %s",
612 __func__, strerror(errno));
613 return;
616 /* build a string which will become the new favroites file */
617 new_favs = g_strdup("");
619 for (i = 1;;) {
620 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
621 if (feof(f) || ferror(f))
622 break;
623 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
624 if (len == 0) {
625 free(title);
626 title = NULL;
627 continue;
630 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
631 if (feof(f) || ferror(f)) {
632 show_oops(t, "%s: can't parse favorites %s",
633 __func__, strerror(errno));
634 goto clean;
638 /* as long as this isn't the one we are deleting add to file */
639 if (i != index) {
640 tmp = new_favs;
641 new_favs = g_strdup_printf("%s%s\n%s\n",
642 new_favs, title, uri);
643 g_free(tmp);
646 free(uri);
647 uri = NULL;
648 free(title);
649 title = NULL;
650 i++;
652 fclose(f);
654 /* write back new favorites file */
655 if ((f = fopen(file, "w")) == NULL) {
656 show_oops(t, "%s: can't open favorites: %s",
657 __func__, strerror(errno));
658 goto clean;
661 if (fwrite(new_favs, strlen(new_favs), 1, f) != 1)
662 show_oops(t, "%s: can't fwrite", __func__);
663 fclose(f);
665 clean:
666 if (uri)
667 free(uri);
668 if (title)
669 free(title);
671 g_free(new_favs);
675 add_favorite(struct tab *t, struct karg *args)
677 char file[PATH_MAX];
678 FILE *f;
679 char *line = NULL;
680 size_t urilen, linelen;
681 const gchar *uri, *title;
683 if (t == NULL)
684 return (1);
686 /* don't allow adding of xtp pages to favorites */
687 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
688 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
689 return (1);
692 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
693 if ((f = fopen(file, "r+")) == NULL) {
694 show_oops(t, "Can't open favorites file: %s", strerror(errno));
695 return (1);
698 title = get_title(t, FALSE);
699 uri = get_uri(t);
701 if (title == NULL || uri == NULL) {
702 show_oops(t, "can't add page to favorites");
703 goto done;
706 urilen = strlen(uri);
708 for (;;) {
709 if ((line = fparseln(f, &linelen, NULL, NULL, 0)) == NULL)
710 if (feof(f) || ferror(f))
711 break;
713 if (linelen == urilen && !strcmp(line, uri))
714 goto done;
716 free(line);
717 line = NULL;
720 fprintf(f, "\n%s\n%s", title, uri);
721 done:
722 if (line)
723 free(line);
724 fclose(f);
726 update_favorite_tabs(NULL);
728 return (0);
731 char *
732 search_engine_add(char *body, const char *name, const char *url,
733 const char *key, int select)
735 char *b = body;
737 body = g_strdup_printf("%s<tr>"
738 "<td>%s</td>"
739 "<td>%s</td>"
740 "<td style='text-align: center'>"
741 "<a href='%s%d/%s/%d/%d'>[ Select ]</a></td>"
742 "</tr>\n",
743 body,
744 name,
745 url,
746 XT_XTP_STR, XT_XTP_SL, key, XT_XTP_SL_SET, select);
747 g_free(b);
748 return (body);
751 void
752 xtp_handle_ab(struct tab *t, uint8_t cmd, int arg, const char *query)
754 char config[PATH_MAX];
755 char *cmdstr;
756 char **sv;
758 switch (cmd) {
759 case XT_XTP_AB_EDIT_CONF:
760 if (external_editor == NULL || strlen(external_editor) == 0) {
761 show_oops(t, "external_editor is unset");
762 break;
765 snprintf(config, sizeof config, "%s" PS ".%s", pwd->pw_dir,
766 XT_CONF_FILE);
767 sv = g_strsplit(external_editor, "<file>", -1);
768 cmdstr = g_strjoinv(config, sv);
769 g_strfreev(sv);
770 sv = g_strsplit_set(cmdstr, " \t", -1);
772 if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
773 NULL, NULL))
774 show_oops(t, "%s: could not spawn process", __func__);
776 g_strfreev(sv);
777 g_free(cmdstr);
778 break;
779 default:
780 show_oops(t, "%s, invalid about command", __func__);
781 break;
783 xtp_page_ab(t, NULL);
785 void
786 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg, const char *query)
788 struct karg args = {0};
790 switch (cmd) {
791 case XT_XTP_FL_LIST:
792 /* nothing, just the below call to xtp_page_fl() */
793 break;
794 case XT_XTP_FL_REMOVE:
795 remove_favorite(t, arg);
796 args.i = XT_DELETE;
797 break;
798 default:
799 show_oops(t, "%s: invalid favorites command", __func__);
800 break;
803 xtp_page_fl(t, &args);
806 void
807 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg, const char *query)
809 switch (cmd) {
810 case XT_XTP_CL_LIST:
811 /* nothing, just xtp_page_cl() */
812 break;
813 case XT_XTP_CL_REMOVE:
814 remove_cookie(arg);
815 break;
816 case XT_XTP_CL_REMOVE_DOMAIN:
817 remove_cookie_domain(arg);
818 break;
819 case XT_XTP_CL_REMOVE_ALL:
820 remove_cookie_all();
821 break;
822 default:
823 show_oops(t, "%s: unknown cookie xtp command", __func__);
824 break;
827 xtp_page_cl(t, NULL);
830 void
831 xtp_handle_sl(struct tab *t, uint8_t cmd, int arg, const char *query)
833 const char *search;
834 char *enc_search, *uri;
835 char **sv;
837 switch (cmd) {
838 case XT_XTP_SL_SET:
839 set_search_string((char *)search_list[arg].url);
840 if (save_runtime_setting("search_string", search_list[arg].url))
841 show_oops(t, "could not set search_string in runtime");
842 break;
843 default:
844 show_oops(t, "%s: unknown search xtp command", __func__);
845 break;
848 search = gtk_entry_get_text(GTK_ENTRY(t->search_entry)); /* static */
849 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
850 sv = g_strsplit(search_string, "%s", 2);
851 uri = g_strjoinv(enc_search, sv);
852 load_uri(t, uri);
853 g_free(enc_search);
854 g_strfreev(sv);
855 g_free(uri);
858 void
859 xtp_handle_sv(struct tab *t, uint8_t cmd, int id, const char *query)
861 SoupURI *soupuri = NULL;
862 struct karg args = {0};
863 struct secviolation find, *sv;
865 find.xtp_arg = id;
866 if ((sv = RB_FIND(secviolation_list, &svl, &find)) == NULL)
867 return;
869 args.ptr = (void *)sv->t;
870 args.s = sv->uri;
872 switch (cmd) {
873 case XT_XTP_SV_SHOW_NEW_CERT:
874 args.i = XT_SHOW;
875 if (cert_cmd(t, &args)) {
876 xtp_page_sv(t, &args);
877 return;
879 break;
880 case XT_XTP_SV_SHOW_CACHED_CERT:
881 args.i = XT_CACHE | XT_SHOW;
882 if (cert_cmd(t, &args)) {
883 xtp_page_sv(t, &args);
884 return;
886 break;
887 case XT_XTP_SV_ALLOW_SESSION:
888 soupuri = soup_uri_new(sv->uri);
889 wl_add(soupuri->host, &svil, 0);
890 load_uri(t, sv->uri);
891 focus_webview(t);
892 break;
893 case XT_XTP_SV_CACHE:
894 args.i = XT_CACHE;
895 if (cert_cmd(t, &args)) {
896 xtp_page_sv(t, &args);
897 return;
899 load_uri(t, sv->uri);
900 focus_webview(t);
901 break;
902 default:
903 show_oops(t, "%s: invalid secviolation command", __func__);
904 break;
907 g_free(sv->uri);
908 if (soupuri)
909 soup_uri_free(soupuri);
910 RB_REMOVE(secviolation_list, &svl, sv);
913 void
914 xtp_handle_rt(struct tab *t, uint8_t cmd, int id, const char *query)
916 struct set_reject *sr;
917 GHashTable *new_settings = NULL;
918 int modify;
919 char *val, *curval, *s;
920 int i = 0;
922 switch (cmd) {
923 case XT_XTP_RT_SAVE:
924 if (query == NULL)
925 break;
926 new_settings = soup_form_decode(query);
927 for (i = 0; i < get_settings_size(); ++i) {
928 if (!rs[i].activate)
929 continue;
930 sr = g_malloc(sizeof *sr);
931 val = (char *)g_hash_table_lookup(new_settings,
932 rs[i].name);
933 modify = 0;
934 switch (rs[i].type) {
935 case XT_S_INT: /* FALLTHROUGH */
936 case XT_S_BOOL:
937 if (atoi(val) != *rs[i].ival)
938 modify = 1;
939 break;
940 case XT_S_FLOAT:
941 if (atof(val) != *rs[i].fval)
942 modify = 1;
943 break;
944 case XT_S_STR:
945 s = (rs[i].sval == NULL || *rs[i].sval == NULL)
946 ? "" : *rs[i].sval;
947 if (rs[i].sval && g_strcmp0(val, s))
948 modify = 1;
949 else if (rs[i].s && rs[i].s->get) {
950 curval = rs[i].s->get(NULL);
951 if (g_strcmp0(val, curval))
952 modify = 1;
953 g_free(curval);
955 break;
956 case XT_S_INVALID: /* FALLTHROUGH */
957 default:
958 break;
960 if (rs[i].activate(val)) {
961 sr->name = g_strdup(rs[i].name);
962 sr->value = g_strdup(val);
963 TAILQ_INSERT_TAIL(&srl, sr, entry);
964 continue;
966 if (modify)
967 if (save_runtime_setting(rs[i].name, val))
968 show_oops(t, "error");
970 break;
971 default:
972 show_oops(t, "%s: invalid set command", __func__);
973 break;
976 if (new_settings)
977 g_hash_table_destroy(new_settings);
979 xtp_page_rt(t, NULL);
982 /* link an XTP class to it's session key and handler function */
983 struct xtp_despatch {
984 uint8_t xtp_class;
985 void (*handle_func)(struct tab *, uint8_t, int,
986 const char *query);
989 struct xtp_despatch xtp_despatches[] = {
990 { XT_XTP_DL, xtp_handle_dl },
991 { XT_XTP_HL, xtp_handle_hl },
992 { XT_XTP_FL, xtp_handle_fl },
993 { XT_XTP_CL, xtp_handle_cl },
994 { XT_XTP_SL, xtp_handle_sl },
995 { XT_XTP_AB, xtp_handle_ab },
996 { XT_XTP_SV, xtp_handle_sv },
997 { XT_XTP_RT, xtp_handle_rt },
998 { XT_XTP_INVALID, NULL }
1002 * generate a session key to secure xtp commands.
1003 * pass in a ptr to the key in question and it will
1004 * be modified in place.
1006 void
1007 generate_xtp_session_key(char **key)
1009 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
1011 /* free old key */
1012 if (*key)
1013 g_free(*key);
1015 /* make a new one */
1016 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
1017 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
1018 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
1019 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
1021 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
1025 * validate a xtp session key.
1026 * return (1) if OK
1029 validate_xtp_session_key(struct tab *t, char *key)
1031 if (t == NULL || t->session_key == NULL || key == NULL)
1032 return (0);
1034 if (strcmp(t->session_key, key) != 0) {
1035 show_oops(t, "%s: xtp session key mismatch possible spoof",
1036 __func__);
1037 return (0);
1040 return (1);
1044 * is the url xtp protocol? (xxxt://)
1045 * if so, parse and despatch correct bahvior
1048 parse_xtp_url(struct tab *t, const char *uri_str)
1050 SoupURI *uri = NULL;
1051 struct xtp_despatch *dsp, *dsp_match = NULL;
1052 int ret = FALSE;
1053 int class = 0;
1054 char **sv = NULL;
1057 * uri->host = class
1058 * sv[0] = session key
1059 * sv[1] = command
1060 * sv[2] = optional argument
1063 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, uri_str);
1065 if ((uri = soup_uri_new(uri_str)) == NULL)
1066 goto clean;
1067 if (strncmp(uri->scheme, XT_XTP_SCHEME, strlen(XT_XTP_SCHEME)))
1068 goto clean;
1069 if (uri->host == NULL || strlen(uri->host) == 0)
1070 goto clean;
1071 else
1072 class = atoi(uri->host);
1073 if ((sv = g_strsplit(uri->path + 1, "/", 3)) == NULL)
1074 goto clean;
1076 if (sv[0] == NULL || sv[1] == NULL)
1077 goto clean;
1079 dsp = xtp_despatches;
1080 class = atoi(uri->host);
1081 while (dsp->xtp_class) {
1082 if (dsp->xtp_class == class) {
1083 dsp_match = dsp;
1084 break;
1086 dsp++;
1089 /* did we find one atall? */
1090 if (dsp_match == NULL) {
1091 show_oops(t, "%s: no matching xtp despatch found", __func__);
1092 goto clean;
1095 /* check session key and call despatch function */
1096 if (validate_xtp_session_key(t, sv[0])) {
1097 ret = TRUE; /* all is well, this was a valid xtp request */
1098 if (sv[2])
1099 dsp_match->handle_func(t, atoi(sv[1]), atoi(sv[2]),
1100 uri->query);
1101 else
1102 dsp_match->handle_func(t, atoi(sv[1]), 0, uri->query);
1105 clean:
1106 if (uri)
1107 soup_uri_free(uri);
1108 if (sv)
1109 g_strfreev(sv);
1111 return (ret);
1115 * update all favorite tabs apart from one. Pass NULL if
1116 * you want to update all.
1118 void
1119 update_favorite_tabs(struct tab *apart_from)
1121 struct tab *t;
1123 if (!updating_fl_tabs) {
1124 updating_fl_tabs = 1; /* stop infinite recursion */
1125 TAILQ_FOREACH(t, &tabs, entry)
1126 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1127 && (t != apart_from))
1128 xtp_page_fl(t, NULL);
1129 updating_fl_tabs = 0;
1134 * update all download tabs apart from one. Pass NULL if
1135 * you want to update all.
1137 void
1138 update_download_tabs(struct tab *apart_from)
1140 struct tab *t;
1142 if (!updating_dl_tabs) {
1143 updating_dl_tabs = 1; /* stop infinite recursion */
1144 TAILQ_FOREACH(t, &tabs, entry)
1145 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
1146 && (t != apart_from))
1147 xtp_page_dl(t, NULL);
1148 updating_dl_tabs = 0;
1153 * update all cookie tabs apart from one. Pass NULL if
1154 * you want to update all.
1156 void
1157 update_cookie_tabs(struct tab *apart_from)
1159 struct tab *t;
1161 if (!updating_cl_tabs) {
1162 updating_cl_tabs = 1; /* stop infinite recursion */
1163 TAILQ_FOREACH(t, &tabs, entry)
1164 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
1165 && (t != apart_from))
1166 xtp_page_cl(t, NULL);
1167 updating_cl_tabs = 0;
1172 * update all history tabs apart from one. Pass NULL if
1173 * you want to update all.
1175 void
1176 update_history_tabs(struct tab *apart_from)
1178 struct tab *t;
1180 if (!updating_hl_tabs) {
1181 updating_hl_tabs = 1; /* stop infinite recursion */
1182 TAILQ_FOREACH(t, &tabs, entry)
1183 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
1184 && (t != apart_from))
1185 xtp_page_hl(t, NULL);
1186 updating_hl_tabs = 0;
1191 * update all search tabs apart from one. Pass NULL if
1192 * you want to update all.
1194 void
1195 update_search_tabs(struct tab *apart_from)
1197 struct tab *t;
1199 if (!updating_sl_tabs) {
1200 updating_sl_tabs = 1; /* stop infinite recursion */
1201 TAILQ_FOREACH(t, &tabs, entry)
1202 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_SL)
1203 && (t != apart_from))
1204 xtp_page_sl(t, NULL);
1205 updating_sl_tabs = 0;
1210 xtp_page_ab(struct tab *t, struct karg *args)
1212 char *page, *body;
1214 if (t == NULL)
1215 show_oops(NULL, "about invalid parameters");
1217 generate_xtp_session_key(&t->session_key);
1219 body = g_strdup_printf("<b>Version: %s</b>"
1220 #ifdef XOMBRERO_BUILDSTR
1221 "<br><b>Build: %s</b>"
1222 #endif
1223 "<br><b>WebKit: %d.%d.%d</b>"
1224 "<br><b>User Agent: %d.%d</b>"
1225 #ifdef WEBKITGTK_API_VERSION
1226 "<br><b>WebKit API: %.1f</b>"
1227 #endif
1228 "<br><b>Configuration: %s" PS "<a href='%s%d/%s/%d'>.%s</a>"
1229 " (remember to restart the browser after any changes)</b>"
1230 "<p>"
1231 "Authors:"
1232 "<ul>"
1233 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1234 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1235 "<li>Edd Barrett &lt;vext01@gmail.com&gt;</li>"
1236 "<li>Todd T. Fries &lt;todd@fries.net&gt;</li>"
1237 "<li>Raphael Graf &lt;r@undefined.ch&gt;</li>"
1238 "<li>Michal Mazurek &lt;akfaew@jasminek.net&gt;</li>"
1239 "<li>Josh Rickmar &lt;jrick@devio.us&gt;</li>"
1240 "</ul>"
1241 "Copyrights and licenses can be found on the xombrero "
1242 "<a href=\"http://opensource.conformal.com/wiki/xombrero\">website</a>"
1243 "</p>",
1244 #ifdef XOMBRERO_BUILDSTR
1245 version, XOMBRERO_BUILDSTR,
1246 #else
1247 version,
1248 #endif
1249 WEBKIT_MAJOR_VERSION, WEBKIT_MINOR_VERSION, WEBKIT_MICRO_VERSION,
1250 WEBKIT_USER_AGENT_MAJOR_VERSION, WEBKIT_USER_AGENT_MINOR_VERSION
1251 #ifdef WEBKITGTK_API_VERSION
1252 ,WEBKITGTK_API_VERSION
1253 #endif
1254 ,pwd->pw_dir,
1255 XT_XTP_STR,
1256 XT_XTP_AB,
1257 t->session_key,
1258 XT_XTP_AB_EDIT_CONF,
1259 XT_CONF_FILE
1262 page = get_html_page("About", body, "", 0);
1263 g_free(body);
1265 load_webkit_string(t, page, XT_URI_ABOUT_ABOUT, 0);
1267 g_free(page);
1269 return (0);
1272 /* show a list of favorites (bookmarks) */
1274 xtp_page_fl(struct tab *t, struct karg *args)
1276 char file[PATH_MAX];
1277 FILE *f;
1278 char *uri = NULL, *title = NULL;
1279 size_t len, lineno = 0;
1280 int i, failed = 0;
1281 char *body, *tmp, *page = NULL;
1282 const char delim[3] = {'\\', '\\', '\0'};
1284 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1286 if (t == NULL)
1287 warn("%s: bad param", __func__);
1289 generate_xtp_session_key(&t->session_key);
1291 /* open favorites */
1292 snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_FAVS_FILE);
1293 if ((f = fopen(file, "r")) == NULL) {
1294 show_oops(t, "Can't open favorites file: %s", strerror(errno));
1295 return (1);
1298 /* body */
1299 if (args && args->i & XT_DELETE)
1300 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1301 "<th style='width: 40px'>&#35;</th><th>Link</th>"
1302 "<th style='width: 40px'>Rm</th></tr>\n");
1303 else
1304 body = g_strdup_printf("<table style='table-layout:fixed'><tr>"
1305 "<th style='width: 40px'>&#35;</th><th>Link</th></tr>\n");
1307 for (i = 1;;) {
1308 if ((title = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1309 break;
1310 if (strlen(title) == 0) {
1311 free(title);
1312 title = NULL;
1313 continue;
1316 if ((uri = fparseln(f, &len, &lineno, delim, 0)) == NULL)
1317 if (feof(f) || ferror(f)) {
1318 show_oops(t, "favorites file corrupt");
1319 failed = 1;
1320 break;
1323 tmp = body;
1324 if (args && args->i & XT_DELETE)
1325 body = g_strdup_printf("%s<tr>"
1326 "<td>%d</td>"
1327 "<td><a href='%s'>%s</a></td>"
1328 "<td style='text-align: center'>"
1329 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1330 "</tr>\n",
1331 body, i, uri, title,
1332 XT_XTP_STR, XT_XTP_FL, t->session_key,
1333 XT_XTP_FL_REMOVE, i);
1334 else
1335 body = g_strdup_printf("%s<tr>"
1336 "<td>%d</td>"
1337 "<td><a href='%s'>%s</a></td>"
1338 "</tr>\n",
1339 body, i, uri, title);
1340 g_free(tmp);
1342 free(uri);
1343 uri = NULL;
1344 free(title);
1345 title = NULL;
1346 i++;
1348 fclose(f);
1350 /* if none, say so */
1351 if (i == 1) {
1352 tmp = body;
1353 body = g_strdup_printf("%s<tr>"
1354 "<td colspan='%d' style='text-align: center'>"
1355 "No favorites - To add one use the 'favadd' command."
1356 "</td></tr>", body, (args && args->i & XT_DELETE) ? 3 : 2);
1357 g_free(tmp);
1360 tmp = body;
1361 body = g_strdup_printf("%s</table>", body);
1362 g_free(tmp);
1364 if (uri)
1365 free(uri);
1366 if (title)
1367 free(title);
1369 /* render */
1370 if (!failed) {
1371 page = get_html_page("Favorites", body, "", 1);
1372 load_webkit_string(t, page, XT_URI_ABOUT_FAVORITES, 0);
1373 g_free(page);
1376 update_favorite_tabs(t);
1378 if (body)
1379 g_free(body);
1381 return (failed);
1385 * Return a new string with a download row (in html)
1386 * appended. Old string is freed.
1388 char *
1389 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
1392 WebKitDownloadStatus stat;
1393 const gchar *destination;
1394 char *status_html = NULL, *cmd_html = NULL, *new_html;
1395 gdouble progress;
1396 char cur_sz[FMT_SCALED_STRSIZE];
1397 char tot_sz[FMT_SCALED_STRSIZE];
1398 char *xtp_prefix;
1400 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
1402 /* All actions wil take this form:
1403 * xxxt://class/seskey
1405 xtp_prefix = g_strdup_printf("%s%d/%s/",
1406 XT_XTP_STR, XT_XTP_DL, t->session_key);
1408 stat = webkit_download_get_status(dl->download);
1410 switch (stat) {
1411 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
1412 status_html = g_strdup_printf("Finished");
1413 cmd_html = g_strdup_printf(
1414 "<a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1415 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1416 XT_XTP_DL_UNLINK, dl->id);
1417 break;
1418 case WEBKIT_DOWNLOAD_STATUS_STARTED:
1419 /* gather size info */
1420 progress = 100 * webkit_download_get_progress(dl->download);
1422 fmt_scaled(
1423 webkit_download_get_current_size(dl->download), cur_sz);
1424 fmt_scaled(
1425 webkit_download_get_total_size(dl->download), tot_sz);
1427 status_html = g_strdup_printf(
1428 "<div style='width: 100%%' align='center'>"
1429 "<div class='progress-outer'>"
1430 "<div class='progress-inner' style='width: %.2f%%'>"
1431 "</div></div></div>"
1432 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
1433 progress, cur_sz, tot_sz, progress);
1435 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
1436 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
1438 break;
1439 /* LLL */
1440 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
1441 status_html = g_strdup_printf("Cancelled");
1442 cmd_html = g_strdup_printf(
1443 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1444 xtp_prefix, XT_XTP_DL_START, dl->id,
1445 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1446 XT_XTP_DL_UNLINK, dl->id);
1447 break;
1448 case WEBKIT_DOWNLOAD_STATUS_ERROR:
1449 status_html = g_strdup_printf("Error!");
1450 cmd_html = g_strdup_printf(
1451 "<a href='%s%d/%d'>Restart</a> / <a href='%s%d/%d'>Remove</a> / <a href='%s%d/%d'>Unlink</a>",
1452 xtp_prefix, XT_XTP_DL_START, dl->id,
1453 xtp_prefix, XT_XTP_DL_REMOVE, dl->id, xtp_prefix,
1454 XT_XTP_DL_UNLINK, dl->id);
1455 break;
1456 case WEBKIT_DOWNLOAD_STATUS_CREATED:
1457 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Start</a> / <a href='%s%d/%d'>Cancel</a>",
1458 xtp_prefix, XT_XTP_DL_START, dl->id, xtp_prefix,
1459 XT_XTP_DL_CANCEL, dl->id);
1460 status_html = g_strdup_printf("Created");
1461 break;
1462 default:
1463 show_oops(t, "%s: unknown download status", __func__);
1466 destination = webkit_download_get_destination_uri(dl->download);
1467 /* we might not have a destination set yet */
1468 if (!destination)
1469 destination = webkit_download_get_suggested_filename(dl->download);
1470 new_html = g_strdup_printf(
1471 "%s\n<tr><td>%s</td><td>%s</td>"
1472 "<td style='text-align:center'>%s</td></tr>\n",
1473 html, basename((char *)destination),
1474 status_html, cmd_html);
1475 g_free(html);
1477 if (status_html)
1478 g_free(status_html);
1480 if (cmd_html)
1481 g_free(cmd_html);
1483 g_free(xtp_prefix);
1485 return new_html;
1488 /* cookie management XTP page */
1490 xtp_page_cl(struct tab *t, struct karg *args)
1492 char *body, *page, *tmp;
1493 int i = 1; /* all ids start 1 */
1494 int domain_id = 0;
1495 GSList *sc, *pc, *pc_start;
1496 SoupCookie *c;
1497 char *type, *table_headers, *last_domain;
1499 DNPRINTF(XT_D_CMD, "%s", __func__);
1501 if (t == NULL) {
1502 show_oops(NULL, "%s invalid parameters", __func__);
1503 return (1);
1506 generate_xtp_session_key(&t->session_key);
1508 /* table headers */
1509 table_headers = g_strdup_printf("<table><tr>"
1510 "<th>Type</th>"
1511 "<th>Name</th>"
1512 "<th style='width:200px'>Value</th>"
1513 "<th>Path</th>"
1514 "<th>Expires</th>"
1515 "<th>Secure</th>"
1516 "<th>HTTP<br />only</th>"
1517 "<th style='width:40px'>Rm</th></tr>\n");
1519 sc = soup_cookie_jar_all_cookies(s_cookiejar);
1520 pc = soup_cookie_jar_all_cookies(p_cookiejar);
1521 pc_start = pc;
1523 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1524 "[ Remove All Cookies From All Domains ]</a></div>\n",
1525 XT_XTP_STR, XT_XTP_CL, t->session_key, XT_XTP_CL_REMOVE_ALL);
1527 last_domain = g_strdup("");
1528 for (; sc; sc = sc->next) {
1529 c = sc->data;
1531 if (strcmp(last_domain, c->domain) != 0) {
1532 /* new domain */
1533 domain_id ++;
1534 g_free(last_domain);
1535 last_domain = g_strdup(c->domain);
1537 if (body != NULL) {
1538 tmp = body;
1539 body = g_strdup_printf("%s</table>"
1540 "<h2>%s</h2><div align=\"center\">"
1541 "<a href='%s%d/%s/%d/%d'>"
1542 "[ Remove All From This Domain ]"
1543 "</a></div>%s\n",
1544 body, c->domain,
1545 XT_XTP_STR, XT_XTP_CL, t->session_key,
1546 XT_XTP_CL_REMOVE_DOMAIN, domain_id,
1547 table_headers);
1548 g_free(tmp);
1549 } else {
1550 /* first domain */
1551 body = g_strdup_printf("<h2>%s</h2>"
1552 "<div align=\"center\">"
1553 "<a href='%s%d/%s/%d/%d'>"
1554 "[ Remove All From This Domain ]</a></div>%s\n",
1555 c->domain, XT_XTP_STR, XT_XTP_CL,
1556 t->session_key, XT_XTP_CL_REMOVE_DOMAIN,
1557 domain_id, table_headers);
1561 type = "Session";
1562 for (pc = pc_start; pc; pc = pc->next)
1563 if (soup_cookie_equal(pc->data, c)) {
1564 type = "Session + Persistent";
1565 break;
1568 tmp = body;
1569 body = g_strdup_printf(
1570 "%s\n<tr>"
1571 "<td>%s</td>"
1572 "<td style='word-wrap:normal'>%s</td>"
1573 "<td>"
1574 " <textarea rows='4'>%s</textarea>"
1575 "</td>"
1576 "<td>%s</td>"
1577 "<td>%s</td>"
1578 "<td>%d</td>"
1579 "<td>%d</td>"
1580 "<td style='text-align:center'>"
1581 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1582 body,
1583 type,
1584 c->name,
1585 c->value,
1586 c->path,
1587 c->expires ?
1588 soup_date_to_string(c->expires, SOUP_DATE_COOKIE) : "",
1589 c->secure,
1590 c->http_only,
1592 XT_XTP_STR,
1593 XT_XTP_CL,
1594 t->session_key,
1595 XT_XTP_CL_REMOVE,
1599 g_free(tmp);
1600 i++;
1603 soup_cookies_free(sc);
1604 soup_cookies_free(pc);
1606 /* small message if there are none */
1607 if (i == 1) {
1608 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1609 "colspan='8'>No Cookies</td></tr>\n", table_headers);
1611 tmp = body;
1612 body = g_strdup_printf("%s</table>", body);
1613 g_free(tmp);
1615 page = get_html_page("Cookie Jar", body, "", TRUE);
1616 g_free(body);
1617 g_free(table_headers);
1618 g_free(last_domain);
1620 load_webkit_string(t, page, XT_URI_ABOUT_COOKIEJAR, 0);
1621 update_cookie_tabs(t);
1623 g_free(page);
1625 return (0);
1629 xtp_page_hl(struct tab *t, struct karg *args)
1631 char *body, *page, *tmp;
1632 struct history *h;
1633 int i = 1; /* all ids start 1 */
1635 DNPRINTF(XT_D_CMD, "%s", __func__);
1637 if (t == NULL) {
1638 show_oops(NULL, "%s invalid parameters", __func__);
1639 return (1);
1642 generate_xtp_session_key(&t->session_key);
1644 /* body */
1645 body = g_strdup_printf("<div align=\"center\"><a href=\"%s%d/%s/%d\">"
1646 "[ Remove All ]</a></div>"
1647 "<table style='table-layout:fixed'><tr>"
1648 "<th>URI</th><th>Title</th><th>Last visited</th>"
1649 "<th style='width: 40px'>Rm</th></tr>\n",
1650 XT_XTP_STR, XT_XTP_HL, t->session_key, XT_XTP_HL_REMOVE_ALL);
1652 RB_FOREACH_REVERSE(h, history_list, &hl) {
1653 tmp = body;
1654 body = g_strdup_printf(
1655 "%s\n<tr>"
1656 "<td><a href='%s'>%s</a></td>"
1657 "<td>%s</td>"
1658 "<td>%s</td>"
1659 "<td style='text-align: center'>"
1660 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
1661 body, h->uri, h->uri, h->title, ctime(&h->time),
1662 XT_XTP_STR, XT_XTP_HL, t->session_key,
1663 XT_XTP_HL_REMOVE, i);
1665 g_free(tmp);
1666 i++;
1669 /* small message if there are none */
1670 if (i == 1) {
1671 tmp = body;
1672 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
1673 "colspan='4'>No History</td></tr>\n", body);
1674 g_free(tmp);
1677 tmp = body;
1678 body = g_strdup_printf("%s</table>", body);
1679 g_free(tmp);
1681 page = get_html_page("History", body, "", TRUE);
1682 g_free(body);
1685 * update all history manager tabs as the xtp session
1686 * key has now changed. No need to update the current tab.
1687 * Already did that above.
1689 update_history_tabs(t);
1691 load_webkit_string(t, page, XT_URI_ABOUT_HISTORY, 0);
1692 g_free(page);
1694 return (0);
1698 * Generate a web page detailing the status of any downloads
1701 xtp_page_dl(struct tab *t, struct karg *args)
1703 struct download *dl;
1704 char *body, *page, *tmp;
1705 char *ref;
1706 int n_dl = 1;
1708 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
1710 if (t == NULL) {
1711 show_oops(NULL, "%s invalid parameters", __func__);
1712 return (1);
1715 generate_xtp_session_key(&t->session_key);
1717 /* header - with refresh so as to update */
1718 if (refresh_interval >= 1)
1719 ref = g_strdup_printf(
1720 "<meta http-equiv='refresh' content='%u"
1721 ";url=%s%d/%s/%d' />\n",
1722 refresh_interval,
1723 XT_XTP_STR,
1724 XT_XTP_DL,
1725 t->session_key,
1726 XT_XTP_DL_LIST);
1727 else
1728 ref = g_strdup("");
1730 body = g_strdup_printf("<div align='center'>"
1731 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
1732 "</p><table><tr><th style='width: 60%%'>"
1733 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
1734 XT_XTP_STR, XT_XTP_DL, t->session_key, XT_XTP_DL_LIST);
1736 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
1737 body = xtp_page_dl_row(t, body, dl);
1738 n_dl++;
1741 /* message if no downloads in list */
1742 if (n_dl == 1) {
1743 tmp = body;
1744 body = g_strdup_printf("%s\n<tr><td colspan='3'"
1745 " style='text-align: center'>"
1746 "No downloads</td></tr>\n", body);
1747 g_free(tmp);
1750 tmp = body;
1751 body = g_strdup_printf("%s</table></div>", body);
1752 g_free(tmp);
1754 page = get_html_page("Downloads", body, ref, 1);
1755 g_free(ref);
1756 g_free(body);
1759 * update all download manager tabs as the xtp session
1760 * key has now changed. No need to update the current tab.
1761 * Already did that above.
1763 update_download_tabs(t);
1765 load_webkit_string(t, page, XT_URI_ABOUT_DOWNLOADS, 0);
1766 g_free(page);
1768 return (0);
1772 xtp_page_sl(struct tab *t, struct karg *args)
1774 int i;
1775 char *page, *body, *tmp;
1777 DNPRINTF(XT_D_SEARCH, "%s", __func__);
1779 generate_xtp_session_key(&t->session_key);
1781 if (t == NULL) {
1782 show_oops(NULL, "%s invalid parameters", __func__);
1783 return (1);
1786 body = g_strdup_printf("<p>The xombrero authors will not choose a "
1787 "default search engine for you. What follows is a list of search "
1788 "engines (in no particular order) you may be interested in. "
1789 "To permanently choose a search engine, click [ Select ] to save "
1790 "<tt>search_string</tt> as a runtime setting, or set "
1791 "<tt>search_string</tt> to the appropriate URL in your xombrero "
1792 "configuration.</p>");
1794 tmp = body;
1795 body = g_strdup_printf("%s\n<table style='table-layout:fixed'><tr>"
1796 "<th style='width: 200px'>Name</th><th>URL</th>"
1797 "<th style='width: 100px'>Select</th></tr>\n", body);
1798 g_free(tmp);
1800 for (i = 0; i < (sizeof search_list / sizeof (struct search_type)); ++i)
1801 body = search_engine_add(body, search_list[i].name,
1802 search_list[i].url, t->session_key, i);
1804 tmp = body;
1805 body = g_strdup_printf("%s</table>", body);
1806 g_free(tmp);
1808 page = get_html_page("Choose a search engine", body, "", 1);
1809 g_free(body);
1812 * update all search tabs as the xtp session key has now changed. No
1813 * need to update the current tab. Already did that above.
1815 update_search_tabs(t);
1817 load_webkit_string(t, page, XT_URI_ABOUT_SEARCH, 0);
1818 g_free(page);
1820 return (0);
1824 xtp_page_sv(struct tab *t, struct karg *args)
1826 SoupURI *soupuri;
1827 static int arg = 0;
1828 struct secviolation find, *sv;
1829 char *page, *body;
1831 if (t == NULL)
1832 show_oops(NULL, "secviolation invalid parameters");
1834 generate_xtp_session_key(&t->session_key);
1836 if (args == NULL) {
1837 find.xtp_arg = t->xtp_arg;
1838 sv = RB_FIND(secviolation_list, &svl, &find);
1839 if (sv == NULL)
1840 return (-1);
1841 } else {
1842 sv = g_malloc(sizeof(struct secviolation));
1843 sv->xtp_arg = ++arg;
1844 t->xtp_arg = arg;
1845 sv->t = t;
1846 sv->uri = args->s;
1847 RB_INSERT(secviolation_list, &svl, sv);
1850 if (sv->uri == NULL || (soupuri = soup_uri_new(sv->uri)) == NULL)
1851 return (-1);
1853 body = g_strdup_printf(
1854 "<p><b>You tried to access %s</b>."
1855 "<p><b>The site's security certificate has been modified.</b>"
1856 "<p>The domain of the page you have tried to access, <b>%s</b>, "
1857 "has a different remote certificate then the local cached version "
1858 "from a previous visit. As a security precaution to help prevent "
1859 "against man-in-the-middle attacks, please choose one of the "
1860 "following actions to continue, or disable the "
1861 "<tt>warn_cert_changes</tt> setting in your xombrero "
1862 "configuration."
1863 "<p><b>Choose an action:"
1864 "<br><a href='%s%d/%s/%d/%d'>Allow for this session</a>"
1865 "<br><a href='%s%d/%s/%d/%d'>Cache new certificate</a>"
1866 "<br><a href='%s%d/%s/%d/%d'>Show cached certificate</a>"
1867 "<br><a href='%s%d/%s/%d/%d'>Show new certificate</a>",
1868 sv->uri,
1869 soupuri->host,
1870 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_ALLOW_SESSION,
1871 sv->xtp_arg,
1872 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_CACHE,
1873 sv->xtp_arg,
1874 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_CACHED_CERT,
1875 sv->xtp_arg,
1876 XT_XTP_STR, XT_XTP_SV, t->session_key, XT_XTP_SV_SHOW_NEW_CERT,
1877 sv->xtp_arg);
1879 page = get_html_page("Security Violation", body, "", 0);
1880 g_free(body);
1882 load_webkit_string(t, page, XT_URI_ABOUT_SECVIOLATION, 1);
1884 g_free(page);
1885 if (soupuri)
1886 soup_uri_free(soupuri);
1888 return (0);
1892 startpage(struct tab *t, struct karg *args)
1894 char *page, *body, *b;
1895 struct sp *s;
1897 if (t == NULL)
1898 show_oops(NULL, "startpage invalid parameters");
1900 body = g_strdup_printf("<b>Startup Exception(s):</b><p>");
1902 TAILQ_FOREACH(s, &spl, entry) {
1903 b = body;
1904 body = g_strdup_printf("%s%s<br>", body, s->line);
1905 g_free(b);
1908 page = get_html_page("Startup Exception", body, "", 0);
1909 g_free(body);
1911 load_webkit_string(t, page, XT_URI_ABOUT_STARTPAGE, 0);
1912 g_free(page);
1914 return (0);
1917 void
1918 startpage_add(const char *fmt, ...)
1920 va_list ap;
1921 char *msg;
1922 struct sp *s;
1924 if (fmt == NULL)
1925 return;
1927 va_start(ap, fmt);
1928 if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
1929 errx(1, "startpage_add failed");
1930 va_end(ap);
1932 s = g_malloc0(sizeof *s);
1933 s->line = msg;
1935 TAILQ_INSERT_TAIL(&spl, s, entry);
1937 gchar *show_g_object_settings(GObject *, char *, int);
1939 char *
1940 xt_g_object_serialize(GValue *value, const gchar *tname, char *str, int recurse)
1942 int typeno = 0;
1943 char *valstr, *tmpstr, *tmpsettings;
1944 GObject *object;
1946 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(value) );
1947 switch ( typeno ) {
1948 case G_TYPE_ENUM:
1949 valstr = g_strdup_printf("%d",
1950 g_value_get_enum(value));
1951 break;
1952 case G_TYPE_CHAR:
1953 valstr = g_strdup_printf("%c",
1954 #if GLIB_CHECK_VERSION(2, 32, 0)
1955 g_value_get_schar(value));
1956 #else
1957 g_value_get_char(value));
1958 #endif
1959 break;
1960 case G_TYPE_UCHAR:
1961 valstr = g_strdup_printf("%c",
1962 g_value_get_uchar(value));
1963 break;
1964 case G_TYPE_LONG:
1965 valstr = g_strdup_printf("%ld",
1966 g_value_get_long(value));
1967 break;
1968 case G_TYPE_ULONG:
1969 valstr = g_strdup_printf("%ld",
1970 g_value_get_ulong(value));
1971 break;
1972 case G_TYPE_INT:
1973 valstr = g_strdup_printf("%d",
1974 g_value_get_int(value));
1975 break;
1976 case G_TYPE_INT64:
1977 valstr = g_strdup_printf("%" PRIo64,
1978 (int64_t) g_value_get_int64(value));
1979 break;
1980 case G_TYPE_UINT:
1981 valstr = g_strdup_printf("%d",
1982 g_value_get_uint(value));
1983 break;
1984 case G_TYPE_UINT64:
1985 valstr = g_strdup_printf("%" PRIu64,
1986 (uint64_t) g_value_get_uint64(value));
1987 break;
1988 case G_TYPE_FLAGS:
1989 valstr = g_strdup_printf("0x%x",
1990 g_value_get_flags(value));
1991 break;
1992 case G_TYPE_BOOLEAN:
1993 valstr = g_strdup_printf("%s",
1994 g_value_get_boolean(value) ? "TRUE" : "FALSE");
1995 break;
1996 case G_TYPE_FLOAT:
1997 valstr = g_strdup_printf("%f",
1998 g_value_get_float(value));
1999 break;
2000 case G_TYPE_DOUBLE:
2001 valstr = g_strdup_printf("%f",
2002 g_value_get_double(value));
2003 break;
2004 case G_TYPE_STRING:
2005 valstr = g_strdup_printf("\"%s\"",
2006 g_value_get_string(value));
2007 break;
2008 case G_TYPE_POINTER:
2009 valstr = g_strdup_printf("%p",
2010 g_value_get_pointer(value));
2011 break;
2012 case G_TYPE_OBJECT:
2013 object = g_value_get_object(value);
2014 if (object != NULL) {
2015 if (recurse) {
2016 tmpstr = g_strdup_printf("%s ", str);
2017 tmpsettings = show_g_object_settings( object,
2018 tmpstr, recurse);
2019 g_free(tmpstr);
2021 if (strrchr(tmpsettings, '\n') != NULL) {
2022 valstr = g_strdup_printf("%s%s }",
2023 tmpsettings, str);
2024 g_free(tmpsettings);
2025 } else {
2026 valstr = tmpsettings;
2028 } else {
2029 valstr = g_strdup_printf("<...>");
2031 } else {
2032 valstr = g_strdup_printf("settings[] = NULL");
2034 break;
2035 default:
2036 valstr = g_strdup_printf("type %s unhandled", tname);
2038 return valstr;
2041 gchar *
2042 show_g_object_settings(GObject *o, char *str, int recurse)
2044 char *b, *p, *body, *valstr, *tmpstr;
2045 guint n_props = 0;
2046 int i, typeno = 0;
2047 GParamSpec *pspec;
2048 const gchar *tname;
2049 GValue value;
2050 GParamSpec **proplist;
2051 const gchar *name;
2053 if (!G_IS_OBJECT(o)) {
2054 fprintf(stderr, "%s is not a g_object\n", str);
2055 return g_strdup("");
2057 proplist = g_object_class_list_properties(
2058 G_OBJECT_GET_CLASS(o), &n_props);
2060 if (GTK_IS_WIDGET(o)) {
2061 name = gtk_widget_get_name(GTK_WIDGET(o));
2062 } else {
2063 name = "settings";
2065 if (n_props == 0) {
2066 body = g_strdup_printf("%s[0] = { }", name);
2067 goto end_show_g_objects;
2070 body = g_strdup_printf("%s[%d] = {\n", name, n_props);
2071 for (i=0; i < n_props; i++) {
2072 pspec = proplist[i];
2073 tname = G_OBJECT_TYPE_NAME(pspec);
2074 bzero(&value, sizeof value);
2075 valstr = NULL;
2077 if (!(pspec->flags & G_PARAM_READABLE))
2078 valstr = g_strdup_printf("not a readable property");
2079 else {
2080 g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
2081 g_object_get_property(G_OBJECT(o), pspec->name,
2082 &value);
2083 typeno = G_TYPE_FUNDAMENTAL( G_VALUE_TYPE(&value) );
2086 /* based on the type, recurse and display values */
2087 if (valstr == NULL) {
2088 valstr = xt_g_object_serialize(&value, tname, str,
2089 recurse);
2092 tmpstr = g_strdup_printf("%-13s %s%s%s,", tname, pspec->name,
2093 (typeno == G_TYPE_OBJECT) ? "." : " = ", valstr);
2094 b = body;
2096 #define XT_G_OBJECT_SPACING 50
2097 p = strrchr(tmpstr, '\n');
2098 if (p == NULL && strlen(tmpstr) > XT_G_OBJECT_SPACING) {
2099 body = g_strdup_printf(
2100 "%s%s %-50s\n%s %50s /* %3d flags=0x%08x */\n",
2101 body, str, tmpstr, str, "", i, pspec->flags);
2102 } else {
2103 char *fmt;
2104 int strspaces;
2105 if (p == NULL)
2106 strspaces = XT_G_OBJECT_SPACING;
2107 else
2108 strspaces = strlen(tmpstr) - (strlen(p) - strlen(str)) + XT_G_OBJECT_SPACING + 5;
2109 fmt = g_strdup_printf("%%s%%s %%-%ds /* %%3d flags=0x%%08x */\n", strspaces);
2110 body = g_strdup_printf(fmt, body, str, tmpstr, i, pspec->flags);
2111 g_free(fmt);
2113 g_free(tmpstr);
2114 g_free(b);
2115 g_free(valstr);
2117 end_show_g_objects:
2118 g_free(proplist);
2119 return (body);
2122 char *
2123 xt_append_settings(char *str, GObject *object, char *name, int recurse)
2125 char *newstr, *settings;
2127 settings = show_g_object_settings(object, name, recurse);
2128 if (str == NULL)
2129 str = g_strdup("");
2131 newstr = g_strdup_printf("%s%s %s%s };\n", str, name, settings, name);
2132 g_free(str);
2134 return newstr;
2138 about_webkit(struct tab *t, struct karg *arg)
2140 char *page, *body, *settingstr;
2142 settingstr = xt_append_settings(NULL, G_OBJECT(t->settings),
2143 "t->settings", 0);
2144 body = g_strdup_printf("<pre>%s</pre>\n", settingstr);
2145 g_free(settingstr);
2147 page = get_html_page("About Webkit", body, "", 0);
2148 g_free(body);
2150 load_webkit_string(t, page, XT_URI_ABOUT_WEBKIT, 0);
2151 g_free(page);
2153 return (0);
2156 static int toplevelcount = 0;
2158 void
2159 xt_append_toplevel(GtkWindow *w, char **body)
2161 char *n;
2163 n = g_strdup_printf("toplevel#%d", toplevelcount++);
2164 *body = xt_append_settings(*body, G_OBJECT(w), n, 0);
2165 g_free(n);
2169 allthethings(struct tab *t, struct karg *arg)
2171 GList *list;
2172 char *page, *body, *b;
2174 body = xt_append_settings(NULL, G_OBJECT(t->wv), "t->wv", 1);
2175 body = xt_append_settings(body, G_OBJECT(t->inspector),
2176 "t->inspector", 1);
2177 #if 0 /* not until warnings are gone */
2178 body = xt_append_settings(body, G_OBJECT(session),
2179 "session", 1);
2180 #endif
2181 toplevelcount = 0;
2182 list = gtk_window_list_toplevels();
2183 g_list_foreach(list, (GFunc)g_object_ref, NULL);
2184 g_list_foreach(list, (GFunc)xt_append_toplevel, &body);
2185 g_list_foreach(list, (GFunc)g_object_unref, NULL);
2186 g_list_free(list);
2188 b = body;
2189 body = g_strdup_printf("<pre>%scan paste clipboard = %d\n</pre>", body,
2190 webkit_web_view_can_paste_clipboard(t->wv));
2191 g_free(b);
2193 page = get_html_page("About All The Things _o/", body, "", 0);
2194 g_free(body);
2196 load_webkit_string(t, page, XT_URI_ABOUT_ALLTHETHINGS, 0);
2197 g_free(page);
2199 return (0);