reshuffle favicon stuff to prevent some race conditions and intricacies'
[xxxterm.git] / xxxterm.c
blob957a628e0f89a2a754e9e74b884e352fa898c30b
1 /* $xxxterm$ */
2 /*
3 * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
4 * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
5 * Copyright (c) 2010 Edd Barrett <vext01@gmail.com>
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21 * TODO:
22 * inverse color browsing
23 * favs
24 * - store in sqlite
25 * multi letter commands
26 * pre and post counts for commands
27 * autocompletion on various inputs
28 * create privacy browsing
29 * - encrypted local data
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <err.h>
35 #include <pwd.h>
36 #include <string.h>
37 #include <unistd.h>
38 #include <util.h>
39 #include <pthread.h>
40 #include <dlfcn.h>
41 #include <errno.h>
43 #ifdef __linux__
44 #include "linux/tree.h"
45 #else
46 #include <sys/tree.h>
47 #endif
48 #include <sys/queue.h>
49 #include <sys/types.h>
50 #include <sys/stat.h>
51 #include <sys/socket.h>
52 #include <sys/un.h>
54 #include <gtk/gtk.h>
55 #include <gdk/gdkkeysyms.h>
56 #include <webkit/webkit.h>
57 #include <libsoup/soup.h>
58 #include <gnutls/gnutls.h>
59 #include <JavaScriptCore/JavaScript.h>
60 #include <gnutls/x509.h>
62 #include "javascript.h"
65 javascript.h borrowed from vimprobable2 under the following license:
67 Copyright (c) 2009 Leon Winter
68 Copyright (c) 2009 Hannes Schueller
69 Copyright (c) 2009 Matto Fransen
71 Permission is hereby granted, free of charge, to any person obtaining a copy
72 of this software and associated documentation files (the "Software"), to deal
73 in the Software without restriction, including without limitation the rights
74 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
75 copies of the Software, and to permit persons to whom the Software is
76 furnished to do so, subject to the following conditions:
78 The above copyright notice and this permission notice shall be included in
79 all copies or substantial portions of the Software.
81 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
82 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
83 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
84 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
85 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
86 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
87 THE SOFTWARE.
90 static char *version = "$xxxterm$";
92 /* hooked functions */
93 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
94 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
95 SoupCookie *);
97 /*#define XT_DEBUG*/
98 #ifdef XT_DEBUG
99 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
100 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
101 #define XT_D_MOVE 0x0001
102 #define XT_D_KEY 0x0002
103 #define XT_D_TAB 0x0004
104 #define XT_D_URL 0x0008
105 #define XT_D_CMD 0x0010
106 #define XT_D_NAV 0x0020
107 #define XT_D_DOWNLOAD 0x0040
108 #define XT_D_CONFIG 0x0080
109 #define XT_D_JS 0x0100
110 #define XT_D_FAVORITE 0x0200
111 #define XT_D_PRINTING 0x0400
112 #define XT_D_COOKIE 0x0800
113 u_int32_t swm_debug = 0
114 | XT_D_MOVE
115 | XT_D_KEY
116 | XT_D_TAB
117 | XT_D_URL
118 | XT_D_CMD
119 | XT_D_NAV
120 | XT_D_DOWNLOAD
121 | XT_D_CONFIG
122 | XT_D_JS
123 | XT_D_FAVORITE
124 | XT_D_PRINTING
125 | XT_D_COOKIE
127 #else
128 #define DPRINTF(x...)
129 #define DNPRINTF(n,x...)
130 #endif
132 #define LENGTH(x) (sizeof x / sizeof x[0])
133 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
134 ~(GDK_BUTTON1_MASK) & \
135 ~(GDK_BUTTON2_MASK) & \
136 ~(GDK_BUTTON3_MASK) & \
137 ~(GDK_BUTTON4_MASK) & \
138 ~(GDK_BUTTON5_MASK))
140 char *icons[] = {
141 "xxxtermicon16.png",
142 "xxxtermicon32.png",
143 "xxxtermicon48.png",
144 "xxxtermicon64.png",
145 "xxxtermicon128.png"
148 struct tab {
149 TAILQ_ENTRY(tab) entry;
150 GtkWidget *vbox;
151 GtkWidget *tab_content;
152 GtkWidget *label;
153 GtkWidget *spinner;
154 GtkWidget *uri_entry;
155 GtkWidget *search_entry;
156 GtkWidget *toolbar;
157 GtkWidget *browser_win;
158 GtkWidget *cmd;
159 GtkWidget *oops;
160 GtkWidget *backward;
161 GtkWidget *forward;
162 GtkWidget *stop;
163 GtkWidget *js_toggle;
164 guint tab_id;
165 WebKitWebView *wv;
167 WebKitWebHistoryItem *item;
168 WebKitWebBackForwardList *bfl;
170 /* favicon */
171 WebKitNetworkRequest *icon_request;
172 WebKitDownload *icon_download;
173 GdkPixbuf *icon_pixbuf;
174 gchar *icon_dest_uri;
176 /* adjustments for browser */
177 GtkScrollbar *sb_h;
178 GtkScrollbar *sb_v;
179 GtkAdjustment *adjust_h;
180 GtkAdjustment *adjust_v;
182 /* flags */
183 int focus_wv;
184 int ctrl_click;
185 gchar *hover;
186 int xtp_meaning; /* identifies dls/favorites */
188 /* hints */
189 int hints_on;
190 int hint_mode;
191 #define XT_HINT_NONE (0)
192 #define XT_HINT_NUMERICAL (1)
193 #define XT_HINT_ALPHANUM (2)
194 char hint_buf[128];
195 char hint_num[128];
197 /* search */
198 char *search_text;
199 int search_forward;
201 /* settings */
202 WebKitWebSettings *settings;
203 int font_size;
204 gchar *user_agent;
206 TAILQ_HEAD(tab_list, tab);
208 struct history {
209 RB_ENTRY(history) entry;
210 const gchar *uri;
211 const gchar *title;
213 RB_HEAD(history_list, history);
215 struct download {
216 RB_ENTRY(download) entry;
217 int id;
218 WebKitDownload *download;
219 struct tab *tab;
221 RB_HEAD(download_list, download);
223 struct domain {
224 RB_ENTRY(domain) entry;
225 gchar *d;
226 int handy; /* app use */
228 RB_HEAD(domain_list, domain);
230 struct undo {
231 TAILQ_ENTRY(undo) entry;
232 gchar *uri;
233 GList *history;
234 int back; /* Keeps track of how many back
235 * history items there are. */
237 TAILQ_HEAD(undo_tailq, undo);
239 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
240 int next_download_id = 1;
242 struct karg {
243 int i;
244 char *s;
247 /* defines */
248 #define XT_NAME ("XXXTerm")
249 #define XT_DIR (".xxxterm")
250 #define XT_CACHE_DIR ("cache")
251 #define XT_CERT_DIR ("certs/")
252 #define XT_SESSIONS_DIR ("sessions/")
253 #define XT_CONF_FILE ("xxxterm.conf")
254 #define XT_FAVS_FILE ("favorites")
255 #define XT_SAVED_TABS_FILE ("main_session")
256 #define XT_RESTART_TABS_FILE ("restart_tabs")
257 #define XT_SOCKET_FILE ("socket")
258 #define XT_HISTORY_FILE ("history")
259 #define XT_CB_HANDLED (TRUE)
260 #define XT_CB_PASSTHROUGH (FALSE)
261 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
262 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
263 #define XT_DLMAN_REFRESH "10"
264 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
265 "td {overflow: hidden;}\n" \
266 "th {background-color: #cccccc}" \
267 "table {width: 90%%; border: 1px black" \
268 " solid; table-layout: fixed}\n</style>\n\n"
269 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
270 #define XT_MAX_UNDO_CLOSE_TAB (32)
272 /* file sizes */
273 #define SZ_KB ((uint64_t) 1024)
274 #define SZ_MB (SZ_KB * SZ_KB)
275 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
276 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
279 * xxxterm "protocol" (xtp)
280 * We use this for managing stuff like downloads and favorites. They
281 * make magical HTML pages in memory which have xxxt:// links in order
282 * to communicate with xxxterm's internals. These links take the format:
283 * xxxt://class/session_key/action/arg
285 * Don't begin xtp class/actions as 0. atoi returns that on error.
287 * Typically we have not put addition of items in this framework, as
288 * adding items is either done via an ex-command or via a keybinding instead.
291 #define XT_XTP_STR "xxxt://"
293 /* XTP classes (xxxt://<class>) */
294 #define XT_XTP_DL 1 /* downloads */
295 #define XT_XTP_HL 2 /* history */
296 #define XT_XTP_CL 3 /* cookies */
297 #define XT_XTP_FL 4 /* favorites */
299 /* XTP download actions */
300 #define XT_XTP_DL_LIST 1
301 #define XT_XTP_DL_CANCEL 2
302 #define XT_XTP_DL_REMOVE 3
304 /* XTP history actions */
305 #define XT_XTP_HL_LIST 1
306 #define XT_XTP_HL_REMOVE 2
308 /* XTP cookie actions */
309 #define XT_XTP_CL_LIST 1
310 #define XT_XTP_CL_REMOVE 2
312 /* XTP cookie actions */
313 #define XT_XTP_FL_LIST 1
314 #define XT_XTP_FL_REMOVE 2
316 /* xtp tab meanings - identifies which tabs have xtp pages in */
317 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
318 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
319 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
320 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
321 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
323 /* actions */
324 #define XT_MOVE_INVALID (0)
325 #define XT_MOVE_DOWN (1)
326 #define XT_MOVE_UP (2)
327 #define XT_MOVE_BOTTOM (3)
328 #define XT_MOVE_TOP (4)
329 #define XT_MOVE_PAGEDOWN (5)
330 #define XT_MOVE_PAGEUP (6)
331 #define XT_MOVE_HALFDOWN (7)
332 #define XT_MOVE_HALFUP (8)
333 #define XT_MOVE_LEFT (9)
334 #define XT_MOVE_FARLEFT (10)
335 #define XT_MOVE_RIGHT (11)
336 #define XT_MOVE_FARRIGHT (12)
338 #define XT_TAB_LAST (-4)
339 #define XT_TAB_FIRST (-3)
340 #define XT_TAB_PREV (-2)
341 #define XT_TAB_NEXT (-1)
342 #define XT_TAB_INVALID (0)
343 #define XT_TAB_NEW (1)
344 #define XT_TAB_DELETE (2)
345 #define XT_TAB_DELQUIT (3)
346 #define XT_TAB_OPEN (4)
347 #define XT_TAB_UNDO_CLOSE (5)
349 #define XT_NAV_INVALID (0)
350 #define XT_NAV_BACK (1)
351 #define XT_NAV_FORWARD (2)
352 #define XT_NAV_RELOAD (3)
353 #define XT_NAV_RELOAD_CACHE (4)
355 #define XT_FOCUS_INVALID (0)
356 #define XT_FOCUS_URI (1)
357 #define XT_FOCUS_SEARCH (2)
359 #define XT_SEARCH_INVALID (0)
360 #define XT_SEARCH_NEXT (1)
361 #define XT_SEARCH_PREV (2)
363 #define XT_PASTE_CURRENT_TAB (0)
364 #define XT_PASTE_NEW_TAB (1)
366 #define XT_FONT_SET (0)
368 #define XT_WL_TOGGLE (1<<0)
369 #define XT_WL_ENABLE (1<<1)
370 #define XT_WL_DISABLE (1<<2)
371 #define XT_WL_FQDN (1<<3) /* default */
372 #define XT_WL_TOPLEVEL (1<<4)
374 #define XT_CMD_OPEN (0)
375 #define XT_CMD_OPEN_CURRENT (1)
376 #define XT_CMD_TABNEW (2)
377 #define XT_CMD_TABNEW_CURRENT (3)
379 /* mime types */
380 struct mime_type {
381 char *mt_type;
382 char *mt_action;
383 int mt_default;
384 TAILQ_ENTRY(mime_type) entry;
386 TAILQ_HEAD(mime_type_list, mime_type);
388 /* uri aliases */
389 struct alias {
390 char *a_name;
391 char *a_uri;
392 TAILQ_ENTRY(alias) entry;
394 TAILQ_HEAD(alias_list, alias);
396 /* settings that require restart */
397 int showtabs = 1; /* show tabs on notebook */
398 int showurl = 1; /* show url toolbar on notebook */
399 int tabless = 0; /* allow only 1 tab */
400 int enable_socket = 0;
401 int single_instance = 0; /* only allow one xxxterm to run */
402 int fancy_bar = 1; /* fancy toolbar */
404 /* runtime settings */
405 int ctrl_click_focus = 0; /* ctrl click gets focus */
406 int cookies_enabled = 1; /* enable cookies */
407 int read_only_cookies = 0; /* enable to not write cookies */
408 int enable_scripts = 0;
409 int enable_plugins = 0;
410 int default_font_size = 12;
411 int window_height = 768;
412 int window_width = 1024;
413 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
414 unsigned refresh_interval = 10; /* download refresh interval */
415 int enable_cookie_whitelist = 1;
416 int enable_js_whitelist = 1;
417 time_t session_timeout = 3600; /* cookie session timeout */
418 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
419 char *ssl_ca_file = NULL;
420 char *resource_dir = NULL;
421 gboolean ssl_strict_certs = FALSE;
422 int append_next = 1; /* append tab after current tab */
423 char *home = NULL;
424 char *search_string = NULL;
425 char *http_proxy = NULL;
426 char download_dir[PATH_MAX];
427 char runtime_settings[PATH_MAX]; /* override of settings */
428 int allow_volatile_cookies = 0;
429 int save_global_history = 0; /* save global history to disk */
430 char *user_agent = NULL;
431 int save_rejected_cookies = 0;
433 struct settings;
434 int set_download_dir(struct settings *, char *);
435 int set_runtime_dir(struct settings *, char *);
436 int set_cookie_policy(struct settings *, char *);
437 int add_alias(struct settings *, char *);
438 int add_mime_type(struct settings *, char *);
439 int add_cookie_wl(struct settings *, char *);
440 int add_js_wl(struct settings *, char *);
441 void button_set_stockid(GtkWidget *, char *);
442 GtkWidget * create_button(char *, char *, int);
444 char *get_cookie_policy(struct settings *);
446 char *get_download_dir(struct settings *);
447 char *get_runtime_dir(struct settings *);
449 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
450 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
451 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
452 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
454 struct special {
455 int (*set)(struct settings *, char *);
456 char *(*get)(struct settings *);
457 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
460 struct special s_cookie = {
461 set_cookie_policy,
462 get_cookie_policy,
463 NULL
466 struct special s_alias = {
467 add_alias,
468 NULL,
469 walk_alias
472 struct special s_mime = {
473 add_mime_type,
474 NULL,
475 walk_mime_type
478 struct special s_js = {
479 add_js_wl,
480 NULL,
481 walk_js_wl
484 struct special s_cookie_wl = {
485 add_cookie_wl,
486 NULL,
487 walk_cookie_wl
490 struct special s_download_dir = {
491 set_download_dir,
492 get_download_dir,
493 NULL
496 struct settings {
497 char *name;
498 int type;
499 #define XT_S_INVALID (0)
500 #define XT_S_INT (1)
501 #define XT_S_STR (2)
502 uint32_t flags;
503 #define XT_SF_RESTART (1<<0)
504 #define XT_SF_RUNTIME (1<<1)
505 int *ival;
506 char **sval;
507 struct special *s;
508 } rs[] = {
509 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
510 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
511 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
512 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
513 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
514 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
515 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
516 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
517 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
518 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
519 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
520 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
521 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
522 { "home", XT_S_STR, 0 , NULL, &home, NULL },
523 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
524 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
525 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
526 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
527 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
528 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
529 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
530 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
531 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART , &save_rejected_cookies, NULL, NULL },
532 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
533 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
534 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
535 { "user_agent", XT_S_STR, 0 , NULL, &user_agent, NULL },
536 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
537 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
539 /* runtime settings */
540 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
541 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
542 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
543 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
546 /* globals */
547 extern char *__progname;
548 char **start_argv;
549 struct passwd *pwd;
550 GtkWidget *main_window;
551 GtkNotebook *notebook;
552 GtkWidget *arrow, *abtn;
553 struct tab_list tabs;
554 struct history_list hl;
555 struct download_list downloads;
556 struct domain_list c_wl;
557 struct domain_list js_wl;
558 struct undo_tailq undos;
559 int undo_count;
560 int updating_dl_tabs = 0;
561 int updating_hl_tabs = 0;
562 int updating_cl_tabs = 0;
563 int updating_fl_tabs = 0;
564 char *global_search;
565 uint64_t blocked_cookies = 0;
566 char named_session[PATH_MAX];
567 void update_favicon(struct tab *);
568 int icon_size_map(int);
570 void
571 load_webkit_string(struct tab *t, const char *str)
573 /* we set this to indicate we want to manually do navaction */
574 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
575 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
578 void
579 hide_oops(struct tab *t)
581 gtk_widget_hide(t->oops);
584 void
585 hide_cmd(struct tab *t)
587 gtk_widget_hide(t->cmd);
590 void
591 show_cmd(struct tab *t)
593 gtk_widget_hide(t->oops);
594 gtk_widget_show(t->cmd);
597 void
598 show_oops(struct tab *t, const char *fmt, ...)
600 va_list ap;
601 char *msg;
603 if (fmt == NULL)
604 return;
606 va_start(ap, fmt);
607 if (vasprintf(&msg, fmt, ap) == -1)
608 errx(1, "moo");
609 va_end(ap);
611 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
612 gtk_widget_hide(t->cmd);
613 gtk_widget_show(t->oops);
615 char *
616 get_as_string(struct settings *s)
618 char *r = NULL;
620 if (s == NULL)
621 return (NULL);
623 if (s->s) {
624 if (s->s->get)
625 r = s->s->get(s);
626 else
627 warnx("get_as_string skip %s\n", s->name);
628 } else if (s->type == XT_S_INT)
629 r = g_strdup_printf("%d", *s->ival);
630 else if (s->type == XT_S_STR)
631 r = g_strdup(*s->sval);
632 else
633 r = g_strdup_printf("INVALID TYPE");
635 return (r);
638 void
639 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
641 int i;
642 char *s;
644 for (i = 0; i < LENGTH(rs); i++) {
645 if (rs[i].s && rs[i].s->walk)
646 rs[i].s->walk(&rs[i], cb, cb_args);
647 else {
648 s = get_as_string(&rs[i]);
649 cb(&rs[i], s, cb_args);
650 g_free(s);
656 set_cookie_policy(struct settings *s, char *val)
658 if (!strcmp(val, "no3rdparty"))
659 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
660 else if (!strcmp(val, "accept"))
661 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
662 else if (!strcmp(val, "reject"))
663 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
664 else
665 return (1);
667 return (0);
670 char *
671 get_cookie_policy(struct settings *s)
673 char *r = NULL;
675 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
676 r = g_strdup("no3rdparty");
677 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
678 r = g_strdup("accept");
679 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
680 r = g_strdup("reject");
681 else
682 return (NULL);
684 return (r);
687 char *
688 get_download_dir(struct settings *s)
690 if (download_dir[0] == '\0')
691 return (0);
692 return (g_strdup(download_dir));
696 set_download_dir(struct settings *s, char *val)
698 if (val[0] == '~')
699 snprintf(download_dir, sizeof download_dir, "%s/%s",
700 pwd->pw_dir, &val[1]);
701 else
702 strlcpy(download_dir, val, sizeof download_dir);
704 return (0);
708 * Session IDs.
709 * We use these to prevent people putting xxxt:// URLs on
710 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
712 #define XT_XTP_SES_KEY_SZ 8
713 #define XT_XTP_SES_KEY_HEX_FMT \
714 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
715 char *dl_session_key; /* downloads */
716 char *hl_session_key; /* history list */
717 char *cl_session_key; /* cookie list */
718 char *fl_session_key; /* favorites list */
720 char work_dir[PATH_MAX];
721 char certs_dir[PATH_MAX];
722 char cache_dir[PATH_MAX];
723 char sessions_dir[PATH_MAX];
724 char cookie_file[PATH_MAX];
725 SoupURI *proxy_uri = NULL;
726 SoupSession *session;
727 SoupCookieJar *s_cookiejar;
728 SoupCookieJar *p_cookiejar;
729 char rc_fname[PATH_MAX];
731 struct mime_type_list mtl;
732 struct alias_list aliases;
734 /* protos */
735 void create_new_tab(char *, struct undo *, int);
736 void delete_tab(struct tab *);
737 void adjustfont_webkit(struct tab *, int);
738 int run_script(struct tab *, char *);
739 int download_rb_cmp(struct download *, struct download *);
740 int xtp_page_hl(struct tab *t, struct karg *args);
741 int xtp_page_dl(struct tab *t, struct karg *args);
742 int xtp_page_cl(struct tab *t, struct karg *args);
743 int xtp_page_fl(struct tab *t, struct karg *args);
746 history_rb_cmp(struct history *h1, struct history *h2)
748 return (strcmp(h1->uri, h2->uri));
750 RB_GENERATE(history_list, history, entry, history_rb_cmp);
753 domain_rb_cmp(struct domain *d1, struct domain *d2)
755 return (strcmp(d1->d, d2->d));
757 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
760 * generate a session key to secure xtp commands.
761 * pass in a ptr to the key in question and it will
762 * be modified in place.
764 void
765 generate_xtp_session_key(char **key)
767 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
769 /* free old key */
770 if (*key)
771 g_free(*key);
773 /* make a new one */
774 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
775 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
776 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
777 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
779 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
783 * validate a xtp session key.
784 * return 1 if OK
787 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
789 if (strcmp(trusted, untrusted) != 0) {
790 show_oops(t, "%s: xtp session key mismatch possible spoof",
791 __func__);
792 return (0);
795 return (1);
799 download_rb_cmp(struct download *e1, struct download *e2)
801 return (e1->id < e2->id ? -1 : e1->id > e2->id);
803 RB_GENERATE(download_list, download, entry, download_rb_cmp);
805 struct valid_url_types {
806 char *type;
807 } vut[] = {
808 { "http://" },
809 { "https://" },
810 { "ftp://" },
811 { "file://" },
812 { XT_XTP_STR },
816 valid_url_type(char *url)
818 int i;
820 for (i = 0; i < LENGTH(vut); i++)
821 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
822 return (0);
824 return (1);
827 void
828 print_cookie(char *msg, SoupCookie *c)
830 if (c == NULL)
831 return;
833 if (msg)
834 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
835 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
836 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
837 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
838 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
839 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
840 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
841 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
842 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
843 DNPRINTF(XT_D_COOKIE, "====================================\n");
846 void
847 walk_alias(struct settings *s,
848 void (*cb)(struct settings *, char *, void *), void *cb_args)
850 struct alias *a;
851 char *str;
853 if (s == NULL || cb == NULL)
854 errx(1, "walk_alias");
856 TAILQ_FOREACH(a, &aliases, entry) {
857 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
858 cb(s, str, cb_args);
859 g_free(str);
863 char *
864 match_alias(char *url_in)
866 struct alias *a;
867 char *arg;
868 char *url_out = NULL, *search;
870 search = g_strdup(url_in);
871 arg = search;
872 if (strsep(&arg, " \t") == NULL)
873 errx(1, "match_alias: NULL URL");
875 TAILQ_FOREACH(a, &aliases, entry) {
876 if (!strcmp(search, a->a_name))
877 break;
880 if (a != NULL) {
881 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
882 a->a_name);
883 if (arg != NULL)
884 url_out = g_strdup_printf(a->a_uri, arg);
885 else
886 url_out = g_strdup(a->a_uri);
889 g_free(search);
891 return (url_out);
894 char *
895 guess_url_type(char *url_in)
897 struct stat sb;
898 char *url_out = NULL;
900 url_out = match_alias(url_in);
901 if (url_out != NULL)
902 return (url_out);
904 /* XXX not sure about this heuristic */
905 if (stat(url_in, &sb) == 0)
906 url_out = g_strdup_printf("file://%s", url_in);
907 else
908 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
910 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
912 return (url_out);
916 add_alias(struct settings *s, char *line)
918 char *l, *alias;
919 struct alias *a;
921 if (line == NULL)
922 errx(1, "add_alias");
923 l = line;
925 a = g_malloc(sizeof(*a));
927 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
928 errx(1, "add_alias: incomplete alias definition");
930 if (strlen(alias) == 0 || strlen(l) == 0)
931 errx(1, "add_alias: invalid alias definition");
933 a->a_name = g_strdup(alias);
934 a->a_uri = g_strdup(l);
936 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
938 TAILQ_INSERT_TAIL(&aliases, a, entry);
940 return (0);
944 add_mime_type(struct settings *s, char *line)
946 char *mime_type;
947 char *l = NULL;
948 struct mime_type *m;
950 /* XXX this could be smarter */
952 if (line == NULL)
953 errx(1, "add_mime_type");
954 l = line;
956 m = g_malloc(sizeof(*m));
958 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
959 errx(1, "add_mime_type: invalid mime_type");
961 if (mime_type[strlen(mime_type) - 1] == '*') {
962 mime_type[strlen(mime_type) - 1] = '\0';
963 m->mt_default = 1;
964 } else
965 m->mt_default = 0;
967 if (strlen(mime_type) == 0 || strlen(l) == 0)
968 errx(1, "add_mime_type: invalid mime_type");
970 m->mt_type = g_strdup(mime_type);
971 m->mt_action = g_strdup(l);
973 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
974 m->mt_type, m->mt_action, m->mt_default);
976 TAILQ_INSERT_TAIL(&mtl, m, entry);
978 return (0);
981 struct mime_type *
982 find_mime_type(char *mime_type)
984 struct mime_type *m, *def = NULL, *rv = NULL;
986 TAILQ_FOREACH(m, &mtl, entry) {
987 if (m->mt_default &&
988 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
989 def = m;
991 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
992 rv = m;
993 break;
997 if (rv == NULL)
998 rv = def;
1000 return (rv);
1003 void
1004 walk_mime_type(struct settings *s,
1005 void (*cb)(struct settings *, char *, void *), void *cb_args)
1007 struct mime_type *m;
1008 char *str;
1010 if (s == NULL || cb == NULL)
1011 errx(1, "walk_mime_type");
1013 TAILQ_FOREACH(m, &mtl, entry) {
1014 str = g_strdup_printf("%s%s --> %s",
1015 m->mt_type,
1016 m->mt_default ? "*" : "",
1017 m->mt_action);
1018 cb(s, str, cb_args);
1019 g_free(str);
1023 void
1024 wl_add(char *str, struct domain_list *wl, int handy)
1026 struct domain *d;
1027 int add_dot = 0;
1029 if (str == NULL || wl == NULL)
1030 return;
1031 if (strlen(str) < 2)
1032 return;
1034 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1036 /* treat *.moo.com the same as .moo.com */
1037 if (str[0] == '*' && str[1] == '.')
1038 str = &str[1];
1039 else if (str[0] == '.')
1040 str = &str[0];
1041 else
1042 add_dot = 1;
1044 d = g_malloc(sizeof *d);
1045 if (add_dot)
1046 d->d = g_strdup_printf(".%s", str);
1047 else
1048 d->d = g_strdup(str);
1049 d->handy = handy;
1051 if (RB_INSERT(domain_list, wl, d))
1052 goto unwind;
1054 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1055 return;
1056 unwind:
1057 if (d) {
1058 if (d->d)
1059 g_free(d->d);
1060 g_free(d);
1065 add_cookie_wl(struct settings *s, char *entry)
1067 wl_add(entry, &c_wl, 1);
1068 return (0);
1071 void
1072 walk_cookie_wl(struct settings *s,
1073 void (*cb)(struct settings *, char *, void *), void *cb_args)
1075 struct domain *d;
1077 if (s == NULL || cb == NULL)
1078 errx(1, "walk_cookie_wl");
1080 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1081 cb(s, d->d, cb_args);
1084 void
1085 walk_js_wl(struct settings *s,
1086 void (*cb)(struct settings *, char *, void *), void *cb_args)
1088 struct domain *d;
1090 if (s == NULL || cb == NULL)
1091 errx(1, "walk_js_wl");
1093 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1094 cb(s, d->d, cb_args);
1098 add_js_wl(struct settings *s, char *entry)
1100 wl_add(entry, &js_wl, 1 /* persistent */);
1101 return (0);
1104 struct domain *
1105 wl_find(const gchar *search, struct domain_list *wl)
1107 int i;
1108 struct domain *d = NULL, dfind;
1109 gchar *s = NULL;
1111 if (search == NULL || wl == NULL)
1112 return (NULL);
1113 if (strlen(search) < 2)
1114 return (NULL);
1116 if (search[0] != '.')
1117 s = g_strdup_printf(".%s", search);
1118 else
1119 s = g_strdup(search);
1121 for (i = strlen(s) - 1; i >= 0; i--) {
1122 if (s[i] == '.') {
1123 dfind.d = &s[i];
1124 d = RB_FIND(domain_list, wl, &dfind);
1125 if (d)
1126 goto done;
1130 done:
1131 if (s)
1132 g_free(s);
1134 return (d);
1137 struct domain *
1138 wl_find_uri(const gchar *s, struct domain_list *wl)
1140 int i;
1141 char *ss;
1142 struct domain *r;
1144 if (s == NULL || wl == NULL)
1145 return (NULL);
1147 if (!strncmp(s, "http://", strlen("http://")))
1148 s = &s[strlen("http://")];
1149 else if (!strncmp(s, "https://", strlen("https://")))
1150 s = &s[strlen("https://")];
1152 if (strlen(s) < 2)
1153 return (NULL);
1155 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1156 /* chop string at first slash */
1157 if (s[i] == '/' || s[i] == '\0') {
1158 ss = g_strdup(s);
1159 ss[i] = '\0';
1160 r = wl_find(ss, wl);
1161 g_free(ss);
1162 return (r);
1165 return (NULL);
1168 char *
1169 get_toplevel_domain(char *domain)
1171 char *s;
1172 int found = 0;
1174 if (domain == NULL)
1175 return (NULL);
1176 if (strlen(domain) < 2)
1177 return (NULL);
1179 s = &domain[strlen(domain) - 1];
1180 while (s != domain) {
1181 if (*s == '.') {
1182 found++;
1183 if (found == 2)
1184 return (s);
1186 s--;
1189 if (found)
1190 return (domain);
1192 return (NULL);
1195 #define WS "\n= \t"
1196 void
1197 config_parse(char *filename, int runtime)
1199 FILE *config, *f;
1200 char *line, *cp, *var, *val;
1201 size_t len, lineno = 0;
1202 int i, handled, *p;
1203 char **s, file[PATH_MAX];
1204 struct stat sb;
1206 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1208 if (filename == NULL)
1209 return;
1211 if (runtime && runtime_settings[0] != '\0') {
1212 snprintf(file, sizeof file, "%s/%s",
1213 work_dir, runtime_settings);
1214 if (stat(file, &sb)) {
1215 warnx("runtime file doesn't exist, creating it");
1216 if ((f = fopen(file, "w")) == NULL)
1217 err(1, "runtime");
1218 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1219 fclose(f);
1221 } else
1222 strlcpy(file, filename, sizeof file);
1224 if ((config = fopen(file, "r")) == NULL) {
1225 warn("config_parse: cannot open %s", filename);
1226 return;
1229 for (;;) {
1230 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1231 if (feof(config) || ferror(config))
1232 break;
1234 cp = line;
1235 cp += (long)strspn(cp, WS);
1236 if (cp[0] == '\0') {
1237 /* empty line */
1238 free(line);
1239 continue;
1242 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1243 break;
1245 cp += (long)strspn(cp, WS);
1247 if ((val = strsep(&cp, "\0")) == NULL)
1248 break;
1250 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1252 /* get settings */
1253 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1254 if (strcmp(var, rs[i].name))
1255 continue;
1257 if (rs[i].s) {
1258 if (rs[i].s->set(&rs[i], val))
1259 errx(1, "invalid value for %s", var);
1260 handled = 1;
1261 break;
1262 } else {
1263 switch (rs[i].type) {
1264 case XT_S_INT:
1265 p = rs[i].ival;
1266 *p = atoi(val);
1267 handled = 1;
1268 break;
1269 case XT_S_STR:
1270 s = rs[i].sval;
1271 if (s == NULL)
1272 errx(1, "invalid sval for %s",
1273 rs[i].name);
1274 if (*s)
1275 g_free(*s);
1276 *s = g_strdup(val);
1277 handled = 1;
1278 break;
1279 case XT_S_INVALID:
1280 default:
1281 errx(1, "invalid type for %s", var);
1284 break;
1286 if (handled == 0)
1287 errx(1, "invalid conf file entry: %s=%s", var, val);
1289 free(line);
1292 fclose(config);
1295 char *
1296 js_ref_to_string(JSContextRef context, JSValueRef ref)
1298 char *s = NULL;
1299 size_t l;
1300 JSStringRef jsref;
1302 jsref = JSValueToStringCopy(context, ref, NULL);
1303 if (jsref == NULL)
1304 return (NULL);
1306 l = JSStringGetMaximumUTF8CStringSize(jsref);
1307 s = g_malloc(l);
1308 if (s)
1309 JSStringGetUTF8CString(jsref, s, l);
1310 JSStringRelease(jsref);
1312 return (s);
1315 void
1316 disable_hints(struct tab *t)
1318 bzero(t->hint_buf, sizeof t->hint_buf);
1319 bzero(t->hint_num, sizeof t->hint_num);
1320 run_script(t, "vimprobable_clear()");
1321 t->hints_on = 0;
1322 t->hint_mode = XT_HINT_NONE;
1325 void
1326 enable_hints(struct tab *t)
1328 bzero(t->hint_buf, sizeof t->hint_buf);
1329 run_script(t, "vimprobable_show_hints()");
1330 t->hints_on = 1;
1331 t->hint_mode = XT_HINT_NONE;
1334 #define XT_JS_OPEN ("open;")
1335 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1336 #define XT_JS_FIRE ("fire;")
1337 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1338 #define XT_JS_FOUND ("found;")
1339 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1342 run_script(struct tab *t, char *s)
1344 JSGlobalContextRef ctx;
1345 WebKitWebFrame *frame;
1346 JSStringRef str;
1347 JSValueRef val, exception;
1348 char *es, buf[128];
1350 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1351 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1353 frame = webkit_web_view_get_main_frame(t->wv);
1354 ctx = webkit_web_frame_get_global_context(frame);
1356 str = JSStringCreateWithUTF8CString(s);
1357 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1358 NULL, 0, &exception);
1359 JSStringRelease(str);
1361 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1362 if (val == NULL) {
1363 es = js_ref_to_string(ctx, exception);
1364 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1365 g_free(es);
1366 return (1);
1367 } else {
1368 es = js_ref_to_string(ctx, val);
1369 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1371 /* handle return value right here */
1372 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1373 disable_hints(t);
1374 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1377 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1378 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1379 &es[XT_JS_FIRE_LEN]);
1380 run_script(t, buf);
1381 disable_hints(t);
1384 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1385 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1386 disable_hints(t);
1389 g_free(es);
1392 return (0);
1396 hint(struct tab *t, struct karg *args)
1399 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1401 if (t->hints_on == 0)
1402 enable_hints(t);
1403 else
1404 disable_hints(t);
1406 return (0);
1409 /* Doesn't work fully, due to the following bug:
1410 * https://bugs.webkit.org/show_bug.cgi?id=51747
1413 restore_global_history(void)
1415 char file[PATH_MAX];
1416 FILE *f;
1417 struct history *h;
1418 gchar *uri;
1419 gchar *title;
1421 snprintf(file, sizeof file, "%s/%s/%s",
1422 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1424 if ((f = fopen(file, "r")) == NULL) {
1425 warnx("%s: fopen", __func__);
1426 return (1);
1429 for (;;) {
1430 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1431 if (feof(f) || ferror(f))
1432 break;
1434 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1435 if (feof(f) || ferror(f)) {
1436 free(uri);
1437 warnx("%s: broken history file\n", __func__);
1438 return (1);
1441 if (uri && strlen(uri) && title && strlen(title)) {
1442 webkit_web_history_item_new_with_data(uri, title);
1443 h = g_malloc(sizeof(struct history));
1444 h->uri = g_strdup(uri);
1445 h->title = g_strdup(title);
1446 RB_INSERT(history_list, &hl, h);
1447 } else {
1448 warnx("%s: failed to restore history\n", __func__);
1449 free(uri);
1450 free(title);
1451 return (1);
1454 free(uri);
1455 free(title);
1456 uri = NULL;
1457 title = NULL;
1460 return (0);
1464 save_global_history_to_disk(struct tab *t)
1466 char file[PATH_MAX];
1467 FILE *f;
1468 struct history *h;
1470 snprintf(file, sizeof file, "%s/%s/%s",
1471 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1473 if ((f = fopen(file, "w")) == NULL) {
1474 show_oops(t, "%s: global history file: %s",
1475 __func__, strerror(errno));
1476 return (1);
1479 RB_FOREACH_REVERSE(h, history_list, &hl) {
1480 if (h->uri && h->title)
1481 fprintf(f, "%s\n%s\n", h->uri, h->title);
1484 fclose(f);
1486 return (0);
1490 quit(struct tab *t, struct karg *args)
1492 if (save_global_history)
1493 save_global_history_to_disk(t);
1495 gtk_main_quit();
1497 return (1);
1501 open_tabs(struct tab *t, struct karg *a)
1503 char file[PATH_MAX];
1504 FILE *f = NULL;
1505 char *uri = NULL;
1506 int rv = 1;
1508 if (a == NULL)
1509 goto done;
1511 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1513 if ((f = fopen(file, "r")) == NULL)
1514 goto done;
1516 for (;;) {
1517 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1518 if (feof(f) || ferror(f))
1519 break;
1521 if (uri && strlen(uri)) {
1522 create_new_tab(uri, NULL, 1);
1525 free(uri);
1526 uri = NULL;
1529 rv = 0;
1530 done:
1531 if (f)
1532 fclose(f);
1534 return (rv);
1538 restore_saved_tabs(void)
1540 char file[PATH_MAX];
1541 int unlink_file = 0;
1542 struct stat sb;
1543 struct karg a;
1544 int rv = 0;
1546 snprintf(file, sizeof file, "%s/%s",
1547 sessions_dir, XT_RESTART_TABS_FILE);
1548 if (stat(file, &sb) == -1)
1549 a.s = XT_SAVED_TABS_FILE;
1550 else {
1551 unlink_file = 1;
1552 a.s = XT_RESTART_TABS_FILE;
1555 rv = open_tabs(NULL, &a);
1557 if (unlink_file)
1558 unlink(file);
1560 return (rv);
1564 save_tabs(struct tab *t, struct karg *a)
1566 char file[PATH_MAX];
1567 FILE *f;
1568 struct tab *ti;
1569 WebKitWebFrame *frame;
1570 const gchar *uri;
1572 if (a == NULL)
1573 return (1);
1574 if (a->s == NULL)
1575 return (1);
1577 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1579 if ((f = fopen(file, "w")) == NULL) {
1580 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1581 return (1);
1584 TAILQ_FOREACH(ti, &tabs, entry) {
1585 frame = webkit_web_view_get_main_frame(ti->wv);
1586 uri = webkit_web_frame_get_uri(frame);
1587 if (uri && strlen(uri) > 0)
1588 fprintf(f, "%s\n", uri);
1591 fclose(f);
1593 return (0);
1597 save_tabs_and_quit(struct tab *t, struct karg *args)
1599 struct karg a;
1601 a.s = XT_SAVED_TABS_FILE;
1602 save_tabs(t, &a);
1603 quit(t, NULL);
1605 return (1);
1609 yank_uri(struct tab *t, struct karg *args)
1611 WebKitWebFrame *frame;
1612 const gchar *uri;
1613 GtkClipboard *clipboard;
1615 frame = webkit_web_view_get_main_frame(t->wv);
1616 uri = webkit_web_frame_get_uri(frame);
1617 if (!uri)
1618 return (1);
1620 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1621 gtk_clipboard_set_text(clipboard, uri, -1);
1623 return (0);
1626 struct paste_args {
1627 struct tab *t;
1628 int i;
1631 void
1632 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1634 struct paste_args *pap;
1636 if (data == NULL)
1637 return;
1639 pap = (struct paste_args *)data;
1641 switch(pap->i) {
1642 case XT_PASTE_CURRENT_TAB:
1643 webkit_web_view_load_uri(pap->t->wv, text);
1644 break;
1645 case XT_PASTE_NEW_TAB:
1646 create_new_tab((char *)text, NULL, 1);
1647 break;
1650 g_free(pap);
1654 paste_uri(struct tab *t, struct karg *args)
1656 GtkClipboard *clipboard;
1657 struct paste_args *pap;
1659 pap = g_malloc(sizeof(struct paste_args));
1661 pap->t = t;
1662 pap->i = args->i;
1664 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1665 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1667 return (0);
1670 char *
1671 find_domain(const char *s, int add_dot)
1673 int i;
1674 char *r = NULL, *ss = NULL;
1676 if (s == NULL)
1677 return (NULL);
1679 if (!strncmp(s, "http://", strlen("http://")))
1680 s = &s[strlen("http://")];
1681 else if (!strncmp(s, "https://", strlen("https://")))
1682 s = &s[strlen("https://")];
1684 if (strlen(s) < 2)
1685 return (NULL);
1687 ss = g_strdup(s);
1688 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1689 /* chop string at first slash */
1690 if (ss[i] == '/' || ss[i] == '\0') {
1691 ss[i] = '\0';
1692 if (add_dot)
1693 r = g_strdup_printf(".%s", ss);
1694 else
1695 r = g_strdup(ss);
1696 break;
1698 g_free(ss);
1700 return (r);
1704 toggle_cwl(struct tab *t, struct karg *args)
1706 WebKitWebFrame *frame;
1707 struct domain *d;
1708 char *uri;
1709 char *dom = NULL, *dom_toggle = NULL;
1710 int es;
1712 if (args == NULL)
1713 return (0);
1715 frame = webkit_web_view_get_main_frame(t->wv);
1716 uri = (char *)webkit_web_frame_get_uri(frame);
1717 dom = find_domain(uri, 1);
1718 d = wl_find(dom, &c_wl);
1719 if (d == NULL)
1720 es = 0;
1721 else
1722 es = 1;
1724 if (args->i & XT_WL_TOGGLE)
1725 es = !es;
1726 else if ((args->i & XT_WL_ENABLE) && es != 1)
1727 es = 1;
1728 else if ((args->i & XT_WL_DISABLE) && es != 0)
1729 es = 0;
1731 if (args->i & XT_WL_TOPLEVEL)
1732 dom_toggle = get_toplevel_domain(dom);
1733 else
1734 dom_toggle = dom;
1736 if (es) {
1737 /* enable cookies for domain */
1738 wl_add(dom_toggle, &c_wl, 0);
1739 } else {
1740 /* disable cookies for domain */
1741 RB_REMOVE(domain_list, &c_wl, d);
1744 webkit_web_view_reload(t->wv);
1746 g_free(dom);
1747 return (0);
1751 toggle_js(struct tab *t, struct karg *args)
1753 int es;
1754 WebKitWebFrame *frame;
1755 const gchar *uri;
1756 struct domain *d;
1757 char *dom = NULL, *dom_toggle = NULL;
1759 if (args == NULL)
1760 return (0);
1762 g_object_get((GObject *)t->settings,
1763 "enable-scripts", &es, (char *)NULL);
1764 if (args->i & XT_WL_TOGGLE)
1765 es = !es;
1766 else if ((args->i & XT_WL_ENABLE) && es != 1)
1767 es = 1;
1768 else if ((args->i & XT_WL_DISABLE) && es != 0)
1769 es = 0;
1770 else
1771 return (0);
1773 frame = webkit_web_view_get_main_frame(t->wv);
1774 uri = (char *)webkit_web_frame_get_uri(frame);
1775 dom = find_domain(uri, 1);
1776 if (uri == NULL || dom == NULL) {
1777 show_oops(t, "Can't toggle domain in JavaScript white list");
1778 goto done;
1781 if (args->i & XT_WL_TOPLEVEL)
1782 dom_toggle = get_toplevel_domain(dom);
1783 else
1784 dom_toggle = dom;
1786 if (es) {
1787 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1788 wl_add(dom_toggle, &js_wl, 0 /* session */);
1789 } else {
1790 d = wl_find(dom_toggle, &js_wl);
1791 if (d)
1792 RB_REMOVE(domain_list, &js_wl, d);
1793 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1795 g_object_set((GObject *)t->settings,
1796 "enable-scripts", es, (char *)NULL);
1797 webkit_web_view_set_settings(t->wv, t->settings);
1798 webkit_web_view_reload(t->wv);
1799 done:
1800 if (dom)
1801 g_free(dom);
1802 return (0);
1805 void
1806 js_toggle_cb(GtkWidget *w, struct tab *t)
1808 struct karg a;
1810 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1811 toggle_js(t, &a);
1815 toggle_src(struct tab *t, struct karg *args)
1817 gboolean mode;
1819 if (t == NULL)
1820 return (0);
1822 mode = webkit_web_view_get_view_source_mode(t->wv);
1823 webkit_web_view_set_view_source_mode(t->wv, !mode);
1824 webkit_web_view_reload(t->wv);
1826 return (0);
1830 focus(struct tab *t, struct karg *args)
1832 if (t == NULL || args == NULL)
1833 errx(1, "focus");
1835 if (args->i == XT_FOCUS_URI)
1836 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1837 else if (args->i == XT_FOCUS_SEARCH)
1838 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1840 return (0);
1844 stats(struct tab *t, struct karg *args)
1846 char *stats, *s, line[64 * 1024];
1847 uint64_t line_count = 0;
1848 FILE *r_cookie_f;
1850 if (t == NULL)
1851 errx(1, "stats");
1853 line[0] = '\0';
1854 if (save_rejected_cookies) {
1855 if ((r_cookie_f = fopen(rc_fname, "r"))) {
1856 for (;;) {
1857 s = fgets(line, sizeof line, r_cookie_f);
1858 if (s == NULL || feof(r_cookie_f) ||
1859 ferror(r_cookie_f))
1860 break;
1861 line_count++;
1863 fclose(r_cookie_f);
1864 snprintf(line, sizeof line,
1865 "<br>Cookies blocked(*) total: %llu", line_count);
1866 } else
1867 show_oops(t, "Can't open blocked cookies file: %s",
1868 strerror(errno));
1871 stats = g_strdup_printf(XT_DOCTYPE
1872 "<html>"
1873 "<head>"
1874 "<title>Statistics</title>"
1875 "</head>"
1876 "<h1>Statistics</h1>"
1877 "<body>"
1878 "Cookies blocked(*) this session: %llu"
1879 "%s"
1880 "<p><small><b>*</b> results vary based on settings"
1881 "</body>"
1882 "</html>",
1883 blocked_cookies,
1884 line);
1886 load_webkit_string(t, stats);
1887 g_free(stats);
1889 return (0);
1893 about(struct tab *t, struct karg *args)
1895 char *about;
1897 if (t == NULL)
1898 errx(1, "about");
1900 about = g_strdup_printf(XT_DOCTYPE
1901 "<html>"
1902 "<head>"
1903 "<title>About</title>"
1904 "</head>"
1905 "<h1>About</h1>"
1906 "<body>"
1907 "<b>Version: %s</b><p>"
1908 "Authors:"
1909 "<ul>"
1910 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1911 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1912 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1913 "</ul>"
1914 "Copyrights and licenses can be found on the XXXterm "
1915 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1916 "</body>"
1917 "</html>",
1918 version
1921 load_webkit_string(t, about);
1922 g_free(about);
1924 return (0);
1928 help(struct tab *t, struct karg *args)
1930 char *help;
1932 if (t == NULL)
1933 errx(1, "help");
1935 help = XT_DOCTYPE
1936 "<html>"
1937 "<head>"
1938 "<title>XXXterm</title>"
1939 "<meta http-equiv=\"REFRESH\" content=\"0;"
1940 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
1941 "</head>"
1942 "<body>"
1943 "XXXterm man page <a href=\"http://opensource.conformal.com/"
1944 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
1945 "cgi-bin/man-cgi?xxxterm</a>"
1946 "</body>"
1947 "</html>"
1950 load_webkit_string(t, help);
1952 return (0);
1956 * update all favorite tabs apart from one. Pass NULL if
1957 * you want to update all.
1959 void
1960 update_favorite_tabs(struct tab *apart_from)
1962 struct tab *t;
1963 if (!updating_fl_tabs) {
1964 updating_fl_tabs = 1; /* stop infinite recursion */
1965 TAILQ_FOREACH(t, &tabs, entry)
1966 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1967 && (t != apart_from))
1968 xtp_page_fl(t, NULL);
1969 updating_fl_tabs = 0;
1973 /* show a list of favorites (bookmarks) */
1975 xtp_page_fl(struct tab *t, struct karg *args)
1977 char file[PATH_MAX];
1978 FILE *f;
1979 char *uri = NULL, *title = NULL;
1980 size_t len, lineno = 0;
1981 int i, failed = 0;
1982 char *header, *body, *tmp, *html = NULL;
1984 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1986 if (t == NULL)
1987 warn("%s: bad param", __func__);
1989 /* mark tab as favorite list */
1990 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
1992 /* new session key */
1993 if (!updating_fl_tabs)
1994 generate_xtp_session_key(&fl_session_key);
1996 /* open favorites */
1997 snprintf(file, sizeof file, "%s/%s/%s",
1998 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
1999 if ((f = fopen(file, "r")) == NULL) {
2000 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2001 return (1);
2004 /* header */
2005 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2006 "<title>Favorites</title>\n"
2007 "%s"
2008 "</head>"
2009 "<h1>Favorites</h1>\n",
2010 XT_PAGE_STYLE);
2012 /* body */
2013 body = g_strdup_printf("<div align='center'><table><tr>"
2014 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2015 "<th style='width: 15%%'>Remove</th></tr>\n");
2017 for (i = 1;;) {
2018 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2019 if (feof(f) || ferror(f))
2020 break;
2021 if (len == 0) {
2022 free(title);
2023 title = NULL;
2024 continue;
2027 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2028 if (feof(f) || ferror(f)) {
2029 errx(0, "%s: can't parse favorites\n",
2030 __func__);
2031 failed = 1;
2032 break;
2035 tmp = body;
2036 body = g_strdup_printf("%s<tr>"
2037 "<td>%d</td>"
2038 "<td><a href='%s'>%s</a></td>"
2039 "<td style='text-align: center'>"
2040 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2041 "</tr>\n",
2042 body, i, uri, title,
2043 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2045 g_free(tmp);
2047 free(uri);
2048 uri = NULL;
2049 free(title);
2050 title = NULL;
2051 i++;
2053 fclose(f);
2055 /* if none, say so */
2056 if (i == 1) {
2057 tmp = body;
2058 body = g_strdup_printf("%s<tr>"
2059 "<td colspan='3' style='text-align: center'>"
2060 "No favorites - To add one use the 'favadd' command."
2061 "</td></tr>", body);
2062 g_free(tmp);
2065 if (uri)
2066 free(uri);
2067 if (title)
2068 free(title);
2070 /* render */
2071 if (!failed) {
2072 html = g_strdup_printf("%s%s</table></div></html>",
2073 header, body);
2074 load_webkit_string(t, html);
2077 update_favorite_tabs(t);
2079 if (header)
2080 g_free(header);
2081 if (body)
2082 g_free(body);
2083 if (html)
2084 g_free(html);
2086 return (failed);
2089 char *
2090 getparams(char *cmd, char *cmp)
2092 char *rv = NULL;
2094 if (cmd && cmp) {
2095 if (!strncmp(cmd, cmp, strlen(cmp))) {
2096 rv = cmd + strlen(cmp);
2097 while (*rv == ' ')
2098 rv++;
2099 if (strlen(rv) == 0)
2100 rv = NULL;
2104 return (rv);
2107 void
2108 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2109 size_t cert_count, char *title)
2111 gnutls_datum_t cinfo;
2112 char *tmp, *header, *body, *footer;
2113 int i;
2115 header = g_strdup_printf("<title>%s</title><html><body>", title);
2116 footer = g_strdup("</body></html>");
2117 body = g_strdup("");
2119 for (i = 0; i < cert_count; i++) {
2120 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2121 &cinfo))
2122 return;
2124 tmp = body;
2125 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2126 body, i, cinfo.data);
2127 gnutls_free(cinfo.data);
2128 g_free(tmp);
2131 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2132 g_free(header);
2133 g_free(body);
2134 g_free(footer);
2135 load_webkit_string(t, tmp);
2136 g_free(tmp);
2140 ca_cmd(struct tab *t, struct karg *args)
2142 FILE *f = NULL;
2143 int rv = 1, certs = 0, certs_read;
2144 struct stat sb;
2145 gnutls_datum dt;
2146 gnutls_x509_crt_t *c = NULL;
2147 char *certs_buf = NULL, *s;
2149 /* yeah yeah stat race */
2150 if (stat(ssl_ca_file, &sb)) {
2151 show_oops(t, "no CA file: %s", ssl_ca_file);
2152 goto done;
2155 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2156 show_oops(t, "Can't open CA file: %s", strerror(errno));
2157 return (1);
2160 certs_buf = g_malloc(sb.st_size + 1);
2161 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2162 show_oops(t, "Can't read CA file: %s", strerror(errno));
2163 goto done;
2165 certs_buf[sb.st_size] = '\0';
2167 s = certs_buf;
2168 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2169 certs++;
2170 s += strlen("BEGIN CERTIFICATE");
2173 bzero(&dt, sizeof dt);
2174 dt.data = certs_buf;
2175 dt.size = sb.st_size;
2176 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2177 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2178 if (certs_read <= 0) {
2179 show_oops(t, "No cert(s) available");
2180 goto done;
2182 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2183 done:
2184 if (c)
2185 g_free(c);
2186 if (certs_buf)
2187 g_free(certs_buf);
2188 if (f)
2189 fclose(f);
2191 return (rv);
2195 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2197 SoupURI *su = NULL;
2198 struct addrinfo hints, *res = NULL, *ai;
2199 int s = -1, on;
2200 char port[8];
2202 if (uri && !g_str_has_prefix(uri, "https://"))
2203 goto done;
2205 su = soup_uri_new(uri);
2206 if (su == NULL)
2207 goto done;
2208 if (!SOUP_URI_VALID_FOR_HTTP(su))
2209 goto done;
2211 snprintf(port, sizeof port, "%d", su->port);
2212 bzero(&hints, sizeof(struct addrinfo));
2213 hints.ai_flags = AI_CANONNAME;
2214 hints.ai_family = AF_UNSPEC;
2215 hints.ai_socktype = SOCK_STREAM;
2217 if (getaddrinfo(su->host, port, &hints, &res))
2218 goto done;
2220 for (ai = res; ai; ai = ai->ai_next) {
2221 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2222 continue;
2224 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2225 if (s < 0)
2226 goto done;
2227 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2228 sizeof(on)) == -1)
2229 goto done;
2231 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2232 goto done;
2235 if (domain)
2236 strlcpy(domain, su->host, domain_sz);
2237 done:
2238 if (su)
2239 soup_uri_free(su);
2240 if (res)
2241 freeaddrinfo(res);
2243 return (s);
2247 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2249 if (gsession)
2250 gnutls_deinit(gsession);
2251 if (xcred)
2252 gnutls_certificate_free_credentials(xcred);
2254 return (0);
2258 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2259 gnutls_certificate_credentials_t *xc)
2261 gnutls_certificate_credentials_t xcred;
2262 gnutls_session_t gsession;
2263 int rv = 1;
2265 if (gs == NULL || xc == NULL)
2266 goto done;
2268 *gs = NULL;
2269 *xc = NULL;
2271 gnutls_certificate_allocate_credentials(&xcred);
2272 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2273 GNUTLS_X509_FMT_PEM);
2274 gnutls_init(&gsession, GNUTLS_CLIENT);
2275 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2276 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2277 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2278 if ((rv = gnutls_handshake(gsession)) < 0) {
2279 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2281 gnutls_error_is_fatal(rv),
2282 gnutls_strerror_name(rv));
2283 stop_tls(gsession, xcred);
2284 goto done;
2287 gnutls_credentials_type_t cred;
2288 cred = gnutls_auth_get_type(gsession);
2289 if (cred != GNUTLS_CRD_CERTIFICATE) {
2290 stop_tls(gsession, xcred);
2291 goto done;
2294 *gs = gsession;
2295 *xc = xcred;
2296 rv = 0;
2297 done:
2298 return (rv);
2302 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2303 size_t *cert_count)
2305 unsigned int len;
2306 const gnutls_datum_t *cl;
2307 gnutls_x509_crt_t *all_certs;
2308 int i, rv = 1;
2310 if (certs == NULL || cert_count == NULL)
2311 goto done;
2312 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2313 goto done;
2314 cl = gnutls_certificate_get_peers(gsession, &len);
2315 if (len == 0)
2316 goto done;
2318 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2319 for (i = 0; i < len; i++) {
2320 gnutls_x509_crt_init(&all_certs[i]);
2321 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2322 GNUTLS_X509_FMT_PEM < 0)) {
2323 g_free(all_certs);
2324 goto done;
2328 *certs = all_certs;
2329 *cert_count = len;
2330 rv = 0;
2331 done:
2332 return (rv);
2335 void
2336 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2338 int i;
2340 for (i = 0; i < cert_count; i++)
2341 gnutls_x509_crt_deinit(certs[i]);
2342 g_free(certs);
2345 void
2346 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2347 size_t cert_count, char *domain)
2349 size_t cert_buf_sz;
2350 char cert_buf[64 * 1024], file[PATH_MAX];
2351 int i;
2352 FILE *f;
2353 GdkColor color;
2355 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2356 return;
2358 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2359 if ((f = fopen(file, "w")) == NULL) {
2360 show_oops(t, "Can't create cert file %s %s",
2361 file, strerror(errno));
2362 return;
2365 for (i = 0; i < cert_count; i++) {
2366 cert_buf_sz = sizeof cert_buf;
2367 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2368 cert_buf, &cert_buf_sz)) {
2369 show_oops(t, "gnutls_x509_crt_export failed");
2370 goto done;
2372 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2373 show_oops(t, "Can't write certs: %s", strerror(errno));
2374 goto done;
2378 /* not the best spot but oh well */
2379 gdk_color_parse("lightblue", &color);
2380 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2381 done:
2382 fclose(f);
2386 load_compare_cert(struct tab *t, struct karg *args)
2388 WebKitWebFrame *frame;
2389 char *uri, domain[8182], file[PATH_MAX];
2390 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2391 int s = -1, rv = 1, i;
2392 size_t cert_count;
2393 FILE *f = NULL;
2394 size_t cert_buf_sz;
2395 gnutls_session_t gsession;
2396 gnutls_x509_crt_t *certs;
2397 gnutls_certificate_credentials_t xcred;
2399 if (t == NULL)
2400 return (1);
2402 frame = webkit_web_view_get_main_frame(t->wv);
2403 uri = (char *)webkit_web_frame_get_uri(frame);
2404 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2405 return (1);
2407 /* go ssl/tls */
2408 if (start_tls(t, s, &gsession, &xcred)) {
2409 show_oops(t, "Start TLS failed");
2410 goto done;
2413 /* get certs */
2414 if (get_connection_certs(gsession, &certs, &cert_count)) {
2415 show_oops(t, "Can't get connection certificates");
2416 goto done;
2419 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2420 if ((f = fopen(file, "r")) == NULL)
2421 goto freeit;
2423 for (i = 0; i < cert_count; i++) {
2424 cert_buf_sz = sizeof cert_buf;
2425 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2426 cert_buf, &cert_buf_sz)) {
2427 goto freeit;
2429 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2430 rv = -1; /* critical */
2431 goto freeit;
2433 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2434 rv = -1; /* critical */
2435 goto freeit;
2439 rv = 0;
2440 freeit:
2441 if (f)
2442 fclose(f);
2443 free_connection_certs(certs, cert_count);
2444 done:
2445 /* we close the socket first for speed */
2446 if (s != -1)
2447 close(s);
2448 stop_tls(gsession, xcred);
2450 return (rv);
2454 cert_cmd(struct tab *t, struct karg *args)
2456 WebKitWebFrame *frame;
2457 char *uri, *action, domain[8182];
2458 int s = -1;
2459 size_t cert_count;
2460 gnutls_session_t gsession;
2461 gnutls_x509_crt_t *certs;
2462 gnutls_certificate_credentials_t xcred;
2464 if (t == NULL)
2465 return (1);
2467 if ((action = getparams(args->s, "cert")))
2469 else
2470 action = "show";
2472 frame = webkit_web_view_get_main_frame(t->wv);
2473 uri = (char *)webkit_web_frame_get_uri(frame);
2474 if (uri && strlen(uri) == 0) {
2475 show_oops(t, "Invalid URI");
2477 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2478 show_oops(t, "Invalid certidicate URI: %s", uri);
2479 return (1);
2482 /* go ssl/tls */
2483 if (start_tls(t, s, &gsession, &xcred)) {
2484 show_oops(t, "Start TLS failed");
2485 goto done;
2488 /* get certs */
2489 if (get_connection_certs(gsession, &certs, &cert_count)) {
2490 show_oops(t, "get_connection_certs failed");
2491 goto done;
2494 if (!strcmp(action, "show"))
2495 show_certs(t, certs, cert_count, "Certificate Chain");
2496 else if (!strcmp(action, "save"))
2497 save_certs(t, certs, cert_count, domain);
2498 else
2499 show_oops(t, "Invalid command: %s", action);
2501 free_connection_certs(certs, cert_count);
2502 done:
2503 /* we close the socket first for speed */
2504 if (s != -1)
2505 close(s);
2506 stop_tls(gsession, xcred);
2508 return (0);
2512 remove_cookie(int index)
2514 int i, rv = 1;
2515 GSList *cf;
2516 SoupCookie *c;
2518 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2520 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2522 for (i = 1; cf; cf = cf->next, i++) {
2523 if (i != index)
2524 continue;
2525 c = cf->data;
2526 print_cookie("remove cookie", c);
2527 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2528 rv = 0;
2529 break;
2532 soup_cookies_free(cf);
2534 return (rv);
2538 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2540 struct domain *d;
2541 char *tmp, *header, *body, *footer;
2542 int p_js = 0, s_js = 0;
2544 /* we set this to indicate we want to manually do navaction */
2545 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2547 if (g_str_has_prefix(args, "show a") ||
2548 !strcmp(args, "show")) {
2549 /* show all */
2550 p_js = 1;
2551 s_js = 1;
2552 } else if (g_str_has_prefix(args, "show p")) {
2553 /* show persistent */
2554 p_js = 1;
2555 } else if (g_str_has_prefix(args, "show s")) {
2556 /* show session */
2557 s_js = 1;
2558 } else
2559 return (1);
2561 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2562 title, title);
2563 footer = g_strdup("</body></html>");
2564 body = g_strdup("");
2566 /* p list */
2567 if (p_js) {
2568 tmp = body;
2569 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2570 g_free(tmp);
2571 RB_FOREACH(d, domain_list, wl) {
2572 if (d->handy == 0)
2573 continue;
2574 tmp = body;
2575 body = g_strdup_printf("%s%s<br>", body, d->d);
2576 g_free(tmp);
2580 /* s list */
2581 if (s_js) {
2582 tmp = body;
2583 body = g_strdup_printf("%s<h2>Session</h2>", body);
2584 g_free(tmp);
2585 RB_FOREACH(d, domain_list, wl) {
2586 if (d->handy == 1)
2587 continue;
2588 tmp = body;
2589 body = g_strdup_printf("%s%s", body, d->d);
2590 g_free(tmp);
2594 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2595 g_free(header);
2596 g_free(body);
2597 g_free(footer);
2598 load_webkit_string(t, tmp);
2599 g_free(tmp);
2600 return (0);
2604 wl_save(struct tab *t, struct karg *args, int js)
2606 char file[PATH_MAX];
2607 FILE *f;
2608 char *line = NULL, *lt = NULL;
2609 size_t linelen;
2610 WebKitWebFrame *frame;
2611 char *dom = NULL, *uri, *dom_save = NULL;
2612 struct karg a;
2613 struct domain *d;
2614 GSList *cf;
2615 SoupCookie *ci, *c;
2616 int flags;
2618 if (t == NULL || args == NULL)
2619 return (1);
2621 if (runtime_settings[0] == '\0')
2622 return (1);
2624 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2625 if ((f = fopen(file, "r+")) == NULL)
2626 return (1);
2628 frame = webkit_web_view_get_main_frame(t->wv);
2629 uri = (char *)webkit_web_frame_get_uri(frame);
2630 dom = find_domain(uri, 1);
2631 if (uri == NULL || dom == NULL) {
2632 show_oops(t, "Can't add domain to %s white list",
2633 js ? "JavaScript" : "cookie");
2634 goto done;
2637 if (g_str_has_prefix(args->s, "save d")) {
2638 /* save domain */
2639 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2640 show_oops(t, "invalid domain: %s", dom);
2641 goto done;
2643 flags = XT_WL_TOPLEVEL;
2644 } else if (g_str_has_prefix(args->s, "save f") ||
2645 !strcmp(args->s, "save")) {
2646 /* save fqdn */
2647 dom_save = dom;
2648 flags = XT_WL_FQDN;
2649 } else {
2650 show_oops(t, "invalid command: %s", args->s);
2651 goto done;
2654 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2656 while (!feof(f)) {
2657 line = fparseln(f, &linelen, NULL, NULL, 0);
2658 if (line == NULL)
2659 continue;
2660 if (!strcmp(line, lt))
2661 goto done;
2662 free(line);
2663 line = NULL;
2666 fprintf(f, "%s\n", lt);
2668 a.i = XT_WL_ENABLE;
2669 a.i |= flags;
2670 if (js) {
2671 d = wl_find(dom_save, &js_wl);
2672 toggle_js(t, &a);
2673 } else {
2674 d = wl_find(dom_save, &c_wl);
2675 toggle_cwl(t, &a);
2677 /* find and add to persistent jar */
2678 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2679 for (;cf; cf = cf->next) {
2680 ci = cf->data;
2681 if (!strcmp(dom_save, ci->domain) ||
2682 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2683 c = soup_cookie_copy(ci);
2684 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2687 soup_cookies_free(cf);
2689 if (d)
2690 d->handy = 1;
2692 done:
2693 if (line)
2694 free(line);
2695 if (dom)
2696 g_free(dom);
2697 if (lt)
2698 g_free(lt);
2699 fclose(f);
2701 return (0);
2705 cookie_cmd(struct tab *t, struct karg *args)
2707 char *cmd;
2708 struct karg a;
2710 if ((cmd = getparams(args->s, "cookie")))
2712 else
2713 cmd = "show all";
2716 if (g_str_has_prefix(cmd, "show")) {
2717 wl_show(t, cmd, "Cookie White List", &c_wl);
2718 } else if (g_str_has_prefix(cmd, "save")) {
2719 a.s = cmd;
2720 wl_save(t, &a, 0);
2721 } else if (g_str_has_prefix(cmd, "toggle")) {
2722 a.i = XT_WL_TOGGLE;
2723 if (g_str_has_prefix(cmd, "toggle d"))
2724 a.i |= XT_WL_TOPLEVEL;
2725 else
2726 a.i |= XT_WL_FQDN;
2727 toggle_cwl(t, &a);
2728 } else if (g_str_has_prefix(cmd, "delete")) {
2729 show_oops(t, "'cookie delete' currently unimplemented");
2730 } else
2731 show_oops(t, "unknown cookie command: %s", cmd);
2733 return (0);
2737 js_cmd(struct tab *t, struct karg *args)
2739 char *cmd;
2740 struct karg a;
2742 if ((cmd = getparams(args->s, "js")))
2744 else
2745 cmd = "show all";
2747 if (g_str_has_prefix(cmd, "show")) {
2748 wl_show(t, cmd, "JavaScript White List", &js_wl);
2749 } else if (g_str_has_prefix(cmd, "save")) {
2750 a.s = cmd;
2751 wl_save(t, &a, 1);
2752 } else if (g_str_has_prefix(cmd, "toggle")) {
2753 a.i = XT_WL_TOGGLE;
2754 if (g_str_has_prefix(cmd, "toggle d"))
2755 a.i |= XT_WL_TOPLEVEL;
2756 else
2757 a.i |= XT_WL_FQDN;
2758 toggle_js(t, &a);
2759 } else if (g_str_has_prefix(cmd, "delete")) {
2760 show_oops(t, "'js delete' currently unimplemented");
2761 } else
2762 show_oops(t, "unknown js command: %s", cmd);
2764 return (0);
2768 add_favorite(struct tab *t, struct karg *args)
2770 char file[PATH_MAX];
2771 FILE *f;
2772 char *line = NULL;
2773 size_t urilen, linelen;
2774 WebKitWebFrame *frame;
2775 const gchar *uri, *title;
2777 if (t == NULL)
2778 return (1);
2780 /* don't allow adding of xtp pages to favorites */
2781 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2782 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
2783 return (1);
2786 snprintf(file, sizeof file, "%s/%s/%s",
2787 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2788 if ((f = fopen(file, "r+")) == NULL) {
2789 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2790 return (1);
2793 title = webkit_web_view_get_title(t->wv);
2794 frame = webkit_web_view_get_main_frame(t->wv);
2795 uri = webkit_web_frame_get_uri(frame);
2796 if (title == NULL)
2797 title = uri;
2799 if (title == NULL || uri == NULL) {
2800 show_oops(t, "can't add page to favorites");
2801 goto done;
2804 urilen = strlen(uri);
2806 while (!feof(f)) {
2807 line = fparseln(f, &linelen, NULL, NULL, 0);
2808 if (linelen == urilen && !strcmp(line, uri))
2809 goto done;
2810 free(line);
2811 line = NULL;
2814 fprintf(f, "\n%s\n%s", title, uri);
2815 done:
2816 if (line)
2817 free(line);
2818 fclose(f);
2820 update_favorite_tabs(NULL);
2822 return (0);
2826 navaction(struct tab *t, struct karg *args)
2828 WebKitWebHistoryItem *item;
2830 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2831 t->tab_id, args->i);
2833 if (t->item) {
2834 if (args->i == XT_NAV_BACK)
2835 item = webkit_web_back_forward_list_get_current_item(t->bfl);
2836 else
2837 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
2838 if (item == NULL)
2839 return (XT_CB_PASSTHROUGH);;
2840 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
2841 t->item = NULL;
2842 return (XT_CB_PASSTHROUGH);
2845 switch (args->i) {
2846 case XT_NAV_BACK:
2847 webkit_web_view_go_back(t->wv);
2848 break;
2849 case XT_NAV_FORWARD:
2850 webkit_web_view_go_forward(t->wv);
2851 break;
2852 case XT_NAV_RELOAD:
2853 webkit_web_view_reload(t->wv);
2854 break;
2855 case XT_NAV_RELOAD_CACHE:
2856 webkit_web_view_reload_bypass_cache(t->wv);
2857 break;
2859 return (XT_CB_PASSTHROUGH);
2863 move(struct tab *t, struct karg *args)
2865 GtkAdjustment *adjust;
2866 double pi, si, pos, ps, upper, lower, max;
2868 switch (args->i) {
2869 case XT_MOVE_DOWN:
2870 case XT_MOVE_UP:
2871 case XT_MOVE_BOTTOM:
2872 case XT_MOVE_TOP:
2873 case XT_MOVE_PAGEDOWN:
2874 case XT_MOVE_PAGEUP:
2875 case XT_MOVE_HALFDOWN:
2876 case XT_MOVE_HALFUP:
2877 adjust = t->adjust_v;
2878 break;
2879 default:
2880 adjust = t->adjust_h;
2881 break;
2884 pos = gtk_adjustment_get_value(adjust);
2885 ps = gtk_adjustment_get_page_size(adjust);
2886 upper = gtk_adjustment_get_upper(adjust);
2887 lower = gtk_adjustment_get_lower(adjust);
2888 si = gtk_adjustment_get_step_increment(adjust);
2889 pi = gtk_adjustment_get_page_increment(adjust);
2890 max = upper - ps;
2892 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2893 "max %f si %f pi %f\n",
2894 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2895 pos, ps, upper, lower, max, si, pi);
2897 switch (args->i) {
2898 case XT_MOVE_DOWN:
2899 case XT_MOVE_RIGHT:
2900 pos += si;
2901 gtk_adjustment_set_value(adjust, MIN(pos, max));
2902 break;
2903 case XT_MOVE_UP:
2904 case XT_MOVE_LEFT:
2905 pos -= si;
2906 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2907 break;
2908 case XT_MOVE_BOTTOM:
2909 case XT_MOVE_FARRIGHT:
2910 gtk_adjustment_set_value(adjust, max);
2911 break;
2912 case XT_MOVE_TOP:
2913 case XT_MOVE_FARLEFT:
2914 gtk_adjustment_set_value(adjust, lower);
2915 break;
2916 case XT_MOVE_PAGEDOWN:
2917 pos += pi;
2918 gtk_adjustment_set_value(adjust, MIN(pos, max));
2919 break;
2920 case XT_MOVE_PAGEUP:
2921 pos -= pi;
2922 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2923 break;
2924 case XT_MOVE_HALFDOWN:
2925 pos += pi / 2;
2926 gtk_adjustment_set_value(adjust, MIN(pos, max));
2927 break;
2928 case XT_MOVE_HALFUP:
2929 pos -= pi / 2;
2930 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2931 break;
2932 default:
2933 return (XT_CB_PASSTHROUGH);
2936 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
2938 return (XT_CB_HANDLED);
2942 tabaction(struct tab *t, struct karg *args)
2944 int rv = XT_CB_HANDLED;
2945 char *url = NULL, *newuri = NULL;
2946 struct undo *u;
2948 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
2950 if (t == NULL)
2951 return (XT_CB_PASSTHROUGH);
2953 switch (args->i) {
2954 case XT_TAB_NEW:
2955 if ((url = getparams(args->s, "tabnew")))
2956 create_new_tab(url, NULL, 1);
2957 else
2958 create_new_tab(NULL, NULL, 1);
2959 break;
2960 case XT_TAB_DELETE:
2961 delete_tab(t);
2962 break;
2963 case XT_TAB_DELQUIT:
2964 if (gtk_notebook_get_n_pages(notebook) > 1)
2965 delete_tab(t);
2966 else
2967 quit(t, args);
2968 break;
2969 case XT_TAB_OPEN:
2970 if ((url = getparams(args->s, "open")) ||
2971 ((url = getparams(args->s, "op"))) ||
2972 ((url = getparams(args->s, "o"))))
2974 else {
2975 rv = XT_CB_PASSTHROUGH;
2976 goto done;
2979 if (valid_url_type(url)) {
2980 newuri = guess_url_type(url);
2981 url = newuri;
2983 webkit_web_view_load_uri(t->wv, url);
2984 if (newuri)
2985 g_free(newuri);
2986 break;
2987 case XT_TAB_UNDO_CLOSE:
2988 if (undo_count == 0) {
2989 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
2990 goto done;
2991 } else {
2992 undo_count--;
2993 u = TAILQ_FIRST(&undos);
2994 create_new_tab(u->uri, u, 1);
2996 TAILQ_REMOVE(&undos, u, entry);
2997 g_free(u->uri);
2998 /* u->history is freed in create_new_tab() */
2999 g_free(u);
3001 break;
3002 default:
3003 rv = XT_CB_PASSTHROUGH;
3004 goto done;
3007 done:
3008 if (args->s) {
3009 g_free(args->s);
3010 args->s = NULL;
3013 return (rv);
3017 resizetab(struct tab *t, struct karg *args)
3019 if (t == NULL || args == NULL)
3020 errx(1, "resizetab");
3022 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3023 t->tab_id, args->i);
3025 adjustfont_webkit(t, args->i);
3027 return (XT_CB_HANDLED);
3031 movetab(struct tab *t, struct karg *args)
3033 struct tab *tt;
3034 int x;
3036 if (t == NULL || args == NULL)
3037 errx(1, "movetab");
3039 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3040 t->tab_id, args->i);
3042 if (args->i == XT_TAB_INVALID)
3043 return (XT_CB_PASSTHROUGH);
3045 if (args->i < XT_TAB_INVALID) {
3046 /* next or previous tab */
3047 if (TAILQ_EMPTY(&tabs))
3048 return (XT_CB_PASSTHROUGH);
3050 switch (args->i) {
3051 case XT_TAB_NEXT:
3052 /* if at the last page, loop around to the first */
3053 if (gtk_notebook_get_current_page(notebook) ==
3054 gtk_notebook_get_n_pages(notebook) - 1) {
3055 gtk_notebook_set_current_page(notebook, 0);
3056 } else {
3057 gtk_notebook_next_page(notebook);
3059 break;
3060 case XT_TAB_PREV:
3061 /* if at the first page, loop around to the last */
3062 if (gtk_notebook_current_page(notebook) == 0) {
3063 gtk_notebook_set_current_page(notebook,
3064 gtk_notebook_get_n_pages(notebook) - 1);
3065 } else {
3066 gtk_notebook_prev_page(notebook);
3068 break;
3069 case XT_TAB_FIRST:
3070 gtk_notebook_set_current_page(notebook, 0);
3071 break;
3072 case XT_TAB_LAST:
3073 gtk_notebook_set_current_page(notebook, -1);
3074 break;
3075 default:
3076 return (XT_CB_PASSTHROUGH);
3079 return (XT_CB_HANDLED);
3082 /* jump to tab */
3083 x = args->i - 1;
3084 if (t->tab_id == x) {
3085 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3086 return (XT_CB_HANDLED);
3089 TAILQ_FOREACH(tt, &tabs, entry) {
3090 if (tt->tab_id == x) {
3091 gtk_notebook_set_current_page(notebook, x);
3092 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3093 if (tt->focus_wv)
3094 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
3098 return (XT_CB_HANDLED);
3102 command(struct tab *t, struct karg *args)
3104 WebKitWebFrame *frame;
3105 char *s = NULL, *ss = NULL;
3106 GdkColor color;
3107 const gchar *uri;
3109 if (t == NULL || args == NULL)
3110 errx(1, "command");
3112 switch (args->i) {
3113 case '/':
3114 s = "/";
3115 break;
3116 case '?':
3117 s = "?";
3118 break;
3119 case ':':
3120 s = ":";
3121 break;
3122 case XT_CMD_OPEN:
3123 s = ":open ";
3124 break;
3125 case XT_CMD_TABNEW:
3126 s = ":tabnew ";
3127 break;
3128 case XT_CMD_OPEN_CURRENT:
3129 s = ":open ";
3130 /* FALL THROUGH */
3131 case XT_CMD_TABNEW_CURRENT:
3132 if (!s) /* FALL THROUGH? */
3133 s = ":tabnew ";
3134 frame = webkit_web_view_get_main_frame(t->wv);
3135 uri = webkit_web_frame_get_uri(frame);
3136 if (uri && strlen(uri)) {
3137 ss = g_strdup_printf("%s%s", s, uri);
3138 s = ss;
3140 break;
3141 default:
3142 show_oops(t, "command: invalid opcode %d", args->i);
3143 return (XT_CB_PASSTHROUGH);
3146 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3148 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3149 gdk_color_parse("white", &color);
3150 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3151 show_cmd(t);
3152 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3153 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3155 if (ss)
3156 g_free(ss);
3158 return (XT_CB_HANDLED);
3162 * Return a new string with a download row (in html)
3163 * appended. Old string is freed.
3165 char *
3166 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3169 WebKitDownloadStatus stat;
3170 char *status_html = NULL, *cmd_html = NULL, *new_html;
3171 gdouble progress;
3172 char cur_sz[FMT_SCALED_STRSIZE];
3173 char tot_sz[FMT_SCALED_STRSIZE];
3174 char *xtp_prefix;
3176 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3178 /* All actions wil take this form:
3179 * xxxt://class/seskey
3181 xtp_prefix = g_strdup_printf("%s%d/%s/",
3182 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3184 stat = webkit_download_get_status(dl->download);
3186 switch (stat) {
3187 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3188 status_html = g_strdup_printf("Finished");
3189 cmd_html = g_strdup_printf(
3190 "<a href='%s%d/%d'>Remove</a>",
3191 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3192 break;
3193 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3194 /* gather size info */
3195 progress = 100 * webkit_download_get_progress(dl->download);
3197 fmt_scaled(
3198 webkit_download_get_current_size(dl->download), cur_sz);
3199 fmt_scaled(
3200 webkit_download_get_total_size(dl->download), tot_sz);
3202 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3203 tot_sz, progress);
3204 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3205 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3207 break;
3208 /* LLL */
3209 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3210 status_html = g_strdup_printf("Cancelled");
3211 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3212 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3213 break;
3214 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3215 status_html = g_strdup_printf("Error!");
3216 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3217 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3218 break;
3219 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3220 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3221 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3222 status_html = g_strdup_printf("Starting");
3223 break;
3224 default:
3225 show_oops(t, "%s: unknown download status", __func__);
3228 new_html = g_strdup_printf(
3229 "%s\n<tr><td>%s</td><td>%s</td>"
3230 "<td style='text-align:center'>%s</td></tr>\n",
3231 html, webkit_download_get_uri(dl->download),
3232 status_html, cmd_html);
3233 g_free(html);
3235 if (status_html)
3236 g_free(status_html);
3238 if (cmd_html)
3239 g_free(cmd_html);
3241 g_free(xtp_prefix);
3243 return new_html;
3247 * update all download tabs apart from one. Pass NULL if
3248 * you want to update all.
3250 void
3251 update_download_tabs(struct tab *apart_from)
3253 struct tab *t;
3254 if (!updating_dl_tabs) {
3255 updating_dl_tabs = 1; /* stop infinite recursion */
3256 TAILQ_FOREACH(t, &tabs, entry)
3257 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3258 && (t != apart_from))
3259 xtp_page_dl(t, NULL);
3260 updating_dl_tabs = 0;
3265 * update all cookie tabs apart from one. Pass NULL if
3266 * you want to update all.
3268 void
3269 update_cookie_tabs(struct tab *apart_from)
3271 struct tab *t;
3272 if (!updating_cl_tabs) {
3273 updating_cl_tabs = 1; /* stop infinite recursion */
3274 TAILQ_FOREACH(t, &tabs, entry)
3275 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3276 && (t != apart_from))
3277 xtp_page_cl(t, NULL);
3278 updating_cl_tabs = 0;
3283 * update all history tabs apart from one. Pass NULL if
3284 * you want to update all.
3286 void
3287 update_history_tabs(struct tab *apart_from)
3289 struct tab *t;
3291 if (!updating_hl_tabs) {
3292 updating_hl_tabs = 1; /* stop infinite recursion */
3293 TAILQ_FOREACH(t, &tabs, entry)
3294 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3295 && (t != apart_from))
3296 xtp_page_hl(t, NULL);
3297 updating_hl_tabs = 0;
3301 /* cookie management XTP page */
3303 xtp_page_cl(struct tab *t, struct karg *args)
3305 char *header, *body, *footer, *page, *tmp;
3306 int i = 1; /* all ids start 1 */
3307 GSList *sc, *pc, *pc_start;
3308 SoupCookie *c;
3309 char *type;
3311 DNPRINTF(XT_D_CMD, "%s", __func__);
3313 if (t == NULL)
3314 errx(1, "%s: null tab", __func__);
3316 /* mark this tab as cookie jar */
3317 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3319 /* Generate a new session key */
3320 if (!updating_cl_tabs)
3321 generate_xtp_session_key(&cl_session_key);
3323 /* header */
3324 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3325 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3326 "</head><body><h1>Cookie Jar</h1>\n");
3328 /* body */
3329 body = g_strdup_printf("<div align='center'><table><tr>"
3330 "<th>Type</th>"
3331 "<th>Name</th>"
3332 "<th>Value</th>"
3333 "<th>Domain</th>"
3334 "<th>Path</th>"
3335 "<th>Expires</th>"
3336 "<th>Secure</th>"
3337 "<th>HTTP_only</th>"
3338 "<th>Remove</th></tr>\n");
3340 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3341 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3342 pc_start = pc;
3344 for (; sc; sc = sc->next) {
3345 c = sc->data;
3347 type = "Session";
3348 for (pc = pc_start; pc; pc = pc->next)
3349 if (soup_cookie_equal(pc->data, c)) {
3350 type = "Session + Persistent";
3351 break;
3354 tmp = body;
3355 body = g_strdup_printf(
3356 "%s\n<tr>"
3357 "<td style='width: 3%%; text-align: center'>%s</td>"
3358 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3359 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3360 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3361 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3362 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3363 "<td style='width: 3%%; text-align: center'>%d</td>"
3364 "<td style='width: 3%%; text-align: center'>%d</td>"
3365 "<td style='width: 3%%; text-align: center'>"
3366 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3367 body,
3368 type,
3369 c->name,
3370 c->value,
3371 c->domain,
3372 c->path,
3373 c->expires ?
3374 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3375 c->secure,
3376 c->http_only,
3378 XT_XTP_STR,
3379 XT_XTP_CL,
3380 cl_session_key,
3381 XT_XTP_CL_REMOVE,
3385 g_free(tmp);
3386 i++;
3389 soup_cookies_free(sc);
3390 soup_cookies_free(pc);
3392 /* small message if there are none */
3393 if (i == 1) {
3394 tmp = body;
3395 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3396 "colspan='8'>No Cookies</td></tr>\n", body);
3397 g_free(tmp);
3400 /* footer */
3401 footer = g_strdup_printf("</table></div></body></html>");
3403 page = g_strdup_printf("%s%s%s", header, body, footer);
3405 g_free(header);
3406 g_free(body);
3407 g_free(footer);
3409 load_webkit_string(t, page);
3410 update_cookie_tabs(t);
3412 g_free(page);
3414 return (0);
3418 xtp_page_hl(struct tab *t, struct karg *args)
3420 char *header, *body, *footer, *page, *tmp;
3421 struct history *h;
3422 int i = 1; /* all ids start 1 */
3424 DNPRINTF(XT_D_CMD, "%s", __func__);
3426 if (t == NULL)
3427 errx(1, "%s: null tab", __func__);
3429 /* mark this tab as history manager */
3430 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3432 /* Generate a new session key */
3433 if (!updating_hl_tabs)
3434 generate_xtp_session_key(&hl_session_key);
3436 /* header */
3437 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3438 "<title>History</title>\n"
3439 "%s"
3440 "</head>"
3441 "<h1>History</h1>\n",
3442 XT_PAGE_STYLE);
3444 /* body */
3445 body = g_strdup_printf("<div align='center'><table><tr>"
3446 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3448 RB_FOREACH_REVERSE(h, history_list, &hl) {
3449 tmp = body;
3450 body = g_strdup_printf(
3451 "%s\n<tr>"
3452 "<td><a href='%s'>%s</a></td>"
3453 "<td>%s</td>"
3454 "<td style='text-align: center'>"
3455 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3456 body, h->uri, h->uri, h->title,
3457 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3458 XT_XTP_HL_REMOVE, i);
3460 g_free(tmp);
3461 i++;
3464 /* small message if there are none */
3465 if (i == 1) {
3466 tmp = body;
3467 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3468 "colspan='3'>No History</td></tr>\n", body);
3469 g_free(tmp);
3472 /* footer */
3473 footer = g_strdup_printf("</table></div></body></html>");
3475 page = g_strdup_printf("%s%s%s", header, body, footer);
3478 * update all history manager tabs as the xtp session
3479 * key has now changed. No need to update the current tab.
3480 * Already did that above.
3482 update_history_tabs(t);
3484 g_free(header);
3485 g_free(body);
3486 g_free(footer);
3488 load_webkit_string(t, page);
3489 g_free(page);
3491 return (0);
3495 * Generate a web page detailing the status of any downloads
3498 xtp_page_dl(struct tab *t, struct karg *args)
3500 struct download *dl;
3501 char *header, *body, *footer, *page, *tmp;
3502 char *ref;
3503 int n_dl = 1;
3505 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3507 if (t == NULL)
3508 errx(1, "%s: null tab", __func__);
3510 /* mark as a download manager tab */
3511 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3514 * Generate a new session key for next page instance.
3515 * This only happens for the top level call to xtp_page_dl()
3516 * in which case updating_dl_tabs is 0.
3518 if (!updating_dl_tabs)
3519 generate_xtp_session_key(&dl_session_key);
3521 /* header - with refresh so as to update */
3522 if (refresh_interval >= 1)
3523 ref = g_strdup_printf(
3524 "<meta http-equiv='refresh' content='%u"
3525 ";url=%s%d/%s/%d' />\n",
3526 refresh_interval,
3527 XT_XTP_STR,
3528 XT_XTP_DL,
3529 dl_session_key,
3530 XT_XTP_DL_LIST);
3531 else
3532 ref = g_strdup("");
3535 header = g_strdup_printf(
3536 "%s\n<head>"
3537 "<title>Downloads</title>\n%s%s</head>\n",
3538 XT_DOCTYPE XT_HTML_TAG,
3539 ref,
3540 XT_PAGE_STYLE);
3542 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3543 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3544 "</p><table><tr><th style='width: 60%%'>"
3545 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3546 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3548 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3549 body = xtp_page_dl_row(t, body, dl);
3550 n_dl++;
3553 /* message if no downloads in list */
3554 if (n_dl == 1) {
3555 tmp = body;
3556 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3557 " style='text-align: center'>"
3558 "No downloads</td></tr>\n", body);
3559 g_free(tmp);
3562 /* footer */
3563 footer = g_strdup_printf("</table></div></body></html>");
3565 page = g_strdup_printf("%s%s%s", header, body, footer);
3569 * update all download manager tabs as the xtp session
3570 * key has now changed. No need to update the current tab.
3571 * Already did that above.
3573 update_download_tabs(t);
3575 g_free(ref);
3576 g_free(header);
3577 g_free(body);
3578 g_free(footer);
3580 load_webkit_string(t, page);
3581 g_free(page);
3583 return (0);
3587 search(struct tab *t, struct karg *args)
3589 gboolean d;
3591 if (t == NULL || args == NULL)
3592 errx(1, "search");
3593 if (t->search_text == NULL) {
3594 if (global_search == NULL)
3595 return (XT_CB_PASSTHROUGH);
3596 else {
3597 t->search_text = g_strdup(global_search);
3598 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3599 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3603 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3604 t->tab_id, args->i, t->search_forward, t->search_text);
3606 switch (args->i) {
3607 case XT_SEARCH_NEXT:
3608 d = t->search_forward;
3609 break;
3610 case XT_SEARCH_PREV:
3611 d = !t->search_forward;
3612 break;
3613 default:
3614 return (XT_CB_PASSTHROUGH);
3617 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3619 return (XT_CB_HANDLED);
3622 struct settings_args {
3623 char **body;
3624 int i;
3627 void
3628 print_setting(struct settings *s, char *val, void *cb_args)
3630 char *tmp, *color;
3631 struct settings_args *sa = cb_args;
3633 if (sa == NULL)
3634 return;
3636 if (s->flags & XT_SF_RUNTIME)
3637 color = "#22cc22";
3638 else
3639 color = "#cccccc";
3641 tmp = *sa->body;
3642 *sa->body = g_strdup_printf(
3643 "%s\n<tr>"
3644 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3645 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3646 *sa->body,
3647 color,
3648 s->name,
3649 color,
3652 g_free(tmp);
3653 sa->i++;
3657 set(struct tab *t, struct karg *args)
3659 char *header, *body, *footer, *page, *tmp, *pars;
3660 int i = 1;
3661 struct settings_args sa;
3663 if ((pars = getparams(args->s, "set")) == NULL) {
3664 bzero(&sa, sizeof sa);
3665 sa.body = &body;
3667 /* header */
3668 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3669 "\n<head><title>Settings</title>\n"
3670 "</head><body><h1>Settings</h1>\n");
3672 /* body */
3673 body = g_strdup_printf("<div align='center'><table><tr>"
3674 "<th align='left'>Setting</th>"
3675 "<th align='left'>Value</th></tr>\n");
3677 settings_walk(print_setting, &sa);
3678 i = sa.i;
3680 /* small message if there are none */
3681 if (i == 1) {
3682 tmp = body;
3683 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3684 "colspan='2'>No settings</td></tr>\n", body);
3685 g_free(tmp);
3688 /* footer */
3689 footer = g_strdup_printf("</table></div></body></html>");
3691 page = g_strdup_printf("%s%s%s", header, body, footer);
3693 g_free(header);
3694 g_free(body);
3695 g_free(footer);
3697 load_webkit_string(t, page);
3698 } else {
3699 show_oops(t, "Invalid command: %s", pars);
3702 return (XT_CB_PASSTHROUGH);
3706 session_save(struct tab *t, char *filename, char **ret)
3708 struct karg a;
3709 char *f = filename;
3710 int rv = 1;
3712 f += strlen("save");
3713 while (*f == ' ' && *f != '\0')
3714 f++;
3715 if (strlen(f) == 0)
3716 goto done;
3718 a.s = f;
3719 save_tabs(t, &a);
3720 strlcpy(named_session, f, sizeof named_session);
3722 *ret = f;
3723 rv = 0;
3724 done:
3725 return (rv);
3729 session_open(struct tab *t, char *filename, char **ret)
3731 struct karg a;
3732 char *f = filename;
3733 int rv = 1;
3735 f += strlen("open");
3736 while (*f == ' ' && *f != '\0')
3737 f++;
3738 if (strlen(f) == 0)
3739 goto done;
3741 a.s = f;
3742 open_tabs(t, &a);
3743 strlcpy(named_session, f, sizeof named_session);
3745 *ret = f;
3746 rv = 0;
3747 done:
3748 return (rv);
3752 session_cmd(struct tab *t, struct karg *args)
3754 char *action = NULL;
3755 char *filename = NULL;
3757 if (t == NULL)
3758 return (1);
3760 if ((action = getparams(args->s, "session")))
3762 else
3763 action = "show";
3765 if (!strcmp(action, "show"))
3766 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
3767 XT_SAVED_TABS_FILE : named_session);
3768 else if (g_str_has_prefix(action, "save ")) {
3769 if (session_save(t, action, &filename)) {
3770 show_oops(t, "Can't save session: %s",
3771 filename ? filename : "INVALID");
3772 goto done;
3774 } else if (g_str_has_prefix(action, "open ")) {
3775 if (session_open(t, action, &filename)) {
3776 show_oops(t, "Can't open session: %s",
3777 filename ? filename : "INVALID");
3778 goto done;
3780 } else
3781 show_oops(t, "Invalid command: %s", action);
3782 done:
3783 return (XT_CB_PASSTHROUGH);
3787 * Make a hardcopy of the page
3790 print_page(struct tab *t, struct karg *args)
3792 WebKitWebFrame *frame;
3794 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3797 * for now we just call the GTK print box,
3798 * but later we might decide to hook in a command.
3800 frame = webkit_web_view_get_main_frame(t->wv);
3801 webkit_web_frame_print(frame);
3803 return (0);
3807 go_home(struct tab *t, struct karg *args)
3809 char *newuri;
3811 newuri = guess_url_type((char *)home);
3812 webkit_web_view_load_uri(t->wv, newuri);
3813 free(newuri);
3815 return (0);
3819 restart(struct tab *t, struct karg *args)
3821 struct karg a;
3823 a.s = XT_RESTART_TABS_FILE;
3824 save_tabs(t, &a);
3825 execvp(start_argv[0], start_argv);
3826 /* NOTREACHED */
3828 return (0);
3831 /* inherent to GTK not all keys will be caught at all times */
3832 /* XXX sort key bindings */
3833 struct key_bindings {
3834 guint mask;
3835 guint use_in_entry;
3836 guint key;
3837 int (*func)(struct tab *, struct karg *);
3838 struct karg arg;
3839 } keys[] = {
3840 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
3841 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
3842 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
3843 { 0, 0, GDK_slash, command, {.i = '/'} },
3844 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
3845 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
3846 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
3847 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
3848 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3849 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3850 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
3851 { 0, 0, GDK_y, yank_uri, {0} },
3852 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
3853 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
3855 /* search */
3856 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
3857 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
3859 /* focus */
3860 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
3861 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
3863 /* command aliases (handy when -S flag is used) */
3864 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
3865 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
3866 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
3867 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
3869 /* hinting */
3870 { 0, 0, GDK_f, hint, {.i = 0} },
3872 /* navigation */
3873 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
3874 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
3875 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
3876 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
3877 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
3878 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
3879 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
3880 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
3881 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
3883 /* vertical movement */
3884 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
3885 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
3886 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
3887 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
3888 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
3889 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
3890 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
3891 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
3892 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
3893 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
3894 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
3895 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
3896 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
3897 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
3898 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
3899 /* horizontal movement */
3900 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
3901 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
3902 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
3903 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
3904 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
3905 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
3907 /* tabs */
3908 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
3909 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
3910 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
3911 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
3912 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
3913 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
3914 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
3915 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
3916 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
3917 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
3918 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
3919 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
3920 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
3921 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
3922 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
3923 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
3924 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
3925 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
3926 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
3927 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
3930 struct cmd {
3931 char *cmd;
3932 int params;
3933 int (*func)(struct tab *, struct karg *);
3934 struct karg arg;
3935 } cmds[] = {
3936 { "q!", 0, quit, {0} },
3937 { "qa", 0, quit, {0} },
3938 { "qa!", 0, quit, {0} },
3939 { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} },
3940 { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3941 { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3942 { "help", 0, help, {0} },
3943 { "about", 0, about, {0} },
3944 { "stats", 0, stats, {0} },
3945 { "version", 0, about, {0} },
3946 { "cookies", 0, xtp_page_cl, {0} },
3947 { "fav", 0, xtp_page_fl, {0} },
3948 { "favadd", 0, add_favorite, {0} },
3949 { "js", 2, js_cmd, {0} },
3950 { "cookie", 2, cookie_cmd, {0} },
3951 { "cert", 1, cert_cmd, {0} },
3952 { "ca", 0, ca_cmd, {0} },
3953 { "dl" , 0, xtp_page_dl, {0} },
3954 { "h" , 0, xtp_page_hl, {0} },
3955 { "hist" , 0, xtp_page_hl, {0} },
3956 { "history" , 0, xtp_page_hl, {0} },
3957 { "home" , 0, go_home, {0} },
3958 { "restart" , 0, restart, {0} },
3960 { "1", 0, move, {.i = XT_MOVE_TOP} },
3961 { "print", 0, print_page, {0} },
3963 /* tabs */
3964 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
3965 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
3966 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
3967 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
3968 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
3969 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
3970 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
3971 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
3972 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3973 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3974 /* XXX add count to these commands */
3975 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
3976 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
3977 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
3978 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
3979 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
3980 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
3981 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
3982 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
3983 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
3984 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
3986 /* settings */
3987 { "set", 1, set, {0} },
3989 /* sessions */
3990 { "session", 1, session_cmd, {0} },
3993 gboolean
3994 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
3996 hide_oops(t);
3998 return (FALSE);
4001 gboolean
4002 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4004 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4006 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4007 delete_tab(t);
4009 return (FALSE);
4013 * cancel, remove, etc. downloads
4015 void
4016 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4018 struct download find, *d;
4020 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4022 /* some commands require a valid download id */
4023 if (cmd != XT_XTP_DL_LIST) {
4024 /* lookup download in question */
4025 find.id = id;
4026 d = RB_FIND(download_list, &downloads, &find);
4028 if (d == NULL) {
4029 show_oops(t, "%s: no such download", __func__);
4030 return;
4034 /* decide what to do */
4035 switch (cmd) {
4036 case XT_XTP_DL_CANCEL:
4037 webkit_download_cancel(d->download);
4038 break;
4039 case XT_XTP_DL_REMOVE:
4040 webkit_download_cancel(d->download); /* just incase */
4041 g_object_unref(d->download);
4042 RB_REMOVE(download_list, &downloads, d);
4043 break;
4044 case XT_XTP_DL_LIST:
4045 /* Nothing */
4046 break;
4047 default:
4048 show_oops(t, "%s: unknown command", __func__);
4049 break;
4051 xtp_page_dl(t, NULL);
4055 * Actions on history, only does one thing for now, but
4056 * we provide the function for future actions
4058 void
4059 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4061 struct history *h, *next;
4062 int i = 1;
4064 switch (cmd) {
4065 case XT_XTP_HL_REMOVE:
4066 /* walk backwards, as listed in reverse */
4067 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4068 next = RB_PREV(history_list, &hl, h);
4069 if (id == i) {
4070 RB_REMOVE(history_list, &hl, h);
4071 g_free((gpointer) h->title);
4072 g_free((gpointer) h->uri);
4073 g_free(h);
4074 break;
4076 i++;
4078 break;
4079 case XT_XTP_HL_LIST:
4080 /* Nothing - just xtp_page_hl() below */
4081 break;
4082 default:
4083 show_oops(t, "%s: unknown command", __func__);
4084 break;
4087 xtp_page_hl(t, NULL);
4090 /* remove a favorite */
4091 void
4092 remove_favorite(struct tab *t, int index)
4094 char file[PATH_MAX], *title, *uri;
4095 char *new_favs, *tmp;
4096 FILE *f;
4097 int i;
4098 size_t len, lineno;
4100 /* open favorites */
4101 snprintf(file, sizeof file, "%s/%s/%s",
4102 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
4104 if ((f = fopen(file, "r")) == NULL) {
4105 show_oops(t, "%s: can't open favorites: %s",
4106 __func__, strerror(errno));
4107 return;
4110 /* build a string which will become the new favroites file */
4111 new_favs = g_strdup_printf("%s", "");
4113 for (i = 1;;) {
4114 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4115 if (feof(f) || ferror(f))
4116 break;
4117 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4118 if (len == 0) {
4119 free(title);
4120 title = NULL;
4121 continue;
4124 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4125 if (feof(f) || ferror(f)) {
4126 show_oops(t, "%s: can't parse favorites %s",
4127 __func__, strerror(errno));
4128 goto clean;
4132 /* as long as this isn't the one we are deleting add to file */
4133 if (i != index) {
4134 tmp = new_favs;
4135 new_favs = g_strdup_printf("%s%s\n%s\n",
4136 new_favs, title, uri);
4137 g_free(tmp);
4140 free(uri);
4141 uri = NULL;
4142 free(title);
4143 title = NULL;
4144 i++;
4146 fclose(f);
4148 /* write back new favorites file */
4149 if ((f = fopen(file, "w")) == NULL) {
4150 show_oops(t, "%s: can't open favorites: %s",
4151 __func__, strerror(errno));
4152 goto clean;
4155 fwrite(new_favs, strlen(new_favs), 1, f);
4156 fclose(f);
4158 clean:
4159 if (uri)
4160 free(uri);
4161 if (title)
4162 free(title);
4164 g_free(new_favs);
4167 void
4168 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4170 switch (cmd) {
4171 case XT_XTP_FL_LIST:
4172 /* nothing, just the below call to xtp_page_fl() */
4173 break;
4174 case XT_XTP_FL_REMOVE:
4175 remove_favorite(t, arg);
4176 break;
4177 default:
4178 show_oops(t, "%s: invalid favorites command", __func__);
4179 break;
4182 xtp_page_fl(t, NULL);
4185 void
4186 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4188 switch (cmd) {
4189 case XT_XTP_CL_LIST:
4190 /* nothing, just xtp_page_cl() */
4191 break;
4192 case XT_XTP_CL_REMOVE:
4193 remove_cookie(arg);
4194 break;
4195 default:
4196 show_oops(t, "%s: unknown cookie xtp command", __func__);
4197 break;
4200 xtp_page_cl(t, NULL);
4203 /* link an XTP class to it's session key and handler function */
4204 struct xtp_despatch {
4205 uint8_t xtp_class;
4206 char **session_key;
4207 void (*handle_func)(struct tab *, uint8_t, int);
4210 struct xtp_despatch xtp_despatches[] = {
4211 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4212 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4213 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4214 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4215 { NULL, NULL, NULL }
4219 * is the url xtp protocol? (xxxt://)
4220 * if so, parse and despatch correct bahvior
4223 parse_xtp_url(struct tab *t, const char *url)
4225 char *dup = NULL, *p, *last;
4226 uint8_t n_tokens = 0;
4227 char *tokens[4] = {NULL, NULL, NULL, ""};
4228 struct xtp_despatch *dsp, *dsp_match = NULL;
4229 uint8_t req_class;
4232 * tokens array meaning:
4233 * tokens[0] = class
4234 * tokens[1] = session key
4235 * tokens[2] = action
4236 * tokens[3] = optional argument
4239 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4241 /*xtp tab meaning is normal unless proven special */
4242 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4244 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4245 return 0;
4247 dup = g_strdup(url + strlen(XT_XTP_STR));
4249 /* split out the url */
4250 for ((p = strtok_r(dup, "/", &last)); p;
4251 (p = strtok_r(NULL, "/", &last))) {
4252 if (n_tokens < 4)
4253 tokens[n_tokens++] = p;
4256 /* should be atleast three fields 'class/seskey/command/arg' */
4257 if (n_tokens < 3)
4258 goto clean;
4260 dsp = xtp_despatches;
4261 req_class = atoi(tokens[0]);
4262 while (dsp->xtp_class != NULL) {
4263 if (dsp->xtp_class == req_class) {
4264 dsp_match = dsp;
4265 break;
4267 dsp++;
4270 /* did we find one atall? */
4271 if (dsp_match == NULL) {
4272 show_oops(t, "%s: no matching xtp despatch found", __func__);
4273 goto clean;
4276 /* check session key and call despatch function */
4277 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
4278 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4281 clean:
4282 if (dup)
4283 g_free(dup);
4285 return 1;
4290 void
4291 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4293 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4294 char *newuri = NULL;
4296 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4298 if (t == NULL)
4299 errx(1, "activate_uri_entry_cb");
4301 if (uri == NULL)
4302 errx(1, "uri");
4304 uri += strspn(uri, "\t ");
4306 /* if xxxt:// treat specially */
4307 if (!parse_xtp_url(t, uri)) {
4308 if (valid_url_type((char *)uri)) {
4309 newuri = guess_url_type((char *)uri);
4310 uri = newuri;
4313 webkit_web_view_load_uri(t->wv, uri);
4314 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4317 if (newuri)
4318 g_free(newuri);
4321 void
4322 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4324 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4325 char *newuri = NULL;
4327 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4329 if (t == NULL)
4330 errx(1, "activate_search_entry_cb");
4332 if (search_string == NULL) {
4333 show_oops(t, "no search_string");
4334 return;
4337 newuri = g_strdup_printf(search_string, search);
4339 webkit_web_view_load_uri(t->wv, newuri);
4340 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4342 if (newuri)
4343 g_free(newuri);
4346 void
4347 check_and_set_js(gchar *uri, struct tab *t)
4349 struct domain *d = NULL;
4350 int es = 0;
4352 if (uri == NULL || t == NULL)
4353 return;
4355 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4356 es = 0;
4357 else
4358 es = 1;
4360 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4361 es ? "enable" : "disable", uri);
4363 g_object_set((GObject *)t->settings,
4364 "enable-scripts", es, (char *)NULL);
4365 webkit_web_view_set_settings(t->wv, t->settings);
4367 button_set_stockid(t->js_toggle,
4368 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4371 void
4372 show_ca_status(struct tab *t, const char *uri)
4374 WebKitWebFrame *frame;
4375 WebKitWebDataSource *source;
4376 WebKitNetworkRequest *request;
4377 SoupMessage *message;
4378 GdkColor color;
4379 gchar *col_str = "white";
4380 int r;
4382 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4383 ssl_strict_certs, ssl_ca_file, uri);
4385 if (uri == NULL)
4386 goto done;
4387 if (ssl_ca_file == NULL) {
4388 if (g_str_has_prefix(uri, "http://"))
4389 goto done;
4390 if (g_str_has_prefix(uri, "https://")) {
4391 col_str = "red";
4392 goto done;
4394 return;
4396 if (g_str_has_prefix(uri, "http://") ||
4397 !g_str_has_prefix(uri, "https://"))
4398 goto done;
4400 frame = webkit_web_view_get_main_frame(t->wv);
4401 source = webkit_web_frame_get_data_source(frame);
4402 request = webkit_web_data_source_get_request(source);
4403 message = webkit_network_request_get_message(request);
4405 if (message && (soup_message_get_flags(message) &
4406 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4407 col_str = "green";
4408 goto done;
4409 } else {
4410 r = load_compare_cert(t, NULL);
4411 if (r == 0)
4412 col_str = "lightblue";
4413 else if (r == 1)
4414 col_str = "yellow";
4415 else
4416 col_str = "red";
4417 goto done;
4419 done:
4420 if (col_str) {
4421 gdk_color_parse(col_str, &color);
4422 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4426 void
4427 abort_and_free_favicon(struct tab *t)
4429 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
4430 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
4431 if (t->icon_download) {
4432 webkit_download_cancel(t->icon_download);
4433 g_object_unref(t->icon_download);
4435 if (t->icon_request)
4436 g_object_unref(t->icon_request);
4437 if (t->icon_pixbuf)
4438 g_object_unref(t->icon_pixbuf);
4439 if (t->icon_dest_uri)
4440 g_free(t->icon_dest_uri);
4442 t->icon_pixbuf = NULL;
4443 t->icon_request = NULL;
4444 t->icon_download = NULL;
4445 t->icon_dest_uri = NULL;
4447 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
4448 GTK_ENTRY_ICON_PRIMARY, "text-html");
4451 void
4452 set_favicon_from_file(struct tab *t, char *file)
4454 gint width, height;
4455 GdkPixbuf *pixbuf, *scaled;
4456 struct stat sb;
4458 if (t == NULL || file == NULL)
4459 return;
4460 if (t->icon_pixbuf) {
4461 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
4462 return;
4465 if (g_str_has_prefix(file, "file://"))
4466 file += strlen("file://");
4467 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
4469 if (!stat(file, &sb)) {
4470 if (sb.st_size == 0) {
4471 /* corrupt icon so trash it */
4472 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
4473 __func__, file);
4474 unlink(file);
4475 /* no need to set icon to default here */
4476 return;
4480 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
4481 if (pixbuf == NULL) {
4482 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
4483 GTK_ENTRY_ICON_PRIMARY, "text-html");
4484 return;
4487 g_object_get(pixbuf, "width", &width, "height", &height,
4488 (char *)NULL);
4489 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
4490 __func__, t->tab_id, width, height);
4492 if (width > 16 || height > 16) {
4493 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4494 GDK_INTERP_BILINEAR);
4495 g_object_unref(pixbuf);
4496 } else
4497 scaled = pixbuf;
4499 if (scaled == NULL) {
4500 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4501 GDK_INTERP_BILINEAR);
4502 return;
4505 t->icon_pixbuf = scaled;
4506 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
4507 GTK_ENTRY_ICON_PRIMARY, t->icon_pixbuf);
4510 void
4511 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
4512 struct tab *t)
4514 WebKitDownloadStatus status = webkit_download_get_status (download);
4516 if (t == NULL)
4517 return;
4519 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
4520 __func__, t->tab_id, status);
4522 switch (status) {
4523 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4524 /* -1 */
4525 break;
4526 case WEBKIT_DOWNLOAD_STATUS_CREATED:
4527 /* 0 */
4528 break;
4529 case WEBKIT_DOWNLOAD_STATUS_STARTED:
4530 /* 1 */
4531 break;
4532 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4533 /* 2 */
4534 break;
4535 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4536 /* 3 */
4537 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
4538 __func__, t->icon_dest_uri);
4539 set_favicon_from_file(t, t->icon_dest_uri);
4540 g_free(t->icon_dest_uri);
4541 /* these will be freed post callback */
4542 t->icon_request = NULL;
4543 t->icon_download = NULL;
4544 t->icon_dest_uri = NULL;
4545 break;
4546 default:
4547 break;
4551 void
4552 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
4554 gchar *name_hash, file[PATH_MAX];
4555 struct stat sb;
4557 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
4559 if (uri == NULL || t == NULL)
4560 return;
4562 if (t->icon_request) {
4563 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
4564 return;
4567 /* check to see if we got the icon in cache */
4568 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
4569 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
4570 g_free(name_hash);
4572 if (!stat(file, &sb)) {
4573 if (sb.st_size > 0) {
4574 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
4575 __func__, file);
4576 set_favicon_from_file(t, file);
4577 return;
4580 /* corrupt icon so trash it */
4581 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
4582 __func__, file);
4583 unlink(file);
4586 /* create download for icon */
4587 t->icon_request = webkit_network_request_new(uri);
4588 if (t->icon_request == NULL) {
4589 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
4590 __func__, uri);
4591 return;
4594 t->icon_download = webkit_download_new(t->icon_request);
4596 /* we have to free icon_dest_uri later */
4597 t->icon_dest_uri = g_strdup_printf("file://%s", file);
4598 webkit_download_set_destination_uri(t->icon_download,
4599 t->icon_dest_uri);
4601 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
4602 G_CALLBACK(favicon_download_status_changed_cb), t);
4604 webkit_download_start(t->icon_download);
4607 void
4608 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4610 WebKitWebFrame *frame;
4611 const gchar *set = NULL, *uri = NULL, *title = NULL;
4612 struct history *h, find;
4613 int add = 0;
4614 const gchar *s_loading;
4616 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4617 webkit_web_view_get_load_status(wview));
4619 if (t == NULL)
4620 errx(1, "notify_load_status_cb");
4622 switch (webkit_web_view_get_load_status(wview)) {
4623 case WEBKIT_LOAD_PROVISIONAL:
4624 abort_and_free_favicon(t);
4625 #if GTK_CHECK_VERSION(2, 20, 0)
4626 gtk_widget_show(t->spinner);
4627 gtk_spinner_start(GTK_SPINNER(t->spinner));
4628 #endif
4629 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4631 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4632 t->focus_wv = 1;
4634 /* take focus if we are visible */
4635 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4636 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4638 break;
4640 case WEBKIT_LOAD_COMMITTED:
4641 frame = webkit_web_view_get_main_frame(wview);
4642 uri = webkit_web_frame_get_uri(frame);
4643 if (uri)
4644 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4646 /* check if js white listing is enabled */
4647 if (enable_js_whitelist) {
4648 frame = webkit_web_view_get_main_frame(wview);
4649 uri = webkit_web_frame_get_uri(frame);
4650 check_and_set_js((gchar *)uri, t);
4653 show_ca_status(t, uri);
4654 break;
4656 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4657 title = webkit_web_view_get_title(wview);
4658 frame = webkit_web_view_get_main_frame(wview);
4659 uri = webkit_web_frame_get_uri(frame);
4660 if (title)
4661 set = title;
4662 else if (uri)
4663 set = uri;
4664 else
4665 break;
4667 gtk_label_set_text(GTK_LABEL(t->label), set);
4668 gtk_window_set_title(GTK_WINDOW(main_window), set);
4670 if (uri) {
4671 if (!strncmp(uri, "http://", strlen("http://")) ||
4672 !strncmp(uri, "https://", strlen("https://")) ||
4673 !strncmp(uri, "file://", strlen("file://")))
4674 add = 1;
4675 if (add == 0)
4676 break;
4677 find.uri = uri;
4678 h = RB_FIND(history_list, &hl, &find);
4679 if (h)
4680 break;
4682 h = g_malloc(sizeof *h);
4683 h->uri = g_strdup(uri);
4684 if (title)
4685 h->title = g_strdup(title);
4686 else
4687 h->title = g_strdup(uri);
4688 RB_INSERT(history_list, &hl, h);
4689 update_history_tabs(NULL);
4692 break;
4694 case WEBKIT_LOAD_FINISHED:
4695 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4696 case WEBKIT_LOAD_FAILED:
4697 #endif
4698 #if GTK_CHECK_VERSION(2, 20, 0)
4699 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4700 gtk_widget_hide(t->spinner);
4701 #endif
4702 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4703 if (s_loading && !strcmp(s_loading, "Loading"))
4704 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4705 default:
4706 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4707 break;
4710 if (t->item)
4711 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
4712 else
4713 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4714 webkit_web_view_can_go_back(wview));
4716 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4717 webkit_web_view_can_go_forward(wview));
4720 void
4721 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4723 run_script(t, JS_HINTING);
4726 void
4727 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4729 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4730 progress == 100 ? 0 : (double)progress / 100);
4734 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4735 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4736 WebKitWebPolicyDecision *pd, struct tab *t)
4738 char *uri;
4740 if (t == NULL)
4741 errx(1, "webview_nw_cb");
4743 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4744 webkit_network_request_get_uri(request));
4746 /* open in current tab */
4747 uri = (char *)webkit_network_request_get_uri(request);
4748 webkit_web_view_load_uri(t->wv, uri);
4749 webkit_web_policy_decision_ignore(pd);
4751 return (TRUE); /* we made the decission */
4755 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4756 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4757 WebKitWebPolicyDecision *pd, struct tab *t)
4759 char *uri;
4761 if (t == NULL)
4762 errx(1, "webview_npd_cb");
4764 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4765 t->ctrl_click,
4766 webkit_network_request_get_uri(request));
4768 uri = (char *)webkit_network_request_get_uri(request);
4770 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4771 t->ctrl_click = 0;
4772 create_new_tab(uri, NULL, ctrl_click_focus);
4773 webkit_web_policy_decision_ignore(pd);
4774 return (TRUE); /* we made the decission */
4777 webkit_web_policy_decision_use(pd);
4778 return (TRUE); /* we made the decission */
4781 WebKitWebView *
4782 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4784 if (t == NULL)
4785 errx(1, "webview_cwv_cb");
4787 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
4788 webkit_web_view_get_uri(wv));
4790 return (wv);
4794 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
4796 /* we can not eat the event without throwing gtk off so defer it */
4798 /* catch middle click */
4799 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
4800 t->ctrl_click = 1;
4801 goto done;
4804 /* catch ctrl click */
4805 if (e->type == GDK_BUTTON_RELEASE &&
4806 CLEAN(e->state) == GDK_CONTROL_MASK)
4807 t->ctrl_click = 1;
4808 else
4809 t->ctrl_click = 0;
4810 done:
4811 return (XT_CB_PASSTHROUGH);
4815 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
4817 struct mime_type *m;
4819 m = find_mime_type(mime_type);
4820 if (m == NULL)
4821 return (1);
4823 switch (fork()) {
4824 case -1:
4825 err(1, "fork");
4826 /* NOTREACHED */
4827 case 0:
4828 break;
4829 default:
4830 return (0);
4833 /* child */
4834 execlp(m->mt_action, m->mt_action,
4835 webkit_network_request_get_uri(request), (void *)NULL);
4837 _exit(0);
4839 /* NOTREACHED */
4840 return (0);
4844 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
4845 WebKitNetworkRequest *request, char *mime_type,
4846 WebKitWebPolicyDecision *decision, struct tab *t)
4848 if (t == NULL)
4849 errx(1, "webview_mimetype_cb");
4851 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
4852 t->tab_id, mime_type);
4854 if (run_mimehandler(t, mime_type, request) == 0) {
4855 webkit_web_policy_decision_ignore(decision);
4856 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4857 return (TRUE);
4860 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
4861 webkit_web_policy_decision_download(decision);
4862 return (TRUE);
4865 return (FALSE);
4869 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
4871 const gchar *filename;
4872 char *uri = NULL;
4873 struct download *download_entry;
4874 int ret = TRUE;
4876 if (wk_download == NULL || t == NULL)
4877 errx(1, "%s: invalid pointers", __func__);
4879 filename = webkit_download_get_suggested_filename(wk_download);
4880 if (filename == NULL)
4881 return (FALSE); /* abort download */
4883 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
4885 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
4886 "local %s\n", __func__, t->tab_id, filename, uri);
4888 webkit_download_set_destination_uri(wk_download, uri);
4890 if (webkit_download_get_status(wk_download) ==
4891 WEBKIT_DOWNLOAD_STATUS_ERROR) {
4892 show_oops(t, "%s: download failed to start", __func__);
4893 ret = FALSE;
4894 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
4895 } else {
4896 download_entry = g_malloc(sizeof(struct download));
4897 download_entry->download = wk_download;
4898 download_entry->tab = t;
4899 download_entry->id = next_download_id++;
4900 RB_INSERT(download_list, &downloads, download_entry);
4901 /* get from history */
4902 g_object_ref(wk_download);
4903 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
4906 if (uri)
4907 g_free(uri);
4909 /* sync other download manager tabs */
4910 update_download_tabs(NULL);
4913 * NOTE: never redirect/render the current tab before this
4914 * function returns. This will cause the download to never start.
4916 return (ret); /* start download */
4919 /* XXX currently unused */
4920 void
4921 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
4923 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
4925 if (t == NULL)
4926 errx(1, "webview_hover_cb");
4928 if (uri) {
4929 if (t->hover) {
4930 g_free(t->hover);
4931 t->hover = NULL;
4933 t->hover = g_strdup(uri);
4934 } else if (t->hover) {
4935 g_free(t->hover);
4936 t->hover = NULL;
4941 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
4943 int i;
4944 char s[2], buf[128];
4945 const char *errstr = NULL;
4946 long long link;
4948 /* don't use w directly; use t->whatever instead */
4950 if (t == NULL)
4951 errx(1, "wv_keypress_after_cb");
4953 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
4954 e->keyval, e->state, t);
4956 if (t->hints_on) {
4957 /* ESC */
4958 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
4959 disable_hints(t);
4960 return (XT_CB_HANDLED);
4963 /* RETURN */
4964 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
4965 link = strtonum(t->hint_num, 1, 1000, &errstr);
4966 if (errstr) {
4967 /* we have a string */
4968 } else {
4969 /* we have a number */
4970 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
4971 t->hint_num);
4972 run_script(t, buf);
4974 disable_hints(t);
4977 /* BACKSPACE */
4978 /* XXX unfuck this */
4979 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
4980 if (t->hint_mode == XT_HINT_NUMERICAL) {
4981 /* last input was numerical */
4982 int l;
4983 l = strlen(t->hint_num);
4984 if (l > 0) {
4985 l--;
4986 if (l == 0) {
4987 disable_hints(t);
4988 enable_hints(t);
4989 } else {
4990 t->hint_num[l] = '\0';
4991 goto num;
4994 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
4995 /* last input was alphanumerical */
4996 int l;
4997 l = strlen(t->hint_buf);
4998 if (l > 0) {
4999 l--;
5000 if (l == 0) {
5001 disable_hints(t);
5002 enable_hints(t);
5003 } else {
5004 t->hint_buf[l] = '\0';
5005 goto anum;
5008 } else {
5009 /* bogus */
5010 disable_hints(t);
5014 /* numerical input */
5015 if (CLEAN(e->state) == 0 &&
5016 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
5017 snprintf(s, sizeof s, "%c", e->keyval);
5018 strlcat(t->hint_num, s, sizeof t->hint_num);
5019 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
5020 t->hint_num);
5021 num:
5022 link = strtonum(t->hint_num, 1, 1000, &errstr);
5023 if (errstr) {
5024 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
5025 disable_hints(t);
5026 } else {
5027 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
5028 t->hint_num);
5029 t->hint_mode = XT_HINT_NUMERICAL;
5030 run_script(t, buf);
5033 /* empty the counter buffer */
5034 bzero(t->hint_buf, sizeof t->hint_buf);
5035 return (XT_CB_HANDLED);
5038 /* alphanumerical input */
5039 if (
5040 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5041 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5042 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5043 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5044 snprintf(s, sizeof s, "%c", e->keyval);
5045 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5046 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5047 t->hint_buf);
5048 anum:
5049 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5050 run_script(t, buf);
5052 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5053 t->hint_buf);
5054 t->hint_mode = XT_HINT_ALPHANUM;
5055 run_script(t, buf);
5057 /* empty the counter buffer */
5058 bzero(t->hint_num, sizeof t->hint_num);
5059 return (XT_CB_HANDLED);
5062 return (XT_CB_HANDLED);
5065 for (i = 0; i < LENGTH(keys); i++)
5066 if (e->keyval == keys[i].key && CLEAN(e->state) ==
5067 keys[i].mask) {
5068 keys[i].func(t, &keys[i].arg);
5069 return (XT_CB_HANDLED);
5073 return (XT_CB_PASSTHROUGH);
5077 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5079 hide_oops(t);
5081 return (XT_CB_PASSTHROUGH);
5085 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5087 const gchar *c = gtk_entry_get_text(w);
5088 GdkColor color;
5089 int forward = TRUE;
5091 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5092 e->keyval, e->state, t);
5094 if (t == NULL)
5095 errx(1, "cmd_keyrelease_cb");
5097 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5098 e->keyval, e->state, t);
5100 if (c[0] == ':')
5101 goto done;
5102 if (strlen(c) == 1) {
5103 webkit_web_view_unmark_text_matches(t->wv);
5104 goto done;
5107 if (c[0] == '/')
5108 forward = TRUE;
5109 else if (c[0] == '?')
5110 forward = FALSE;
5111 else
5112 goto done;
5114 /* search */
5115 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
5116 FALSE) {
5117 /* not found, mark red */
5118 gdk_color_parse("red", &color);
5119 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5120 /* unmark and remove selection */
5121 webkit_web_view_unmark_text_matches(t->wv);
5122 /* my kingdom for a way to unselect text in webview */
5123 } else {
5124 /* found, highlight all */
5125 webkit_web_view_unmark_text_matches(t->wv);
5126 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
5127 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5128 gdk_color_parse("white", &color);
5129 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5131 done:
5132 return (XT_CB_PASSTHROUGH);
5135 #if 0
5137 cmd_complete(struct tab *t, char *s)
5139 int i;
5140 GtkEntry *w = GTK_ENTRY(t->cmd);
5142 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
5144 for (i = 0; i < LENGTH(cmds); i++) {
5145 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
5146 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
5147 #if 0
5148 gtk_entry_set_text(w, ":");
5149 gtk_entry_append_text(w, cmds[i].cmd);
5150 gtk_editable_set_position(GTK_EDITABLE(w), -1);
5151 #endif
5155 return (0);
5157 #endif
5160 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5162 int i;
5164 if (t == NULL)
5165 errx(1, "entry_key_cb");
5167 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5168 e->keyval, e->state, t);
5170 hide_oops(t);
5172 if (e->keyval == GDK_Escape)
5173 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5175 for (i = 0; i < LENGTH(keys); i++)
5176 if (e->keyval == keys[i].key &&
5177 CLEAN(e->state) == keys[i].mask &&
5178 keys[i].use_in_entry) {
5179 keys[i].func(t, &keys[i].arg);
5180 return (XT_CB_HANDLED);
5183 return (XT_CB_PASSTHROUGH);
5187 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5189 int rv = XT_CB_HANDLED;
5190 const gchar *c = gtk_entry_get_text(w);
5192 if (t == NULL)
5193 errx(1, "cmd_keypress_cb");
5195 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5196 e->keyval, e->state, t);
5198 /* sanity */
5199 if (c == NULL)
5200 e->keyval = GDK_Escape;
5201 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5202 e->keyval = GDK_Escape;
5204 switch (e->keyval) {
5205 #if 0
5206 case GDK_Tab:
5207 if (c[0] != ':')
5208 goto done;
5210 if (strchr (c, ' ')) {
5211 /* par completion */
5212 fprintf(stderr, "completeme par\n");
5213 goto done;
5216 cmd_complete(t, (char *)&c[1]);
5218 goto done;
5219 #endif
5220 case GDK_BackSpace:
5221 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
5222 break;
5223 /* FALLTHROUGH */
5224 case GDK_Escape:
5225 hide_cmd(t);
5226 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5228 /* cancel search */
5229 if (c[0] == '/' || c[0] == '?')
5230 webkit_web_view_unmark_text_matches(t->wv);
5231 goto done;
5234 rv = XT_CB_PASSTHROUGH;
5235 done:
5236 return (rv);
5240 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
5242 if (t == NULL)
5243 errx(1, "cmd_focusout_cb");
5245 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
5246 t->tab_id, t->focus_wv);
5248 hide_cmd(t);
5249 hide_oops(t);
5251 if (t->focus_wv)
5252 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5253 else
5254 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5256 return (XT_CB_PASSTHROUGH);
5259 void
5260 cmd_activate_cb(GtkEntry *entry, struct tab *t)
5262 int i;
5263 char *s;
5264 const gchar *c = gtk_entry_get_text(entry);
5266 if (t == NULL)
5267 errx(1, "cmd_activate_cb");
5269 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
5271 /* sanity */
5272 if (c == NULL)
5273 goto done;
5274 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5275 goto done;
5276 if (strlen(c) < 2)
5277 goto done;
5278 s = (char *)&c[1];
5280 if (c[0] == '/' || c[0] == '?') {
5281 if (t->search_text) {
5282 g_free(t->search_text);
5283 t->search_text = NULL;
5286 t->search_text = g_strdup(s);
5287 if (global_search)
5288 g_free(global_search);
5289 global_search = g_strdup(s);
5290 t->search_forward = c[0] == '/';
5292 goto done;
5295 for (i = 0; i < LENGTH(cmds); i++)
5296 if (cmds[i].params) {
5297 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
5298 cmds[i].arg.s = g_strdup(s);
5299 goto execute_command;
5301 } else {
5302 if (!strcmp(s, cmds[i].cmd))
5303 goto execute_command;
5305 show_oops(t, "Invalid command: %s", s);
5306 done:
5307 hide_cmd(t);
5308 return;
5310 execute_command:
5311 hide_cmd(t);
5312 cmds[i].func(t, &cmds[i].arg);
5314 void
5315 backward_cb(GtkWidget *w, struct tab *t)
5317 struct karg a;
5319 if (t == NULL)
5320 errx(1, "backward_cb");
5322 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
5324 a.i = XT_NAV_BACK;
5325 navaction(t, &a);
5328 void
5329 forward_cb(GtkWidget *w, struct tab *t)
5331 struct karg a;
5333 if (t == NULL)
5334 errx(1, "forward_cb");
5336 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
5338 a.i = XT_NAV_FORWARD;
5339 navaction(t, &a);
5342 void
5343 stop_cb(GtkWidget *w, struct tab *t)
5345 WebKitWebFrame *frame;
5347 if (t == NULL)
5348 errx(1, "stop_cb");
5350 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5352 frame = webkit_web_view_get_main_frame(t->wv);
5353 if (frame == NULL) {
5354 show_oops(t, "stop_cb: no frame");
5355 return;
5358 webkit_web_frame_stop_loading(frame);
5359 abort_and_free_favicon(t);
5362 void
5363 setup_webkit(struct tab *t)
5365 g_object_set((GObject *)t->settings,
5366 "user-agent", t->user_agent, (char *)NULL);
5367 g_object_set((GObject *)t->settings,
5368 "enable-scripts", enable_scripts, (char *)NULL);
5369 g_object_set((GObject *)t->settings,
5370 "enable-plugins", enable_plugins, (char *)NULL);
5371 adjustfont_webkit(t, XT_FONT_SET);
5373 webkit_web_view_set_settings(t->wv, t->settings);
5376 GtkWidget *
5377 create_browser(struct tab *t)
5379 GtkWidget *w;
5380 gchar *strval;
5382 if (t == NULL)
5383 errx(1, "create_browser");
5385 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5386 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5387 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5388 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5390 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5391 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5392 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5394 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5395 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5397 /* set defaults */
5398 t->settings = webkit_web_settings_new();
5400 if (user_agent == NULL) {
5401 g_object_get((GObject *)t->settings, "user-agent", &strval,
5402 (char *)NULL);
5403 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5404 g_free(strval);
5405 } else {
5406 t->user_agent = g_strdup(user_agent);
5409 setup_webkit(t);
5411 return (w);
5414 GtkWidget *
5415 create_window(void)
5417 GtkWidget *w;
5419 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5420 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
5421 gtk_widget_set_name(w, "xxxterm");
5422 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
5423 g_signal_connect(G_OBJECT(w), "delete_event",
5424 G_CALLBACK (gtk_main_quit), NULL);
5426 return (w);
5429 GtkWidget *
5430 create_toolbar(struct tab *t)
5432 GtkWidget *toolbar = NULL, *b, *eb1;
5434 b = gtk_hbox_new(FALSE, 0);
5435 toolbar = b;
5436 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5438 if (fancy_bar) {
5439 /* backward button */
5440 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
5441 gtk_widget_set_sensitive(t->backward, FALSE);
5442 g_signal_connect(G_OBJECT(t->backward), "clicked",
5443 G_CALLBACK(backward_cb), t);
5444 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5446 /* forward button */
5447 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
5448 gtk_widget_set_sensitive(t->forward, FALSE);
5449 g_signal_connect(G_OBJECT(t->forward), "clicked",
5450 G_CALLBACK(forward_cb), t);
5451 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5452 FALSE, 0);
5454 /* stop button */
5455 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
5456 gtk_widget_set_sensitive(t->stop, FALSE);
5457 g_signal_connect(G_OBJECT(t->stop), "clicked",
5458 G_CALLBACK(stop_cb), t);
5459 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5460 FALSE, 0);
5462 /* JS button */
5463 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
5464 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5465 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5466 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5467 G_CALLBACK(js_toggle_cb), t);
5468 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5471 t->uri_entry = gtk_entry_new();
5472 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5473 G_CALLBACK(activate_uri_entry_cb), t);
5474 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5475 (GCallback)entry_key_cb, t);
5476 eb1 = gtk_hbox_new(FALSE, 0);
5477 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5478 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5479 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5481 /* set empty favicon */
5482 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5483 GTK_ENTRY_ICON_PRIMARY, "text-html");
5485 /* search entry */
5486 if (fancy_bar && search_string) {
5487 GtkWidget *eb2;
5488 t->search_entry = gtk_entry_new();
5489 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5490 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5491 G_CALLBACK(activate_search_entry_cb), t);
5492 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5493 (GCallback)entry_key_cb, t);
5494 gtk_widget_set_size_request(t->search_entry, -1, -1);
5495 eb2 = gtk_hbox_new(FALSE, 0);
5496 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5497 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5499 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5501 return (toolbar);
5504 void
5505 recalc_tabs(void)
5507 struct tab *t;
5509 TAILQ_FOREACH(t, &tabs, entry)
5510 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5514 undo_close_tab_save(struct tab *t)
5516 int m, n;
5517 const gchar *uri;
5518 struct undo *u1, *u2;
5519 WebKitWebFrame *frame;
5520 GList *items;
5521 WebKitWebHistoryItem *item;
5523 frame = webkit_web_view_get_main_frame(t->wv);
5524 uri = webkit_web_frame_get_uri(frame);
5526 if (uri && !strlen(uri))
5527 return (1);
5529 u1 = g_malloc0(sizeof(struct undo));
5530 u1->uri = g_strdup(uri);
5532 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5534 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
5535 n = webkit_web_back_forward_list_get_back_length(t->bfl);
5536 u1->back = n;
5538 /* forward history */
5539 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
5541 while (items) {
5542 item = items->data;
5543 u1->history = g_list_prepend(u1->history,
5544 webkit_web_history_item_copy(item));
5545 items = g_list_next(items);
5548 /* current item */
5549 if (m) {
5550 item = webkit_web_back_forward_list_get_current_item(t->bfl);
5551 u1->history = g_list_prepend(u1->history,
5552 webkit_web_history_item_copy(item));
5555 /* back history */
5556 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
5558 while (items) {
5559 item = items->data;
5560 u1->history = g_list_prepend(u1->history,
5561 webkit_web_history_item_copy(item));
5562 items = g_list_next(items);
5565 TAILQ_INSERT_HEAD(&undos, u1, entry);
5567 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5568 u2 = TAILQ_LAST(&undos, undo_tailq);
5569 TAILQ_REMOVE(&undos, u2, entry);
5570 g_free(u2->uri);
5571 g_list_free(u2->history);
5572 g_free(u2);
5573 } else
5574 undo_count++;
5576 return (0);
5579 void
5580 delete_tab(struct tab *t)
5582 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5584 if (t == NULL)
5585 return;
5587 TAILQ_REMOVE(&tabs, t, entry);
5589 /* halt all webkit activity */
5590 abort_and_free_favicon(t);
5591 webkit_web_view_stop_loading(t->wv);
5592 undo_close_tab_save(t);
5594 gtk_widget_destroy(t->vbox);
5595 g_free(t->user_agent);
5596 g_free(t);
5598 recalc_tabs();
5599 if (TAILQ_EMPTY(&tabs))
5600 create_new_tab(NULL, NULL, 1);
5603 void
5604 adjustfont_webkit(struct tab *t, int adjust)
5606 if (t == NULL)
5607 errx(1, "adjustfont_webkit");
5609 if (adjust == XT_FONT_SET)
5610 t->font_size = default_font_size;
5612 t->font_size += adjust;
5613 g_object_set((GObject *)t->settings, "default-font-size",
5614 t->font_size, (char *)NULL);
5615 g_object_get((GObject *)t->settings, "default-font-size",
5616 &t->font_size, (char *)NULL);
5619 void
5620 append_tab(struct tab *t)
5622 if (t == NULL)
5623 return;
5625 TAILQ_INSERT_TAIL(&tabs, t, entry);
5626 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5629 void
5630 create_new_tab(char *title, struct undo *u, int focus)
5632 struct tab *t, *tt;
5633 int load = 1, id, notfound;
5634 char *newuri = NULL;
5635 GtkWidget *b, *bb;
5636 WebKitWebHistoryItem *item;
5637 GList *items;
5638 GdkColor color;
5640 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5642 if (tabless && !TAILQ_EMPTY(&tabs)) {
5643 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5644 return;
5647 t = g_malloc0(sizeof *t);
5649 if (title == NULL) {
5650 title = "(untitled)";
5651 load = 0;
5652 } else {
5653 if (valid_url_type(title)) {
5654 newuri = guess_url_type(title);
5655 title = newuri;
5659 t->vbox = gtk_vbox_new(FALSE, 0);
5661 /* label + button for tab */
5662 b = gtk_hbox_new(FALSE, 0);
5663 t->tab_content = b;
5665 #if GTK_CHECK_VERSION(2, 20, 0)
5666 t->spinner = gtk_spinner_new ();
5667 #endif
5668 t->label = gtk_label_new(title);
5669 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
5670 gtk_widget_set_size_request(t->label, 100, 0);
5671 gtk_widget_set_size_request(b, 130, 0);
5673 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5674 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5675 #if GTK_CHECK_VERSION(2, 20, 0)
5676 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5677 #endif
5679 /* toolbar */
5680 t->toolbar = create_toolbar(t);
5681 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5683 /* browser */
5684 t->browser_win = create_browser(t);
5685 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5687 /* oops message for user feedback */
5688 t->oops = gtk_entry_new();
5689 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
5690 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
5691 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
5692 gdk_color_parse("red", &color);
5693 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
5694 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
5696 /* command entry */
5697 t->cmd = gtk_entry_new();
5698 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5699 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5700 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5702 /* xtp meaning is normal by default */
5703 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5705 /* and show it all */
5706 gtk_widget_show_all(b);
5707 gtk_widget_show_all(t->vbox);
5709 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5710 append_tab(t);
5711 else {
5712 notfound = 1;
5713 id = gtk_notebook_get_current_page(notebook);
5714 TAILQ_FOREACH(tt, &tabs, entry) {
5715 if (tt->tab_id == id) {
5716 notfound = 0;
5717 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5718 gtk_notebook_insert_page(notebook, t->vbox, b,
5719 id + 1);
5720 recalc_tabs();
5721 break;
5724 if (notfound)
5725 append_tab(t);
5728 #if GTK_CHECK_VERSION(2, 20, 0)
5729 /* turn spinner off if we are a new tab without uri */
5730 if (!load) {
5731 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5732 gtk_widget_hide(t->spinner);
5734 #endif
5735 /* make notebook tabs reorderable */
5736 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5738 g_object_connect((GObject*)t->cmd,
5739 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5740 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5741 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5742 "signal::activate", (GCallback)cmd_activate_cb, t,
5743 (char *)NULL);
5745 /* reuse wv_button_cb to hide oops */
5746 g_object_connect((GObject*)t->oops,
5747 "signal::button_press_event", (GCallback)wv_button_cb, t,
5748 (char *)NULL);
5750 g_object_connect((GObject*)t->wv,
5751 "signal::key-press-event", (GCallback)wv_keypress_cb, t,
5752 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5753 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5754 "signal::download-requested", (GCallback)webview_download_cb, t,
5755 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5756 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5757 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5758 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5759 "signal::event", (GCallback)webview_event_cb, t,
5760 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5761 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5762 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5763 "signal::icon-loaded", (GCallback)notify_icon_loaded_cb, t,
5764 #endif
5765 "signal::button_press_event", (GCallback)wv_button_cb, t,
5766 (char *)NULL);
5767 g_signal_connect(t->wv,
5768 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
5770 /* hijack the unused keys as if we were the browser */
5771 g_object_connect((GObject*)t->toolbar,
5772 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5773 (char *)NULL);
5775 g_signal_connect(G_OBJECT(bb), "button_press_event",
5776 G_CALLBACK(tab_close_cb), t);
5778 /* hide stuff */
5779 hide_cmd(t);
5780 hide_oops(t);
5781 if (showurl == 0)
5782 gtk_widget_hide(t->toolbar);
5784 if (focus) {
5785 gtk_notebook_set_current_page(notebook, t->tab_id);
5786 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
5787 t->tab_id);
5790 if (load)
5791 webkit_web_view_load_uri(t->wv, title);
5792 else
5793 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5795 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5796 /* restore the tab's history */
5797 if (u && u->history) {
5798 items = u->history;
5799 while (items) {
5800 item = items->data;
5801 webkit_web_back_forward_list_add_item(t->bfl, item);
5802 items = g_list_next(items);
5805 item = g_list_nth_data(u->history, u->back);
5806 if (item)
5807 webkit_web_view_go_to_back_forward_item(t->wv, item);
5809 g_list_free(items);
5810 g_list_free(u->history);
5813 if (newuri)
5814 g_free(newuri);
5817 void
5818 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
5819 gpointer *udata)
5821 struct tab *t;
5822 const gchar *uri;
5824 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
5826 TAILQ_FOREACH(t, &tabs, entry) {
5827 if (t->tab_id == pn) {
5828 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
5829 "%d\n", pn);
5831 uri = webkit_web_view_get_title(t->wv);
5832 if (uri == NULL)
5833 uri = XT_NAME;
5834 gtk_window_set_title(GTK_WINDOW(main_window), uri);
5836 hide_cmd(t);
5837 hide_oops(t);
5839 if (t->focus_wv)
5840 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5845 void
5846 menuitem_response(struct tab *t)
5848 gtk_notebook_set_current_page(notebook, t->tab_id);
5851 gboolean
5852 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
5854 GtkWidget *menu, *menu_items;
5855 GdkEventButton *bevent;
5856 WebKitWebFrame *frame;
5857 const gchar *uri;
5858 struct tab *ti;
5860 if (event->type == GDK_BUTTON_PRESS) {
5861 bevent = (GdkEventButton *) event;
5862 menu = gtk_menu_new();
5864 TAILQ_FOREACH(ti, &tabs, entry) {
5865 frame = webkit_web_view_get_main_frame(ti->wv);
5866 uri = webkit_web_frame_get_uri(frame);
5867 /* XXX make sure there is something to print */
5868 /* XXX add gui pages in here to look purdy */
5869 if (uri == NULL)
5870 uri = "(untitled)";
5871 if (strlen(uri) == 0)
5872 uri = "(untitled)";
5873 menu_items = gtk_menu_item_new_with_label(uri);
5874 gtk_menu_append(GTK_MENU (menu), menu_items);
5875 gtk_widget_show(menu_items);
5877 gtk_signal_connect_object(GTK_OBJECT(menu_items),
5878 "activate", GTK_SIGNAL_FUNC(menuitem_response),
5879 (gpointer)ti);
5882 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
5883 bevent->button, bevent->time);
5885 /* unref object so it'll free itself when popped down */
5886 g_object_ref_sink(menu);
5887 g_object_unref(menu);
5889 return (TRUE /* eat event */);
5892 return (FALSE /* propagate */);
5896 icon_size_map(int icon_size)
5898 if (icon_size <= GTK_ICON_SIZE_INVALID ||
5899 icon_size > GTK_ICON_SIZE_DIALOG)
5900 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
5902 return (icon_size);
5905 GtkWidget *
5906 create_button(char *name, char *stockid, int size)
5908 GtkWidget *button, *image;
5909 gchar *rcstring;
5910 int gtk_icon_size;
5911 rcstring = g_strdup_printf(
5912 "style \"%s-style\"\n"
5913 "{\n"
5914 " GtkWidget::focus-padding = 0\n"
5915 " GtkWidget::focus-line-width = 0\n"
5916 " xthickness = 0\n"
5917 " ythickness = 0\n"
5918 "}\n"
5919 "widget \"*.%s\" style \"%s-style\"",name,name,name);
5920 gtk_rc_parse_string(rcstring);
5921 g_free(rcstring);
5922 button = gtk_button_new();
5923 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5924 gtk_icon_size = icon_size_map(size?size:icon_size);
5926 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
5927 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5928 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
5929 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
5930 gtk_widget_set_name(button, name);
5931 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
5932 gtk_widget_set_tooltip_text(button, name);
5934 return button;
5937 void
5938 button_set_stockid(GtkWidget *button, char *stockid)
5940 GtkWidget *image;
5941 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
5942 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5943 gtk_button_set_image(GTK_BUTTON(button), image);
5946 void
5947 create_canvas(void)
5949 GtkWidget *vbox;
5950 GList *l = NULL;
5951 GdkPixbuf *pb;
5952 char file[PATH_MAX];
5953 int i;
5955 vbox = gtk_vbox_new(FALSE, 0);
5956 gtk_box_set_spacing(GTK_BOX(vbox), 0);
5957 notebook = GTK_NOTEBOOK(gtk_notebook_new());
5958 if (showtabs == 0)
5959 gtk_notebook_set_show_tabs(notebook, FALSE);
5960 else {
5961 gtk_notebook_set_tab_hborder(notebook, 0);
5962 gtk_notebook_set_tab_vborder(notebook, 0);
5964 gtk_notebook_set_show_border(notebook, FALSE);
5965 gtk_notebook_set_scrollable(notebook, TRUE);
5966 gtk_notebook_set_homogeneous_tabs(notebook, TRUE);
5967 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
5969 abtn = gtk_button_new();
5970 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5971 gtk_widget_set_size_request(arrow, -1, -1);
5972 gtk_container_add(GTK_CONTAINER(abtn), arrow);
5973 gtk_widget_set_size_request(abtn, -1, 20);
5974 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
5976 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
5977 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
5978 gtk_widget_set_size_request(vbox, -1, -1);
5980 g_object_connect((GObject*)notebook,
5981 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
5982 (char *)NULL);
5983 g_signal_connect(G_OBJECT(abtn), "button_press_event",
5984 G_CALLBACK(arrow_cb), NULL);
5986 main_window = create_window();
5987 gtk_container_add(GTK_CONTAINER(main_window), vbox);
5988 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5990 /* icons */
5991 for (i = 0; i < LENGTH(icons); i++) {
5992 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
5993 pb = gdk_pixbuf_new_from_file(file, NULL);
5994 l = g_list_append(l, pb);
5996 gtk_window_set_default_icon_list(l);
5998 gtk_widget_show_all(abtn);
5999 gtk_widget_show_all(main_window);
6002 void
6003 set_hook(void **hook, char *name)
6005 if (hook == NULL)
6006 errx(1, "set_hook");
6008 if (*hook == NULL) {
6009 *hook = dlsym(RTLD_NEXT, name);
6010 if (*hook == NULL)
6011 errx(1, "can't hook %s", name);
6015 /* override libsoup soup_cookie_equal because it doesn't look at domain */
6016 gboolean
6017 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
6019 g_return_val_if_fail(cookie1, FALSE);
6020 g_return_val_if_fail(cookie2, FALSE);
6022 return (!strcmp (cookie1->name, cookie2->name) &&
6023 !strcmp (cookie1->value, cookie2->value) &&
6024 !strcmp (cookie1->path, cookie2->path) &&
6025 !strcmp (cookie1->domain, cookie2->domain));
6028 void
6029 transfer_cookies(void)
6031 GSList *cf;
6032 SoupCookie *sc, *pc;
6034 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6036 for (;cf; cf = cf->next) {
6037 pc = cf->data;
6038 sc = soup_cookie_copy(pc);
6039 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
6042 soup_cookies_free(cf);
6045 void
6046 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6048 GSList *cf;
6049 SoupCookie *ci;
6051 print_cookie("soup_cookie_jar_delete_cookie", c);
6053 if (cookies_enabled == 0)
6054 return;
6056 if (jar == NULL || c == NULL)
6057 return;
6059 /* find and remove from persistent jar */
6060 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6062 for (;cf; cf = cf->next) {
6063 ci = cf->data;
6064 if (soup_cookie_equal(ci, c)) {
6065 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6066 break;
6070 soup_cookies_free(cf);
6072 /* delete from session jar */
6073 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
6076 void
6077 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
6079 struct domain *d;
6080 SoupCookie *c;
6081 FILE *r_cookie_f;
6083 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
6084 jar, p_cookiejar, s_cookiejar);
6086 if (cookies_enabled == 0)
6087 return;
6089 /* see if we are up and running */
6090 if (p_cookiejar == NULL) {
6091 _soup_cookie_jar_add_cookie(jar, cookie);
6092 return;
6094 /* disallow p_cookiejar adds, shouldn't happen */
6095 if (jar == p_cookiejar)
6096 return;
6098 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
6099 blocked_cookies++;
6100 DNPRINTF(XT_D_COOKIE,
6101 "soup_cookie_jar_add_cookie: reject %s\n",
6102 cookie->domain);
6103 if (save_rejected_cookies) {
6104 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
6105 err(1, "reject cookie file");
6106 fseek(r_cookie_f, 0, SEEK_END);
6107 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6108 cookie->http_only ? "#HttpOnly_" : "",
6109 cookie->domain,
6110 *cookie->domain == '.' ? "TRUE" : "FALSE",
6111 cookie->path,
6112 cookie->secure ? "TRUE" : "FALSE",
6113 cookie->expires ?
6114 (gulong)soup_date_to_time_t(cookie->expires) :
6116 cookie->name,
6117 cookie->value);
6118 fflush(r_cookie_f);
6119 fclose(r_cookie_f);
6121 if (!allow_volatile_cookies)
6122 return;
6125 if (cookie->expires == NULL && session_timeout) {
6126 soup_cookie_set_expires(cookie,
6127 soup_date_new_from_now(session_timeout));
6128 print_cookie("modified add cookie", cookie);
6131 /* see if we are white listed for persistence */
6132 if (d && d->handy) {
6133 /* add to persistent jar */
6134 c = soup_cookie_copy(cookie);
6135 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
6136 _soup_cookie_jar_add_cookie(p_cookiejar, c);
6139 /* add to session jar */
6140 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
6141 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
6144 void
6145 setup_cookies(void)
6147 char file[PATH_MAX];
6149 set_hook((void *)&_soup_cookie_jar_add_cookie,
6150 "soup_cookie_jar_add_cookie");
6151 set_hook((void *)&_soup_cookie_jar_delete_cookie,
6152 "soup_cookie_jar_delete_cookie");
6154 if (cookies_enabled == 0)
6155 return;
6158 * the following code is intricate due to overriding several libsoup
6159 * functions.
6160 * do not alter order of these operations.
6163 /* rejected cookies */
6164 if (save_rejected_cookies)
6165 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
6167 /* persistent cookies */
6168 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6169 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
6171 /* session cookies */
6172 s_cookiejar = soup_cookie_jar_new();
6173 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
6174 cookie_policy, (void *)NULL);
6175 transfer_cookies();
6177 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
6180 void
6181 setup_proxy(char *uri)
6183 if (proxy_uri) {
6184 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
6185 soup_uri_free(proxy_uri);
6186 proxy_uri = NULL;
6188 if (http_proxy) {
6189 if (http_proxy != uri) {
6190 g_free(http_proxy);
6191 http_proxy = NULL;
6195 if (uri) {
6196 http_proxy = g_strdup(uri);
6197 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
6198 proxy_uri = soup_uri_new(http_proxy);
6199 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
6204 send_url_to_socket(char *url)
6206 int s, len, rv = -1;
6207 struct sockaddr_un sa;
6209 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6210 warnx("send_url_to_socket: socket");
6211 return (-1);
6214 sa.sun_family = AF_UNIX;
6215 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6216 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6217 len = SUN_LEN(&sa);
6219 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6220 warnx("send_url_to_socket: connect");
6221 goto done;
6224 if (send(s, url, strlen(url) + 1, 0) == -1) {
6225 warnx("send_url_to_socket: send");
6226 goto done;
6228 done:
6229 close(s);
6230 return (rv);
6233 void
6234 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
6236 int s, n;
6237 char str[XT_MAX_URL_LENGTH];
6238 socklen_t t = sizeof(struct sockaddr_un);
6239 struct sockaddr_un sa;
6240 struct passwd *p;
6241 uid_t uid;
6242 gid_t gid;
6244 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
6245 warn("socket_watcher: accept");
6246 return;
6249 if (getpeereid(s, &uid, &gid) == -1) {
6250 warn("socket_watcher: getpeereid");
6251 return;
6253 if (uid != getuid() || gid != getgid()) {
6254 warnx("socket_watcher: unauthorized user");
6255 return;
6258 p = getpwuid(uid);
6259 if (p == NULL) {
6260 warnx("socket_watcher: not a valid user");
6261 return;
6264 n = recv(s, str, sizeof(str), 0);
6265 if (n <= 0)
6266 return;
6268 create_new_tab(str, NULL, 1);
6272 is_running(void)
6274 int s, len, rv = 1;
6275 struct sockaddr_un sa;
6277 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6278 warn("is_running: socket");
6279 return (-1);
6282 sa.sun_family = AF_UNIX;
6283 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6284 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6285 len = SUN_LEN(&sa);
6287 /* connect to see if there is a listener */
6288 if (connect(s, (struct sockaddr *)&sa, len) == -1)
6289 rv = 0; /* not running */
6290 else
6291 rv = 1; /* already running */
6293 close(s);
6295 return (rv);
6299 build_socket(void)
6301 int s, len;
6302 struct sockaddr_un sa;
6304 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6305 warn("build_socket: socket");
6306 return (-1);
6309 sa.sun_family = AF_UNIX;
6310 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6311 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6312 len = SUN_LEN(&sa);
6314 /* connect to see if there is a listener */
6315 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6316 /* no listener so we will */
6317 unlink(sa.sun_path);
6319 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
6320 warn("build_socket: bind");
6321 goto done;
6324 if (listen(s, 1) == -1) {
6325 warn("build_socket: listen");
6326 goto done;
6329 return (s);
6332 done:
6333 close(s);
6334 return (-1);
6337 void
6338 usage(void)
6340 fprintf(stderr,
6341 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
6342 exit(0);
6346 main(int argc, char *argv[])
6348 struct stat sb;
6349 int c, s, optn = 0, focus = 1;
6350 char conf[PATH_MAX] = { '\0' };
6351 char file[PATH_MAX];
6352 char *env_proxy = NULL;
6353 FILE *f = NULL;
6354 struct karg a;
6356 start_argv = argv;
6358 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
6360 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
6361 switch (c) {
6362 case 'S':
6363 showurl = 0;
6364 break;
6365 case 'T':
6366 showtabs = 0;
6367 break;
6368 case 'V':
6369 errx(0 , "Version: %s", version);
6370 break;
6371 case 'f':
6372 strlcpy(conf, optarg, sizeof(conf));
6373 break;
6374 case 's':
6375 strlcpy(named_session, optarg, sizeof(named_session));
6376 break;
6377 case 't':
6378 tabless = 1;
6379 break;
6380 case 'n':
6381 optn = 1;
6382 break;
6383 default:
6384 usage();
6385 /* NOTREACHED */
6388 argc -= optind;
6389 argv += optind;
6391 TAILQ_INIT(&tabs);
6392 RB_INIT(&hl);
6393 RB_INIT(&js_wl);
6394 RB_INIT(&downloads);
6395 TAILQ_INIT(&mtl);
6396 TAILQ_INIT(&aliases);
6397 TAILQ_INIT(&undos);
6399 gnutls_global_init();
6401 /* generate session keys for xtp pages */
6402 generate_xtp_session_key(&dl_session_key);
6403 generate_xtp_session_key(&hl_session_key);
6404 generate_xtp_session_key(&cl_session_key);
6405 generate_xtp_session_key(&fl_session_key);
6407 /* prepare gtk */
6408 gtk_init(&argc, &argv);
6409 if (!g_thread_supported())
6410 g_thread_init(NULL);
6412 pwd = getpwuid(getuid());
6413 if (pwd == NULL)
6414 errx(1, "invalid user %d", getuid());
6416 /* set download dir */
6417 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
6419 /* set default string settings */
6420 home = g_strdup("http://www.peereboom.us");
6421 resource_dir = g_strdup("/usr/local/share/xxxterm/");
6422 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
6424 /* read config file */
6425 if (strlen(conf) == 0)
6426 snprintf(conf, sizeof conf, "%s/.%s",
6427 pwd->pw_dir, XT_CONF_FILE);
6428 config_parse(conf, 0);
6430 /* working directory */
6431 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
6432 if (stat(work_dir, &sb)) {
6433 if (mkdir(work_dir, S_IRWXU) == -1)
6434 err(1, "mkdir work_dir");
6435 if (stat(work_dir, &sb))
6436 err(1, "stat work_dir");
6438 if (S_ISDIR(sb.st_mode) == 0)
6439 errx(1, "%s not a dir", work_dir);
6440 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6441 warnx("fixing invalid permissions on %s", work_dir);
6442 if (chmod(work_dir, S_IRWXU) == -1)
6443 err(1, "chmod");
6446 /* icon cache dir */
6447 snprintf(cache_dir, sizeof cache_dir, "%s/%s/%s",
6448 pwd->pw_dir, XT_DIR, XT_CACHE_DIR);
6449 if (stat(cache_dir, &sb)) {
6450 if (mkdir(cache_dir, S_IRWXU) == -1)
6451 err(1, "mkdir cache_dir");
6452 if (stat(cache_dir, &sb))
6453 err(1, "stat cache_dir");
6455 if (S_ISDIR(sb.st_mode) == 0)
6456 errx(1, "%s not a dir", cache_dir);
6457 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6458 warnx("fixing invalid permissions on %s", cache_dir);
6459 if (chmod(cache_dir, S_IRWXU) == -1)
6460 err(1, "chmod");
6463 /* certs dir */
6464 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
6465 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
6466 if (stat(certs_dir, &sb)) {
6467 if (mkdir(certs_dir, S_IRWXU) == -1)
6468 err(1, "mkdir certs_dir");
6469 if (stat(certs_dir, &sb))
6470 err(1, "stat certs_dir");
6472 if (S_ISDIR(sb.st_mode) == 0)
6473 errx(1, "%s not a dir", certs_dir);
6474 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6475 warnx("fixing invalid permissions on %s", certs_dir);
6476 if (chmod(certs_dir, S_IRWXU) == -1)
6477 err(1, "chmod");
6480 /* sessions dir */
6481 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s/%s",
6482 pwd->pw_dir, XT_DIR, XT_SESSIONS_DIR);
6483 if (stat(sessions_dir, &sb)) {
6484 if (mkdir(sessions_dir, S_IRWXU) == -1)
6485 err(1, "mkdir sessions_dir");
6486 if (stat(sessions_dir, &sb))
6487 err(1, "stat sessions_dir");
6489 if (S_ISDIR(sb.st_mode) == 0)
6490 errx(1, "%s not a dir", sessions_dir);
6491 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6492 warnx("fixing invalid permissions on %s", sessions_dir);
6493 if (chmod(sessions_dir, S_IRWXU) == -1)
6494 err(1, "chmod");
6496 /* runtime settings that can override config file */
6497 if (runtime_settings[0] != '\0')
6498 config_parse(runtime_settings, 1);
6500 /* download dir */
6501 if (!strcmp(download_dir, pwd->pw_dir))
6502 strlcat(download_dir, "/downloads", sizeof download_dir);
6503 if (stat(download_dir, &sb)) {
6504 if (mkdir(download_dir, S_IRWXU) == -1)
6505 err(1, "mkdir download_dir");
6506 if (stat(download_dir, &sb))
6507 err(1, "stat download_dir");
6509 if (S_ISDIR(sb.st_mode) == 0)
6510 errx(1, "%s not a dir", download_dir);
6511 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6512 warnx("fixing invalid permissions on %s", download_dir);
6513 if (chmod(download_dir, S_IRWXU) == -1)
6514 err(1, "chmod");
6517 /* favorites file */
6518 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6519 if (stat(file, &sb)) {
6520 warnx("favorites file doesn't exist, creating it");
6521 if ((f = fopen(file, "w")) == NULL)
6522 err(1, "favorites");
6523 fclose(f);
6526 /* cookies */
6527 session = webkit_get_default_session();
6528 setup_cookies();
6530 /* certs */
6531 if (ssl_ca_file) {
6532 if (stat(ssl_ca_file, &sb)) {
6533 warn("no CA file: %s", ssl_ca_file);
6534 g_free(ssl_ca_file);
6535 ssl_ca_file = NULL;
6536 } else
6537 g_object_set(session,
6538 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6539 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6540 (void *)NULL);
6543 /* proxy */
6544 env_proxy = getenv("http_proxy");
6545 if (env_proxy)
6546 setup_proxy(env_proxy);
6547 else
6548 setup_proxy(http_proxy);
6550 /* see if there is already an xxxterm running */
6551 if (single_instance && is_running()) {
6552 optn = 1;
6553 warnx("already running");
6556 if (optn) {
6557 while (argc) {
6558 send_url_to_socket(argv[0]);
6560 argc--;
6561 argv++;
6563 exit(0);
6566 /* go graphical */
6567 create_canvas();
6569 if (save_global_history)
6570 restore_global_history();
6572 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
6573 restore_saved_tabs();
6574 else {
6575 a.s = named_session;
6576 open_tabs(NULL, &a);
6579 while (argc) {
6580 create_new_tab(argv[0], NULL, focus);
6581 focus = 0;
6583 argc--;
6584 argv++;
6587 if (TAILQ_EMPTY(&tabs))
6588 create_new_tab(home, NULL, 1);
6590 if (enable_socket)
6591 if ((s = build_socket()) != -1)
6592 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6594 gtk_main();
6596 gnutls_global_deinit();
6598 return (0);