yay no more perl all the time!
[xxxterm.git] / xxxterm.c
blobfec7fddf36c7e6f556faccd89e54a99446ae10da
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 *oops;
161 GtkWidget *backward;
162 GtkWidget *forward;
163 GtkWidget *stop;
164 GtkWidget *js_toggle;
165 guint tab_id;
166 WebKitWebView *wv;
168 /* adjustments for browser */
169 GtkScrollbar *sb_h;
170 GtkScrollbar *sb_v;
171 GtkAdjustment *adjust_h;
172 GtkAdjustment *adjust_v;
174 /* flags */
175 int focus_wv;
176 int ctrl_click;
177 gchar *hover;
178 int xtp_meaning; /* identifies dls/favorites */
180 /* hints */
181 int hints_on;
182 int hint_mode;
183 #define XT_HINT_NONE (0)
184 #define XT_HINT_NUMERICAL (1)
185 #define XT_HINT_ALPHANUM (2)
186 char hint_buf[128];
187 char hint_num[128];
189 /* search */
190 char *search_text;
191 int search_forward;
193 /* settings */
194 WebKitWebSettings *settings;
195 int font_size;
196 gchar *user_agent;
198 TAILQ_HEAD(tab_list, tab);
200 struct history {
201 RB_ENTRY(history) entry;
202 const gchar *uri;
203 const gchar *title;
205 RB_HEAD(history_list, history);
207 struct download {
208 RB_ENTRY(download) entry;
209 int id;
210 WebKitDownload *download;
211 struct tab *tab;
213 RB_HEAD(download_list, download);
215 struct domain {
216 RB_ENTRY(domain) entry;
217 gchar *d;
218 int handy; /* app use */
220 RB_HEAD(domain_list, domain);
222 struct undo {
223 TAILQ_ENTRY(undo) entry;
224 gchar *uri;
225 GList *history;
226 int back; /* Keeps track of how many back
227 * history items there are. */
229 TAILQ_HEAD(undo_tailq, undo);
231 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
232 int next_download_id = 1;
234 struct karg {
235 int i;
236 char *s;
239 /* defines */
240 #define XT_NAME ("XXXTerm")
241 #define XT_DIR (".xxxterm")
242 #define XT_CERT_DIR ("certs/")
243 #define XT_CONF_FILE ("xxxterm.conf")
244 #define XT_FAVS_FILE ("favorites")
245 #define XT_SAVED_TABS_FILE ("saved_tabs")
246 #define XT_RESTART_TABS_FILE ("restart_tabs")
247 #define XT_SOCKET_FILE ("socket")
248 #define XT_HISTORY_FILE ("history")
249 #define XT_CB_HANDLED (TRUE)
250 #define XT_CB_PASSTHROUGH (FALSE)
251 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
252 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
253 #define XT_DLMAN_REFRESH "10"
254 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
255 "td {overflow: hidden;}\n" \
256 "th {background-color: #cccccc}" \
257 "table {width: 90%%; border: 1px black" \
258 " solid; table-layout: fixed}\n</style>\n\n"
259 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
260 #define XT_MAX_UNDO_CLOSE_TAB (32)
262 /* file sizes */
263 #define SZ_KB ((uint64_t) 1024)
264 #define SZ_MB (SZ_KB * SZ_KB)
265 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
266 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
269 * xxxterm "protocol" (xtp)
270 * We use this for managing stuff like downloads and favorites. They
271 * make magical HTML pages in memory which have xxxt:// links in order
272 * to communicate with xxxterm's internals. These links take the format:
273 * xxxt://class/session_key/action/arg
275 * Don't begin xtp class/actions as 0. atoi returns that on error.
277 * Typically we have not put addition of items in this framework, as
278 * adding items is either done via an ex-command or via a keybinding instead.
281 #define XT_XTP_STR "xxxt://"
283 /* XTP classes (xxxt://<class>) */
284 #define XT_XTP_DL 1 /* downloads */
285 #define XT_XTP_HL 2 /* history */
286 #define XT_XTP_CL 3 /* cookies */
287 #define XT_XTP_FL 4 /* favorites */
289 /* XTP download actions */
290 #define XT_XTP_DL_LIST 1
291 #define XT_XTP_DL_CANCEL 2
292 #define XT_XTP_DL_REMOVE 3
294 /* XTP history actions */
295 #define XT_XTP_HL_LIST 1
296 #define XT_XTP_HL_REMOVE 2
298 /* XTP cookie actions */
299 #define XT_XTP_CL_LIST 1
300 #define XT_XTP_CL_REMOVE 2
302 /* XTP cookie actions */
303 #define XT_XTP_FL_LIST 1
304 #define XT_XTP_FL_REMOVE 2
306 /* xtp tab meanings - identifies which tabs have xtp pages in */
307 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
308 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
309 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
310 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
311 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
313 /* actions */
314 #define XT_MOVE_INVALID (0)
315 #define XT_MOVE_DOWN (1)
316 #define XT_MOVE_UP (2)
317 #define XT_MOVE_BOTTOM (3)
318 #define XT_MOVE_TOP (4)
319 #define XT_MOVE_PAGEDOWN (5)
320 #define XT_MOVE_PAGEUP (6)
321 #define XT_MOVE_HALFDOWN (7)
322 #define XT_MOVE_HALFUP (8)
323 #define XT_MOVE_LEFT (9)
324 #define XT_MOVE_FARLEFT (10)
325 #define XT_MOVE_RIGHT (11)
326 #define XT_MOVE_FARRIGHT (12)
328 #define XT_TAB_LAST (-4)
329 #define XT_TAB_FIRST (-3)
330 #define XT_TAB_PREV (-2)
331 #define XT_TAB_NEXT (-1)
332 #define XT_TAB_INVALID (0)
333 #define XT_TAB_NEW (1)
334 #define XT_TAB_DELETE (2)
335 #define XT_TAB_DELQUIT (3)
336 #define XT_TAB_OPEN (4)
337 #define XT_TAB_UNDO_CLOSE (5)
339 #define XT_NAV_INVALID (0)
340 #define XT_NAV_BACK (1)
341 #define XT_NAV_FORWARD (2)
342 #define XT_NAV_RELOAD (3)
343 #define XT_NAV_RELOAD_CACHE (4)
345 #define XT_FOCUS_INVALID (0)
346 #define XT_FOCUS_URI (1)
347 #define XT_FOCUS_SEARCH (2)
349 #define XT_SEARCH_INVALID (0)
350 #define XT_SEARCH_NEXT (1)
351 #define XT_SEARCH_PREV (2)
353 #define XT_PASTE_CURRENT_TAB (0)
354 #define XT_PASTE_NEW_TAB (1)
356 #define XT_FONT_SET (0)
358 #define XT_WL_TOGGLE (1<<0)
359 #define XT_WL_ENABLE (1<<1)
360 #define XT_WL_DISABLE (1<<2)
361 #define XT_WL_FQDN (1<<3) /* default */
362 #define XT_WL_TOPLEVEL (1<<4)
364 #define XT_CMD_OPEN (0)
365 #define XT_CMD_OPEN_CURRENT (1)
366 #define XT_CMD_TABNEW (2)
367 #define XT_CMD_TABNEW_CURRENT (3)
369 /* mime types */
370 struct mime_type {
371 char *mt_type;
372 char *mt_action;
373 int mt_default;
374 TAILQ_ENTRY(mime_type) entry;
376 TAILQ_HEAD(mime_type_list, mime_type);
378 /* uri aliases */
379 struct alias {
380 char *a_name;
381 char *a_uri;
382 TAILQ_ENTRY(alias) entry;
384 TAILQ_HEAD(alias_list, alias);
386 /* settings that require restart */
387 int showtabs = 1; /* show tabs on notebook */
388 int showurl = 1; /* show url toolbar on notebook */
389 int tabless = 0; /* allow only 1 tab */
390 int enable_socket = 0;
391 int single_instance = 0; /* only allow one xxxterm to run */
392 int fancy_bar = 1; /* fancy toolbar */
394 /* runtime settings */
395 int ctrl_click_focus = 0; /* ctrl click gets focus */
396 int cookies_enabled = 1; /* enable cookies */
397 int read_only_cookies = 0; /* enable to not write cookies */
398 int enable_scripts = 0;
399 int enable_plugins = 0;
400 int default_font_size = 12;
401 int window_height = 768;
402 int window_width = 1024;
403 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
404 unsigned refresh_interval = 10; /* download refresh interval */
405 int enable_cookie_whitelist = 1;
406 int enable_js_whitelist = 1;
407 time_t session_timeout = 3600; /* cookie session timeout */
408 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
409 char *ssl_ca_file = NULL;
410 char *resource_dir = NULL;
411 gboolean ssl_strict_certs = FALSE;
412 int append_next = 1; /* append tab after current tab */
413 char *home = NULL;
414 char *search_string = NULL;
415 char *http_proxy = NULL;
416 char download_dir[PATH_MAX];
417 char runtime_settings[PATH_MAX]; /* override of settings */
418 int allow_volatile_cookies = 0;
419 int save_global_history = 0; /* save global history to disk */
420 char *user_agent = NULL;
421 int save_rejected_cookies = 0;
423 struct settings;
424 int set_download_dir(struct settings *, char *);
425 int set_runtime_dir(struct settings *, char *);
426 int set_cookie_policy(struct settings *, char *);
427 int add_alias(struct settings *, char *);
428 int add_mime_type(struct settings *, char *);
429 int add_cookie_wl(struct settings *, char *);
430 int add_js_wl(struct settings *, char *);
431 void button_set_stockid(GtkWidget *, char *);
432 GtkWidget * create_button(char *, char *, int);
434 char *get_cookie_policy(struct settings *);
436 char *get_download_dir(struct settings *);
437 char *get_runtime_dir(struct settings *);
439 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
440 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
441 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
442 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
444 struct special {
445 int (*set)(struct settings *, char *);
446 char *(*get)(struct settings *);
447 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
450 struct special s_cookie = {
451 set_cookie_policy,
452 get_cookie_policy,
453 NULL
456 struct special s_alias = {
457 add_alias,
458 NULL,
459 walk_alias
462 struct special s_mime = {
463 add_mime_type,
464 NULL,
465 walk_mime_type
468 struct special s_js = {
469 add_js_wl,
470 NULL,
471 walk_js_wl
474 struct special s_cookie_wl = {
475 add_cookie_wl,
476 NULL,
477 walk_cookie_wl
480 struct special s_download_dir = {
481 set_download_dir,
482 get_download_dir,
483 NULL
486 struct settings {
487 char *name;
488 int type;
489 #define XT_S_INVALID (0)
490 #define XT_S_INT (1)
491 #define XT_S_STR (2)
492 uint32_t flags;
493 #define XT_SF_RESTART (1<<0)
494 #define XT_SF_RUNTIME (1<<1)
495 int *ival;
496 char **sval;
497 struct special *s;
498 } rs[] = {
499 { "append_next", XT_S_INT, 0 , &append_next, NULL, NULL },
500 { "allow_volatile_cookies", XT_S_INT, 0 , &allow_volatile_cookies, NULL, NULL },
501 { "cookies_enabled", XT_S_INT, 0 , &cookies_enabled, NULL, NULL },
502 { "cookie_policy", XT_S_INT, 0 , NULL, NULL, &s_cookie },
503 { "ctrl_click_focus", XT_S_INT, 0 , &ctrl_click_focus, NULL, NULL },
504 { "default_font_size", XT_S_INT, 0 , &default_font_size, NULL, NULL },
505 { "download_dir", XT_S_STR, 0 , NULL, NULL, &s_download_dir },
506 { "enable_cookie_whitelist", XT_S_INT, 0 , &enable_cookie_whitelist, NULL, NULL },
507 { "enable_js_whitelist", XT_S_INT, 0 , &enable_js_whitelist, NULL, NULL },
508 { "enable_plugins", XT_S_INT, 0 , &enable_plugins, NULL, NULL },
509 { "enable_scripts", XT_S_INT, 0 , &enable_scripts, NULL, NULL },
510 { "enable_socket", XT_S_INT, XT_SF_RESTART , &enable_socket, NULL, NULL },
511 { "fancy_bar", XT_S_INT, XT_SF_RESTART , &fancy_bar, NULL, NULL },
512 { "home", XT_S_STR, 0 , NULL, &home, NULL },
513 { "http_proxy", XT_S_STR, 0 , NULL, &http_proxy, NULL },
514 { "icon_size", XT_S_INT, 0 , &icon_size, NULL, NULL },
515 { "read_only_cookies", XT_S_INT, 0 , &read_only_cookies, NULL, NULL },
516 { "refresh_interval", XT_S_INT, 0 , &refresh_interval, NULL, NULL },
517 { "resource_dir", XT_S_STR, 0 , NULL, &resource_dir, NULL },
518 { "search_string", XT_S_STR, 0 , NULL, &search_string, NULL },
519 { "session_timeout", XT_S_INT, 0 , &session_timeout, NULL, NULL },
520 { "save_global_history", XT_S_INT, XT_SF_RESTART , &save_global_history, NULL, NULL },
521 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART , &save_rejected_cookies, NULL, NULL },
522 { "single_instance", XT_S_INT, XT_SF_RESTART , &single_instance, NULL, NULL },
523 { "ssl_ca_file", XT_S_STR, 0 , NULL, &ssl_ca_file, NULL },
524 { "ssl_strict_certs", XT_S_INT, 0 , &ssl_strict_certs, NULL, NULL },
525 { "user_agent", XT_S_STR, 0 , NULL, &user_agent, NULL },
526 { "window_height", XT_S_INT, 0 , &window_height, NULL, NULL },
527 { "window_width", XT_S_INT, 0 , &window_width, NULL, NULL },
529 /* runtime settings */
530 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
531 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
532 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
533 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
536 /* globals */
537 extern char *__progname;
538 char **start_argv;
539 struct passwd *pwd;
540 GtkWidget *main_window;
541 GtkNotebook *notebook;
542 GtkWidget *arrow, *abtn;
543 struct tab_list tabs;
544 struct history_list hl;
545 struct download_list downloads;
546 struct domain_list c_wl;
547 struct domain_list js_wl;
548 struct undo_tailq undos;
549 int undo_count;
550 int updating_dl_tabs = 0;
551 int updating_hl_tabs = 0;
552 int updating_cl_tabs = 0;
553 int updating_fl_tabs = 0;
554 char *global_search;
555 uint64_t blocked_cookies = 0;
557 void
558 hide_oops(struct tab *t)
560 gtk_widget_hide(t->oops);
562 void
563 hide_cmd(struct tab *t)
565 gtk_widget_hide(t->cmd);
567 void
568 show_cmd(struct tab *t)
570 gtk_widget_hide(t->oops);
571 gtk_widget_show(t->cmd);
573 void
574 show_oops(struct tab *t, char *msg)
576 DNPRINTF(XT_D_CMD,"show_oops(%d, '%s')\n",t->tab_id, msg);
577 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
578 gtk_widget_hide(t->cmd);
579 gtk_widget_show(t->oops);
581 char *
582 get_as_string(struct settings *s)
584 char *r = NULL;
586 if (s == NULL)
587 return (NULL);
589 if (s->s) {
590 if (s->s->get)
591 r = s->s->get(s);
592 else
593 warnx("get_as_string skip %s\n", s->name);
594 } else if (s->type == XT_S_INT)
595 r = g_strdup_printf("%d", *s->ival);
596 else if (s->type == XT_S_STR)
597 r = g_strdup(*s->sval);
598 else
599 r = g_strdup_printf("INVALID TYPE");
601 return (r);
604 void
605 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
607 int i;
608 char *s;
610 for (i = 0; i < LENGTH(rs); i++) {
611 if (rs[i].s && rs[i].s->walk)
612 rs[i].s->walk(&rs[i], cb, cb_args);
613 else {
614 s = get_as_string(&rs[i]);
615 cb(&rs[i], s, cb_args);
616 g_free(s);
622 set_cookie_policy(struct settings *s, char *val)
624 if (!strcmp(val, "no3rdparty"))
625 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
626 else if (!strcmp(val, "accept"))
627 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
628 else if (!strcmp(val, "reject"))
629 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
630 else
631 return (1);
633 return (0);
636 char *
637 get_cookie_policy(struct settings *s)
639 char *r = NULL;
641 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
642 r = g_strdup("no3rdparty");
643 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
644 r = g_strdup("accept");
645 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
646 r = g_strdup("reject");
647 else
648 return (NULL);
650 return (r);
653 char *
654 get_download_dir(struct settings *s)
656 if (download_dir[0] == '\0')
657 return (0);
658 return (g_strdup(download_dir));
662 set_download_dir(struct settings *s, char *val)
664 if (val[0] == '~')
665 snprintf(download_dir, sizeof download_dir, "%s/%s",
666 pwd->pw_dir, &val[1]);
667 else
668 strlcpy(download_dir, val, sizeof download_dir);
670 return (0);
674 * Session IDs.
675 * We use these to prevent people putting xxxt:// URLs on
676 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
678 #define XT_XTP_SES_KEY_SZ 8
679 #define XT_XTP_SES_KEY_HEX_FMT \
680 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
681 char *dl_session_key; /* downloads */
682 char *hl_session_key; /* history list */
683 char *cl_session_key; /* cookie list */
684 char *fl_session_key; /* favorites list */
686 char work_dir[PATH_MAX];
687 char certs_dir[PATH_MAX];
688 char cookie_file[PATH_MAX];
689 SoupURI *proxy_uri = NULL;
690 SoupSession *session;
691 SoupCookieJar *s_cookiejar;
692 SoupCookieJar *p_cookiejar;
693 char rc_fname[PATH_MAX];
695 struct mime_type_list mtl;
696 struct alias_list aliases;
698 /* protos */
699 void create_new_tab(char *, struct undo *, int);
700 void delete_tab(struct tab *);
701 void adjustfont_webkit(struct tab *, int);
702 int run_script(struct tab *, char *);
703 int download_rb_cmp(struct download *, struct download *);
704 int xtp_page_hl(struct tab *t, struct karg *args);
705 int xtp_page_dl(struct tab *t, struct karg *args);
706 int xtp_page_cl(struct tab *t, struct karg *args);
707 int xtp_page_fl(struct tab *t, struct karg *args);
710 history_rb_cmp(struct history *h1, struct history *h2)
712 return (strcmp(h1->uri, h2->uri));
714 RB_GENERATE(history_list, history, entry, history_rb_cmp);
717 domain_rb_cmp(struct domain *d1, struct domain *d2)
719 return (strcmp(d1->d, d2->d));
721 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
724 * generate a session key to secure xtp commands.
725 * pass in a ptr to the key in question and it will
726 * be modified in place.
728 void
729 generate_xtp_session_key(char **key)
731 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
733 /* free old key */
734 if (*key)
735 g_free(*key);
737 /* make a new one */
738 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
739 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
740 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
741 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
743 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
747 * validate a xtp session key.
748 * return 1 if OK
751 validate_xtp_session_key(char *trusted, char *untrusted)
753 if (strcmp(trusted, untrusted) != 0) {
754 warn("%s: xtp session key mismatch possible spoof", __func__);
755 return (0);
758 return (1);
762 download_rb_cmp(struct download *e1, struct download *e2)
764 return (e1->id < e2->id ? -1 : e1->id > e2->id);
766 RB_GENERATE(download_list, download, entry, download_rb_cmp);
768 struct valid_url_types {
769 char *type;
770 } vut[] = {
771 { "http://" },
772 { "https://" },
773 { "ftp://" },
774 { "file://" },
775 { XT_XTP_STR },
779 valid_url_type(char *url)
781 int i;
783 for (i = 0; i < LENGTH(vut); i++)
784 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
785 return (0);
787 return (1);
790 void
791 print_cookie(char *msg, SoupCookie *c)
793 if (c == NULL)
794 return;
796 if (msg)
797 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
798 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
799 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
800 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
801 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
802 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
803 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
804 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
805 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
806 DNPRINTF(XT_D_COOKIE, "====================================\n");
809 void
810 walk_alias(struct settings *s,
811 void (*cb)(struct settings *, char *, void *), void *cb_args)
813 struct alias *a;
814 char *str;
816 if (s == NULL || cb == NULL)
817 errx(1, "walk_alias");
819 TAILQ_FOREACH(a, &aliases, entry) {
820 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
821 cb(s, str, cb_args);
822 g_free(str);
826 char *
827 match_alias(char *url_in)
829 struct alias *a;
830 char *arg;
831 char *url_out = NULL;
833 arg = url_in;
834 if (strsep(&arg, " \t") == NULL)
835 errx(1, "match_alias: NULL URL");
837 TAILQ_FOREACH(a, &aliases, entry) {
838 if (!strcmp(url_in, a->a_name))
839 break;
842 if (a != NULL) {
843 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
844 a->a_name);
845 if (arg != NULL)
846 url_out = g_strdup_printf(a->a_uri, arg);
847 else
848 url_out = g_strdup(a->a_uri);
851 return (url_out);
854 char *
855 guess_url_type(char *url_in)
857 struct stat sb;
858 char *url_out = NULL;
860 url_out = match_alias(url_in);
861 if (url_out != NULL)
862 return (url_out);
864 /* XXX not sure about this heuristic */
865 if (stat(url_in, &sb) == 0)
866 url_out = g_strdup_printf("file://%s", url_in);
867 else
868 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
870 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
872 return (url_out);
876 add_alias(struct settings *s, char *line)
878 char *l, *alias;
879 struct alias *a;
881 if (line == NULL)
882 errx(1, "add_alias");
883 l = line;
885 a = g_malloc(sizeof(*a));
887 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL)
888 errx(1, "add_alias: incomplete alias definition");
890 if (strlen(alias) == 0 || strlen(l) == 0)
891 errx(1, "add_alias: invalid alias definition");
893 a->a_name = g_strdup(alias);
894 a->a_uri = g_strdup(l);
896 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
898 TAILQ_INSERT_TAIL(&aliases, a, entry);
900 return (0);
904 add_mime_type(struct settings *s, char *line)
906 char *mime_type;
907 char *l = NULL;
908 struct mime_type *m;
910 /* XXX this could be smarter */
912 if (line == NULL)
913 errx(1, "add_mime_type");
914 l = line;
916 m = g_malloc(sizeof(*m));
918 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL)
919 errx(1, "add_mime_type: invalid mime_type");
921 if (mime_type[strlen(mime_type) - 1] == '*') {
922 mime_type[strlen(mime_type) - 1] = '\0';
923 m->mt_default = 1;
924 } else
925 m->mt_default = 0;
927 if (strlen(mime_type) == 0 || strlen(l) == 0)
928 errx(1, "add_mime_type: invalid mime_type");
930 m->mt_type = g_strdup(mime_type);
931 m->mt_action = g_strdup(l);
933 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
934 m->mt_type, m->mt_action, m->mt_default);
936 TAILQ_INSERT_TAIL(&mtl, m, entry);
938 return (0);
941 struct mime_type *
942 find_mime_type(char *mime_type)
944 struct mime_type *m, *def = NULL, *rv = NULL;
946 TAILQ_FOREACH(m, &mtl, entry) {
947 if (m->mt_default &&
948 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
949 def = m;
951 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
952 rv = m;
953 break;
957 if (rv == NULL)
958 rv = def;
960 return (rv);
963 void
964 walk_mime_type(struct settings *s,
965 void (*cb)(struct settings *, char *, void *), void *cb_args)
967 struct mime_type *m;
968 char *str;
970 if (s == NULL || cb == NULL)
971 errx(1, "walk_mime_type");
973 TAILQ_FOREACH(m, &mtl, entry) {
974 str = g_strdup_printf("%s%s --> %s",
975 m->mt_type,
976 m->mt_default ? "*" : "",
977 m->mt_action);
978 cb(s, str, cb_args);
979 g_free(str);
983 void
984 wl_add(char *str, struct domain_list *wl, int handy)
986 struct domain *d;
987 int add_dot = 0;
989 if (str == NULL || wl == NULL)
990 return;
991 if (strlen(str) < 2)
992 return;
994 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
996 /* treat *.moo.com the same as .moo.com */
997 if (str[0] == '*' && str[1] == '.')
998 str = &str[1];
999 else if (str[0] == '.')
1000 str = &str[0];
1001 else
1002 add_dot = 1;
1004 d = g_malloc(sizeof *d);
1005 if (add_dot)
1006 d->d = g_strdup_printf(".%s", str);
1007 else
1008 d->d = g_strdup(str);
1009 d->handy = handy;
1011 if (RB_INSERT(domain_list, wl, d))
1012 goto unwind;
1014 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1015 return;
1016 unwind:
1017 if (d) {
1018 if (d->d)
1019 g_free(d->d);
1020 g_free(d);
1025 add_cookie_wl(struct settings *s, char *entry)
1027 wl_add(entry, &c_wl, 1);
1028 return (0);
1031 void
1032 walk_cookie_wl(struct settings *s,
1033 void (*cb)(struct settings *, char *, void *), void *cb_args)
1035 struct domain *d;
1037 if (s == NULL || cb == NULL)
1038 errx(1, "walk_cookie_wl");
1040 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1041 cb(s, d->d, cb_args);
1044 void
1045 walk_js_wl(struct settings *s,
1046 void (*cb)(struct settings *, char *, void *), void *cb_args)
1048 struct domain *d;
1050 if (s == NULL || cb == NULL)
1051 errx(1, "walk_js_wl");
1053 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1054 cb(s, d->d, cb_args);
1058 add_js_wl(struct settings *s, char *entry)
1060 wl_add(entry, &js_wl, 1 /* persistent */);
1061 return (0);
1064 struct domain *
1065 wl_find(const gchar *search, struct domain_list *wl)
1067 int i;
1068 struct domain *d = NULL, dfind;
1069 gchar *s = NULL;
1071 if (search == NULL || wl == NULL)
1072 return (NULL);
1073 if (strlen(search) < 2)
1074 return (NULL);
1076 if (search[0] != '.')
1077 s = g_strdup_printf(".%s", search);
1078 else
1079 s = g_strdup(search);
1081 for (i = strlen(s) - 1; i >= 0; i--) {
1082 if (s[i] == '.') {
1083 dfind.d = &s[i];
1084 d = RB_FIND(domain_list, wl, &dfind);
1085 if (d)
1086 goto done;
1090 done:
1091 if (s)
1092 g_free(s);
1094 return (d);
1097 struct domain *
1098 wl_find_uri(const gchar *s, struct domain_list *wl)
1100 int i;
1101 char *ss;
1102 struct domain *r;
1104 if (s == NULL || wl == NULL)
1105 return (NULL);
1107 if (!strncmp(s, "http://", strlen("http://")))
1108 s = &s[strlen("http://")];
1109 else if (!strncmp(s, "https://", strlen("https://")))
1110 s = &s[strlen("https://")];
1112 if (strlen(s) < 2)
1113 return (NULL);
1115 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1116 /* chop string at first slash */
1117 if (s[i] == '/' || s[i] == '\0') {
1118 ss = g_strdup(s);
1119 ss[i] = '\0';
1120 r = wl_find(ss, wl);
1121 g_free(ss);
1122 return (r);
1125 return (NULL);
1128 char *
1129 get_toplevel_domain(char *domain)
1131 char *s;
1132 int found = 0;
1134 if (domain == NULL)
1135 return (NULL);
1136 if (strlen(domain) < 2)
1137 return (NULL);
1139 s = &domain[strlen(domain) - 1];
1140 while (s != domain) {
1141 if (*s == '.') {
1142 found++;
1143 if (found == 2)
1144 return (s);
1146 s--;
1149 if (found)
1150 return (domain);
1152 return (NULL);
1155 #define WS "\n= \t"
1156 void
1157 config_parse(char *filename, int runtime)
1159 FILE *config, *f;
1160 char *line, *cp, *var, *val;
1161 size_t len, lineno = 0;
1162 int i, handled, *p;
1163 char **s, file[PATH_MAX];
1164 struct stat sb;
1166 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1168 if (filename == NULL)
1169 return;
1171 if (runtime && runtime_settings[0] != '\0') {
1172 snprintf(file, sizeof file, "%s/%s",
1173 work_dir, runtime_settings);
1174 if (stat(file, &sb)) {
1175 warnx("runtime file doesn't exist, creating it");
1176 if ((f = fopen(file, "w")) == NULL)
1177 err(1, "runtime");
1178 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1179 fclose(f);
1181 } else
1182 strlcpy(file, filename, sizeof file);
1184 if ((config = fopen(file, "r")) == NULL) {
1185 warn("config_parse: cannot open %s", filename);
1186 return;
1189 for (;;) {
1190 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1191 if (feof(config) || ferror(config))
1192 break;
1194 cp = line;
1195 cp += (long)strspn(cp, WS);
1196 if (cp[0] == '\0') {
1197 /* empty line */
1198 free(line);
1199 continue;
1202 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1203 break;
1205 cp += (long)strspn(cp, WS);
1207 if ((val = strsep(&cp, "\0")) == NULL)
1208 break;
1210 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1212 /* get settings */
1213 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1214 if (strcmp(var, rs[i].name))
1215 continue;
1217 if (rs[i].s) {
1218 if (rs[i].s->set(&rs[i], val))
1219 errx(1, "invalid value for %s", var);
1220 handled = 1;
1221 break;
1222 } else {
1223 switch (rs[i].type) {
1224 case XT_S_INT:
1225 p = rs[i].ival;
1226 *p = atoi(val);
1227 handled = 1;
1228 break;
1229 case XT_S_STR:
1230 s = rs[i].sval;
1231 if (s == NULL)
1232 errx(1, "invalid sval for %s",
1233 rs[i].name);
1234 if (*s)
1235 g_free(*s);
1236 *s = g_strdup(val);
1237 handled = 1;
1238 break;
1239 case XT_S_INVALID:
1240 default:
1241 errx(1, "invalid type for %s", var);
1244 break;
1246 if (handled == 0)
1247 errx(1, "invalid conf file entry: %s=%s", var, val);
1249 free(line);
1252 fclose(config);
1255 char *
1256 js_ref_to_string(JSContextRef context, JSValueRef ref)
1258 char *s = NULL;
1259 size_t l;
1260 JSStringRef jsref;
1262 jsref = JSValueToStringCopy(context, ref, NULL);
1263 if (jsref == NULL)
1264 return (NULL);
1266 l = JSStringGetMaximumUTF8CStringSize(jsref);
1267 s = g_malloc(l);
1268 if (s)
1269 JSStringGetUTF8CString(jsref, s, l);
1270 JSStringRelease(jsref);
1272 return (s);
1275 void
1276 disable_hints(struct tab *t)
1278 bzero(t->hint_buf, sizeof t->hint_buf);
1279 bzero(t->hint_num, sizeof t->hint_num);
1280 run_script(t, "vimprobable_clear()");
1281 t->hints_on = 0;
1282 t->hint_mode = XT_HINT_NONE;
1285 void
1286 enable_hints(struct tab *t)
1288 bzero(t->hint_buf, sizeof t->hint_buf);
1289 run_script(t, "vimprobable_show_hints()");
1290 t->hints_on = 1;
1291 t->hint_mode = XT_HINT_NONE;
1294 #define XT_JS_OPEN ("open;")
1295 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1296 #define XT_JS_FIRE ("fire;")
1297 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1298 #define XT_JS_FOUND ("found;")
1299 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1302 run_script(struct tab *t, char *s)
1304 JSGlobalContextRef ctx;
1305 WebKitWebFrame *frame;
1306 JSStringRef str;
1307 JSValueRef val, exception;
1308 char *es, buf[128];
1310 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1311 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1313 frame = webkit_web_view_get_main_frame(t->wv);
1314 ctx = webkit_web_frame_get_global_context(frame);
1316 str = JSStringCreateWithUTF8CString(s);
1317 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1318 NULL, 0, &exception);
1319 JSStringRelease(str);
1321 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1322 if (val == NULL) {
1323 es = js_ref_to_string(ctx, exception);
1324 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1325 g_free(es);
1326 return (1);
1327 } else {
1328 es = js_ref_to_string(ctx, val);
1329 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1331 /* handle return value right here */
1332 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1333 disable_hints(t);
1334 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1337 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1338 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1339 &es[XT_JS_FIRE_LEN]);
1340 run_script(t, buf);
1341 disable_hints(t);
1344 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1345 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1346 disable_hints(t);
1349 g_free(es);
1352 return (0);
1356 hint(struct tab *t, struct karg *args)
1359 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1361 if (t->hints_on == 0)
1362 enable_hints(t);
1363 else
1364 disable_hints(t);
1366 return (0);
1369 /* Doesn't work fully, due to the following bug:
1370 * https://bugs.webkit.org/show_bug.cgi?id=51747
1373 restore_global_history(void)
1375 char file[PATH_MAX];
1376 FILE *f;
1377 struct history *h;
1378 gchar *uri;
1379 gchar *title;
1381 snprintf(file, sizeof file, "%s/%s/%s",
1382 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1384 if ((f = fopen(file, "r")) == NULL) {
1385 warnx("%s: fopen", __func__);
1386 return (1);
1389 for (;;) {
1390 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1391 if (feof(f) || ferror(f))
1392 break;
1394 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1395 if (feof(f) || ferror(f)) {
1396 free(uri);
1397 warnx("%s: broken history file\n", __func__);
1398 return (1);
1401 if (uri && strlen(uri) && title && strlen(title)) {
1402 webkit_web_history_item_new_with_data(uri, title);
1403 h = g_malloc(sizeof(struct history));
1404 h->uri = g_strdup(uri);
1405 h->title = g_strdup(title);
1406 RB_INSERT(history_list, &hl, h);
1407 } else {
1408 warnx("%s: failed to restore history\n", __func__);
1409 free(uri);
1410 free(title);
1411 return (1);
1414 free(uri);
1415 free(title);
1416 uri = NULL;
1417 title = NULL;
1420 return (0);
1424 save_global_history_to_disk(void)
1426 char file[PATH_MAX];
1427 FILE *f;
1428 struct history *h;
1430 snprintf(file, sizeof file, "%s/%s/%s",
1431 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1433 if ((f = fopen(file, "w")) == NULL) {
1434 warnx("%s: fopen", __func__);
1435 return (1);
1438 RB_FOREACH_REVERSE(h, history_list, &hl) {
1439 if (h->uri && h->title)
1440 fprintf(f, "%s\n%s\n", h->uri, h->title);
1443 fclose(f);
1445 return (0);
1449 quit(struct tab *t, struct karg *args)
1451 if (save_global_history)
1452 save_global_history_to_disk();
1454 gtk_main_quit();
1456 return (1);
1460 restore_saved_tabs(void)
1462 char file[PATH_MAX];
1463 FILE *f;
1464 char *uri = NULL;
1465 int empty_saved_tabs_file = 1;
1466 int unlink_file = 0;
1467 struct stat sb;
1469 snprintf(file, sizeof file, "%s/%s/%s",
1470 pwd->pw_dir, XT_DIR, XT_RESTART_TABS_FILE);
1471 if (stat(file, &sb) == -1)
1472 snprintf(file, sizeof file, "%s/%s/%s",
1473 pwd->pw_dir, XT_DIR, XT_SAVED_TABS_FILE);
1474 else
1475 unlink_file = 1;
1477 if ((f = fopen(file, "r")) == NULL)
1478 return (empty_saved_tabs_file);
1480 for (;;) {
1481 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1482 if (feof(f) || ferror(f))
1483 break;
1485 if (uri && strlen(uri)) {
1486 create_new_tab(uri, NULL, 1);
1487 empty_saved_tabs_file = 0;
1490 free(uri);
1491 uri = NULL;
1494 fclose(f);
1496 if (unlink_file)
1497 unlink(file);
1499 return (empty_saved_tabs_file);
1503 save_tabs(struct tab *t, struct karg *a)
1505 char file[PATH_MAX];
1506 FILE *f;
1507 struct tab *ti;
1508 WebKitWebFrame *frame;
1509 const gchar *uri;
1511 if (a == NULL)
1512 return (1);
1513 if (a->s == NULL)
1514 return (1);
1516 snprintf(file, sizeof file, "%s/%s/%s",
1517 pwd->pw_dir, XT_DIR, a->s);
1519 if ((f = fopen(file, "w")) == NULL) {
1520 warn("save_tabs");
1521 return (1);
1524 TAILQ_FOREACH(ti, &tabs, entry) {
1525 frame = webkit_web_view_get_main_frame(ti->wv);
1526 uri = webkit_web_frame_get_uri(frame);
1527 if (uri && strlen(uri) > 0)
1528 fprintf(f, "%s\n", uri);
1531 fclose(f);
1533 return (0);
1537 save_tabs_and_quit(struct tab *t, struct karg *args)
1539 struct karg a;
1541 a.s = XT_SAVED_TABS_FILE;
1542 save_tabs(t, &a);
1543 quit(t, NULL);
1545 return (1);
1549 yank_uri(struct tab *t, struct karg *args)
1551 WebKitWebFrame *frame;
1552 const gchar *uri;
1553 GtkClipboard *clipboard;
1555 frame = webkit_web_view_get_main_frame(t->wv);
1556 uri = webkit_web_frame_get_uri(frame);
1557 if (!uri)
1558 return (1);
1560 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1561 gtk_clipboard_set_text(clipboard, uri, -1);
1563 return (0);
1566 struct paste_args {
1567 struct tab *t;
1568 int i;
1571 void
1572 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1574 struct paste_args *pap;
1576 if (data == NULL)
1577 return;
1579 pap = (struct paste_args *)data;
1581 switch(pap->i) {
1582 case XT_PASTE_CURRENT_TAB:
1583 webkit_web_view_load_uri(pap->t->wv, text);
1584 break;
1585 case XT_PASTE_NEW_TAB:
1586 create_new_tab((char *)text, NULL, 1);
1587 break;
1590 g_free(pap);
1594 paste_uri(struct tab *t, struct karg *args)
1596 GtkClipboard *clipboard;
1597 struct paste_args *pap;
1599 pap = g_malloc(sizeof(struct paste_args));
1601 pap->t = t;
1602 pap->i = args->i;
1604 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1605 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1607 return (0);
1610 char *
1611 find_domain(const char *s, int add_dot)
1613 int i;
1614 char *r = NULL, *ss = NULL;
1616 if (s == NULL)
1617 return (NULL);
1619 if (!strncmp(s, "http://", strlen("http://")))
1620 s = &s[strlen("http://")];
1621 else if (!strncmp(s, "https://", strlen("https://")))
1622 s = &s[strlen("https://")];
1624 if (strlen(s) < 2)
1625 return (NULL);
1627 ss = g_strdup(s);
1628 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
1629 /* chop string at first slash */
1630 if (ss[i] == '/' || ss[i] == '\0') {
1631 ss[i] = '\0';
1632 if (add_dot)
1633 r = g_strdup_printf(".%s", ss);
1634 else
1635 r = g_strdup(ss);
1636 break;
1638 g_free(ss);
1640 return (r);
1644 toggle_cwl(struct tab *t, struct karg *args)
1646 WebKitWebFrame *frame;
1647 struct domain *d;
1648 char *uri;
1649 char *dom = NULL, *dom_toggle = NULL;
1650 int es;
1652 if (args == NULL)
1653 return (0);
1655 frame = webkit_web_view_get_main_frame(t->wv);
1656 uri = (char *)webkit_web_frame_get_uri(frame);
1657 dom = find_domain(uri, 1);
1658 d = wl_find(dom, &c_wl);
1659 if (d == NULL)
1660 es = 0;
1661 else
1662 es = 1;
1664 if (args->i & XT_WL_TOGGLE)
1665 es = !es;
1666 else if ((args->i & XT_WL_ENABLE) && es != 1)
1667 es = 1;
1668 else if ((args->i & XT_WL_DISABLE) && es != 0)
1669 es = 0;
1671 if (args->i & XT_WL_TOPLEVEL)
1672 dom_toggle = get_toplevel_domain(dom);
1673 else
1674 dom_toggle = dom;
1676 if (es) {
1677 /* enable cookies for domain */
1678 wl_add(dom_toggle, &c_wl, 0);
1679 } else {
1680 /* disable cookies for domain */
1681 RB_REMOVE(domain_list, &c_wl, d);
1684 webkit_web_view_reload(t->wv);
1686 g_free(dom);
1687 return (0);
1691 toggle_js(struct tab *t, struct karg *args)
1693 int es;
1694 WebKitWebFrame *frame;
1695 const gchar *uri;
1696 struct domain *d;
1697 char *dom = NULL, *dom_toggle = NULL;
1699 if (args == NULL)
1700 return (0);
1702 g_object_get((GObject *)t->settings,
1703 "enable-scripts", &es, (char *)NULL);
1704 if (args->i & XT_WL_TOGGLE)
1705 es = !es;
1706 else if ((args->i & XT_WL_ENABLE) && es != 1)
1707 es = 1;
1708 else if ((args->i & XT_WL_DISABLE) && es != 0)
1709 es = 0;
1710 else
1711 return (0);
1713 frame = webkit_web_view_get_main_frame(t->wv);
1714 uri = (char *)webkit_web_frame_get_uri(frame);
1715 dom = find_domain(uri, 1);
1716 if (uri == NULL || dom == NULL) {
1717 webkit_web_view_load_string(t->wv,
1718 "<html><body>Can't toggle domain in JavaScript white list</body></html>",
1719 NULL,
1720 NULL,
1721 NULL);
1722 goto done;
1725 if (args->i & XT_WL_TOPLEVEL)
1726 dom_toggle = get_toplevel_domain(dom);
1727 else
1728 dom_toggle = dom;
1730 if (es) {
1731 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
1732 wl_add(dom_toggle, &js_wl, 0 /* session */);
1733 } else {
1734 d = wl_find(dom_toggle, &js_wl);
1735 if (d)
1736 RB_REMOVE(domain_list, &js_wl, d);
1737 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
1739 g_object_set((GObject *)t->settings,
1740 "enable-scripts", es, (char *)NULL);
1741 webkit_web_view_set_settings(t->wv, t->settings);
1742 webkit_web_view_reload(t->wv);
1743 done:
1744 if (dom)
1745 g_free(dom);
1746 return (0);
1749 void
1750 js_toggle_cb(GtkWidget *w, struct tab *t)
1752 struct karg a;
1754 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
1755 toggle_js(t, &a);
1759 toggle_src(struct tab *t, struct karg *args)
1761 gboolean mode;
1763 if (t == NULL)
1764 return (0);
1766 mode = webkit_web_view_get_view_source_mode(t->wv);
1767 webkit_web_view_set_view_source_mode(t->wv, !mode);
1768 webkit_web_view_reload(t->wv);
1770 return (0);
1774 focus(struct tab *t, struct karg *args)
1776 if (t == NULL || args == NULL)
1777 errx(1, "focus");
1779 if (args->i == XT_FOCUS_URI)
1780 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
1781 else if (args->i == XT_FOCUS_SEARCH)
1782 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
1784 return (0);
1788 stats(struct tab *t, struct karg *args)
1790 char *stats, *s, line[64 * 1024];
1791 uint64_t line_count = 0;
1792 FILE *r_cookie_f;
1794 if (t == NULL)
1795 errx(1, "stats");
1797 line[0] = '\0';
1798 if (save_rejected_cookies) {
1799 if ((r_cookie_f = fopen(rc_fname, "r"))) {
1800 for (;;) {
1801 s = fgets(line, sizeof line, r_cookie_f);
1802 if (s == NULL || feof(r_cookie_f) ||
1803 ferror(r_cookie_f))
1804 break;
1805 line_count++;
1807 fclose(r_cookie_f);
1808 snprintf(line, sizeof line,
1809 "<br>Cookies blocked(*) total: %llu", line_count);
1813 stats = g_strdup_printf(XT_DOCTYPE
1814 "<html>"
1815 "<head>"
1816 "<title>Statistics</title>"
1817 "</head>"
1818 "<h1>Statistics</h1>"
1819 "<body>"
1820 "Cookies blocked(*) this session: %llu"
1821 "%s"
1822 "<p><small><b>*</b> results vary based on settings"
1823 "</body>"
1824 "</html>",
1825 blocked_cookies,
1826 line);
1828 webkit_web_view_load_string(t->wv, stats, NULL, NULL, "");
1829 g_free(stats);
1831 return (0);
1835 about(struct tab *t, struct karg *args)
1837 char *about;
1839 if (t == NULL)
1840 errx(1, "about");
1842 about = g_strdup_printf(XT_DOCTYPE
1843 "<html>"
1844 "<head>"
1845 "<title>About</title>"
1846 "</head>"
1847 "<h1>About</h1>"
1848 "<body>"
1849 "<b>Version: %s</b><p>"
1850 "Authors:"
1851 "<ul>"
1852 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
1853 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
1854 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
1855 "</ul>"
1856 "Copyrights and licenses can be found on the XXXterm "
1857 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
1858 "</body>"
1859 "</html>",
1860 version
1863 webkit_web_view_load_string(t->wv, about, NULL, NULL, "");
1864 g_free(about);
1866 return (0);
1870 help(struct tab *t, struct karg *args)
1872 char *help;
1874 if (t == NULL)
1875 errx(1, "help");
1877 help = XT_DOCTYPE
1878 "<html>"
1879 "<head>"
1880 "<title>XXXterm</title>"
1881 "<meta http-equiv=\"REFRESH\" content=\"0;"
1882 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
1883 "</head>"
1884 "<body>"
1885 "XXXterm man page <a href=\"http://opensource.conformal.com/"
1886 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
1887 "cgi-bin/man-cgi?xxxterm</a>"
1888 "</body>"
1889 "</html>"
1892 webkit_web_view_load_string(t->wv, help, NULL, NULL, "");
1894 return (0);
1898 * update all favorite tabs apart from one. Pass NULL if
1899 * you want to update all.
1901 void
1902 update_favorite_tabs(struct tab *apart_from)
1904 struct tab *t;
1905 if (!updating_fl_tabs) {
1906 updating_fl_tabs = 1; /* stop infinite recursion */
1907 TAILQ_FOREACH(t, &tabs, entry)
1908 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
1909 && (t != apart_from))
1910 xtp_page_fl(t, NULL);
1911 updating_fl_tabs = 0;
1915 /* show a list of favorites (bookmarks) */
1917 xtp_page_fl(struct tab *t, struct karg *args)
1919 char file[PATH_MAX];
1920 FILE *f;
1921 char *uri = NULL, *title = NULL;
1922 size_t len, lineno = 0;
1923 int i, failed = 0;
1924 char *header, *body, *tmp, *html = NULL;
1926 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
1928 if (t == NULL)
1929 warn("%s: bad param", __func__);
1931 /* mark tab as favorite list */
1932 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
1934 /* new session key */
1935 if (!updating_fl_tabs)
1936 generate_xtp_session_key(&fl_session_key);
1938 /* open favorites */
1939 snprintf(file, sizeof file, "%s/%s/%s",
1940 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
1941 if ((f = fopen(file, "r")) == NULL) {
1942 warn("favorites");
1943 return (1);
1946 /* header */
1947 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
1948 "<title>Favorites</title>\n"
1949 "%s"
1950 "</head>"
1951 "<h1>Favorites</h1>\n",
1952 XT_PAGE_STYLE);
1954 /* body */
1955 body = g_strdup_printf("<div align='center'><table><tr>"
1956 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
1957 "<th style='width: 15%%'>Remove</th></tr>\n");
1959 for (i = 1;;) {
1960 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
1961 if (feof(f) || ferror(f))
1962 break;
1963 if (len == 0) {
1964 free(title);
1965 title = NULL;
1966 continue;
1969 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
1970 if (feof(f) || ferror(f)) {
1971 errx(0, "%s: can't parse favorites\n",
1972 __func__);
1973 failed = 1;
1974 break;
1977 tmp = body;
1978 body = g_strdup_printf("%s<tr>"
1979 "<td>%d</td>"
1980 "<td><a href='%s'>%s</a></td>"
1981 "<td style='text-align: center'>"
1982 "<a href='%s%d/%s/%d/%d'>X</a></td>"
1983 "</tr>\n",
1984 body, i, uri, title,
1985 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
1987 g_free(tmp);
1989 free(uri);
1990 uri = NULL;
1991 free(title);
1992 title = NULL;
1993 i++;
1995 fclose(f);
1997 /* if none, say so */
1998 if (i == 1) {
1999 tmp = body;
2000 body = g_strdup_printf("%s<tr>"
2001 "<td colspan='3' style='text-align: center'>"
2002 "No favorites - To add one use the 'favadd' command."
2003 "</td></tr>", body);
2004 g_free(tmp);
2007 if (uri)
2008 free(uri);
2009 if (title)
2010 free(title);
2012 /* render */
2013 if (!failed) {
2014 html = g_strdup_printf("%s%s</table></div></html>",
2015 header, body);
2016 webkit_web_view_load_string(t->wv, html, NULL, NULL, "");
2019 update_favorite_tabs(t);
2021 if (header)
2022 g_free(header);
2023 if (body)
2024 g_free(body);
2025 if (html)
2026 g_free(html);
2028 return (failed);
2031 char *
2032 getparams(char *cmd, char *cmp)
2034 char *rv = NULL;
2036 if (cmd && cmp) {
2037 if (!strncmp(cmd, cmp, strlen(cmp))) {
2038 rv = cmd + strlen(cmp);
2039 while (*rv == ' ')
2040 rv++;
2041 if (strlen(rv) == 0)
2042 rv = NULL;
2046 return (rv);
2049 void
2050 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2051 size_t cert_count, char *title)
2053 gnutls_datum_t cinfo;
2054 char *tmp, *header, *body, *footer;
2055 int i;
2057 header = g_strdup_printf("<title>%s</title><html><body>", title);
2058 footer = g_strdup("</body></html>");
2059 body = g_strdup("");
2061 for (i = 0; i < cert_count; i++) {
2062 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2063 &cinfo))
2064 return;
2066 tmp = body;
2067 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2068 body, i, cinfo.data);
2069 gnutls_free(cinfo.data);
2070 g_free(tmp);
2073 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2074 g_free(header);
2075 g_free(body);
2076 g_free(footer);
2077 webkit_web_view_load_string(t->wv, tmp, NULL, NULL, NULL);
2078 g_free(tmp);
2082 ca_cmd(struct tab *t, struct karg *args)
2084 FILE *f = NULL;
2085 int rv = 1, certs = 0, certs_read;
2086 struct stat sb;
2087 gnutls_datum dt;
2088 gnutls_x509_crt_t *c = NULL;
2089 char *certs_buf = NULL, *s;
2091 /* yeah yeah stat race */
2092 if (stat(ssl_ca_file, &sb)) {
2093 warn("no CA file: %s", ssl_ca_file);
2094 goto done;
2097 if ((f = fopen(ssl_ca_file, "r")) == NULL)
2098 return (1);
2100 certs_buf = g_malloc(sb.st_size + 1);
2101 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2102 warn("certs");
2103 goto done;
2105 certs_buf[sb.st_size] = '\0';
2107 s = certs_buf;
2108 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2109 certs++;
2110 s += strlen("BEGIN CERTIFICATE");
2113 bzero(&dt, sizeof dt);
2114 dt.data = certs_buf;
2115 dt.size = sb.st_size;
2116 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2117 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt, GNUTLS_X509_FMT_PEM, 0);
2118 if (certs_read <= 0) {
2119 warnx("couldn't read certs");
2120 goto done;
2122 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2123 done:
2124 if (c)
2125 g_free(c);
2126 if (certs_buf)
2127 g_free(certs_buf);
2128 if (f)
2129 fclose(f);
2131 return (rv);
2135 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2137 SoupURI *su = NULL;
2138 struct addrinfo hints, *res = NULL, *ai;
2139 int s = -1, on;
2140 char port[8];
2142 if (uri && !g_str_has_prefix(uri, "https://"))
2143 goto done;
2145 su = soup_uri_new(uri);
2146 if (su == NULL)
2147 goto done;
2148 if (!SOUP_URI_VALID_FOR_HTTP(su))
2149 goto done;
2151 snprintf(port, sizeof port, "%d", su->port);
2152 bzero(&hints, sizeof(struct addrinfo));
2153 hints.ai_flags = AI_CANONNAME;
2154 hints.ai_family = AF_UNSPEC;
2155 hints.ai_socktype = SOCK_STREAM;
2157 if (getaddrinfo(su->host, port, &hints, &res))
2158 goto done;
2160 for (ai = res; ai; ai = ai->ai_next) {
2161 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2162 continue;
2164 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2165 if (s < 0)
2166 goto done;
2167 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2168 sizeof(on)) == -1)
2169 goto done;
2171 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2172 goto done;
2175 if (domain)
2176 strlcpy(domain, su->host, domain_sz);
2177 done:
2178 if (su)
2179 soup_uri_free(su);
2180 if (res)
2181 freeaddrinfo(res);
2183 return (s);
2187 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2189 if (gsession)
2190 gnutls_deinit(gsession);
2191 if (xcred)
2192 gnutls_certificate_free_credentials(xcred);
2194 return (0);
2198 start_tls(int s, gnutls_session_t *gs, gnutls_certificate_credentials_t *xc)
2200 gnutls_certificate_credentials_t xcred;
2201 gnutls_session_t gsession;
2202 int rv = 1;
2204 if (gs == NULL || xc == NULL)
2205 goto done;
2207 *gs = NULL;
2208 *xc = NULL;
2210 gnutls_certificate_allocate_credentials(&xcred);
2211 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2212 GNUTLS_X509_FMT_PEM);
2213 gnutls_init(&gsession, GNUTLS_CLIENT);
2214 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2215 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2216 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2217 if ((rv = gnutls_handshake(gsession)) < 0) {
2218 warnx("gnutls_handshake failed %d fatal %d %s",
2220 gnutls_error_is_fatal(rv),
2221 gnutls_strerror_name(rv));
2222 stop_tls(gsession, xcred);
2223 goto done;
2226 gnutls_credentials_type_t cred;
2227 cred = gnutls_auth_get_type(gsession);
2228 if (cred != GNUTLS_CRD_CERTIFICATE) {
2229 stop_tls(gsession, xcred);
2230 goto done;
2233 *gs = gsession;
2234 *xc = xcred;
2235 rv = 0;
2236 done:
2237 return (rv);
2241 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2242 size_t *cert_count)
2244 unsigned int len;
2245 const gnutls_datum_t *cl;
2246 gnutls_x509_crt_t *all_certs;
2247 int i, rv = 1;
2249 if (certs == NULL || cert_count == NULL)
2250 goto done;
2251 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2252 goto done;
2253 cl = gnutls_certificate_get_peers(gsession, &len);
2254 if (len == 0)
2255 goto done;
2257 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2258 for (i = 0; i < len; i++) {
2259 gnutls_x509_crt_init(&all_certs[i]);
2260 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2261 GNUTLS_X509_FMT_PEM < 0)) {
2262 g_free(all_certs);
2263 goto done;
2267 *certs = all_certs;
2268 *cert_count = len;
2269 rv = 0;
2270 done:
2271 return (rv);
2274 void
2275 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2277 int i;
2279 for (i = 0; i < cert_count; i++)
2280 gnutls_x509_crt_deinit(certs[i]);
2281 g_free(certs);
2284 void
2285 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2286 size_t cert_count, char *domain)
2288 size_t cert_buf_sz;
2289 char cert_buf[64 * 1024], file[PATH_MAX];
2290 int i;
2291 FILE *f;
2292 GdkColor color;
2294 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2295 return;
2297 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2298 if ((f = fopen(file, "w")) == NULL) {
2299 warn("save_certs");
2300 return;
2303 for (i = 0; i < cert_count; i++) {
2304 cert_buf_sz = sizeof cert_buf;
2305 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2306 cert_buf, &cert_buf_sz)) {
2307 warnx("gnutls_x509_crt_export");
2308 goto done;
2310 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2311 warn("fwrite certs");
2312 goto done;
2316 /* not the best spot but oh well */
2317 gdk_color_parse("lightblue", &color);
2318 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2319 done:
2320 fclose(f);
2324 load_compare_cert(struct tab *t, struct karg *args)
2326 WebKitWebFrame *frame;
2327 char *uri, domain[8182], file[PATH_MAX];
2328 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2329 int s = -1, rv = 1, i;
2330 size_t cert_count;
2331 FILE *f = NULL;
2332 size_t cert_buf_sz;
2333 gnutls_session_t gsession;
2334 gnutls_x509_crt_t *certs;
2335 gnutls_certificate_credentials_t xcred;
2337 if (t == NULL)
2338 return (1);
2340 frame = webkit_web_view_get_main_frame(t->wv);
2341 uri = (char *)webkit_web_frame_get_uri(frame);
2342 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2343 return (1);
2345 /* go ssl/tls */
2346 if (start_tls(s, &gsession, &xcred)) {
2347 warnx("start_tls");
2348 goto done;
2351 /* get certs */
2352 if (get_connection_certs(gsession, &certs, &cert_count)) {
2353 warnx("get_connection_certs");
2354 goto done;
2357 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2358 if ((f = fopen(file, "r")) == NULL)
2359 goto freeit;
2361 for (i = 0; i < cert_count; i++) {
2362 cert_buf_sz = sizeof cert_buf;
2363 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2364 cert_buf, &cert_buf_sz)) {
2365 warnx("gnutls_x509_crt_export");
2366 goto freeit;
2368 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2369 warn("fread certs");
2370 rv = -1; /* critical */
2371 goto freeit;
2373 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2374 warnx("invalid cert");
2375 rv = -1; /* critical */
2376 goto freeit;
2380 rv = 0;
2381 freeit:
2382 if (f)
2383 fclose(f);
2384 free_connection_certs(certs, cert_count);
2385 done:
2386 /* we close the socket first for speed */
2387 if (s != -1)
2388 close(s);
2389 stop_tls(gsession, xcred);
2391 return (rv);
2395 cert_cmd(struct tab *t, struct karg *args)
2397 WebKitWebFrame *frame;
2398 char *uri, *action, domain[8182];
2399 int s = -1;
2400 size_t cert_count;
2401 gnutls_session_t gsession;
2402 gnutls_x509_crt_t *certs;
2403 gnutls_certificate_credentials_t xcred;
2405 if (t == NULL)
2406 return (1);
2408 if ((action = getparams(args->s, "cert")))
2410 else
2411 action = "show";
2413 frame = webkit_web_view_get_main_frame(t->wv);
2414 uri = (char *)webkit_web_frame_get_uri(frame);
2415 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2416 return (1);
2418 /* go ssl/tls */
2419 if (start_tls(s, &gsession, &xcred)) {
2420 warnx("start_tls");
2421 goto done;
2424 /* get certs */
2425 if (get_connection_certs(gsession, &certs, &cert_count)) {
2426 warnx("get_connection_certs");
2427 goto done;
2430 if (!strcmp(action, "show"))
2431 show_certs(t, certs, cert_count, "Certificate Chain");
2432 else if (!strcmp(action, "save"))
2433 save_certs(t, certs, cert_count, domain);
2435 free_connection_certs(certs, cert_count);
2436 done:
2437 /* we close the socket first for speed */
2438 if (s != -1)
2439 close(s);
2440 stop_tls(gsession, xcred);
2442 return (0);
2446 remove_cookie(int index)
2448 int i, rv = 1;
2449 GSList *cf;
2450 SoupCookie *c;
2452 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2454 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2456 for (i = 1; cf; cf = cf->next, i++) {
2457 if (i != index)
2458 continue;
2459 c = cf->data;
2460 print_cookie("remove cookie", c);
2461 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2462 rv = 0;
2463 break;
2466 soup_cookies_free(cf);
2468 return (rv);
2472 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2474 struct domain *d;
2475 char *tmp, *header, *body, *footer;
2476 int p_js = 0, s_js = 0;
2478 if (g_str_has_prefix(args, "show a") ||
2479 !strcmp(args, "show")) {
2480 /* show all */
2481 p_js = 1;
2482 s_js = 1;
2483 } else if (g_str_has_prefix(args, "show p")) {
2484 /* show persistent */
2485 p_js = 1;
2486 } else if (g_str_has_prefix(args, "show s")) {
2487 /* show session */
2488 s_js = 1;
2489 } else
2490 return (1);
2492 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2493 title, title);
2494 footer = g_strdup("</body></html>");
2495 body = g_strdup("");
2497 /* p list */
2498 if (p_js) {
2499 tmp = body;
2500 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2501 g_free(tmp);
2502 RB_FOREACH(d, domain_list, wl) {
2503 if (d->handy == 0)
2504 continue;
2505 tmp = body;
2506 body = g_strdup_printf("%s%s<br>", body, d->d);
2507 g_free(tmp);
2511 /* s list */
2512 if (s_js) {
2513 tmp = body;
2514 body = g_strdup_printf("%s<h2>Session</h2>", body);
2515 g_free(tmp);
2516 RB_FOREACH(d, domain_list, wl) {
2517 if (d->handy == 1)
2518 continue;
2519 tmp = body;
2520 body = g_strdup_printf("%s%s", body, d->d);
2521 g_free(tmp);
2525 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2526 g_free(header);
2527 g_free(body);
2528 g_free(footer);
2529 webkit_web_view_load_string(t->wv, tmp, NULL, NULL, NULL);
2530 g_free(tmp);
2531 return (0);
2535 wl_save(struct tab *t, struct karg *args, int js)
2537 char file[PATH_MAX];
2538 FILE *f;
2539 char *line = NULL, *lt = NULL;
2540 size_t linelen;
2541 WebKitWebFrame *frame;
2542 char *dom = NULL, *uri, *dom_save = NULL;
2543 struct karg a;
2544 struct domain *d;
2545 GSList *cf;
2546 SoupCookie *ci, *c;
2547 int flags;
2549 if (t == NULL || args == NULL)
2550 return (1);
2552 if (runtime_settings[0] == '\0')
2553 return (1);
2555 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2556 if ((f = fopen(file, "r+")) == NULL)
2557 return (1);
2559 frame = webkit_web_view_get_main_frame(t->wv);
2560 uri = (char *)webkit_web_frame_get_uri(frame);
2561 dom = find_domain(uri, 1);
2562 if (uri == NULL || dom == NULL) {
2563 /* XXX this needs to be generalized */
2564 webkit_web_view_load_string(t->wv,
2565 "<html><body>Can't add domain to JavaScript white list</body></html>",
2566 NULL,
2567 NULL,
2568 NULL);
2569 goto done;
2572 if (g_str_has_prefix(args->s, "save d")) {
2573 /* save domain */
2574 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2575 /* XXX this needs use feedback */
2576 warnx("bad bad bad");
2577 goto done;
2579 flags = XT_WL_TOPLEVEL;
2580 } else if (g_str_has_prefix(args->s, "save f") ||
2581 !strcmp(args->s, "save")) {
2582 /* save fqdn */
2583 dom_save = dom;
2584 flags = XT_WL_FQDN;
2585 } else {
2586 /* XXX this needs use feedback */
2587 warnx("invalid command");
2588 goto done;
2591 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2593 while (!feof(f)) {
2594 line = fparseln(f, &linelen, NULL, NULL, 0);
2595 if (line == NULL)
2596 continue;
2597 if (!strcmp(line, lt))
2598 goto done;
2599 free(line);
2600 line = NULL;
2603 fprintf(f, "%s\n", lt);
2605 a.i = XT_WL_ENABLE;
2606 a.i |= flags;
2607 if (js) {
2608 d = wl_find(dom_save, &js_wl);
2609 toggle_js(t, &a);
2610 } else {
2611 d = wl_find(dom_save, &c_wl);
2612 toggle_cwl(t, &a);
2614 /* find and add to persistent jar */
2615 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2616 for (;cf; cf = cf->next) {
2617 ci = cf->data;
2618 if (!strcmp(dom_save, ci->domain) ||
2619 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
2620 c = soup_cookie_copy(ci);
2621 _soup_cookie_jar_add_cookie(p_cookiejar, c);
2624 soup_cookies_free(cf);
2626 if (d)
2627 d->handy = 1;
2629 done:
2630 if (line)
2631 free(line);
2632 if (dom)
2633 g_free(dom);
2634 if (lt)
2635 g_free(lt);
2636 fclose(f);
2638 return (0);
2642 cookie_cmd(struct tab *t, struct karg *args)
2644 char *cmd;
2645 struct karg a;
2647 if ((cmd = getparams(args->s, "cookie")))
2649 else
2650 cmd = "show all";
2653 if (g_str_has_prefix(cmd, "show")) {
2654 wl_show(t, cmd, "Cookie White List", &c_wl);
2655 } else if (g_str_has_prefix(cmd, "save")) {
2656 a.s = cmd;
2657 wl_save(t, &a, 0);
2658 } else if (g_str_has_prefix(cmd, "toggle")) {
2659 a.i = XT_WL_TOGGLE;
2660 if (g_str_has_prefix(cmd, "toggle d"))
2661 a.i |= XT_WL_TOPLEVEL;
2662 else
2663 a.i |= XT_WL_FQDN;
2664 toggle_cwl(t, &a);
2665 } else if (g_str_has_prefix(cmd, "delete")) {
2666 show_oops(t, "'cookie delete' currently unimplemented");
2669 return (0);
2673 js_cmd(struct tab *t, struct karg *args)
2675 char *cmd;
2676 struct karg a;
2678 if ((cmd = getparams(args->s, "js")))
2680 else
2681 cmd = "show all";
2684 if (g_str_has_prefix(cmd, "show")) {
2685 wl_show(t, cmd, "JavaScript White List", &js_wl);
2686 } else if (g_str_has_prefix(cmd, "save")) {
2687 a.s = cmd;
2688 wl_save(t, &a, 1);
2689 } else if (g_str_has_prefix(cmd, "toggle")) {
2690 a.i = XT_WL_TOGGLE;
2691 if (g_str_has_prefix(cmd, "toggle d"))
2692 a.i |= XT_WL_TOPLEVEL;
2693 else
2694 a.i |= XT_WL_FQDN;
2695 toggle_js(t, &a);
2696 } else if (g_str_has_prefix(cmd, "delete")) {
2697 show_oops(t, "'js delete' currently unimplemented");
2700 return (0);
2704 add_favorite(struct tab *t, struct karg *args)
2706 char file[PATH_MAX];
2707 FILE *f;
2708 char *line = NULL;
2709 size_t urilen, linelen;
2710 WebKitWebFrame *frame;
2711 const gchar *uri, *title;
2713 if (t == NULL)
2714 return (1);
2716 /* don't allow adding of xtp pages to favorites */
2717 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
2718 warn("%s: can't add xtp pages to favorites", __func__);
2719 return (1);
2722 snprintf(file, sizeof file, "%s/%s/%s",
2723 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2724 if ((f = fopen(file, "r+")) == NULL) {
2725 warn("favorites");
2726 return (1);
2729 title = webkit_web_view_get_title(t->wv);
2730 frame = webkit_web_view_get_main_frame(t->wv);
2731 uri = webkit_web_frame_get_uri(frame);
2732 if (title == NULL)
2733 title = uri;
2735 if (title == NULL || uri == NULL) {
2736 webkit_web_view_load_string(t->wv,
2737 "<html><body>can't add page to favorites</body></html>",
2738 NULL,
2739 NULL,
2740 NULL);
2741 goto done;
2744 urilen = strlen(uri);
2746 while (!feof(f)) {
2747 line = fparseln(f, &linelen, NULL, NULL, 0);
2748 if (linelen == urilen && !strcmp(line, uri))
2749 goto done;
2750 free(line);
2751 line = NULL;
2754 fprintf(f, "\n%s\n%s", title, uri);
2755 done:
2756 if (line)
2757 free(line);
2758 fclose(f);
2760 update_favorite_tabs(NULL);
2762 return (0);
2766 navaction(struct tab *t, struct karg *args)
2768 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
2769 t->tab_id, args->i);
2771 switch (args->i) {
2772 case XT_NAV_BACK:
2773 webkit_web_view_go_back(t->wv);
2774 break;
2775 case XT_NAV_FORWARD:
2776 webkit_web_view_go_forward(t->wv);
2777 break;
2778 case XT_NAV_RELOAD:
2779 webkit_web_view_reload(t->wv);
2780 break;
2781 case XT_NAV_RELOAD_CACHE:
2782 webkit_web_view_reload_bypass_cache(t->wv);
2783 break;
2785 return (XT_CB_PASSTHROUGH);
2789 move(struct tab *t, struct karg *args)
2791 GtkAdjustment *adjust;
2792 double pi, si, pos, ps, upper, lower, max;
2794 switch (args->i) {
2795 case XT_MOVE_DOWN:
2796 case XT_MOVE_UP:
2797 case XT_MOVE_BOTTOM:
2798 case XT_MOVE_TOP:
2799 case XT_MOVE_PAGEDOWN:
2800 case XT_MOVE_PAGEUP:
2801 case XT_MOVE_HALFDOWN:
2802 case XT_MOVE_HALFUP:
2803 adjust = t->adjust_v;
2804 break;
2805 default:
2806 adjust = t->adjust_h;
2807 break;
2810 pos = gtk_adjustment_get_value(adjust);
2811 ps = gtk_adjustment_get_page_size(adjust);
2812 upper = gtk_adjustment_get_upper(adjust);
2813 lower = gtk_adjustment_get_lower(adjust);
2814 si = gtk_adjustment_get_step_increment(adjust);
2815 pi = gtk_adjustment_get_page_increment(adjust);
2816 max = upper - ps;
2818 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
2819 "max %f si %f pi %f\n",
2820 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
2821 pos, ps, upper, lower, max, si, pi);
2823 switch (args->i) {
2824 case XT_MOVE_DOWN:
2825 case XT_MOVE_RIGHT:
2826 pos += si;
2827 gtk_adjustment_set_value(adjust, MIN(pos, max));
2828 break;
2829 case XT_MOVE_UP:
2830 case XT_MOVE_LEFT:
2831 pos -= si;
2832 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2833 break;
2834 case XT_MOVE_BOTTOM:
2835 case XT_MOVE_FARRIGHT:
2836 gtk_adjustment_set_value(adjust, max);
2837 break;
2838 case XT_MOVE_TOP:
2839 case XT_MOVE_FARLEFT:
2840 gtk_adjustment_set_value(adjust, lower);
2841 break;
2842 case XT_MOVE_PAGEDOWN:
2843 pos += pi;
2844 gtk_adjustment_set_value(adjust, MIN(pos, max));
2845 break;
2846 case XT_MOVE_PAGEUP:
2847 pos -= pi;
2848 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2849 break;
2850 case XT_MOVE_HALFDOWN:
2851 pos += pi / 2;
2852 gtk_adjustment_set_value(adjust, MIN(pos, max));
2853 break;
2854 case XT_MOVE_HALFUP:
2855 pos -= pi / 2;
2856 gtk_adjustment_set_value(adjust, MAX(pos, lower));
2857 break;
2858 default:
2859 return (XT_CB_PASSTHROUGH);
2862 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
2864 return (XT_CB_HANDLED);
2868 tabaction(struct tab *t, struct karg *args)
2870 int rv = XT_CB_HANDLED;
2871 char *url = NULL, *newuri = NULL;
2872 struct undo *u;
2874 DNPRINTF(XT_D_TAB, "tabaction: %p %d %d\n", t, args->i, t->focus_wv);
2876 if (t == NULL)
2877 return (XT_CB_PASSTHROUGH);
2879 switch (args->i) {
2880 case XT_TAB_NEW:
2881 if ((url = getparams(args->s, "tabnew")))
2882 create_new_tab(url, NULL, 1);
2883 else
2884 create_new_tab(NULL, NULL, 1);
2885 break;
2886 case XT_TAB_DELETE:
2887 delete_tab(t);
2888 break;
2889 case XT_TAB_DELQUIT:
2890 if (gtk_notebook_get_n_pages(notebook) > 1)
2891 delete_tab(t);
2892 else
2893 quit(t, args);
2894 break;
2895 case XT_TAB_OPEN:
2896 if ((url = getparams(args->s, "open")) ||
2897 ((url = getparams(args->s, "op"))) ||
2898 ((url = getparams(args->s, "o"))))
2900 else {
2901 rv = XT_CB_PASSTHROUGH;
2902 goto done;
2905 if (valid_url_type(url)) {
2906 newuri = guess_url_type(url);
2907 url = newuri;
2909 webkit_web_view_load_uri(t->wv, url);
2910 if (newuri)
2911 g_free(newuri);
2912 break;
2913 case XT_TAB_UNDO_CLOSE:
2914 if (undo_count == 0) {
2915 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
2916 goto done;
2917 } else {
2918 undo_count--;
2919 u = TAILQ_FIRST(&undos);
2920 create_new_tab(u->uri, u, 1);
2922 TAILQ_REMOVE(&undos, u, entry);
2923 g_free(u->uri);
2924 /* u->history is freed in create_new_tab() */
2925 g_free(u);
2927 break;
2928 default:
2929 rv = XT_CB_PASSTHROUGH;
2930 goto done;
2933 done:
2934 if (args->s) {
2935 g_free(args->s);
2936 args->s = NULL;
2939 return (rv);
2943 resizetab(struct tab *t, struct karg *args)
2945 if (t == NULL || args == NULL)
2946 errx(1, "resizetab");
2948 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
2949 t->tab_id, args->i);
2951 adjustfont_webkit(t, args->i);
2953 return (XT_CB_HANDLED);
2957 movetab(struct tab *t, struct karg *args)
2959 struct tab *tt;
2960 int x;
2962 if (t == NULL || args == NULL)
2963 errx(1, "movetab");
2965 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
2966 t->tab_id, args->i);
2968 if (args->i == XT_TAB_INVALID)
2969 return (XT_CB_PASSTHROUGH);
2971 if (args->i < XT_TAB_INVALID) {
2972 /* next or previous tab */
2973 if (TAILQ_EMPTY(&tabs))
2974 return (XT_CB_PASSTHROUGH);
2976 switch (args->i) {
2977 case XT_TAB_NEXT:
2978 /* if at the last page, loop around to the first */
2979 if (gtk_notebook_get_current_page(notebook) ==
2980 gtk_notebook_get_n_pages(notebook) - 1) {
2981 gtk_notebook_set_current_page(notebook, 0);
2982 } else {
2983 gtk_notebook_next_page(notebook);
2985 break;
2986 case XT_TAB_PREV:
2987 /* if at the first page, loop around to the last */
2988 if (gtk_notebook_current_page(notebook) == 0) {
2989 gtk_notebook_set_current_page(notebook,
2990 gtk_notebook_get_n_pages(notebook) - 1);
2991 } else {
2992 gtk_notebook_prev_page(notebook);
2994 break;
2995 case XT_TAB_FIRST:
2996 gtk_notebook_set_current_page(notebook, 0);
2997 break;
2998 case XT_TAB_LAST:
2999 gtk_notebook_set_current_page(notebook, -1);
3000 break;
3001 default:
3002 return (XT_CB_PASSTHROUGH);
3005 return (XT_CB_HANDLED);
3008 /* jump to tab */
3009 x = args->i - 1;
3010 if (t->tab_id == x) {
3011 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3012 return (XT_CB_HANDLED);
3015 TAILQ_FOREACH(tt, &tabs, entry) {
3016 if (tt->tab_id == x) {
3017 gtk_notebook_set_current_page(notebook, x);
3018 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3019 if (tt->focus_wv)
3020 gtk_widget_grab_focus(GTK_WIDGET(tt->wv));
3024 return (XT_CB_HANDLED);
3028 command(struct tab *t, struct karg *args)
3030 WebKitWebFrame *frame;
3031 char *s = NULL, *ss = NULL;
3032 GdkColor color;
3033 const gchar *uri;
3035 if (t == NULL || args == NULL)
3036 errx(1, "command");
3038 switch (args->i) {
3039 case '/':
3040 s = "/";
3041 break;
3042 case '?':
3043 s = "?";
3044 break;
3045 case ':':
3046 s = ":";
3047 break;
3048 case XT_CMD_OPEN:
3049 s = ":open ";
3050 break;
3051 case XT_CMD_TABNEW:
3052 s = ":tabnew ";
3053 break;
3054 case XT_CMD_OPEN_CURRENT:
3055 s = ":open ";
3056 /* FALL THROUGH */
3057 case XT_CMD_TABNEW_CURRENT:
3058 if (!s) /* FALL THROUGH? */
3059 s = ":tabnew ";
3060 frame = webkit_web_view_get_main_frame(t->wv);
3061 uri = webkit_web_frame_get_uri(frame);
3062 if (uri && strlen(uri)) {
3063 ss = g_strdup_printf("%s%s", s, uri);
3064 s = ss;
3066 break;
3067 default:
3068 warnx("command: invalid command %c\n", args->i);
3069 return (XT_CB_PASSTHROUGH);
3072 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3074 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3075 gdk_color_parse("white", &color);
3076 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3077 show_cmd(t);
3078 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3079 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3081 if (ss)
3082 g_free(ss);
3084 return (XT_CB_HANDLED);
3088 * Return a new string with a download row (in html)
3089 * appended. Old string is freed.
3091 char *
3092 xtp_page_dl_row(char *html, struct download *dl)
3095 WebKitDownloadStatus stat;
3096 char *status_html = NULL, *cmd_html = NULL, *new_html;
3097 gdouble progress;
3098 char cur_sz[FMT_SCALED_STRSIZE];
3099 char tot_sz[FMT_SCALED_STRSIZE];
3100 char *xtp_prefix;
3102 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3104 /* All actions wil take this form:
3105 * xxxt://class/seskey
3107 xtp_prefix = g_strdup_printf("%s%d/%s/",
3108 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3110 stat = webkit_download_get_status(dl->download);
3112 switch (stat) {
3113 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3114 status_html = g_strdup_printf("Finished");
3115 cmd_html = g_strdup_printf(
3116 "<a href='%s%d/%d'>Remove</a>",
3117 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3118 break;
3119 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3120 /* gather size info */
3121 progress = 100 * webkit_download_get_progress(dl->download);
3123 fmt_scaled(
3124 webkit_download_get_current_size(dl->download), cur_sz);
3125 fmt_scaled(
3126 webkit_download_get_total_size(dl->download), tot_sz);
3128 status_html = g_strdup_printf("%s of %s (%.2f%%)", cur_sz,
3129 tot_sz, progress);
3130 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3131 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3133 break;
3134 /* LLL */
3135 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3136 status_html = g_strdup_printf("Cancelled");
3137 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3138 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3139 break;
3140 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3141 status_html = g_strdup_printf("Error!");
3142 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3143 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3144 break;
3145 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3146 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3147 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3148 status_html = g_strdup_printf("Starting");
3149 break;
3150 default:
3151 warn("%s: unknown download status", __func__);
3154 new_html = g_strdup_printf(
3155 "%s\n<tr><td>%s</td><td>%s</td>"
3156 "<td style='text-align:center'>%s</td></tr>\n",
3157 html, webkit_download_get_uri(dl->download),
3158 status_html, cmd_html);
3159 g_free(html);
3161 if (status_html)
3162 g_free(status_html);
3164 if (cmd_html)
3165 g_free(cmd_html);
3167 g_free(xtp_prefix);
3169 return new_html;
3173 * update all download tabs apart from one. Pass NULL if
3174 * you want to update all.
3176 void
3177 update_download_tabs(struct tab *apart_from)
3179 struct tab *t;
3180 if (!updating_dl_tabs) {
3181 updating_dl_tabs = 1; /* stop infinite recursion */
3182 TAILQ_FOREACH(t, &tabs, entry)
3183 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3184 && (t != apart_from))
3185 xtp_page_dl(t, NULL);
3186 updating_dl_tabs = 0;
3191 * update all cookie tabs apart from one. Pass NULL if
3192 * you want to update all.
3194 void
3195 update_cookie_tabs(struct tab *apart_from)
3197 struct tab *t;
3198 if (!updating_cl_tabs) {
3199 updating_cl_tabs = 1; /* stop infinite recursion */
3200 TAILQ_FOREACH(t, &tabs, entry)
3201 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3202 && (t != apart_from))
3203 xtp_page_cl(t, NULL);
3204 updating_cl_tabs = 0;
3209 * update all history tabs apart from one. Pass NULL if
3210 * you want to update all.
3212 void
3213 update_history_tabs(struct tab *apart_from)
3215 struct tab *t;
3217 if (!updating_hl_tabs) {
3218 updating_hl_tabs = 1; /* stop infinite recursion */
3219 TAILQ_FOREACH(t, &tabs, entry)
3220 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3221 && (t != apart_from))
3222 xtp_page_hl(t, NULL);
3223 updating_hl_tabs = 0;
3227 /* cookie management XTP page */
3229 xtp_page_cl(struct tab *t, struct karg *args)
3231 char *header, *body, *footer, *page, *tmp;
3232 int i = 1; /* all ids start 1 */
3233 GSList *sc, *pc, *pc_start;
3234 SoupCookie *c;
3235 char *type;
3237 DNPRINTF(XT_D_CMD, "%s", __func__);
3239 if (t == NULL)
3240 errx(1, "%s: null tab", __func__);
3242 /* mark this tab as cookie jar */
3243 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3245 /* Generate a new session key */
3246 if (!updating_cl_tabs)
3247 generate_xtp_session_key(&cl_session_key);
3249 /* header */
3250 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3251 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3252 "</head><body><h1>Cookie Jar</h1>\n");
3254 /* body */
3255 body = g_strdup_printf("<div align='center'><table><tr>"
3256 "<th>Type</th>"
3257 "<th>Name</th>"
3258 "<th>Value</th>"
3259 "<th>Domain</th>"
3260 "<th>Path</th>"
3261 "<th>Expires</th>"
3262 "<th>Secure</th>"
3263 "<th>HTTP_only</th>"
3264 "<th>Remove</th></tr>\n");
3266 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3267 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3268 pc_start = pc;
3270 for (; sc; sc = sc->next) {
3271 c = sc->data;
3273 type = "Session";
3274 for (pc = pc_start; pc; pc = pc->next)
3275 if (soup_cookie_equal(pc->data, c)) {
3276 type = "Session + Persistent";
3277 break;
3280 tmp = body;
3281 body = g_strdup_printf(
3282 "%s\n<tr>"
3283 "<td style='width: 3%%; text-align: center'>%s</td>"
3284 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3285 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3286 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3287 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3288 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3289 "<td style='width: 3%%; text-align: center'>%d</td>"
3290 "<td style='width: 3%%; text-align: center'>%d</td>"
3291 "<td style='width: 3%%; text-align: center'>"
3292 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3293 body,
3294 type,
3295 c->name,
3296 c->value,
3297 c->domain,
3298 c->path,
3299 c->expires ?
3300 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3301 c->secure,
3302 c->http_only,
3304 XT_XTP_STR,
3305 XT_XTP_CL,
3306 cl_session_key,
3307 XT_XTP_CL_REMOVE,
3311 g_free(tmp);
3312 i++;
3315 soup_cookies_free(sc);
3316 soup_cookies_free(pc);
3318 /* small message if there are none */
3319 if (i == 1) {
3320 tmp = body;
3321 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3322 "colspan='8'>No Cookies</td></tr>\n", body);
3323 g_free(tmp);
3326 /* footer */
3327 footer = g_strdup_printf("</table></div></body></html>");
3329 page = g_strdup_printf("%s%s%s", header, body, footer);
3331 g_free(header);
3332 g_free(body);
3333 g_free(footer);
3335 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3336 update_cookie_tabs(t);
3338 g_free(page);
3340 return (0);
3344 xtp_page_hl(struct tab *t, struct karg *args)
3346 char *header, *body, *footer, *page, *tmp;
3347 struct history *h;
3348 int i = 1; /* all ids start 1 */
3350 DNPRINTF(XT_D_CMD, "%s", __func__);
3352 if (t == NULL)
3353 errx(1, "%s: null tab", __func__);
3355 /* mark this tab as history manager */
3356 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3358 /* Generate a new session key */
3359 if (!updating_hl_tabs)
3360 generate_xtp_session_key(&hl_session_key);
3362 /* header */
3363 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3364 "<title>History</title>\n"
3365 "%s"
3366 "</head>"
3367 "<h1>History</h1>\n",
3368 XT_PAGE_STYLE);
3370 /* body */
3371 body = g_strdup_printf("<div align='center'><table><tr>"
3372 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3374 RB_FOREACH_REVERSE(h, history_list, &hl) {
3375 tmp = body;
3376 body = g_strdup_printf(
3377 "%s\n<tr>"
3378 "<td><a href='%s'>%s</a></td>"
3379 "<td>%s</td>"
3380 "<td style='text-align: center'>"
3381 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3382 body, h->uri, h->uri, h->title,
3383 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3384 XT_XTP_HL_REMOVE, i);
3386 g_free(tmp);
3387 i++;
3390 /* small message if there are none */
3391 if (i == 1) {
3392 tmp = body;
3393 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3394 "colspan='3'>No History</td></tr>\n", body);
3395 g_free(tmp);
3398 /* footer */
3399 footer = g_strdup_printf("</table></div></body></html>");
3401 page = g_strdup_printf("%s%s%s", header, body, footer);
3404 * update all history manager tabs as the xtp session
3405 * key has now changed. No need to update the current tab.
3406 * Already did that above.
3408 update_history_tabs(t);
3410 g_free(header);
3411 g_free(body);
3412 g_free(footer);
3414 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3415 g_free(page);
3417 return (0);
3421 * Generate a web page detailing the status of any downloads
3424 xtp_page_dl(struct tab *t, struct karg *args)
3426 struct download *dl;
3427 char *header, *body, *footer, *page, *tmp;
3428 char *ref;
3429 int n_dl = 1;
3431 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3433 if (t == NULL)
3434 errx(1, "%s: null tab", __func__);
3436 /* mark as a download manager tab */
3437 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3440 * Generate a new session key for next page instance.
3441 * This only happens for the top level call to xtp_page_dl()
3442 * in which case updating_dl_tabs is 0.
3444 if (!updating_dl_tabs)
3445 generate_xtp_session_key(&dl_session_key);
3447 /* header - with refresh so as to update */
3448 if (refresh_interval >= 1)
3449 ref = g_strdup_printf(
3450 "<meta http-equiv='refresh' content='%u"
3451 ";url=%s%d/%s/%d' />\n",
3452 refresh_interval,
3453 XT_XTP_STR,
3454 XT_XTP_DL,
3455 dl_session_key,
3456 XT_XTP_DL_LIST);
3457 else
3458 ref = g_strdup("");
3461 header = g_strdup_printf(
3462 "%s\n<head>"
3463 "<title>Downloads</title>\n%s%s</head>\n",
3464 XT_DOCTYPE XT_HTML_TAG,
3465 ref,
3466 XT_PAGE_STYLE);
3468 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
3469 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
3470 "</p><table><tr><th style='width: 60%%'>"
3471 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
3472 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
3474 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
3475 body = xtp_page_dl_row(body, dl);
3476 n_dl++;
3479 /* message if no downloads in list */
3480 if (n_dl == 1) {
3481 tmp = body;
3482 body = g_strdup_printf("%s\n<tr><td colspan='3'"
3483 " style='text-align: center'>"
3484 "No downloads</td></tr>\n", body);
3485 g_free(tmp);
3488 /* footer */
3489 footer = g_strdup_printf("</table></div></body></html>");
3491 page = g_strdup_printf("%s%s%s", header, body, footer);
3495 * update all download manager tabs as the xtp session
3496 * key has now changed. No need to update the current tab.
3497 * Already did that above.
3499 update_download_tabs(t);
3501 g_free(ref);
3502 g_free(header);
3503 g_free(body);
3504 g_free(footer);
3506 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3507 g_free(page);
3509 return (0);
3513 search(struct tab *t, struct karg *args)
3515 gboolean d;
3517 if (t == NULL || args == NULL)
3518 errx(1, "search");
3519 if (t->search_text == NULL) {
3520 if (global_search == NULL)
3521 return (XT_CB_PASSTHROUGH);
3522 else {
3523 t->search_text = g_strdup(global_search);
3524 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
3525 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
3529 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
3530 t->tab_id, args->i, t->search_forward, t->search_text);
3532 switch (args->i) {
3533 case XT_SEARCH_NEXT:
3534 d = t->search_forward;
3535 break;
3536 case XT_SEARCH_PREV:
3537 d = !t->search_forward;
3538 break;
3539 default:
3540 return (XT_CB_PASSTHROUGH);
3543 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
3545 return (XT_CB_HANDLED);
3548 struct settings_args {
3549 char **body;
3550 int i;
3553 void
3554 print_setting(struct settings *s, char *val, void *cb_args)
3556 char *tmp, *color;
3557 struct settings_args *sa = cb_args;
3559 if (sa == NULL) {
3560 warnx("*** %s", s->name);
3561 return;
3564 if (s->flags & XT_SF_RUNTIME)
3565 color = "#22cc22";
3566 else
3567 color = "#cccccc";
3569 tmp = *sa->body;
3570 *sa->body = g_strdup_printf(
3571 "%s\n<tr>"
3572 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
3573 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
3574 *sa->body,
3575 color,
3576 s->name,
3577 color,
3580 g_free(tmp);
3581 sa->i++;
3585 set(struct tab *t, struct karg *args)
3587 char *header, *body, *footer, *page, *tmp, *pars;
3588 int i = 1;
3589 struct settings_args sa;
3591 if ((pars = getparams(args->s, "set")) == NULL) {
3592 bzero(&sa, sizeof sa);
3593 sa.body = &body;
3595 /* header */
3596 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3597 "\n<head><title>Settings</title>\n"
3598 "</head><body><h1>Settings</h1>\n");
3600 /* body */
3601 body = g_strdup_printf("<div align='center'><table><tr>"
3602 "<th align='left'>Setting</th>"
3603 "<th align='left'>Value</th></tr>\n");
3605 settings_walk(print_setting, &sa);
3606 i = sa.i;
3608 /* small message if there are none */
3609 if (i == 1) {
3610 tmp = body;
3611 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3612 "colspan='2'>No settings</td></tr>\n", body);
3613 g_free(tmp);
3616 /* footer */
3617 footer = g_strdup_printf("</table></div></body></html>");
3619 page = g_strdup_printf("%s%s%s", header, body, footer);
3621 g_free(header);
3622 g_free(body);
3623 g_free(footer);
3625 webkit_web_view_load_string(t->wv, page, "text/html", "UTF-8", "");
3626 } else {
3627 fprintf(stderr, "pars %s\n", pars);
3630 return (XT_CB_PASSTHROUGH);
3634 * Make a hardcopy of the page
3637 print_page(struct tab *t, struct karg *args)
3639 WebKitWebFrame *frame;
3641 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
3644 * for now we just call the GTK print box,
3645 * but later we might decide to hook in a command.
3647 frame = webkit_web_view_get_main_frame(t->wv);
3648 webkit_web_frame_print(frame);
3650 return (0);
3654 go_home(struct tab *t, struct karg *args)
3656 char *newuri;
3658 newuri = guess_url_type((char *)home);
3659 webkit_web_view_load_uri(t->wv, newuri);
3660 free(newuri);
3662 return (0);
3666 restart(struct tab *t, struct karg *args)
3668 struct karg a;
3670 a.s = XT_RESTART_TABS_FILE;
3671 save_tabs(t, &a);
3672 execvp(start_argv[0], start_argv);
3673 /* NOTREACHED */
3675 return (0);
3678 /* inherent to GTK not all keys will be caught at all times */
3679 /* XXX sort key bindings */
3680 struct key_bindings {
3681 guint mask;
3682 guint use_in_entry;
3683 guint key;
3684 int (*func)(struct tab *, struct karg *);
3685 struct karg arg;
3686 } keys[] = {
3687 { GDK_MOD1_MASK, 0, GDK_d, xtp_page_dl, {0} },
3688 { GDK_MOD1_MASK, 0, GDK_h, xtp_page_hl, {0} },
3689 { GDK_CONTROL_MASK, 0, GDK_p, print_page, {0}},
3690 { 0, 0, GDK_slash, command, {.i = '/'} },
3691 { GDK_SHIFT_MASK, 0, GDK_question, command, {.i = '?'} },
3692 { GDK_SHIFT_MASK, 0, GDK_colon, command, {.i = ':'} },
3693 { GDK_CONTROL_MASK, 0, GDK_q, quit, {0} },
3694 { GDK_MOD1_MASK, 0, GDK_q, restart, {0} },
3695 { GDK_CONTROL_MASK, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3696 { GDK_MOD1_MASK, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
3697 { GDK_CONTROL_MASK, 0, GDK_s, toggle_src, {0} },
3698 { 0, 0, GDK_y, yank_uri, {0} },
3699 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
3700 { GDK_SHIFT_MASK, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
3702 /* search */
3703 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
3704 { GDK_SHIFT_MASK, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
3706 /* focus */
3707 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
3708 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
3710 /* command aliases (handy when -S flag is used) */
3711 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
3712 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
3713 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
3714 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
3716 /* hinting */
3717 { 0, 0, GDK_f, hint, {.i = 0} },
3719 /* navigation */
3720 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
3721 { GDK_MOD1_MASK, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
3722 { GDK_SHIFT_MASK, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
3723 { GDK_MOD1_MASK, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
3724 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
3725 { GDK_CONTROL_MASK, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
3726 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
3727 { GDK_CONTROL_MASK, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
3728 { GDK_MOD1_MASK, 1, GDK_f, xtp_page_fl, {0} },
3730 /* vertical movement */
3731 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
3732 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
3733 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
3734 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
3735 { GDK_SHIFT_MASK, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
3736 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
3737 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
3738 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
3739 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
3740 { GDK_CONTROL_MASK, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
3741 { GDK_CONTROL_MASK, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
3742 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
3743 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
3744 { GDK_CONTROL_MASK, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
3745 { GDK_CONTROL_MASK, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
3746 /* horizontal movement */
3747 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
3748 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
3749 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
3750 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
3751 { GDK_SHIFT_MASK, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
3752 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
3754 /* tabs */
3755 { GDK_CONTROL_MASK, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
3756 { GDK_CONTROL_MASK, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
3757 { GDK_SHIFT_MASK, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
3758 { GDK_CONTROL_MASK, 0, GDK_1, movetab, {.i = 1} },
3759 { GDK_CONTROL_MASK, 0, GDK_2, movetab, {.i = 2} },
3760 { GDK_CONTROL_MASK, 0, GDK_3, movetab, {.i = 3} },
3761 { GDK_CONTROL_MASK, 0, GDK_4, movetab, {.i = 4} },
3762 { GDK_CONTROL_MASK, 0, GDK_5, movetab, {.i = 5} },
3763 { GDK_CONTROL_MASK, 0, GDK_6, movetab, {.i = 6} },
3764 { GDK_CONTROL_MASK, 0, GDK_7, movetab, {.i = 7} },
3765 { GDK_CONTROL_MASK, 0, GDK_8, movetab, {.i = 8} },
3766 { GDK_CONTROL_MASK, 0, GDK_9, movetab, {.i = 9} },
3767 { GDK_CONTROL_MASK, 0, GDK_0, movetab, {.i = 10} },
3768 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
3769 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
3770 { GDK_CONTROL_MASK, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
3771 { GDK_CONTROL_MASK, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
3772 { GDK_CONTROL_MASK, 0, GDK_minus, resizetab, {.i = -1} },
3773 { GDK_CONTROL_MASK|GDK_SHIFT_MASK, 0, GDK_plus, resizetab, {.i = 1} },
3774 { GDK_CONTROL_MASK, 0, GDK_equal, resizetab, {.i = 1} },
3777 struct cmd {
3778 char *cmd;
3779 int params;
3780 int (*func)(struct tab *, struct karg *);
3781 struct karg arg;
3782 } cmds[] = {
3783 { "q!", 0, quit, {0} },
3784 { "qa", 0, quit, {0} },
3785 { "qa!", 0, quit, {0} },
3786 { "w", 0, save_tabs, {.s = XT_SAVED_TABS_FILE} },
3787 { "wq", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3788 { "wq!", 0, save_tabs_and_quit, {.s = XT_SAVED_TABS_FILE} },
3789 { "help", 0, help, {0} },
3790 { "about", 0, about, {0} },
3791 { "stats", 0, stats, {0} },
3792 { "version", 0, about, {0} },
3793 { "cookies", 0, xtp_page_cl, {0} },
3794 { "fav", 0, xtp_page_fl, {0} },
3795 { "favadd", 0, add_favorite, {0} },
3796 { "js", 2, js_cmd, {0} },
3797 { "cookie", 2, cookie_cmd, {0} },
3798 { "cert", 1, cert_cmd, {0} },
3799 { "ca", 0, ca_cmd, {0} },
3800 { "dl" , 0, xtp_page_dl, {0} },
3801 { "h" , 0, xtp_page_hl, {0} },
3802 { "hist" , 0, xtp_page_hl, {0} },
3803 { "history" , 0, xtp_page_hl, {0} },
3804 { "home" , 0, go_home, {0} },
3805 { "restart" , 0, restart, {0} },
3807 { "1", 0, move, {.i = XT_MOVE_TOP} },
3808 { "print", 0, print_page, {0} },
3810 /* tabs */
3811 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
3812 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
3813 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
3814 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
3815 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
3816 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
3817 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
3818 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
3819 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3820 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
3821 /* XXX add count to these commands */
3822 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
3823 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
3824 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
3825 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
3826 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
3827 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
3828 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
3829 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
3830 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
3831 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
3833 /* settings */
3834 { "set", 1, set, {0} },
3837 gboolean
3838 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
3840 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
3842 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
3843 delete_tab(t);
3845 return (FALSE);
3849 * cancel, remove, etc. downloads
3851 void
3852 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
3854 struct download find, *d;
3856 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
3858 /* some commands require a valid download id */
3859 if (cmd != XT_XTP_DL_LIST) {
3860 /* lookup download in question */
3861 find.id = id;
3862 d = RB_FIND(download_list, &downloads, &find);
3864 if (d == NULL) {
3865 warn("%s: no such download", __func__);
3866 return;
3870 /* decide what to do */
3871 switch (cmd) {
3872 case XT_XTP_DL_CANCEL:
3873 webkit_download_cancel(d->download);
3874 break;
3875 case XT_XTP_DL_REMOVE:
3876 webkit_download_cancel(d->download); /* just incase */
3877 g_object_unref(d->download);
3878 RB_REMOVE(download_list, &downloads, d);
3879 break;
3880 case XT_XTP_DL_LIST:
3881 /* Nothing */
3882 break;
3883 default:
3884 warn("%s: unknown command", __func__);
3885 break;
3887 xtp_page_dl(t, NULL);
3891 * Actions on history, only does one thing for now, but
3892 * we provide the function for future actions
3894 void
3895 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
3897 struct history *h, *next;
3898 int i = 1;
3900 switch (cmd) {
3901 case XT_XTP_HL_REMOVE:
3902 /* walk backwards, as listed in reverse */
3903 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
3904 next = RB_PREV(history_list, &hl, h);
3905 if (id == i) {
3906 RB_REMOVE(history_list, &hl, h);
3907 g_free((gpointer) h->title);
3908 g_free((gpointer) h->uri);
3909 g_free(h);
3910 break;
3912 i++;
3914 break;
3915 case XT_XTP_HL_LIST:
3916 /* Nothing - just xtp_page_hl() below */
3917 break;
3918 default:
3919 warn("%s: unknown command", __func__);
3920 break;
3923 xtp_page_hl(t, NULL);
3926 /* remove a favorite */
3927 void
3928 remove_favorite(int index)
3930 char file[PATH_MAX], *title, *uri;
3931 char *new_favs, *tmp;
3932 FILE *f;
3933 int i;
3934 size_t len, lineno;
3936 /* open favorites */
3937 snprintf(file, sizeof file, "%s/%s/%s",
3938 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
3940 if ((f = fopen(file, "r")) == NULL) {
3941 warn("%s: can't open favorites", __func__);
3942 return;
3945 /* build a string which will become the new favroites file */
3946 new_favs = g_strdup_printf("%s", "");
3948 for (i = 1;;) {
3949 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
3950 if (feof(f) || ferror(f))
3951 break;
3952 if (len == 0) {
3953 free(title);
3954 title = NULL;
3955 continue;
3958 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
3959 if (feof(f) || ferror(f)) {
3960 warn("%s: can't parse favorites", __func__);
3961 goto clean;
3965 /* as long as this isn't the one we are deleting add to file */
3966 if (i != index) {
3967 tmp = new_favs;
3968 new_favs = g_strdup_printf("%s%s\n%s\n",
3969 new_favs, title, uri);
3970 g_free(tmp);
3973 free(uri);
3974 uri = NULL;
3975 free(title);
3976 title = NULL;
3977 i++;
3979 fclose(f);
3981 /* write back new favorites file */
3982 if ((f = fopen(file, "w")) == NULL) {
3983 warn("%s: can't open favorites", __func__);
3984 goto clean;
3987 fwrite(new_favs, strlen(new_favs), 1, f);
3988 fclose(f);
3990 clean:
3991 if (uri)
3992 free(uri);
3993 if (title)
3994 free(title);
3996 g_free(new_favs);
3999 void
4000 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4002 switch (cmd) {
4003 case XT_XTP_FL_LIST:
4004 /* nothing, just the below call to xtp_page_fl() */
4005 break;
4006 case XT_XTP_FL_REMOVE:
4007 remove_favorite(arg);
4008 break;
4009 default:
4010 warn("%s: invalid favorites command", __func__);
4011 break;
4014 xtp_page_fl(t, NULL);
4017 void
4018 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4020 switch (cmd) {
4021 case XT_XTP_CL_LIST:
4022 /* nothing, just xtp_page_cl() */
4023 break;
4024 case XT_XTP_CL_REMOVE:
4025 remove_cookie(arg);
4026 break;
4027 default:
4028 warn("%s: unknown cookie xtp command", __func__);
4029 break;
4032 xtp_page_cl(t, NULL);
4035 /* link an XTP class to it's session key and handler function */
4036 struct xtp_despatch {
4037 uint8_t xtp_class;
4038 char **session_key;
4039 void (*handle_func)(struct tab *, uint8_t, int);
4042 struct xtp_despatch xtp_despatches[] = {
4043 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4044 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4045 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4046 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4047 { NULL, NULL, NULL }
4051 * is the url xtp protocol? (xxxt://)
4052 * if so, parse and despatch correct bahvior
4055 parse_xtp_url(struct tab *t, const char *url)
4057 char *dup = NULL, *p, *last;
4058 uint8_t n_tokens = 0;
4059 char *tokens[4] = {NULL, NULL, NULL, ""};
4060 struct xtp_despatch *dsp, *dsp_match = NULL;
4061 uint8_t req_class;
4064 * tokens array meaning:
4065 * tokens[0] = class
4066 * tokens[1] = session key
4067 * tokens[2] = action
4068 * tokens[3] = optional argument
4071 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4073 /*xtp tab meaning is normal unless proven special */
4074 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4076 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4077 return 0;
4079 dup = g_strdup(url + strlen(XT_XTP_STR));
4081 /* split out the url */
4082 for ((p = strtok_r(dup, "/", &last)); p;
4083 (p = strtok_r(NULL, "/", &last))) {
4084 if (n_tokens < 4)
4085 tokens[n_tokens++] = p;
4088 /* should be atleast three fields 'class/seskey/command/arg' */
4089 if (n_tokens < 3)
4090 goto clean;
4092 dsp = xtp_despatches;
4093 req_class = atoi(tokens[0]);
4094 while (dsp->xtp_class != NULL) {
4095 if (dsp->xtp_class == req_class) {
4096 dsp_match = dsp;
4097 break;
4099 dsp++;
4102 /* did we find one atall? */
4103 if (dsp_match == NULL) {
4104 warn("%s: no matching xtp despatch found", __func__);
4105 goto clean;
4108 /* check session key and call despatch function */
4109 if (validate_xtp_session_key(*(dsp_match->session_key), tokens[1])) {
4110 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4113 clean:
4114 if (dup)
4115 g_free(dup);
4117 return 1;
4122 void
4123 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4125 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4126 char *newuri = NULL;
4128 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4130 if (t == NULL)
4131 errx(1, "activate_uri_entry_cb");
4133 if (uri == NULL)
4134 errx(1, "uri");
4136 uri += strspn(uri, "\t ");
4138 /* if xxxt:// treat specially */
4139 if (!parse_xtp_url(t, uri)) {
4140 if (valid_url_type((char *)uri)) {
4141 newuri = guess_url_type((char *)uri);
4142 uri = newuri;
4145 webkit_web_view_load_uri(t->wv, uri);
4146 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4149 if (newuri)
4150 g_free(newuri);
4153 void
4154 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4156 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4157 char *newuri = NULL;
4159 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4161 if (t == NULL)
4162 errx(1, "activate_search_entry_cb");
4164 if (search_string == NULL) {
4165 warnx("no search_string");
4166 return;
4169 newuri = g_strdup_printf(search_string, search);
4171 webkit_web_view_load_uri(t->wv, newuri);
4172 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4174 if (newuri)
4175 g_free(newuri);
4178 void
4179 check_and_set_js(gchar *uri, struct tab *t)
4181 struct domain *d = NULL;
4182 int es = 0;
4184 if (uri == NULL || t == NULL)
4185 return;
4187 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4188 es = 0;
4189 else
4190 es = 1;
4192 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4193 es ? "enable" : "disable", uri);
4195 g_object_set((GObject *)t->settings,
4196 "enable-scripts", es, (char *)NULL);
4197 webkit_web_view_set_settings(t->wv, t->settings);
4199 button_set_stockid(t->js_toggle,
4200 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4203 void
4204 show_ca_status(struct tab *t, const char *uri)
4206 WebKitWebFrame *frame;
4207 WebKitWebDataSource *source;
4208 WebKitNetworkRequest *request;
4209 SoupMessage *message;
4210 GdkColor color;
4211 gchar *col_str = "white";
4212 int r;
4214 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4215 ssl_strict_certs, ssl_ca_file, uri);
4217 if (uri == NULL)
4218 goto done;
4219 if (ssl_ca_file == NULL) {
4220 if (g_str_has_prefix(uri, "http://"))
4221 goto done;
4222 if (g_str_has_prefix(uri, "https://")) {
4223 col_str = "red";
4224 goto done;
4226 return;
4228 if (g_str_has_prefix(uri, "http://") ||
4229 !g_str_has_prefix(uri, "https://"))
4230 goto done;
4232 frame = webkit_web_view_get_main_frame(t->wv);
4233 source = webkit_web_frame_get_data_source(frame);
4234 request = webkit_web_data_source_get_request(source);
4235 message = webkit_network_request_get_message(request);
4237 if (message && (soup_message_get_flags(message) &
4238 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4239 col_str = "green";
4240 goto done;
4241 } else {
4242 r = load_compare_cert(t, NULL);
4243 if (r == 0)
4244 col_str = "lightblue";
4245 else if (r == 1)
4246 col_str = "yellow";
4247 else
4248 col_str = "red";
4249 goto done;
4251 done:
4252 if (col_str) {
4253 gdk_color_parse(col_str, &color);
4254 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4258 void
4259 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
4261 WebKitWebFrame *frame;
4262 const gchar *set = NULL, *uri = NULL, *title = NULL;
4263 struct history *h, find;
4264 int add = 0;
4265 const gchar *s_loading;
4267 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
4268 webkit_web_view_get_load_status(wview));
4270 if (t == NULL)
4271 errx(1, "notify_load_status_cb");
4273 switch (webkit_web_view_get_load_status(wview)) {
4274 case WEBKIT_LOAD_PROVISIONAL:
4275 #if GTK_CHECK_VERSION(2, 20, 0)
4276 gtk_widget_show(t->spinner);
4277 gtk_spinner_start(GTK_SPINNER(t->spinner));
4278 #endif
4279 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
4281 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
4282 t->focus_wv = 1;
4284 /* take focus if we are visible */
4285 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
4286 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4288 break;
4290 case WEBKIT_LOAD_COMMITTED:
4291 frame = webkit_web_view_get_main_frame(wview);
4292 uri = webkit_web_frame_get_uri(frame);
4293 if (uri)
4294 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
4296 /* check if js white listing is enabled */
4297 if (enable_js_whitelist) {
4298 frame = webkit_web_view_get_main_frame(wview);
4299 uri = webkit_web_frame_get_uri(frame);
4300 check_and_set_js((gchar *)uri, t);
4303 show_ca_status(t, uri);
4304 break;
4306 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
4307 title = webkit_web_view_get_title(wview);
4308 frame = webkit_web_view_get_main_frame(wview);
4309 uri = webkit_web_frame_get_uri(frame);
4310 if (title)
4311 set = title;
4312 else if (uri)
4313 set = uri;
4314 else
4315 break;
4317 gtk_label_set_text(GTK_LABEL(t->label), set);
4318 gtk_window_set_title(GTK_WINDOW(main_window), set);
4320 if (uri) {
4321 if (!strncmp(uri, "http://", strlen("http://")) ||
4322 !strncmp(uri, "https://", strlen("https://")) ||
4323 !strncmp(uri, "file://", strlen("file://")))
4324 add = 1;
4325 if (add == 0)
4326 break;
4327 find.uri = uri;
4328 h = RB_FIND(history_list, &hl, &find);
4329 if (h)
4330 break;
4332 h = g_malloc(sizeof *h);
4333 h->uri = g_strdup(uri);
4334 if (title)
4335 h->title = g_strdup(title);
4336 else
4337 h->title = g_strdup(uri);
4338 RB_INSERT(history_list, &hl, h);
4339 update_history_tabs(NULL);
4342 break;
4344 case WEBKIT_LOAD_FINISHED:
4345 #if WEBKIT_CHECK_VERSION(1, 1, 18)
4346 case WEBKIT_LOAD_FAILED:
4347 #endif
4348 #if GTK_CHECK_VERSION(2, 20, 0)
4349 gtk_spinner_stop(GTK_SPINNER(t->spinner));
4350 gtk_widget_hide(t->spinner);
4351 #endif
4352 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
4353 if (s_loading && !strcmp(s_loading, "Loading"))
4354 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
4355 default:
4356 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
4357 break;
4360 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
4361 webkit_web_view_can_go_back(wview));
4363 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
4364 webkit_web_view_can_go_forward(wview));
4367 void
4368 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4370 run_script(t, JS_HINTING);
4373 void
4374 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
4376 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
4377 progress == 100 ? 0 : (double)progress / 100);
4381 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4382 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4383 WebKitWebPolicyDecision *pd, struct tab *t)
4385 char *uri;
4387 if (t == NULL)
4388 errx(1, "webview_nw_cb");
4390 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
4391 webkit_network_request_get_uri(request));
4393 /* open in current tab */
4394 uri = (char *)webkit_network_request_get_uri(request);
4395 webkit_web_view_load_uri(t->wv, uri);
4396 webkit_web_policy_decision_ignore(pd);
4398 return (TRUE); /* we made the decission */
4402 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
4403 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
4404 WebKitWebPolicyDecision *pd, struct tab *t)
4406 char *uri;
4408 if (t == NULL)
4409 errx(1, "webview_npd_cb");
4411 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
4412 t->ctrl_click,
4413 webkit_network_request_get_uri(request));
4415 uri = (char *)webkit_network_request_get_uri(request);
4417 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
4418 t->ctrl_click = 0;
4419 create_new_tab(uri, NULL, ctrl_click_focus);
4420 webkit_web_policy_decision_ignore(pd);
4421 return (TRUE); /* we made the decission */
4424 webkit_web_policy_decision_use(pd);
4425 return (TRUE); /* we made the decission */
4428 WebKitWebView *
4429 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
4431 if (t == NULL)
4432 errx(1, "webview_cwv_cb");
4434 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
4435 webkit_web_view_get_uri(wv));
4437 return (wv);
4441 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
4443 /* we can not eat the event without throwing gtk off so defer it */
4445 /* catch middle click */
4446 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
4447 t->ctrl_click = 1;
4448 goto done;
4451 /* catch ctrl click */
4452 if (e->type == GDK_BUTTON_RELEASE &&
4453 CLEAN(e->state) == GDK_CONTROL_MASK)
4454 t->ctrl_click = 1;
4455 else
4456 t->ctrl_click = 0;
4457 done:
4458 return (XT_CB_PASSTHROUGH);
4462 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
4464 struct mime_type *m;
4466 m = find_mime_type(mime_type);
4467 if (m == NULL)
4468 return (1);
4470 switch (fork()) {
4471 case -1:
4472 err(1, "fork");
4473 /* NOTREACHED */
4474 case 0:
4475 break;
4476 default:
4477 return (0);
4480 /* child */
4481 execlp(m->mt_action, m->mt_action,
4482 webkit_network_request_get_uri(request), (void *)NULL);
4484 _exit(0);
4486 /* NOTREACHED */
4487 return (0);
4491 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
4492 WebKitNetworkRequest *request, char *mime_type,
4493 WebKitWebPolicyDecision *decision, struct tab *t)
4495 if (t == NULL)
4496 errx(1, "webview_mimetype_cb");
4498 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
4499 t->tab_id, mime_type);
4501 if (run_mimehandler(t, mime_type, request) == 0) {
4502 webkit_web_policy_decision_ignore(decision);
4503 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4504 return (TRUE);
4507 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
4508 webkit_web_policy_decision_download(decision);
4509 return (TRUE);
4512 return (FALSE);
4516 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download, struct tab *t)
4518 const gchar *filename;
4519 char *uri = NULL;
4520 struct download *download_entry;
4521 int ret = TRUE;
4523 if (wk_download == NULL || t == NULL)
4524 errx(1, "%s: invalid pointers", __func__);
4526 filename = webkit_download_get_suggested_filename(wk_download);
4527 if (filename == NULL)
4528 return (FALSE); /* abort download */
4530 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
4532 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
4533 "local %s\n", __func__, t->tab_id, filename, uri);
4535 webkit_download_set_destination_uri(wk_download, uri);
4537 if (webkit_download_get_status(wk_download) ==
4538 WEBKIT_DOWNLOAD_STATUS_ERROR) {
4539 warn("%s: download failed to start", __func__);
4540 ret = FALSE;
4541 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
4542 } else {
4543 download_entry = g_malloc(sizeof(struct download));
4544 download_entry->download = wk_download;
4545 download_entry->tab = t;
4546 download_entry->id = next_download_id++;
4547 RB_INSERT(download_list, &downloads, download_entry);
4548 /* get from history */
4549 g_object_ref(wk_download);
4550 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
4553 if (uri)
4554 g_free(uri);
4556 /* sync other download manager tabs */
4557 update_download_tabs(NULL);
4560 * NOTE: never redirect/render the current tab before this
4561 * function returns. This will cause the download to never start.
4563 return (ret); /* start download */
4566 /* XXX currently unused */
4567 void
4568 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
4570 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
4572 if (t == NULL)
4573 errx(1, "webview_hover_cb");
4575 if (uri) {
4576 if (t->hover) {
4577 g_free(t->hover);
4578 t->hover = NULL;
4580 t->hover = g_strdup(uri);
4581 } else if (t->hover) {
4582 g_free(t->hover);
4583 t->hover = NULL;
4588 webview_keypress_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
4590 int i;
4591 char s[2], buf[128];
4592 const char *errstr = NULL;
4593 long long link;
4595 /* don't use w directly; use t->whatever instead */
4597 if (t == NULL)
4598 errx(1, "webview_keypress_cb");
4600 DNPRINTF(XT_D_KEY, "webview_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
4601 e->keyval, e->state, t);
4603 if (w == GTK_WIDGET(t->cmd)) {
4604 goto done;
4606 hide_oops(t);
4608 if (t->hints_on) {
4609 /* ESC */
4610 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
4611 disable_hints(t);
4612 return (XT_CB_HANDLED);
4615 /* RETURN */
4616 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
4617 link = strtonum(t->hint_num, 1, 1000, &errstr);
4618 if (errstr) {
4619 /* we have a string */
4620 } else {
4621 /* we have a number */
4622 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
4623 t->hint_num);
4624 run_script(t, buf);
4626 disable_hints(t);
4629 /* BACKSPACE */
4630 /* XXX unfuck this */
4631 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
4632 if (t->hint_mode == XT_HINT_NUMERICAL) {
4633 /* last input was numerical */
4634 int l;
4635 l = strlen(t->hint_num);
4636 if (l > 0) {
4637 l--;
4638 if (l == 0) {
4639 disable_hints(t);
4640 enable_hints(t);
4641 } else {
4642 t->hint_num[l] = '\0';
4643 goto num;
4646 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
4647 /* last input was alphanumerical */
4648 int l;
4649 l = strlen(t->hint_buf);
4650 if (l > 0) {
4651 l--;
4652 if (l == 0) {
4653 disable_hints(t);
4654 enable_hints(t);
4655 } else {
4656 t->hint_buf[l] = '\0';
4657 goto anum;
4660 } else {
4661 /* bogus */
4662 disable_hints(t);
4666 /* numerical input */
4667 if (CLEAN(e->state) == 0 &&
4668 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
4669 snprintf(s, sizeof s, "%c", e->keyval);
4670 strlcat(t->hint_num, s, sizeof t->hint_num);
4671 DNPRINTF(XT_D_JS, "webview_keypress_cb: numerical %s\n",
4672 t->hint_num);
4673 num:
4674 link = strtonum(t->hint_num, 1, 1000, &errstr);
4675 if (errstr) {
4676 DNPRINTF(XT_D_JS, "webview_keypress_cb: invalid link number\n");
4677 disable_hints(t);
4678 } else {
4679 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
4680 t->hint_num);
4681 t->hint_mode = XT_HINT_NUMERICAL;
4682 run_script(t, buf);
4685 /* empty the counter buffer */
4686 bzero(t->hint_buf, sizeof t->hint_buf);
4687 return (XT_CB_HANDLED);
4690 /* alphanumerical input */
4691 if (
4692 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
4693 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
4694 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
4695 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
4696 snprintf(s, sizeof s, "%c", e->keyval);
4697 strlcat(t->hint_buf, s, sizeof t->hint_buf);
4698 DNPRINTF(XT_D_JS, "webview_keypress_cb: alphanumerical %s\n",
4699 t->hint_buf);
4700 anum:
4701 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
4702 run_script(t, buf);
4704 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
4705 t->hint_buf);
4706 t->hint_mode = XT_HINT_ALPHANUM;
4707 run_script(t, buf);
4709 /* empty the counter buffer */
4710 bzero(t->hint_num, sizeof t->hint_num);
4711 return (XT_CB_HANDLED);
4714 return (XT_CB_HANDLED);
4717 for (i = 0; i < LENGTH(keys); i++)
4718 if (e->keyval == keys[i].key && CLEAN(e->state) ==
4719 keys[i].mask) {
4720 keys[i].func(t, &keys[i].arg);
4721 return (XT_CB_HANDLED);
4724 done:
4725 return (XT_CB_PASSTHROUGH);
4729 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4731 const gchar *c = gtk_entry_get_text(w);
4732 GdkColor color;
4733 int forward = TRUE;
4735 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
4736 e->keyval, e->state, t);
4738 if (t == NULL)
4739 errx(1, "cmd_keyrelease_cb");
4741 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
4742 e->keyval, e->state, t);
4744 if (w == GTK_ENTRY(t->oops)) {
4745 hide_oops(t);
4746 goto done;
4749 if (c[0] == ':')
4750 goto done;
4751 if (strlen(c) == 1) {
4752 webkit_web_view_unmark_text_matches(t->wv);
4753 goto done;
4756 if (c[0] == '/')
4757 forward = TRUE;
4758 else if (c[0] == '?')
4759 forward = FALSE;
4760 else
4761 goto done;
4763 /* search */
4764 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
4765 FALSE) {
4766 /* not found, mark red */
4767 gdk_color_parse("red", &color);
4768 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4769 /* unmark and remove selection */
4770 webkit_web_view_unmark_text_matches(t->wv);
4771 /* my kingdom for a way to unselect text in webview */
4772 } else {
4773 /* found, highlight all */
4774 webkit_web_view_unmark_text_matches(t->wv);
4775 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
4776 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4777 gdk_color_parse("white", &color);
4778 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
4780 done:
4781 return (XT_CB_PASSTHROUGH);
4784 #if 0
4786 cmd_complete(struct tab *t, char *s)
4788 int i;
4789 GtkEntry *w = GTK_ENTRY(t->cmd);
4791 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
4793 for (i = 0; i < LENGTH(cmds); i++) {
4794 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
4795 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
4796 #if 0
4797 gtk_entry_set_text(w, ":");
4798 gtk_entry_append_text(w, cmds[i].cmd);
4799 gtk_editable_set_position(GTK_EDITABLE(w), -1);
4800 #endif
4804 return (0);
4806 #endif
4809 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4811 int i;
4813 if (t == NULL)
4814 errx(1, "entry_key_cb");
4816 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
4817 e->keyval, e->state, t);
4819 hide_oops(t);
4821 if (e->keyval == GDK_Escape)
4822 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4824 for (i = 0; i < LENGTH(keys); i++)
4825 if (e->keyval == keys[i].key &&
4826 CLEAN(e->state) == keys[i].mask &&
4827 keys[i].use_in_entry) {
4828 keys[i].func(t, &keys[i].arg);
4829 return (XT_CB_HANDLED);
4832 return (XT_CB_PASSTHROUGH);
4836 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
4838 int rv = XT_CB_HANDLED;
4839 const gchar *c = gtk_entry_get_text(w);
4841 if (t == NULL)
4842 errx(1, "cmd_keypress_cb");
4844 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
4845 e->keyval, e->state, t);
4847 if (w == GTK_ENTRY(t->oops)) {
4848 hide_oops(t);
4849 rv = XT_CB_PASSTHROUGH;
4850 goto done;
4853 /* sanity */
4854 if (c == NULL)
4855 e->keyval = GDK_Escape;
4856 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
4857 e->keyval = GDK_Escape;
4859 switch (e->keyval) {
4860 #if 0
4861 case GDK_Tab:
4862 if (c[0] != ':')
4863 goto done;
4865 if (strchr (c, ' ')) {
4866 /* par completion */
4867 fprintf(stderr, "completeme par\n");
4868 goto done;
4871 cmd_complete(t, (char *)&c[1]);
4873 goto done;
4874 #endif
4875 case GDK_BackSpace:
4876 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
4877 break;
4878 /* FALLTHROUGH */
4879 case GDK_Escape:
4880 hide_cmd(t);
4881 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4883 /* cancel search */
4884 if (c[0] == '/' || c[0] == '?')
4885 webkit_web_view_unmark_text_matches(t->wv);
4886 goto done;
4889 rv = XT_CB_PASSTHROUGH;
4890 done:
4891 return (rv);
4895 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
4897 if (t == NULL)
4898 errx(1, "cmd_focusout_cb");
4900 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d focus_wv %d\n",
4901 t->tab_id, t->focus_wv);
4903 /* abort command when losing focus */
4904 if (w == GTK_WIDGET(t->cmd)) {
4905 hide_cmd(t);
4906 } else {
4907 hide_oops(t);
4909 if (t->focus_wv)
4910 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
4911 else
4912 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
4914 return (XT_CB_PASSTHROUGH);
4917 void
4918 cmd_activate_cb(GtkEntry *entry, struct tab *t)
4920 int i;
4921 char *s;
4922 const gchar *c = gtk_entry_get_text(entry);
4924 if (t == NULL)
4925 errx(1, "cmd_activate_cb");
4927 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
4929 if (entry == GTK_ENTRY(t->oops)) {
4930 hide_oops(t);
4931 return;
4934 /* sanity */
4935 if (c == NULL)
4936 goto done;
4937 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
4938 goto done;
4939 if (strlen(c) < 2)
4940 goto done;
4941 s = (char *)&c[1];
4943 if (c[0] == '/' || c[0] == '?') {
4944 if (t->search_text) {
4945 g_free(t->search_text);
4946 t->search_text = NULL;
4949 t->search_text = g_strdup(s);
4950 if (global_search)
4951 g_free(global_search);
4952 global_search = g_strdup(s);
4953 t->search_forward = c[0] == '/';
4955 goto done;
4958 for (i = 0; i < LENGTH(cmds); i++)
4959 if (cmds[i].params) {
4960 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
4961 cmds[i].arg.s = g_strdup(s);
4962 goto execute_command;
4964 } else {
4965 if (!strcmp(s, cmds[i].cmd))
4966 goto execute_command;
4969 done:
4970 hide_cmd(t);
4971 return;
4973 execute_command:
4974 hide_cmd(t);
4975 cmds[i].func(t, &cmds[i].arg);
4977 void
4978 backward_cb(GtkWidget *w, struct tab *t)
4980 if (t == NULL)
4981 errx(1, "backward_cb");
4983 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
4985 webkit_web_view_go_back(t->wv);
4988 void
4989 forward_cb(GtkWidget *w, struct tab *t)
4991 if (t == NULL)
4992 errx(1, "forward_cb");
4994 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
4996 webkit_web_view_go_forward(t->wv);
4999 void
5000 stop_cb(GtkWidget *w, struct tab *t)
5002 WebKitWebFrame *frame;
5004 if (t == NULL)
5005 errx(1, "stop_cb");
5007 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5009 frame = webkit_web_view_get_main_frame(t->wv);
5010 if (frame == NULL) {
5011 warnx("stop_cb: no frame");
5012 return;
5015 webkit_web_frame_stop_loading(frame);
5018 void
5019 setup_webkit(struct tab *t)
5021 g_object_set((GObject *)t->settings,
5022 "user-agent", t->user_agent, (char *)NULL);
5023 g_object_set((GObject *)t->settings,
5024 "enable-scripts", enable_scripts, (char *)NULL);
5025 g_object_set((GObject *)t->settings,
5026 "enable-plugins", enable_plugins, (char *)NULL);
5027 adjustfont_webkit(t, XT_FONT_SET);
5029 webkit_web_view_set_settings(t->wv, t->settings);
5032 GtkWidget *
5033 create_browser(struct tab *t)
5035 GtkWidget *w;
5036 gchar *strval;
5038 if (t == NULL)
5039 errx(1, "create_browser");
5041 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5042 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5043 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5044 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5046 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5047 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5048 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5050 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5051 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5053 /* set defaults */
5054 t->settings = webkit_web_settings_new();
5056 if (user_agent == NULL) {
5057 g_object_get((GObject *)t->settings, "user-agent", &strval,
5058 (char *)NULL);
5059 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5060 g_free(strval);
5061 } else {
5062 t->user_agent = g_strdup(user_agent);
5065 setup_webkit(t);
5067 return (w);
5070 GtkWidget *
5071 create_window(void)
5073 GtkWidget *w;
5075 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
5076 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
5077 gtk_widget_set_name(w, "xxxterm");
5078 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
5079 g_signal_connect(G_OBJECT(w), "delete_event",
5080 G_CALLBACK (gtk_main_quit), NULL);
5082 return (w);
5085 GtkWidget *
5086 create_toolbar(struct tab *t)
5088 GtkWidget *toolbar = NULL, *b, *eb1;
5090 b = gtk_hbox_new(FALSE, 0);
5091 toolbar = b;
5092 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
5094 if (fancy_bar) {
5095 /* backward button */
5096 t->backward = create_button("go-back", GTK_STOCK_GO_BACK, 0);
5097 gtk_widget_set_sensitive(t->backward, FALSE);
5098 g_signal_connect(G_OBJECT(t->backward), "clicked",
5099 G_CALLBACK(backward_cb), t);
5100 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
5102 /* forward button */
5103 t->forward = create_button("go-forward",GTK_STOCK_GO_FORWARD, 0);
5104 gtk_widget_set_sensitive(t->forward, FALSE);
5105 g_signal_connect(G_OBJECT(t->forward), "clicked",
5106 G_CALLBACK(forward_cb), t);
5107 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
5108 FALSE, 0);
5110 /* stop button */
5111 t->stop = create_button("stop", GTK_STOCK_STOP, 0);
5112 gtk_widget_set_sensitive(t->stop, FALSE);
5113 g_signal_connect(G_OBJECT(t->stop), "clicked",
5114 G_CALLBACK(stop_cb), t);
5115 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
5116 FALSE, 0);
5118 /* JS button */
5119 t->js_toggle = create_button("js-toggle", enable_scripts ?
5120 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
5121 gtk_widget_set_sensitive(t->js_toggle, TRUE);
5122 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
5123 G_CALLBACK(js_toggle_cb), t);
5124 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
5127 t->uri_entry = gtk_entry_new();
5128 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
5129 G_CALLBACK(activate_uri_entry_cb), t);
5130 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
5131 (GCallback)entry_key_cb, t);
5132 eb1 = gtk_hbox_new(FALSE, 0);
5133 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
5134 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
5135 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
5137 /* search entry */
5138 if (fancy_bar && search_string) {
5139 GtkWidget *eb2;
5140 t->search_entry = gtk_entry_new();
5141 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
5142 g_signal_connect(G_OBJECT(t->search_entry), "activate",
5143 G_CALLBACK(activate_search_entry_cb), t);
5144 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
5145 (GCallback)entry_key_cb, t);
5146 gtk_widget_set_size_request(t->search_entry, -1, -1);
5147 eb2 = gtk_hbox_new(FALSE, 0);
5148 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
5149 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
5151 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
5153 return (toolbar);
5156 void
5157 recalc_tabs(void)
5159 struct tab *t;
5161 TAILQ_FOREACH(t, &tabs, entry)
5162 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
5166 undo_close_tab_save(struct tab *t)
5168 int m, n;
5169 const gchar *uri;
5170 struct undo *u1, *u2;
5171 WebKitWebFrame *frame;
5172 WebKitWebBackForwardList *bfl;
5173 GList *items;
5174 WebKitWebHistoryItem *item;
5176 frame = webkit_web_view_get_main_frame(t->wv);
5177 uri = webkit_web_frame_get_uri(frame);
5179 if (uri && !strlen(uri))
5180 return (1);
5182 u1 = g_malloc0(sizeof(struct undo));
5183 u1->uri = g_strdup(uri);
5185 bfl = webkit_web_view_get_back_forward_list(t->wv);
5187 m = webkit_web_back_forward_list_get_forward_length(bfl);
5188 n = webkit_web_back_forward_list_get_back_length(bfl);
5189 u1->back = n;
5191 /* forward history */
5192 items = webkit_web_back_forward_list_get_forward_list_with_limit(bfl, m);
5194 while (items) {
5195 item = items->data;
5196 u1->history = g_list_prepend(u1->history,
5197 webkit_web_history_item_copy(item));
5198 items = g_list_next(items);
5201 /* current item */
5202 if (m) {
5203 item = webkit_web_back_forward_list_get_current_item(bfl);
5204 u1->history = g_list_prepend(u1->history,
5205 webkit_web_history_item_copy(item));
5208 /* back history */
5209 items = webkit_web_back_forward_list_get_back_list_with_limit(bfl, n);
5211 while (items) {
5212 item = items->data;
5213 u1->history = g_list_prepend(u1->history,
5214 webkit_web_history_item_copy(item));
5215 items = g_list_next(items);
5218 TAILQ_INSERT_HEAD(&undos, u1, entry);
5220 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
5221 u2 = TAILQ_LAST(&undos, undo_tailq);
5222 TAILQ_REMOVE(&undos, u2, entry);
5223 g_free(u2->uri);
5224 g_list_free(u2->history);
5225 g_free(u2);
5226 } else
5227 undo_count++;
5229 return (0);
5232 void
5233 delete_tab(struct tab *t)
5235 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
5237 if (t == NULL)
5238 return;
5240 TAILQ_REMOVE(&tabs, t, entry);
5242 /* halt all webkit activity */
5243 webkit_web_view_stop_loading(t->wv);
5244 undo_close_tab_save(t);
5246 gtk_widget_destroy(t->vbox);
5247 g_free(t->user_agent);
5248 g_free(t);
5250 recalc_tabs();
5251 if (TAILQ_EMPTY(&tabs))
5252 create_new_tab(NULL, NULL, 1);
5255 void
5256 adjustfont_webkit(struct tab *t, int adjust)
5258 if (t == NULL)
5259 errx(1, "adjustfont_webkit");
5261 if (adjust == XT_FONT_SET)
5262 t->font_size = default_font_size;
5264 t->font_size += adjust;
5265 g_object_set((GObject *)t->settings, "default-font-size",
5266 t->font_size, (char *)NULL);
5267 g_object_get((GObject *)t->settings, "default-font-size",
5268 &t->font_size, (char *)NULL);
5271 void
5272 append_tab(struct tab *t)
5274 if (t == NULL)
5275 return;
5277 TAILQ_INSERT_TAIL(&tabs, t, entry);
5278 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
5281 void
5282 create_new_tab(char *title, struct undo *u, int focus)
5284 struct tab *t, *tt;
5285 int load = 1, id, notfound;
5286 char *newuri = NULL;
5287 GtkWidget *b, *bb;
5288 WebKitWebHistoryItem *item;
5289 GList *items;
5290 WebKitWebBackForwardList *bfl;
5291 GdkColor color;
5293 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
5295 if (tabless && !TAILQ_EMPTY(&tabs)) {
5296 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
5297 return;
5300 t = g_malloc0(sizeof *t);
5302 if (title == NULL) {
5303 title = "(untitled)";
5304 load = 0;
5305 } else {
5306 if (valid_url_type(title)) {
5307 newuri = guess_url_type(title);
5308 title = newuri;
5312 t->vbox = gtk_vbox_new(FALSE, 0);
5314 /* label + button for tab */
5315 b = gtk_hbox_new(FALSE, 0);
5316 t->tab_content = b;
5318 #if GTK_CHECK_VERSION(2, 20, 0)
5319 t->spinner = gtk_spinner_new ();
5320 #endif
5321 t->label = gtk_label_new(title);
5322 bb = create_button("my-close-button", GTK_STOCK_CLOSE, 1);
5323 gtk_widget_set_size_request(t->label, 100, 0);
5324 gtk_widget_set_size_request(b, 130, 0);
5325 gtk_notebook_set_homogeneous_tabs(notebook, TRUE);
5327 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
5328 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
5329 #if GTK_CHECK_VERSION(2, 20, 0)
5330 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
5331 #endif
5333 /* toolbar */
5334 t->toolbar = create_toolbar(t);
5335 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
5337 /* browser */
5338 t->browser_win = create_browser(t);
5339 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
5341 /* oops message for user feedback */
5342 t->oops = gtk_entry_new();
5343 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
5344 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
5345 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
5346 gdk_color_parse("red", &color);
5347 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
5348 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
5350 /* command entry */
5351 t->cmd = gtk_entry_new();
5352 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
5353 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
5354 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
5356 /* xtp meaning is normal by default */
5357 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
5359 /* and show it all */
5360 gtk_widget_show_all(b);
5361 gtk_widget_show_all(t->vbox);
5363 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
5364 append_tab(t);
5365 else {
5366 notfound = 1;
5367 id = gtk_notebook_get_current_page(notebook);
5368 TAILQ_FOREACH(tt, &tabs, entry) {
5369 if (tt->tab_id == id) {
5370 notfound = 0;
5371 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
5372 gtk_notebook_insert_page(notebook, t->vbox, b,
5373 id + 1);
5374 recalc_tabs();
5375 break;
5378 if (notfound)
5379 append_tab(t);
5382 #if GTK_CHECK_VERSION(2, 20, 0)
5383 /* turn spinner off if we are a new tab without uri */
5384 if (!load) {
5385 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5386 gtk_widget_hide(t->spinner);
5388 #endif
5389 /* make notebook tabs reorderable */
5390 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
5392 g_object_connect((GObject*)t->cmd,
5393 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5394 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5395 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5396 "signal::activate", (GCallback)cmd_activate_cb, t,
5397 (char *)NULL);
5398 g_object_connect((GObject*)t->oops,
5399 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
5400 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
5401 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
5402 "signal::activate", (GCallback)cmd_activate_cb, t,
5403 (char *)NULL);
5405 g_object_connect((GObject*)t->wv,
5406 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
5407 /* "signal::hovering-over-link", (GCallback)webview_hover_cb, t, */
5408 "signal::download-requested", (GCallback)webview_download_cb, t,
5409 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
5410 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
5411 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
5412 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
5413 "signal::event", (GCallback)webview_event_cb, t,
5414 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
5415 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
5416 (char *)NULL);
5417 g_signal_connect(t->wv, "notify::load-status",
5418 G_CALLBACK(notify_load_status_cb), t);
5420 /* hijack the unused keys as if we were the browser */
5421 g_object_connect((GObject*)t->toolbar,
5422 "signal-after::key-press-event", (GCallback)webview_keypress_cb, t,
5423 (char *)NULL);
5425 g_signal_connect(G_OBJECT(bb), "button_press_event",
5426 G_CALLBACK(tab_close_cb), t);
5428 /* hide stuff */
5429 hide_cmd(t);
5430 hide_oops(t);
5431 if (showurl == 0)
5432 gtk_widget_hide(t->toolbar);
5434 if (focus) {
5435 gtk_notebook_set_current_page(notebook, t->tab_id);
5436 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
5437 t->tab_id);
5440 if (load)
5441 webkit_web_view_load_uri(t->wv, title);
5442 else
5443 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5445 /* restore the tab's history */
5446 if (u && u->history) {
5447 bfl = webkit_web_view_get_back_forward_list(t->wv);
5449 items = u->history;
5450 while (items) {
5451 item = items->data;
5452 webkit_web_back_forward_list_add_item(bfl, item);
5453 items = g_list_next(items);
5456 item = g_list_nth_data(u->history, u->back);
5457 webkit_web_view_go_to_back_forward_item(t->wv, item);
5459 g_list_free(items);
5460 g_list_free(u->history);
5463 if (newuri)
5464 g_free(newuri);
5467 void
5468 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
5469 gpointer *udata)
5471 struct tab *t;
5472 const gchar *uri;
5474 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
5476 TAILQ_FOREACH(t, &tabs, entry) {
5477 if (t->tab_id == pn) {
5478 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
5479 "%d\n", pn);
5481 uri = webkit_web_view_get_title(t->wv);
5482 if (uri == NULL)
5483 uri = XT_NAME;
5484 gtk_window_set_title(GTK_WINDOW(main_window), uri);
5486 hide_cmd(t);
5487 hide_oops(t);
5489 if (t->focus_wv)
5490 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5495 void
5496 menuitem_response(struct tab *t)
5498 gtk_notebook_set_current_page(notebook, t->tab_id);
5501 gboolean
5502 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
5504 GtkWidget *menu, *menu_items;
5505 GdkEventButton *bevent;
5506 WebKitWebFrame *frame;
5507 const gchar *uri;
5508 struct tab *ti;
5510 if (event->type == GDK_BUTTON_PRESS) {
5511 bevent = (GdkEventButton *) event;
5512 menu = gtk_menu_new();
5514 TAILQ_FOREACH(ti, &tabs, entry) {
5515 frame = webkit_web_view_get_main_frame(ti->wv);
5516 uri = webkit_web_frame_get_uri(frame);
5517 /* XXX make sure there is something to print */
5518 /* XXX add gui pages in here to look purdy */
5519 if (uri == NULL)
5520 uri = "(untitled)";
5521 if (strlen(uri) == 0)
5522 uri = "(untitled)";
5523 menu_items = gtk_menu_item_new_with_label(uri);
5524 gtk_menu_append(GTK_MENU (menu), menu_items);
5525 gtk_widget_show(menu_items);
5527 gtk_signal_connect_object(GTK_OBJECT(menu_items),
5528 "activate", GTK_SIGNAL_FUNC(menuitem_response),
5529 (gpointer)ti);
5532 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
5533 bevent->button, bevent->time);
5535 /* unref object so it'll free itself when popped down */
5536 g_object_ref_sink(menu);
5537 g_object_unref(menu);
5539 return (TRUE /* eat event */);
5542 return (FALSE /* propagate */);
5546 icon_size_map(int icon_size)
5548 if (icon_size <= GTK_ICON_SIZE_INVALID ||
5549 icon_size > GTK_ICON_SIZE_DIALOG)
5550 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
5552 return (icon_size);
5555 GtkWidget *
5556 create_button(char *name, char *stockid, int size)
5558 GtkWidget *button, *image;
5559 char *rcstring;
5560 int gtk_icon_size;
5561 asprintf(&rcstring,
5562 "style \"%s-style\"\n"
5563 "{\n"
5564 " GtkWidget::focus-padding = 0\n"
5565 " GtkWidget::focus-line-width = 0\n"
5566 " xthickness = 0\n"
5567 " ythickness = 0\n"
5568 "}\n"
5569 "widget \"*.%s\" style \"%s-style\"",name,name,name);
5570 gtk_rc_parse_string(rcstring);
5571 free(rcstring);
5572 button = gtk_button_new();
5573 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
5574 gtk_icon_size = icon_size_map(size?size:icon_size);
5576 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
5577 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5578 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
5579 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
5580 gtk_widget_set_name(button, name);
5582 return button;
5585 void
5586 button_set_stockid(GtkWidget *button, char *stockid)
5588 GtkWidget *image;
5589 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
5590 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
5591 gtk_button_set_image(GTK_BUTTON(button), image);
5594 void
5595 create_canvas(void)
5597 GtkWidget *vbox;
5598 GList *l = NULL;
5599 GdkPixbuf *pb;
5600 char file[PATH_MAX];
5601 int i;
5603 vbox = gtk_vbox_new(FALSE, 0);
5604 gtk_box_set_spacing(GTK_BOX(vbox), 0);
5605 notebook = GTK_NOTEBOOK(gtk_notebook_new());
5606 if (showtabs == 0)
5607 gtk_notebook_set_show_tabs(notebook, FALSE);
5608 else {
5609 gtk_notebook_set_tab_hborder(notebook, 0);
5610 gtk_notebook_set_tab_vborder(notebook, 0);
5612 gtk_notebook_set_show_border(notebook, FALSE);
5613 gtk_notebook_set_scrollable(notebook, TRUE);
5614 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
5616 abtn = gtk_button_new();
5617 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
5618 gtk_widget_set_size_request(arrow, -1, -1);
5619 gtk_container_add(GTK_CONTAINER(abtn), arrow);
5620 gtk_widget_set_size_request(abtn, -1, 20);
5621 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
5623 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
5624 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
5625 gtk_widget_set_size_request(vbox, -1, -1);
5627 g_object_connect((GObject*)notebook,
5628 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
5629 (char *)NULL);
5630 g_signal_connect(G_OBJECT(abtn), "button_press_event",
5631 G_CALLBACK(arrow_cb), NULL);
5633 main_window = create_window();
5634 gtk_container_add(GTK_CONTAINER(main_window), vbox);
5635 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
5637 /* icons */
5638 for (i = 0; i < LENGTH(icons); i++) {
5639 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
5640 pb = gdk_pixbuf_new_from_file(file, NULL);
5641 l = g_list_append(l, pb);
5643 gtk_window_set_default_icon_list(l);
5645 gtk_widget_show_all(abtn);
5646 gtk_widget_show_all(main_window);
5649 void
5650 set_hook(void **hook, char *name)
5652 if (hook == NULL)
5653 errx(1, "set_hook");
5655 if (*hook == NULL) {
5656 *hook = dlsym(RTLD_NEXT, name);
5657 if (*hook == NULL)
5658 errx(1, "can't hook %s", name);
5662 /* override libsoup soup_cookie_equal because it doesn't look at domain */
5663 gboolean
5664 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
5666 g_return_val_if_fail(cookie1, FALSE);
5667 g_return_val_if_fail(cookie2, FALSE);
5669 return (!strcmp (cookie1->name, cookie2->name) &&
5670 !strcmp (cookie1->value, cookie2->value) &&
5671 !strcmp (cookie1->path, cookie2->path) &&
5672 !strcmp (cookie1->domain, cookie2->domain));
5675 void
5676 transfer_cookies(void)
5678 GSList *cf;
5679 SoupCookie *sc, *pc;
5681 cf = soup_cookie_jar_all_cookies(p_cookiejar);
5683 for (;cf; cf = cf->next) {
5684 pc = cf->data;
5685 sc = soup_cookie_copy(pc);
5686 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
5689 soup_cookies_free(cf);
5692 void
5693 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
5695 GSList *cf;
5696 SoupCookie *ci;
5698 print_cookie("soup_cookie_jar_delete_cookie", c);
5700 if (cookies_enabled == 0)
5701 return;
5703 if (jar == NULL || c == NULL)
5704 return;
5706 /* find and remove from persistent jar */
5707 cf = soup_cookie_jar_all_cookies(p_cookiejar);
5709 for (;cf; cf = cf->next) {
5710 ci = cf->data;
5711 if (soup_cookie_equal(ci, c)) {
5712 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
5713 break;
5717 soup_cookies_free(cf);
5719 /* delete from session jar */
5720 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
5723 void
5724 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
5726 struct domain *d;
5727 SoupCookie *c;
5728 FILE *r_cookie_f;
5730 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
5731 jar, p_cookiejar, s_cookiejar);
5733 if (cookies_enabled == 0)
5734 return;
5736 /* see if we are up and running */
5737 if (p_cookiejar == NULL) {
5738 _soup_cookie_jar_add_cookie(jar, cookie);
5739 return;
5741 /* disallow p_cookiejar adds, shouldn't happen */
5742 if (jar == p_cookiejar)
5743 return;
5745 if ((d = wl_find(cookie->domain, &c_wl)) == NULL) {
5746 blocked_cookies++;
5747 DNPRINTF(XT_D_COOKIE,
5748 "soup_cookie_jar_add_cookie: reject %s\n",
5749 cookie->domain);
5750 if (save_rejected_cookies) {
5751 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
5752 err(1, "reject cookie file");
5753 fseek(r_cookie_f, 0, SEEK_END);
5754 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
5755 cookie->http_only ? "#HttpOnly_" : "",
5756 cookie->domain,
5757 *cookie->domain == '.' ? "TRUE" : "FALSE",
5758 cookie->path,
5759 cookie->secure ? "TRUE" : "FALSE",
5760 cookie->expires ?
5761 (gulong)soup_date_to_time_t(cookie->expires) :
5763 cookie->name,
5764 cookie->value);
5765 fflush(r_cookie_f);
5766 fclose(r_cookie_f);
5768 if (!allow_volatile_cookies)
5769 return;
5772 if (cookie->expires == NULL && session_timeout) {
5773 soup_cookie_set_expires(cookie,
5774 soup_date_new_from_now(session_timeout));
5775 print_cookie("modified add cookie", cookie);
5778 /* see if we are white listed for persistence */
5779 if (d && d->handy) {
5780 /* add to persistent jar */
5781 c = soup_cookie_copy(cookie);
5782 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
5783 _soup_cookie_jar_add_cookie(p_cookiejar, c);
5786 /* add to session jar */
5787 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
5788 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
5791 void
5792 setup_cookies(void)
5794 char file[PATH_MAX];
5796 set_hook((void *)&_soup_cookie_jar_add_cookie,
5797 "soup_cookie_jar_add_cookie");
5798 set_hook((void *)&_soup_cookie_jar_delete_cookie,
5799 "soup_cookie_jar_delete_cookie");
5801 if (cookies_enabled == 0)
5802 return;
5805 * the following code is intricate due to overriding several libsoup
5806 * functions.
5807 * do not alter order of these operations.
5810 /* rejected cookies */
5811 if (save_rejected_cookies)
5812 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
5814 /* persistent cookies */
5815 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
5816 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
5818 /* session cookies */
5819 s_cookiejar = soup_cookie_jar_new();
5820 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
5821 cookie_policy, (void *)NULL);
5822 transfer_cookies();
5824 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
5827 void
5828 setup_proxy(char *uri)
5830 if (proxy_uri) {
5831 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
5832 soup_uri_free(proxy_uri);
5833 proxy_uri = NULL;
5835 if (http_proxy) {
5836 if (http_proxy != uri) {
5837 g_free(http_proxy);
5838 http_proxy = NULL;
5842 if (uri) {
5843 http_proxy = g_strdup(uri);
5844 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
5845 proxy_uri = soup_uri_new(http_proxy);
5846 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
5851 send_url_to_socket(char *url)
5853 int s, len, rv = -1;
5854 struct sockaddr_un sa;
5856 pwd = getpwuid(getuid());
5857 if (pwd == NULL)
5858 errx(1, "invalid user %d", getuid());
5860 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5861 warnx("send_url_to_socket: socket");
5862 return (-1);
5865 sa.sun_family = AF_UNIX;
5866 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5867 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5868 len = SUN_LEN(&sa);
5870 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
5871 warnx("send_url_to_socket: connect");
5872 goto done;
5875 if (send(s, url, strlen(url) + 1, 0) == -1) {
5876 warnx("send_url_to_socket: send");
5877 goto done;
5879 done:
5880 close(s);
5881 return (rv);
5884 void
5885 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
5887 int s, n;
5888 char str[XT_MAX_URL_LENGTH];
5889 socklen_t t = sizeof(struct sockaddr_un);
5890 struct sockaddr_un sa;
5891 struct passwd *p;
5892 uid_t uid;
5893 gid_t gid;
5895 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
5896 warn("socket_watcher: accept");
5897 return;
5900 if (getpeereid(s, &uid, &gid) == -1) {
5901 warn("socket_watcher: getpeereid");
5902 return;
5904 if (uid != getuid() || gid != getgid()) {
5905 warnx("socket_watcher: unauthorized user");
5906 return;
5909 p = getpwuid(uid);
5910 if (p == NULL) {
5911 warnx("socket_watcher: not a valid user");
5912 return;
5915 n = recv(s, str, sizeof(str), 0);
5916 if (n <= 0)
5917 return;
5919 create_new_tab(str, NULL, 1);
5923 is_running(void)
5925 int s, len, rv = 1;
5926 struct sockaddr_un sa;
5928 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5929 warn("is_running: socket");
5930 return (-1);
5933 sa.sun_family = AF_UNIX;
5934 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5935 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5936 len = SUN_LEN(&sa);
5938 /* connect to see if there is a listener */
5939 if (connect(s, (struct sockaddr *)&sa, len) == -1)
5940 rv = 0; /* not running */
5941 else
5942 rv = 1; /* already running */
5944 close(s);
5946 return (rv);
5950 build_socket(void)
5952 int s, len;
5953 struct sockaddr_un sa;
5955 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
5956 warn("build_socket: socket");
5957 return (-1);
5960 sa.sun_family = AF_UNIX;
5961 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
5962 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
5963 len = SUN_LEN(&sa);
5965 /* connect to see if there is a listener */
5966 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
5967 /* no listener so we will */
5968 unlink(sa.sun_path);
5970 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
5971 warn("build_socket: bind");
5972 goto done;
5975 if (listen(s, 1) == -1) {
5976 warn("build_socket: listen");
5977 goto done;
5980 return (s);
5983 done:
5984 close(s);
5985 return (-1);
5988 void
5989 usage(void)
5991 fprintf(stderr,
5992 "%s [-nSTVt][-f file] url ...\n", __progname);
5993 exit(0);
5997 main(int argc, char *argv[])
5999 struct stat sb;
6000 int c, focus = 1, s, optn = 0;
6001 char conf[PATH_MAX] = { '\0' };
6002 char file[PATH_MAX];
6003 char *env_proxy = NULL;
6004 FILE *f = NULL;
6006 start_argv = argv;
6008 while ((c = getopt(argc, argv, "STVf:tn")) != -1) {
6009 switch (c) {
6010 case 'S':
6011 showurl = 0;
6012 break;
6013 case 'T':
6014 showtabs = 0;
6015 break;
6016 case 'V':
6017 errx(0 , "Version: %s", version);
6018 break;
6019 case 'f':
6020 strlcpy(conf, optarg, sizeof(conf));
6021 break;
6022 case 't':
6023 tabless = 1;
6024 break;
6025 case 'n':
6026 optn = 1;
6027 break;
6028 default:
6029 usage();
6030 /* NOTREACHED */
6033 argc -= optind;
6034 argv += optind;
6036 TAILQ_INIT(&tabs);
6037 RB_INIT(&hl);
6038 RB_INIT(&js_wl);
6039 RB_INIT(&downloads);
6040 TAILQ_INIT(&mtl);
6041 TAILQ_INIT(&aliases);
6042 TAILQ_INIT(&undos);
6044 gnutls_global_init();
6046 /* generate session keys for xtp pages */
6047 generate_xtp_session_key(&dl_session_key);
6048 generate_xtp_session_key(&hl_session_key);
6049 generate_xtp_session_key(&cl_session_key);
6050 generate_xtp_session_key(&fl_session_key);
6052 /* prepare gtk */
6053 gtk_init(&argc, &argv);
6054 if (!g_thread_supported())
6055 g_thread_init(NULL);
6057 pwd = getpwuid(getuid());
6058 if (pwd == NULL)
6059 errx(1, "invalid user %d", getuid());
6061 /* set download dir */
6062 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
6064 /* set default string settings */
6065 home = g_strdup("http://www.peereboom.us");
6066 resource_dir = g_strdup("/usr/local/share/xxxterm/");
6067 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
6069 /* read config file */
6070 if (strlen(conf) == 0)
6071 snprintf(conf, sizeof conf, "%s/.%s",
6072 pwd->pw_dir, XT_CONF_FILE);
6073 config_parse(conf, 0);
6075 /* working directory */
6076 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
6077 if (stat(work_dir, &sb)) {
6078 if (mkdir(work_dir, S_IRWXU) == -1)
6079 err(1, "mkdir work_dir");
6080 if (stat(work_dir, &sb))
6081 err(1, "stat work_dir");
6083 if (S_ISDIR(sb.st_mode) == 0)
6084 errx(1, "%s not a dir", work_dir);
6085 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6086 warnx("fixing invalid permissions on %s", work_dir);
6087 if (chmod(work_dir, S_IRWXU) == -1)
6088 err(1, "chmod");
6091 /* certs dir */
6092 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
6093 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
6094 if (stat(certs_dir, &sb)) {
6095 if (mkdir(certs_dir, S_IRWXU) == -1)
6096 err(1, "mkdir certs_dir");
6097 if (stat(certs_dir, &sb))
6098 err(1, "stat certs_dir");
6100 if (S_ISDIR(sb.st_mode) == 0)
6101 errx(1, "%s not a dir", certs_dir);
6102 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6103 warnx("fixing invalid permissions on %s", certs_dir);
6104 if (chmod(certs_dir, S_IRWXU) == -1)
6105 err(1, "chmod");
6108 /* runtime settings that can override config file */
6109 if (runtime_settings[0] != '\0')
6110 config_parse(runtime_settings, 1);
6112 /* download dir */
6113 if (!strcmp(download_dir, pwd->pw_dir))
6114 strlcat(download_dir, "/downloads", sizeof download_dir);
6115 if (stat(download_dir, &sb)) {
6116 if (mkdir(download_dir, S_IRWXU) == -1)
6117 err(1, "mkdir download_dir");
6118 if (stat(download_dir, &sb))
6119 err(1, "stat download_dir");
6121 if (S_ISDIR(sb.st_mode) == 0)
6122 errx(1, "%s not a dir", download_dir);
6123 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
6124 warnx("fixing invalid permissions on %s", download_dir);
6125 if (chmod(download_dir, S_IRWXU) == -1)
6126 err(1, "chmod");
6129 /* favorites file */
6130 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
6131 if (stat(file, &sb)) {
6132 warnx("favorites file doesn't exist, creating it");
6133 if ((f = fopen(file, "w")) == NULL)
6134 err(1, "favorites");
6135 fclose(f);
6138 /* cookies */
6139 session = webkit_get_default_session();
6140 setup_cookies();
6142 /* certs */
6143 if (ssl_ca_file) {
6144 if (stat(ssl_ca_file, &sb)) {
6145 warn("no CA file: %s", ssl_ca_file);
6146 g_free(ssl_ca_file);
6147 ssl_ca_file = NULL;
6148 } else
6149 g_object_set(session,
6150 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
6151 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
6152 (void *)NULL);
6155 /* proxy */
6156 env_proxy = getenv("http_proxy");
6157 if (env_proxy)
6158 setup_proxy(env_proxy);
6159 else
6160 setup_proxy(http_proxy);
6162 /* see if there is already an xxxterm running */
6163 if (single_instance && is_running()) {
6164 optn = 1;
6165 warnx("already running");
6168 if (optn) {
6169 while (argc) {
6170 send_url_to_socket(argv[0]);
6172 argc--;
6173 argv++;
6175 exit(0);
6178 /* go graphical */
6179 create_canvas();
6181 if (save_global_history)
6182 restore_global_history();
6184 focus = restore_saved_tabs();
6186 while (argc) {
6187 create_new_tab(argv[0], NULL, focus);
6188 focus = 0;
6190 argc--;
6191 argv++;
6193 if (focus == 1)
6194 create_new_tab(home, NULL, 1);
6196 if (enable_socket)
6197 if ((s = build_socket()) != -1)
6198 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
6200 gtk_main();
6202 gnutls_global_deinit();
6204 return (0);