more checking is always a safe bet, ok marco@
[xxxterm.git] / xxxterm.c
blob3d8d9a357a913bc71c099610991528f97ce3cbe3
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 GdkPixbuf *icon_pixbuf;
165 guint tab_id;
166 char *icon_uri;
167 WebKitWebView *wv;
169 WebKitWebHistoryItem *item;
170 WebKitWebBackForwardList *bfl;
172 /* adjustments for browser */
173 GtkScrollbar *sb_h;
174 GtkScrollbar *sb_v;
175 GtkAdjustment *adjust_h;
176 GtkAdjustment *adjust_v;
178 /* flags */
179 int focus_wv;
180 int ctrl_click;
181 gchar *hover;
182 int xtp_meaning; /* identifies dls/favorites */
184 /* hints */
185 int hints_on;
186 int hint_mode;
187 #define XT_HINT_NONE (0)
188 #define XT_HINT_NUMERICAL (1)
189 #define XT_HINT_ALPHANUM (2)
190 char hint_buf[128];
191 char hint_num[128];
193 /* search */
194 char *search_text;
195 int search_forward;
197 /* settings */
198 WebKitWebSettings *settings;
199 int font_size;
200 gchar *user_agent;
202 TAILQ_HEAD(tab_list, tab);
204 struct history {
205 RB_ENTRY(history) entry;
206 const gchar *uri;
207 const gchar *title;
209 RB_HEAD(history_list, history);
211 struct download {
212 RB_ENTRY(download) entry;
213 int id;
214 WebKitDownload *download;
215 struct tab *tab;
217 RB_HEAD(download_list, download);
219 struct domain {
220 RB_ENTRY(domain) entry;
221 gchar *d;
222 int handy; /* app use */
224 RB_HEAD(domain_list, domain);
226 struct undo {
227 TAILQ_ENTRY(undo) entry;
228 gchar *uri;
229 GList *history;
230 int back; /* Keeps track of how many back
231 * history items there are. */
233 TAILQ_HEAD(undo_tailq, undo);
235 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
236 int next_download_id = 1;
238 struct karg {
239 int i;
240 char *s;
243 /* defines */
244 #define XT_NAME ("XXXTerm")
245 #define XT_DIR (".xxxterm")
246 #define XT_CACHE_DIR ("cache")
247 #define XT_CERT_DIR ("certs/")
248 #define XT_SESSIONS_DIR ("sessions/")
249 #define XT_CONF_FILE ("xxxterm.conf")
250 #define XT_FAVS_FILE ("favorites")
251 #define XT_SAVED_TABS_FILE ("main_session")
252 #define XT_RESTART_TABS_FILE ("restart_tabs")
253 #define XT_SOCKET_FILE ("socket")
254 #define XT_HISTORY_FILE ("history")
255 #define XT_CB_HANDLED (TRUE)
256 #define XT_CB_PASSTHROUGH (FALSE)
257 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
258 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
259 #define XT_DLMAN_REFRESH "10"
260 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
261 "td {overflow: hidden;}\n" \
262 "th {background-color: #cccccc}" \
263 "table {width: 90%%; border: 1px black" \
264 " solid; table-layout: fixed}\n</style>\n\n"
265 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
266 #define XT_MAX_UNDO_CLOSE_TAB (32)
268 /* file sizes */
269 #define SZ_KB ((uint64_t) 1024)
270 #define SZ_MB (SZ_KB * SZ_KB)
271 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
272 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
275 * xxxterm "protocol" (xtp)
276 * We use this for managing stuff like downloads and favorites. They
277 * make magical HTML pages in memory which have xxxt:// links in order
278 * to communicate with xxxterm's internals. These links take the format:
279 * xxxt://class/session_key/action/arg
281 * Don't begin xtp class/actions as 0. atoi returns that on error.
283 * Typically we have not put addition of items in this framework, as
284 * adding items is either done via an ex-command or via a keybinding instead.
287 #define XT_XTP_STR "xxxt://"
289 /* XTP classes (xxxt://<class>) */
290 #define XT_XTP_DL 1 /* downloads */
291 #define XT_XTP_HL 2 /* history */
292 #define XT_XTP_CL 3 /* cookies */
293 #define XT_XTP_FL 4 /* favorites */
295 /* XTP download actions */
296 #define XT_XTP_DL_LIST 1
297 #define XT_XTP_DL_CANCEL 2
298 #define XT_XTP_DL_REMOVE 3
300 /* XTP history actions */
301 #define XT_XTP_HL_LIST 1
302 #define XT_XTP_HL_REMOVE 2
304 /* XTP cookie actions */
305 #define XT_XTP_CL_LIST 1
306 #define XT_XTP_CL_REMOVE 2
308 /* XTP cookie actions */
309 #define XT_XTP_FL_LIST 1
310 #define XT_XTP_FL_REMOVE 2
312 /* xtp tab meanings - identifies which tabs have xtp pages in */
313 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
314 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
315 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
316 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
317 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
319 /* actions */
320 #define XT_MOVE_INVALID (0)
321 #define XT_MOVE_DOWN (1)
322 #define XT_MOVE_UP (2)
323 #define XT_MOVE_BOTTOM (3)
324 #define XT_MOVE_TOP (4)
325 #define XT_MOVE_PAGEDOWN (5)
326 #define XT_MOVE_PAGEUP (6)
327 #define XT_MOVE_HALFDOWN (7)
328 #define XT_MOVE_HALFUP (8)
329 #define XT_MOVE_LEFT (9)
330 #define XT_MOVE_FARLEFT (10)
331 #define XT_MOVE_RIGHT (11)
332 #define XT_MOVE_FARRIGHT (12)
334 #define XT_TAB_LAST (-4)
335 #define XT_TAB_FIRST (-3)
336 #define XT_TAB_PREV (-2)
337 #define XT_TAB_NEXT (-1)
338 #define XT_TAB_INVALID (0)
339 #define XT_TAB_NEW (1)
340 #define XT_TAB_DELETE (2)
341 #define XT_TAB_DELQUIT (3)
342 #define XT_TAB_OPEN (4)
343 #define XT_TAB_UNDO_CLOSE (5)
345 #define XT_NAV_INVALID (0)
346 #define XT_NAV_BACK (1)
347 #define XT_NAV_FORWARD (2)
348 #define XT_NAV_RELOAD (3)
349 #define XT_NAV_RELOAD_CACHE (4)
351 #define XT_FOCUS_INVALID (0)
352 #define XT_FOCUS_URI (1)
353 #define XT_FOCUS_SEARCH (2)
355 #define XT_SEARCH_INVALID (0)
356 #define XT_SEARCH_NEXT (1)
357 #define XT_SEARCH_PREV (2)
359 #define XT_PASTE_CURRENT_TAB (0)
360 #define XT_PASTE_NEW_TAB (1)
362 #define XT_FONT_SET (0)
364 #define XT_WL_TOGGLE (1<<0)
365 #define XT_WL_ENABLE (1<<1)
366 #define XT_WL_DISABLE (1<<2)
367 #define XT_WL_FQDN (1<<3) /* default */
368 #define XT_WL_TOPLEVEL (1<<4)
370 #define XT_CMD_OPEN (0)
371 #define XT_CMD_OPEN_CURRENT (1)
372 #define XT_CMD_TABNEW (2)
373 #define XT_CMD_TABNEW_CURRENT (3)
375 /* mime types */
376 struct mime_type {
377 char *mt_type;
378 char *mt_action;
379 int mt_default;
380 TAILQ_ENTRY(mime_type) entry;
382 TAILQ_HEAD(mime_type_list, mime_type);
384 /* uri aliases */
385 struct alias {
386 char *a_name;
387 char *a_uri;
388 TAILQ_ENTRY(alias) entry;
390 TAILQ_HEAD(alias_list, alias);
392 /* settings that require restart */
393 int showtabs = 1; /* show tabs on notebook */
394 int showurl = 1; /* show url toolbar on notebook */
395 int tabless = 0; /* allow only 1 tab */
396 int enable_socket = 0;
397 int single_instance = 0; /* only allow one xxxterm to run */
398 int fancy_bar = 1; /* fancy toolbar */
400 /* runtime settings */
401 int ctrl_click_focus = 0; /* ctrl click gets focus */
402 int cookies_enabled = 1; /* enable cookies */
403 int read_only_cookies = 0; /* enable to not write cookies */
404 int enable_scripts = 0;
405 int enable_plugins = 0;
406 int default_font_size = 12;
407 int window_height = 768;
408 int window_width = 1024;
409 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
410 unsigned refresh_interval = 10; /* download refresh interval */
411 int enable_cookie_whitelist = 1;
412 int enable_js_whitelist = 1;
413 time_t session_timeout = 3600; /* cookie session timeout */
414 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
415 char *ssl_ca_file = NULL;
416 char *resource_dir = NULL;
417 gboolean ssl_strict_certs = FALSE;
418 int append_next = 1; /* append tab after current tab */
419 char *home = NULL;
420 char *search_string = NULL;
421 char *http_proxy = NULL;
422 char download_dir[PATH_MAX];
423 char runtime_settings[PATH_MAX]; /* override of settings */
424 int allow_volatile_cookies = 0;
425 int save_global_history = 0; /* save global history to disk */
426 char *user_agent = NULL;
427 int save_rejected_cookies = 0;
429 struct settings;
430 int set_download_dir(struct settings *, char *);
431 int set_runtime_dir(struct settings *, char *);
432 int set_cookie_policy(struct settings *, char *);
433 int add_alias(struct settings *, char *);
434 int add_mime_type(struct settings *, char *);
435 int add_cookie_wl(struct settings *, char *);
436 int add_js_wl(struct settings *, char *);
437 void button_set_stockid(GtkWidget *, char *);
438 GtkWidget * create_button(char *, char *, int);
440 char *get_cookie_policy(struct settings *);
442 char *get_download_dir(struct settings *);
443 char *get_runtime_dir(struct settings *);
445 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
446 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
447 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
448 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
450 struct special {
451 int (*set)(struct settings *, char *);
452 char *(*get)(struct settings *);
453 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
456 struct special s_cookie = {
457 set_cookie_policy,
458 get_cookie_policy,
459 NULL
462 struct special s_alias = {
463 add_alias,
464 NULL,
465 walk_alias
468 struct special s_mime = {
469 add_mime_type,
470 NULL,
471 walk_mime_type
474 struct special s_js = {
475 add_js_wl,
476 NULL,
477 walk_js_wl
480 struct special s_cookie_wl = {
481 add_cookie_wl,
482 NULL,
483 walk_cookie_wl
486 struct special s_download_dir = {
487 set_download_dir,
488 get_download_dir,
489 NULL
492 struct settings {
493 char *name;
494 int type;
495 #define XT_S_INVALID (0)
496 #define XT_S_INT (1)
497 #define XT_S_STR (2)
498 uint32_t flags;
499 #define XT_SF_RESTART (1<<0)
500 #define XT_SF_RUNTIME (1<<1)
501 int *ival;
502 char **sval;
503 struct special *s;
504 } rs[] = {
505 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
506 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
507 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
508 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
509 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
510 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
511 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
512 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
513 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
514 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
515 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
516 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
517 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
518 { "home", XT_S_STR, 0 , NULL, &home, NULL },
519 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
520 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
521 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
522 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
523 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
524 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
525 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
526 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
527 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART , &save_rejected_cookies, NULL, NULL },
528 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
529 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
530 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
531 { "user_agent", XT_S_STR, 0 , NULL, &user_agent, NULL },
532 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
533 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
535 /* runtime settings */
536 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
537 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
538 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
539 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
542 /* globals */
543 extern char *__progname;
544 char **start_argv;
545 struct passwd *pwd;
546 GtkWidget *main_window;
547 GtkNotebook *notebook;
548 GtkWidget *arrow, *abtn;
549 struct tab_list tabs;
550 struct history_list hl;
551 struct download_list downloads;
552 struct domain_list c_wl;
553 struct domain_list js_wl;
554 struct undo_tailq undos;
555 int undo_count;
556 int updating_dl_tabs = 0;
557 int updating_hl_tabs = 0;
558 int updating_cl_tabs = 0;
559 int updating_fl_tabs = 0;
560 char *global_search;
561 uint64_t blocked_cookies = 0;
562 char named_session[PATH_MAX];
563 int notify_icon_loaded_cb(WebKitWebView *, char *,
564 struct tab *);
565 void update_favicon(struct tab *);
566 int icon_size_map(int);
568 void
569 check_favicon(struct tab *t)
571 const gchar *iconuri = webkit_web_view_get_icon_uri(t->wv);
572 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'\n",
573 __func__, t->tab_id, iconuri);
574 if (iconuri && strlen(iconuri) > 0) {
575 notify_icon_loaded_cb(t->wv, (char *)iconuri, t);
576 } else {
577 free(t->icon_uri);
578 t->icon_uri = NULL;
579 update_favicon(t);
583 void
584 update_favicon(struct tab *t)
586 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'\n",
587 __func__, t->tab_id, t->icon_uri);
588 if (t->icon_uri && strlen(t->icon_uri) > 0 && t->icon_pixbuf) {
589 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
590 GTK_ENTRY_ICON_PRIMARY, t->icon_pixbuf);
591 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'"
592 " (set)\n", __func__, t->tab_id,
593 t->icon_uri ? t->icon_uri : "(NULL)");
594 } else {
595 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
596 GTK_ENTRY_ICON_PRIMARY, "text-html");
597 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri '%s'"
598 " (!set)\n", __func__, t->tab_id,
599 (t->icon_uri && strlen(t->icon_uri) > 0) ?
600 t->icon_uri : "<empty>");
603 void
604 load_webkit_string(struct tab *t, const char *str)
606 /* we set this to indicate we want to manually do navaction */
607 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
608 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
611 void
612 hide_oops(struct tab *t)
614 gtk_widget_hide(t->oops);
617 void
618 hide_cmd(struct tab *t)
620 gtk_widget_hide(t->cmd);
623 void
624 show_cmd(struct tab *t)
626 gtk_widget_hide(t->oops);
627 gtk_widget_show(t->cmd);
630 void
631 show_oops(struct tab *t, const char *fmt, ...)
633 va_list ap;
634 char *msg;
636 if (fmt == NULL)
637 return;
639 va_start(ap, fmt);
640 if (vasprintf(&msg, fmt, ap) == -1)
641 errx(1, "moo");
642 va_end(ap);
644 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
645 gtk_widget_hide(t->cmd);
646 gtk_widget_show(t->oops);
648 char *
649 get_as_string(struct settings *s)
651 char *r = NULL;
653 if (s == NULL)
654 return (NULL);
656 if (s->s) {
657 if (s->s->get)
658 r = s->s->get(s);
659 else
660 warnx("get_as_string skip %s\n", s->name);
661 } else if (s->type == XT_S_INT)
662 r = g_strdup_printf("%d", *s->ival);
663 else if (s->type == XT_S_STR)
664 r = g_strdup(*s->sval);
665 else
666 r = g_strdup_printf("INVALID TYPE");
668 return (r);
671 void
672 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
674 int i;
675 char *s;
677 for (i = 0; i < LENGTH(rs); i++) {
678 if (rs[i].s && rs[i].s->walk)
679 rs[i].s->walk(&rs[i], cb, cb_args);
680 else {
681 s = get_as_string(&rs[i]);
682 cb(&rs[i], s, cb_args);
683 g_free(s);
689 set_cookie_policy(struct settings *s, char *val)
691 if (!strcmp(val, "no3rdparty"))
692 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
693 else if (!strcmp(val, "accept"))
694 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
695 else if (!strcmp(val, "reject"))
696 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
697 else
698 return (1);
700 return (0);
703 char *
704 get_cookie_policy(struct settings *s)
706 char *r = NULL;
708 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
709 r = g_strdup("no3rdparty");
710 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
711 r = g_strdup("accept");
712 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
713 r = g_strdup("reject");
714 else
715 return (NULL);
717 return (r);
720 char *
721 get_download_dir(struct settings *s)
723 if (download_dir[0] == '\0')
724 return (0);
725 return (g_strdup(download_dir));
729 set_download_dir(struct settings *s, char *val)
731 if (val[0] == '~')
732 snprintf(download_dir, sizeof download_dir, "%s/%s",
733 pwd->pw_dir, &val[1]);
734 else
735 strlcpy(download_dir, val, sizeof download_dir);
737 return (0);
741 * Session IDs.
742 * We use these to prevent people putting xxxt:// URLs on
743 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
745 #define XT_XTP_SES_KEY_SZ 8
746 #define XT_XTP_SES_KEY_HEX_FMT \
747 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
748 char *dl_session_key; /* downloads */
749 char *hl_session_key; /* history list */
750 char *cl_session_key; /* cookie list */
751 char *fl_session_key; /* favorites list */
753 char work_dir[PATH_MAX];
754 char certs_dir[PATH_MAX];
755 char cache_dir[PATH_MAX];
756 char sessions_dir[PATH_MAX];
757 char cookie_file[PATH_MAX];
758 SoupURI *proxy_uri = NULL;
759 SoupSession *session;
760 SoupCookieJar *s_cookiejar;
761 SoupCookieJar *p_cookiejar;
762 char rc_fname[PATH_MAX];
764 struct mime_type_list mtl;
765 struct alias_list aliases;
767 /* protos */
768 void create_new_tab(char *, struct undo *, int);
769 void delete_tab(struct tab *);
770 void adjustfont_webkit(struct tab *, int);
771 int run_script(struct tab *, char *);
772 int download_rb_cmp(struct download *, struct download *);
773 int xtp_page_hl(struct tab *t, struct karg *args);
774 int xtp_page_dl(struct tab *t, struct karg *args);
775 int xtp_page_cl(struct tab *t, struct karg *args);
776 int xtp_page_fl(struct tab *t, struct karg *args);
779 history_rb_cmp(struct history *h1, struct history *h2)
781 return (strcmp(h1->uri, h2->uri));
783 RB_GENERATE(history_list, history, entry, history_rb_cmp);
786 domain_rb_cmp(struct domain *d1, struct domain *d2)
788 return (strcmp(d1->d, d2->d));
790 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
793 * generate a session key to secure xtp commands.
794 * pass in a ptr to the key in question and it will
795 * be modified in place.
797 void
798 generate_xtp_session_key(char **key)
800 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
802 /* free old key */
803 if (*key)
804 g_free(*key);
806 /* make a new one */
807 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
808 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
809 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
810 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
812 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
816 * validate a xtp session key.
817 * return 1 if OK
820 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
822 if (strcmp(trusted, untrusted) != 0) {
823 show_oops(t, "%s: xtp session key mismatch possible spoof",
824 __func__);
825 return (0);
828 return (1);
832 download_rb_cmp(struct download *e1, struct download *e2)
834 return (e1->id < e2->id ? -1 : e1->id > e2->id);
836 RB_GENERATE(download_list, download, entry, download_rb_cmp);
838 struct valid_url_types {
839 char *type;
840 } vut[] = {
841 { "http://" },
842 { "https://" },
843 { "ftp://" },
844 { "file://" },
845 { XT_XTP_STR },
849 valid_url_type(char *url)
851 int i;
853 for (i = 0; i < LENGTH(vut); i++)
854 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
855 return (0);
857 return (1);
860 void
861 print_cookie(char *msg, SoupCookie *c)
863 if (c == NULL)
864 return;
866 if (msg)
867 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
868 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
869 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
870 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
871 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
872 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
873 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
874 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
875 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
876 DNPRINTF(XT_D_COOKIE, "====================================\n");
879 void
880 walk_alias(struct settings *s,
881 void (*cb)(struct settings *, char *, void *), void *cb_args)
883 struct alias *a;
884 char *str;
886 if (s == NULL || cb == NULL)
887 errx(1, "walk_alias");
889 TAILQ_FOREACH(a, &aliases, entry) {
890 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
891 cb(s, str, cb_args);
892 g_free(str);
896 char *
897 match_alias(char *url_in)
899 struct alias *a;
900 char *arg;
901 char *url_out = NULL;
903 arg = url_in;
904 if (strsep(&arg, " \t") == NULL)
905 errx(1, "match_alias: NULL URL");
907 TAILQ_FOREACH(a, &aliases, entry) {
908 if (!strcmp(url_in, a->a_name))
909 break;
912 if (a != NULL) {
913 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
914 a->a_name);
915 if (arg != NULL)
916 url_out = g_strdup_printf(a->a_uri, arg);
917 else
918 url_out = g_strdup(a->a_uri);
921 return (url_out);
924 char *
925 guess_url_type(char *url_in)
927 struct stat sb;
928 char *url_out = NULL;
930 url_out = match_alias(url_in);
931 if (url_out != NULL)
932 return (url_out);
934 /* XXX not sure about this heuristic */
935 if (stat(url_in, &sb) == 0)
936 url_out = g_strdup_printf("file://%s", url_in);
937 else
938 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
940 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
942 return (url_out);
946 add_alias(struct settings *s, char *line)
948 char *l, *alias;
949 struct alias *a;
951 if (line == NULL)
952 errx(1, "add_alias");
953 l = line;
955 a = g_malloc(sizeof(*a));
957 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
958 errx(1, "add_alias: incomplete alias definition");
960 if (strlen(alias) == 0 || strlen(l) == 0)
961 errx(1, "add_alias: invalid alias definition");
963 a->a_name = g_strdup(alias);
964 a->a_uri = g_strdup(l);
966 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
968 TAILQ_INSERT_TAIL(&aliases, a, entry);
970 return (0);
974 add_mime_type(struct settings *s, char *line)
976 char *mime_type;
977 char *l = NULL;
978 struct mime_type *m;
980 /* XXX this could be smarter */
982 if (line == NULL)
983 errx(1, "add_mime_type");
984 l = line;
986 m = g_malloc(sizeof(*m));
988 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
989 errx(1, "add_mime_type: invalid mime_type");
991 if (mime_type[strlen(mime_type) - 1] == '*') {
992 mime_type[strlen(mime_type) - 1] = '\0';
993 m->mt_default = 1;
994 } else
995 m->mt_default = 0;
997 if (strlen(mime_type) == 0 || strlen(l) == 0)
998 errx(1, "add_mime_type: invalid mime_type");
1000 m->mt_type = g_strdup(mime_type);
1001 m->mt_action = g_strdup(l);
1003 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1004 m->mt_type, m->mt_action, m->mt_default);
1006 TAILQ_INSERT_TAIL(&mtl, m, entry);
1008 return (0);
1011 struct mime_type *
1012 find_mime_type(char *mime_type)
1014 struct mime_type *m, *def = NULL, *rv = NULL;
1016 TAILQ_FOREACH(m, &mtl, entry) {
1017 if (m->mt_default &&
1018 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1019 def = m;
1021 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1022 rv = m;
1023 break;
1027 if (rv == NULL)
1028 rv = def;
1030 return (rv);
1033 void
1034 walk_mime_type(struct settings *s,
1035 void (*cb)(struct settings *, char *, void *), void *cb_args)
1037 struct mime_type *m;
1038 char *str;
1040 if (s == NULL || cb == NULL)
1041 errx(1, "walk_mime_type");
1043 TAILQ_FOREACH(m, &mtl, entry) {
1044 str = g_strdup_printf("%s%s --> %s",
1045 m->mt_type,
1046 m->mt_default ? "*" : "",
1047 m->mt_action);
1048 cb(s, str, cb_args);
1049 g_free(str);
1053 void
1054 wl_add(char *str, struct domain_list *wl, int handy)
1056 struct domain *d;
1057 int add_dot = 0;
1059 if (str == NULL || wl == NULL)
1060 return;
1061 if (strlen(str) < 2)
1062 return;
1064 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1066 /* treat *.moo.com the same as .moo.com */
1067 if (str[0] == '*' && str[1] == '.')
1068 str = &str[1];
1069 else if (str[0] == '.')
1070 str = &str[0];
1071 else
1072 add_dot = 1;
1074 d = g_malloc(sizeof *d);
1075 if (add_dot)
1076 d->d = g_strdup_printf(".%s", str);
1077 else
1078 d->d = g_strdup(str);
1079 d->handy = handy;
1081 if (RB_INSERT(domain_list, wl, d))
1082 goto unwind;
1084 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1085 return;
1086 unwind:
1087 if (d) {
1088 if (d->d)
1089 g_free(d->d);
1090 g_free(d);
1095 add_cookie_wl(struct settings *s, char *entry)
1097 wl_add(entry, &c_wl, 1);
1098 return (0);
1101 void
1102 walk_cookie_wl(struct settings *s,
1103 void (*cb)(struct settings *, char *, void *), void *cb_args)
1105 struct domain *d;
1107 if (s == NULL || cb == NULL)
1108 errx(1, "walk_cookie_wl");
1110 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1111 cb(s, d->d, cb_args);
1114 void
1115 walk_js_wl(struct settings *s,
1116 void (*cb)(struct settings *, char *, void *), void *cb_args)
1118 struct domain *d;
1120 if (s == NULL || cb == NULL)
1121 errx(1, "walk_js_wl");
1123 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1124 cb(s, d->d, cb_args);
1128 add_js_wl(struct settings *s, char *entry)
1130 wl_add(entry, &js_wl, 1 /* persistent */);
1131 return (0);
1134 struct domain *
1135 wl_find(const gchar *search, struct domain_list *wl)
1137 int i;
1138 struct domain *d = NULL, dfind;
1139 gchar *s = NULL;
1141 if (search == NULL || wl == NULL)
1142 return (NULL);
1143 if (strlen(search) < 2)
1144 return (NULL);
1146 if (search[0] != '.')
1147 s = g_strdup_printf(".%s", search);
1148 else
1149 s = g_strdup(search);
1151 for (i = strlen(s) - 1; i >= 0; i--) {
1152 if (s[i] == '.') {
1153 dfind.d = &s[i];
1154 d = RB_FIND(domain_list, wl, &dfind);
1155 if (d)
1156 goto done;
1160 done:
1161 if (s)
1162 g_free(s);
1164 return (d);
1167 struct domain *
1168 wl_find_uri(const gchar *s, struct domain_list *wl)
1170 int i;
1171 char *ss;
1172 struct domain *r;
1174 if (s == NULL || wl == NULL)
1175 return (NULL);
1177 if (!strncmp(s, "http://", strlen("http://")))
1178 s = &s[strlen("http://")];
1179 else if (!strncmp(s, "https://", strlen("https://")))
1180 s = &s[strlen("https://")];
1182 if (strlen(s) < 2)
1183 return (NULL);
1185 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1186 /* chop string at first slash */
1187 if (s[i] == '/' || s[i] == '\0') {
1188 ss = g_strdup(s);
1189 ss[i] = '\0';
1190 r = wl_find(ss, wl);
1191 g_free(ss);
1192 return (r);
1195 return (NULL);
1198 char *
1199 get_toplevel_domain(char *domain)
1201 char *s;
1202 int found = 0;
1204 if (domain == NULL)
1205 return (NULL);
1206 if (strlen(domain) < 2)
1207 return (NULL);
1209 s = &domain[strlen(domain) - 1];
1210 while (s != domain) {
1211 if (*s == '.') {
1212 found++;
1213 if (found == 2)
1214 return (s);
1216 s--;
1219 if (found)
1220 return (domain);
1222 return (NULL);
1225 #define WS "\n= \t"
1226 void
1227 config_parse(char *filename, int runtime)
1229 FILE *config, *f;
1230 char *line, *cp, *var, *val;
1231 size_t len, lineno = 0;
1232 int i, handled, *p;
1233 char **s, file[PATH_MAX];
1234 struct stat sb;
1236 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1238 if (filename == NULL)
1239 return;
1241 if (runtime && runtime_settings[0] != '\0') {
1242 snprintf(file, sizeof file, "%s/%s",
1243 work_dir, runtime_settings);
1244 if (stat(file, &sb)) {
1245 warnx("runtime file doesn't exist, creating it");
1246 if ((f = fopen(file, "w")) == NULL)
1247 err(1, "runtime");
1248 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1249 fclose(f);
1251 } else
1252 strlcpy(file, filename, sizeof file);
1254 if ((config = fopen(file, "r")) == NULL) {
1255 warn("config_parse: cannot open %s", filename);
1256 return;
1259 for (;;) {
1260 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1261 if (feof(config) || ferror(config))
1262 break;
1264 cp = line;
1265 cp += (long)strspn(cp, WS);
1266 if (cp[0] == '\0') {
1267 /* empty line */
1268 free(line);
1269 continue;
1272 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1273 break;
1275 cp += (long)strspn(cp, WS);
1277 if ((val = strsep(&cp, "\0")) == NULL)
1278 break;
1280 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1282 /* get settings */
1283 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1284 if (strcmp(var, rs[i].name))
1285 continue;
1287 if (rs[i].s) {
1288 if (rs[i].s->set(&rs[i], val))
1289 errx(1, "invalid value for %s", var);
1290 handled = 1;
1291 break;
1292 } else {
1293 switch (rs[i].type) {
1294 case XT_S_INT:
1295 p = rs[i].ival;
1296 *p = atoi(val);
1297 handled = 1;
1298 break;
1299 case XT_S_STR:
1300 s = rs[i].sval;
1301 if (s == NULL)
1302 errx(1, "invalid sval for %s",
1303 rs[i].name);
1304 if (*s)
1305 g_free(*s);
1306 *s = g_strdup(val);
1307 handled = 1;
1308 break;
1309 case XT_S_INVALID:
1310 default:
1311 errx(1, "invalid type for %s", var);
1314 break;
1316 if (handled == 0)
1317 errx(1, "invalid conf file entry: %s=%s", var, val);
1319 free(line);
1322 fclose(config);
1325 char *
1326 js_ref_to_string(JSContextRef context, JSValueRef ref)
1328 char *s = NULL;
1329 size_t l;
1330 JSStringRef jsref;
1332 jsref = JSValueToStringCopy(context, ref, NULL);
1333 if (jsref == NULL)
1334 return (NULL);
1336 l = JSStringGetMaximumUTF8CStringSize(jsref);
1337 s = g_malloc(l);
1338 if (s)
1339 JSStringGetUTF8CString(jsref, s, l);
1340 JSStringRelease(jsref);
1342 return (s);
1345 void
1346 disable_hints(struct tab *t)
1348 bzero(t->hint_buf, sizeof t->hint_buf);
1349 bzero(t->hint_num, sizeof t->hint_num);
1350 run_script(t, "vimprobable_clear()");
1351 t->hints_on = 0;
1352 t->hint_mode = XT_HINT_NONE;
1355 void
1356 enable_hints(struct tab *t)
1358 bzero(t->hint_buf, sizeof t->hint_buf);
1359 run_script(t, "vimprobable_show_hints()");
1360 t->hints_on = 1;
1361 t->hint_mode = XT_HINT_NONE;
1364 #define XT_JS_OPEN ("open;")
1365 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1366 #define XT_JS_FIRE ("fire;")
1367 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1368 #define XT_JS_FOUND ("found;")
1369 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1372 run_script(struct tab *t, char *s)
1374 JSGlobalContextRef ctx;
1375 WebKitWebFrame *frame;
1376 JSStringRef str;
1377 JSValueRef val, exception;
1378 char *es, buf[128];
1380 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1381 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1383 frame = webkit_web_view_get_main_frame(t->wv);
1384 ctx = webkit_web_frame_get_global_context(frame);
1386 str = JSStringCreateWithUTF8CString(s);
1387 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1388 NULL, 0, &exception);
1389 JSStringRelease(str);
1391 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1392 if (val == NULL) {
1393 es = js_ref_to_string(ctx, exception);
1394 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1395 g_free(es);
1396 return (1);
1397 } else {
1398 es = js_ref_to_string(ctx, val);
1399 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1401 /* handle return value right here */
1402 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1403 disable_hints(t);
1404 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1407 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1408 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1409 &es[XT_JS_FIRE_LEN]);
1410 run_script(t, buf);
1411 disable_hints(t);
1414 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1415 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1416 disable_hints(t);
1419 g_free(es);
1422 return (0);
1426 hint(struct tab *t, struct karg *args)
1429 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1431 if (t->hints_on == 0)
1432 enable_hints(t);
1433 else
1434 disable_hints(t);
1436 return (0);
1439 /* Doesn't work fully, due to the following bug:
1440 * https://bugs.webkit.org/show_bug.cgi?id=51747
1443 restore_global_history(void)
1445 char file[PATH_MAX];
1446 FILE *f;
1447 struct history *h;
1448 gchar *uri;
1449 gchar *title;
1451 snprintf(file, sizeof file, "%s/%s/%s",
1452 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1454 if ((f = fopen(file, "r")) == NULL) {
1455 warnx("%s: fopen", __func__);
1456 return (1);
1459 for (;;) {
1460 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1461 if (feof(f) || ferror(f))
1462 break;
1464 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1465 if (feof(f) || ferror(f)) {
1466 free(uri);
1467 warnx("%s: broken history file\n", __func__);
1468 return (1);
1471 if (uri && strlen(uri) && title && strlen(title)) {
1472 webkit_web_history_item_new_with_data(uri, title);
1473 h = g_malloc(sizeof(struct history));
1474 h->uri = g_strdup(uri);
1475 h->title = g_strdup(title);
1476 RB_INSERT(history_list, &hl, h);
1477 } else {
1478 warnx("%s: failed to restore history\n", __func__);
1479 free(uri);
1480 free(title);
1481 return (1);
1484 free(uri);
1485 free(title);
1486 uri = NULL;
1487 title = NULL;
1490 return (0);
1494 save_global_history_to_disk(struct tab *t)
1496 char file[PATH_MAX];
1497 FILE *f;
1498 struct history *h;
1500 snprintf(file, sizeof file, "%s/%s/%s",
1501 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1503 if ((f = fopen(file, "w")) == NULL) {
1504 show_oops(t, "%s: global history file: %s",
1505 __func__, strerror(errno));
1506 return (1);
1509 RB_FOREACH_REVERSE(h, history_list, &hl) {
1510 if (h->uri && h->title)
1511 fprintf(f, "%s\n%s\n", h->uri, h->title);
1514 fclose(f);
1516 return (0);
1520 quit(struct tab *t, struct karg *args)
1522 if (save_global_history)
1523 save_global_history_to_disk(t);
1525 gtk_main_quit();
1527 return (1);
1531 open_tabs(struct tab *t, struct karg *a)
1533 char file[PATH_MAX];
1534 FILE *f = NULL;
1535 char *uri = NULL;
1536 int rv = 1;
1538 if (a == NULL)
1539 goto done;
1541 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1543 if ((f = fopen(file, "r")) == NULL)
1544 goto done;
1546 for (;;) {
1547 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1548 if (feof(f) || ferror(f))
1549 break;
1551 if (uri && strlen(uri))
1552 create_new_tab(uri, NULL, 1);
1554 free(uri);
1555 uri = NULL;
1558 rv = 0;
1559 done:
1560 if (f)
1561 fclose(f);
1563 return (rv);
1567 restore_saved_tabs(void)
1569 char file[PATH_MAX];
1570 int unlink_file = 0;
1571 struct stat sb;
1572 struct karg a;
1574 snprintf(file, sizeof file, "%s/%s",
1575 sessions_dir, XT_RESTART_TABS_FILE);
1576 if (stat(file, &sb) == -1)
1577 a.s = XT_SAVED_TABS_FILE;
1578 else {
1579 unlink_file = 1;
1580 a.s = XT_RESTART_TABS_FILE;
1583 open_tabs(NULL, &a);
1585 if (unlink_file)
1586 unlink(file);
1588 return (1);
1592 save_tabs(struct tab *t, struct karg *a)
1594 char file[PATH_MAX];
1595 FILE *f;
1596 struct tab *ti;
1597 WebKitWebFrame *frame;
1598 const gchar *uri;
1600 if (a == NULL)
1601 return (1);
1602 if (a->s == NULL)
1603 return (1);
1605 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1607 if ((f = fopen(file, "w")) == NULL) {
1608 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1609 return (1);
1612 TAILQ_FOREACH(ti, &tabs, entry) {
1613 frame = webkit_web_view_get_main_frame(ti->wv);
1614 uri = webkit_web_frame_get_uri(frame);
1615 if (uri && strlen(uri) > 0)
1616 fprintf(f, "%s\n", uri);
1619 fclose(f);
1621 return (0);
1625 save_tabs_and_quit(struct tab *t, struct karg *args)
1627 struct karg a;
1629 a.s = XT_SAVED_TABS_FILE;
1630 save_tabs(t, &a);
1631 quit(t, NULL);
1633 return (1);
1637 yank_uri(struct tab *t, struct karg *args)
1639 WebKitWebFrame *frame;
1640 const gchar *uri;
1641 GtkClipboard *clipboard;
1643 frame = webkit_web_view_get_main_frame(t->wv);
1644 uri = webkit_web_frame_get_uri(frame);
1645 if (!uri)
1646 return (1);
1648 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1649 gtk_clipboard_set_text(clipboard, uri, -1);
1651 return (0);
1654 struct paste_args {
1655 struct tab *t;
1656 int i;
1659 void
1660 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1662 struct paste_args *pap;
1664 if (data == NULL)
1665 return;
1667 pap = (struct paste_args *)data;
1669 switch(pap->i) {
1670 case XT_PASTE_CURRENT_TAB:
1671 webkit_web_view_load_uri(pap->t->wv, text);
1672 break;
1673 case XT_PASTE_NEW_TAB:
1674 create_new_tab((char *)text, NULL, 1);
1675 break;
1678 g_free(pap);
1682 paste_uri(struct tab *t, struct karg *args)
1684 GtkClipboard *clipboard;
1685 struct paste_args *pap;
1687 pap = g_malloc(sizeof(struct paste_args));
1689 pap->t = t;
1690 pap->i = args->i;
1692 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1693 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1695 return (0);
1698 char *
1699 find_domain(const char *s, int add_dot)
1701 int i;
1702 char *r = NULL, *ss = NULL;
1704 if (s == NULL)
1705 return (NULL);
1707 if (!strncmp(s, "http://", strlen("http://")))
1708 s = &s[strlen("http://")];
1709 else if (!strncmp(s, "https://", strlen("https://")))
1710 s = &s[strlen("https://")];
1712 if (strlen(s) < 2)
1713 return (NULL);
1715 ss = g_strdup(s);
1716 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1717 /* chop string at first slash */
1718 if (ss[i] == '/' || ss[i] == '\0') {
1719 ss[i] = '\0';
1720 if (add_dot)
1721 r = g_strdup_printf(".%s", ss);
1722 else
1723 r = g_strdup(ss);
1724 break;
1726 g_free(ss);
1728 return (r);
1732 toggle_cwl(struct tab *t, struct karg *args)
1734 WebKitWebFrame *frame;
1735 struct domain *d;
1736 char *uri;
1737 char *dom = NULL, *dom_toggle = NULL;
1738 int es;
1740 if (args == NULL)
1741 return (0);
1743 frame = webkit_web_view_get_main_frame(t->wv);
1744 uri = (char *)webkit_web_frame_get_uri(frame);
1745 dom = find_domain(uri, 1);
1746 d = wl_find(dom, &c_wl);
1747 if (d == NULL)
1748 es = 0;
1749 else
1750 es = 1;
1752 if (args->i & XT_WL_TOGGLE)
1753 es = !es;
1754 else if ((args->i & XT_WL_ENABLE) && es != 1)
1755 es = 1;
1756 else if ((args->i & XT_WL_DISABLE) && es != 0)
1757 es = 0;
1759 if (args->i & XT_WL_TOPLEVEL)
1760 dom_toggle = get_toplevel_domain(dom);
1761 else
1762 dom_toggle = dom;
1764 if (es) {
1765 /* enable cookies for domain */
1766 wl_add(dom_toggle, &c_wl, 0);
1767 } else {
1768 /* disable cookies for domain */
1769 RB_REMOVE(domain_list, &c_wl, d);
1772 webkit_web_view_reload(t->wv);
1774 g_free(dom);
1775 return (0);
1779 toggle_js(struct tab *t, struct karg *args)
1781 int es;
1782 WebKitWebFrame *frame;
1783 const gchar *uri;
1784 struct domain *d;
1785 char *dom = NULL, *dom_toggle = NULL;
1787 if (args == NULL)
1788 return (0);
1790 g_object_get((GObject *)t->settings,
1791 "enable-scripts", &es, (char *)NULL);
1792 if (args->i & XT_WL_TOGGLE)
1793 es = !es;
1794 else if ((args->i & XT_WL_ENABLE) && es != 1)
1795 es = 1;
1796 else if ((args->i & XT_WL_DISABLE) && es != 0)
1797 es = 0;
1798 else
1799 return (0);
1801 frame = webkit_web_view_get_main_frame(t->wv);
1802 uri = (char *)webkit_web_frame_get_uri(frame);
1803 dom = find_domain(uri, 1);
1804 if (uri == NULL || dom == NULL) {
1805 show_oops(t, "Can't toggle domain in JavaScript white list");
1806 goto done;
1809 if (args->i & XT_WL_TOPLEVEL)
1810 dom_toggle = get_toplevel_domain(dom);
1811 else
1812 dom_toggle = dom;
1814 if (es) {
1815 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1816 wl_add(dom_toggle, &js_wl, 0 /* session */);
1817 } else {
1818 d = wl_find(dom_toggle, &js_wl);
1819 if (d)
1820 RB_REMOVE(domain_list, &js_wl, d);
1821 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1823 g_object_set((GObject *)t->settings,
1824 "enable-scripts", es, (char *)NULL);
1825 webkit_web_view_set_settings(t->wv, t->settings);
1826 webkit_web_view_reload(t->wv);
1827 done:
1828 if (dom)
1829 g_free(dom);
1830 return (0);
1833 void
1834 js_toggle_cb(GtkWidget *w, struct tab *t)
1836 struct karg a;
1838 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1839 toggle_js(t, &a);
1843 toggle_src(struct tab *t, struct karg *args)
1845 gboolean mode;
1847 if (t == NULL)
1848 return (0);
1850 mode = webkit_web_view_get_view_source_mode(t->wv);
1851 webkit_web_view_set_view_source_mode(t->wv, !mode);
1852 webkit_web_view_reload(t->wv);
1854 return (0);
1858 focus(struct tab *t, struct karg *args)
1860 if (t == NULL || args == NULL)
1861 errx(1, "focus");
1863 if (args->i == XT_FOCUS_URI)
1864 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1865 else if (args->i == XT_FOCUS_SEARCH)
1866 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1868 return (0);
1872 stats(struct tab *t, struct karg *args)
1874 char *stats, *s, line[64 * 1024];
1875 uint64_t line_count = 0;
1876 FILE *r_cookie_f;
1878 if (t == NULL)
1879 errx(1, "stats");
1881 line[0] = '\0';
1882 if (save_rejected_cookies) {
1883 if ((r_cookie_f = fopen(rc_fname, "r"))) {
1884 for (;;) {
1885 s = fgets(line, sizeof line, r_cookie_f);
1886 if (s == NULL || feof(r_cookie_f) ||
1887 ferror(r_cookie_f))
1888 break;
1889 line_count++;
1891 fclose(r_cookie_f);
1892 snprintf(line, sizeof line,
1893 "<br>Cookies blocked(*) total: %llu", line_count);
1894 } else
1895 show_oops(t, "Can't open blocked cookies file: %s",
1896 strerror(errno));
1899 stats = g_strdup_printf(XT_DOCTYPE
1900 "<html>"
1901 "<head>"
1902 "<title>Statistics</title>"
1903 "</head>"
1904 "<h1>Statistics</h1>"
1905 "<body>"
1906 "Cookies blocked(*) this session: %llu"
1907 "%s"
1908 "<p><small><b>*</b> results vary based on settings"
1909 "</body>"
1910 "</html>",
1911 blocked_cookies,
1912 line);
1914 load_webkit_string(t, stats);
1915 g_free(stats);
1917 return (0);
1921 about(struct tab *t, struct karg *args)
1923 char *about;
1925 if (t == NULL)
1926 errx(1, "about");
1928 about = g_strdup_printf(XT_DOCTYPE
1929 "<html>"
1930 "<head>"
1931 "<title>About</title>"
1932 "</head>"
1933 "<h1>About</h1>"
1934 "<body>"
1935 "<b>Version: %s</b><p>"
1936 "Authors:"
1937 "<ul>"
1938 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1939 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1940 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1941 "</ul>"
1942 "Copyrights and licenses can be found on the XXXterm "
1943 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1944 "</body>"
1945 "</html>",
1946 version
1949 load_webkit_string(t, about);
1950 g_free(about);
1952 return (0);
1956 help(struct tab *t, struct karg *args)
1958 char *help;
1960 if (t == NULL)
1961 errx(1, "help");
1963 help = XT_DOCTYPE
1964 "<html>"
1965 "<head>"
1966 "<title>XXXterm</title>"
1967 "<meta http-equiv=\"REFRESH\" content=\"0;"
1968 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
1969 "</head>"
1970 "<body>"
1971 "XXXterm man page <a href=\"http://opensource.conformal.com/"
1972 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
1973 "cgi-bin/man-cgi?xxxterm</a>"
1974 "</body>"
1975 "</html>"
1978 load_webkit_string(t, help);
1980 return (0);
1984 * update all favorite tabs apart from one. Pass NULL if
1985 * you want to update all.
1987 void
1988 update_favorite_tabs(struct tab *apart_from)
1990 struct tab *t;
1991 if (!updating_fl_tabs) {
1992 updating_fl_tabs = 1; /* stop infinite recursion */
1993 TAILQ_FOREACH(t, &tabs, entry)
1994 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1995 && (t != apart_from))
1996 xtp_page_fl(t, NULL);
1997 updating_fl_tabs = 0;
2001 /* show a list of favorites (bookmarks) */
2003 xtp_page_fl(struct tab *t, struct karg *args)
2005 char file[PATH_MAX];
2006 FILE *f;
2007 char *uri = NULL, *title = NULL;
2008 size_t len, lineno = 0;
2009 int i, failed = 0;
2010 char *header, *body, *tmp, *html = NULL;
2012 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2014 if (t == NULL)
2015 warn("%s: bad param", __func__);
2017 /* mark tab as favorite list */
2018 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2020 /* new session key */
2021 if (!updating_fl_tabs)
2022 generate_xtp_session_key(&fl_session_key);
2024 /* open favorites */
2025 snprintf(file, sizeof file, "%s/%s/%s",
2026 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2027 if ((f = fopen(file, "r")) == NULL) {
2028 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2029 return (1);
2032 /* header */
2033 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2034 "<title>Favorites</title>\n"
2035 "%s"
2036 "</head>"
2037 "<h1>Favorites</h1>\n",
2038 XT_PAGE_STYLE);
2040 /* body */
2041 body = g_strdup_printf("<div align='center'><table><tr>"
2042 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2043 "<th style='width: 15%%'>Remove</th></tr>\n");
2045 for (i = 1;;) {
2046 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2047 if (feof(f) || ferror(f))
2048 break;
2049 if (len == 0) {
2050 free(title);
2051 title = NULL;
2052 continue;
2055 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2056 if (feof(f) || ferror(f)) {
2057 errx(0, "%s: can't parse favorites\n",
2058 __func__);
2059 failed = 1;
2060 break;
2063 tmp = body;
2064 body = g_strdup_printf("%s<tr>"
2065 "<td>%d</td>"
2066 "<td><a href='%s'>%s</a></td>"
2067 "<td style='text-align: center'>"
2068 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2069 "</tr>\n",
2070 body, i, uri, title,
2071 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2073 g_free(tmp);
2075 free(uri);
2076 uri = NULL;
2077 free(title);
2078 title = NULL;
2079 i++;
2081 fclose(f);
2083 /* if none, say so */
2084 if (i == 1) {
2085 tmp = body;
2086 body = g_strdup_printf("%s<tr>"
2087 "<td colspan='3' style='text-align: center'>"
2088 "No favorites - To add one use the 'favadd' command."
2089 "</td></tr>", body);
2090 g_free(tmp);
2093 if (uri)
2094 free(uri);
2095 if (title)
2096 free(title);
2098 /* render */
2099 if (!failed) {
2100 html = g_strdup_printf("%s%s</table></div></html>",
2101 header, body);
2102 load_webkit_string(t, html);
2105 update_favorite_tabs(t);
2107 if (header)
2108 g_free(header);
2109 if (body)
2110 g_free(body);
2111 if (html)
2112 g_free(html);
2114 return (failed);
2117 char *
2118 getparams(char *cmd, char *cmp)
2120 char *rv = NULL;
2122 if (cmd && cmp) {
2123 if (!strncmp(cmd, cmp, strlen(cmp))) {
2124 rv = cmd + strlen(cmp);
2125 while (*rv == ' ')
2126 rv++;
2127 if (strlen(rv) == 0)
2128 rv = NULL;
2132 return (rv);
2135 void
2136 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2137 size_t cert_count, char *title)
2139 gnutls_datum_t cinfo;
2140 char *tmp, *header, *body, *footer;
2141 int i;
2143 header = g_strdup_printf("<title>%s</title><html><body>", title);
2144 footer = g_strdup("</body></html>");
2145 body = g_strdup("");
2147 for (i = 0; i < cert_count; i++) {
2148 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2149 &cinfo))
2150 return;
2152 tmp = body;
2153 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2154 body, i, cinfo.data);
2155 gnutls_free(cinfo.data);
2156 g_free(tmp);
2159 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2160 g_free(header);
2161 g_free(body);
2162 g_free(footer);
2163 load_webkit_string(t, tmp);
2164 g_free(tmp);
2168 ca_cmd(struct tab *t, struct karg *args)
2170 FILE *f = NULL;
2171 int rv = 1, certs = 0, certs_read;
2172 struct stat sb;
2173 gnutls_datum dt;
2174 gnutls_x509_crt_t *c = NULL;
2175 char *certs_buf = NULL, *s;
2177 /* yeah yeah stat race */
2178 if (stat(ssl_ca_file, &sb)) {
2179 show_oops(t, "no CA file: %s", ssl_ca_file);
2180 goto done;
2183 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2184 show_oops(t, "Can't open CA file: %s", strerror(errno));
2185 return (1);
2188 certs_buf = g_malloc(sb.st_size + 1);
2189 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2190 show_oops(t, "Can't read CA file: %s", strerror(errno));
2191 goto done;
2193 certs_buf[sb.st_size] = '\0';
2195 s = certs_buf;
2196 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2197 certs++;
2198 s += strlen("BEGIN CERTIFICATE");
2201 bzero(&dt, sizeof dt);
2202 dt.data = certs_buf;
2203 dt.size = sb.st_size;
2204 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2205 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2206 if (certs_read <= 0) {
2207 show_oops(t, "No cert(s) available");
2208 goto done;
2210 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2211 done:
2212 if (c)
2213 g_free(c);
2214 if (certs_buf)
2215 g_free(certs_buf);
2216 if (f)
2217 fclose(f);
2219 return (rv);
2223 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2225 SoupURI *su = NULL;
2226 struct addrinfo hints, *res = NULL, *ai;
2227 int s = -1, on;
2228 char port[8];
2230 if (uri && !g_str_has_prefix(uri, "https://"))
2231 goto done;
2233 su = soup_uri_new(uri);
2234 if (su == NULL)
2235 goto done;
2236 if (!SOUP_URI_VALID_FOR_HTTP(su))
2237 goto done;
2239 snprintf(port, sizeof port, "%d", su->port);
2240 bzero(&hints, sizeof(struct addrinfo));
2241 hints.ai_flags = AI_CANONNAME;
2242 hints.ai_family = AF_UNSPEC;
2243 hints.ai_socktype = SOCK_STREAM;
2245 if (getaddrinfo(su->host, port, &hints, &res))
2246 goto done;
2248 for (ai = res; ai; ai = ai->ai_next) {
2249 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2250 continue;
2252 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2253 if (s < 0)
2254 goto done;
2255 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2256 sizeof(on)) == -1)
2257 goto done;
2259 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2260 goto done;
2263 if (domain)
2264 strlcpy(domain, su->host, domain_sz);
2265 done:
2266 if (su)
2267 soup_uri_free(su);
2268 if (res)
2269 freeaddrinfo(res);
2271 return (s);
2275 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2277 if (gsession)
2278 gnutls_deinit(gsession);
2279 if (xcred)
2280 gnutls_certificate_free_credentials(xcred);
2282 return (0);
2286 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2287 gnutls_certificate_credentials_t *xc)
2289 gnutls_certificate_credentials_t xcred;
2290 gnutls_session_t gsession;
2291 int rv = 1;
2293 if (gs == NULL || xc == NULL)
2294 goto done;
2296 *gs = NULL;
2297 *xc = NULL;
2299 gnutls_certificate_allocate_credentials(&xcred);
2300 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2301 GNUTLS_X509_FMT_PEM);
2302 gnutls_init(&gsession, GNUTLS_CLIENT);
2303 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2304 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2305 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2306 if ((rv = gnutls_handshake(gsession)) < 0) {
2307 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2309 gnutls_error_is_fatal(rv),
2310 gnutls_strerror_name(rv));
2311 stop_tls(gsession, xcred);
2312 goto done;
2315 gnutls_credentials_type_t cred;
2316 cred = gnutls_auth_get_type(gsession);
2317 if (cred != GNUTLS_CRD_CERTIFICATE) {
2318 stop_tls(gsession, xcred);
2319 goto done;
2322 *gs = gsession;
2323 *xc = xcred;
2324 rv = 0;
2325 done:
2326 return (rv);
2330 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2331 size_t *cert_count)
2333 unsigned int len;
2334 const gnutls_datum_t *cl;
2335 gnutls_x509_crt_t *all_certs;
2336 int i, rv = 1;
2338 if (certs == NULL || cert_count == NULL)
2339 goto done;
2340 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2341 goto done;
2342 cl = gnutls_certificate_get_peers(gsession, &len);
2343 if (len == 0)
2344 goto done;
2346 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2347 for (i = 0; i < len; i++) {
2348 gnutls_x509_crt_init(&all_certs[i]);
2349 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2350 GNUTLS_X509_FMT_PEM < 0)) {
2351 g_free(all_certs);
2352 goto done;
2356 *certs = all_certs;
2357 *cert_count = len;
2358 rv = 0;
2359 done:
2360 return (rv);
2363 void
2364 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2366 int i;
2368 for (i = 0; i < cert_count; i++)
2369 gnutls_x509_crt_deinit(certs[i]);
2370 g_free(certs);
2373 void
2374 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2375 size_t cert_count, char *domain)
2377 size_t cert_buf_sz;
2378 char cert_buf[64 * 1024], file[PATH_MAX];
2379 int i;
2380 FILE *f;
2381 GdkColor color;
2383 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2384 return;
2386 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2387 if ((f = fopen(file, "w")) == NULL) {
2388 show_oops(t, "Can't create cert file %s %s",
2389 file, strerror(errno));
2390 return;
2393 for (i = 0; i < cert_count; i++) {
2394 cert_buf_sz = sizeof cert_buf;
2395 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2396 cert_buf, &cert_buf_sz)) {
2397 show_oops(t, "gnutls_x509_crt_export failed");
2398 goto done;
2400 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2401 show_oops(t, "Can't write certs: %s", strerror(errno));
2402 goto done;
2406 /* not the best spot but oh well */
2407 gdk_color_parse("lightblue", &color);
2408 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2409 done:
2410 fclose(f);
2414 load_compare_cert(struct tab *t, struct karg *args)
2416 WebKitWebFrame *frame;
2417 char *uri, domain[8182], file[PATH_MAX];
2418 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2419 int s = -1, rv = 1, i;
2420 size_t cert_count;
2421 FILE *f = NULL;
2422 size_t cert_buf_sz;
2423 gnutls_session_t gsession;
2424 gnutls_x509_crt_t *certs;
2425 gnutls_certificate_credentials_t xcred;
2427 if (t == NULL)
2428 return (1);
2430 frame = webkit_web_view_get_main_frame(t->wv);
2431 uri = (char *)webkit_web_frame_get_uri(frame);
2432 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2433 return (1);
2435 /* go ssl/tls */
2436 if (start_tls(t, s, &gsession, &xcred)) {
2437 show_oops(t, "Start TLS failed");
2438 goto done;
2441 /* get certs */
2442 if (get_connection_certs(gsession, &certs, &cert_count)) {
2443 show_oops(t, "Can't get connection certificates");
2444 goto done;
2447 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2448 if ((f = fopen(file, "r")) == NULL)
2449 goto freeit;
2451 for (i = 0; i < cert_count; i++) {
2452 cert_buf_sz = sizeof cert_buf;
2453 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2454 cert_buf, &cert_buf_sz)) {
2455 goto freeit;
2457 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2458 rv = -1; /* critical */
2459 goto freeit;
2461 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2462 rv = -1; /* critical */
2463 goto freeit;
2467 rv = 0;
2468 freeit:
2469 if (f)
2470 fclose(f);
2471 free_connection_certs(certs, cert_count);
2472 done:
2473 /* we close the socket first for speed */
2474 if (s != -1)
2475 close(s);
2476 stop_tls(gsession, xcred);
2478 return (rv);
2482 cert_cmd(struct tab *t, struct karg *args)
2484 WebKitWebFrame *frame;
2485 char *uri, *action, domain[8182];
2486 int s = -1;
2487 size_t cert_count;
2488 gnutls_session_t gsession;
2489 gnutls_x509_crt_t *certs;
2490 gnutls_certificate_credentials_t xcred;
2492 if (t == NULL)
2493 return (1);
2495 if ((action = getparams(args->s, "cert")))
2497 else
2498 action = "show";
2500 frame = webkit_web_view_get_main_frame(t->wv);
2501 uri = (char *)webkit_web_frame_get_uri(frame);
2502 if (uri && strlen(uri) == 0) {
2503 show_oops(t, "Invalid URI");
2505 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2506 show_oops(t, "Invalid certidicate URI: %s", uri);
2507 return (1);
2510 /* go ssl/tls */
2511 if (start_tls(t, s, &gsession, &xcred)) {
2512 show_oops(t, "Start TLS failed");
2513 goto done;
2516 /* get certs */
2517 if (get_connection_certs(gsession, &certs, &cert_count)) {
2518 show_oops(t, "get_connection_certs failed");
2519 goto done;
2522 if (!strcmp(action, "show"))
2523 show_certs(t, certs, cert_count, "Certificate Chain");
2524 else if (!strcmp(action, "save"))
2525 save_certs(t, certs, cert_count, domain);
2526 else
2527 show_oops(t, "Invalid command: %s", action);
2529 free_connection_certs(certs, cert_count);
2530 done:
2531 /* we close the socket first for speed */
2532 if (s != -1)
2533 close(s);
2534 stop_tls(gsession, xcred);
2536 return (0);
2540 remove_cookie(int index)
2542 int i, rv = 1;
2543 GSList *cf;
2544 SoupCookie *c;
2546 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2548 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2550 for (i = 1; cf; cf = cf->next, i++) {
2551 if (i != index)
2552 continue;
2553 c = cf->data;
2554 print_cookie("remove cookie", c);
2555 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2556 rv = 0;
2557 break;
2560 soup_cookies_free(cf);
2562 return (rv);
2566 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2568 struct domain *d;
2569 char *tmp, *header, *body, *footer;
2570 int p_js = 0, s_js = 0;
2572 /* we set this to indicate we want to manually do navaction */
2573 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2575 if (g_str_has_prefix(args, "show a") ||
2576 !strcmp(args, "show")) {
2577 /* show all */
2578 p_js = 1;
2579 s_js = 1;
2580 } else if (g_str_has_prefix(args, "show p")) {
2581 /* show persistent */
2582 p_js = 1;
2583 } else if (g_str_has_prefix(args, "show s")) {
2584 /* show session */
2585 s_js = 1;
2586 } else
2587 return (1);
2589 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2590 title, title);
2591 footer = g_strdup("</body></html>");
2592 body = g_strdup("");
2594 /* p list */
2595 if (p_js) {
2596 tmp = body;
2597 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2598 g_free(tmp);
2599 RB_FOREACH(d, domain_list, wl) {
2600 if (d->handy == 0)
2601 continue;
2602 tmp = body;
2603 body = g_strdup_printf("%s%s<br>", body, d->d);
2604 g_free(tmp);
2608 /* s list */
2609 if (s_js) {
2610 tmp = body;
2611 body = g_strdup_printf("%s<h2>Session</h2>", body);
2612 g_free(tmp);
2613 RB_FOREACH(d, domain_list, wl) {
2614 if (d->handy == 1)
2615 continue;
2616 tmp = body;
2617 body = g_strdup_printf("%s%s", body, d->d);
2618 g_free(tmp);
2622 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2623 g_free(header);
2624 g_free(body);
2625 g_free(footer);
2626 load_webkit_string(t, tmp);
2627 g_free(tmp);
2628 return (0);
2632 wl_save(struct tab *t, struct karg *args, int js)
2634 char file[PATH_MAX];
2635 FILE *f;
2636 char *line = NULL, *lt = NULL;
2637 size_t linelen;
2638 WebKitWebFrame *frame;
2639 char *dom = NULL, *uri, *dom_save = NULL;
2640 struct karg a;
2641 struct domain *d;
2642 GSList *cf;
2643 SoupCookie *ci, *c;
2644 int flags;
2646 if (t == NULL || args == NULL)
2647 return (1);
2649 if (runtime_settings[0] == '\0')
2650 return (1);
2652 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2653 if ((f = fopen(file, "r+")) == NULL)
2654 return (1);
2656 frame = webkit_web_view_get_main_frame(t->wv);
2657 uri = (char *)webkit_web_frame_get_uri(frame);
2658 dom = find_domain(uri, 1);
2659 if (uri == NULL || dom == NULL) {
2660 show_oops(t, "Can't add domain to %s white list",
2661 js ? "JavaScript" : "cookie");
2662 goto done;
2665 if (g_str_has_prefix(args->s, "save d")) {
2666 /* save domain */
2667 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2668 show_oops(t, "invalid domain: %s", dom);
2669 goto done;
2671 flags = XT_WL_TOPLEVEL;
2672 } else if (g_str_has_prefix(args->s, "save f") ||
2673 !strcmp(args->s, "save")) {
2674 /* save fqdn */
2675 dom_save = dom;
2676 flags = XT_WL_FQDN;
2677 } else {
2678 show_oops(t, "invalid command: %s", args->s);
2679 goto done;
2682 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2684 while (!feof(f)) {
2685 line = fparseln(f, &linelen, NULL, NULL, 0);
2686 if (line == NULL)
2687 continue;
2688 if (!strcmp(line, lt))
2689 goto done;
2690 free(line);
2691 line = NULL;
2694 fprintf(f, "%s\n", lt);
2696 a.i = XT_WL_ENABLE;
2697 a.i |= flags;
2698 if (js) {
2699 d = wl_find(dom_save, &js_wl);
2700 toggle_js(t, &a);
2701 } else {
2702 d = wl_find(dom_save, &c_wl);
2703 toggle_cwl(t, &a);
2705 /* find and add to persistent jar */
2706 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2707 for (;cf; cf = cf->next) {
2708 ci = cf->data;
2709 if (!strcmp(dom_save, ci->domain) ||
2710 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2711 c = soup_cookie_copy(ci);
2712 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2715 soup_cookies_free(cf);
2717 if (d)
2718 d->handy = 1;
2720 done:
2721 if (line)
2722 free(line);
2723 if (dom)
2724 g_free(dom);
2725 if (lt)
2726 g_free(lt);
2727 fclose(f);
2729 return (0);
2733 cookie_cmd(struct tab *t, struct karg *args)
2735 char *cmd;
2736 struct karg a;
2738 if ((cmd = getparams(args->s, "cookie")))
2740 else
2741 cmd = "show all";
2744 if (g_str_has_prefix(cmd, "show")) {
2745 wl_show(t, cmd, "Cookie White List", &c_wl);
2746 } else if (g_str_has_prefix(cmd, "save")) {
2747 a.s = cmd;
2748 wl_save(t, &a, 0);
2749 } else if (g_str_has_prefix(cmd, "toggle")) {
2750 a.i = XT_WL_TOGGLE;
2751 if (g_str_has_prefix(cmd, "toggle d"))
2752 a.i |= XT_WL_TOPLEVEL;
2753 else
2754 a.i |= XT_WL_FQDN;
2755 toggle_cwl(t, &a);
2756 } else if (g_str_has_prefix(cmd, "delete")) {
2757 show_oops(t, "'cookie delete' currently unimplemented");
2758 } else
2759 show_oops(t, "unknown cookie command: %s", cmd);
2761 return (0);
2765 js_cmd(struct tab *t, struct karg *args)
2767 char *cmd;
2768 struct karg a;
2770 if ((cmd = getparams(args->s, "js")))
2772 else
2773 cmd = "show all";
2775 if (g_str_has_prefix(cmd, "show")) {
2776 wl_show(t, cmd, "JavaScript White List", &js_wl);
2777 } else if (g_str_has_prefix(cmd, "save")) {
2778 a.s = cmd;
2779 wl_save(t, &a, 1);
2780 } else if (g_str_has_prefix(cmd, "toggle")) {
2781 a.i = XT_WL_TOGGLE;
2782 if (g_str_has_prefix(cmd, "toggle d"))
2783 a.i |= XT_WL_TOPLEVEL;
2784 else
2785 a.i |= XT_WL_FQDN;
2786 toggle_js(t, &a);
2787 } else if (g_str_has_prefix(cmd, "delete")) {
2788 show_oops(t, "'js delete' currently unimplemented");
2789 } else
2790 show_oops(t, "unknown js command: %s", cmd);
2792 return (0);
2796 add_favorite(struct tab *t, struct karg *args)
2798 char file[PATH_MAX];
2799 FILE *f;
2800 char *line = NULL;
2801 size_t urilen, linelen;
2802 WebKitWebFrame *frame;
2803 const gchar *uri, *title;
2805 if (t == NULL)
2806 return (1);
2808 /* don't allow adding of xtp pages to favorites */
2809 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2810 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
2811 return (1);
2814 snprintf(file, sizeof file, "%s/%s/%s",
2815 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2816 if ((f = fopen(file, "r+")) == NULL) {
2817 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2818 return (1);
2821 title = webkit_web_view_get_title(t->wv);
2822 frame = webkit_web_view_get_main_frame(t->wv);
2823 uri = webkit_web_frame_get_uri(frame);
2824 if (title == NULL)
2825 title = uri;
2827 if (title == NULL || uri == NULL) {
2828 show_oops(t, "can't add page to favorites");
2829 goto done;
2832 urilen = strlen(uri);
2834 while (!feof(f)) {
2835 line = fparseln(f, &linelen, NULL, NULL, 0);
2836 if (linelen == urilen && !strcmp(line, uri))
2837 goto done;
2838 free(line);
2839 line = NULL;
2842 fprintf(f, "\n%s\n%s", title, uri);
2843 done:
2844 if (line)
2845 free(line);
2846 fclose(f);
2848 update_favorite_tabs(NULL);
2850 return (0);
2854 navaction(struct tab *t, struct karg *args)
2856 WebKitWebHistoryItem *item;
2858 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2859 t->tab_id, args->i);
2861 if (t->item) {
2862 if (args->i == XT_NAV_BACK)
2863 item = webkit_web_back_forward_list_get_current_item(t->bfl);
2864 else
2865 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
2866 if (item == NULL)
2867 return (XT_CB_PASSTHROUGH);;
2868 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
2869 t->item = NULL;
2870 return (XT_CB_PASSTHROUGH);
2873 switch (args->i) {
2874 case XT_NAV_BACK:
2875 webkit_web_view_go_back(t->wv);
2876 break;
2877 case XT_NAV_FORWARD:
2878 webkit_web_view_go_forward(t->wv);
2879 break;
2880 case XT_NAV_RELOAD:
2881 webkit_web_view_reload(t->wv);
2882 break;
2883 case XT_NAV_RELOAD_CACHE:
2884 webkit_web_view_reload_bypass_cache(t->wv);
2885 break;
2887 return (XT_CB_PASSTHROUGH);
2891 move(struct tab *t, struct karg *args)
2893 GtkAdjustment *adjust;
2894 double pi, si, pos, ps, upper, lower, max;
2896 switch (args->i) {
2897 case XT_MOVE_DOWN:
2898 case XT_MOVE_UP:
2899 case XT_MOVE_BOTTOM:
2900 case XT_MOVE_TOP:
2901 case XT_MOVE_PAGEDOWN:
2902 case XT_MOVE_PAGEUP:
2903 case XT_MOVE_HALFDOWN:
2904 case XT_MOVE_HALFUP:
2905 adjust = t->adjust_v;
2906 break;
2907 default:
2908 adjust = t->adjust_h;
2909 break;
2912 pos = gtk_adjustment_get_value(adjust);
2913 ps = gtk_adjustment_get_page_size(adjust);
2914 upper = gtk_adjustment_get_upper(adjust);
2915 lower = gtk_adjustment_get_lower(adjust);
2916 si = gtk_adjustment_get_step_increment(adjust);
2917 pi = gtk_adjustment_get_page_increment(adjust);
2918 max = upper - ps;
2920 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2921 "max %f si %f pi %f\n",
2922 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2923 pos, ps, upper, lower, max, si, pi);
2925 switch (args->i) {
2926 case XT_MOVE_DOWN:
2927 case XT_MOVE_RIGHT:
2928 pos += si;
2929 gtk_adjustment_set_value(adjust, MIN(pos, max));
2930 break;
2931 case XT_MOVE_UP:
2932 case XT_MOVE_LEFT:
2933 pos -= si;
2934 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2935 break;
2936 case XT_MOVE_BOTTOM:
2937 case XT_MOVE_FARRIGHT:
2938 gtk_adjustment_set_value(adjust, max);
2939 break;
2940 case XT_MOVE_TOP:
2941 case XT_MOVE_FARLEFT:
2942 gtk_adjustment_set_value(adjust, lower);
2943 break;
2944 case XT_MOVE_PAGEDOWN:
2945 pos += pi;
2946 gtk_adjustment_set_value(adjust, MIN(pos, max));
2947 break;
2948 case XT_MOVE_PAGEUP:
2949 pos -= pi;
2950 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2951 break;
2952 case XT_MOVE_HALFDOWN:
2953 pos += pi / 2;
2954 gtk_adjustment_set_value(adjust, MIN(pos, max));
2955 break;
2956 case XT_MOVE_HALFUP:
2957 pos -= pi / 2;
2958 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2959 break;
2960 default:
2961 return (XT_CB_PASSTHROUGH);
2964 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
2966 return (XT_CB_HANDLED);
2970 tabaction(struct tab *t, struct karg *args)
2972 int rv = XT_CB_HANDLED;
2973 char *url = NULL, *newuri = NULL;
2974 struct undo *u;
2976 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
2978 if (t == NULL)
2979 return (XT_CB_PASSTHROUGH);
2981 switch (args->i) {
2982 case XT_TAB_NEW:
2983 if ((url = getparams(args->s, "tabnew")))
2984 create_new_tab(url, NULL, 1);
2985 else
2986 create_new_tab(NULL, NULL, 1);
2987 break;
2988 case XT_TAB_DELETE:
2989 delete_tab(t);
2990 break;
2991 case XT_TAB_DELQUIT:
2992 if (gtk_notebook_get_n_pages(notebook) > 1)
2993 delete_tab(t);
2994 else
2995 quit(t, args);
2996 break;
2997 case XT_TAB_OPEN:
2998 if ((url = getparams(args->s, "open")) ||
2999 ((url = getparams(args->s, "op"))) ||
3000 ((url = getparams(args->s, "o"))))
3002 else {
3003 rv = XT_CB_PASSTHROUGH;
3004 goto done;
3007 if (valid_url_type(url)) {
3008 newuri = guess_url_type(url);
3009 url = newuri;
3011 webkit_web_view_load_uri(t->wv, url);
3012 if (newuri)
3013 g_free(newuri);
3014 break;
3015 case XT_TAB_UNDO_CLOSE:
3016 if (undo_count == 0) {
3017 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3018 goto done;
3019 } else {
3020 undo_count--;
3021 u = TAILQ_FIRST(&undos);
3022 create_new_tab(u->uri, u, 1);
3024 TAILQ_REMOVE(&undos, u, entry);
3025 g_free(u->uri);
3026 /* u->history is freed in create_new_tab() */
3027 g_free(u);
3029 break;
3030 default:
3031 rv = XT_CB_PASSTHROUGH;
3032 goto done;
3035 done:
3036 if (args->s) {
3037 g_free(args->s);
3038 args->s = NULL;
3041 return (rv);
3045 resizetab(struct tab *t, struct karg *args)
3047 if (t == NULL || args == NULL)
3048 errx(1, "resizetab");
3050 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3051 t->tab_id, args->i);
3053 adjustfont_webkit(t, args->i);
3055 return (XT_CB_HANDLED);
3059 movetab(struct tab *t, struct karg *args)
3061 struct tab *tt;
3062 int x;
3064 if (t == NULL || args == NULL)
3065 errx(1, "movetab");
3067 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3068 t->tab_id, args->i);
3070 if (args->i == XT_TAB_INVALID)
3071 return (XT_CB_PASSTHROUGH);
3073 if (args->i < XT_TAB_INVALID) {
3074 /* next or previous tab */
3075 if (TAILQ_EMPTY(&tabs))
3076 return (XT_CB_PASSTHROUGH);
3078 switch (args->i) {
3079 case XT_TAB_NEXT:
3080 /* if at the last page, loop around to the first */
3081 if (gtk_notebook_get_current_page(notebook) ==
3082 gtk_notebook_get_n_pages(notebook) - 1) {
3083 gtk_notebook_set_current_page(notebook, 0);
3084 } else {
3085 gtk_notebook_next_page(notebook);
3087 break;
3088 case XT_TAB_PREV:
3089 /* if at the first page, loop around to the last */
3090 if (gtk_notebook_current_page(notebook) == 0) {
3091 gtk_notebook_set_current_page(notebook,
3092 gtk_notebook_get_n_pages(notebook) - 1);
3093 } else {
3094 gtk_notebook_prev_page(notebook);
3096 break;
3097 case XT_TAB_FIRST:
3098 gtk_notebook_set_current_page(notebook, 0);
3099 break;
3100 case XT_TAB_LAST:
3101 gtk_notebook_set_current_page(notebook, -1);
3102 break;
3103 default:
3104 return (XT_CB_PASSTHROUGH);
3107 return (XT_CB_HANDLED);
3110 /* jump to tab */
3111 x = args->i - 1;
3112 if (t->tab_id == x) {
3113 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3114 return (XT_CB_HANDLED);
3117 TAILQ_FOREACH(tt, &tabs, entry) {
3118 if (tt->tab_id == x) {
3119 gtk_notebook_set_current_page(notebook, x);
3120 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3121 if (tt->focus_wv)
3122 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
3126 return (XT_CB_HANDLED);
3130 command(struct tab *t, struct karg *args)
3132 WebKitWebFrame *frame;
3133 char *s = NULL, *ss = NULL;
3134 GdkColor color;
3135 const gchar *uri;
3137 if (t == NULL || args == NULL)
3138 errx(1, "command");
3140 switch (args->i) {
3141 case '/':
3142 s = "/";
3143 break;
3144 case '?':
3145 s = "?";
3146 break;
3147 case ':':
3148 s = ":";
3149 break;
3150 case XT_CMD_OPEN:
3151 s = ":open ";
3152 break;
3153 case XT_CMD_TABNEW:
3154 s = ":tabnew ";
3155 break;
3156 case XT_CMD_OPEN_CURRENT:
3157 s = ":open ";
3158 /* FALL THROUGH */
3159 case XT_CMD_TABNEW_CURRENT:
3160 if (!s) /* FALL THROUGH? */
3161 s = ":tabnew ";
3162 frame = webkit_web_view_get_main_frame(t->wv);
3163 uri = webkit_web_frame_get_uri(frame);
3164 if (uri && strlen(uri)) {
3165 ss = g_strdup_printf("%s%s", s, uri);
3166 s = ss;
3168 break;
3169 default:
3170 show_oops(t, "command: invalid opcode %d", args->i);
3171 return (XT_CB_PASSTHROUGH);
3174 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3176 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3177 gdk_color_parse("white", &color);
3178 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3179 show_cmd(t);
3180 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3181 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3183 if (ss)
3184 g_free(ss);
3186 return (XT_CB_HANDLED);
3190 * Return a new string with a download row (in html)
3191 * appended. Old string is freed.
3193 char *
3194 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3197 WebKitDownloadStatus stat;
3198 char *status_html = NULL, *cmd_html = NULL, *new_html;
3199 gdouble progress;
3200 char cur_sz[FMT_SCALED_STRSIZE];
3201 char tot_sz[FMT_SCALED_STRSIZE];
3202 char *xtp_prefix;
3204 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3206 /* All actions wil take this form:
3207 * xxxt://class/seskey
3209 xtp_prefix = g_strdup_printf("%s%d/%s/",
3210 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3212 stat = webkit_download_get_status(dl->download);
3214 switch (stat) {
3215 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3216 status_html = g_strdup_printf("Finished");
3217 cmd_html = g_strdup_printf(
3218 "<a href='%s%d/%d'>Remove</a>",
3219 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3220 break;
3221 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3222 /* gather size info */
3223 progress = 100 * webkit_download_get_progress(dl->download);
3225 fmt_scaled(
3226 webkit_download_get_current_size(dl->download), cur_sz);
3227 fmt_scaled(
3228 webkit_download_get_total_size(dl->download), tot_sz);
3230 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3231 tot_sz, progress);
3232 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3233 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3235 break;
3236 /* LLL */
3237 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3238 status_html = g_strdup_printf("Cancelled");
3239 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3240 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3241 break;
3242 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3243 status_html = g_strdup_printf("Error!");
3244 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3245 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3246 break;
3247 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3248 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3249 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3250 status_html = g_strdup_printf("Starting");
3251 break;
3252 default:
3253 show_oops(t, "%s: unknown download status", __func__);
3256 new_html = g_strdup_printf(
3257 "%s\n<tr><td>%s</td><td>%s</td>"
3258 "<td style='text-align:center'>%s</td></tr>\n",
3259 html, webkit_download_get_uri(dl->download),
3260 status_html, cmd_html);
3261 g_free(html);
3263 if (status_html)
3264 g_free(status_html);
3266 if (cmd_html)
3267 g_free(cmd_html);
3269 g_free(xtp_prefix);
3271 return new_html;
3275 * update all download tabs apart from one. Pass NULL if
3276 * you want to update all.
3278 void
3279 update_download_tabs(struct tab *apart_from)
3281 struct tab *t;
3282 if (!updating_dl_tabs) {
3283 updating_dl_tabs = 1; /* stop infinite recursion */
3284 TAILQ_FOREACH(t, &tabs, entry)
3285 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3286 && (t != apart_from))
3287 xtp_page_dl(t, NULL);
3288 updating_dl_tabs = 0;
3293 * update all cookie tabs apart from one. Pass NULL if
3294 * you want to update all.
3296 void
3297 update_cookie_tabs(struct tab *apart_from)
3299 struct tab *t;
3300 if (!updating_cl_tabs) {
3301 updating_cl_tabs = 1; /* stop infinite recursion */
3302 TAILQ_FOREACH(t, &tabs, entry)
3303 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3304 && (t != apart_from))
3305 xtp_page_cl(t, NULL);
3306 updating_cl_tabs = 0;
3311 * update all history tabs apart from one. Pass NULL if
3312 * you want to update all.
3314 void
3315 update_history_tabs(struct tab *apart_from)
3317 struct tab *t;
3319 if (!updating_hl_tabs) {
3320 updating_hl_tabs = 1; /* stop infinite recursion */
3321 TAILQ_FOREACH(t, &tabs, entry)
3322 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3323 && (t != apart_from))
3324 xtp_page_hl(t, NULL);
3325 updating_hl_tabs = 0;
3329 /* cookie management XTP page */
3331 xtp_page_cl(struct tab *t, struct karg *args)
3333 char *header, *body, *footer, *page, *tmp;
3334 int i = 1; /* all ids start 1 */
3335 GSList *sc, *pc, *pc_start;
3336 SoupCookie *c;
3337 char *type;
3339 DNPRINTF(XT_D_CMD, "%s", __func__);
3341 if (t == NULL)
3342 errx(1, "%s: null tab", __func__);
3344 /* mark this tab as cookie jar */
3345 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3347 /* Generate a new session key */
3348 if (!updating_cl_tabs)
3349 generate_xtp_session_key(&cl_session_key);
3351 /* header */
3352 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3353 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3354 "</head><body><h1>Cookie Jar</h1>\n");
3356 /* body */
3357 body = g_strdup_printf("<div align='center'><table><tr>"
3358 "<th>Type</th>"
3359 "<th>Name</th>"
3360 "<th>Value</th>"
3361 "<th>Domain</th>"
3362 "<th>Path</th>"
3363 "<th>Expires</th>"
3364 "<th>Secure</th>"
3365 "<th>HTTP_only</th>"
3366 "<th>Remove</th></tr>\n");
3368 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3369 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3370 pc_start = pc;
3372 for (; sc; sc = sc->next) {
3373 c = sc->data;
3375 type = "Session";
3376 for (pc = pc_start; pc; pc = pc->next)
3377 if (soup_cookie_equal(pc->data, c)) {
3378 type = "Session + Persistent";
3379 break;
3382 tmp = body;
3383 body = g_strdup_printf(
3384 "%s\n<tr>"
3385 "<td style='width: 3%%; text-align: center'>%s</td>"
3386 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3387 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3388 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3389 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3390 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3391 "<td style='width: 3%%; text-align: center'>%d</td>"
3392 "<td style='width: 3%%; text-align: center'>%d</td>"
3393 "<td style='width: 3%%; text-align: center'>"
3394 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3395 body,
3396 type,
3397 c->name,
3398 c->value,
3399 c->domain,
3400 c->path,
3401 c->expires ?
3402 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3403 c->secure,
3404 c->http_only,
3406 XT_XTP_STR,
3407 XT_XTP_CL,
3408 cl_session_key,
3409 XT_XTP_CL_REMOVE,
3413 g_free(tmp);
3414 i++;
3417 soup_cookies_free(sc);
3418 soup_cookies_free(pc);
3420 /* small message if there are none */
3421 if (i == 1) {
3422 tmp = body;
3423 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3424 "colspan='8'>No Cookies</td></tr>\n", body);
3425 g_free(tmp);
3428 /* footer */
3429 footer = g_strdup_printf("</table></div></body></html>");
3431 page = g_strdup_printf("%s%s%s", header, body, footer);
3433 g_free(header);
3434 g_free(body);
3435 g_free(footer);
3437 load_webkit_string(t, page);
3438 update_cookie_tabs(t);
3440 g_free(page);
3442 return (0);
3446 xtp_page_hl(struct tab *t, struct karg *args)
3448 char *header, *body, *footer, *page, *tmp;
3449 struct history *h;
3450 int i = 1; /* all ids start 1 */
3452 DNPRINTF(XT_D_CMD, "%s", __func__);
3454 if (t == NULL)
3455 errx(1, "%s: null tab", __func__);
3457 /* mark this tab as history manager */
3458 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3460 /* Generate a new session key */
3461 if (!updating_hl_tabs)
3462 generate_xtp_session_key(&hl_session_key);
3464 /* header */
3465 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3466 "<title>History</title>\n"
3467 "%s"
3468 "</head>"
3469 "<h1>History</h1>\n",
3470 XT_PAGE_STYLE);
3472 /* body */
3473 body = g_strdup_printf("<div align='center'><table><tr>"
3474 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3476 RB_FOREACH_REVERSE(h, history_list, &hl) {
3477 tmp = body;
3478 body = g_strdup_printf(
3479 "%s\n<tr>"
3480 "<td><a href='%s'>%s</a></td>"
3481 "<td>%s</td>"
3482 "<td style='text-align: center'>"
3483 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3484 body, h->uri, h->uri, h->title,
3485 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3486 XT_XTP_HL_REMOVE, i);
3488 g_free(tmp);
3489 i++;
3492 /* small message if there are none */
3493 if (i == 1) {
3494 tmp = body;
3495 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3496 "colspan='3'>No History</td></tr>\n", body);
3497 g_free(tmp);
3500 /* footer */
3501 footer = g_strdup_printf("</table></div></body></html>");
3503 page = g_strdup_printf("%s%s%s", header, body, footer);
3506 * update all history manager tabs as the xtp session
3507 * key has now changed. No need to update the current tab.
3508 * Already did that above.
3510 update_history_tabs(t);
3512 g_free(header);
3513 g_free(body);
3514 g_free(footer);
3516 load_webkit_string(t, page);
3517 g_free(page);
3519 return (0);
3523 * Generate a web page detailing the status of any downloads
3526 xtp_page_dl(struct tab *t, struct karg *args)
3528 struct download *dl;
3529 char *header, *body, *footer, *page, *tmp;
3530 char *ref;
3531 int n_dl = 1;
3533 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3535 if (t == NULL)
3536 errx(1, "%s: null tab", __func__);
3538 /* mark as a download manager tab */
3539 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3542 * Generate a new session key for next page instance.
3543 * This only happens for the top level call to xtp_page_dl()
3544 * in which case updating_dl_tabs is 0.
3546 if (!updating_dl_tabs)
3547 generate_xtp_session_key(&dl_session_key);
3549 /* header - with refresh so as to update */
3550 if (refresh_interval >= 1)
3551 ref = g_strdup_printf(
3552 "<meta http-equiv='refresh' content='%u"
3553 ";url=%s%d/%s/%d' />\n",
3554 refresh_interval,
3555 XT_XTP_STR,
3556 XT_XTP_DL,
3557 dl_session_key,
3558 XT_XTP_DL_LIST);
3559 else
3560 ref = g_strdup("");
3563 header = g_strdup_printf(
3564 "%s\n<head>"
3565 "<title>Downloads</title>\n%s%s</head>\n",
3566 XT_DOCTYPE XT_HTML_TAG,
3567 ref,
3568 XT_PAGE_STYLE);
3570 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3571 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3572 "</p><table><tr><th style='width: 60%%'>"
3573 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3574 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3576 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3577 body = xtp_page_dl_row(t, body, dl);
3578 n_dl++;
3581 /* message if no downloads in list */
3582 if (n_dl == 1) {
3583 tmp = body;
3584 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3585 " style='text-align: center'>"
3586 "No downloads</td></tr>\n", body);
3587 g_free(tmp);
3590 /* footer */
3591 footer = g_strdup_printf("</table></div></body></html>");
3593 page = g_strdup_printf("%s%s%s", header, body, footer);
3597 * update all download manager tabs as the xtp session
3598 * key has now changed. No need to update the current tab.
3599 * Already did that above.
3601 update_download_tabs(t);
3603 g_free(ref);
3604 g_free(header);
3605 g_free(body);
3606 g_free(footer);
3608 load_webkit_string(t, page);
3609 g_free(page);
3611 return (0);
3615 search(struct tab *t, struct karg *args)
3617 gboolean d;
3619 if (t == NULL || args == NULL)
3620 errx(1, "search");
3621 if (t->search_text == NULL) {
3622 if (global_search == NULL)
3623 return (XT_CB_PASSTHROUGH);
3624 else {
3625 t->search_text = g_strdup(global_search);
3626 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3627 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3631 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3632 t->tab_id, args->i, t->search_forward, t->search_text);
3634 switch (args->i) {
3635 case XT_SEARCH_NEXT:
3636 d = t->search_forward;
3637 break;
3638 case XT_SEARCH_PREV:
3639 d = !t->search_forward;
3640 break;
3641 default:
3642 return (XT_CB_PASSTHROUGH);
3645 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3647 return (XT_CB_HANDLED);
3650 struct settings_args {
3651 char **body;
3652 int i;
3655 void
3656 print_setting(struct settings *s, char *val, void *cb_args)
3658 char *tmp, *color;
3659 struct settings_args *sa = cb_args;
3661 if (sa == NULL)
3662 return;
3664 if (s->flags & XT_SF_RUNTIME)
3665 color = "#22cc22";
3666 else
3667 color = "#cccccc";
3669 tmp = *sa->body;
3670 *sa->body = g_strdup_printf(
3671 "%s\n<tr>"
3672 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3673 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3674 *sa->body,
3675 color,
3676 s->name,
3677 color,
3680 g_free(tmp);
3681 sa->i++;
3685 set(struct tab *t, struct karg *args)
3687 char *header, *body, *footer, *page, *tmp, *pars;
3688 int i = 1;
3689 struct settings_args sa;
3691 if ((pars = getparams(args->s, "set")) == NULL) {
3692 bzero(&sa, sizeof sa);
3693 sa.body = &body;
3695 /* header */
3696 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3697 "\n<head><title>Settings</title>\n"
3698 "</head><body><h1>Settings</h1>\n");
3700 /* body */
3701 body = g_strdup_printf("<div align='center'><table><tr>"
3702 "<th align='left'>Setting</th>"
3703 "<th align='left'>Value</th></tr>\n");
3705 settings_walk(print_setting, &sa);
3706 i = sa.i;
3708 /* small message if there are none */
3709 if (i == 1) {
3710 tmp = body;
3711 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3712 "colspan='2'>No settings</td></tr>\n", body);
3713 g_free(tmp);
3716 /* footer */
3717 footer = g_strdup_printf("</table></div></body></html>");
3719 page = g_strdup_printf("%s%s%s", header, body, footer);
3721 g_free(header);
3722 g_free(body);
3723 g_free(footer);
3725 load_webkit_string(t, page);
3726 } else {
3727 show_oops(t, "Invalid command: %s", pars);
3730 return (XT_CB_PASSTHROUGH);
3734 session_save(struct tab *t, char *filename, char **ret)
3736 struct karg a;
3737 char *f = filename;
3738 int rv = 1;
3740 f += strlen("save");
3741 while (*f == ' ' && *f != '\0')
3742 f++;
3743 if (strlen(f) == 0)
3744 goto done;
3746 a.s = f;
3747 save_tabs(t, &a);
3748 strlcpy(named_session, f, sizeof named_session);
3750 *ret = f;
3751 rv = 0;
3752 done:
3753 return (rv);
3757 session_open(struct tab *t, char *filename, char **ret)
3759 struct karg a;
3760 char *f = filename;
3761 int rv = 1;
3763 f += strlen("open");
3764 while (*f == ' ' && *f != '\0')
3765 f++;
3766 if (strlen(f) == 0)
3767 goto done;
3769 a.s = f;
3770 open_tabs(t, &a);
3771 strlcpy(named_session, f, sizeof named_session);
3773 *ret = f;
3774 rv = 0;
3775 done:
3776 return (rv);
3780 session_cmd(struct tab *t, struct karg *args)
3782 char *action = NULL;
3783 char *filename = NULL;
3785 if (t == NULL)
3786 return (1);
3788 if ((action = getparams(args->s, "session")))
3790 else
3791 action = "show";
3793 if (!strcmp(action, "show"))
3794 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
3795 XT_SAVED_TABS_FILE : named_session);
3796 else if (g_str_has_prefix(action, "save ")) {
3797 if (session_save(t, action, &filename)) {
3798 show_oops(t, "Can't save session: %s",
3799 filename ? filename : "INVALID");
3800 goto done;
3802 } else if (g_str_has_prefix(action, "open ")) {
3803 if (session_open(t, action, &filename)) {
3804 show_oops(t, "Can't open session: %s",
3805 filename ? filename : "INVALID");
3806 goto done;
3808 } else
3809 show_oops(t, "Invalid command: %s", action);
3810 done:
3811 return (XT_CB_PASSTHROUGH);
3815 * Make a hardcopy of the page
3818 print_page(struct tab *t, struct karg *args)
3820 WebKitWebFrame *frame;
3822 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3825 * for now we just call the GTK print box,
3826 * but later we might decide to hook in a command.
3828 frame = webkit_web_view_get_main_frame(t->wv);
3829 webkit_web_frame_print(frame);
3831 return (0);
3835 go_home(struct tab *t, struct karg *args)
3837 char *newuri;
3839 newuri = guess_url_type((char *)home);
3840 webkit_web_view_load_uri(t->wv, newuri);
3841 free(newuri);
3843 return (0);
3847 restart(struct tab *t, struct karg *args)
3849 struct karg a;
3851 a.s = XT_RESTART_TABS_FILE;
3852 save_tabs(t, &a);
3853 execvp(start_argv[0], start_argv);
3854 /* NOTREACHED */
3856 return (0);
3859 /* inherent to GTK not all keys will be caught at all times */
3860 /* XXX sort key bindings */
3861 struct key_bindings {
3862 guint mask;
3863 guint use_in_entry;
3864 guint key;
3865 int (*func)(struct tab *, struct karg *);
3866 struct karg arg;
3867 } keys[] = {
3868 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
3869 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
3870 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
3871 { 0, 0, GDK_slash, command, {.i = '/'} },
3872 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
3873 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
3874 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
3875 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
3876 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3877 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3878 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
3879 { 0, 0, GDK_y, yank_uri, {0} },
3880 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
3881 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
3883 /* search */
3884 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
3885 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
3887 /* focus */
3888 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
3889 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
3891 /* command aliases (handy when -S flag is used) */
3892 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
3893 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
3894 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
3895 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
3897 /* hinting */
3898 { 0, 0, GDK_f, hint, {.i = 0} },
3900 /* navigation */
3901 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
3902 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
3903 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
3904 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
3905 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
3906 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
3907 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
3908 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
3909 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
3911 /* vertical movement */
3912 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
3913 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
3914 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
3915 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
3916 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
3917 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
3918 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
3919 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
3920 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
3921 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
3922 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
3923 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
3924 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
3925 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
3926 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
3927 /* horizontal movement */
3928 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
3929 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
3930 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
3931 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
3932 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
3933 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
3935 /* tabs */
3936 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
3937 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
3938 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
3939 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
3940 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
3941 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
3942 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
3943 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
3944 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
3945 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
3946 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
3947 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
3948 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
3949 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
3950 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
3951 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
3952 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
3953 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
3954 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
3955 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
3958 struct cmd {
3959 char *cmd;
3960 int params;
3961 int (*func)(struct tab *, struct karg *);
3962 struct karg arg;
3963 } cmds[] = {
3964 { "q!", 0, quit, {0} },
3965 { "qa", 0, quit, {0} },
3966 { "qa!", 0, quit, {0} },
3967 { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} },
3968 { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3969 { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3970 { "help", 0, help, {0} },
3971 { "about", 0, about, {0} },
3972 { "stats", 0, stats, {0} },
3973 { "version", 0, about, {0} },
3974 { "cookies", 0, xtp_page_cl, {0} },
3975 { "fav", 0, xtp_page_fl, {0} },
3976 { "favadd", 0, add_favorite, {0} },
3977 { "js", 2, js_cmd, {0} },
3978 { "cookie", 2, cookie_cmd, {0} },
3979 { "cert", 1, cert_cmd, {0} },
3980 { "ca", 0, ca_cmd, {0} },
3981 { "dl" , 0, xtp_page_dl, {0} },
3982 { "h" , 0, xtp_page_hl, {0} },
3983 { "hist" , 0, xtp_page_hl, {0} },
3984 { "history" , 0, xtp_page_hl, {0} },
3985 { "home" , 0, go_home, {0} },
3986 { "restart" , 0, restart, {0} },
3988 { "1", 0, move, {.i = XT_MOVE_TOP} },
3989 { "print", 0, print_page, {0} },
3991 /* tabs */
3992 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
3993 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
3994 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
3995 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
3996 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
3997 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
3998 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
3999 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4000 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4001 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4002 /* XXX add count to these commands */
4003 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4004 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4005 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4006 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4007 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4008 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4009 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4010 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4011 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4012 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4014 /* settings */
4015 { "set", 1, set, {0} },
4017 /* sessions */
4018 { "session", 1, session_cmd, {0} },
4021 gboolean
4022 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4024 hide_oops(t);
4026 return (FALSE);
4029 gboolean
4030 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4032 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4034 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4035 delete_tab(t);
4037 return (FALSE);
4041 * cancel, remove, etc. downloads
4043 void
4044 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4046 struct download find, *d;
4048 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4050 /* some commands require a valid download id */
4051 if (cmd != XT_XTP_DL_LIST) {
4052 /* lookup download in question */
4053 find.id = id;
4054 d = RB_FIND(download_list, &downloads, &find);
4056 if (d == NULL) {
4057 show_oops(t, "%s: no such download", __func__);
4058 return;
4062 /* decide what to do */
4063 switch (cmd) {
4064 case XT_XTP_DL_CANCEL:
4065 webkit_download_cancel(d->download);
4066 break;
4067 case XT_XTP_DL_REMOVE:
4068 webkit_download_cancel(d->download); /* just incase */
4069 g_object_unref(d->download);
4070 RB_REMOVE(download_list, &downloads, d);
4071 break;
4072 case XT_XTP_DL_LIST:
4073 /* Nothing */
4074 break;
4075 default:
4076 show_oops(t, "%s: unknown command", __func__);
4077 break;
4079 xtp_page_dl(t, NULL);
4083 * Actions on history, only does one thing for now, but
4084 * we provide the function for future actions
4086 void
4087 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4089 struct history *h, *next;
4090 int i = 1;
4092 switch (cmd) {
4093 case XT_XTP_HL_REMOVE:
4094 /* walk backwards, as listed in reverse */
4095 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4096 next = RB_PREV(history_list, &hl, h);
4097 if (id == i) {
4098 RB_REMOVE(history_list, &hl, h);
4099 g_free((gpointer) h->title);
4100 g_free((gpointer) h->uri);
4101 g_free(h);
4102 break;
4104 i++;
4106 break;
4107 case XT_XTP_HL_LIST:
4108 /* Nothing - just xtp_page_hl() below */
4109 break;
4110 default:
4111 show_oops(t, "%s: unknown command", __func__);
4112 break;
4115 xtp_page_hl(t, NULL);
4118 /* remove a favorite */
4119 void
4120 remove_favorite(struct tab *t, int index)
4122 char file[PATH_MAX], *title, *uri;
4123 char *new_favs, *tmp;
4124 FILE *f;
4125 int i;
4126 size_t len, lineno;
4128 /* open favorites */
4129 snprintf(file, sizeof file, "%s/%s/%s",
4130 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
4132 if ((f = fopen(file, "r")) == NULL) {
4133 show_oops(t, "%s: can't open favorites: %s",
4134 __func__, strerror(errno));
4135 return;
4138 /* build a string which will become the new favroites file */
4139 new_favs = g_strdup_printf("%s", "");
4141 for (i = 1;;) {
4142 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4143 if (feof(f) || ferror(f))
4144 break;
4145 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4146 if (len == 0) {
4147 free(title);
4148 title = NULL;
4149 continue;
4152 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4153 if (feof(f) || ferror(f)) {
4154 show_oops(t, "%s: can't parse favorites %s",
4155 __func__, strerror(errno));
4156 goto clean;
4160 /* as long as this isn't the one we are deleting add to file */
4161 if (i != index) {
4162 tmp = new_favs;
4163 new_favs = g_strdup_printf("%s%s\n%s\n",
4164 new_favs, title, uri);
4165 g_free(tmp);
4168 free(uri);
4169 uri = NULL;
4170 free(title);
4171 title = NULL;
4172 i++;
4174 fclose(f);
4176 /* write back new favorites file */
4177 if ((f = fopen(file, "w")) == NULL) {
4178 show_oops(t, "%s: can't open favorites: %s",
4179 __func__, strerror(errno));
4180 goto clean;
4183 fwrite(new_favs, strlen(new_favs), 1, f);
4184 fclose(f);
4186 clean:
4187 if (uri)
4188 free(uri);
4189 if (title)
4190 free(title);
4192 g_free(new_favs);
4195 void
4196 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4198 switch (cmd) {
4199 case XT_XTP_FL_LIST:
4200 /* nothing, just the below call to xtp_page_fl() */
4201 break;
4202 case XT_XTP_FL_REMOVE:
4203 remove_favorite(t, arg);
4204 break;
4205 default:
4206 show_oops(t, "%s: invalid favorites command", __func__);
4207 break;
4210 xtp_page_fl(t, NULL);
4213 void
4214 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4216 switch (cmd) {
4217 case XT_XTP_CL_LIST:
4218 /* nothing, just xtp_page_cl() */
4219 break;
4220 case XT_XTP_CL_REMOVE:
4221 remove_cookie(arg);
4222 break;
4223 default:
4224 show_oops(t, "%s: unknown cookie xtp command", __func__);
4225 break;
4228 xtp_page_cl(t, NULL);
4231 /* link an XTP class to it's session key and handler function */
4232 struct xtp_despatch {
4233 uint8_t xtp_class;
4234 char **session_key;
4235 void (*handle_func)(struct tab *, uint8_t, int);
4238 struct xtp_despatch xtp_despatches[] = {
4239 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4240 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4241 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4242 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4243 { NULL, NULL, NULL }
4247 * is the url xtp protocol? (xxxt://)
4248 * if so, parse and despatch correct bahvior
4251 parse_xtp_url(struct tab *t, const char *url)
4253 char *dup = NULL, *p, *last;
4254 uint8_t n_tokens = 0;
4255 char *tokens[4] = {NULL, NULL, NULL, ""};
4256 struct xtp_despatch *dsp, *dsp_match = NULL;
4257 uint8_t req_class;
4260 * tokens array meaning:
4261 * tokens[0] = class
4262 * tokens[1] = session key
4263 * tokens[2] = action
4264 * tokens[3] = optional argument
4267 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4269 /*xtp tab meaning is normal unless proven special */
4270 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4272 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4273 return 0;
4275 dup = g_strdup(url + strlen(XT_XTP_STR));
4277 /* split out the url */
4278 for ((p = strtok_r(dup, "/", &last)); p;
4279 (p = strtok_r(NULL, "/", &last))) {
4280 if (n_tokens < 4)
4281 tokens[n_tokens++] = p;
4284 /* should be atleast three fields 'class/seskey/command/arg' */
4285 if (n_tokens < 3)
4286 goto clean;
4288 dsp = xtp_despatches;
4289 req_class = atoi(tokens[0]);
4290 while (dsp->xtp_class != NULL) {
4291 if (dsp->xtp_class == req_class) {
4292 dsp_match = dsp;
4293 break;
4295 dsp++;
4298 /* did we find one atall? */
4299 if (dsp_match == NULL) {
4300 show_oops(t, "%s: no matching xtp despatch found", __func__);
4301 goto clean;
4304 /* check session key and call despatch function */
4305 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
4306 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4309 clean:
4310 if (dup)
4311 g_free(dup);
4313 return 1;
4318 void
4319 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4321 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4322 char *newuri = NULL;
4324 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4326 if (t == NULL)
4327 errx(1, "activate_uri_entry_cb");
4329 if (uri == NULL)
4330 errx(1, "uri");
4332 uri += strspn(uri, "\t ");
4334 /* if xxxt:// treat specially */
4335 if (!parse_xtp_url(t, uri)) {
4336 if (valid_url_type((char *)uri)) {
4337 newuri = guess_url_type((char *)uri);
4338 uri = newuri;
4341 webkit_web_view_load_uri(t->wv, uri);
4342 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4345 if (newuri)
4346 g_free(newuri);
4349 void
4350 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4352 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4353 char *newuri = NULL;
4355 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4357 if (t == NULL)
4358 errx(1, "activate_search_entry_cb");
4360 if (search_string == NULL) {
4361 show_oops(t, "no search_string");
4362 return;
4365 newuri = g_strdup_printf(search_string, search);
4367 webkit_web_view_load_uri(t->wv, newuri);
4368 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4370 if (newuri)
4371 g_free(newuri);
4374 void
4375 check_and_set_js(gchar *uri, struct tab *t)
4377 struct domain *d = NULL;
4378 int es = 0;
4380 if (uri == NULL || t == NULL)
4381 return;
4383 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4384 es = 0;
4385 else
4386 es = 1;
4388 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4389 es ? "enable" : "disable", uri);
4391 g_object_set((GObject *)t->settings,
4392 "enable-scripts", es, (char *)NULL);
4393 webkit_web_view_set_settings(t->wv, t->settings);
4395 button_set_stockid(t->js_toggle,
4396 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4399 void
4400 show_ca_status(struct tab *t, const char *uri)
4402 WebKitWebFrame *frame;
4403 WebKitWebDataSource *source;
4404 WebKitNetworkRequest *request;
4405 SoupMessage *message;
4406 GdkColor color;
4407 gchar *col_str = "white";
4408 int r;
4410 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4411 ssl_strict_certs, ssl_ca_file, uri);
4413 if (uri == NULL)
4414 goto done;
4415 if (ssl_ca_file == NULL) {
4416 if (g_str_has_prefix(uri, "http://"))
4417 goto done;
4418 if (g_str_has_prefix(uri, "https://")) {
4419 col_str = "red";
4420 goto done;
4422 return;
4424 if (g_str_has_prefix(uri, "http://") ||
4425 !g_str_has_prefix(uri, "https://"))
4426 goto done;
4428 frame = webkit_web_view_get_main_frame(t->wv);
4429 source = webkit_web_frame_get_data_source(frame);
4430 request = webkit_web_data_source_get_request(source);
4431 message = webkit_network_request_get_message(request);
4433 if (message && (soup_message_get_flags(message) &
4434 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4435 col_str = "green";
4436 goto done;
4437 } else {
4438 r = load_compare_cert(t, NULL);
4439 if (r == 0)
4440 col_str = "lightblue";
4441 else if (r == 1)
4442 col_str = "yellow";
4443 else
4444 col_str = "red";
4445 goto done;
4447 done:
4448 if (col_str) {
4449 gdk_color_parse(col_str, &color);
4450 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4454 void
4455 load_favicon(struct tab *t)
4457 gchar *name_hash;
4458 gint width, height;
4459 char *file;
4460 GdkPixbuf *pixbuf, *scaled;
4462 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256,
4463 t->icon_uri, -1);
4464 asprintf(&file, "%s/%s.ico", cache_dir, name_hash);
4465 g_free(name_hash);
4466 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
4467 free(file);
4468 if (!pixbuf) {
4469 update_favicon(t);
4470 return;
4472 g_object_get(pixbuf,
4473 "width", &width,
4474 "height", &height,
4475 (char *)NULL);
4476 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
4477 __func__, t->tab_id, width, height);
4479 if (width > 16 || height > 16) {
4480 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
4481 GDK_INTERP_BILINEAR);
4482 g_object_unref(pixbuf);
4483 } else {
4484 scaled = pixbuf;
4486 if (!scaled)
4487 update_favicon(t);
4489 if (t->icon_pixbuf) {
4490 g_object_unref(t->icon_pixbuf);
4492 t->icon_pixbuf = scaled;
4494 update_favicon(t);
4497 void
4498 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
4499 struct tab *t)
4501 WebKitDownloadStatus status = webkit_download_get_status (download);
4503 switch (status) {
4504 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
4505 load_favicon(t);
4506 /* fallthrough */
4507 case WEBKIT_DOWNLOAD_STATUS_ERROR:
4508 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
4509 g_object_unref(download);
4510 break;
4511 default:
4512 break;
4517 notify_icon_loaded_cb(WebKitWebView *wv, char *uri, struct tab *t)
4519 WebKitNetworkRequest *request;
4520 WebKitDownload *download;
4521 gchar *name_hash;
4522 char *dest_uri, *file;
4523 struct stat sb;
4525 if (uri == NULL || t == NULL)
4526 errx(1, "%s: invalid pointers", __func__);
4527 if (t->icon_uri) {
4528 if (!strcmp(t->icon_uri,uri)) {
4529 return TRUE;
4531 free(t->icon_uri);
4533 asprintf(&t->icon_uri, "%s", uri);
4534 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri %s "
4535 "(dl!)\n", __func__, t->tab_id, uri);
4537 request = webkit_network_request_new(t->icon_uri);
4538 if (!request) {
4539 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon uri %s "
4540 "(request failed)\n", __func__, t->tab_id, uri);
4541 return FALSE;
4543 download = webkit_download_new(request);
4544 g_object_unref(request);
4546 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
4547 asprintf(&file, "%s/%s.ico", cache_dir, name_hash);
4548 fflush(stderr);
4549 g_free(name_hash);
4550 if (!stat(file, &sb)) {
4551 free(file);
4552 load_favicon(t);
4553 return TRUE;
4555 asprintf(&dest_uri, "file://%s", file);
4556 free(file);
4558 webkit_download_set_destination_uri(download, dest_uri);
4559 free(dest_uri);
4561 g_signal_connect(G_OBJECT (download), "notify::status",
4562 G_CALLBACK (favicon_download_status_changed_cb), t);
4564 webkit_download_start(download);
4565 return TRUE;
4568 void
4569 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4571 WebKitWebFrame *frame;
4572 const gchar *set = NULL, *uri = NULL, *title = NULL;
4573 struct history *h, find;
4574 int add = 0;
4575 const gchar *s_loading;
4577 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4578 webkit_web_view_get_load_status(wview));
4580 if (t == NULL)
4581 errx(1, "notify_load_status_cb");
4583 switch (webkit_web_view_get_load_status(wview)) {
4584 case WEBKIT_LOAD_PROVISIONAL:
4585 #if GTK_CHECK_VERSION(2, 20, 0)
4586 gtk_widget_show(t->spinner);
4587 gtk_spinner_start(GTK_SPINNER(t->spinner));
4588 #endif
4589 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4591 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4592 t->focus_wv = 1;
4594 /* take focus if we are visible */
4595 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4596 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4598 break;
4600 case WEBKIT_LOAD_COMMITTED:
4601 frame = webkit_web_view_get_main_frame(wview);
4602 uri = webkit_web_frame_get_uri(frame);
4603 if (uri)
4604 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4606 /* check if js white listing is enabled */
4607 if (enable_js_whitelist) {
4608 frame = webkit_web_view_get_main_frame(wview);
4609 uri = webkit_web_frame_get_uri(frame);
4610 check_and_set_js((gchar *)uri, t);
4613 show_ca_status(t, uri);
4614 break;
4616 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4617 title = webkit_web_view_get_title(wview);
4618 frame = webkit_web_view_get_main_frame(wview);
4619 uri = webkit_web_frame_get_uri(frame);
4620 if (title)
4621 set = title;
4622 else if (uri)
4623 set = uri;
4624 else
4625 break;
4627 gtk_label_set_text(GTK_LABEL(t->label), set);
4628 gtk_window_set_title(GTK_WINDOW(main_window), set);
4630 if (uri) {
4631 if (!strncmp(uri, "http://", strlen("http://")) ||
4632 !strncmp(uri, "https://", strlen("https://")) ||
4633 !strncmp(uri, "file://", strlen("file://")))
4634 add = 1;
4635 if (add == 0)
4636 break;
4637 find.uri = uri;
4638 h = RB_FIND(history_list, &hl, &find);
4639 if (h)
4640 break;
4642 h = g_malloc(sizeof *h);
4643 h->uri = g_strdup(uri);
4644 if (title)
4645 h->title = g_strdup(title);
4646 else
4647 h->title = g_strdup(uri);
4648 RB_INSERT(history_list, &hl, h);
4649 update_history_tabs(NULL);
4652 break;
4654 case WEBKIT_LOAD_FINISHED:
4655 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4656 case WEBKIT_LOAD_FAILED:
4657 #endif
4658 #if GTK_CHECK_VERSION(2, 20, 0)
4659 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4660 gtk_widget_hide(t->spinner);
4661 #endif
4662 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4663 if (s_loading && !strcmp(s_loading, "Loading"))
4664 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4665 default:
4666 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4667 break;
4669 check_favicon(t);
4671 if (t->item)
4672 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
4673 else
4674 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4675 webkit_web_view_can_go_back(wview));
4677 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4678 webkit_web_view_can_go_forward(wview));
4681 void
4682 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4684 run_script(t, JS_HINTING);
4687 void
4688 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4690 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4691 progress == 100 ? 0 : (double)progress / 100);
4695 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4696 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4697 WebKitWebPolicyDecision *pd, struct tab *t)
4699 char *uri;
4701 if (t == NULL)
4702 errx(1, "webview_nw_cb");
4704 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4705 webkit_network_request_get_uri(request));
4707 /* open in current tab */
4708 uri = (char *)webkit_network_request_get_uri(request);
4709 webkit_web_view_load_uri(t->wv, uri);
4710 webkit_web_policy_decision_ignore(pd);
4712 return (TRUE); /* we made the decission */
4716 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4717 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4718 WebKitWebPolicyDecision *pd, struct tab *t)
4720 char *uri;
4722 if (t == NULL)
4723 errx(1, "webview_npd_cb");
4725 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4726 t->ctrl_click,
4727 webkit_network_request_get_uri(request));
4729 uri = (char *)webkit_network_request_get_uri(request);
4731 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4732 t->ctrl_click = 0;
4733 create_new_tab(uri, NULL, ctrl_click_focus);
4734 webkit_web_policy_decision_ignore(pd);
4735 return (TRUE); /* we made the decission */
4738 webkit_web_policy_decision_use(pd);
4739 return (TRUE); /* we made the decission */
4742 WebKitWebView *
4743 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4745 if (t == NULL)
4746 errx(1, "webview_cwv_cb");
4748 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
4749 webkit_web_view_get_uri(wv));
4751 return (wv);
4755 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
4757 /* we can not eat the event without throwing gtk off so defer it */
4759 /* catch middle click */
4760 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
4761 t->ctrl_click = 1;
4762 goto done;
4765 /* catch ctrl click */
4766 if (e->type == GDK_BUTTON_RELEASE &&
4767 CLEAN(e->state) == GDK_CONTROL_MASK)
4768 t->ctrl_click = 1;
4769 else
4770 t->ctrl_click = 0;
4771 done:
4772 return (XT_CB_PASSTHROUGH);
4776 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
4778 struct mime_type *m;
4780 m = find_mime_type(mime_type);
4781 if (m == NULL)
4782 return (1);
4784 switch (fork()) {
4785 case -1:
4786 err(1, "fork");
4787 /* NOTREACHED */
4788 case 0:
4789 break;
4790 default:
4791 return (0);
4794 /* child */
4795 execlp(m->mt_action, m->mt_action,
4796 webkit_network_request_get_uri(request), (void *)NULL);
4798 _exit(0);
4800 /* NOTREACHED */
4801 return (0);
4805 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
4806 WebKitNetworkRequest *request, char *mime_type,
4807 WebKitWebPolicyDecision *decision, struct tab *t)
4809 if (t == NULL)
4810 errx(1, "webview_mimetype_cb");
4812 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
4813 t->tab_id, mime_type);
4815 if (run_mimehandler(t, mime_type, request) == 0) {
4816 webkit_web_policy_decision_ignore(decision);
4817 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4818 return (TRUE);
4821 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
4822 webkit_web_policy_decision_download(decision);
4823 return (TRUE);
4826 return (FALSE);
4830 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
4832 const gchar *filename;
4833 char *uri = NULL;
4834 struct download *download_entry;
4835 int ret = TRUE;
4837 if (wk_download == NULL || t == NULL)
4838 errx(1, "%s: invalid pointers", __func__);
4840 filename = webkit_download_get_suggested_filename(wk_download);
4841 if (filename == NULL)
4842 return (FALSE); /* abort download */
4844 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
4846 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
4847 "local %s\n", __func__, t->tab_id, filename, uri);
4849 webkit_download_set_destination_uri(wk_download, uri);
4851 if (webkit_download_get_status(wk_download) ==
4852 WEBKIT_DOWNLOAD_STATUS_ERROR) {
4853 show_oops(t, "%s: download failed to start", __func__);
4854 ret = FALSE;
4855 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
4856 } else {
4857 download_entry = g_malloc(sizeof(struct download));
4858 download_entry->download = wk_download;
4859 download_entry->tab = t;
4860 download_entry->id = next_download_id++;
4861 RB_INSERT(download_list, &downloads, download_entry);
4862 /* get from history */
4863 g_object_ref(wk_download);
4864 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
4867 if (uri)
4868 g_free(uri);
4870 /* sync other download manager tabs */
4871 update_download_tabs(NULL);
4874 * NOTE: never redirect/render the current tab before this
4875 * function returns. This will cause the download to never start.
4877 return (ret); /* start download */
4880 /* XXX currently unused */
4881 void
4882 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
4884 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
4886 if (t == NULL)
4887 errx(1, "webview_hover_cb");
4889 if (uri) {
4890 if (t->hover) {
4891 g_free(t->hover);
4892 t->hover = NULL;
4894 t->hover = g_strdup(uri);
4895 } else if (t->hover) {
4896 g_free(t->hover);
4897 t->hover = NULL;
4902 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
4904 int i;
4905 char s[2], buf[128];
4906 const char *errstr = NULL;
4907 long long link;
4909 /* don't use w directly; use t->whatever instead */
4911 if (t == NULL)
4912 errx(1, "wv_keypress_after_cb");
4914 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
4915 e->keyval, e->state, t);
4917 if (t->hints_on) {
4918 /* ESC */
4919 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
4920 disable_hints(t);
4921 return (XT_CB_HANDLED);
4924 /* RETURN */
4925 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
4926 link = strtonum(t->hint_num, 1, 1000, &errstr);
4927 if (errstr) {
4928 /* we have a string */
4929 } else {
4930 /* we have a number */
4931 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
4932 t->hint_num);
4933 run_script(t, buf);
4935 disable_hints(t);
4938 /* BACKSPACE */
4939 /* XXX unfuck this */
4940 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
4941 if (t->hint_mode == XT_HINT_NUMERICAL) {
4942 /* last input was numerical */
4943 int l;
4944 l = strlen(t->hint_num);
4945 if (l > 0) {
4946 l--;
4947 if (l == 0) {
4948 disable_hints(t);
4949 enable_hints(t);
4950 } else {
4951 t->hint_num[l] = '\0';
4952 goto num;
4955 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
4956 /* last input was alphanumerical */
4957 int l;
4958 l = strlen(t->hint_buf);
4959 if (l > 0) {
4960 l--;
4961 if (l == 0) {
4962 disable_hints(t);
4963 enable_hints(t);
4964 } else {
4965 t->hint_buf[l] = '\0';
4966 goto anum;
4969 } else {
4970 /* bogus */
4971 disable_hints(t);
4975 /* numerical input */
4976 if (CLEAN(e->state) == 0 &&
4977 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
4978 snprintf(s, sizeof s, "%c", e->keyval);
4979 strlcat(t->hint_num, s, sizeof t->hint_num);
4980 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
4981 t->hint_num);
4982 num:
4983 link = strtonum(t->hint_num, 1, 1000, &errstr);
4984 if (errstr) {
4985 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
4986 disable_hints(t);
4987 } else {
4988 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
4989 t->hint_num);
4990 t->hint_mode = XT_HINT_NUMERICAL;
4991 run_script(t, buf);
4994 /* empty the counter buffer */
4995 bzero(t->hint_buf, sizeof t->hint_buf);
4996 return (XT_CB_HANDLED);
4999 /* alphanumerical input */
5000 if (
5001 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5002 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5003 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5004 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5005 snprintf(s, sizeof s, "%c", e->keyval);
5006 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5007 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5008 t->hint_buf);
5009 anum:
5010 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5011 run_script(t, buf);
5013 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5014 t->hint_buf);
5015 t->hint_mode = XT_HINT_ALPHANUM;
5016 run_script(t, buf);
5018 /* empty the counter buffer */
5019 bzero(t->hint_num, sizeof t->hint_num);
5020 return (XT_CB_HANDLED);
5023 return (XT_CB_HANDLED);
5026 for (i = 0; i < LENGTH(keys); i++)
5027 if (e->keyval == keys[i].key && CLEAN(e->state) ==
5028 keys[i].mask) {
5029 keys[i].func(t, &keys[i].arg);
5030 return (XT_CB_HANDLED);
5034 return (XT_CB_PASSTHROUGH);
5038 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5040 hide_oops(t);
5042 return (XT_CB_PASSTHROUGH);
5046 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5048 const gchar *c = gtk_entry_get_text(w);
5049 GdkColor color;
5050 int forward = TRUE;
5052 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5053 e->keyval, e->state, t);
5055 if (t == NULL)
5056 errx(1, "cmd_keyrelease_cb");
5058 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5059 e->keyval, e->state, t);
5061 if (c[0] == ':')
5062 goto done;
5063 if (strlen(c) == 1) {
5064 webkit_web_view_unmark_text_matches(t->wv);
5065 goto done;
5068 if (c[0] == '/')
5069 forward = TRUE;
5070 else if (c[0] == '?')
5071 forward = FALSE;
5072 else
5073 goto done;
5075 /* search */
5076 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
5077 FALSE) {
5078 /* not found, mark red */
5079 gdk_color_parse("red", &color);
5080 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5081 /* unmark and remove selection */
5082 webkit_web_view_unmark_text_matches(t->wv);
5083 /* my kingdom for a way to unselect text in webview */
5084 } else {
5085 /* found, highlight all */
5086 webkit_web_view_unmark_text_matches(t->wv);
5087 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
5088 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5089 gdk_color_parse("white", &color);
5090 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5092 done:
5093 return (XT_CB_PASSTHROUGH);
5096 #if 0
5098 cmd_complete(struct tab *t, char *s)
5100 int i;
5101 GtkEntry *w = GTK_ENTRY(t->cmd);
5103 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
5105 for (i = 0; i < LENGTH(cmds); i++) {
5106 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
5107 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
5108 #if 0
5109 gtk_entry_set_text(w, ":");
5110 gtk_entry_append_text(w, cmds[i].cmd);
5111 gtk_editable_set_position(GTK_EDITABLE(w), -1);
5112 #endif
5116 return (0);
5118 #endif
5121 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5123 int i;
5125 if (t == NULL)
5126 errx(1, "entry_key_cb");
5128 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5129 e->keyval, e->state, t);
5131 hide_oops(t);
5133 if (e->keyval == GDK_Escape)
5134 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5136 for (i = 0; i < LENGTH(keys); i++)
5137 if (e->keyval == keys[i].key &&
5138 CLEAN(e->state) == keys[i].mask &&
5139 keys[i].use_in_entry) {
5140 keys[i].func(t, &keys[i].arg);
5141 return (XT_CB_HANDLED);
5144 return (XT_CB_PASSTHROUGH);
5148 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5150 int rv = XT_CB_HANDLED;
5151 const gchar *c = gtk_entry_get_text(w);
5153 if (t == NULL)
5154 errx(1, "cmd_keypress_cb");
5156 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5157 e->keyval, e->state, t);
5159 /* sanity */
5160 if (c == NULL)
5161 e->keyval = GDK_Escape;
5162 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5163 e->keyval = GDK_Escape;
5165 switch (e->keyval) {
5166 #if 0
5167 case GDK_Tab:
5168 if (c[0] != ':')
5169 goto done;
5171 if (strchr (c, ' ')) {
5172 /* par completion */
5173 fprintf(stderr, "completeme par\n");
5174 goto done;
5177 cmd_complete(t, (char *)&c[1]);
5179 goto done;
5180 #endif
5181 case GDK_BackSpace:
5182 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
5183 break;
5184 /* FALLTHROUGH */
5185 case GDK_Escape:
5186 hide_cmd(t);
5187 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5189 /* cancel search */
5190 if (c[0] == '/' || c[0] == '?')
5191 webkit_web_view_unmark_text_matches(t->wv);
5192 goto done;
5195 rv = XT_CB_PASSTHROUGH;
5196 done:
5197 return (rv);
5201 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
5203 if (t == NULL)
5204 errx(1, "cmd_focusout_cb");
5206 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
5207 t->tab_id, t->focus_wv);
5209 hide_cmd(t);
5210 hide_oops(t);
5212 if (t->focus_wv)
5213 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5214 else
5215 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5217 return (XT_CB_PASSTHROUGH);
5220 void
5221 cmd_activate_cb(GtkEntry *entry, struct tab *t)
5223 int i;
5224 char *s;
5225 const gchar *c = gtk_entry_get_text(entry);
5227 if (t == NULL)
5228 errx(1, "cmd_activate_cb");
5230 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
5232 /* sanity */
5233 if (c == NULL)
5234 goto done;
5235 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5236 goto done;
5237 if (strlen(c) < 2)
5238 goto done;
5239 s = (char *)&c[1];
5241 if (c[0] == '/' || c[0] == '?') {
5242 if (t->search_text) {
5243 g_free(t->search_text);
5244 t->search_text = NULL;
5247 t->search_text = g_strdup(s);
5248 if (global_search)
5249 g_free(global_search);
5250 global_search = g_strdup(s);
5251 t->search_forward = c[0] == '/';
5253 goto done;
5256 for (i = 0; i < LENGTH(cmds); i++)
5257 if (cmds[i].params) {
5258 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
5259 cmds[i].arg.s = g_strdup(s);
5260 goto execute_command;
5262 } else {
5263 if (!strcmp(s, cmds[i].cmd))
5264 goto execute_command;
5266 show_oops(t, "Invalid command: %s", s);
5267 done:
5268 hide_cmd(t);
5269 return;
5271 execute_command:
5272 hide_cmd(t);
5273 cmds[i].func(t, &cmds[i].arg);
5275 void
5276 backward_cb(GtkWidget *w, struct tab *t)
5278 struct karg a;
5280 if (t == NULL)
5281 errx(1, "backward_cb");
5283 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
5285 a.i = XT_NAV_BACK;
5286 navaction(t, &a);
5289 void
5290 forward_cb(GtkWidget *w, struct tab *t)
5292 struct karg a;
5294 if (t == NULL)
5295 errx(1, "forward_cb");
5297 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
5299 a.i = XT_NAV_FORWARD;
5300 navaction(t, &a);
5303 void
5304 stop_cb(GtkWidget *w, struct tab *t)
5306 WebKitWebFrame *frame;
5308 if (t == NULL)
5309 errx(1, "stop_cb");
5311 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5313 frame = webkit_web_view_get_main_frame(t->wv);
5314 if (frame == NULL) {
5315 show_oops(t, "stop_cb: no frame");
5316 return;
5319 webkit_web_frame_stop_loading(frame);
5322 void
5323 setup_webkit(struct tab *t)
5325 g_object_set((GObject *)t->settings,
5326 "user-agent", t->user_agent, (char *)NULL);
5327 g_object_set((GObject *)t->settings,
5328 "enable-scripts", enable_scripts, (char *)NULL);
5329 g_object_set((GObject *)t->settings,
5330 "enable-plugins", enable_plugins, (char *)NULL);
5331 adjustfont_webkit(t, XT_FONT_SET);
5333 webkit_web_view_set_settings(t->wv, t->settings);
5336 GtkWidget *
5337 create_browser(struct tab *t)
5339 GtkWidget *w;
5340 gchar *strval;
5342 if (t == NULL)
5343 errx(1, "create_browser");
5345 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5346 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5347 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5348 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5350 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5351 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5352 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5354 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5355 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5357 /* set defaults */
5358 t->settings = webkit_web_settings_new();
5360 if (user_agent == NULL) {
5361 g_object_get((GObject *)t->settings, "user-agent", &strval,
5362 (char *)NULL);
5363 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5364 g_free(strval);
5365 } else {
5366 t->user_agent = g_strdup(user_agent);
5369 setup_webkit(t);
5371 return (w);
5374 GtkWidget *
5375 create_window(void)
5377 GtkWidget *w;
5379 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5380 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
5381 gtk_widget_set_name(w, "xxxterm");
5382 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
5383 g_signal_connect(G_OBJECT(w), "delete_event",
5384 G_CALLBACK (gtk_main_quit), NULL);
5386 return (w);
5389 GtkWidget *
5390 create_toolbar(struct tab *t)
5392 GtkWidget *toolbar = NULL, *b, *eb1;
5394 b = gtk_hbox_new(FALSE, 0);
5395 toolbar = b;
5396 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5398 if (fancy_bar) {
5399 /* backward button */
5400 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
5401 gtk_widget_set_sensitive(t->backward, FALSE);
5402 g_signal_connect(G_OBJECT(t->backward), "clicked",
5403 G_CALLBACK(backward_cb), t);
5404 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5406 /* forward button */
5407 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
5408 gtk_widget_set_sensitive(t->forward, FALSE);
5409 g_signal_connect(G_OBJECT(t->forward), "clicked",
5410 G_CALLBACK(forward_cb), t);
5411 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5412 FALSE, 0);
5414 /* stop button */
5415 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
5416 gtk_widget_set_sensitive(t->stop, FALSE);
5417 g_signal_connect(G_OBJECT(t->stop), "clicked",
5418 G_CALLBACK(stop_cb), t);
5419 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5420 FALSE, 0);
5422 /* JS button */
5423 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
5424 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5425 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5426 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5427 G_CALLBACK(js_toggle_cb), t);
5428 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5431 t->uri_entry = gtk_entry_new();
5432 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5433 G_CALLBACK(activate_uri_entry_cb), t);
5434 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5435 (GCallback)entry_key_cb, t);
5436 eb1 = gtk_hbox_new(FALSE, 0);
5437 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5438 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5439 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5441 /* search entry */
5442 if (fancy_bar && search_string) {
5443 GtkWidget *eb2;
5444 t->search_entry = gtk_entry_new();
5445 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5446 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5447 G_CALLBACK(activate_search_entry_cb), t);
5448 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5449 (GCallback)entry_key_cb, t);
5450 gtk_widget_set_size_request(t->search_entry, -1, -1);
5451 eb2 = gtk_hbox_new(FALSE, 0);
5452 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5453 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5455 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5457 return (toolbar);
5460 void
5461 recalc_tabs(void)
5463 struct tab *t;
5465 TAILQ_FOREACH(t, &tabs, entry)
5466 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5470 undo_close_tab_save(struct tab *t)
5472 int m, n;
5473 const gchar *uri;
5474 struct undo *u1, *u2;
5475 WebKitWebFrame *frame;
5476 GList *items;
5477 WebKitWebHistoryItem *item;
5479 frame = webkit_web_view_get_main_frame(t->wv);
5480 uri = webkit_web_frame_get_uri(frame);
5482 if (uri && !strlen(uri))
5483 return (1);
5485 u1 = g_malloc0(sizeof(struct undo));
5486 u1->uri = g_strdup(uri);
5488 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5490 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
5491 n = webkit_web_back_forward_list_get_back_length(t->bfl);
5492 u1->back = n;
5494 /* forward history */
5495 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
5497 while (items) {
5498 item = items->data;
5499 u1->history = g_list_prepend(u1->history,
5500 webkit_web_history_item_copy(item));
5501 items = g_list_next(items);
5504 /* current item */
5505 if (m) {
5506 item = webkit_web_back_forward_list_get_current_item(t->bfl);
5507 u1->history = g_list_prepend(u1->history,
5508 webkit_web_history_item_copy(item));
5511 /* back history */
5512 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
5514 while (items) {
5515 item = items->data;
5516 u1->history = g_list_prepend(u1->history,
5517 webkit_web_history_item_copy(item));
5518 items = g_list_next(items);
5521 TAILQ_INSERT_HEAD(&undos, u1, entry);
5523 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5524 u2 = TAILQ_LAST(&undos, undo_tailq);
5525 TAILQ_REMOVE(&undos, u2, entry);
5526 g_free(u2->uri);
5527 g_list_free(u2->history);
5528 g_free(u2);
5529 } else
5530 undo_count++;
5532 return (0);
5535 void
5536 delete_tab(struct tab *t)
5538 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5540 if (t == NULL)
5541 return;
5543 TAILQ_REMOVE(&tabs, t, entry);
5545 /* halt all webkit activity */
5546 webkit_web_view_stop_loading(t->wv);
5547 undo_close_tab_save(t);
5549 gtk_widget_destroy(t->vbox);
5550 g_free(t->user_agent);
5551 g_free(t);
5553 recalc_tabs();
5554 if (TAILQ_EMPTY(&tabs))
5555 create_new_tab(NULL, NULL, 1);
5558 void
5559 adjustfont_webkit(struct tab *t, int adjust)
5561 if (t == NULL)
5562 errx(1, "adjustfont_webkit");
5564 if (adjust == XT_FONT_SET)
5565 t->font_size = default_font_size;
5567 t->font_size += adjust;
5568 g_object_set((GObject *)t->settings, "default-font-size",
5569 t->font_size, (char *)NULL);
5570 g_object_get((GObject *)t->settings, "default-font-size",
5571 &t->font_size, (char *)NULL);
5574 void
5575 append_tab(struct tab *t)
5577 if (t == NULL)
5578 return;
5580 TAILQ_INSERT_TAIL(&tabs, t, entry);
5581 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5584 void
5585 create_new_tab(char *title, struct undo *u, int focus)
5587 struct tab *t, *tt;
5588 int load = 1, id, notfound;
5589 char *newuri = NULL;
5590 GtkWidget *b, *bb;
5591 WebKitWebHistoryItem *item;
5592 GList *items;
5593 GdkColor color;
5595 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5597 if (tabless && !TAILQ_EMPTY(&tabs)) {
5598 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5599 return;
5602 t = g_malloc0(sizeof *t);
5604 if (title == NULL) {
5605 title = "(untitled)";
5606 load = 0;
5607 } else {
5608 if (valid_url_type(title)) {
5609 newuri = guess_url_type(title);
5610 title = newuri;
5614 t->vbox = gtk_vbox_new(FALSE, 0);
5616 /* label + button for tab */
5617 b = gtk_hbox_new(FALSE, 0);
5618 t->tab_content = b;
5620 #if GTK_CHECK_VERSION(2, 20, 0)
5621 t->spinner = gtk_spinner_new ();
5622 #endif
5623 t->label = gtk_label_new(title);
5624 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
5625 gtk_widget_set_size_request(t->label, 100, 0);
5626 gtk_widget_set_size_request(b, 130, 0);
5627 gtk_notebook_set_homogeneous_tabs(notebook, TRUE);
5629 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5630 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5631 #if GTK_CHECK_VERSION(2, 20, 0)
5632 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5633 #endif
5635 /* toolbar */
5636 t->toolbar = create_toolbar(t);
5637 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5639 /* browser */
5640 t->browser_win = create_browser(t);
5641 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5643 /* oops message for user feedback */
5644 t->oops = gtk_entry_new();
5645 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
5646 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
5647 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
5648 gdk_color_parse("red", &color);
5649 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
5650 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
5652 /* command entry */
5653 t->cmd = gtk_entry_new();
5654 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5655 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5656 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5658 /* xtp meaning is normal by default */
5659 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5661 /* and show it all */
5662 gtk_widget_show_all(b);
5663 gtk_widget_show_all(t->vbox);
5665 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5666 append_tab(t);
5667 else {
5668 notfound = 1;
5669 id = gtk_notebook_get_current_page(notebook);
5670 TAILQ_FOREACH(tt, &tabs, entry) {
5671 if (tt->tab_id == id) {
5672 notfound = 0;
5673 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5674 gtk_notebook_insert_page(notebook, t->vbox, b,
5675 id + 1);
5676 recalc_tabs();
5677 break;
5680 if (notfound)
5681 append_tab(t);
5684 #if GTK_CHECK_VERSION(2, 20, 0)
5685 /* turn spinner off if we are a new tab without uri */
5686 if (!load) {
5687 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5688 gtk_widget_hide(t->spinner);
5690 #endif
5691 /* make notebook tabs reorderable */
5692 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5694 g_object_connect((GObject*)t->cmd,
5695 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5696 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5697 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5698 "signal::activate", (GCallback)cmd_activate_cb, t,
5699 (char *)NULL);
5701 /* reuse wv_button_cb to hide oops */
5702 g_object_connect((GObject*)t->oops,
5703 "signal::button_press_event", (GCallback)wv_button_cb, t,
5704 (char *)NULL);
5706 g_object_connect((GObject*)t->wv,
5707 "signal::key-press-event", (GCallback)wv_keypress_cb, t,
5708 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5709 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5710 "signal::download-requested", (GCallback)webview_download_cb, t,
5711 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5712 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5713 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5714 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5715 "signal::event", (GCallback)webview_event_cb, t,
5716 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5717 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5718 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5719 "signal::icon-loaded", (GCallback)notify_icon_loaded_cb, t,
5720 #endif
5721 "signal::button_press_event", (GCallback)wv_button_cb, t,
5722 (char *)NULL);
5723 g_signal_connect(t->wv,
5724 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
5726 /* hijack the unused keys as if we were the browser */
5727 g_object_connect((GObject*)t->toolbar,
5728 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
5729 (char *)NULL);
5731 g_signal_connect(G_OBJECT(bb), "button_press_event",
5732 G_CALLBACK(tab_close_cb), t);
5734 /* hide stuff */
5735 hide_cmd(t);
5736 hide_oops(t);
5737 if (showurl == 0)
5738 gtk_widget_hide(t->toolbar);
5740 if (focus) {
5741 gtk_notebook_set_current_page(notebook, t->tab_id);
5742 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
5743 t->tab_id);
5746 if (load)
5747 webkit_web_view_load_uri(t->wv, title);
5748 else {
5749 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5750 check_favicon(t);
5753 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
5754 /* restore the tab's history */
5755 if (u && u->history) {
5756 items = u->history;
5757 while (items) {
5758 item = items->data;
5759 webkit_web_back_forward_list_add_item(t->bfl, item);
5760 items = g_list_next(items);
5763 item = g_list_nth_data(u->history, u->back);
5764 if (item)
5765 webkit_web_view_go_to_back_forward_item(t->wv, item);
5767 g_list_free(items);
5768 g_list_free(u->history);
5771 if (newuri)
5772 g_free(newuri);
5775 void
5776 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
5777 gpointer *udata)
5779 struct tab *t;
5780 const gchar *uri;
5782 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
5784 TAILQ_FOREACH(t, &tabs, entry) {
5785 if (t->tab_id == pn) {
5786 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
5787 "%d\n", pn);
5789 uri = webkit_web_view_get_title(t->wv);
5790 if (uri == NULL)
5791 uri = XT_NAME;
5792 gtk_window_set_title(GTK_WINDOW(main_window), uri);
5794 hide_cmd(t);
5795 hide_oops(t);
5797 if (t->focus_wv)
5798 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5803 void
5804 menuitem_response(struct tab *t)
5806 gtk_notebook_set_current_page(notebook, t->tab_id);
5809 gboolean
5810 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
5812 GtkWidget *menu, *menu_items;
5813 GdkEventButton *bevent;
5814 WebKitWebFrame *frame;
5815 const gchar *uri;
5816 struct tab *ti;
5818 if (event->type == GDK_BUTTON_PRESS) {
5819 bevent = (GdkEventButton *) event;
5820 menu = gtk_menu_new();
5822 TAILQ_FOREACH(ti, &tabs, entry) {
5823 frame = webkit_web_view_get_main_frame(ti->wv);
5824 uri = webkit_web_frame_get_uri(frame);
5825 /* XXX make sure there is something to print */
5826 /* XXX add gui pages in here to look purdy */
5827 if (uri == NULL)
5828 uri = "(untitled)";
5829 if (strlen(uri) == 0)
5830 uri = "(untitled)";
5831 menu_items = gtk_menu_item_new_with_label(uri);
5832 gtk_menu_append(GTK_MENU (menu), menu_items);
5833 gtk_widget_show(menu_items);
5835 gtk_signal_connect_object(GTK_OBJECT(menu_items),
5836 "activate", GTK_SIGNAL_FUNC(menuitem_response),
5837 (gpointer)ti);
5840 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
5841 bevent->button, bevent->time);
5843 /* unref object so it'll free itself when popped down */
5844 g_object_ref_sink(menu);
5845 g_object_unref(menu);
5847 return (TRUE /* eat event */);
5850 return (FALSE /* propagate */);
5854 icon_size_map(int icon_size)
5856 if (icon_size <= GTK_ICON_SIZE_INVALID ||
5857 icon_size > GTK_ICON_SIZE_DIALOG)
5858 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
5860 return (icon_size);
5863 GtkWidget *
5864 create_button(char *name, char *stockid, int size)
5866 GtkWidget *button, *image;
5867 char *rcstring;
5868 int gtk_icon_size;
5869 asprintf(&rcstring,
5870 "style \"%s-style\"\n"
5871 "{\n"
5872 " GtkWidget::focus-padding = 0\n"
5873 " GtkWidget::focus-line-width = 0\n"
5874 " xthickness = 0\n"
5875 " ythickness = 0\n"
5876 "}\n"
5877 "widget \"*.%s\" style \"%s-style\"",name,name,name);
5878 gtk_rc_parse_string(rcstring);
5879 free(rcstring);
5880 button = gtk_button_new();
5881 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5882 gtk_icon_size = icon_size_map(size?size:icon_size);
5884 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
5885 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5886 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
5887 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
5888 gtk_widget_set_name(button, name);
5889 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
5890 gtk_widget_set_tooltip_text(button, name);
5892 return button;
5895 void
5896 button_set_stockid(GtkWidget *button, char *stockid)
5898 GtkWidget *image;
5899 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
5900 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5901 gtk_button_set_image(GTK_BUTTON(button), image);
5904 void
5905 create_canvas(void)
5907 GtkWidget *vbox;
5908 GList *l = NULL;
5909 GdkPixbuf *pb;
5910 char file[PATH_MAX];
5911 int i;
5913 vbox = gtk_vbox_new(FALSE, 0);
5914 gtk_box_set_spacing(GTK_BOX(vbox), 0);
5915 notebook = GTK_NOTEBOOK(gtk_notebook_new());
5916 if (showtabs == 0)
5917 gtk_notebook_set_show_tabs(notebook, FALSE);
5918 else {
5919 gtk_notebook_set_tab_hborder(notebook, 0);
5920 gtk_notebook_set_tab_vborder(notebook, 0);
5922 gtk_notebook_set_show_border(notebook, FALSE);
5923 gtk_notebook_set_scrollable(notebook, TRUE);
5924 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
5926 abtn = gtk_button_new();
5927 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5928 gtk_widget_set_size_request(arrow, -1, -1);
5929 gtk_container_add(GTK_CONTAINER(abtn), arrow);
5930 gtk_widget_set_size_request(abtn, -1, 20);
5931 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
5933 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
5934 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
5935 gtk_widget_set_size_request(vbox, -1, -1);
5937 g_object_connect((GObject*)notebook,
5938 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
5939 (char *)NULL);
5940 g_signal_connect(G_OBJECT(abtn), "button_press_event",
5941 G_CALLBACK(arrow_cb), NULL);
5943 main_window = create_window();
5944 gtk_container_add(GTK_CONTAINER(main_window), vbox);
5945 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5947 /* icons */
5948 for (i = 0; i < LENGTH(icons); i++) {
5949 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
5950 pb = gdk_pixbuf_new_from_file(file, NULL);
5951 l = g_list_append(l, pb);
5953 gtk_window_set_default_icon_list(l);
5955 gtk_widget_show_all(abtn);
5956 gtk_widget_show_all(main_window);
5959 void
5960 set_hook(void **hook, char *name)
5962 if (hook == NULL)
5963 errx(1, "set_hook");
5965 if (*hook == NULL) {
5966 *hook = dlsym(RTLD_NEXT, name);
5967 if (*hook == NULL)
5968 errx(1, "can't hook %s", name);
5972 /* override libsoup soup_cookie_equal because it doesn't look at domain */
5973 gboolean
5974 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
5976 g_return_val_if_fail(cookie1, FALSE);
5977 g_return_val_if_fail(cookie2, FALSE);
5979 return (!strcmp (cookie1->name, cookie2->name) &&
5980 !strcmp (cookie1->value, cookie2->value) &&
5981 !strcmp (cookie1->path, cookie2->path) &&
5982 !strcmp (cookie1->domain, cookie2->domain));
5985 void
5986 transfer_cookies(void)
5988 GSList *cf;
5989 SoupCookie *sc, *pc;
5991 cf = soup_cookie_jar_all_cookies(p_cookiejar);
5993 for (;cf; cf = cf->next) {
5994 pc = cf->data;
5995 sc = soup_cookie_copy(pc);
5996 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
5999 soup_cookies_free(cf);
6002 void
6003 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6005 GSList *cf;
6006 SoupCookie *ci;
6008 print_cookie("soup_cookie_jar_delete_cookie", c);
6010 if (cookies_enabled == 0)
6011 return;
6013 if (jar == NULL || c == NULL)
6014 return;
6016 /* find and remove from persistent jar */
6017 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6019 for (;cf; cf = cf->next) {
6020 ci = cf->data;
6021 if (soup_cookie_equal(ci, c)) {
6022 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6023 break;
6027 soup_cookies_free(cf);
6029 /* delete from session jar */
6030 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
6033 void
6034 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
6036 struct domain *d;
6037 SoupCookie *c;
6038 FILE *r_cookie_f;
6040 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
6041 jar, p_cookiejar, s_cookiejar);
6043 if (cookies_enabled == 0)
6044 return;
6046 /* see if we are up and running */
6047 if (p_cookiejar == NULL) {
6048 _soup_cookie_jar_add_cookie(jar, cookie);
6049 return;
6051 /* disallow p_cookiejar adds, shouldn't happen */
6052 if (jar == p_cookiejar)
6053 return;
6055 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
6056 blocked_cookies++;
6057 DNPRINTF(XT_D_COOKIE,
6058 "soup_cookie_jar_add_cookie: reject %s\n",
6059 cookie->domain);
6060 if (save_rejected_cookies) {
6061 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
6062 err(1, "reject cookie file");
6063 fseek(r_cookie_f, 0, SEEK_END);
6064 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6065 cookie->http_only ? "#HttpOnly_" : "",
6066 cookie->domain,
6067 *cookie->domain == '.' ? "TRUE" : "FALSE",
6068 cookie->path,
6069 cookie->secure ? "TRUE" : "FALSE",
6070 cookie->expires ?
6071 (gulong)soup_date_to_time_t(cookie->expires) :
6073 cookie->name,
6074 cookie->value);
6075 fflush(r_cookie_f);
6076 fclose(r_cookie_f);
6078 if (!allow_volatile_cookies)
6079 return;
6082 if (cookie->expires == NULL && session_timeout) {
6083 soup_cookie_set_expires(cookie,
6084 soup_date_new_from_now(session_timeout));
6085 print_cookie("modified add cookie", cookie);
6088 /* see if we are white listed for persistence */
6089 if (d && d->handy) {
6090 /* add to persistent jar */
6091 c = soup_cookie_copy(cookie);
6092 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
6093 _soup_cookie_jar_add_cookie(p_cookiejar, c);
6096 /* add to session jar */
6097 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
6098 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
6101 void
6102 setup_cookies(void)
6104 char file[PATH_MAX];
6106 set_hook((void *)&_soup_cookie_jar_add_cookie,
6107 "soup_cookie_jar_add_cookie");
6108 set_hook((void *)&_soup_cookie_jar_delete_cookie,
6109 "soup_cookie_jar_delete_cookie");
6111 if (cookies_enabled == 0)
6112 return;
6115 * the following code is intricate due to overriding several libsoup
6116 * functions.
6117 * do not alter order of these operations.
6120 /* rejected cookies */
6121 if (save_rejected_cookies)
6122 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
6124 /* persistent cookies */
6125 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6126 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
6128 /* session cookies */
6129 s_cookiejar = soup_cookie_jar_new();
6130 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
6131 cookie_policy, (void *)NULL);
6132 transfer_cookies();
6134 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
6137 void
6138 setup_proxy(char *uri)
6140 if (proxy_uri) {
6141 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
6142 soup_uri_free(proxy_uri);
6143 proxy_uri = NULL;
6145 if (http_proxy) {
6146 if (http_proxy != uri) {
6147 g_free(http_proxy);
6148 http_proxy = NULL;
6152 if (uri) {
6153 http_proxy = g_strdup(uri);
6154 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
6155 proxy_uri = soup_uri_new(http_proxy);
6156 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
6161 send_url_to_socket(char *url)
6163 int s, len, rv = -1;
6164 struct sockaddr_un sa;
6166 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6167 warnx("send_url_to_socket: socket");
6168 return (-1);
6171 sa.sun_family = AF_UNIX;
6172 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6173 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6174 len = SUN_LEN(&sa);
6176 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6177 warnx("send_url_to_socket: connect");
6178 goto done;
6181 if (send(s, url, strlen(url) + 1, 0) == -1) {
6182 warnx("send_url_to_socket: send");
6183 goto done;
6185 done:
6186 close(s);
6187 return (rv);
6190 void
6191 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
6193 int s, n;
6194 char str[XT_MAX_URL_LENGTH];
6195 socklen_t t = sizeof(struct sockaddr_un);
6196 struct sockaddr_un sa;
6197 struct passwd *p;
6198 uid_t uid;
6199 gid_t gid;
6201 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
6202 warn("socket_watcher: accept");
6203 return;
6206 if (getpeereid(s, &uid, &gid) == -1) {
6207 warn("socket_watcher: getpeereid");
6208 return;
6210 if (uid != getuid() || gid != getgid()) {
6211 warnx("socket_watcher: unauthorized user");
6212 return;
6215 p = getpwuid(uid);
6216 if (p == NULL) {
6217 warnx("socket_watcher: not a valid user");
6218 return;
6221 n = recv(s, str, sizeof(str), 0);
6222 if (n <= 0)
6223 return;
6225 create_new_tab(str, NULL, 1);
6229 is_running(void)
6231 int s, len, rv = 1;
6232 struct sockaddr_un sa;
6234 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6235 warn("is_running: socket");
6236 return (-1);
6239 sa.sun_family = AF_UNIX;
6240 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6241 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6242 len = SUN_LEN(&sa);
6244 /* connect to see if there is a listener */
6245 if (connect(s, (struct sockaddr *)&sa, len) == -1)
6246 rv = 0; /* not running */
6247 else
6248 rv = 1; /* already running */
6250 close(s);
6252 return (rv);
6256 build_socket(void)
6258 int s, len;
6259 struct sockaddr_un sa;
6261 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6262 warn("build_socket: socket");
6263 return (-1);
6266 sa.sun_family = AF_UNIX;
6267 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6268 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6269 len = SUN_LEN(&sa);
6271 /* connect to see if there is a listener */
6272 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6273 /* no listener so we will */
6274 unlink(sa.sun_path);
6276 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
6277 warn("build_socket: bind");
6278 goto done;
6281 if (listen(s, 1) == -1) {
6282 warn("build_socket: listen");
6283 goto done;
6286 return (s);
6289 done:
6290 close(s);
6291 return (-1);
6294 void
6295 usage(void)
6297 fprintf(stderr,
6298 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
6299 exit(0);
6303 main(int argc, char *argv[])
6305 struct stat sb;
6306 int c, focus = 1, s, optn = 0;
6307 char conf[PATH_MAX] = { '\0' };
6308 char file[PATH_MAX];
6309 char *env_proxy = NULL;
6310 FILE *f = NULL;
6311 struct karg a;
6313 start_argv = argv;
6315 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
6317 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
6318 switch (c) {
6319 case 'S':
6320 showurl = 0;
6321 break;
6322 case 'T':
6323 showtabs = 0;
6324 break;
6325 case 'V':
6326 errx(0 , "Version: %s", version);
6327 break;
6328 case 'f':
6329 strlcpy(conf, optarg, sizeof(conf));
6330 break;
6331 case 's':
6332 strlcpy(named_session, optarg, sizeof(named_session));
6333 break;
6334 case 't':
6335 tabless = 1;
6336 break;
6337 case 'n':
6338 optn = 1;
6339 break;
6340 default:
6341 usage();
6342 /* NOTREACHED */
6345 argc -= optind;
6346 argv += optind;
6348 TAILQ_INIT(&tabs);
6349 RB_INIT(&hl);
6350 RB_INIT(&js_wl);
6351 RB_INIT(&downloads);
6352 TAILQ_INIT(&mtl);
6353 TAILQ_INIT(&aliases);
6354 TAILQ_INIT(&undos);
6356 gnutls_global_init();
6358 /* generate session keys for xtp pages */
6359 generate_xtp_session_key(&dl_session_key);
6360 generate_xtp_session_key(&hl_session_key);
6361 generate_xtp_session_key(&cl_session_key);
6362 generate_xtp_session_key(&fl_session_key);
6364 /* prepare gtk */
6365 gtk_init(&argc, &argv);
6366 if (!g_thread_supported())
6367 g_thread_init(NULL);
6369 pwd = getpwuid(getuid());
6370 if (pwd == NULL)
6371 errx(1, "invalid user %d", getuid());
6373 /* set download dir */
6374 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
6376 /* set default string settings */
6377 home = g_strdup("http://www.peereboom.us");
6378 resource_dir = g_strdup("/usr/local/share/xxxterm/");
6379 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
6381 /* read config file */
6382 if (strlen(conf) == 0)
6383 snprintf(conf, sizeof conf, "%s/.%s",
6384 pwd->pw_dir, XT_CONF_FILE);
6385 config_parse(conf, 0);
6387 /* working directory */
6388 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
6389 if (stat(work_dir, &sb)) {
6390 if (mkdir(work_dir, S_IRWXU) == -1)
6391 err(1, "mkdir work_dir");
6392 if (stat(work_dir, &sb))
6393 err(1, "stat work_dir");
6395 if (S_ISDIR(sb.st_mode) == 0)
6396 errx(1, "%s not a dir", work_dir);
6397 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6398 warnx("fixing invalid permissions on %s", work_dir);
6399 if (chmod(work_dir, S_IRWXU) == -1)
6400 err(1, "chmod");
6403 /* icon cache dir */
6404 snprintf(cache_dir, sizeof cache_dir, "%s/%s/%s",
6405 pwd->pw_dir, XT_DIR, XT_CACHE_DIR);
6406 if (stat(cache_dir, &sb)) {
6407 if (mkdir(cache_dir, S_IRWXU) == -1)
6408 err(1, "mkdir cache_dir");
6409 if (stat(cache_dir, &sb))
6410 err(1, "stat cache_dir");
6412 if (S_ISDIR(sb.st_mode) == 0)
6413 errx(1, "%s not a dir", cache_dir);
6414 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6415 warnx("fixing invalid permissions on %s", cache_dir);
6416 if (chmod(cache_dir, S_IRWXU) == -1)
6417 err(1, "chmod");
6420 /* certs dir */
6421 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
6422 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
6423 if (stat(certs_dir, &sb)) {
6424 if (mkdir(certs_dir, S_IRWXU) == -1)
6425 err(1, "mkdir certs_dir");
6426 if (stat(certs_dir, &sb))
6427 err(1, "stat certs_dir");
6429 if (S_ISDIR(sb.st_mode) == 0)
6430 errx(1, "%s not a dir", certs_dir);
6431 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6432 warnx("fixing invalid permissions on %s", certs_dir);
6433 if (chmod(certs_dir, S_IRWXU) == -1)
6434 err(1, "chmod");
6437 /* sessions dir */
6438 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s/%s",
6439 pwd->pw_dir, XT_DIR, XT_SESSIONS_DIR);
6440 if (stat(sessions_dir, &sb)) {
6441 if (mkdir(sessions_dir, S_IRWXU) == -1)
6442 err(1, "mkdir sessions_dir");
6443 if (stat(sessions_dir, &sb))
6444 err(1, "stat sessions_dir");
6446 if (S_ISDIR(sb.st_mode) == 0)
6447 errx(1, "%s not a dir", sessions_dir);
6448 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6449 warnx("fixing invalid permissions on %s", sessions_dir);
6450 if (chmod(sessions_dir, S_IRWXU) == -1)
6451 err(1, "chmod");
6453 /* runtime settings that can override config file */
6454 if (runtime_settings[0] != '\0')
6455 config_parse(runtime_settings, 1);
6457 /* download dir */
6458 if (!strcmp(download_dir, pwd->pw_dir))
6459 strlcat(download_dir, "/downloads", sizeof download_dir);
6460 if (stat(download_dir, &sb)) {
6461 if (mkdir(download_dir, S_IRWXU) == -1)
6462 err(1, "mkdir download_dir");
6463 if (stat(download_dir, &sb))
6464 err(1, "stat download_dir");
6466 if (S_ISDIR(sb.st_mode) == 0)
6467 errx(1, "%s not a dir", download_dir);
6468 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6469 warnx("fixing invalid permissions on %s", download_dir);
6470 if (chmod(download_dir, S_IRWXU) == -1)
6471 err(1, "chmod");
6474 /* favorites file */
6475 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6476 if (stat(file, &sb)) {
6477 warnx("favorites file doesn't exist, creating it");
6478 if ((f = fopen(file, "w")) == NULL)
6479 err(1, "favorites");
6480 fclose(f);
6483 /* cookies */
6484 session = webkit_get_default_session();
6485 setup_cookies();
6487 /* certs */
6488 if (ssl_ca_file) {
6489 if (stat(ssl_ca_file, &sb)) {
6490 warn("no CA file: %s", ssl_ca_file);
6491 g_free(ssl_ca_file);
6492 ssl_ca_file = NULL;
6493 } else
6494 g_object_set(session,
6495 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6496 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6497 (void *)NULL);
6500 /* proxy */
6501 env_proxy = getenv("http_proxy");
6502 if (env_proxy)
6503 setup_proxy(env_proxy);
6504 else
6505 setup_proxy(http_proxy);
6507 /* see if there is already an xxxterm running */
6508 if (single_instance && is_running()) {
6509 optn = 1;
6510 warnx("already running");
6513 if (optn) {
6514 while (argc) {
6515 send_url_to_socket(argv[0]);
6517 argc--;
6518 argv++;
6520 exit(0);
6523 /* go graphical */
6524 create_canvas();
6526 if (save_global_history)
6527 restore_global_history();
6529 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
6530 focus = restore_saved_tabs();
6531 else {
6532 a.s = named_session;
6533 focus = open_tabs(NULL, &a);
6536 while (argc) {
6537 create_new_tab(argv[0], NULL, focus);
6538 focus = 0;
6540 argc--;
6541 argv++;
6543 if (focus == 1)
6544 create_new_tab(home, NULL, 1);
6546 if (enable_socket)
6547 if ((s = build_socket()) != -1)
6548 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6550 gtk_main();
6552 gnutls_global_deinit();
6554 return (0);