add cookie save command and reuse same scaffold as js save
[xxxterm.git] / xxxterm.c
blob8fc37c081c9cdc98c0d70b697c5d748b1a8f79f2
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 * - add favicon
25 * - store in sqlite
26 * multi letter commands
27 * pre and post counts for commands
28 * fav icon
29 * autocompletion on various inputs
30 * create privacy browsing
31 * - encrypted local data
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <err.h>
37 #include <pwd.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <util.h>
41 #include <pthread.h>
42 #include <dlfcn.h>
44 #ifdef __linux__
45 #include "linux/tree.h"
46 #else
47 #include <sys/tree.h>
48 #endif
49 #include <sys/queue.h>
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <sys/socket.h>
53 #include <sys/un.h>
55 #include <gtk/gtk.h>
56 #include <gdk/gdkkeysyms.h>
57 #include <webkit/webkit.h>
58 #include <libsoup/soup.h>
59 #include <gnutls/gnutls.h>
60 #include <JavaScriptCore/JavaScript.h>
61 #include <gnutls/x509.h>
63 #include "javascript.h"
66 javascript.h borrowed from vimprobable2 under the following license:
68 Copyright (c) 2009 Leon Winter
69 Copyright (c) 2009 Hannes Schueller
70 Copyright (c) 2009 Matto Fransen
72 Permission is hereby granted, free of charge, to any person obtaining a copy
73 of this software and associated documentation files (the "Software"), to deal
74 in the Software without restriction, including without limitation the rights
75 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
76 copies of the Software, and to permit persons to whom the Software is
77 furnished to do so, subject to the following conditions:
79 The above copyright notice and this permission notice shall be included in
80 all copies or substantial portions of the Software.
82 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
83 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
84 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
85 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
86 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
87 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
88 THE SOFTWARE.
91 static char *version = "$xxxterm$";
93 /* hooked functions */
94 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
95 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
96 SoupCookie *);
98 /*#define XT_DEBUG*/
99 #ifdef XT_DEBUG
100 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
101 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
102 #define XT_D_MOVE 0x0001
103 #define XT_D_KEY 0x0002
104 #define XT_D_TAB 0x0004
105 #define XT_D_URL 0x0008
106 #define XT_D_CMD 0x0010
107 #define XT_D_NAV 0x0020
108 #define XT_D_DOWNLOAD 0x0040
109 #define XT_D_CONFIG 0x0080
110 #define XT_D_JS 0x0100
111 #define XT_D_FAVORITE 0x0200
112 #define XT_D_PRINTING 0x0400
113 #define XT_D_COOKIE 0x0800
114 u_int32_t swm_debug = 0
115 | XT_D_MOVE
116 | XT_D_KEY
117 | XT_D_TAB
118 | XT_D_URL
119 | XT_D_CMD
120 | XT_D_NAV
121 | XT_D_DOWNLOAD
122 | XT_D_CONFIG
123 | XT_D_JS
124 | XT_D_FAVORITE
125 | XT_D_PRINTING
126 | XT_D_COOKIE
128 #else
129 #define DPRINTF(x...)
130 #define DNPRINTF(n,x...)
131 #endif
133 #define LENGTH(x) (sizeof x / sizeof x[0])
134 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
135 ~(GDK_BUTTON1_MASK) & \
136 ~(GDK_BUTTON2_MASK) & \
137 ~(GDK_BUTTON3_MASK) & \
138 ~(GDK_BUTTON4_MASK) & \
139 ~(GDK_BUTTON5_MASK))
141 char *icons[] = {
142 "xxxtermicon16.png",
143 "xxxtermicon32.png",
144 "xxxtermicon48.png",
145 "xxxtermicon64.png",
146 "xxxtermicon128.png"
149 struct tab {
150 TAILQ_ENTRY(tab) entry;
151 GtkWidget *vbox;
152 GtkWidget *tab_content;
153 GtkWidget *label;
154 GtkWidget *spinner;
155 GtkWidget *uri_entry;
156 GtkWidget *search_entry;
157 GtkWidget *toolbar;
158 GtkWidget *browser_win;
159 GtkWidget *cmd;
160 GtkWidget *backward;
161 GtkWidget *forward;
162 GtkWidget *stop;
163 GtkWidget *js_toggle;
164 guint tab_id;
165 WebKitWebView *wv;
167 /* adjustments for browser */
168 GtkScrollbar *sb_h;
169 GtkScrollbar *sb_v;
170 GtkAdjustment *adjust_h;
171 GtkAdjustment *adjust_v;
173 /* flags */
174 int focus_wv;
175 int ctrl_click;
176 gchar *hover;
177 int xtp_meaning; /* identifies dls/favorites */
179 /* hints */
180 int hints_on;
181 int hint_mode;
182 #define XT_HINT_NONE (0)
183 #define XT_HINT_NUMERICAL (1)
184 #define XT_HINT_ALPHANUM (2)
185 char hint_buf[128];
186 char hint_num[128];
188 /* search */
189 char *search_text;
190 int search_forward;
192 /* settings */
193 WebKitWebSettings *settings;
194 int font_size;
195 gchar *user_agent;
197 TAILQ_HEAD(tab_list, tab);
199 struct history {
200 RB_ENTRY(history) entry;
201 const gchar *uri;
202 const gchar *title;
204 RB_HEAD(history_list, history);
206 struct download {
207 RB_ENTRY(download) entry;
208 int id;
209 WebKitDownload *download;
210 struct tab *tab;
212 RB_HEAD(download_list, download);
214 struct domain {
215 RB_ENTRY(domain) entry;
216 gchar *d;
217 int handy; /* app use */
219 RB_HEAD(domain_list, domain);
221 struct undo {
222 TAILQ_ENTRY(undo) entry;
223 gchar *uri;
224 GList *history;
225 int back; /* Keeps track of how many back
226 * history items there are. */
228 TAILQ_HEAD(undo_tailq, undo);
230 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
231 int next_download_id = 1;
233 struct karg {
234 int i;
235 char *s;
238 /* defines */
239 #define XT_NAME ("XXXTerm")
240 #define XT_DIR (".xxxterm")
241 #define XT_CERT_DIR ("certs/")
242 #define XT_CONF_FILE ("xxxterm.conf")
243 #define XT_FAVS_FILE ("favorites")
244 #define XT_SAVED_TABS_FILE ("saved_tabs")
245 #define XT_RESTART_TABS_FILE ("restart_tabs")
246 #define XT_SOCKET_FILE ("socket")
247 #define XT_HISTORY_FILE ("history")
248 #define XT_CB_HANDLED (TRUE)
249 #define XT_CB_PASSTHROUGH (FALSE)
250 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
251 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
252 #define XT_DLMAN_REFRESH "10"
253 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
254 "td {overflow: hidden;}\n" \
255 "th {background-color: #cccccc}" \
256 "table {width: 90%%; border: 1px black" \
257 " solid; table-layout: fixed}\n</style>\n\n"
258 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
259 #define XT_MAX_UNDO_CLOSE_TAB (32)
261 /* file sizes */
262 #define SZ_KB ((uint64_t) 1024)
263 #define SZ_MB (SZ_KB * SZ_KB)
264 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
265 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
268 * xxxterm "protocol" (xtp)
269 * We use this for managing stuff like downloads and favorites. They
270 * make magical HTML pages in memory which have xxxt:// links in order
271 * to communicate with xxxterm's internals. These links take the format:
272 * xxxt://class/session_key/action/arg
274 * Don't begin xtp class/actions as 0. atoi returns that on error.
276 * Typically we have not put addition of items in this framework, as
277 * adding items is either done via an ex-command or via a keybinding instead.
280 #define XT_XTP_STR "xxxt://"
282 /* XTP classes (xxxt://<class>) */
283 #define XT_XTP_DL 1 /* downloads */
284 #define XT_XTP_HL 2 /* history */
285 #define XT_XTP_CL 3 /* cookies */
286 #define XT_XTP_FL 4 /* favorites */
288 /* XTP download actions */
289 #define XT_XTP_DL_LIST 1
290 #define XT_XTP_DL_CANCEL 2
291 #define XT_XTP_DL_REMOVE 3
293 /* XTP history actions */
294 #define XT_XTP_HL_LIST 1
295 #define XT_XTP_HL_REMOVE 2
297 /* XTP cookie actions */
298 #define XT_XTP_CL_LIST 1
299 #define XT_XTP_CL_REMOVE 2
301 /* XTP cookie actions */
302 #define XT_XTP_FL_LIST 1
303 #define XT_XTP_FL_REMOVE 2
305 /* xtp tab meanings - identifies which tabs have xtp pages in */
306 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
307 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
308 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
309 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
310 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
312 /* actions */
313 #define XT_MOVE_INVALID (0)
314 #define XT_MOVE_DOWN (1)
315 #define XT_MOVE_UP (2)
316 #define XT_MOVE_BOTTOM (3)
317 #define XT_MOVE_TOP (4)
318 #define XT_MOVE_PAGEDOWN (5)
319 #define XT_MOVE_PAGEUP (6)
320 #define XT_MOVE_HALFDOWN (7)
321 #define XT_MOVE_HALFUP (8)
322 #define XT_MOVE_LEFT (9)
323 #define XT_MOVE_FARLEFT (10)
324 #define XT_MOVE_RIGHT (11)
325 #define XT_MOVE_FARRIGHT (12)
327 #define XT_TAB_LAST (-4)
328 #define XT_TAB_FIRST (-3)
329 #define XT_TAB_PREV (-2)
330 #define XT_TAB_NEXT (-1)
331 #define XT_TAB_INVALID (0)
332 #define XT_TAB_NEW (1)
333 #define XT_TAB_DELETE (2)
334 #define XT_TAB_DELQUIT (3)
335 #define XT_TAB_OPEN (4)
336 #define XT_TAB_UNDO_CLOSE (5)
338 #define XT_NAV_INVALID (0)
339 #define XT_NAV_BACK (1)
340 #define XT_NAV_FORWARD (2)
341 #define XT_NAV_RELOAD (3)
342 #define XT_NAV_RELOAD_CACHE (4)
344 #define XT_FOCUS_INVALID (0)
345 #define XT_FOCUS_URI (1)
346 #define XT_FOCUS_SEARCH (2)
348 #define XT_SEARCH_INVALID (0)
349 #define XT_SEARCH_NEXT (1)
350 #define XT_SEARCH_PREV (2)
352 #define XT_PASTE_CURRENT_TAB (0)
353 #define XT_PASTE_NEW_TAB (1)
355 #define XT_FONT_SET (0)
357 #define XT_WL_TOGGLE (1<<0)
358 #define XT_WL_ENABLE (1<<1)
359 #define XT_WL_DISABLE (1<<2)
360 #define XT_WL_FQDN (1<<3) /* default */
361 #define XT_WL_TOPLEVEL (1<<4)
363 #define XT_CMD_OPEN (0)
364 #define XT_CMD_OPEN_CURRENT (1)
365 #define XT_CMD_TABNEW (2)
366 #define XT_CMD_TABNEW_CURRENT (3)
368 /* mime types */
369 struct mime_type {
370 char *mt_type;
371 char *mt_action;
372 int mt_default;
373 TAILQ_ENTRY(mime_type) entry;
375 TAILQ_HEAD(mime_type_list, mime_type);
377 /* uri aliases */
378 struct alias {
379 char *a_name;
380 char *a_uri;
381 TAILQ_ENTRY(alias) entry;
383 TAILQ_HEAD(alias_list, alias);
385 /* settings that require restart */
386 int showtabs = 1; /* show tabs on notebook */
387 int showurl = 1; /* show url toolbar on notebook */
388 int tabless = 0; /* allow only 1 tab */
389 int enable_socket = 0;
390 int single_instance = 0; /* only allow one xxxterm to run */
391 int fancy_bar = 1; /* fancy toolbar */
393 /* runtime settings */
394 int ctrl_click_focus = 0; /* ctrl click gets focus */
395 int cookies_enabled = 1; /* enable cookies */
396 int read_only_cookies = 0; /* enable to not write cookies */
397 int enable_scripts = 0;
398 int enable_plugins = 0;
399 int default_font_size = 12;
400 int window_height = 768;
401 int window_width = 1024;
402 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
403 unsigned refresh_interval = 10; /* download refresh interval */
404 int enable_cookie_whitelist = 1;
405 int enable_js_whitelist = 1;
406 time_t session_timeout = 3600; /* cookie session timeout */
407 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
408 char *ssl_ca_file = NULL;
409 char *resource_dir = NULL;
410 gboolean ssl_strict_certs = FALSE;
411 int append_next = 1; /* append tab after current tab */
412 char *home = NULL;
413 char *search_string = NULL;
414 char *http_proxy = NULL;
415 char download_dir[PATH_MAX];
416 char runtime_settings[PATH_MAX]; /* override of settings */
417 int allow_volatile_cookies = 0;
418 int save_global_history = 0; /* save global history to disk */
420 struct settings;
421 int set_download_dir(struct settings *, char *);
422 int set_runtime_dir(struct settings *, char *);
423 int set_cookie_policy(struct settings *, char *);
424 int add_alias(struct settings *, char *);
425 int add_mime_type(struct settings *, char *);
426 int add_cookie_wl(struct settings *, char *);
427 int add_js_wl(struct settings *, char *);
428 void button_set_stockid(GtkWidget *, char *);
429 GtkWidget * create_button(char *, char *, int);
431 char *get_cookie_policy(struct settings *);
433 char *get_download_dir(struct settings *);
434 char *get_runtime_dir(struct settings *);
436 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
437 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
438 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
439 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
441 struct special {
442 int (*set)(struct settings *, char *);
443 char *(*get)(struct settings *);
444 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
447 struct special s_cookie = {
448 set_cookie_policy,
449 get_cookie_policy,
450 NULL
453 struct special s_alias = {
454 add_alias,
455 NULL,
456 walk_alias
459 struct special s_mime = {
460 add_mime_type,
461 NULL,
462 walk_mime_type
465 struct special s_js = {
466 add_js_wl,
467 NULL,
468 walk_js_wl
471 struct special s_cookie_wl = {
472 add_cookie_wl,
473 NULL,
474 walk_cookie_wl
477 struct special s_download_dir = {
478 set_download_dir,
479 get_download_dir,
480 NULL
483 struct special s_runtime = {
484 set_runtime_dir,
485 get_runtime_dir,
486 NULL
489 struct settings {
490 char *name;
491 int type;
492 #define XT_S_INVALID (0)
493 #define XT_S_INT (1)
494 #define XT_S_STR (2)
495 uint32_t flags;
496 #define XT_SF_RESTART (1<<0)
497 #define XT_SF_RUNTIME (1<<1)
498 int *ival;
499 char **sval;
500 struct special *s;
501 } rs[] = {
502 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
503 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
504 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
505 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
506 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
507 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
508 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
509 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
510 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
511 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
512 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
513 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
514 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
515 { "home", XT_S_STR, 0 , NULL, &home, NULL },
516 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
517 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
518 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
519 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
520 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
521 { "runtime_settings", XT_S_STR, 0 , NULL, NULL, &s_runtime },
522 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
523 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
524 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
525 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
526 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
527 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
528 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
529 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
531 /* runtime settings */
532 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
533 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
534 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
535 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
538 /* globals */
539 extern char *__progname;
540 char **start_argv;
541 struct passwd *pwd;
542 GtkWidget *main_window;
543 GtkNotebook *notebook;
544 GtkWidget *arrow, *abtn;
545 struct tab_list tabs;
546 struct history_list hl;
547 struct download_list downloads;
548 struct domain_list c_wl;
549 struct domain_list js_wl;
550 struct undo_tailq undos;
551 int undo_count;
552 int updating_dl_tabs = 0;
553 int updating_hl_tabs = 0;
554 int updating_cl_tabs = 0;
555 int updating_fl_tabs = 0;
556 char *global_search;
557 uint64_t blocked_cookies = 0;
559 char *
560 get_as_string(struct settings *s)
562 char *r = NULL;
564 if (s == NULL)
565 return (NULL);
567 if (s->s) {
568 if (s->s->get)
569 r = s->s->get(s);
570 else
571 warnx("get_as_string skip %s\n", s->name);
572 } else if (s->type == XT_S_INT)
573 r = g_strdup_printf("%d", *s->ival);
574 else if (s->type == XT_S_STR)
575 r = g_strdup(*s->sval);
576 else
577 r = g_strdup_printf("INVALID TYPE");
579 return (r);
582 void
583 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
585 int i;
586 char *s;
588 for (i = 0; i < LENGTH(rs); i++) {
589 if (rs[i].s && rs[i].s->walk)
590 rs[i].s->walk(&rs[i], cb, cb_args);
591 else {
592 s = get_as_string(&rs[i]);
593 cb(&rs[i], s, cb_args);
594 g_free(s);
600 set_cookie_policy(struct settings *s, char *val)
602 if (!strcmp(val, "no3rdparty"))
603 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
604 else if (!strcmp(val, "accept"))
605 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
606 else if (!strcmp(val, "reject"))
607 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
608 else
609 return (1);
611 return (0);
614 char *
615 get_cookie_policy(struct settings *s)
617 char *r = NULL;
619 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
620 r = g_strdup("no3rdparty");
621 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
622 r = g_strdup("accept");
623 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
624 r = g_strdup("reject");
625 else
626 return (NULL);
628 return (r);
631 char *
632 get_download_dir(struct settings *s)
634 if (download_dir[0] == '\0')
635 return (0);
636 return (g_strdup(download_dir));
640 set_download_dir(struct settings *s, char *val)
642 if (val[0] == '~')
643 snprintf(download_dir, sizeof download_dir, "%s/%s",
644 pwd->pw_dir, &val[1]);
645 else
646 strlcpy(download_dir, val, sizeof download_dir);
648 return (0);
651 char *
652 get_runtime_dir(struct settings *s)
654 if (runtime_settings[0] == '\0')
655 return (0);
656 return (g_strdup(runtime_settings));
660 set_runtime_dir(struct settings *s, char *val)
662 if (val[0] == '~')
663 snprintf(runtime_settings, sizeof runtime_settings, "%s/%s",
664 pwd->pw_dir, &val[1]);
665 else
666 strlcpy(runtime_settings, val, sizeof runtime_settings);
668 return (0);
672 * Session IDs.
673 * We use these to prevent people putting xxxt:// URLs on
674 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
676 #define XT_XTP_SES_KEY_SZ 8
677 #define XT_XTP_SES_KEY_HEX_FMT \
678 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
679 char *dl_session_key; /* downloads */
680 char *hl_session_key; /* history list */
681 char *cl_session_key; /* cookie list */
682 char *fl_session_key; /* favorites list */
684 char work_dir[PATH_MAX];
685 char certs_dir[PATH_MAX];
686 char cookie_file[PATH_MAX];
687 SoupURI *proxy_uri = NULL;
688 SoupSession *session;
689 SoupCookieJar *s_cookiejar;
690 SoupCookieJar *p_cookiejar;
692 struct mime_type_list mtl;
693 struct alias_list aliases;
695 /* protos */
696 void create_new_tab(char *, struct undo *, int);
697 void delete_tab(struct tab *);
698 void adjustfont_webkit(struct tab *, int);
699 int run_script(struct tab *, char *);
700 int download_rb_cmp(struct download *, struct download *);
701 int xtp_page_hl(struct tab *t, struct karg *args);
702 int xtp_page_dl(struct tab *t, struct karg *args);
703 int xtp_page_cl(struct tab *t, struct karg *args);
704 int xtp_page_fl(struct tab *t, struct karg *args);
707 history_rb_cmp(struct history *h1, struct history *h2)
709 return (strcmp(h1->uri, h2->uri));
711 RB_GENERATE(history_list, history, entry, history_rb_cmp);
714 domain_rb_cmp(struct domain *d1, struct domain *d2)
716 return (strcmp(d1->d, d2->d));
718 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
721 * generate a session key to secure xtp commands.
722 * pass in a ptr to the key in question and it will
723 * be modified in place.
725 void
726 generate_xtp_session_key(char **key)
728 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
730 /* free old key */
731 if (*key)
732 g_free(*key);
734 /* make a new one */
735 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
736 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
737 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
738 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
740 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
744 * validate a xtp session key.
745 * return 1 if OK
748 validate_xtp_session_key(char *trusted, char *untrusted)
750 if (strcmp(trusted, untrusted) != 0) {
751 warn("%s: xtp session key mismatch possible spoof", __func__);
752 return (0);
755 return (1);
759 download_rb_cmp(struct download *e1, struct download *e2)
761 return (e1->id < e2->id ? -1 : e1->id > e2->id);
763 RB_GENERATE(download_list, download, entry, download_rb_cmp);
765 struct valid_url_types {
766 char *type;
767 } vut[] = {
768 { "http://" },
769 { "https://" },
770 { "ftp://" },
771 { "file://" },
772 { XT_XTP_STR },
776 valid_url_type(char *url)
778 int i;
780 for (i = 0; i < LENGTH(vut); i++)
781 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
782 return (0);
784 return (1);
787 void
788 print_cookie(char *msg, SoupCookie *c)
790 if (c == NULL)
791 return;
793 if (msg)
794 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
795 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
796 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
797 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
798 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
799 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
800 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
801 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
802 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
803 DNPRINTF(XT_D_COOKIE, "====================================\n");
806 void
807 walk_alias(struct settings *s,
808 void (*cb)(struct settings *, char *, void *), void *cb_args)
810 struct alias *a;
811 char *str;
813 if (s == NULL || cb == NULL)
814 errx(1, "walk_alias");
816 TAILQ_FOREACH(a, &aliases, entry) {
817 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
818 cb(s, str, cb_args);
819 g_free(str);
823 char *
824 match_alias(char *url_in)
826 struct alias *a;
827 char *arg;
828 char *url_out = NULL;
830 arg = url_in;
831 if (strsep(&arg, " \t") == NULL)
832 errx(1, "match_alias: NULL URL");
834 TAILQ_FOREACH(a, &aliases, entry) {
835 if (!strcmp(url_in, a->a_name))
836 break;
839 if (a != NULL) {
840 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
841 a->a_name);
842 if (arg != NULL)
843 url_out = g_strdup_printf(a->a_uri, arg);
844 else
845 url_out = g_strdup(a->a_uri);
848 return (url_out);
851 char *
852 guess_url_type(char *url_in)
854 struct stat sb;
855 char *url_out = NULL;
857 url_out = match_alias(url_in);
858 if (url_out != NULL)
859 return (url_out);
861 /* XXX not sure about this heuristic */
862 if (stat(url_in, &sb) == 0)
863 url_out = g_strdup_printf("file://%s", url_in);
864 else
865 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
867 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
869 return (url_out);
873 add_alias(struct settings *s, char *line)
875 char *l, *alias;
876 struct alias *a;
878 if (line == NULL)
879 errx(1, "add_alias");
880 l = line;
882 a = g_malloc(sizeof(*a));
884 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
885 errx(1, "add_alias: incomplete alias definition");
887 if (strlen(alias) == 0 || strlen(l) == 0)
888 errx(1, "add_alias: invalid alias definition");
890 a->a_name = g_strdup(alias);
891 a->a_uri = g_strdup(l);
893 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
895 TAILQ_INSERT_TAIL(&aliases, a, entry);
897 return (0);
901 add_mime_type(struct settings *s, char *line)
903 char *mime_type;
904 char *l = NULL;
905 struct mime_type *m;
907 /* XXX this could be smarter */
909 if (line == NULL)
910 errx(1, "add_mime_type");
911 l = line;
913 m = g_malloc(sizeof(*m));
915 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
916 errx(1, "add_mime_type: invalid mime_type");
918 if (mime_type[strlen(mime_type) - 1] == '*') {
919 mime_type[strlen(mime_type) - 1] = '\0';
920 m->mt_default = 1;
921 } else
922 m->mt_default = 0;
924 if (strlen(mime_type) == 0 || strlen(l) == 0)
925 errx(1, "add_mime_type: invalid mime_type");
927 m->mt_type = g_strdup(mime_type);
928 m->mt_action = g_strdup(l);
930 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
931 m->mt_type, m->mt_action, m->mt_default);
933 TAILQ_INSERT_TAIL(&mtl, m, entry);
935 return (0);
938 struct mime_type *
939 find_mime_type(char *mime_type)
941 struct mime_type *m, *def = NULL, *rv = NULL;
943 TAILQ_FOREACH(m, &mtl, entry) {
944 if (m->mt_default &&
945 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
946 def = m;
948 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
949 rv = m;
950 break;
954 if (rv == NULL)
955 rv = def;
957 return (rv);
960 void
961 walk_mime_type(struct settings *s,
962 void (*cb)(struct settings *, char *, void *), void *cb_args)
964 struct mime_type *m;
965 char *str;
967 if (s == NULL || cb == NULL)
968 errx(1, "walk_mime_type");
970 TAILQ_FOREACH(m, &mtl, entry) {
971 str = g_strdup_printf("%s%s --> %s",
972 m->mt_type,
973 m->mt_default ? "*" : "",
974 m->mt_action);
975 cb(s, str, cb_args);
976 g_free(str);
980 void
981 wl_add(char *str, struct domain_list *wl, int handy)
983 struct domain *d;
984 int add_dot = 0;
986 if (str == NULL || wl == NULL)
987 return;
988 if (strlen(str) < 2)
989 return;
991 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
993 /* treat *.moo.com the same as .moo.com */
994 if (str[0] == '*' && str[1] == '.')
995 str = &str[1];
996 else if (str[0] == '.')
997 str = &str[0];
998 else
999 add_dot = 1;
1001 d = g_malloc(sizeof *d);
1002 if (add_dot)
1003 d->d = g_strdup_printf(".%s", str);
1004 else
1005 d->d = g_strdup(str);
1006 d->handy = handy;
1008 if (RB_INSERT(domain_list, wl, d))
1009 goto unwind;
1011 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1012 return;
1013 unwind:
1014 if (d) {
1015 if (d->d)
1016 g_free(d->d);
1017 g_free(d);
1022 add_cookie_wl(struct settings *s, char *entry)
1024 wl_add(entry, &c_wl, 1);
1025 return (0);
1028 void
1029 walk_cookie_wl(struct settings *s,
1030 void (*cb)(struct settings *, char *, void *), void *cb_args)
1032 struct domain *d;
1034 if (s == NULL || cb == NULL)
1035 errx(1, "walk_cookie_wl");
1037 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1038 cb(s, d->d, cb_args);
1041 void
1042 walk_js_wl(struct settings *s,
1043 void (*cb)(struct settings *, char *, void *), void *cb_args)
1045 struct domain *d;
1047 if (s == NULL || cb == NULL)
1048 errx(1, "walk_js_wl");
1050 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1051 cb(s, d->d, cb_args);
1055 add_js_wl(struct settings *s, char *entry)
1057 wl_add(entry, &js_wl, 1 /* persistent */);
1058 return (0);
1061 struct domain *
1062 wl_find(const gchar *search, struct domain_list *wl)
1064 int i;
1065 struct domain *d = NULL, dfind;
1066 gchar *s = NULL;
1068 if (search == NULL || wl == NULL)
1069 return (NULL);
1070 if (strlen(search) < 2)
1071 return (NULL);
1073 if (search[0] != '.')
1074 s = g_strdup_printf(".%s", search);
1075 else
1076 s = g_strdup(search);
1078 for (i = strlen(s) - 1; i >= 0; i--) {
1079 if (s[i] == '.') {
1080 dfind.d = &s[i];
1081 d = RB_FIND(domain_list, wl, &dfind);
1082 if (d)
1083 goto done;
1087 done:
1088 if (s)
1089 g_free(s);
1091 return (d);
1094 struct domain *
1095 wl_find_uri(const gchar *s, struct domain_list *wl)
1097 int i;
1098 char *ss;
1099 struct domain *r;
1101 if (s == NULL || wl == NULL)
1102 return (NULL);
1104 if (!strncmp(s, "http://", strlen("http://")))
1105 s = &s[strlen("http://")];
1106 else if (!strncmp(s, "https://", strlen("https://")))
1107 s = &s[strlen("https://")];
1109 if (strlen(s) < 2)
1110 return (NULL);
1112 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1113 /* chop string at first slash */
1114 if (s[i] == '/' || s[i] == '\0') {
1115 ss = g_strdup(s);
1116 ss[i] = '\0';
1117 r = wl_find(ss, wl);
1118 g_free(ss);
1119 return (r);
1122 return (NULL);
1125 char *
1126 get_toplevel_domain(char *domain)
1128 char *s;
1129 int found = 0;
1131 if (domain == NULL)
1132 return (NULL);
1133 if (strlen(domain) < 2)
1134 return (NULL);
1136 s = &domain[strlen(domain) - 1];
1137 while (s != domain) {
1138 if (*s == '.') {
1139 found++;
1140 if (found == 2)
1141 return (s);
1143 s--;
1146 return (NULL);
1149 #define WS "\n= \t"
1150 void
1151 config_parse(char *filename, int runtime)
1153 FILE *config, *f;
1154 char *line, *cp, *var, *val;
1155 size_t len, lineno = 0;
1156 int i, handled, *p;
1157 char **s, file[PATH_MAX];
1158 struct stat sb;
1160 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1162 if (filename == NULL)
1163 return;
1165 if (runtime && runtime_settings[0] != '\0') {
1166 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
1167 if (stat(file, &sb)) {
1168 warnx("runtime file doesn't exist, creating it");
1169 if ((f = fopen(file, "w")) == NULL)
1170 err(1, "runtime");
1171 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1172 fclose(f);
1174 } else
1175 strlcpy(file, filename, sizeof file);
1177 if ((config = fopen(file, "r")) == NULL) {
1178 warn("config_parse: cannot open %s", filename);
1179 return;
1182 for (;;) {
1183 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1184 if (feof(config) || ferror(config))
1185 break;
1187 cp = line;
1188 cp += (long)strspn(cp, WS);
1189 if (cp[0] == '\0') {
1190 /* empty line */
1191 free(line);
1192 continue;
1195 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1196 break;
1198 cp += (long)strspn(cp, WS);
1200 if ((val = strsep(&cp, "\0")) == NULL)
1201 break;
1203 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1205 /* get settings */
1206 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1207 if (strcmp(var, rs[i].name))
1208 continue;
1210 if (rs[i].s) {
1211 if (rs[i].s->set(&rs[i], val))
1212 errx(1, "invalid value for %s", var);
1213 handled = 1;
1214 break;
1215 } else {
1216 switch (rs[i].type) {
1217 case XT_S_INT:
1218 p = rs[i].ival;
1219 *p = atoi(val);
1220 handled = 1;
1221 break;
1222 case XT_S_STR:
1223 s = rs[i].sval;
1224 if (s == NULL)
1225 errx(1, "invalid sval for %s",
1226 rs[i].name);
1227 if (*s)
1228 g_free(*s);
1229 *s = g_strdup(val);
1230 handled = 1;
1231 break;
1232 case XT_S_INVALID:
1233 default:
1234 errx(1, "invalid type for %s", var);
1237 break;
1239 if (handled == 0)
1240 errx(1, "invalid conf file entry: %s=%s", var, val);
1242 free(line);
1245 fclose(config);
1248 char *
1249 js_ref_to_string(JSContextRef context, JSValueRef ref)
1251 char *s = NULL;
1252 size_t l;
1253 JSStringRef jsref;
1255 jsref = JSValueToStringCopy(context, ref, NULL);
1256 if (jsref == NULL)
1257 return (NULL);
1259 l = JSStringGetMaximumUTF8CStringSize(jsref);
1260 s = g_malloc(l);
1261 if (s)
1262 JSStringGetUTF8CString(jsref, s, l);
1263 JSStringRelease(jsref);
1265 return (s);
1268 void
1269 disable_hints(struct tab *t)
1271 bzero(t->hint_buf, sizeof t->hint_buf);
1272 bzero(t->hint_num, sizeof t->hint_num);
1273 run_script(t, "vimprobable_clear()");
1274 t->hints_on = 0;
1275 t->hint_mode = XT_HINT_NONE;
1278 void
1279 enable_hints(struct tab *t)
1281 bzero(t->hint_buf, sizeof t->hint_buf);
1282 run_script(t, "vimprobable_show_hints()");
1283 t->hints_on = 1;
1284 t->hint_mode = XT_HINT_NONE;
1287 #define XT_JS_OPEN ("open;")
1288 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1289 #define XT_JS_FIRE ("fire;")
1290 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1291 #define XT_JS_FOUND ("found;")
1292 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1295 run_script(struct tab *t, char *s)
1297 JSGlobalContextRef ctx;
1298 WebKitWebFrame *frame;
1299 JSStringRef str;
1300 JSValueRef val, exception;
1301 char *es, buf[128];
1303 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1304 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1306 frame = webkit_web_view_get_main_frame(t->wv);
1307 ctx = webkit_web_frame_get_global_context(frame);
1309 str = JSStringCreateWithUTF8CString(s);
1310 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1311 NULL, 0, &exception);
1312 JSStringRelease(str);
1314 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1315 if (val == NULL) {
1316 es = js_ref_to_string(ctx, exception);
1317 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1318 g_free(es);
1319 return (1);
1320 } else {
1321 es = js_ref_to_string(ctx, val);
1322 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1324 /* handle return value right here */
1325 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1326 disable_hints(t);
1327 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1330 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1331 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1332 &es[XT_JS_FIRE_LEN]);
1333 run_script(t, buf);
1334 disable_hints(t);
1337 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1338 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1339 disable_hints(t);
1342 g_free(es);
1345 return (0);
1349 hint(struct tab *t, struct karg *args)
1352 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1354 if (t->hints_on == 0)
1355 enable_hints(t);
1356 else
1357 disable_hints(t);
1359 return (0);
1362 /* Doesn't work fully, due to the following bug:
1363 * https://bugs.webkit.org/show_bug.cgi?id=51747
1366 restore_global_history(void)
1368 char file[PATH_MAX];
1369 FILE *f;
1370 struct history *h;
1371 gchar *uri;
1372 gchar *title;
1374 snprintf(file, sizeof file, "%s/%s/%s",
1375 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1377 if ((f = fopen(file, "r")) == NULL) {
1378 warnx("%s: fopen", __func__);
1379 return (1);
1382 for (;;) {
1383 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1384 if (feof(f) || ferror(f))
1385 break;
1387 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1388 if (feof(f) || ferror(f)) {
1389 free(uri);
1390 warnx("%s: broken history file\n", __func__);
1391 return (1);
1394 if (uri && strlen(uri) && title && strlen(title)) {
1395 webkit_web_history_item_new_with_data(uri, title);
1396 h = g_malloc(sizeof(struct history));
1397 h->uri = g_strdup(uri);
1398 h->title = g_strdup(title);
1399 RB_INSERT(history_list, &hl, h);
1400 } else {
1401 warnx("%s: failed to restore history\n", __func__);
1402 free(uri);
1403 free(title);
1404 return (1);
1407 free(uri);
1408 free(title);
1409 uri = NULL;
1410 title = NULL;
1413 return (0);
1417 save_global_history_to_disk(void)
1419 char file[PATH_MAX];
1420 FILE *f;
1421 struct history *h;
1423 snprintf(file, sizeof file, "%s/%s/%s",
1424 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1426 if ((f = fopen(file, "w")) == NULL) {
1427 warnx("%s: fopen", __func__);
1428 return (1);
1431 RB_FOREACH_REVERSE(h, history_list, &hl) {
1432 if (h->uri && h->title)
1433 fprintf(f, "%s\n%s\n", h->uri, h->title);
1436 fclose(f);
1438 return (0);
1442 quit(struct tab *t, struct karg *args)
1444 if (save_global_history)
1445 save_global_history_to_disk();
1447 gtk_main_quit();
1449 return (1);
1453 restore_saved_tabs(void)
1455 char file[PATH_MAX];
1456 FILE *f;
1457 char *uri = NULL;
1458 int empty_saved_tabs_file = 1;
1459 int unlink_file = 0;
1460 struct stat sb;
1462 snprintf(file, sizeof file, "%s/%s/%s",
1463 pwd->pw_dir, XT_DIR, XT_RESTART_TABS_FILE);
1464 if (stat(file, &sb) == -1)
1465 snprintf(file, sizeof file, "%s/%s/%s",
1466 pwd->pw_dir, XT_DIR, XT_SAVED_TABS_FILE);
1467 else
1468 unlink_file = 1;
1470 if ((f = fopen(file, "r")) == NULL)
1471 return (empty_saved_tabs_file);
1473 for (;;) {
1474 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1475 if (feof(f) || ferror(f))
1476 break;
1478 if (uri && strlen(uri)) {
1479 create_new_tab(uri, NULL, 1);
1480 empty_saved_tabs_file = 0;
1483 free(uri);
1484 uri = NULL;
1487 fclose(f);
1489 if (unlink_file)
1490 unlink(file);
1492 return (empty_saved_tabs_file);
1496 save_tabs(struct tab *t, struct karg *a)
1498 char file[PATH_MAX];
1499 FILE *f;
1500 struct tab *ti;
1501 WebKitWebFrame *frame;
1502 const gchar *uri;
1504 if (a == NULL)
1505 return (1);
1506 if (a->s == NULL)
1507 return (1);
1509 snprintf(file, sizeof file, "%s/%s/%s",
1510 pwd->pw_dir, XT_DIR, a->s);
1512 if ((f = fopen(file, "w")) == NULL) {
1513 warn("save_tabs");
1514 return (1);
1517 TAILQ_FOREACH(ti, &tabs, entry) {
1518 frame = webkit_web_view_get_main_frame(ti->wv);
1519 uri = webkit_web_frame_get_uri(frame);
1520 if (uri && strlen(uri) > 0)
1521 fprintf(f, "%s\n", uri);
1524 fclose(f);
1526 return (0);
1530 save_tabs_and_quit(struct tab *t, struct karg *args)
1532 struct karg a;
1534 a.s = XT_SAVED_TABS_FILE;
1535 save_tabs(t, &a);
1536 quit(t, NULL);
1538 return (1);
1542 yank_uri(struct tab *t, struct karg *args)
1544 WebKitWebFrame *frame;
1545 const gchar *uri;
1546 GtkClipboard *clipboard;
1548 frame = webkit_web_view_get_main_frame(t->wv);
1549 uri = webkit_web_frame_get_uri(frame);
1550 if (!uri)
1551 return (1);
1553 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1554 gtk_clipboard_set_text(clipboard, uri, -1);
1556 return (0);
1559 struct paste_args {
1560 struct tab *t;
1561 int i;
1564 void
1565 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1567 struct paste_args *pap;
1569 if (data == NULL)
1570 return;
1572 pap = (struct paste_args *)data;
1574 switch(pap->i) {
1575 case XT_PASTE_CURRENT_TAB:
1576 webkit_web_view_load_uri(pap->t->wv, text);
1577 break;
1578 case XT_PASTE_NEW_TAB:
1579 create_new_tab((char *)text, NULL, 1);
1580 break;
1583 g_free(pap);
1587 paste_uri(struct tab *t, struct karg *args)
1589 GtkClipboard *clipboard;
1590 struct paste_args *pap;
1592 pap = g_malloc(sizeof(struct paste_args));
1594 pap->t = t;
1595 pap->i = args->i;
1597 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1598 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1600 return (0);
1603 char *
1604 find_domain(const char *s, int add_dot)
1606 int i;
1607 char *r = NULL, *ss = NULL;
1609 if (s == NULL)
1610 return (NULL);
1612 if (!strncmp(s, "http://", strlen("http://")))
1613 s = &s[strlen("http://")];
1614 else if (!strncmp(s, "https://", strlen("https://")))
1615 s = &s[strlen("https://")];
1617 if (strlen(s) < 2)
1618 return (NULL);
1620 ss = g_strdup(s);
1621 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1622 /* chop string at first slash */
1623 if (ss[i] == '/' || ss[i] == '\0') {
1624 ss[i] = '\0';
1625 if (add_dot)
1626 r = g_strdup_printf(".%s", ss);
1627 else
1628 r = g_strdup(ss);
1629 break;
1631 g_free(ss);
1633 return (r);
1637 toggle_cwl(struct tab *t, struct karg *args)
1639 WebKitWebFrame *frame;
1640 struct domain *d;
1641 char *uri;
1642 char *dom = NULL, *dom_toggle = NULL;
1643 int es;
1645 if (args == NULL)
1646 return (0);
1648 frame = webkit_web_view_get_main_frame(t->wv);
1649 uri = (char *)webkit_web_frame_get_uri(frame);
1650 dom = find_domain(uri, 1);
1651 d = wl_find(dom, &c_wl);
1652 if (d == NULL)
1653 es = 0;
1654 else
1655 es = 1;
1657 if (args->i & XT_WL_TOGGLE)
1658 es = !es;
1659 else if ((args->i & XT_WL_ENABLE) && es != 1)
1660 es = 1;
1661 else if ((args->i & XT_WL_DISABLE) && es != 0)
1662 es = 0;
1664 if (args->i & XT_WL_TOPLEVEL)
1665 dom_toggle = get_toplevel_domain(dom);
1666 else
1667 dom_toggle = dom;
1669 if (es) {
1670 /* enable cookies for domain */
1671 wl_add(dom_toggle, &c_wl, 0);
1672 } else {
1673 /* disable cookies for domain */
1674 RB_REMOVE(domain_list, &c_wl, d);
1677 webkit_web_view_reload(t->wv);
1679 g_free(dom);
1680 return (0);
1684 toggle_js(struct tab *t, struct karg *args)
1686 int es;
1687 WebKitWebFrame *frame;
1688 const gchar *uri;
1689 struct domain *d;
1690 char *dom = NULL, *dom_toggle = NULL;
1692 if (args == NULL)
1693 return (0);
1695 g_object_get((GObject *)t->settings,
1696 "enable-scripts", &es, (char *)NULL);
1697 if (args->i & XT_WL_TOGGLE)
1698 es = !es;
1699 else if ((args->i & XT_WL_ENABLE) && es != 1)
1700 es = 1;
1701 else if ((args->i & XT_WL_DISABLE) && es != 0)
1702 es = 0;
1703 else
1704 return (0);
1706 frame = webkit_web_view_get_main_frame(t->wv);
1707 uri = (char *)webkit_web_frame_get_uri(frame);
1708 dom = find_domain(uri, 1);
1709 if (uri == NULL || dom == NULL) {
1710 webkit_web_view_load_string(t->wv,
1711 "<html><body>Can't toggle domain in JavaScript white list</body></html>",
1712 NULL,
1713 NULL,
1714 NULL);
1715 goto done;
1718 if (args->i & XT_WL_TOPLEVEL)
1719 dom_toggle = get_toplevel_domain(dom);
1720 else
1721 dom_toggle = dom;
1723 if (es) {
1724 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1725 wl_add(dom_toggle, &js_wl, 0 /* session */);
1726 } else {
1727 d = wl_find(dom_toggle, &js_wl);
1728 if (d)
1729 RB_REMOVE(domain_list, &js_wl, d);
1730 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1732 g_object_set((GObject *)t->settings,
1733 "enable-scripts", es, (char *)NULL);
1734 webkit_web_view_set_settings(t->wv, t->settings);
1735 webkit_web_view_reload(t->wv);
1736 done:
1737 if (dom)
1738 g_free(dom);
1739 return (0);
1742 void
1743 js_toggle_cb(GtkWidget *w, struct tab *t)
1745 struct karg a;
1747 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1748 toggle_js(t, &a);
1752 toggle_src(struct tab *t, struct karg *args)
1754 gboolean mode;
1756 if (t == NULL)
1757 return (0);
1759 mode = webkit_web_view_get_view_source_mode(t->wv);
1760 webkit_web_view_set_view_source_mode(t->wv, !mode);
1761 webkit_web_view_reload(t->wv);
1763 return (0);
1767 focus(struct tab *t, struct karg *args)
1769 if (t == NULL || args == NULL)
1770 errx(1, "focus");
1772 if (args->i == XT_FOCUS_URI)
1773 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1774 else if (args->i == XT_FOCUS_SEARCH)
1775 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1777 return (0);
1781 stats(struct tab *t, struct karg *args)
1783 char *stats;
1785 if (t == NULL)
1786 errx(1, "stats");
1788 stats = g_strdup_printf(XT_DOCTYPE
1789 "<html>"
1790 "<head>"
1791 "<title>Statistics</title>"
1792 "</head>"
1793 "<h1>Statistics</h1>"
1794 "<body>"
1795 "Cookies blocked(*) this session: %llu\n"
1796 "<p><small><b>*</b> results vary based on settings"
1797 "</body>"
1798 "</html>",
1799 blocked_cookies
1802 webkit_web_view_load_string(t->wv, stats, NULL, NULL, "");
1803 g_free(stats);
1805 return (0);
1809 about(struct tab *t, struct karg *args)
1811 char *about;
1813 if (t == NULL)
1814 errx(1, "about");
1816 about = g_strdup_printf(XT_DOCTYPE
1817 "<html>"
1818 "<head>"
1819 "<title>About</title>"
1820 "</head>"
1821 "<h1>About</h1>"
1822 "<body>"
1823 "<b>Version: %s</b><p>"
1824 "Authors:"
1825 "<ul>"
1826 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1827 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1828 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1829 "</ul>"
1830 "Copyrights and licenses can be found on the XXXterm "
1831 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1832 "</body>"
1833 "</html>",
1834 version
1837 webkit_web_view_load_string(t->wv, about, NULL, NULL, "");
1838 g_free(about);
1840 return (0);
1844 help(struct tab *t, struct karg *args)
1846 char *help;
1848 if (t == NULL)
1849 errx(1, "help");
1851 help = XT_DOCTYPE
1852 "<html>"
1853 "<head>"
1854 "<title>XXXterm</title>"
1855 "<meta http-equiv=\"REFRESH\" content=\"0;"
1856 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
1857 "</head>"
1858 "<body>"
1859 "XXXterm man page <a href=\"http://opensource.conformal.com/"
1860 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
1861 "cgi-bin/man-cgi?xxxterm</a>"
1862 "</body>"
1863 "</html>"
1866 webkit_web_view_load_string(t->wv, help, NULL, NULL, "");
1868 return (0);
1872 * update all favorite tabs apart from one. Pass NULL if
1873 * you want to update all.
1875 void
1876 update_favorite_tabs(struct tab *apart_from)
1878 struct tab *t;
1879 if (!updating_fl_tabs) {
1880 updating_fl_tabs = 1; /* stop infinite recursion */
1881 TAILQ_FOREACH(t, &tabs, entry)
1882 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1883 && (t != apart_from))
1884 xtp_page_fl(t, NULL);
1885 updating_fl_tabs = 0;
1889 /* show a list of favorites (bookmarks) */
1891 xtp_page_fl(struct tab *t, struct karg *args)
1893 char file[PATH_MAX];
1894 FILE *f;
1895 char *uri = NULL, *title = NULL;
1896 size_t len, lineno = 0;
1897 int i, failed = 0;
1898 char *header, *body, *tmp, *html = NULL;
1900 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1902 if (t == NULL)
1903 warn("%s: bad param", __func__);
1905 /* mark tab as favorite list */
1906 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
1908 /* new session key */
1909 if (!updating_fl_tabs)
1910 generate_xtp_session_key(&fl_session_key);
1912 /* open favorites */
1913 snprintf(file, sizeof file, "%s/%s/%s",
1914 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
1915 if ((f = fopen(file, "r")) == NULL) {
1916 warn("favorites");
1917 return (1);
1920 /* header */
1921 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
1922 "<title>Favorites</title>\n"
1923 "%s"
1924 "</head>"
1925 "<h1>Favorites</h1>\n",
1926 XT_PAGE_STYLE);
1928 /* body */
1929 body = g_strdup_printf("<div align='center'><table><tr>"
1930 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
1931 "<th style='width: 15%%'>Remove</th></tr>\n");
1933 for (i = 1;;) {
1934 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
1935 if (feof(f) || ferror(f))
1936 break;
1937 if (len == 0) {
1938 free(title);
1939 title = NULL;
1940 continue;
1943 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
1944 if (feof(f) || ferror(f)) {
1945 errx(0, "%s: can't parse favorites\n",
1946 __func__);
1947 failed = 1;
1948 break;
1951 tmp = body;
1952 body = g_strdup_printf("%s<tr>"
1953 "<td>%d</td>"
1954 "<td><a href='%s'>%s</a></td>"
1955 "<td style='text-align: center'>"
1956 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1957 "</tr>\n",
1958 body, i, uri, title,
1959 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
1961 g_free(tmp);
1963 free(uri);
1964 uri = NULL;
1965 free(title);
1966 title = NULL;
1967 i++;
1969 fclose(f);
1971 /* if none, say so */
1972 if (i == 1) {
1973 tmp = body;
1974 body = g_strdup_printf("%s<tr>"
1975 "<td colspan='3' style='text-align: center'>"
1976 "No favorites - To add one use the 'favadd' command."
1977 "</td></tr>", body);
1978 g_free(tmp);
1981 if (uri)
1982 free(uri);
1983 if (title)
1984 free(title);
1986 /* render */
1987 if (!failed) {
1988 html = g_strdup_printf("%s%s</table></div></html>",
1989 header, body);
1990 webkit_web_view_load_string(t->wv, html, NULL, NULL, "");
1993 update_favorite_tabs(t);
1995 if (header)
1996 g_free(header);
1997 if (body)
1998 g_free(body);
1999 if (html)
2000 g_free(html);
2002 return (failed);
2005 char *
2006 getparams(char *cmd, char *cmp)
2008 char *rv = NULL;
2010 if (cmd && cmp) {
2011 if (!strncmp(cmd, cmp, strlen(cmp))) {
2012 rv = cmd + strlen(cmp);
2013 while (*rv == ' ')
2014 rv++;
2015 if (strlen(rv) == 0)
2016 rv = NULL;
2020 return (rv);
2023 void
2024 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2025 size_t cert_count, char *title)
2027 gnutls_datum_t cinfo;
2028 char *tmp, *header, *body, *footer;
2029 int i;
2031 header = g_strdup_printf("<title>%s</title><html><body>", title);
2032 footer = g_strdup("</body></html>");
2033 body = g_strdup("");
2035 for (i = 0; i < cert_count; i++) {
2036 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2037 &cinfo))
2038 return;
2040 tmp = body;
2041 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2042 body, i, cinfo.data);
2043 gnutls_free(cinfo.data);
2044 g_free(tmp);
2047 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2048 g_free(header);
2049 g_free(body);
2050 g_free(footer);
2051 webkit_web_view_load_string(t->wv, tmp, NULL, NULL, NULL);
2052 g_free(tmp);
2056 ca_cmd(struct tab *t, struct karg *args)
2058 FILE *f = NULL;
2059 int rv = 1, certs = 0, certs_read;
2060 struct stat sb;
2061 gnutls_datum dt;
2062 gnutls_x509_crt_t *c = NULL;
2063 char *certs_buf = NULL, *s;
2065 /* yeah yeah stat race */
2066 if (stat(ssl_ca_file, &sb)) {
2067 warn("no CA file: %s", ssl_ca_file);
2068 goto done;
2071 if ((f = fopen(ssl_ca_file, "r")) == NULL)
2072 return (1);
2074 certs_buf = g_malloc(sb.st_size + 1);
2075 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2076 warn("certs");
2077 goto done;
2079 certs_buf[sb.st_size] = '\0';
2081 s = certs_buf;
2082 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2083 certs++;
2084 s += strlen("BEGIN CERTIFICATE");
2087 bzero(&dt, sizeof dt);
2088 dt.data = certs_buf;
2089 dt.size = sb.st_size;
2090 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2091 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2092 if (certs_read <= 0) {
2093 warnx("couldn't read certs");
2094 goto done;
2096 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2097 done:
2098 if (c)
2099 g_free(c);
2100 if (certs_buf)
2101 g_free(certs_buf);
2102 if (f)
2103 fclose(f);
2105 return (rv);
2109 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2111 SoupURI *su = NULL;
2112 struct addrinfo hints, *res = NULL, *ai;
2113 int s = -1, on;
2114 char port[8];
2116 if (uri && !g_str_has_prefix(uri, "https://"))
2117 goto done;
2119 su = soup_uri_new(uri);
2120 if (su == NULL)
2121 goto done;
2122 if (!SOUP_URI_VALID_FOR_HTTP(su))
2123 goto done;
2125 snprintf(port, sizeof port, "%d", su->port);
2126 bzero(&hints, sizeof(struct addrinfo));
2127 hints.ai_flags = AI_CANONNAME;
2128 hints.ai_family = AF_UNSPEC;
2129 hints.ai_socktype = SOCK_STREAM;
2131 if (getaddrinfo(su->host, port, &hints, &res))
2132 goto done;
2134 for (ai = res; ai; ai = ai->ai_next) {
2135 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2136 continue;
2138 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2139 if (s < 0)
2140 goto done;
2141 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2142 sizeof(on)) == -1)
2143 goto done;
2145 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2146 goto done;
2149 if (domain)
2150 strlcpy(domain, su->host, domain_sz);
2151 done:
2152 if (su)
2153 soup_uri_free(su);
2154 if (res)
2155 freeaddrinfo(res);
2157 return (s);
2161 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2163 if (gsession)
2164 gnutls_deinit(gsession);
2165 if (xcred)
2166 gnutls_certificate_free_credentials(xcred);
2168 return (0);
2172 start_tls(int s, gnutls_session_t *gs, gnutls_certificate_credentials_t *xc)
2174 gnutls_certificate_credentials_t xcred;
2175 gnutls_session_t gsession;
2176 int rv = 1;
2178 if (gs == NULL || xc == NULL)
2179 goto done;
2181 gnutls_certificate_allocate_credentials(&xcred);
2182 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2183 GNUTLS_X509_FMT_PEM);
2184 gnutls_init(&gsession, GNUTLS_CLIENT);
2185 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2186 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2187 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2188 if ((rv = gnutls_handshake(gsession)) < 0) {
2189 warnx("gnutls_handshake failed %d", rv);
2190 stop_tls(gsession, xcred);
2191 goto done;
2194 gnutls_credentials_type_t cred;
2195 cred = gnutls_auth_get_type(gsession);
2196 if (cred != GNUTLS_CRD_CERTIFICATE) {
2197 warnx("invalid credential type");
2198 stop_tls(gsession, xcred);
2199 goto done;
2202 *gs = gsession;
2203 *xc = xcred;
2204 rv = 0;
2205 done:
2206 return (rv);
2210 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2211 size_t *cert_count)
2213 unsigned int len;
2214 const gnutls_datum_t *cl;
2215 gnutls_x509_crt_t *all_certs;
2216 int i, rv = 1;
2218 if (certs == NULL || cert_count == NULL)
2219 goto done;
2220 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2221 goto done;
2222 cl = gnutls_certificate_get_peers(gsession, &len);
2223 if (len == 0)
2224 goto done;
2226 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2227 for (i = 0; i < len; i++) {
2228 gnutls_x509_crt_init(&all_certs[i]);
2229 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2230 GNUTLS_X509_FMT_PEM < 0)) {
2231 g_free(all_certs);
2232 goto done;
2236 *certs = all_certs;
2237 *cert_count = len;
2238 rv = 0;
2239 done:
2240 return (rv);
2243 void
2244 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2246 int i;
2248 for (i = 0; i < cert_count; i++)
2249 gnutls_x509_crt_deinit(certs[i]);
2250 g_free(certs);
2253 void
2254 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2255 size_t cert_count, char *domain)
2257 size_t cert_buf_sz;
2258 char cert_buf[64 * 1024], file[PATH_MAX];
2259 int i;
2260 FILE *f;
2261 GdkColor color;
2263 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2264 return;
2266 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2267 if ((f = fopen(file, "w")) == NULL) {
2268 warn("save_certs");
2269 return;
2272 for (i = 0; i < cert_count; i++) {
2273 cert_buf_sz = sizeof cert_buf;
2274 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2275 cert_buf, &cert_buf_sz)) {
2276 warnx("gnutls_x509_crt_export");
2277 goto done;
2279 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2280 warn("fwrite certs");
2281 goto done;
2285 /* not the best spot but oh well */
2286 gdk_color_parse("lightblue", &color);
2287 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2288 done:
2289 fclose(f);
2293 load_compare_cert(struct tab *t, struct karg *args)
2295 WebKitWebFrame *frame;
2296 char *uri, domain[8182], file[PATH_MAX];
2297 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2298 int s = -1, rv = 1, i;
2299 size_t cert_count;
2300 FILE *f = NULL;
2301 size_t cert_buf_sz;
2302 gnutls_session_t gsession;
2303 gnutls_x509_crt_t *certs;
2304 gnutls_certificate_credentials_t xcred;
2306 if (t == NULL)
2307 return (1);
2309 frame = webkit_web_view_get_main_frame(t->wv);
2310 uri = (char *)webkit_web_frame_get_uri(frame);
2311 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2312 return (1);
2314 /* go ssl/tls */
2315 if (start_tls(s, &gsession, &xcred)) {
2316 warnx("start_tls");
2317 goto done;
2320 /* get certs */
2321 if (get_connection_certs(gsession, &certs, &cert_count)) {
2322 warnx("get_connection_certs");
2323 goto done;
2326 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2327 if ((f = fopen(file, "r")) == NULL)
2328 goto freeit;
2330 for (i = 0; i < cert_count; i++) {
2331 cert_buf_sz = sizeof cert_buf;
2332 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2333 cert_buf, &cert_buf_sz)) {
2334 warnx("gnutls_x509_crt_export");
2335 goto freeit;
2337 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2338 warn("fread certs");
2339 rv = -1; /* critical */
2340 goto freeit;
2342 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2343 warnx("invalid cert");
2344 rv = -1; /* critical */
2345 goto freeit;
2349 rv = 0;
2350 freeit:
2351 if (f)
2352 fclose(f);
2353 free_connection_certs(certs, cert_count);
2354 done:
2355 /* we close the socket first for speed */
2356 if (s != -1)
2357 close(s);
2358 stop_tls(gsession, xcred);
2360 return (rv);
2364 cert_cmd(struct tab *t, struct karg *args)
2366 WebKitWebFrame *frame;
2367 char *uri, *action, domain[8182];
2368 int s = -1;
2369 size_t cert_count;
2370 gnutls_session_t gsession;
2371 gnutls_x509_crt_t *certs;
2372 gnutls_certificate_credentials_t xcred;
2374 if (t == NULL)
2375 return (1);
2377 if ((action = getparams(args->s, "cert")))
2379 else
2380 action = "show";
2382 frame = webkit_web_view_get_main_frame(t->wv);
2383 uri = (char *)webkit_web_frame_get_uri(frame);
2384 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2385 return (1);
2387 /* go ssl/tls */
2388 if (start_tls(s, &gsession, &xcred)) {
2389 warnx("start_tls");
2390 goto done;
2393 /* get certs */
2394 if (get_connection_certs(gsession, &certs, &cert_count)) {
2395 warnx("get_connection_certs");
2396 goto done;
2399 if (!strcmp(action, "show"))
2400 show_certs(t, certs, cert_count, "Certificate Chain");
2401 else if (!strcmp(action, "save"))
2402 save_certs(t, certs, cert_count, domain);
2404 free_connection_certs(certs, cert_count);
2405 done:
2406 /* we close the socket first for speed */
2407 if (s != -1)
2408 close(s);
2409 stop_tls(gsession, xcred);
2411 return (0);
2415 remove_cookie(int index)
2417 int i, rv = 1;
2418 GSList *cf;
2419 SoupCookie *c;
2421 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2423 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2425 for (i = 1; cf; cf = cf->next, i++) {
2426 if (i != index)
2427 continue;
2428 c = cf->data;
2429 print_cookie("remove cookie", c);
2430 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2431 rv = 0;
2432 break;
2435 soup_cookies_free(cf);
2437 return (rv);
2441 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2443 struct domain *d;
2444 char *tmp, *header, *body, *footer;
2445 int p_js = 0, s_js = 0;
2447 if (g_str_has_prefix(args, "show a") ||
2448 !strcmp(args, "show")) {
2449 /* show all */
2450 p_js = 1;
2451 s_js = 1;
2452 } else if (g_str_has_prefix(args, "show p")) {
2453 /* show persistent */
2454 p_js = 1;
2455 } else if (g_str_has_prefix(args, "show s")) {
2456 /* show session */
2457 s_js = 1;
2458 } else
2459 return (1);
2461 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2462 title, title);
2463 footer = g_strdup("</body></html>");
2464 body = g_strdup("");
2466 /* p list */
2467 if (p_js) {
2468 tmp = body;
2469 body = g_strdup_printf("%s<h2>Persitent</h2>", body);
2470 g_free(tmp);
2471 RB_FOREACH_REVERSE(d, domain_list, wl) {
2472 if (d->handy == 0)
2473 continue;
2474 tmp = body;
2475 body = g_strdup_printf("%s%s<br>", body, d->d);
2476 g_free(tmp);
2480 /* s list */
2481 if (s_js) {
2482 tmp = body;
2483 body = g_strdup_printf("%s<h2>Session</h2>", body);
2484 g_free(tmp);
2485 RB_FOREACH_REVERSE(d, domain_list, wl) {
2486 if (d->handy == 1)
2487 continue;
2488 tmp = body;
2489 body = g_strdup_printf("%s%s", body, d->d);
2490 g_free(tmp);
2494 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2495 g_free(header);
2496 g_free(body);
2497 g_free(footer);
2498 webkit_web_view_load_string(t->wv, tmp, NULL, NULL, NULL);
2499 g_free(tmp);
2500 return (0);
2504 wl_save(struct tab *t, struct karg *args, int js)
2506 char file[PATH_MAX];
2507 FILE *f;
2508 char *line = NULL, *lt = NULL;
2509 size_t linelen;
2510 WebKitWebFrame *frame;
2511 char *dom = NULL, *uri, *dom_save = NULL;
2512 struct karg a;
2513 struct domain *d;
2514 GSList *cf;
2515 SoupCookie *ci, *c;
2516 int flags;
2518 if (t == NULL || args == NULL)
2519 return (1);
2521 if (runtime_settings[0] == '\0')
2522 return (1);
2524 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2525 if ((f = fopen(file, "r+")) == NULL)
2526 return (1);
2528 frame = webkit_web_view_get_main_frame(t->wv);
2529 uri = (char *)webkit_web_frame_get_uri(frame);
2530 dom = find_domain(uri, 1);
2531 if (uri == NULL || dom == NULL) {
2532 webkit_web_view_load_string(t->wv,
2533 "<html><body>Can't add domain to JavaScript white list</body></html>",
2534 NULL,
2535 NULL,
2536 NULL);
2537 goto done;
2540 if (g_str_has_prefix(args->s, "save d")) {
2541 /* save domain */
2542 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2543 /* XXX this needs use feedback */
2544 warnx("bad bad bad");
2545 goto done;
2547 flags = XT_WL_TOPLEVEL;
2548 } else if (g_str_has_prefix(args->s, "save f") ||
2549 !strcmp(args->s, "save")) {
2550 /* save fqdn */
2551 dom_save = dom;
2552 flags = XT_WL_FQDN;
2553 } else {
2554 /* XXX this needs use feedback */
2555 warnx("invalid command");
2556 goto done;
2559 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2561 while (!feof(f)) {
2562 line = fparseln(f, &linelen, NULL, NULL, 0);
2563 if (line == NULL)
2564 continue;
2565 if (!strcmp(line, lt))
2566 goto done;
2567 free(line);
2568 line = NULL;
2571 fprintf(f, "%s\n", lt);
2573 a.i = XT_WL_ENABLE;
2574 a.i |= flags;
2575 if (js) {
2576 d = wl_find(dom_save, &js_wl);
2577 toggle_js(t, &a);
2578 } else {
2579 d = wl_find(dom_save, &c_wl);
2580 toggle_cwl(t, &a);
2582 /* find and add to persistent jar */
2583 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2584 for (;cf; cf = cf->next) {
2585 ci = cf->data;
2586 if (!strcmp(dom_save, ci->domain) ||
2587 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2588 c = soup_cookie_copy(ci);
2589 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2592 soup_cookies_free(cf);
2594 if (d)
2595 d->handy = 1;
2597 done:
2598 if (line)
2599 free(line);
2600 if (dom)
2601 g_free(dom);
2602 if (lt)
2603 g_free(lt);
2604 fclose(f);
2606 return (0);
2610 cookie_cmd(struct tab *t, struct karg *args)
2612 char *cmd;
2613 struct karg a;
2615 if ((cmd = getparams(args->s, "cookie")))
2617 else
2618 cmd = "show all";
2621 if (g_str_has_prefix(cmd, "show")) {
2622 wl_show(t, cmd, "Cookie White List", &c_wl);
2623 } else if (g_str_has_prefix(cmd, "save")) {
2624 a.s = cmd;
2625 wl_save(t, &a, 0);
2626 } else if (g_str_has_prefix(cmd, "toggle")) {
2627 a.i = XT_WL_TOGGLE;
2628 if (g_str_has_prefix(cmd, "toggle d"))
2629 a.i |= XT_WL_TOPLEVEL;
2630 else
2631 a.i |= XT_WL_FQDN;
2632 //toggle_js(t, &a);
2633 } else if (g_str_has_prefix(cmd, "delete")) {
2636 return (0);
2640 js_cmd(struct tab *t, struct karg *args)
2642 char *cmd;
2643 struct karg a;
2645 if ((cmd = getparams(args->s, "js")))
2647 else
2648 cmd = "show all";
2651 if (g_str_has_prefix(cmd, "show")) {
2652 wl_show(t, cmd, "JavaScript White List", &js_wl);
2653 } else if (g_str_has_prefix(cmd, "save")) {
2654 a.s = cmd;
2655 wl_save(t, &a, 1);
2656 } else if (g_str_has_prefix(cmd, "toggle")) {
2657 a.i = XT_WL_TOGGLE;
2658 if (g_str_has_prefix(cmd, "toggle d"))
2659 a.i |= XT_WL_TOPLEVEL;
2660 else
2661 a.i |= XT_WL_FQDN;
2662 toggle_js(t, &a);
2663 } else if (g_str_has_prefix(cmd, "delete")) {
2666 return (0);
2670 add_favorite(struct tab *t, struct karg *args)
2672 char file[PATH_MAX];
2673 FILE *f;
2674 char *line = NULL;
2675 size_t urilen, linelen;
2676 WebKitWebFrame *frame;
2677 const gchar *uri, *title;
2679 if (t == NULL)
2680 return (1);
2682 /* don't allow adding of xtp pages to favorites */
2683 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2684 warn("%s: can't add xtp pages to favorites", __func__);
2685 return (1);
2688 snprintf(file, sizeof file, "%s/%s/%s",
2689 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2690 if ((f = fopen(file, "r+")) == NULL) {
2691 warn("favorites");
2692 return (1);
2695 title = webkit_web_view_get_title(t->wv);
2696 frame = webkit_web_view_get_main_frame(t->wv);
2697 uri = webkit_web_frame_get_uri(frame);
2698 if (title == NULL)
2699 title = uri;
2701 if (title == NULL || uri == NULL) {
2702 webkit_web_view_load_string(t->wv,
2703 "<html><body>can't add page to favorites</body></html>",
2704 NULL,
2705 NULL,
2706 NULL);
2707 goto done;
2710 urilen = strlen(uri);
2712 while (!feof(f)) {
2713 line = fparseln(f, &linelen, NULL, NULL, 0);
2714 if (linelen == urilen && !strcmp(line, uri))
2715 goto done;
2716 free(line);
2717 line = NULL;
2720 fprintf(f, "\n%s\n%s", title, uri);
2721 done:
2722 if (line)
2723 free(line);
2724 fclose(f);
2726 update_favorite_tabs(NULL);
2728 return (0);
2732 navaction(struct tab *t, struct karg *args)
2734 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2735 t->tab_id, args->i);
2737 switch (args->i) {
2738 case XT_NAV_BACK:
2739 webkit_web_view_go_back(t->wv);
2740 break;
2741 case XT_NAV_FORWARD:
2742 webkit_web_view_go_forward(t->wv);
2743 break;
2744 case XT_NAV_RELOAD:
2745 webkit_web_view_reload(t->wv);
2746 break;
2747 case XT_NAV_RELOAD_CACHE:
2748 webkit_web_view_reload_bypass_cache(t->wv);
2749 break;
2751 return (XT_CB_PASSTHROUGH);
2755 move(struct tab *t, struct karg *args)
2757 GtkAdjustment *adjust;
2758 double pi, si, pos, ps, upper, lower, max;
2760 switch (args->i) {
2761 case XT_MOVE_DOWN:
2762 case XT_MOVE_UP:
2763 case XT_MOVE_BOTTOM:
2764 case XT_MOVE_TOP:
2765 case XT_MOVE_PAGEDOWN:
2766 case XT_MOVE_PAGEUP:
2767 case XT_MOVE_HALFDOWN:
2768 case XT_MOVE_HALFUP:
2769 adjust = t->adjust_v;
2770 break;
2771 default:
2772 adjust = t->adjust_h;
2773 break;
2776 pos = gtk_adjustment_get_value(adjust);
2777 ps = gtk_adjustment_get_page_size(adjust);
2778 upper = gtk_adjustment_get_upper(adjust);
2779 lower = gtk_adjustment_get_lower(adjust);
2780 si = gtk_adjustment_get_step_increment(adjust);
2781 pi = gtk_adjustment_get_page_increment(adjust);
2782 max = upper - ps;
2784 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2785 "max %f si %f pi %f\n",
2786 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2787 pos, ps, upper, lower, max, si, pi);
2789 switch (args->i) {
2790 case XT_MOVE_DOWN:
2791 case XT_MOVE_RIGHT:
2792 pos += si;
2793 gtk_adjustment_set_value(adjust, MIN(pos, max));
2794 break;
2795 case XT_MOVE_UP:
2796 case XT_MOVE_LEFT:
2797 pos -= si;
2798 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2799 break;
2800 case XT_MOVE_BOTTOM:
2801 case XT_MOVE_FARRIGHT:
2802 gtk_adjustment_set_value(adjust, max);
2803 break;
2804 case XT_MOVE_TOP:
2805 case XT_MOVE_FARLEFT:
2806 gtk_adjustment_set_value(adjust, lower);
2807 break;
2808 case XT_MOVE_PAGEDOWN:
2809 pos += pi;
2810 gtk_adjustment_set_value(adjust, MIN(pos, max));
2811 break;
2812 case XT_MOVE_PAGEUP:
2813 pos -= pi;
2814 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2815 break;
2816 case XT_MOVE_HALFDOWN:
2817 pos += pi / 2;
2818 gtk_adjustment_set_value(adjust, MIN(pos, max));
2819 break;
2820 case XT_MOVE_HALFUP:
2821 pos -= pi / 2;
2822 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2823 break;
2824 default:
2825 return (XT_CB_PASSTHROUGH);
2828 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
2830 return (XT_CB_HANDLED);
2834 tabaction(struct tab *t, struct karg *args)
2836 int rv = XT_CB_HANDLED;
2837 char *url = NULL, *newuri = NULL;
2838 struct undo *u;
2840 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
2842 if (t == NULL)
2843 return (XT_CB_PASSTHROUGH);
2845 switch (args->i) {
2846 case XT_TAB_NEW:
2847 if ((url = getparams(args->s, "tabnew")))
2848 create_new_tab(url, NULL, 1);
2849 else
2850 create_new_tab(NULL, NULL, 1);
2851 break;
2852 case XT_TAB_DELETE:
2853 delete_tab(t);
2854 break;
2855 case XT_TAB_DELQUIT:
2856 if (gtk_notebook_get_n_pages(notebook) > 1)
2857 delete_tab(t);
2858 else
2859 quit(t, args);
2860 break;
2861 case XT_TAB_OPEN:
2862 if ((url = getparams(args->s, "open")) ||
2863 ((url = getparams(args->s, "op"))) ||
2864 ((url = getparams(args->s, "o"))))
2866 else {
2867 rv = XT_CB_PASSTHROUGH;
2868 goto done;
2871 if (valid_url_type(url)) {
2872 newuri = guess_url_type(url);
2873 url = newuri;
2875 webkit_web_view_load_uri(t->wv, url);
2876 if (newuri)
2877 g_free(newuri);
2878 break;
2879 case XT_TAB_UNDO_CLOSE:
2880 if (undo_count == 0) {
2881 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
2882 goto done;
2883 } else {
2884 undo_count--;
2885 u = TAILQ_FIRST(&undos);
2886 create_new_tab(u->uri, u, 1);
2888 TAILQ_REMOVE(&undos, u, entry);
2889 g_free(u->uri);
2890 /* u->history is freed in create_new_tab() */
2891 g_free(u);
2893 break;
2894 default:
2895 rv = XT_CB_PASSTHROUGH;
2896 goto done;
2899 done:
2900 if (args->s) {
2901 g_free(args->s);
2902 args->s = NULL;
2905 return (rv);
2909 resizetab(struct tab *t, struct karg *args)
2911 if (t == NULL || args == NULL)
2912 errx(1, "resizetab");
2914 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
2915 t->tab_id, args->i);
2917 adjustfont_webkit(t, args->i);
2919 return (XT_CB_HANDLED);
2923 movetab(struct tab *t, struct karg *args)
2925 struct tab *tt;
2926 int x;
2928 if (t == NULL || args == NULL)
2929 errx(1, "movetab");
2931 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
2932 t->tab_id, args->i);
2934 if (args->i == XT_TAB_INVALID)
2935 return (XT_CB_PASSTHROUGH);
2937 if (args->i < XT_TAB_INVALID) {
2938 /* next or previous tab */
2939 if (TAILQ_EMPTY(&tabs))
2940 return (XT_CB_PASSTHROUGH);
2942 switch (args->i) {
2943 case XT_TAB_NEXT:
2944 /* if at the last page, loop around to the first */
2945 if (gtk_notebook_get_current_page(notebook) ==
2946 gtk_notebook_get_n_pages(notebook) - 1) {
2947 gtk_notebook_set_current_page(notebook, 0);
2948 } else {
2949 gtk_notebook_next_page(notebook);
2951 break;
2952 case XT_TAB_PREV:
2953 /* if at the first page, loop around to the last */
2954 if (gtk_notebook_current_page(notebook) == 0) {
2955 gtk_notebook_set_current_page(notebook,
2956 gtk_notebook_get_n_pages(notebook) - 1);
2957 } else {
2958 gtk_notebook_prev_page(notebook);
2960 break;
2961 case XT_TAB_FIRST:
2962 gtk_notebook_set_current_page(notebook, 0);
2963 break;
2964 case XT_TAB_LAST:
2965 gtk_notebook_set_current_page(notebook, -1);
2966 break;
2967 default:
2968 return (XT_CB_PASSTHROUGH);
2971 return (XT_CB_HANDLED);
2974 /* jump to tab */
2975 x = args->i - 1;
2976 if (t->tab_id == x) {
2977 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
2978 return (XT_CB_HANDLED);
2981 TAILQ_FOREACH(tt, &tabs, entry) {
2982 if (tt->tab_id == x) {
2983 gtk_notebook_set_current_page(notebook, x);
2984 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
2985 if (tt->focus_wv)
2986 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
2990 return (XT_CB_HANDLED);
2994 command(struct tab *t, struct karg *args)
2996 WebKitWebFrame *frame;
2997 char *s = NULL, *ss = NULL;
2998 GdkColor color;
2999 const gchar *uri;
3001 if (t == NULL || args == NULL)
3002 errx(1, "command");
3004 switch (args->i) {
3005 case '/':
3006 s = "/";
3007 break;
3008 case '?':
3009 s = "?";
3010 break;
3011 case ':':
3012 s = ":";
3013 break;
3014 case XT_CMD_OPEN:
3015 s = ":open ";
3016 break;
3017 case XT_CMD_TABNEW:
3018 s = ":tabnew ";
3019 break;
3020 case XT_CMD_OPEN_CURRENT:
3021 s = ":open ";
3022 /* FALL THROUGH */
3023 case XT_CMD_TABNEW_CURRENT:
3024 if (!s) /* FALL THROUGH? */
3025 s = ":tabnew ";
3026 frame = webkit_web_view_get_main_frame(t->wv);
3027 uri = webkit_web_frame_get_uri(frame);
3028 if (uri && strlen(uri)) {
3029 ss = g_strdup_printf("%s%s", s, uri);
3030 s = ss;
3032 break;
3033 default:
3034 warnx("command: invalid command %c\n", args->i);
3035 return (XT_CB_PASSTHROUGH);
3038 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3040 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3041 gdk_color_parse("white", &color);
3042 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3043 gtk_widget_show(t->cmd);
3044 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3045 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3047 if (ss)
3048 g_free(ss);
3050 return (XT_CB_HANDLED);
3054 * Return a new string with a download row (in html)
3055 * appended. Old string is freed.
3057 char *
3058 xtp_page_dl_row(char *html, struct download *dl)
3061 WebKitDownloadStatus stat;
3062 char *status_html = NULL, *cmd_html = NULL, *new_html;
3063 gdouble progress;
3064 char cur_sz[FMT_SCALED_STRSIZE];
3065 char tot_sz[FMT_SCALED_STRSIZE];
3066 char *xtp_prefix;
3068 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3070 /* All actions wil take this form:
3071 * xxxt://class/seskey
3073 xtp_prefix = g_strdup_printf("%s%d/%s/",
3074 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3076 stat = webkit_download_get_status(dl->download);
3078 switch (stat) {
3079 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3080 status_html = g_strdup_printf("Finished");
3081 cmd_html = g_strdup_printf(
3082 "<a href='%s%d/%d'>Remove</a>",
3083 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3084 break;
3085 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3086 /* gather size info */
3087 progress = 100 * webkit_download_get_progress(dl->download);
3089 fmt_scaled(
3090 webkit_download_get_current_size(dl->download), cur_sz);
3091 fmt_scaled(
3092 webkit_download_get_total_size(dl->download), tot_sz);
3094 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3095 tot_sz, progress);
3096 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3097 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3099 break;
3100 /* LLL */
3101 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3102 status_html = g_strdup_printf("Cancelled");
3103 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3104 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3105 break;
3106 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3107 status_html = g_strdup_printf("Error!");
3108 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3109 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3110 break;
3111 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3112 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3113 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3114 status_html = g_strdup_printf("Starting");
3115 break;
3116 default:
3117 warn("%s: unknown download status", __func__);
3120 new_html = g_strdup_printf(
3121 "%s\n<tr><td>%s</td><td>%s</td>"
3122 "<td style='text-align:center'>%s</td></tr>\n",
3123 html, webkit_download_get_uri(dl->download),
3124 status_html, cmd_html);
3125 g_free(html);
3127 if (status_html)
3128 g_free(status_html);
3130 if (cmd_html)
3131 g_free(cmd_html);
3133 g_free(xtp_prefix);
3135 return new_html;
3139 * update all download tabs apart from one. Pass NULL if
3140 * you want to update all.
3142 void
3143 update_download_tabs(struct tab *apart_from)
3145 struct tab *t;
3146 if (!updating_dl_tabs) {
3147 updating_dl_tabs = 1; /* stop infinite recursion */
3148 TAILQ_FOREACH(t, &tabs, entry)
3149 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3150 && (t != apart_from))
3151 xtp_page_dl(t, NULL);
3152 updating_dl_tabs = 0;
3157 * update all cookie tabs apart from one. Pass NULL if
3158 * you want to update all.
3160 void
3161 update_cookie_tabs(struct tab *apart_from)
3163 struct tab *t;
3164 if (!updating_cl_tabs) {
3165 updating_cl_tabs = 1; /* stop infinite recursion */
3166 TAILQ_FOREACH(t, &tabs, entry)
3167 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3168 && (t != apart_from))
3169 xtp_page_cl(t, NULL);
3170 updating_cl_tabs = 0;
3175 * update all history tabs apart from one. Pass NULL if
3176 * you want to update all.
3178 void
3179 update_history_tabs(struct tab *apart_from)
3181 struct tab *t;
3183 if (!updating_hl_tabs) {
3184 updating_hl_tabs = 1; /* stop infinite recursion */
3185 TAILQ_FOREACH(t, &tabs, entry)
3186 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3187 && (t != apart_from))
3188 xtp_page_hl(t, NULL);
3189 updating_hl_tabs = 0;
3193 /* cookie management XTP page */
3195 xtp_page_cl(struct tab *t, struct karg *args)
3197 char *header, *body, *footer, *page, *tmp;
3198 int i = 1; /* all ids start 1 */
3199 GSList *cf;
3200 SoupCookie *c;
3202 DNPRINTF(XT_D_CMD, "%s", __func__);
3204 if (t == NULL)
3205 errx(1, "%s: null tab", __func__);
3207 /* mark this tab as cookie jar */
3208 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3210 /* Generate a new session key */
3211 if (!updating_cl_tabs)
3212 generate_xtp_session_key(&cl_session_key);
3214 /* header */
3215 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3216 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3217 "</head><body><h1>Cookie Jar</h1>\n");
3219 /* body */
3220 body = g_strdup_printf("<div align='center'><table><tr>"
3221 "<th>Name</th>"
3222 "<th>Value</th>"
3223 "<th>Domain</th>"
3224 "<th>Path</th>"
3225 "<th>Expires</th>"
3226 "<th>Secure</th>"
3227 "<th>HTTP_only</th>"
3228 "<th>Remove</th></tr>\n");
3230 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3232 for (; cf; cf = cf->next) {
3233 c = cf->data;
3235 tmp = body;
3236 body = g_strdup_printf(
3237 "%s\n<tr>"
3238 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3239 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3240 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3241 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3242 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3243 "<td style='width: 3%%; text-align: center'>%d</td>"
3244 "<td style='width: 3%%; text-align: center'>%d</td>"
3245 "<td style='width: 3%%; text-align: center'>"
3246 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3247 body,
3248 c->name,
3249 c->value,
3250 c->domain,
3251 c->path,
3252 c->expires ?
3253 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3254 c->secure,
3255 c->http_only,
3257 XT_XTP_STR,
3258 XT_XTP_CL,
3259 cl_session_key,
3260 XT_XTP_CL_REMOVE,
3264 g_free(tmp);
3265 i++;
3267 soup_cookies_free(cf);
3269 /* small message if there are none */
3270 if (i == 1) {
3271 tmp = body;
3272 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3273 "colspan='8'>No Cookies</td></tr>\n", body);
3274 g_free(tmp);
3277 /* footer */
3278 footer = g_strdup_printf("</table></div></body></html>");
3280 page = g_strdup_printf("%s%s%s", header, body, footer);
3282 g_free(header);
3283 g_free(body);
3284 g_free(footer);
3286 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3287 update_cookie_tabs(t);
3289 g_free(page);
3291 return (0);
3295 xtp_page_hl(struct tab *t, struct karg *args)
3297 char *header, *body, *footer, *page, *tmp;
3298 struct history *h;
3299 int i = 1; /* all ids start 1 */
3301 DNPRINTF(XT_D_CMD, "%s", __func__);
3303 if (t == NULL)
3304 errx(1, "%s: null tab", __func__);
3306 /* mark this tab as history manager */
3307 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3309 /* Generate a new session key */
3310 if (!updating_hl_tabs)
3311 generate_xtp_session_key(&hl_session_key);
3313 /* header */
3314 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3315 "<title>History</title>\n"
3316 "%s"
3317 "</head>"
3318 "<h1>History</h1>\n",
3319 XT_PAGE_STYLE);
3321 /* body */
3322 body = g_strdup_printf("<div align='center'><table><tr>"
3323 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3325 RB_FOREACH_REVERSE(h, history_list, &hl) {
3326 tmp = body;
3327 body = g_strdup_printf(
3328 "%s\n<tr>"
3329 "<td><a href='%s'>%s</a></td>"
3330 "<td>%s</td>"
3331 "<td style='text-align: center'>"
3332 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3333 body, h->uri, h->uri, h->title,
3334 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3335 XT_XTP_HL_REMOVE, i);
3337 g_free(tmp);
3338 i++;
3341 /* small message if there are none */
3342 if (i == 1) {
3343 tmp = body;
3344 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3345 "colspan='3'>No History</td></tr>\n", body);
3346 g_free(tmp);
3349 /* footer */
3350 footer = g_strdup_printf("</table></div></body></html>");
3352 page = g_strdup_printf("%s%s%s", header, body, footer);
3355 * update all history manager tabs as the xtp session
3356 * key has now changed. No need to update the current tab.
3357 * Already did that above.
3359 update_history_tabs(t);
3361 g_free(header);
3362 g_free(body);
3363 g_free(footer);
3365 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3366 g_free(page);
3368 return (0);
3372 * Generate a web page detailing the status of any downloads
3375 xtp_page_dl(struct tab *t, struct karg *args)
3377 struct download *dl;
3378 char *header, *body, *footer, *page, *tmp;
3379 char *ref;
3380 int n_dl = 1;
3382 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3384 if (t == NULL)
3385 errx(1, "%s: null tab", __func__);
3387 /* mark as a download manager tab */
3388 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3391 * Generate a new session key for next page instance.
3392 * This only happens for the top level call to xtp_page_dl()
3393 * in which case updating_dl_tabs is 0.
3395 if (!updating_dl_tabs)
3396 generate_xtp_session_key(&dl_session_key);
3398 /* header - with refresh so as to update */
3399 if (refresh_interval >= 1)
3400 ref = g_strdup_printf(
3401 "<meta http-equiv='refresh' content='%u"
3402 ";url=%s%d/%s/%d' />\n",
3403 refresh_interval,
3404 XT_XTP_STR,
3405 XT_XTP_DL,
3406 dl_session_key,
3407 XT_XTP_DL_LIST);
3408 else
3409 ref = g_strdup("");
3412 header = g_strdup_printf(
3413 "%s\n<head>"
3414 "<title>Downloads</title>\n%s%s</head>\n",
3415 XT_DOCTYPE XT_HTML_TAG,
3416 ref,
3417 XT_PAGE_STYLE);
3419 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3420 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3421 "</p><table><tr><th style='width: 60%%'>"
3422 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3423 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3425 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3426 body = xtp_page_dl_row(body, dl);
3427 n_dl++;
3430 /* message if no downloads in list */
3431 if (n_dl == 1) {
3432 tmp = body;
3433 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3434 " style='text-align: center'>"
3435 "No downloads</td></tr>\n", body);
3436 g_free(tmp);
3439 /* footer */
3440 footer = g_strdup_printf("</table></div></body></html>");
3442 page = g_strdup_printf("%s%s%s", header, body, footer);
3446 * update all download manager tabs as the xtp session
3447 * key has now changed. No need to update the current tab.
3448 * Already did that above.
3450 update_download_tabs(t);
3452 g_free(ref);
3453 g_free(header);
3454 g_free(body);
3455 g_free(footer);
3457 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3458 g_free(page);
3460 return (0);
3464 search(struct tab *t, struct karg *args)
3466 gboolean d;
3468 if (t == NULL || args == NULL)
3469 errx(1, "search");
3470 if (t->search_text == NULL) {
3471 if (global_search == NULL)
3472 return (XT_CB_PASSTHROUGH);
3473 else {
3474 t->search_text = g_strdup(global_search);
3475 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3476 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3480 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3481 t->tab_id, args->i, t->search_forward, t->search_text);
3483 switch (args->i) {
3484 case XT_SEARCH_NEXT:
3485 d = t->search_forward;
3486 break;
3487 case XT_SEARCH_PREV:
3488 d = !t->search_forward;
3489 break;
3490 default:
3491 return (XT_CB_PASSTHROUGH);
3494 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3496 return (XT_CB_HANDLED);
3499 struct settings_args {
3500 char **body;
3501 int i;
3504 void
3505 print_setting(struct settings *s, char *val, void *cb_args)
3507 char *tmp, *color;
3508 struct settings_args *sa = cb_args;
3510 if (sa == NULL) {
3511 warnx("*** %s", s->name);
3512 return;
3515 if (s->flags & XT_SF_RUNTIME)
3516 color = "#22cc22";
3517 else
3518 color = "#cccccc";
3520 tmp = *sa->body;
3521 *sa->body = g_strdup_printf(
3522 "%s\n<tr>"
3523 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3524 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3525 *sa->body,
3526 color,
3527 s->name,
3528 color,
3531 g_free(tmp);
3532 sa->i++;
3536 set(struct tab *t, struct karg *args)
3538 char *header, *body, *footer, *page, *tmp, *pars;
3539 int i = 1;
3540 struct settings_args sa;
3542 if ((pars = getparams(args->s, "set")) == NULL) {
3543 bzero(&sa, sizeof sa);
3544 sa.body = &body;
3546 /* header */
3547 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3548 "\n<head><title>Settings</title>\n"
3549 "</head><body><h1>Settings</h1>\n");
3551 /* body */
3552 body = g_strdup_printf("<div align='center'><table><tr>"
3553 "<th align='left'>Setting</th>"
3554 "<th align='left'>Value</th></tr>\n");
3556 settings_walk(print_setting, &sa);
3557 i = sa.i;
3559 /* small message if there are none */
3560 if (i == 1) {
3561 tmp = body;
3562 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3563 "colspan='2'>No settings</td></tr>\n", body);
3564 g_free(tmp);
3567 /* footer */
3568 footer = g_strdup_printf("</table></div></body></html>");
3570 page = g_strdup_printf("%s%s%s", header, body, footer);
3572 g_free(header);
3573 g_free(body);
3574 g_free(footer);
3576 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3577 } else {
3578 fprintf(stderr, "pars %s\n", pars);
3581 return (XT_CB_PASSTHROUGH);
3585 * Make a hardcopy of the page
3588 print_page(struct tab *t, struct karg *args)
3590 WebKitWebFrame *frame;
3592 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3595 * for now we just call the GTK print box,
3596 * but later we might decide to hook in a command.
3598 frame = webkit_web_view_get_main_frame(t->wv);
3599 webkit_web_frame_print(frame);
3601 return (0);
3605 go_home(struct tab *t, struct karg *args)
3607 char *newuri;
3609 newuri = guess_url_type((char *)home);
3610 webkit_web_view_load_uri(t->wv, newuri);
3611 free(newuri);
3613 return (0);
3617 restart(struct tab *t, struct karg *args)
3619 struct karg a;
3621 a.s = XT_RESTART_TABS_FILE;
3622 save_tabs(t, &a);
3623 execvp(start_argv[0], start_argv);
3624 /* NOTREACHED */
3626 return (0);
3629 /* inherent to GTK not all keys will be caught at all times */
3630 /* XXX sort key bindings */
3631 struct key_bindings {
3632 guint mask;
3633 guint use_in_entry;
3634 guint key;
3635 int (*func)(struct tab *, struct karg *);
3636 struct karg arg;
3637 } keys[] = {
3638 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
3639 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
3640 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
3641 { 0, 0, GDK_slash, command, {.i = '/'} },
3642 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
3643 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
3644 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
3645 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
3646 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3647 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3648 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
3649 { 0, 0, GDK_y, yank_uri, {0} },
3650 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
3651 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
3653 /* search */
3654 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
3655 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
3657 /* focus */
3658 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
3659 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
3661 /* command aliases (handy when -S flag is used) */
3662 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
3663 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
3664 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
3665 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
3667 /* hinting */
3668 { 0, 0, GDK_f, hint, {.i = 0} },
3670 /* navigation */
3671 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
3672 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
3673 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
3674 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
3675 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
3676 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
3677 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
3678 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
3679 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
3681 /* vertical movement */
3682 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
3683 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
3684 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
3685 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
3686 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
3687 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
3688 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
3689 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
3690 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
3691 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
3692 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
3693 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
3694 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
3695 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
3696 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
3697 /* horizontal movement */
3698 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
3699 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
3700 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
3701 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
3702 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
3703 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
3705 /* tabs */
3706 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
3707 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
3708 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
3709 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
3710 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
3711 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
3712 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
3713 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
3714 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
3715 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
3716 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
3717 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
3718 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
3719 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
3720 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
3721 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
3722 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
3723 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
3724 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
3725 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
3728 struct cmd {
3729 char *cmd;
3730 int params;
3731 int (*func)(struct tab *, struct karg *);
3732 struct karg arg;
3733 } cmds[] = {
3734 { "q!", 0, quit, {0} },
3735 { "qa", 0, quit, {0} },
3736 { "qa!", 0, quit, {0} },
3737 { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} },
3738 { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3739 { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3740 { "help", 0, help, {0} },
3741 { "about", 0, about, {0} },
3742 { "stats", 0, stats, {0} },
3743 { "version", 0, about, {0} },
3744 { "cookies", 0, xtp_page_cl, {0} },
3745 { "fav", 0, xtp_page_fl, {0} },
3746 { "favadd", 0, add_favorite, {0} },
3747 { "js", 2, js_cmd, {0} },
3748 { "cookie", 2, cookie_cmd, {0} },
3749 { "cert", 1, cert_cmd, {0} },
3750 { "ca", 0, ca_cmd, {0} },
3751 { "dl" , 0, xtp_page_dl, {0} },
3752 { "h" , 0, xtp_page_hl, {0} },
3753 { "hist" , 0, xtp_page_hl, {0} },
3754 { "history" , 0, xtp_page_hl, {0} },
3755 { "home" , 0, go_home, {0} },
3756 { "restart" , 0, restart, {0} },
3758 { "1", 0, move, {.i = XT_MOVE_TOP} },
3759 { "print", 0, print_page, {0} },
3761 /* tabs */
3762 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
3763 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
3764 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
3765 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
3766 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
3767 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
3768 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
3769 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
3770 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3771 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3772 /* XXX add count to these commands */
3773 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
3774 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
3775 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
3776 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
3777 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
3778 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
3779 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
3780 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
3781 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
3782 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
3784 /* settings */
3785 { "set", 1, set, {0} },
3788 gboolean
3789 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
3791 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
3793 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
3794 delete_tab(t);
3796 return (FALSE);
3800 * cancel, remove, etc. downloads
3802 void
3803 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
3805 struct download find, *d;
3807 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
3809 /* some commands require a valid download id */
3810 if (cmd != XT_XTP_DL_LIST) {
3811 /* lookup download in question */
3812 find.id = id;
3813 d = RB_FIND(download_list, &downloads, &find);
3815 if (d == NULL) {
3816 warn("%s: no such download", __func__);
3817 return;
3821 /* decide what to do */
3822 switch (cmd) {
3823 case XT_XTP_DL_CANCEL:
3824 webkit_download_cancel(d->download);
3825 break;
3826 case XT_XTP_DL_REMOVE:
3827 webkit_download_cancel(d->download); /* just incase */
3828 g_object_unref(d->download);
3829 RB_REMOVE(download_list, &downloads, d);
3830 break;
3831 case XT_XTP_DL_LIST:
3832 /* Nothing */
3833 break;
3834 default:
3835 warn("%s: unknown command", __func__);
3836 break;
3838 xtp_page_dl(t, NULL);
3842 * Actions on history, only does one thing for now, but
3843 * we provide the function for future actions
3845 void
3846 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
3848 struct history *h, *next;
3849 int i = 1;
3851 switch (cmd) {
3852 case XT_XTP_HL_REMOVE:
3853 /* walk backwards, as listed in reverse */
3854 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
3855 next = RB_PREV(history_list, &hl, h);
3856 if (id == i) {
3857 RB_REMOVE(history_list, &hl, h);
3858 g_free((gpointer) h->title);
3859 g_free((gpointer) h->uri);
3860 g_free(h);
3861 break;
3863 i++;
3865 break;
3866 case XT_XTP_HL_LIST:
3867 /* Nothing - just xtp_page_hl() below */
3868 break;
3869 default:
3870 warn("%s: unknown command", __func__);
3871 break;
3874 xtp_page_hl(t, NULL);
3877 /* remove a favorite */
3878 void
3879 remove_favorite(int index)
3881 char file[PATH_MAX], *title, *uri;
3882 char *new_favs, *tmp;
3883 FILE *f;
3884 int i;
3885 size_t len, lineno;
3887 /* open favorites */
3888 snprintf(file, sizeof file, "%s/%s/%s",
3889 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
3891 if ((f = fopen(file, "r")) == NULL) {
3892 warn("%s: can't open favorites", __func__);
3893 return;
3896 /* build a string which will become the new favroites file */
3897 new_favs = g_strdup_printf("%s", "");
3899 for (i = 1;;) {
3900 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
3901 if (feof(f) || ferror(f))
3902 break;
3903 if (len == 0) {
3904 free(title);
3905 title = NULL;
3906 continue;
3909 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
3910 if (feof(f) || ferror(f)) {
3911 warn("%s: can't parse favorites", __func__);
3912 goto clean;
3916 /* as long as this isn't the one we are deleting add to file */
3917 if (i != index) {
3918 tmp = new_favs;
3919 new_favs = g_strdup_printf("%s%s\n%s\n",
3920 new_favs, title, uri);
3921 g_free(tmp);
3924 free(uri);
3925 uri = NULL;
3926 free(title);
3927 title = NULL;
3928 i++;
3930 fclose(f);
3932 /* write back new favorites file */
3933 if ((f = fopen(file, "w")) == NULL) {
3934 warn("%s: can't open favorites", __func__);
3935 goto clean;
3938 fwrite(new_favs, strlen(new_favs), 1, f);
3939 fclose(f);
3941 clean:
3942 if (uri)
3943 free(uri);
3944 if (title)
3945 free(title);
3947 g_free(new_favs);
3950 void
3951 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
3953 switch (cmd) {
3954 case XT_XTP_FL_LIST:
3955 /* nothing, just the below call to xtp_page_fl() */
3956 break;
3957 case XT_XTP_FL_REMOVE:
3958 remove_favorite(arg);
3959 break;
3960 default:
3961 warn("%s: invalid favorites command", __func__);
3962 break;
3965 xtp_page_fl(t, NULL);
3968 void
3969 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
3971 switch (cmd) {
3972 case XT_XTP_CL_LIST:
3973 /* nothing, just xtp_page_cl() */
3974 break;
3975 case XT_XTP_CL_REMOVE:
3976 remove_cookie(arg);
3977 break;
3978 default:
3979 warn("%s: unknown cookie xtp command", __func__);
3980 break;
3983 xtp_page_cl(t, NULL);
3986 /* link an XTP class to it's session key and handler function */
3987 struct xtp_despatch {
3988 uint8_t xtp_class;
3989 char **session_key;
3990 void (*handle_func)(struct tab *, uint8_t, int);
3993 struct xtp_despatch xtp_despatches[] = {
3994 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
3995 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
3996 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
3997 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
3998 { NULL, NULL, NULL }
4002 * is the url xtp protocol? (xxxt://)
4003 * if so, parse and despatch correct bahvior
4006 parse_xtp_url(struct tab *t, const char *url)
4008 char *dup = NULL, *p, *last;
4009 uint8_t n_tokens = 0;
4010 char *tokens[4] = {NULL, NULL, NULL, ""};
4011 struct xtp_despatch *dsp, *dsp_match = NULL;
4012 uint8_t req_class;
4015 * tokens array meaning:
4016 * tokens[0] = class
4017 * tokens[1] = session key
4018 * tokens[2] = action
4019 * tokens[3] = optional argument
4022 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4024 /*xtp tab meaning is normal unless proven special */
4025 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4027 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4028 return 0;
4030 dup = g_strdup(url + strlen(XT_XTP_STR));
4032 /* split out the url */
4033 for ((p = strtok_r(dup, "/", &last)); p;
4034 (p = strtok_r(NULL, "/", &last))) {
4035 if (n_tokens < 4)
4036 tokens[n_tokens++] = p;
4039 /* should be atleast three fields 'class/seskey/command/arg' */
4040 if (n_tokens < 3)
4041 goto clean;
4043 dsp = xtp_despatches;
4044 req_class = atoi(tokens[0]);
4045 while (dsp->xtp_class != NULL) {
4046 if (dsp->xtp_class == req_class) {
4047 dsp_match = dsp;
4048 break;
4050 dsp++;
4053 /* did we find one atall? */
4054 if (dsp_match == NULL) {
4055 warn("%s: no matching xtp despatch found", __func__);
4056 goto clean;
4059 /* check session key and call despatch function */
4060 if (validate_xtp_session_key(*(dsp_match->session_key), tokens[1])) {
4061 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4064 clean:
4065 if (dup)
4066 g_free(dup);
4068 return 1;
4073 void
4074 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4076 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4077 char *newuri = NULL;
4079 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4081 if (t == NULL)
4082 errx(1, "activate_uri_entry_cb");
4084 if (uri == NULL)
4085 errx(1, "uri");
4087 uri += strspn(uri, "\t ");
4089 /* if xxxt:// treat specially */
4090 if (!parse_xtp_url(t, uri)) {
4091 if (valid_url_type((char *)uri)) {
4092 newuri = guess_url_type((char *)uri);
4093 uri = newuri;
4096 webkit_web_view_load_uri(t->wv, uri);
4097 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4100 if (newuri)
4101 g_free(newuri);
4104 void
4105 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4107 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4108 char *newuri = NULL;
4110 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4112 if (t == NULL)
4113 errx(1, "activate_search_entry_cb");
4115 if (search_string == NULL) {
4116 warnx("no search_string");
4117 return;
4120 newuri = g_strdup_printf(search_string, search);
4122 webkit_web_view_load_uri(t->wv, newuri);
4123 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4125 if (newuri)
4126 g_free(newuri);
4129 void
4130 check_and_set_js(gchar *uri, struct tab *t)
4132 struct domain *d = NULL;
4133 int es = 0;
4135 if (uri == NULL || t == NULL)
4136 return;
4138 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4139 es = 0;
4140 else
4141 es = 1;
4143 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4144 es ? "enable" : "disable", uri);
4146 g_object_set((GObject *)t->settings,
4147 "enable-scripts", es, (char *)NULL);
4148 webkit_web_view_set_settings(t->wv, t->settings);
4150 button_set_stockid(t->js_toggle,
4151 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4154 void
4155 show_ca_status(struct tab *t, const char *uri)
4157 WebKitWebFrame *frame;
4158 WebKitWebDataSource *source;
4159 WebKitNetworkRequest *request;
4160 SoupMessage *message;
4161 GdkColor color;
4162 gchar *col_str = "white";
4163 int r;
4165 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4166 ssl_strict_certs, ssl_ca_file, uri);
4168 if (uri == NULL)
4169 goto done;
4170 if (ssl_ca_file == NULL) {
4171 if (g_str_has_prefix(uri, "http://"))
4172 goto done;
4173 if (g_str_has_prefix(uri, "https://")) {
4174 col_str = "red";
4175 goto done;
4177 return;
4179 if (g_str_has_prefix(uri, "http://") ||
4180 !g_str_has_prefix(uri, "https://"))
4181 goto done;
4183 frame = webkit_web_view_get_main_frame(t->wv);
4184 source = webkit_web_frame_get_data_source(frame);
4185 request = webkit_web_data_source_get_request(source);
4186 message = webkit_network_request_get_message(request);
4188 if (message && (soup_message_get_flags(message) &
4189 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4190 col_str = "green";
4191 goto done;
4192 } else {
4193 r = load_compare_cert(t, NULL);
4194 if (r == 0)
4195 col_str = "lightblue";
4196 else if (r == 1)
4197 col_str = "yellow";
4198 else
4199 col_str = "red";
4200 goto done;
4202 done:
4203 if (col_str) {
4204 gdk_color_parse(col_str, &color);
4205 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4209 void
4210 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4212 WebKitWebFrame *frame;
4213 const gchar *set = NULL, *uri = NULL, *title = NULL;
4214 struct history *h, find;
4215 int add = 0;
4216 const gchar *s_loading;
4218 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4219 webkit_web_view_get_load_status(wview));
4221 if (t == NULL)
4222 errx(1, "notify_load_status_cb");
4224 switch (webkit_web_view_get_load_status(wview)) {
4225 case WEBKIT_LOAD_PROVISIONAL:
4226 #if GTK_CHECK_VERSION(2, 20, 0)
4227 gtk_widget_show(t->spinner);
4228 gtk_spinner_start(GTK_SPINNER(t->spinner));
4229 #endif
4230 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4232 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4233 t->focus_wv = 1;
4235 /* take focus if we are visible */
4236 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4237 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4239 break;
4241 case WEBKIT_LOAD_COMMITTED:
4242 frame = webkit_web_view_get_main_frame(wview);
4243 uri = webkit_web_frame_get_uri(frame);
4244 if (uri)
4245 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4247 /* check if js white listing is enabled */
4248 if (enable_js_whitelist) {
4249 frame = webkit_web_view_get_main_frame(wview);
4250 uri = webkit_web_frame_get_uri(frame);
4251 check_and_set_js((gchar *)uri, t);
4254 show_ca_status(t, uri);
4255 break;
4257 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4258 title = webkit_web_view_get_title(wview);
4259 frame = webkit_web_view_get_main_frame(wview);
4260 uri = webkit_web_frame_get_uri(frame);
4261 if (title)
4262 set = title;
4263 else if (uri)
4264 set = uri;
4265 else
4266 break;
4268 gtk_label_set_text(GTK_LABEL(t->label), set);
4269 gtk_window_set_title(GTK_WINDOW(main_window), set);
4271 if (uri) {
4272 if (!strncmp(uri, "http://", strlen("http://")) ||
4273 !strncmp(uri, "https://", strlen("https://")) ||
4274 !strncmp(uri, "file://", strlen("file://")))
4275 add = 1;
4276 if (add == 0)
4277 break;
4278 find.uri = uri;
4279 h = RB_FIND(history_list, &hl, &find);
4280 if (h)
4281 break;
4283 h = g_malloc(sizeof *h);
4284 h->uri = g_strdup(uri);
4285 if (title)
4286 h->title = g_strdup(title);
4287 else
4288 h->title = g_strdup(uri);
4289 RB_INSERT(history_list, &hl, h);
4290 update_history_tabs(NULL);
4293 break;
4295 case WEBKIT_LOAD_FINISHED:
4296 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4297 case WEBKIT_LOAD_FAILED:
4298 #endif
4299 #if GTK_CHECK_VERSION(2, 20, 0)
4300 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4301 gtk_widget_hide(t->spinner);
4302 #endif
4303 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4304 if (s_loading && !strcmp(s_loading, "Loading"))
4305 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4306 default:
4307 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4308 break;
4311 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4312 webkit_web_view_can_go_back(wview));
4314 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4315 webkit_web_view_can_go_forward(wview));
4318 void
4319 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4321 run_script(t, JS_HINTING);
4324 void
4325 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4327 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4328 progress == 100 ? 0 : (double)progress / 100);
4332 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4333 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4334 WebKitWebPolicyDecision *pd, struct tab *t)
4336 char *uri;
4338 if (t == NULL)
4339 errx(1, "webview_nw_cb");
4341 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4342 webkit_network_request_get_uri(request));
4344 /* open in current tab */
4345 uri = (char *)webkit_network_request_get_uri(request);
4346 webkit_web_view_load_uri(t->wv, uri);
4347 webkit_web_policy_decision_ignore(pd);
4349 return (TRUE); /* we made the decission */
4353 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4354 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4355 WebKitWebPolicyDecision *pd, struct tab *t)
4357 char *uri;
4359 if (t == NULL)
4360 errx(1, "webview_npd_cb");
4362 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4363 t->ctrl_click,
4364 webkit_network_request_get_uri(request));
4366 uri = (char *)webkit_network_request_get_uri(request);
4368 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4369 t->ctrl_click = 0;
4370 create_new_tab(uri, NULL, ctrl_click_focus);
4371 webkit_web_policy_decision_ignore(pd);
4372 return (TRUE); /* we made the decission */
4375 webkit_web_policy_decision_use(pd);
4376 return (TRUE); /* we made the decission */
4379 WebKitWebView *
4380 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4382 if (t == NULL)
4383 errx(1, "webview_cwv_cb");
4385 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
4386 webkit_web_view_get_uri(wv));
4388 return (wv);
4392 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
4394 /* we can not eat the event without throwing gtk off so defer it */
4396 /* catch middle click */
4397 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
4398 t->ctrl_click = 1;
4399 goto done;
4402 /* catch ctrl click */
4403 if (e->type == GDK_BUTTON_RELEASE &&
4404 CLEAN(e->state) == GDK_CONTROL_MASK)
4405 t->ctrl_click = 1;
4406 else
4407 t->ctrl_click = 0;
4408 done:
4409 return (XT_CB_PASSTHROUGH);
4413 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
4415 struct mime_type *m;
4417 m = find_mime_type(mime_type);
4418 if (m == NULL)
4419 return (1);
4421 switch (fork()) {
4422 case -1:
4423 err(1, "fork");
4424 /* NOTREACHED */
4425 case 0:
4426 break;
4427 default:
4428 return (0);
4431 /* child */
4432 execlp(m->mt_action, m->mt_action,
4433 webkit_network_request_get_uri(request), (void *)NULL);
4435 _exit(0);
4437 /* NOTREACHED */
4438 return (0);
4442 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
4443 WebKitNetworkRequest *request, char *mime_type,
4444 WebKitWebPolicyDecision *decision, struct tab *t)
4446 if (t == NULL)
4447 errx(1, "webview_mimetype_cb");
4449 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
4450 t->tab_id, mime_type);
4452 if (run_mimehandler(t, mime_type, request) == 0) {
4453 webkit_web_policy_decision_ignore(decision);
4454 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4455 return (TRUE);
4458 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
4459 webkit_web_policy_decision_download(decision);
4460 return (TRUE);
4463 return (FALSE);
4467 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
4469 const gchar *filename;
4470 char *uri = NULL;
4471 struct download *download_entry;
4472 int ret = TRUE;
4474 if (wk_download == NULL || t == NULL)
4475 errx(1, "%s: invalid pointers", __func__);
4477 filename = webkit_download_get_suggested_filename(wk_download);
4478 if (filename == NULL)
4479 return (FALSE); /* abort download */
4481 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
4483 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
4484 "local %s\n", __func__, t->tab_id, filename, uri);
4486 webkit_download_set_destination_uri(wk_download, uri);
4488 if (webkit_download_get_status(wk_download) ==
4489 WEBKIT_DOWNLOAD_STATUS_ERROR) {
4490 warn("%s: download failed to start", __func__);
4491 ret = FALSE;
4492 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
4493 } else {
4494 download_entry = g_malloc(sizeof(struct download));
4495 download_entry->download = wk_download;
4496 download_entry->tab = t;
4497 download_entry->id = next_download_id++;
4498 RB_INSERT(download_list, &downloads, download_entry);
4499 /* get from history */
4500 g_object_ref(wk_download);
4501 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
4504 if (uri)
4505 g_free(uri);
4507 /* sync other download manager tabs */
4508 update_download_tabs(NULL);
4511 * NOTE: never redirect/render the current tab before this
4512 * function returns. This will cause the download to never start.
4514 return (ret); /* start download */
4517 /* XXX currently unused */
4518 void
4519 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
4521 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
4523 if (t == NULL)
4524 errx(1, "webview_hover_cb");
4526 if (uri) {
4527 if (t->hover) {
4528 g_free(t->hover);
4529 t->hover = NULL;
4531 t->hover = g_strdup(uri);
4532 } else if (t->hover) {
4533 g_free(t->hover);
4534 t->hover = NULL;
4539 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
4541 int i;
4542 char s[2], buf[128];
4543 const char *errstr = NULL;
4544 long long link;
4546 /* don't use w directly; use t->whatever instead */
4548 if (t == NULL)
4549 errx(1, "webview_keypress_cb");
4551 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
4552 e->keyval, e->state, t);
4554 if (t->hints_on) {
4555 /* ESC */
4556 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
4557 disable_hints(t);
4558 return (XT_CB_HANDLED);
4561 /* RETURN */
4562 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
4563 link = strtonum(t->hint_num, 1, 1000, &errstr);
4564 if (errstr) {
4565 /* we have a string */
4566 } else {
4567 /* we have a number */
4568 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
4569 t->hint_num);
4570 run_script(t, buf);
4572 disable_hints(t);
4575 /* BACKSPACE */
4576 /* XXX unfuck this */
4577 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
4578 if (t->hint_mode == XT_HINT_NUMERICAL) {
4579 /* last input was numerical */
4580 int l;
4581 l = strlen(t->hint_num);
4582 if (l > 0) {
4583 l--;
4584 if (l == 0) {
4585 disable_hints(t);
4586 enable_hints(t);
4587 } else {
4588 t->hint_num[l] = '\0';
4589 goto num;
4592 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
4593 /* last input was alphanumerical */
4594 int l;
4595 l = strlen(t->hint_buf);
4596 if (l > 0) {
4597 l--;
4598 if (l == 0) {
4599 disable_hints(t);
4600 enable_hints(t);
4601 } else {
4602 t->hint_buf[l] = '\0';
4603 goto anum;
4606 } else {
4607 /* bogus */
4608 disable_hints(t);
4612 /* numerical input */
4613 if (CLEAN(e->state) == 0 &&
4614 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
4615 snprintf(s, sizeof s, "%c", e->keyval);
4616 strlcat(t->hint_num, s, sizeof t->hint_num);
4617 DNPRINTF(XT_D_JS, "webview_keypress_cb: numerical %s\n",
4618 t->hint_num);
4619 num:
4620 link = strtonum(t->hint_num, 1, 1000, &errstr);
4621 if (errstr) {
4622 DNPRINTF(XT_D_JS, "webview_keypress_cb: invalid link number\n");
4623 disable_hints(t);
4624 } else {
4625 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
4626 t->hint_num);
4627 t->hint_mode = XT_HINT_NUMERICAL;
4628 run_script(t, buf);
4631 /* empty the counter buffer */
4632 bzero(t->hint_buf, sizeof t->hint_buf);
4633 return (XT_CB_HANDLED);
4636 /* alphanumerical input */
4637 if (
4638 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
4639 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
4640 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
4641 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
4642 snprintf(s, sizeof s, "%c", e->keyval);
4643 strlcat(t->hint_buf, s, sizeof t->hint_buf);
4644 DNPRINTF(XT_D_JS, "webview_keypress_cb: alphanumerical %s\n",
4645 t->hint_buf);
4646 anum:
4647 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
4648 run_script(t, buf);
4650 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
4651 t->hint_buf);
4652 t->hint_mode = XT_HINT_ALPHANUM;
4653 run_script(t, buf);
4655 /* empty the counter buffer */
4656 bzero(t->hint_num, sizeof t->hint_num);
4657 return (XT_CB_HANDLED);
4660 return (XT_CB_HANDLED);
4663 for (i = 0; i < LENGTH(keys); i++)
4664 if (e->keyval == keys[i].key && CLEAN(e->state) ==
4665 keys[i].mask) {
4666 keys[i].func(t, &keys[i].arg);
4667 return (XT_CB_HANDLED);
4670 return (XT_CB_PASSTHROUGH);
4674 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4676 const gchar *c = gtk_entry_get_text(w);
4677 GdkColor color;
4678 int forward = TRUE;
4680 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
4681 e->keyval, e->state, t);
4683 if (t == NULL)
4684 errx(1, "cmd_keyrelease_cb");
4686 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
4687 e->keyval, e->state, t);
4689 if (c[0] == ':')
4690 goto done;
4691 if (strlen(c) == 1) {
4692 webkit_web_view_unmark_text_matches(t->wv);
4693 goto done;
4696 if (c[0] == '/')
4697 forward = TRUE;
4698 else if (c[0] == '?')
4699 forward = FALSE;
4700 else
4701 goto done;
4703 /* search */
4704 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
4705 FALSE) {
4706 /* not found, mark red */
4707 gdk_color_parse("red", &color);
4708 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4709 /* unmark and remove selection */
4710 webkit_web_view_unmark_text_matches(t->wv);
4711 /* my kingdom for a way to unselect text in webview */
4712 } else {
4713 /* found, highlight all */
4714 webkit_web_view_unmark_text_matches(t->wv);
4715 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
4716 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4717 gdk_color_parse("white", &color);
4718 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4720 done:
4721 return (XT_CB_PASSTHROUGH);
4724 #if 0
4726 cmd_complete(struct tab *t, char *s)
4728 int i;
4729 GtkEntry *w = GTK_ENTRY(t->cmd);
4731 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
4733 for (i = 0; i < LENGTH(cmds); i++) {
4734 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
4735 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
4736 #if 0
4737 gtk_entry_set_text(w, ":");
4738 gtk_entry_append_text(w, cmds[i].cmd);
4739 gtk_editable_set_position(GTK_EDITABLE(w), -1);
4740 #endif
4744 return (0);
4746 #endif
4749 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4751 int i;
4753 if (t == NULL)
4754 errx(1, "entry_key_cb");
4756 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
4757 e->keyval, e->state, t);
4759 if (e->keyval == GDK_Escape)
4760 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4762 for (i = 0; i < LENGTH(keys); i++)
4763 if (e->keyval == keys[i].key &&
4764 CLEAN(e->state) == keys[i].mask &&
4765 keys[i].use_in_entry) {
4766 keys[i].func(t, &keys[i].arg);
4767 return (XT_CB_HANDLED);
4770 return (XT_CB_PASSTHROUGH);
4774 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4776 int rv = XT_CB_HANDLED;
4777 const gchar *c = gtk_entry_get_text(w);
4779 if (t == NULL)
4780 errx(1, "cmd_keypress_cb");
4782 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
4783 e->keyval, e->state, t);
4785 /* sanity */
4786 if (c == NULL)
4787 e->keyval = GDK_Escape;
4788 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
4789 e->keyval = GDK_Escape;
4791 switch (e->keyval) {
4792 #if 0
4793 case GDK_Tab:
4794 if (c[0] != ':')
4795 goto done;
4797 if (strchr (c, ' ')) {
4798 /* par completion */
4799 fprintf(stderr, "completeme par\n");
4800 goto done;
4803 cmd_complete(t, (char *)&c[1]);
4805 goto done;
4806 #endif
4807 case GDK_BackSpace:
4808 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
4809 break;
4810 /* FALLTHROUGH */
4811 case GDK_Escape:
4812 gtk_widget_hide(t->cmd);
4813 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4815 /* cancel search */
4816 if (c[0] == '/' || c[0] == '?')
4817 webkit_web_view_unmark_text_matches(t->wv);
4818 goto done;
4821 rv = XT_CB_PASSTHROUGH;
4822 done:
4823 return (rv);
4827 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
4829 if (t == NULL)
4830 errx(1, "cmd_focusout_cb");
4832 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
4833 t->tab_id, t->focus_wv);
4835 /* abort command when losing focus */
4836 gtk_widget_hide(t->cmd);
4837 if (t->focus_wv)
4838 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4839 else
4840 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
4842 return (XT_CB_PASSTHROUGH);
4845 void
4846 cmd_activate_cb(GtkEntry *entry, struct tab *t)
4848 int i;
4849 char *s;
4850 const gchar *c = gtk_entry_get_text(entry);
4852 if (t == NULL)
4853 errx(1, "cmd_activate_cb");
4855 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
4857 /* sanity */
4858 if (c == NULL)
4859 goto done;
4860 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
4861 goto done;
4862 if (strlen(c) < 2)
4863 goto done;
4864 s = (char *)&c[1];
4866 if (c[0] == '/' || c[0] == '?') {
4867 if (t->search_text) {
4868 g_free(t->search_text);
4869 t->search_text = NULL;
4872 t->search_text = g_strdup(s);
4873 if (global_search)
4874 g_free(global_search);
4875 global_search = g_strdup(s);
4876 t->search_forward = c[0] == '/';
4878 goto done;
4881 for (i = 0; i < LENGTH(cmds); i++)
4882 if (cmds[i].params) {
4883 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
4884 cmds[i].arg.s = g_strdup(s);
4885 goto execute_command;
4887 } else {
4888 if (!strcmp(s, cmds[i].cmd))
4889 goto execute_command;
4892 done:
4893 gtk_widget_hide(t->cmd);
4894 return;
4896 execute_command:
4897 gtk_widget_hide(t->cmd);
4898 cmds[i].func(t, &cmds[i].arg);
4901 void
4902 backward_cb(GtkWidget *w, struct tab *t)
4904 if (t == NULL)
4905 errx(1, "backward_cb");
4907 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
4909 webkit_web_view_go_back(t->wv);
4912 void
4913 forward_cb(GtkWidget *w, struct tab *t)
4915 if (t == NULL)
4916 errx(1, "forward_cb");
4918 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
4920 webkit_web_view_go_forward(t->wv);
4923 void
4924 stop_cb(GtkWidget *w, struct tab *t)
4926 WebKitWebFrame *frame;
4928 if (t == NULL)
4929 errx(1, "stop_cb");
4931 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
4933 frame = webkit_web_view_get_main_frame(t->wv);
4934 if (frame == NULL) {
4935 warnx("stop_cb: no frame");
4936 return;
4939 webkit_web_frame_stop_loading(frame);
4942 void
4943 setup_webkit(struct tab *t)
4945 g_object_set((GObject *)t->settings,
4946 "user-agent", t->user_agent, (char *)NULL);
4947 g_object_set((GObject *)t->settings,
4948 "enable-scripts", enable_scripts, (char *)NULL);
4949 g_object_set((GObject *)t->settings,
4950 "enable-plugins", enable_plugins, (char *)NULL);
4951 adjustfont_webkit(t, XT_FONT_SET);
4953 webkit_web_view_set_settings(t->wv, t->settings);
4956 GtkWidget *
4957 create_browser(struct tab *t)
4959 GtkWidget *w;
4960 gchar *strval;
4962 if (t == NULL)
4963 errx(1, "create_browser");
4965 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
4966 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
4967 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
4968 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
4970 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
4971 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
4972 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
4974 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
4975 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
4977 /* set defaults */
4978 t->settings = webkit_web_settings_new();
4980 g_object_get((GObject *)t->settings, "user-agent", &strval,
4981 (char *)NULL);
4982 t->user_agent = g_strdup_printf("%s %s+", strval, version);
4983 g_free(strval);
4985 setup_webkit(t);
4987 return (w);
4990 GtkWidget *
4991 create_window(void)
4993 GtkWidget *w;
4995 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
4996 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
4997 gtk_widget_set_name(w, "xxxterm");
4998 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
4999 g_signal_connect(G_OBJECT(w), "delete_event",
5000 G_CALLBACK (gtk_main_quit), NULL);
5002 return (w);
5005 GtkWidget *
5006 create_toolbar(struct tab *t)
5008 GtkWidget *toolbar = NULL, *b, *eb1;
5010 b = gtk_hbox_new(FALSE, 0);
5011 toolbar = b;
5012 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5014 if (fancy_bar) {
5015 /* backward button */
5016 t->backward = create_button("go-back", GTK_STOCK_GO_BACK, 0);
5017 gtk_widget_set_sensitive(t->backward, FALSE);
5018 g_signal_connect(G_OBJECT(t->backward), "clicked",
5019 G_CALLBACK(backward_cb), t);
5020 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5022 /* forward button */
5023 t->forward = create_button("go-forward",GTK_STOCK_GO_FORWARD, 0);
5024 gtk_widget_set_sensitive(t->forward, FALSE);
5025 g_signal_connect(G_OBJECT(t->forward), "clicked",
5026 G_CALLBACK(forward_cb), t);
5027 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5028 FALSE, 0);
5030 /* stop button */
5031 t->stop = create_button("stop", GTK_STOCK_STOP, 0);
5032 gtk_widget_set_sensitive(t->stop, FALSE);
5033 g_signal_connect(G_OBJECT(t->stop), "clicked",
5034 G_CALLBACK(stop_cb), t);
5035 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5036 FALSE, 0);
5038 /* JS button */
5039 t->js_toggle = create_button("js-toggle", enable_scripts ?
5040 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5041 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5042 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5043 G_CALLBACK(js_toggle_cb), t);
5044 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5047 t->uri_entry = gtk_entry_new();
5048 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5049 G_CALLBACK(activate_uri_entry_cb), t);
5050 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5051 (GCallback)entry_key_cb, t);
5052 eb1 = gtk_hbox_new(FALSE, 0);
5053 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5054 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5055 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5057 /* search entry */
5058 if (fancy_bar && search_string) {
5059 GtkWidget *eb2;
5060 t->search_entry = gtk_entry_new();
5061 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5062 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5063 G_CALLBACK(activate_search_entry_cb), t);
5064 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5065 (GCallback)entry_key_cb, t);
5066 gtk_widget_set_size_request(t->search_entry, -1, -1);
5067 eb2 = gtk_hbox_new(FALSE, 0);
5068 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5069 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5071 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5073 return (toolbar);
5076 void
5077 recalc_tabs(void)
5079 struct tab *t;
5081 TAILQ_FOREACH(t, &tabs, entry)
5082 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5086 undo_close_tab_save(struct tab *t)
5088 int m, n;
5089 const gchar *uri;
5090 struct undo *u1, *u2;
5091 WebKitWebFrame *frame;
5092 WebKitWebBackForwardList *bfl;
5093 GList *items;
5094 WebKitWebHistoryItem *item;
5096 frame = webkit_web_view_get_main_frame(t->wv);
5097 uri = webkit_web_frame_get_uri(frame);
5099 if (uri && !strlen(uri))
5100 return (1);
5102 u1 = g_malloc0(sizeof(struct undo));
5103 u1->uri = g_strdup(uri);
5105 bfl = webkit_web_view_get_back_forward_list(t->wv);
5107 m = webkit_web_back_forward_list_get_forward_length(bfl);
5108 n = webkit_web_back_forward_list_get_back_length(bfl);
5109 u1->back = n;
5111 /* forward history */
5112 items = webkit_web_back_forward_list_get_forward_list_with_limit(bfl, m);
5114 while (items) {
5115 item = items->data;
5116 u1->history = g_list_prepend(u1->history,
5117 webkit_web_history_item_copy(item));
5118 items = g_list_next(items);
5121 /* current item */
5122 if (m) {
5123 item = webkit_web_back_forward_list_get_current_item(bfl);
5124 u1->history = g_list_prepend(u1->history,
5125 webkit_web_history_item_copy(item));
5128 /* back history */
5129 items = webkit_web_back_forward_list_get_back_list_with_limit(bfl, n);
5131 while (items) {
5132 item = items->data;
5133 u1->history = g_list_prepend(u1->history,
5134 webkit_web_history_item_copy(item));
5135 items = g_list_next(items);
5138 TAILQ_INSERT_HEAD(&undos, u1, entry);
5140 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5141 u2 = TAILQ_LAST(&undos, undo_tailq);
5142 TAILQ_REMOVE(&undos, u2, entry);
5143 g_free(u2->uri);
5144 g_list_free(u2->history);
5145 g_free(u2);
5146 } else
5147 undo_count++;
5149 return (0);
5152 void
5153 delete_tab(struct tab *t)
5155 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5157 if (t == NULL)
5158 return;
5160 /* halt all webkit activity */
5161 webkit_web_view_stop_loading(t->wv);
5163 undo_close_tab_save(t);
5165 gtk_widget_destroy(t->vbox);
5166 g_free(t->user_agent);
5167 g_free(t);
5169 TAILQ_REMOVE(&tabs, t, entry);
5170 recalc_tabs();
5171 if (TAILQ_EMPTY(&tabs))
5172 create_new_tab(NULL, NULL, 1);
5175 void
5176 adjustfont_webkit(struct tab *t, int adjust)
5178 if (t == NULL)
5179 errx(1, "adjustfont_webkit");
5181 if (adjust == XT_FONT_SET)
5182 t->font_size = default_font_size;
5184 t->font_size += adjust;
5185 g_object_set((GObject *)t->settings, "default-font-size",
5186 t->font_size, (char *)NULL);
5187 g_object_get((GObject *)t->settings, "default-font-size",
5188 &t->font_size, (char *)NULL);
5191 void
5192 append_tab(struct tab *t)
5194 if (t == NULL)
5195 return;
5197 TAILQ_INSERT_TAIL(&tabs, t, entry);
5198 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5201 void
5202 create_new_tab(char *title, struct undo *u, int focus)
5204 struct tab *t, *tt;
5205 int load = 1, id, notfound;
5206 char *newuri = NULL;
5207 GtkWidget *b, *bb;
5208 WebKitWebHistoryItem *item;
5209 GList *items;
5210 WebKitWebBackForwardList *bfl;
5212 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5214 if (tabless && !TAILQ_EMPTY(&tabs)) {
5215 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5216 return;
5219 t = g_malloc0(sizeof *t);
5221 if (title == NULL) {
5222 title = "(untitled)";
5223 load = 0;
5224 } else {
5225 if (valid_url_type(title)) {
5226 newuri = guess_url_type(title);
5227 title = newuri;
5231 t->vbox = gtk_vbox_new(FALSE, 0);
5233 /* label + button for tab */
5234 b = gtk_hbox_new(FALSE, 0);
5235 t->tab_content = b;
5237 #if GTK_CHECK_VERSION(2, 20, 0)
5238 t->spinner = gtk_spinner_new ();
5239 #endif
5240 t->label = gtk_label_new(title);
5241 bb = create_button("my-close-button", GTK_STOCK_CLOSE, 1);
5242 gtk_widget_set_size_request(t->label, 100, 0);
5243 gtk_widget_set_size_request(b, 130, 0);
5244 gtk_notebook_set_homogeneous_tabs(notebook, TRUE);
5246 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5247 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5248 #if GTK_CHECK_VERSION(2, 20, 0)
5249 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5250 #endif
5252 /* toolbar */
5253 t->toolbar = create_toolbar(t);
5254 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5256 /* browser */
5257 t->browser_win = create_browser(t);
5258 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5260 /* command entry */
5261 t->cmd = gtk_entry_new();
5262 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5263 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5264 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5266 /* xtp meaning is normal by default */
5267 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5269 /* and show it all */
5270 gtk_widget_show_all(b);
5271 gtk_widget_show_all(t->vbox);
5273 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5274 append_tab(t);
5275 else {
5276 notfound = 1;
5277 id = gtk_notebook_get_current_page(notebook);
5278 TAILQ_FOREACH(tt, &tabs, entry) {
5279 if (tt->tab_id == id) {
5280 notfound = 0;
5281 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5282 gtk_notebook_insert_page(notebook, t->vbox, b,
5283 id + 1);
5284 recalc_tabs();
5285 break;
5288 if (notfound)
5289 append_tab(t);
5292 #if GTK_CHECK_VERSION(2, 20, 0)
5293 /* turn spinner off if we are a new tab without uri */
5294 if (!load) {
5295 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5296 gtk_widget_hide(t->spinner);
5298 #endif
5299 /* make notebook tabs reorderable */
5300 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5302 g_object_connect((GObject*)t->cmd,
5303 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5304 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5305 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5306 "signal::activate", (GCallback)cmd_activate_cb, t,
5307 (char *)NULL);
5309 g_object_connect((GObject*)t->wv,
5310 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
5311 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5312 "signal::download-requested", (GCallback)webview_download_cb, t,
5313 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5314 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5315 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5316 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5317 "signal::event", (GCallback)webview_event_cb, t,
5318 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5319 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5320 (char *)NULL);
5321 g_signal_connect(t->wv, "notify::load-status",
5322 G_CALLBACK(notify_load_status_cb), t);
5324 /* hijack the unused keys as if we were the browser */
5325 g_object_connect((GObject*)t->toolbar,
5326 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
5327 (char *)NULL);
5329 g_signal_connect(G_OBJECT(bb), "button_press_event",
5330 G_CALLBACK(tab_close_cb), t);
5332 /* hide stuff */
5333 gtk_widget_hide(t->cmd);
5334 if (showurl == 0)
5335 gtk_widget_hide(t->toolbar);
5337 if (focus) {
5338 gtk_notebook_set_current_page(notebook, t->tab_id);
5339 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
5340 t->tab_id);
5343 if (load)
5344 webkit_web_view_load_uri(t->wv, title);
5345 else
5346 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5348 /* restore the tab's history */
5349 if (u && u->history) {
5350 bfl = webkit_web_view_get_back_forward_list(t->wv);
5352 items = u->history;
5353 while (items) {
5354 item = items->data;
5355 webkit_web_back_forward_list_add_item(bfl, item);
5356 items = g_list_next(items);
5359 item = g_list_nth_data(u->history, u->back);
5360 webkit_web_view_go_to_back_forward_item(t->wv, item);
5362 g_list_free(items);
5363 g_list_free(u->history);
5366 if (newuri)
5367 g_free(newuri);
5370 void
5371 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
5372 gpointer *udata)
5374 struct tab *t;
5375 const gchar *uri;
5377 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
5379 TAILQ_FOREACH(t, &tabs, entry) {
5380 if (t->tab_id == pn) {
5381 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
5382 "%d\n", pn);
5384 uri = webkit_web_view_get_title(t->wv);
5385 if (uri == NULL)
5386 uri = XT_NAME;
5387 gtk_window_set_title(GTK_WINDOW(main_window), uri);
5389 gtk_widget_hide(t->cmd);
5391 if (t->focus_wv)
5392 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5397 void
5398 menuitem_response(struct tab *t)
5400 gtk_notebook_set_current_page(notebook, t->tab_id);
5403 gboolean
5404 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
5406 GtkWidget *menu, *menu_items;
5407 GdkEventButton *bevent;
5408 WebKitWebFrame *frame;
5409 const gchar *uri;
5410 struct tab *ti;
5412 if (event->type == GDK_BUTTON_PRESS) {
5413 bevent = (GdkEventButton *) event;
5414 menu = gtk_menu_new();
5416 TAILQ_FOREACH(ti, &tabs, entry) {
5417 frame = webkit_web_view_get_main_frame(ti->wv);
5418 uri = webkit_web_frame_get_uri(frame);
5419 /* XXX make sure there is something to print */
5420 /* XXX add gui pages in here to look purdy */
5421 if (uri == NULL)
5422 uri = "(untitled)";
5423 if (strlen(uri) == 0)
5424 uri = "(untitled)";
5425 menu_items = gtk_menu_item_new_with_label(uri);
5426 gtk_menu_append(GTK_MENU (menu), menu_items);
5427 gtk_widget_show(menu_items);
5429 gtk_signal_connect_object(GTK_OBJECT(menu_items),
5430 "activate", GTK_SIGNAL_FUNC(menuitem_response),
5431 (gpointer)ti);
5434 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
5435 bevent->button, bevent->time);
5437 /* unref object so it'll free itself when popped down */
5438 g_object_ref_sink(menu);
5439 g_object_unref(menu);
5441 return (TRUE /* eat event */);
5444 return (FALSE /* propagate */);
5448 icon_size_map(int icon_size)
5450 if (icon_size <= GTK_ICON_SIZE_INVALID ||
5451 icon_size > GTK_ICON_SIZE_DIALOG)
5452 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
5454 return (icon_size);
5457 GtkWidget *
5458 create_button(char *name, char *stockid, int size)
5460 GtkWidget *button, *image;
5461 char *rcstring;
5462 int gtk_icon_size;
5463 asprintf(&rcstring,
5464 "style \"%s-style\"\n"
5465 "{\n"
5466 " GtkWidget::focus-padding = 0\n"
5467 " GtkWidget::focus-line-width = 0\n"
5468 " xthickness = 0\n"
5469 " ythickness = 0\n"
5470 "}\n"
5471 "widget \"*.%s\" style \"%s-style\"",name,name,name);
5472 gtk_rc_parse_string(rcstring);
5473 free(rcstring);
5474 button = gtk_button_new();
5475 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5476 gtk_icon_size = icon_size_map(size?size:icon_size);
5478 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
5479 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5480 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
5481 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
5482 gtk_widget_set_name(button, name);
5484 return button;
5487 void
5488 button_set_stockid(GtkWidget *button, char *stockid)
5490 GtkWidget *image;
5491 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
5492 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5493 gtk_button_set_image(GTK_BUTTON(button), image);
5496 void
5497 create_canvas(void)
5499 GtkWidget *vbox;
5500 GList *l = NULL;
5501 GdkPixbuf *pb;
5502 char file[PATH_MAX];
5503 int i;
5505 vbox = gtk_vbox_new(FALSE, 0);
5506 gtk_box_set_spacing(GTK_BOX(vbox), 0);
5507 notebook = GTK_NOTEBOOK(gtk_notebook_new());
5508 if (showtabs == 0)
5509 gtk_notebook_set_show_tabs(notebook, FALSE);
5510 else {
5511 gtk_notebook_set_tab_hborder(notebook, 0);
5512 gtk_notebook_set_tab_vborder(notebook, 0);
5514 gtk_notebook_set_show_border(notebook, FALSE);
5515 gtk_notebook_set_scrollable(notebook, TRUE);
5516 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
5518 abtn = gtk_button_new();
5519 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5520 gtk_widget_set_size_request(arrow, -1, -1);
5521 gtk_container_add(GTK_CONTAINER(abtn), arrow);
5522 gtk_widget_set_size_request(abtn, -1, 20);
5523 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
5525 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
5526 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
5527 gtk_widget_set_size_request(vbox, -1, -1);
5529 g_object_connect((GObject*)notebook,
5530 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
5531 (char *)NULL);
5532 g_signal_connect(G_OBJECT(abtn), "button_press_event",
5533 G_CALLBACK(arrow_cb), NULL);
5535 main_window = create_window();
5536 gtk_container_add(GTK_CONTAINER(main_window), vbox);
5537 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5539 /* icons */
5540 for (i = 0; i < LENGTH(icons); i++) {
5541 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
5542 pb = gdk_pixbuf_new_from_file(file, NULL);
5543 l = g_list_append(l, pb);
5545 gtk_window_set_default_icon_list(l);
5547 gtk_widget_show_all(abtn);
5548 gtk_widget_show_all(main_window);
5551 void
5552 set_hook(void **hook, char *name)
5554 if (hook == NULL)
5555 errx(1, "set_hook");
5557 if (*hook == NULL) {
5558 *hook = dlsym(RTLD_NEXT, name);
5559 if (*hook == NULL)
5560 errx(1, "can't hook %s", name);
5564 /* override libsoup soup_cookie_equal because it doesn't look at domain */
5565 gboolean
5566 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
5568 g_return_val_if_fail(cookie1, FALSE);
5569 g_return_val_if_fail(cookie2, FALSE);
5571 return (!strcmp (cookie1->name, cookie2->name) &&
5572 !strcmp (cookie1->value, cookie2->value) &&
5573 !strcmp (cookie1->path, cookie2->path) &&
5574 !strcmp (cookie1->domain, cookie2->domain));
5577 void
5578 transfer_cookies(void)
5580 GSList *cf;
5581 SoupCookie *sc, *pc;
5583 cf = soup_cookie_jar_all_cookies(p_cookiejar);
5585 for (;cf; cf = cf->next) {
5586 pc = cf->data;
5587 sc = soup_cookie_copy(pc);
5588 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
5591 soup_cookies_free(cf);
5594 void
5595 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
5597 GSList *cf;
5598 SoupCookie *ci;
5600 print_cookie("soup_cookie_jar_delete_cookie", c);
5602 if (jar == NULL || c == NULL)
5603 return;
5605 /* find and remove from persistent jar */
5606 cf = soup_cookie_jar_all_cookies(p_cookiejar);
5608 for (;cf; cf = cf->next) {
5609 ci = cf->data;
5610 if (soup_cookie_equal(ci, c)) {
5611 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
5612 break;
5616 soup_cookies_free(cf);
5618 /* delete from session jar */
5619 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
5622 void
5623 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
5625 struct domain *d;
5626 SoupCookie *c;
5628 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
5629 jar, p_cookiejar, s_cookiejar);
5631 /* see if we are up and running */
5632 if (p_cookiejar == NULL) {
5633 _soup_cookie_jar_add_cookie(jar, cookie);
5634 return;
5636 /* disallow p_cookiejar adds, shouldn't happen */
5637 if (jar == p_cookiejar)
5638 return;
5640 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
5641 blocked_cookies++;
5642 DNPRINTF(XT_D_COOKIE,
5643 "soup_cookie_jar_add_cookie: reject %s\n",
5644 cookie->domain);
5645 if (!allow_volatile_cookies)
5646 return;
5649 if (cookie->expires == NULL && session_timeout) {
5650 soup_cookie_set_expires(cookie,
5651 soup_date_new_from_now(session_timeout));
5652 print_cookie("modified add cookie", cookie);
5655 /* see if we are white listed for persistence */
5656 if (d && d->handy) {
5657 /* add to persistent jar */
5658 c = soup_cookie_copy(cookie);
5659 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
5660 _soup_cookie_jar_add_cookie(p_cookiejar, c);
5663 /* add to session jar */
5664 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
5665 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
5668 void
5669 setup_cookies(char *file)
5671 if (cookies_enabled == 0)
5672 return;
5674 set_hook((void *)&_soup_cookie_jar_add_cookie,
5675 "soup_cookie_jar_add_cookie");
5676 set_hook((void *)&_soup_cookie_jar_delete_cookie,
5677 "soup_cookie_jar_delete_cookie");
5680 * the following code is intricate due to overriding several libsoup
5681 * functions.
5682 * do not alter order of these operations.
5684 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
5686 s_cookiejar = soup_cookie_jar_new();
5687 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
5688 cookie_policy, (void *)NULL);
5689 transfer_cookies();
5691 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
5694 void
5695 setup_proxy(char *uri)
5697 if (proxy_uri) {
5698 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
5699 soup_uri_free(proxy_uri);
5700 proxy_uri = NULL;
5702 if (http_proxy) {
5703 if (http_proxy != uri) {
5704 g_free(http_proxy);
5705 http_proxy = NULL;
5709 if (uri) {
5710 http_proxy = g_strdup(uri);
5711 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
5712 proxy_uri = soup_uri_new(http_proxy);
5713 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
5718 send_url_to_socket(char *url)
5720 int s, len, rv = -1;
5721 struct sockaddr_un sa;
5723 pwd = getpwuid(getuid());
5724 if (pwd == NULL)
5725 errx(1, "invalid user %d", getuid());
5727 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5728 warnx("send_url_to_socket: socket");
5729 return (-1);
5732 sa.sun_family = AF_UNIX;
5733 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5734 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5735 len = SUN_LEN(&sa);
5737 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
5738 warnx("send_url_to_socket: connect");
5739 goto done;
5742 if (send(s, url, strlen(url) + 1, 0) == -1) {
5743 warnx("send_url_to_socket: send");
5744 goto done;
5746 done:
5747 close(s);
5748 return (rv);
5751 void
5752 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
5754 int s, n;
5755 char str[XT_MAX_URL_LENGTH];
5756 socklen_t t = sizeof(struct sockaddr_un);
5757 struct sockaddr_un sa;
5758 struct passwd *p;
5759 uid_t uid;
5760 gid_t gid;
5762 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
5763 warn("socket_watcher: accept");
5764 return;
5767 if (getpeereid(s, &uid, &gid) == -1) {
5768 warn("socket_watcher: getpeereid");
5769 return;
5771 if (uid != getuid() || gid != getgid()) {
5772 warnx("socket_watcher: unauthorized user");
5773 return;
5776 p = getpwuid(uid);
5777 if (p == NULL) {
5778 warnx("socket_watcher: not a valid user");
5779 return;
5782 n = recv(s, str, sizeof(str), 0);
5783 if (n <= 0)
5784 return;
5786 create_new_tab(str, NULL, 1);
5790 is_running(void)
5792 int s, len, rv = 1;
5793 struct sockaddr_un sa;
5795 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5796 warn("is_running: socket");
5797 return (-1);
5800 sa.sun_family = AF_UNIX;
5801 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5802 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5803 len = SUN_LEN(&sa);
5805 /* connect to see if there is a listener */
5806 if (connect(s, (struct sockaddr *)&sa, len) == -1)
5807 rv = 0; /* not running */
5808 else
5809 rv = 1; /* already running */
5811 close(s);
5813 return (rv);
5817 build_socket(void)
5819 int s, len;
5820 struct sockaddr_un sa;
5822 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5823 warn("build_socket: socket");
5824 return (-1);
5827 sa.sun_family = AF_UNIX;
5828 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5829 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5830 len = SUN_LEN(&sa);
5832 /* connect to see if there is a listener */
5833 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
5834 /* no listener so we will */
5835 unlink(sa.sun_path);
5837 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
5838 warn("build_socket: bind");
5839 goto done;
5842 if (listen(s, 1) == -1) {
5843 warn("build_socket: listen");
5844 goto done;
5847 return (s);
5850 done:
5851 close(s);
5852 return (-1);
5855 void
5856 usage(void)
5858 fprintf(stderr,
5859 "%s [-nSTVt][-f file] url ...\n", __progname);
5860 exit(0);
5864 main(int argc, char *argv[])
5866 struct stat sb;
5867 int c, focus = 1, s, optn = 0;
5868 char conf[PATH_MAX] = { '\0' };
5869 char file[PATH_MAX];
5870 char *env_proxy = NULL;
5871 FILE *f = NULL;
5873 start_argv = argv;
5875 while ((c = getopt(argc, argv, "STVf:tn")) != -1) {
5876 switch (c) {
5877 case 'S':
5878 showurl = 0;
5879 break;
5880 case 'T':
5881 showtabs = 0;
5882 break;
5883 case 'V':
5884 errx(0 , "Version: %s", version);
5885 break;
5886 case 'f':
5887 strlcpy(conf, optarg, sizeof(conf));
5888 break;
5889 case 't':
5890 tabless = 1;
5891 break;
5892 case 'n':
5893 optn = 1;
5894 break;
5895 default:
5896 usage();
5897 /* NOTREACHED */
5900 argc -= optind;
5901 argv += optind;
5903 TAILQ_INIT(&tabs);
5904 RB_INIT(&hl);
5905 RB_INIT(&js_wl);
5906 RB_INIT(&downloads);
5907 TAILQ_INIT(&mtl);
5908 TAILQ_INIT(&aliases);
5909 TAILQ_INIT(&undos);
5911 gnutls_global_init();
5913 /* generate session keys for xtp pages */
5914 generate_xtp_session_key(&dl_session_key);
5915 generate_xtp_session_key(&hl_session_key);
5916 generate_xtp_session_key(&cl_session_key);
5917 generate_xtp_session_key(&fl_session_key);
5919 /* prepare gtk */
5920 gtk_init(&argc, &argv);
5921 if (!g_thread_supported())
5922 g_thread_init(NULL);
5924 pwd = getpwuid(getuid());
5925 if (pwd == NULL)
5926 errx(1, "invalid user %d", getuid());
5928 /* set download dir */
5929 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
5931 /* set default string settings */
5932 home = g_strdup("http://www.peereboom.us");
5933 resource_dir = g_strdup("/usr/local/share/xxxterm/");
5935 /* read config file */
5936 if (strlen(conf) == 0)
5937 snprintf(conf, sizeof conf, "%s/.%s",
5938 pwd->pw_dir, XT_CONF_FILE);
5939 config_parse(conf, 0);
5941 /* working directory */
5942 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
5943 if (stat(work_dir, &sb)) {
5944 if (mkdir(work_dir, S_IRWXU) == -1)
5945 err(1, "mkdir work_dir");
5946 if (stat(work_dir, &sb))
5947 err(1, "stat work_dir");
5949 if (S_ISDIR(sb.st_mode) == 0)
5950 errx(1, "%s not a dir", work_dir);
5951 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
5952 warnx("fixing invalid permissions on %s", work_dir);
5953 if (chmod(work_dir, S_IRWXU) == -1)
5954 err(1, "chmod");
5957 /* certs dir */
5958 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
5959 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
5960 if (stat(certs_dir, &sb)) {
5961 if (mkdir(certs_dir, S_IRWXU) == -1)
5962 err(1, "mkdir certs_dir");
5963 if (stat(certs_dir, &sb))
5964 err(1, "stat certs_dir");
5966 if (S_ISDIR(sb.st_mode) == 0)
5967 errx(1, "%s not a dir", certs_dir);
5968 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
5969 warnx("fixing invalid permissions on %s", certs_dir);
5970 if (chmod(certs_dir, S_IRWXU) == -1)
5971 err(1, "chmod");
5974 /* runtime settings that can override config file */
5975 if (runtime_settings[0] != '\0')
5976 config_parse(runtime_settings, 1);
5978 /* download dir */
5979 if (!strcmp(download_dir, pwd->pw_dir))
5980 strlcat(download_dir, "/downloads", sizeof download_dir);
5981 if (stat(download_dir, &sb)) {
5982 if (mkdir(download_dir, S_IRWXU) == -1)
5983 err(1, "mkdir download_dir");
5984 if (stat(download_dir, &sb))
5985 err(1, "stat download_dir");
5987 if (S_ISDIR(sb.st_mode) == 0)
5988 errx(1, "%s not a dir", download_dir);
5989 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
5990 warnx("fixing invalid permissions on %s", download_dir);
5991 if (chmod(download_dir, S_IRWXU) == -1)
5992 err(1, "chmod");
5995 /* favorites file */
5996 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
5997 if (stat(file, &sb)) {
5998 warnx("favorites file doesn't exist, creating it");
5999 if ((f = fopen(file, "w")) == NULL)
6000 err(1, "favorites");
6001 fclose(f);
6004 /* cookies */
6005 session = webkit_get_default_session();
6006 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6007 setup_cookies(file);
6009 /* certs */
6010 if (ssl_ca_file) {
6011 if (stat(ssl_ca_file, &sb)) {
6012 warn("no CA file: %s", ssl_ca_file);
6013 g_free(ssl_ca_file);
6014 ssl_ca_file = NULL;
6015 } else
6016 g_object_set(session,
6017 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6018 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6019 (void *)NULL);
6022 /* proxy */
6023 env_proxy = getenv("http_proxy");
6024 if (env_proxy)
6025 setup_proxy(env_proxy);
6026 else
6027 setup_proxy(http_proxy);
6029 /* see if there is already an xxxterm running */
6030 if (single_instance && is_running()) {
6031 optn = 1;
6032 warnx("already running");
6035 if (optn) {
6036 while (argc) {
6037 send_url_to_socket(argv[0]);
6039 argc--;
6040 argv++;
6042 exit(0);
6045 /* go graphical */
6046 create_canvas();
6048 if (save_global_history)
6049 restore_global_history();
6051 focus = restore_saved_tabs();
6053 while (argc) {
6054 create_new_tab(argv[0], NULL, focus);
6055 focus = 0;
6057 argc--;
6058 argv++;
6060 if (focus == 1)
6061 create_new_tab(home, NULL, 1);
6063 if (enable_socket)
6064 if ((s = build_socket()) != -1)
6065 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6067 gtk_main();
6069 gnutls_global_deinit();
6071 return (0);