monster diff to try to get rid of err and errx. people run into these and
[xxxterm.git] / xxxterm.c
blob3be93d9ef96cb338298f83abcd722f5c84deab0f
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>
6 * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
8 * Permission to use, copy, modify, and distribute this software for any
9 * purpose with or without fee is hereby granted, provided that the above
10 * copyright notice and this permission notice appear in all copies.
12 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
13 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
14 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
15 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
16 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
17 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
18 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
22 * TODO:
23 * inverse color browsing
24 * favs
25 * - store in sqlite
26 * multi letter commands
27 * pre and post counts for commands
28 * autocompletion on various inputs
29 * create privacy browsing
30 * - encrypted local data
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <err.h>
36 #include <pwd.h>
37 #include <string.h>
38 #include <unistd.h>
39 #include <pthread.h>
40 #include <dlfcn.h>
41 #include <errno.h>
42 #include <signal.h>
43 #include <libgen.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #if defined(__linux__)
48 #include "linux/util.h"
49 #include "linux/tree.h"
50 #elif defined(__FreeBSD__)
51 #include <libutil.h>
52 #include "freebsd/util.h"
53 #include <sys/tree.h>
54 #else /* OpenBSD */
55 #include <util.h>
56 #include <sys/tree.h>
57 #endif
58 #include <sys/queue.h>
59 #include <sys/stat.h>
60 #include <sys/socket.h>
61 #include <sys/un.h>
63 #include <gtk/gtk.h>
64 #include <gdk/gdkkeysyms.h>
65 #include <webkit/webkit.h>
66 #include <libsoup/soup.h>
67 #include <gnutls/gnutls.h>
68 #include <JavaScriptCore/JavaScript.h>
69 #include <gnutls/x509.h>
71 #include "javascript.h"
74 javascript.h borrowed from vimprobable2 under the following license:
76 Copyright (c) 2009 Leon Winter
77 Copyright (c) 2009 Hannes Schueller
78 Copyright (c) 2009 Matto Fransen
80 Permission is hereby granted, free of charge, to any person obtaining a copy
81 of this software and associated documentation files (the "Software"), to deal
82 in the Software without restriction, including without limitation the rights
83 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
84 copies of the Software, and to permit persons to whom the Software is
85 furnished to do so, subject to the following conditions:
87 The above copyright notice and this permission notice shall be included in
88 all copies or substantial portions of the Software.
90 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
91 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
92 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
93 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
94 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
95 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
96 THE SOFTWARE.
99 static char *version = "$xxxterm$";
101 /* hooked functions */
102 void (*_soup_cookie_jar_add_cookie)(SoupCookieJar *, SoupCookie *);
103 void (*_soup_cookie_jar_delete_cookie)(SoupCookieJar *,
104 SoupCookie *);
106 /*#define XT_DEBUG*/
107 #ifdef XT_DEBUG
108 #define DPRINTF(x...) do { if (swm_debug) fprintf(stderr, x); } while (0)
109 #define DNPRINTF(n,x...) do { if (swm_debug & n) fprintf(stderr, x); } while (0)
110 #define XT_D_MOVE 0x0001
111 #define XT_D_KEY 0x0002
112 #define XT_D_TAB 0x0004
113 #define XT_D_URL 0x0008
114 #define XT_D_CMD 0x0010
115 #define XT_D_NAV 0x0020
116 #define XT_D_DOWNLOAD 0x0040
117 #define XT_D_CONFIG 0x0080
118 #define XT_D_JS 0x0100
119 #define XT_D_FAVORITE 0x0200
120 #define XT_D_PRINTING 0x0400
121 #define XT_D_COOKIE 0x0800
122 u_int32_t swm_debug = 0
123 | XT_D_MOVE
124 | XT_D_KEY
125 | XT_D_TAB
126 | XT_D_URL
127 | XT_D_CMD
128 | XT_D_NAV
129 | XT_D_DOWNLOAD
130 | XT_D_CONFIG
131 | XT_D_JS
132 | XT_D_FAVORITE
133 | XT_D_PRINTING
134 | XT_D_COOKIE
136 #else
137 #define DPRINTF(x...)
138 #define DNPRINTF(n,x...)
139 #endif
141 #define LENGTH(x) (sizeof x / sizeof x[0])
142 #define CLEAN(mask) (mask & ~(GDK_MOD2_MASK) & \
143 ~(GDK_BUTTON1_MASK) & \
144 ~(GDK_BUTTON2_MASK) & \
145 ~(GDK_BUTTON3_MASK) & \
146 ~(GDK_BUTTON4_MASK) & \
147 ~(GDK_BUTTON5_MASK))
149 char *icons[] = {
150 "xxxtermicon16.png",
151 "xxxtermicon32.png",
152 "xxxtermicon48.png",
153 "xxxtermicon64.png",
154 "xxxtermicon128.png"
157 struct tab {
158 TAILQ_ENTRY(tab) entry;
159 GtkWidget *vbox;
160 GtkWidget *tab_content;
161 GtkWidget *label;
162 GtkWidget *spinner;
163 GtkWidget *uri_entry;
164 GtkWidget *search_entry;
165 GtkWidget *toolbar;
166 GtkWidget *browser_win;
167 GtkWidget *statusbar;
168 GtkWidget *cmd;
169 GtkWidget *oops;
170 GtkWidget *backward;
171 GtkWidget *forward;
172 GtkWidget *stop;
173 GtkWidget *js_toggle;
174 guint tab_id;
175 WebKitWebView *wv;
177 WebKitWebHistoryItem *item;
178 WebKitWebBackForwardList *bfl;
180 /* favicon */
181 WebKitNetworkRequest *icon_request;
182 WebKitDownload *icon_download;
183 GdkPixbuf *icon_pixbuf;
184 gchar *icon_dest_uri;
186 /* adjustments for browser */
187 GtkScrollbar *sb_h;
188 GtkScrollbar *sb_v;
189 GtkAdjustment *adjust_h;
190 GtkAdjustment *adjust_v;
192 /* flags */
193 int focus_wv;
194 int ctrl_click;
195 gchar *status;
196 int xtp_meaning; /* identifies dls/favorites */
198 /* hints */
199 int hints_on;
200 int hint_mode;
201 #define XT_HINT_NONE (0)
202 #define XT_HINT_NUMERICAL (1)
203 #define XT_HINT_ALPHANUM (2)
204 char hint_buf[128];
205 char hint_num[128];
207 /* search */
208 char *search_text;
209 int search_forward;
211 /* settings */
212 WebKitWebSettings *settings;
213 int font_size;
214 gchar *user_agent;
216 TAILQ_HEAD(tab_list, tab);
218 struct history {
219 RB_ENTRY(history) entry;
220 const gchar *uri;
221 const gchar *title;
223 RB_HEAD(history_list, history);
225 struct download {
226 RB_ENTRY(download) entry;
227 int id;
228 WebKitDownload *download;
229 struct tab *tab;
231 RB_HEAD(download_list, download);
233 struct domain {
234 RB_ENTRY(domain) entry;
235 gchar *d;
236 int handy; /* app use */
238 RB_HEAD(domain_list, domain);
240 struct undo {
241 TAILQ_ENTRY(undo) entry;
242 gchar *uri;
243 GList *history;
244 int back; /* Keeps track of how many back
245 * history items there are. */
247 TAILQ_HEAD(undo_tailq, undo);
249 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
250 int next_download_id = 1;
252 struct karg {
253 int i;
254 char *s;
257 /* defines */
258 #define XT_NAME ("XXXTerm")
259 #define XT_DIR (".xxxterm")
260 #define XT_CACHE_DIR ("cache")
261 #define XT_CERT_DIR ("certs/")
262 #define XT_SESSIONS_DIR ("sessions/")
263 #define XT_CONF_FILE ("xxxterm.conf")
264 #define XT_FAVS_FILE ("favorites")
265 #define XT_SAVED_TABS_FILE ("main_session")
266 #define XT_RESTART_TABS_FILE ("restart_tabs")
267 #define XT_SOCKET_FILE ("socket")
268 #define XT_HISTORY_FILE ("history")
269 #define XT_SAVE_SESSION_ID ("SESSION_NAME=")
270 #define XT_CB_HANDLED (TRUE)
271 #define XT_CB_PASSTHROUGH (FALSE)
272 #define XT_DOCTYPE "<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN' 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>"
273 #define XT_HTML_TAG "<html xmlns='http://www.w3.org/1999/xhtml'>"
274 #define XT_DLMAN_REFRESH "10"
275 #define XT_PAGE_STYLE "<style type='text/css'>\n" \
276 "td {overflow: hidden;" \
277 " padding: 2px 2px 2px 2px;" \
278 " border: 1px solid black}\n" \
279 "tr:hover {background: #ffff99 ;}\n" \
280 "th {background-color: #cccccc;" \
281 " border: 1px solid black}" \
282 "table {border-spacing: 0; " \
283 " width: 90%%; border: 1px black" \
284 " solid; table-layout: fixed}\n" \
285 ".progress-outer{" \
286 " border: 1px solid black;" \
287 " height: 8px;" \
288 " width: 90%%;}" \
289 ".progress-inner{" \
290 " float: left;" \
291 " height: 8px;" \
292 " background: green;}" \
293 ".dlstatus{" \
294 " font-size: small;" \
295 " text-align: center;}" \
296 "</style>\n\n"
297 #define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
298 #define XT_MAX_UNDO_CLOSE_TAB (32)
299 #define XT_RESERVED_CHARS "$&+,/:;=?@ \"<>#%%{}|^~[]`"
301 /* file sizes */
302 #define SZ_KB ((uint64_t) 1024)
303 #define SZ_MB (SZ_KB * SZ_KB)
304 #define SZ_GB (SZ_KB * SZ_KB * SZ_KB)
305 #define SZ_TB (SZ_KB * SZ_KB * SZ_KB * SZ_KB)
308 * xxxterm "protocol" (xtp)
309 * We use this for managing stuff like downloads and favorites. They
310 * make magical HTML pages in memory which have xxxt:// links in order
311 * to communicate with xxxterm's internals. These links take the format:
312 * xxxt://class/session_key/action/arg
314 * Don't begin xtp class/actions as 0. atoi returns that on error.
316 * Typically we have not put addition of items in this framework, as
317 * adding items is either done via an ex-command or via a keybinding instead.
320 #define XT_XTP_STR "xxxt://"
322 /* XTP classes (xxxt://<class>) */
323 #define XT_XTP_DL 1 /* downloads */
324 #define XT_XTP_HL 2 /* history */
325 #define XT_XTP_CL 3 /* cookies */
326 #define XT_XTP_FL 4 /* favorites */
328 /* XTP download actions */
329 #define XT_XTP_DL_LIST 1
330 #define XT_XTP_DL_CANCEL 2
331 #define XT_XTP_DL_REMOVE 3
333 /* XTP history actions */
334 #define XT_XTP_HL_LIST 1
335 #define XT_XTP_HL_REMOVE 2
337 /* XTP cookie actions */
338 #define XT_XTP_CL_LIST 1
339 #define XT_XTP_CL_REMOVE 2
341 /* XTP cookie actions */
342 #define XT_XTP_FL_LIST 1
343 #define XT_XTP_FL_REMOVE 2
345 /* xtp tab meanings - identifies which tabs have xtp pages in */
346 #define XT_XTP_TAB_MEANING_NORMAL 0 /* normal url */
347 #define XT_XTP_TAB_MEANING_DL 1 /* download manager in this tab */
348 #define XT_XTP_TAB_MEANING_FL 2 /* favorite manager in this tab */
349 #define XT_XTP_TAB_MEANING_HL 3 /* history manager in this tab */
350 #define XT_XTP_TAB_MEANING_CL 4 /* cookie manager in this tab */
352 /* actions */
353 #define XT_MOVE_INVALID (0)
354 #define XT_MOVE_DOWN (1)
355 #define XT_MOVE_UP (2)
356 #define XT_MOVE_BOTTOM (3)
357 #define XT_MOVE_TOP (4)
358 #define XT_MOVE_PAGEDOWN (5)
359 #define XT_MOVE_PAGEUP (6)
360 #define XT_MOVE_HALFDOWN (7)
361 #define XT_MOVE_HALFUP (8)
362 #define XT_MOVE_LEFT (9)
363 #define XT_MOVE_FARLEFT (10)
364 #define XT_MOVE_RIGHT (11)
365 #define XT_MOVE_FARRIGHT (12)
367 #define XT_TAB_LAST (-4)
368 #define XT_TAB_FIRST (-3)
369 #define XT_TAB_PREV (-2)
370 #define XT_TAB_NEXT (-1)
371 #define XT_TAB_INVALID (0)
372 #define XT_TAB_NEW (1)
373 #define XT_TAB_DELETE (2)
374 #define XT_TAB_DELQUIT (3)
375 #define XT_TAB_OPEN (4)
376 #define XT_TAB_UNDO_CLOSE (5)
377 #define XT_TAB_SHOW (6)
378 #define XT_TAB_HIDE (7)
380 #define XT_NAV_INVALID (0)
381 #define XT_NAV_BACK (1)
382 #define XT_NAV_FORWARD (2)
383 #define XT_NAV_RELOAD (3)
384 #define XT_NAV_RELOAD_CACHE (4)
386 #define XT_FOCUS_INVALID (0)
387 #define XT_FOCUS_URI (1)
388 #define XT_FOCUS_SEARCH (2)
390 #define XT_SEARCH_INVALID (0)
391 #define XT_SEARCH_NEXT (1)
392 #define XT_SEARCH_PREV (2)
394 #define XT_PASTE_CURRENT_TAB (0)
395 #define XT_PASTE_NEW_TAB (1)
397 #define XT_FONT_SET (0)
399 #define XT_URL_SHOW (1)
400 #define XT_URL_HIDE (2)
402 #define XT_STATUSBAR_SHOW (1)
403 #define XT_STATUSBAR_HIDE (2)
405 #define XT_WL_TOGGLE (1<<0)
406 #define XT_WL_ENABLE (1<<1)
407 #define XT_WL_DISABLE (1<<2)
408 #define XT_WL_FQDN (1<<3) /* default */
409 #define XT_WL_TOPLEVEL (1<<4)
411 #define XT_CMD_OPEN (0)
412 #define XT_CMD_OPEN_CURRENT (1)
413 #define XT_CMD_TABNEW (2)
414 #define XT_CMD_TABNEW_CURRENT (3)
416 #define XT_STATUS_NOTHING (0)
417 #define XT_STATUS_LINK (1)
418 #define XT_STATUS_URI (2)
420 #define XT_SES_DONOTHING (0)
421 #define XT_SES_CLOSETABS (1)
423 #define XT_COOKIE_NORMAL (0)
424 #define XT_COOKIE_WHITELIST (1)
426 /* mime types */
427 struct mime_type {
428 char *mt_type;
429 char *mt_action;
430 int mt_default;
431 TAILQ_ENTRY(mime_type) entry;
433 TAILQ_HEAD(mime_type_list, mime_type);
435 /* uri aliases */
436 struct alias {
437 char *a_name;
438 char *a_uri;
439 TAILQ_ENTRY(alias) entry;
441 TAILQ_HEAD(alias_list, alias);
443 /* settings that require restart */
444 int tabless = 0; /* allow only 1 tab */
445 int enable_socket = 0;
446 int single_instance = 0; /* only allow one xxxterm to run */
447 int fancy_bar = 1; /* fancy toolbar */
448 int browser_mode = XT_COOKIE_NORMAL;
450 /* runtime settings */
451 int show_tabs = 1; /* show tabs on notebook */
452 int show_url = 1; /* show url toolbar on notebook */
453 int show_statusbar = 0; /* vimperator style status bar */
454 int ctrl_click_focus = 0; /* ctrl click gets focus */
455 int cookies_enabled = 1; /* enable cookies */
456 int read_only_cookies = 0; /* enable to not write cookies */
457 int enable_scripts = 1;
458 int enable_plugins = 0;
459 int default_font_size = 12;
460 int window_height = 768;
461 int window_width = 1024;
462 int icon_size = 2; /* 1 = smallest, 2+ = bigger */
463 unsigned refresh_interval = 10; /* download refresh interval */
464 int enable_cookie_whitelist = 0;
465 int enable_js_whitelist = 0;
466 time_t session_timeout = 3600; /* cookie session timeout */
467 int cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
468 char *ssl_ca_file = NULL;
469 char *resource_dir = NULL;
470 gboolean ssl_strict_certs = FALSE;
471 int append_next = 1; /* append tab after current tab */
472 char *home = NULL;
473 char *search_string = NULL;
474 char *http_proxy = NULL;
475 char download_dir[PATH_MAX];
476 char runtime_settings[PATH_MAX]; /* override of settings */
477 int allow_volatile_cookies = 0;
478 int save_global_history = 0; /* save global history to disk */
479 char *user_agent = NULL;
480 int save_rejected_cookies = 0;
481 time_t session_autosave = 0;
482 int guess_search = 0;
484 struct settings;
485 int set_download_dir(struct settings *, char *);
486 int set_runtime_dir(struct settings *, char *);
487 int set_browser_mode(struct settings *, char *);
488 int set_cookie_policy(struct settings *, char *);
489 int add_alias(struct settings *, char *);
490 int add_mime_type(struct settings *, char *);
491 int add_cookie_wl(struct settings *, char *);
492 int add_js_wl(struct settings *, char *);
493 void button_set_stockid(GtkWidget *, char *);
494 GtkWidget * create_button(char *, char *, int);
496 char *get_browser_mode(struct settings *);
497 char *get_cookie_policy(struct settings *);
499 char *get_download_dir(struct settings *);
500 char *get_runtime_dir(struct settings *);
502 void walk_alias(struct settings *, void (*)(struct settings *, char *, void *), void *);
503 void walk_cookie_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
504 void walk_js_wl(struct settings *, void (*)(struct settings *, char *, void *), void *);
505 void walk_mime_type(struct settings *, void (*)(struct settings *, char *, void *), void *);
507 struct special {
508 int (*set)(struct settings *, char *);
509 char *(*get)(struct settings *);
510 void (*walk)(struct settings *, void (*cb)(struct settings *, char *, void *), void *);
513 struct special s_browser_mode = {
514 set_browser_mode,
515 get_browser_mode,
516 NULL
519 struct special s_cookie = {
520 set_cookie_policy,
521 get_cookie_policy,
522 NULL
525 struct special s_alias = {
526 add_alias,
527 NULL,
528 walk_alias
531 struct special s_mime = {
532 add_mime_type,
533 NULL,
534 walk_mime_type
537 struct special s_js = {
538 add_js_wl,
539 NULL,
540 walk_js_wl
543 struct special s_cookie_wl = {
544 add_cookie_wl,
545 NULL,
546 walk_cookie_wl
549 struct special s_download_dir = {
550 set_download_dir,
551 get_download_dir,
552 NULL
555 struct settings {
556 char *name;
557 int type;
558 #define XT_S_INVALID (0)
559 #define XT_S_INT (1)
560 #define XT_S_STR (2)
561 uint32_t flags;
562 #define XT_SF_RESTART (1<<0)
563 #define XT_SF_RUNTIME (1<<1)
564 int *ival;
565 char **sval;
566 struct special *s;
567 } rs[] = {
568 { "append_next", XT_S_INT, 0, &append_next, NULL, NULL },
569 { "allow_volatile_cookies", XT_S_INT, 0, &allow_volatile_cookies, NULL, NULL },
570 { "browser_mode", XT_S_INT, 0, NULL, NULL,&s_browser_mode },
571 { "cookie_policy", XT_S_INT, 0, NULL, NULL,&s_cookie },
572 { "cookies_enabled", XT_S_INT, 0, &cookies_enabled, NULL, NULL },
573 { "ctrl_click_focus", XT_S_INT, 0, &ctrl_click_focus, NULL, NULL },
574 { "default_font_size", XT_S_INT, 0, &default_font_size, NULL, NULL },
575 { "download_dir", XT_S_STR, 0, NULL, NULL,&s_download_dir },
576 { "enable_cookie_whitelist", XT_S_INT, 0, &enable_cookie_whitelist, NULL, NULL },
577 { "enable_js_whitelist", XT_S_INT, 0, &enable_js_whitelist, NULL, NULL },
578 { "enable_plugins", XT_S_INT, 0, &enable_plugins, NULL, NULL },
579 { "enable_scripts", XT_S_INT, 0, &enable_scripts, NULL, NULL },
580 { "enable_socket", XT_S_INT, XT_SF_RESTART,&enable_socket, NULL, NULL },
581 { "fancy_bar", XT_S_INT, XT_SF_RESTART,&fancy_bar, NULL, NULL },
582 { "home", XT_S_STR, 0, NULL, &home, NULL },
583 { "http_proxy", XT_S_STR, 0, NULL, &http_proxy, NULL },
584 { "icon_size", XT_S_INT, 0, &icon_size, NULL, NULL },
585 { "read_only_cookies", XT_S_INT, 0, &read_only_cookies, NULL, NULL },
586 { "refresh_interval", XT_S_INT, 0, &refresh_interval, NULL, NULL },
587 { "resource_dir", XT_S_STR, 0, NULL, &resource_dir, NULL },
588 { "search_string", XT_S_STR, 0, NULL, &search_string, NULL },
589 { "save_global_history", XT_S_INT, XT_SF_RESTART,&save_global_history, NULL, NULL },
590 { "save_rejected_cookies", XT_S_INT, XT_SF_RESTART,&save_rejected_cookies, NULL, NULL },
591 { "session_timeout", XT_S_INT, 0, &session_timeout, NULL, NULL },
592 { "session_autosave", XT_S_INT, 0, &session_autosave, NULL, NULL },
593 { "single_instance", XT_S_INT, XT_SF_RESTART,&single_instance, NULL, NULL },
594 { "show_tabs", XT_S_INT, 0, &show_tabs, NULL, NULL },
595 { "show_url", XT_S_INT, 0, &show_url, NULL, NULL },
596 { "guess_search", XT_S_INT, 0, &guess_search, NULL, NULL },
597 { "show_statusbar", XT_S_INT, 0, &show_statusbar, NULL, NULL },
598 { "ssl_ca_file", XT_S_STR, 0, NULL, &ssl_ca_file, NULL },
599 { "ssl_strict_certs", XT_S_INT, 0, &ssl_strict_certs, NULL, NULL },
600 { "user_agent", XT_S_STR, 0, NULL, &user_agent, NULL },
601 { "window_height", XT_S_INT, 0, &window_height, NULL, NULL },
602 { "window_width", XT_S_INT, 0, &window_width, NULL, NULL },
604 /* runtime settings */
605 { "alias", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_alias },
606 { "cookie_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_cookie_wl },
607 { "js_wl", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_js },
608 { "mime_type", XT_S_STR, XT_SF_RUNTIME, NULL, NULL, &s_mime },
611 /* globals */
612 extern char *__progname;
613 char **start_argv;
614 struct passwd *pwd;
615 GtkWidget *main_window;
616 GtkNotebook *notebook;
617 GtkWidget *arrow, *abtn;
618 struct tab_list tabs;
619 struct history_list hl;
620 struct download_list downloads;
621 struct domain_list c_wl;
622 struct domain_list js_wl;
623 struct undo_tailq undos;
624 int undo_count;
625 int updating_dl_tabs = 0;
626 int updating_hl_tabs = 0;
627 int updating_cl_tabs = 0;
628 int updating_fl_tabs = 0;
629 char *global_search;
630 uint64_t blocked_cookies = 0;
631 char named_session[PATH_MAX];
632 void update_favicon(struct tab *);
633 int icon_size_map(int);
635 void
636 sigchild(int sig)
638 int saved_errno, status;
639 pid_t pid;
641 saved_errno = errno;
643 while ((pid = waitpid(WAIT_ANY, &status, WNOHANG)) != 0) {
644 if (pid == -1) {
645 if (errno == EINTR)
646 continue;
647 if (errno != ECHILD) {
649 clog_warn("sigchild: waitpid:");
652 break;
655 if (WIFEXITED(status)) {
656 if (WEXITSTATUS(status) != 0) {
658 clog_warnx("sigchild: child exit status: %d",
659 WEXITSTATUS(status));
662 } else {
664 clog_warnx("sigchild: child is terminated abnormally");
669 errno = saved_errno;
672 void
673 load_webkit_string(struct tab *t, const char *str)
675 /* we set this to indicate we want to manually do navaction */
676 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
677 webkit_web_view_load_string(t->wv, str, NULL, NULL, NULL);
680 void
681 set_status(struct tab *t, gchar *s, int status)
683 gchar *type = NULL;
685 if (s == NULL)
686 return;
688 switch (status) {
689 case XT_STATUS_LINK:
690 type = g_strdup_printf("Link: %s", s);
691 if (!t->status)
692 t->status = g_strdup(gtk_entry_get_text(GTK_ENTRY(t->statusbar)));
693 s = type;
694 break;
695 case XT_STATUS_URI:
696 type = g_strdup_printf("%s", s);
697 if (!t->status) {
698 t->status = g_strdup(type);
700 s = type;
701 if (!t->status)
702 t->status = g_strdup(s);
703 break;
704 case XT_STATUS_NOTHING:
705 /* FALL THROUGH */
706 default:
707 break;
709 gtk_entry_set_text(GTK_ENTRY(t->statusbar), s);
710 if (type)
711 g_free(type);
714 void
715 hide_oops(struct tab *t)
717 gtk_widget_hide(t->oops);
720 void
721 hide_cmd(struct tab *t)
723 gtk_widget_hide(t->cmd);
726 void
727 show_cmd(struct tab *t)
729 gtk_widget_hide(t->oops);
730 gtk_widget_show(t->cmd);
733 void
734 show_oops(struct tab *t, const char *fmt, ...)
736 va_list ap;
737 char *msg;
739 if (fmt == NULL)
740 return;
742 va_start(ap, fmt);
743 if (vasprintf(&msg, fmt, ap) == -1)
744 errx(1, "show_oops failed");
745 va_end(ap);
747 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
748 gtk_widget_hide(t->cmd);
749 gtk_widget_show(t->oops);
752 /* XXX collapse with show_oops */
753 void
754 show_oops_s(const char *fmt, ...)
756 va_list ap;
757 char *msg;
758 struct tab *ti, *t = NULL;
760 if (fmt == NULL)
761 return;
763 TAILQ_FOREACH(ti, &tabs, entry)
764 if (ti->tab_id == gtk_notebook_current_page(notebook)) {
765 t = ti;
766 break;
768 if (t == NULL)
769 return;
771 va_start(ap, fmt);
772 if (vasprintf(&msg, fmt, ap) == -1)
773 errx(1, "show_oops_s failed");
774 va_end(ap);
776 gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
777 gtk_widget_hide(t->cmd);
778 gtk_widget_show(t->oops);
781 char *
782 get_as_string(struct settings *s)
784 char *r = NULL;
786 if (s == NULL)
787 return (NULL);
789 if (s->s) {
790 if (s->s->get)
791 r = s->s->get(s);
792 else
793 warnx("get_as_string skip %s\n", s->name);
794 } else if (s->type == XT_S_INT)
795 r = g_strdup_printf("%d", *s->ival);
796 else if (s->type == XT_S_STR)
797 r = g_strdup(*s->sval);
798 else
799 r = g_strdup_printf("INVALID TYPE");
801 return (r);
804 void
805 settings_walk(void (*cb)(struct settings *, char *, void *), void *cb_args)
807 int i;
808 char *s;
810 for (i = 0; i < LENGTH(rs); i++) {
811 if (rs[i].s && rs[i].s->walk)
812 rs[i].s->walk(&rs[i], cb, cb_args);
813 else {
814 s = get_as_string(&rs[i]);
815 cb(&rs[i], s, cb_args);
816 g_free(s);
822 set_browser_mode(struct settings *s, char *val)
824 if (!strcmp(val, "whitelist")) {
825 browser_mode = XT_COOKIE_WHITELIST;
826 allow_volatile_cookies = 0;
827 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
828 cookies_enabled = 1;
829 enable_cookie_whitelist = 1;
830 read_only_cookies = 0;
831 save_rejected_cookies = 0;
832 session_timeout = 3600;
833 enable_scripts = 0;
834 enable_js_whitelist = 1;
835 } else if (!strcmp(val, "normal")) {
836 browser_mode = XT_COOKIE_NORMAL;
837 allow_volatile_cookies = 0;
838 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
839 cookies_enabled = 1;
840 enable_cookie_whitelist = 0;
841 read_only_cookies = 0;
842 save_rejected_cookies = 0;
843 session_timeout = 3600;
844 enable_scripts = 1;
845 enable_js_whitelist = 0;
846 } else
847 return (1);
849 return (0);
852 char *
853 get_browser_mode(struct settings *s)
855 char *r = NULL;
857 if (browser_mode == XT_COOKIE_WHITELIST)
858 r = g_strdup("whitelist");
859 else if (browser_mode == XT_COOKIE_NORMAL)
860 r = g_strdup("normal");
861 else
862 return (NULL);
864 return (r);
868 set_cookie_policy(struct settings *s, char *val)
870 if (!strcmp(val, "no3rdparty"))
871 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY;
872 else if (!strcmp(val, "accept"))
873 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_ALWAYS;
874 else if (!strcmp(val, "reject"))
875 cookie_policy = SOUP_COOKIE_JAR_ACCEPT_NEVER;
876 else
877 return (1);
879 return (0);
882 char *
883 get_cookie_policy(struct settings *s)
885 char *r = NULL;
887 if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NO_THIRD_PARTY)
888 r = g_strdup("no3rdparty");
889 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_ALWAYS)
890 r = g_strdup("accept");
891 else if (cookie_policy == SOUP_COOKIE_JAR_ACCEPT_NEVER)
892 r = g_strdup("reject");
893 else
894 return (NULL);
896 return (r);
899 char *
900 get_download_dir(struct settings *s)
902 if (download_dir[0] == '\0')
903 return (0);
904 return (g_strdup(download_dir));
908 set_download_dir(struct settings *s, char *val)
910 if (val[0] == '~')
911 snprintf(download_dir, sizeof download_dir, "%s/%s",
912 pwd->pw_dir, &val[1]);
913 else
914 strlcpy(download_dir, val, sizeof download_dir);
916 return (0);
920 * Session IDs.
921 * We use these to prevent people putting xxxt:// URLs on
922 * websites in the wild. We generate 8 bytes and represent in hex (16 chars)
924 #define XT_XTP_SES_KEY_SZ 8
925 #define XT_XTP_SES_KEY_HEX_FMT \
926 "%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx"
927 char *dl_session_key; /* downloads */
928 char *hl_session_key; /* history list */
929 char *cl_session_key; /* cookie list */
930 char *fl_session_key; /* favorites list */
932 char work_dir[PATH_MAX];
933 char certs_dir[PATH_MAX];
934 char cache_dir[PATH_MAX];
935 char sessions_dir[PATH_MAX];
936 char cookie_file[PATH_MAX];
937 SoupURI *proxy_uri = NULL;
938 SoupSession *session;
939 SoupCookieJar *s_cookiejar;
940 SoupCookieJar *p_cookiejar;
941 char rc_fname[PATH_MAX];
943 struct mime_type_list mtl;
944 struct alias_list aliases;
946 /* protos */
947 void create_new_tab(char *, struct undo *, int);
948 void delete_tab(struct tab *);
949 void adjustfont_webkit(struct tab *, int);
950 int run_script(struct tab *, char *);
951 int download_rb_cmp(struct download *, struct download *);
952 int xtp_page_hl(struct tab *t, struct karg *args);
953 int xtp_page_dl(struct tab *t, struct karg *args);
954 int xtp_page_cl(struct tab *t, struct karg *args);
955 int xtp_page_fl(struct tab *t, struct karg *args);
958 history_rb_cmp(struct history *h1, struct history *h2)
960 return (strcmp(h1->uri, h2->uri));
962 RB_GENERATE(history_list, history, entry, history_rb_cmp);
965 domain_rb_cmp(struct domain *d1, struct domain *d2)
967 return (strcmp(d1->d, d2->d));
969 RB_GENERATE(domain_list, domain, entry, domain_rb_cmp);
972 * generate a session key to secure xtp commands.
973 * pass in a ptr to the key in question and it will
974 * be modified in place.
976 void
977 generate_xtp_session_key(char **key)
979 uint8_t rand_bytes[XT_XTP_SES_KEY_SZ];
981 /* free old key */
982 if (*key)
983 g_free(*key);
985 /* make a new one */
986 arc4random_buf(rand_bytes, XT_XTP_SES_KEY_SZ);
987 *key = g_strdup_printf(XT_XTP_SES_KEY_HEX_FMT,
988 rand_bytes[0], rand_bytes[1], rand_bytes[2], rand_bytes[3],
989 rand_bytes[4], rand_bytes[5], rand_bytes[6], rand_bytes[7]);
991 DNPRINTF(XT_D_DOWNLOAD, "%s: new session key '%s'\n", __func__, *key);
995 * validate a xtp session key.
996 * return 1 if OK
999 validate_xtp_session_key(struct tab *t, char *trusted, char *untrusted)
1001 if (strcmp(trusted, untrusted) != 0) {
1002 show_oops(t, "%s: xtp session key mismatch possible spoof",
1003 __func__);
1004 return (0);
1007 return (1);
1011 download_rb_cmp(struct download *e1, struct download *e2)
1013 return (e1->id < e2->id ? -1 : e1->id > e2->id);
1015 RB_GENERATE(download_list, download, entry, download_rb_cmp);
1017 struct valid_url_types {
1018 char *type;
1019 } vut[] = {
1020 { "http://" },
1021 { "https://" },
1022 { "ftp://" },
1023 { "file://" },
1024 { XT_XTP_STR },
1028 valid_url_type(char *url)
1030 int i;
1032 for (i = 0; i < LENGTH(vut); i++)
1033 if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
1034 return (0);
1036 return (1);
1039 void
1040 print_cookie(char *msg, SoupCookie *c)
1042 if (c == NULL)
1043 return;
1045 if (msg)
1046 DNPRINTF(XT_D_COOKIE, "%s\n", msg);
1047 DNPRINTF(XT_D_COOKIE, "name : %s\n", c->name);
1048 DNPRINTF(XT_D_COOKIE, "value : %s\n", c->value);
1049 DNPRINTF(XT_D_COOKIE, "domain : %s\n", c->domain);
1050 DNPRINTF(XT_D_COOKIE, "path : %s\n", c->path);
1051 DNPRINTF(XT_D_COOKIE, "expires : %s\n",
1052 c->expires ? soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "");
1053 DNPRINTF(XT_D_COOKIE, "secure : %d\n", c->secure);
1054 DNPRINTF(XT_D_COOKIE, "http_only: %d\n", c->http_only);
1055 DNPRINTF(XT_D_COOKIE, "====================================\n");
1058 void
1059 walk_alias(struct settings *s,
1060 void (*cb)(struct settings *, char *, void *), void *cb_args)
1062 struct alias *a;
1063 char *str;
1065 if (s == NULL || cb == NULL) {
1066 show_oops_s("walk_alias invalid parameters");
1067 return;
1070 TAILQ_FOREACH(a, &aliases, entry) {
1071 str = g_strdup_printf("%s --> %s", a->a_name, a->a_uri);
1072 cb(s, str, cb_args);
1073 g_free(str);
1077 char *
1078 match_alias(char *url_in)
1080 struct alias *a;
1081 char *arg;
1082 char *url_out = NULL, *search, *enc_arg;
1084 search = g_strdup(url_in);
1085 arg = search;
1086 if (strsep(&arg, " \t") == NULL) {
1087 show_oops_s("match_alias: NULL URL");
1088 goto done;
1091 TAILQ_FOREACH(a, &aliases, entry) {
1092 if (!strcmp(search, a->a_name))
1093 break;
1096 if (a != NULL) {
1097 DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
1098 a->a_name);
1099 if (arg != NULL) {
1100 enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
1101 url_out = g_strdup_printf(a->a_uri, enc_arg);
1102 g_free(enc_arg);
1103 } else
1104 url_out = g_strdup(a->a_uri);
1106 done:
1107 g_free(search);
1108 return (url_out);
1111 char *
1112 guess_url_type(char *url_in)
1114 struct stat sb;
1115 char *url_out = NULL, *enc_search = NULL;
1117 url_out = match_alias(url_in);
1118 if (url_out != NULL)
1119 return (url_out);
1121 if (guess_search) {
1123 * If there is no dot nor slash in the string and it isn't a
1124 * path to a local file and doesn't resolves to an IP, assume
1125 * that the user wants to search for the string.
1128 if (strchr(url_in, '.') == NULL &&
1129 strchr(url_in, '/') == NULL &&
1130 stat(url_in, &sb) != 0 &&
1131 gethostbyname(url_in) == NULL) {
1133 enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
1134 url_out = g_strdup_printf(search_string, enc_search);
1135 g_free(enc_search);
1136 return (url_out);
1140 /* XXX not sure about this heuristic */
1141 if (stat(url_in, &sb) == 0)
1142 url_out = g_strdup_printf("file://%s", url_in);
1143 else
1144 url_out = g_strdup_printf("http://%s", url_in); /* guess http */
1146 DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
1148 return (url_out);
1151 void
1152 load_uri(WebKitWebView *wv, gchar *uri)
1154 gchar *newuri = NULL;
1156 if (uri == NULL || !strlen(uri))
1157 return;
1159 if (valid_url_type(uri)) {
1160 newuri = guess_url_type(uri);
1161 uri = newuri;
1164 webkit_web_view_load_uri(wv, uri);
1166 if (newuri)
1167 g_free(newuri);
1171 add_alias(struct settings *s, char *line)
1173 char *l, *alias;
1174 struct alias *a = NULL;
1176 if (s == NULL || line == NULL) {
1177 show_oops_s("add_alias invalid parameters");
1178 return (1);
1181 l = line;
1182 a = g_malloc(sizeof(*a));
1184 if ((alias = strsep(&l, " \t,")) == NULL || l == NULL) {
1185 show_oops_s("add_alias: incomplete alias definition");
1186 goto bad;
1188 if (strlen(alias) == 0 || strlen(l) == 0) {
1189 show_oops_s("add_alias: invalid alias definition");
1190 goto bad;
1193 a->a_name = g_strdup(alias);
1194 a->a_uri = g_strdup(l);
1196 DNPRINTF(XT_D_CONFIG, "add_alias: %s for %s\n", a->a_name, a->a_uri);
1198 TAILQ_INSERT_TAIL(&aliases, a, entry);
1200 return (0);
1201 bad:
1202 if (a)
1203 g_free(a);
1204 return (1);
1208 add_mime_type(struct settings *s, char *line)
1210 char *mime_type;
1211 char *l;
1212 struct mime_type *m = NULL;
1214 /* XXX this could be smarter */
1216 if (line == NULL) {
1217 show_oops_s("add_mime_type invalid parameters");
1218 return (1);
1221 l = line;
1222 m = g_malloc(sizeof(*m));
1224 if ((mime_type = strsep(&l, " \t,")) == NULL || l == NULL) {
1225 show_oops_s("add_mime_type: invalid mime_type");
1226 goto bad;
1228 if (mime_type[strlen(mime_type) - 1] == '*') {
1229 mime_type[strlen(mime_type) - 1] = '\0';
1230 m->mt_default = 1;
1231 } else
1232 m->mt_default = 0;
1234 if (strlen(mime_type) == 0 || strlen(l) == 0) {
1235 show_oops_s("add_mime_type: invalid mime_type");
1236 goto bad;
1239 m->mt_type = g_strdup(mime_type);
1240 m->mt_action = g_strdup(l);
1242 DNPRINTF(XT_D_CONFIG, "add_mime_type: type %s action %s default %d\n",
1243 m->mt_type, m->mt_action, m->mt_default);
1245 TAILQ_INSERT_TAIL(&mtl, m, entry);
1247 return (0);
1248 bad:
1249 if (m)
1250 g_free(m);
1251 return (1);
1254 struct mime_type *
1255 find_mime_type(char *mime_type)
1257 struct mime_type *m, *def = NULL, *rv = NULL;
1259 TAILQ_FOREACH(m, &mtl, entry) {
1260 if (m->mt_default &&
1261 !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
1262 def = m;
1264 if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
1265 rv = m;
1266 break;
1270 if (rv == NULL)
1271 rv = def;
1273 return (rv);
1276 void
1277 walk_mime_type(struct settings *s,
1278 void (*cb)(struct settings *, char *, void *), void *cb_args)
1280 struct mime_type *m;
1281 char *str;
1283 if (s == NULL || cb == NULL)
1284 show_oops_s("walk_mime_type invalid parameters");
1286 TAILQ_FOREACH(m, &mtl, entry) {
1287 str = g_strdup_printf("%s%s --> %s",
1288 m->mt_type,
1289 m->mt_default ? "*" : "",
1290 m->mt_action);
1291 cb(s, str, cb_args);
1292 g_free(str);
1296 void
1297 wl_add(char *str, struct domain_list *wl, int handy)
1299 struct domain *d;
1300 int add_dot = 0;
1302 if (str == NULL || wl == NULL)
1303 return;
1304 if (strlen(str) < 2)
1305 return;
1307 DNPRINTF(XT_D_COOKIE, "wl_add in: %s\n", str);
1309 /* treat *.moo.com the same as .moo.com */
1310 if (str[0] == '*' && str[1] == '.')
1311 str = &str[1];
1312 else if (str[0] == '.')
1313 str = &str[0];
1314 else
1315 add_dot = 1;
1317 d = g_malloc(sizeof *d);
1318 if (add_dot)
1319 d->d = g_strdup_printf(".%s", str);
1320 else
1321 d->d = g_strdup(str);
1322 d->handy = handy;
1324 if (RB_INSERT(domain_list, wl, d))
1325 goto unwind;
1327 DNPRINTF(XT_D_COOKIE, "wl_add: %s\n", d->d);
1328 return;
1329 unwind:
1330 if (d) {
1331 if (d->d)
1332 g_free(d->d);
1333 g_free(d);
1338 add_cookie_wl(struct settings *s, char *entry)
1340 wl_add(entry, &c_wl, 1);
1341 return (0);
1344 void
1345 walk_cookie_wl(struct settings *s,
1346 void (*cb)(struct settings *, char *, void *), void *cb_args)
1348 struct domain *d;
1350 if (s == NULL || cb == NULL)
1351 show_oops_s("walk_cookie_wl invalid parameters");
1353 RB_FOREACH_REVERSE(d, domain_list, &c_wl)
1354 cb(s, d->d, cb_args);
1357 void
1358 walk_js_wl(struct settings *s,
1359 void (*cb)(struct settings *, char *, void *), void *cb_args)
1361 struct domain *d;
1363 if (s == NULL || cb == NULL)
1364 show_oops_s("walk_js_wl invalid parameters");
1366 RB_FOREACH_REVERSE(d, domain_list, &js_wl)
1367 cb(s, d->d, cb_args);
1371 add_js_wl(struct settings *s, char *entry)
1373 wl_add(entry, &js_wl, 1 /* persistent */);
1374 return (0);
1377 struct domain *
1378 wl_find(const gchar *search, struct domain_list *wl)
1380 int i;
1381 struct domain *d = NULL, dfind;
1382 gchar *s = NULL;
1384 if (search == NULL || wl == NULL)
1385 return (NULL);
1386 if (strlen(search) < 2)
1387 return (NULL);
1389 if (search[0] != '.')
1390 s = g_strdup_printf(".%s", search);
1391 else
1392 s = g_strdup(search);
1394 for (i = strlen(s) - 1; i >= 0; i--) {
1395 if (s[i] == '.') {
1396 dfind.d = &s[i];
1397 d = RB_FIND(domain_list, wl, &dfind);
1398 if (d)
1399 goto done;
1403 done:
1404 if (s)
1405 g_free(s);
1407 return (d);
1410 struct domain *
1411 wl_find_uri(const gchar *s, struct domain_list *wl)
1413 int i;
1414 char *ss;
1415 struct domain *r;
1417 if (s == NULL || wl == NULL)
1418 return (NULL);
1420 if (!strncmp(s, "http://", strlen("http://")))
1421 s = &s[strlen("http://")];
1422 else if (!strncmp(s, "https://", strlen("https://")))
1423 s = &s[strlen("https://")];
1425 if (strlen(s) < 2)
1426 return (NULL);
1428 for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
1429 /* chop string at first slash */
1430 if (s[i] == '/' || s[i] == '\0') {
1431 ss = g_strdup(s);
1432 ss[i] = '\0';
1433 r = wl_find(ss, wl);
1434 g_free(ss);
1435 return (r);
1438 return (NULL);
1441 char *
1442 get_toplevel_domain(char *domain)
1444 char *s;
1445 int found = 0;
1447 if (domain == NULL)
1448 return (NULL);
1449 if (strlen(domain) < 2)
1450 return (NULL);
1452 s = &domain[strlen(domain) - 1];
1453 while (s != domain) {
1454 if (*s == '.') {
1455 found++;
1456 if (found == 2)
1457 return (s);
1459 s--;
1462 if (found)
1463 return (domain);
1465 return (NULL);
1468 #define WS "\n= \t"
1469 void
1470 config_parse(char *filename, int runtime)
1472 FILE *config, *f;
1473 char *line, *cp, *var, *val;
1474 size_t len, lineno = 0;
1475 int i, handled, *p;
1476 char **s, file[PATH_MAX];
1477 struct stat sb;
1479 DNPRINTF(XT_D_CONFIG, "config_parse: filename %s\n", filename);
1481 if (filename == NULL)
1482 return;
1484 if (runtime && runtime_settings[0] != '\0') {
1485 snprintf(file, sizeof file, "%s/%s",
1486 work_dir, runtime_settings);
1487 if (stat(file, &sb)) {
1488 warnx("runtime file doesn't exist, creating it");
1489 if ((f = fopen(file, "w")) == NULL)
1490 err(1, "runtime");
1491 fprintf(f, "# AUTO GENERATED, DO NOT EDIT\n");
1492 fclose(f);
1494 } else
1495 strlcpy(file, filename, sizeof file);
1497 if ((config = fopen(file, "r")) == NULL) {
1498 warn("config_parse: cannot open %s", filename);
1499 return;
1502 for (;;) {
1503 if ((line = fparseln(config, &len, &lineno, NULL, 0)) == NULL)
1504 if (feof(config) || ferror(config))
1505 break;
1507 cp = line;
1508 cp += (long)strspn(cp, WS);
1509 if (cp[0] == '\0') {
1510 /* empty line */
1511 free(line);
1512 continue;
1515 if ((var = strsep(&cp, WS)) == NULL || cp == NULL)
1516 break;
1518 cp += (long)strspn(cp, WS);
1520 if ((val = strsep(&cp, "\0")) == NULL)
1521 break;
1523 DNPRINTF(XT_D_CONFIG, "config_parse: %s=%s\n",var ,val);
1525 /* get settings */
1526 for (i = 0, handled = 0; i < LENGTH(rs); i++) {
1527 if (strcmp(var, rs[i].name))
1528 continue;
1530 if (rs[i].s) {
1531 if (rs[i].s->set(&rs[i], val))
1532 errx(1, "invalid value for %s", var);
1533 handled = 1;
1534 break;
1535 } else
1536 switch (rs[i].type) {
1537 case XT_S_INT:
1538 p = rs[i].ival;
1539 *p = atoi(val);
1540 handled = 1;
1541 break;
1542 case XT_S_STR:
1543 s = rs[i].sval;
1544 if (s == NULL)
1545 errx(1, "invalid sval for %s",
1546 rs[i].name);
1547 if (*s)
1548 g_free(*s);
1549 *s = g_strdup(val);
1550 handled = 1;
1551 break;
1552 case XT_S_INVALID:
1553 default:
1554 errx(1, "invalid type for %s", var);
1556 break;
1558 if (handled == 0)
1559 errx(1, "invalid conf file entry: %s=%s", var, val);
1561 free(line);
1564 fclose(config);
1567 char *
1568 js_ref_to_string(JSContextRef context, JSValueRef ref)
1570 char *s = NULL;
1571 size_t l;
1572 JSStringRef jsref;
1574 jsref = JSValueToStringCopy(context, ref, NULL);
1575 if (jsref == NULL)
1576 return (NULL);
1578 l = JSStringGetMaximumUTF8CStringSize(jsref);
1579 s = g_malloc(l);
1580 if (s)
1581 JSStringGetUTF8CString(jsref, s, l);
1582 JSStringRelease(jsref);
1584 return (s);
1587 void
1588 disable_hints(struct tab *t)
1590 bzero(t->hint_buf, sizeof t->hint_buf);
1591 bzero(t->hint_num, sizeof t->hint_num);
1592 run_script(t, "vimprobable_clear()");
1593 t->hints_on = 0;
1594 t->hint_mode = XT_HINT_NONE;
1597 void
1598 enable_hints(struct tab *t)
1600 bzero(t->hint_buf, sizeof t->hint_buf);
1601 run_script(t, "vimprobable_show_hints()");
1602 t->hints_on = 1;
1603 t->hint_mode = XT_HINT_NONE;
1606 #define XT_JS_OPEN ("open;")
1607 #define XT_JS_OPEN_LEN (strlen(XT_JS_OPEN))
1608 #define XT_JS_FIRE ("fire;")
1609 #define XT_JS_FIRE_LEN (strlen(XT_JS_FIRE))
1610 #define XT_JS_FOUND ("found;")
1611 #define XT_JS_FOUND_LEN (strlen(XT_JS_FOUND))
1614 run_script(struct tab *t, char *s)
1616 JSGlobalContextRef ctx;
1617 WebKitWebFrame *frame;
1618 JSStringRef str;
1619 JSValueRef val, exception;
1620 char *es, buf[128];
1622 DNPRINTF(XT_D_JS, "run_script: tab %d %s\n",
1623 t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
1625 frame = webkit_web_view_get_main_frame(t->wv);
1626 ctx = webkit_web_frame_get_global_context(frame);
1628 str = JSStringCreateWithUTF8CString(s);
1629 val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
1630 NULL, 0, &exception);
1631 JSStringRelease(str);
1633 DNPRINTF(XT_D_JS, "run_script: val %p\n", val);
1634 if (val == NULL) {
1635 es = js_ref_to_string(ctx, exception);
1636 DNPRINTF(XT_D_JS, "run_script: exception %s\n", es);
1637 g_free(es);
1638 return (1);
1639 } else {
1640 es = js_ref_to_string(ctx, val);
1641 DNPRINTF(XT_D_JS, "run_script: val %s\n", es);
1643 /* handle return value right here */
1644 if (!strncmp(es, XT_JS_OPEN, XT_JS_OPEN_LEN)) {
1645 disable_hints(t);
1646 webkit_web_view_load_uri(t->wv, &es[XT_JS_OPEN_LEN]);
1649 if (!strncmp(es, XT_JS_FIRE, XT_JS_FIRE_LEN)) {
1650 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
1651 &es[XT_JS_FIRE_LEN]);
1652 run_script(t, buf);
1653 disable_hints(t);
1656 if (!strncmp(es, XT_JS_FOUND, XT_JS_FOUND_LEN)) {
1657 if (atoi(&es[XT_JS_FOUND_LEN]) == 0)
1658 disable_hints(t);
1661 g_free(es);
1664 return (0);
1668 hint(struct tab *t, struct karg *args)
1671 DNPRINTF(XT_D_JS, "hint: tab %d\n", t->tab_id);
1673 if (t->hints_on == 0)
1674 enable_hints(t);
1675 else
1676 disable_hints(t);
1678 return (0);
1682 * Doesn't work fully, due to the following bug:
1683 * https://bugs.webkit.org/show_bug.cgi?id=51747
1686 restore_global_history(void)
1688 char file[PATH_MAX];
1689 FILE *f;
1690 struct history *h;
1691 gchar *uri;
1692 gchar *title;
1694 snprintf(file, sizeof file, "%s/%s/%s",
1695 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1697 if ((f = fopen(file, "r")) == NULL) {
1698 warnx("%s: fopen", __func__);
1699 return (1);
1702 for (;;) {
1703 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1704 if (feof(f) || ferror(f))
1705 break;
1707 if ((title = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1708 if (feof(f) || ferror(f)) {
1709 free(uri);
1710 warnx("%s: broken history file\n", __func__);
1711 return (1);
1714 if (uri && strlen(uri) && title && strlen(title)) {
1715 webkit_web_history_item_new_with_data(uri, title);
1716 h = g_malloc(sizeof(struct history));
1717 h->uri = g_strdup(uri);
1718 h->title = g_strdup(title);
1719 RB_INSERT(history_list, &hl, h);
1720 } else {
1721 warnx("%s: failed to restore history\n", __func__);
1722 free(uri);
1723 free(title);
1724 return (1);
1727 free(uri);
1728 free(title);
1729 uri = NULL;
1730 title = NULL;
1733 return (0);
1737 save_global_history_to_disk(struct tab *t)
1739 char file[PATH_MAX];
1740 FILE *f;
1741 struct history *h;
1743 snprintf(file, sizeof file, "%s/%s/%s",
1744 pwd->pw_dir, XT_DIR, XT_HISTORY_FILE);
1746 if ((f = fopen(file, "w")) == NULL) {
1747 show_oops(t, "%s: global history file: %s",
1748 __func__, strerror(errno));
1749 return (1);
1752 RB_FOREACH_REVERSE(h, history_list, &hl) {
1753 if (h->uri && h->title)
1754 fprintf(f, "%s\n%s\n", h->uri, h->title);
1757 fclose(f);
1759 return (0);
1763 quit(struct tab *t, struct karg *args)
1765 if (save_global_history)
1766 save_global_history_to_disk(t);
1768 gtk_main_quit();
1770 return (1);
1774 open_tabs(struct tab *t, struct karg *a)
1776 char file[PATH_MAX];
1777 FILE *f = NULL;
1778 char *uri = NULL;
1779 int rv = 1;
1780 struct tab *ti, *tt;
1782 if (a == NULL)
1783 goto done;
1785 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1786 if ((f = fopen(file, "r")) == NULL)
1787 goto done;
1789 ti = TAILQ_LAST(&tabs, tab_list);
1791 for (;;) {
1792 if ((uri = fparseln(f, NULL, NULL, NULL, 0)) == NULL)
1793 if (feof(f) || ferror(f))
1794 break;
1796 /* retrieve session name */
1797 if (uri && g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
1798 strlcpy(named_session,
1799 &uri[strlen(XT_SAVE_SESSION_ID)],
1800 sizeof named_session);
1801 continue;
1804 if (uri && strlen(uri))
1805 create_new_tab(uri, NULL, 1);
1807 free(uri);
1808 uri = NULL;
1811 /* close open tabs */
1812 if (a->i == XT_SES_CLOSETABS && ti != NULL) {
1813 for (;;) {
1814 tt = TAILQ_FIRST(&tabs);
1815 if (tt != ti) {
1816 delete_tab(tt);
1817 continue;
1819 delete_tab(tt);
1820 break;
1824 rv = 0;
1825 done:
1826 if (f)
1827 fclose(f);
1829 return (rv);
1833 restore_saved_tabs(void)
1835 char file[PATH_MAX];
1836 int unlink_file = 0;
1837 struct stat sb;
1838 struct karg a;
1839 int rv = 0;
1841 snprintf(file, sizeof file, "%s/%s",
1842 sessions_dir, XT_RESTART_TABS_FILE);
1843 if (stat(file, &sb) == -1)
1844 a.s = XT_SAVED_TABS_FILE;
1845 else {
1846 unlink_file = 1;
1847 a.s = XT_RESTART_TABS_FILE;
1850 a.i = XT_SES_DONOTHING;
1851 rv = open_tabs(NULL, &a);
1853 if (unlink_file)
1854 unlink(file);
1856 return (rv);
1860 save_tabs(struct tab *t, struct karg *a)
1862 char file[PATH_MAX];
1863 FILE *f;
1864 struct tab *ti;
1865 WebKitWebFrame *frame;
1866 const gchar *uri;
1867 int len = 0, i;
1868 gchar **arr = NULL;
1870 if (a == NULL)
1871 return (1);
1872 if (a->s == NULL)
1873 snprintf(file, sizeof file, "%s/%s",
1874 sessions_dir, named_session);
1875 else
1876 snprintf(file, sizeof file, "%s/%s", sessions_dir, a->s);
1878 if ((f = fopen(file, "w")) == NULL) {
1879 show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
1880 return (1);
1883 /* save session name */
1884 fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
1886 /* save tabs, in the order they are arranged in the notebook */
1887 TAILQ_FOREACH(ti, &tabs, entry)
1888 len++;
1890 arr = g_malloc0(len * sizeof(gchar *));
1892 TAILQ_FOREACH(ti, &tabs, entry) {
1893 frame = webkit_web_view_get_main_frame(ti->wv);
1894 uri = webkit_web_frame_get_uri(frame);
1895 if (uri && strlen(uri) > 0)
1896 arr[gtk_notebook_page_num(notebook, ti->vbox)] = (gchar *)uri;
1899 for (i = 0; i < len; i++)
1900 if (arr[i])
1901 fprintf(f, "%s\n", arr[i]);
1903 g_free(arr);
1904 fclose(f);
1906 return (0);
1910 save_tabs_and_quit(struct tab *t, struct karg *args)
1912 struct karg a;
1914 a.s = NULL;
1915 save_tabs(t, &a);
1916 quit(t, NULL);
1918 return (1);
1922 yank_uri(struct tab *t, struct karg *args)
1924 WebKitWebFrame *frame;
1925 const gchar *uri;
1926 GtkClipboard *clipboard;
1928 frame = webkit_web_view_get_main_frame(t->wv);
1929 uri = webkit_web_frame_get_uri(frame);
1930 if (!uri)
1931 return (1);
1933 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1934 gtk_clipboard_set_text(clipboard, uri, -1);
1936 return (0);
1939 struct paste_args {
1940 struct tab *t;
1941 int i;
1944 void
1945 paste_uri_cb(GtkClipboard *clipboard, const gchar *text, gpointer data)
1947 struct paste_args *pap;
1949 if (data == NULL || text == NULL || !strlen(text))
1950 return;
1952 pap = (struct paste_args *)data;
1954 switch(pap->i) {
1955 case XT_PASTE_CURRENT_TAB:
1956 load_uri(pap->t->wv, (gchar *)text);
1957 break;
1958 case XT_PASTE_NEW_TAB:
1959 create_new_tab((gchar *)text, NULL, 1);
1960 break;
1963 g_free(pap);
1967 paste_uri(struct tab *t, struct karg *args)
1969 GtkClipboard *clipboard;
1970 struct paste_args *pap;
1972 pap = g_malloc(sizeof(struct paste_args));
1974 pap->t = t;
1975 pap->i = args->i;
1977 clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
1978 gtk_clipboard_request_text(clipboard, paste_uri_cb, pap);
1980 return (0);
1983 char *
1984 find_domain(const char *s, int add_dot)
1986 int i;
1987 char *r = NULL, *ss = NULL;
1989 if (s == NULL)
1990 return (NULL);
1992 if (!strncmp(s, "http://", strlen("http://")))
1993 s = &s[strlen("http://")];
1994 else if (!strncmp(s, "https://", strlen("https://")))
1995 s = &s[strlen("https://")];
1997 if (strlen(s) < 2)
1998 return (NULL);
2000 ss = g_strdup(s);
2001 for (i = 0; i < strlen(ss) + 1 /* yes er need this */; i++)
2002 /* chop string at first slash */
2003 if (ss[i] == '/' || ss[i] == '\0') {
2004 ss[i] = '\0';
2005 if (add_dot)
2006 r = g_strdup_printf(".%s", ss);
2007 else
2008 r = g_strdup(ss);
2009 break;
2011 g_free(ss);
2013 return (r);
2017 toggle_cwl(struct tab *t, struct karg *args)
2019 WebKitWebFrame *frame;
2020 struct domain *d;
2021 char *uri;
2022 char *dom = NULL, *dom_toggle = NULL;
2023 int es;
2025 if (args == NULL)
2026 return (0);
2028 frame = webkit_web_view_get_main_frame(t->wv);
2029 uri = (char *)webkit_web_frame_get_uri(frame);
2030 dom = find_domain(uri, 1);
2031 d = wl_find(dom, &c_wl);
2032 if (d == NULL)
2033 es = 0;
2034 else
2035 es = 1;
2037 if (args->i & XT_WL_TOGGLE)
2038 es = !es;
2039 else if ((args->i & XT_WL_ENABLE) && es != 1)
2040 es = 1;
2041 else if ((args->i & XT_WL_DISABLE) && es != 0)
2042 es = 0;
2044 if (args->i & XT_WL_TOPLEVEL)
2045 dom_toggle = get_toplevel_domain(dom);
2046 else
2047 dom_toggle = dom;
2049 if (es)
2050 /* enable cookies for domain */
2051 wl_add(dom_toggle, &c_wl, 0);
2052 else
2053 /* disable cookies for domain */
2054 RB_REMOVE(domain_list, &c_wl, d);
2056 webkit_web_view_reload(t->wv);
2058 g_free(dom);
2059 return (0);
2063 toggle_js(struct tab *t, struct karg *args)
2065 int es;
2066 WebKitWebFrame *frame;
2067 const gchar *uri;
2068 struct domain *d;
2069 char *dom = NULL, *dom_toggle = NULL;
2071 if (args == NULL)
2072 return (0);
2074 g_object_get(G_OBJECT(t->settings),
2075 "enable-scripts", &es, (char *)NULL);
2076 if (args->i & XT_WL_TOGGLE)
2077 es = !es;
2078 else if ((args->i & XT_WL_ENABLE) && es != 1)
2079 es = 1;
2080 else if ((args->i & XT_WL_DISABLE) && es != 0)
2081 es = 0;
2082 else
2083 return (0);
2085 frame = webkit_web_view_get_main_frame(t->wv);
2086 uri = (char *)webkit_web_frame_get_uri(frame);
2087 dom = find_domain(uri, 1);
2088 if (uri == NULL || dom == NULL) {
2089 show_oops(t, "Can't toggle domain in JavaScript white list");
2090 goto done;
2093 if (args->i & XT_WL_TOPLEVEL)
2094 dom_toggle = get_toplevel_domain(dom);
2095 else
2096 dom_toggle = dom;
2098 if (es) {
2099 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PLAY);
2100 wl_add(dom_toggle, &js_wl, 0 /* session */);
2101 } else {
2102 d = wl_find(dom_toggle, &js_wl);
2103 if (d)
2104 RB_REMOVE(domain_list, &js_wl, d);
2105 button_set_stockid(t->js_toggle, GTK_STOCK_MEDIA_PAUSE);
2107 g_object_set(G_OBJECT(t->settings),
2108 "enable-scripts", es, (char *)NULL);
2109 webkit_web_view_set_settings(t->wv, t->settings);
2110 webkit_web_view_reload(t->wv);
2111 done:
2112 if (dom)
2113 g_free(dom);
2114 return (0);
2117 void
2118 js_toggle_cb(GtkWidget *w, struct tab *t)
2120 struct karg a;
2122 a.i = XT_WL_TOGGLE | XT_WL_FQDN;
2123 toggle_js(t, &a);
2127 toggle_src(struct tab *t, struct karg *args)
2129 gboolean mode;
2131 if (t == NULL)
2132 return (0);
2134 mode = webkit_web_view_get_view_source_mode(t->wv);
2135 webkit_web_view_set_view_source_mode(t->wv, !mode);
2136 webkit_web_view_reload(t->wv);
2138 return (0);
2141 void
2142 focus_webview(struct tab *t)
2144 if (t == NULL)
2145 return;
2147 /* only grab focus if we are visible */
2148 if (gtk_notebook_get_current_page(notebook) == t->tab_id)
2149 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
2153 focus(struct tab *t, struct karg *args)
2155 if (t == NULL || args == NULL)
2156 return (1);
2158 if (show_url == 0)
2159 return (0);
2161 if (args->i == XT_FOCUS_URI)
2162 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
2163 else if (args->i == XT_FOCUS_SEARCH)
2164 gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
2166 return (0);
2170 stats(struct tab *t, struct karg *args)
2172 char *stats, *s, line[64 * 1024];
2173 uint64_t line_count = 0;
2174 FILE *r_cookie_f;
2176 if (t == NULL)
2177 show_oops_s("stats invalid parameters");
2179 line[0] = '\0';
2180 if (save_rejected_cookies) {
2181 if ((r_cookie_f = fopen(rc_fname, "r"))) {
2182 for (;;) {
2183 s = fgets(line, sizeof line, r_cookie_f);
2184 if (s == NULL || feof(r_cookie_f) ||
2185 ferror(r_cookie_f))
2186 break;
2187 line_count++;
2189 fclose(r_cookie_f);
2190 snprintf(line, sizeof line,
2191 "<br>Cookies blocked(*) total: %llu", line_count);
2192 } else
2193 show_oops(t, "Can't open blocked cookies file: %s",
2194 strerror(errno));
2197 stats = g_strdup_printf(XT_DOCTYPE
2198 "<html>"
2199 "<head>"
2200 "<title>Statistics</title>"
2201 "</head>"
2202 "<h1>Statistics</h1>"
2203 "<body>"
2204 "Cookies blocked(*) this session: %llu"
2205 "%s"
2206 "<p><small><b>*</b> results vary based on settings"
2207 "</body>"
2208 "</html>",
2209 blocked_cookies,
2210 line);
2212 load_webkit_string(t, stats);
2213 g_free(stats);
2215 return (0);
2219 about(struct tab *t, struct karg *args)
2221 char *about;
2223 if (t == NULL)
2224 show_oops_s("about invalid parameters");
2226 about = g_strdup_printf(XT_DOCTYPE
2227 "<html>"
2228 "<head>"
2229 "<title>About</title>"
2230 "</head>"
2231 "<h1>About</h1>"
2232 "<body>"
2233 "<b>Version: %s</b><p>"
2234 "Authors:"
2235 "<ul>"
2236 "<li>Marco Peereboom &lt;marco@peereboom.us&gt;</li>"
2237 "<li>Stevan Andjelkovic &lt;stevan@student.chalmers.se&gt;</li>"
2238 "<li>Edd Barrett &lt;vext01@gmail.com&gt; </li>"
2239 "<li>Todd T. Fries &lt;todd@fries.net&gt; </li>"
2240 "</ul>"
2241 "Copyrights and licenses can be found on the XXXterm "
2242 "<a href=\"http://opensource.conformal.com/wiki/XXXTerm\">website</a>"
2243 "</body>"
2244 "</html>",
2245 version
2248 load_webkit_string(t, about);
2249 g_free(about);
2251 return (0);
2255 help(struct tab *t, struct karg *args)
2257 char *help;
2259 if (t == NULL)
2260 show_oops_s("help invalid parameters");
2262 help = XT_DOCTYPE
2263 "<html>"
2264 "<head>"
2265 "<title>XXXterm</title>"
2266 "<meta http-equiv=\"REFRESH\" content=\"0;"
2267 "url=http://opensource.conformal.com/cgi-bin/man-cgi?xxxterm\">"
2268 "</head>"
2269 "<body>"
2270 "XXXterm man page <a href=\"http://opensource.conformal.com/"
2271 "cgi-bin/man-cgi?xxxterm\">http://opensource.conformal.com/"
2272 "cgi-bin/man-cgi?xxxterm</a>"
2273 "</body>"
2274 "</html>"
2277 load_webkit_string(t, help);
2279 return (0);
2283 * update all favorite tabs apart from one. Pass NULL if
2284 * you want to update all.
2286 void
2287 update_favorite_tabs(struct tab *apart_from)
2289 struct tab *t;
2290 if (!updating_fl_tabs) {
2291 updating_fl_tabs = 1; /* stop infinite recursion */
2292 TAILQ_FOREACH(t, &tabs, entry)
2293 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_FL)
2294 && (t != apart_from))
2295 xtp_page_fl(t, NULL);
2296 updating_fl_tabs = 0;
2300 /* show a list of favorites (bookmarks) */
2302 xtp_page_fl(struct tab *t, struct karg *args)
2304 char file[PATH_MAX];
2305 FILE *f;
2306 char *uri = NULL, *title = NULL;
2307 size_t len, lineno = 0;
2308 int i, failed = 0;
2309 char *header, *body, *tmp, *html = NULL;
2311 DNPRINTF(XT_D_FAVORITE, "%s:", __func__);
2313 if (t == NULL)
2314 warn("%s: bad param", __func__);
2316 /* mark tab as favorite list */
2317 t->xtp_meaning = XT_XTP_TAB_MEANING_FL;
2319 /* new session key */
2320 if (!updating_fl_tabs)
2321 generate_xtp_session_key(&fl_session_key);
2323 /* open favorites */
2324 snprintf(file, sizeof file, "%s/%s/%s",
2325 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
2326 if ((f = fopen(file, "r")) == NULL) {
2327 show_oops(t, "Can't open favorites file: %s", strerror(errno));
2328 return (1);
2331 /* header */
2332 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
2333 "<title>Favorites</title>\n"
2334 "%s"
2335 "</head>"
2336 "<h1>Favorites</h1>\n",
2337 XT_PAGE_STYLE);
2339 /* body */
2340 body = g_strdup_printf("<div align='center'><table><tr>"
2341 "<th style='width: 4%%'>&#35;</th><th>Link</th>"
2342 "<th style='width: 15%%'>Remove</th></tr>\n");
2344 for (i = 1;;) {
2345 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2346 if (feof(f) || ferror(f))
2347 break;
2348 if (len == 0) {
2349 free(title);
2350 title = NULL;
2351 continue;
2354 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
2355 if (feof(f) || ferror(f)) {
2356 show_oops(t, "favorites file corrupt");
2357 failed = 1;
2358 break;
2361 tmp = body;
2362 body = g_strdup_printf("%s<tr>"
2363 "<td>%d</td>"
2364 "<td><a href='%s'>%s</a></td>"
2365 "<td style='text-align: center'>"
2366 "<a href='%s%d/%s/%d/%d'>X</a></td>"
2367 "</tr>\n",
2368 body, i, uri, title,
2369 XT_XTP_STR, XT_XTP_FL, fl_session_key, XT_XTP_FL_REMOVE, i);
2371 g_free(tmp);
2373 free(uri);
2374 uri = NULL;
2375 free(title);
2376 title = NULL;
2377 i++;
2379 fclose(f);
2381 /* if none, say so */
2382 if (i == 1) {
2383 tmp = body;
2384 body = g_strdup_printf("%s<tr>"
2385 "<td colspan='3' style='text-align: center'>"
2386 "No favorites - To add one use the 'favadd' command."
2387 "</td></tr>", body);
2388 g_free(tmp);
2391 if (uri)
2392 free(uri);
2393 if (title)
2394 free(title);
2396 /* render */
2397 if (!failed) {
2398 html = g_strdup_printf("%s%s</table></div></html>",
2399 header, body);
2400 load_webkit_string(t, html);
2403 update_favorite_tabs(t);
2405 if (header)
2406 g_free(header);
2407 if (body)
2408 g_free(body);
2409 if (html)
2410 g_free(html);
2412 return (failed);
2415 char *
2416 getparams(char *cmd, char *cmp)
2418 char *rv = NULL;
2420 if (cmd && cmp) {
2421 if (!strncmp(cmd, cmp, strlen(cmp))) {
2422 rv = cmd + strlen(cmp);
2423 while (*rv == ' ')
2424 rv++;
2425 if (strlen(rv) == 0)
2426 rv = NULL;
2430 return (rv);
2433 void
2434 show_certs(struct tab *t, gnutls_x509_crt_t *certs,
2435 size_t cert_count, char *title)
2437 gnutls_datum_t cinfo;
2438 char *tmp, *header, *body, *footer;
2439 int i;
2441 header = g_strdup_printf("<title>%s</title><html><body>", title);
2442 footer = g_strdup("</body></html>");
2443 body = g_strdup("");
2445 for (i = 0; i < cert_count; i++) {
2446 if (gnutls_x509_crt_print(certs[i], GNUTLS_CRT_PRINT_FULL,
2447 &cinfo))
2448 return;
2450 tmp = body;
2451 body = g_strdup_printf("%s<h2>Cert #%d</h2><pre>%s</pre>",
2452 body, i, cinfo.data);
2453 gnutls_free(cinfo.data);
2454 g_free(tmp);
2457 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2458 g_free(header);
2459 g_free(body);
2460 g_free(footer);
2461 load_webkit_string(t, tmp);
2462 g_free(tmp);
2466 ca_cmd(struct tab *t, struct karg *args)
2468 FILE *f = NULL;
2469 int rv = 1, certs = 0, certs_read;
2470 struct stat sb;
2471 gnutls_datum dt;
2472 gnutls_x509_crt_t *c = NULL;
2473 char *certs_buf = NULL, *s;
2475 /* yeah yeah stat race */
2476 if (stat(ssl_ca_file, &sb)) {
2477 show_oops(t, "no CA file: %s", ssl_ca_file);
2478 goto done;
2481 if ((f = fopen(ssl_ca_file, "r")) == NULL) {
2482 show_oops(t, "Can't open CA file: %s", strerror(errno));
2483 return (1);
2486 certs_buf = g_malloc(sb.st_size + 1);
2487 if (fread(certs_buf, 1, sb.st_size, f) != sb.st_size) {
2488 show_oops(t, "Can't read CA file: %s", strerror(errno));
2489 goto done;
2491 certs_buf[sb.st_size] = '\0';
2493 s = certs_buf;
2494 while ((s = strstr(s, "BEGIN CERTIFICATE"))) {
2495 certs++;
2496 s += strlen("BEGIN CERTIFICATE");
2499 bzero(&dt, sizeof dt);
2500 dt.data = certs_buf;
2501 dt.size = sb.st_size;
2502 c = g_malloc(sizeof(gnutls_x509_crt_t) * certs);
2503 certs_read = gnutls_x509_crt_list_import(c, &certs, &dt,
2504 GNUTLS_X509_FMT_PEM, 0);
2505 if (certs_read <= 0) {
2506 show_oops(t, "No cert(s) available");
2507 goto done;
2509 show_certs(t, c, certs_read, "Certificate Authority Certificates");
2510 done:
2511 if (c)
2512 g_free(c);
2513 if (certs_buf)
2514 g_free(certs_buf);
2515 if (f)
2516 fclose(f);
2518 return (rv);
2522 connect_socket_from_uri(char *uri, char *domain, size_t domain_sz)
2524 SoupURI *su = NULL;
2525 struct addrinfo hints, *res = NULL, *ai;
2526 int s = -1, on;
2527 char port[8];
2529 if (uri && !g_str_has_prefix(uri, "https://"))
2530 goto done;
2532 su = soup_uri_new(uri);
2533 if (su == NULL)
2534 goto done;
2535 if (!SOUP_URI_VALID_FOR_HTTP(su))
2536 goto done;
2538 snprintf(port, sizeof port, "%d", su->port);
2539 bzero(&hints, sizeof(struct addrinfo));
2540 hints.ai_flags = AI_CANONNAME;
2541 hints.ai_family = AF_UNSPEC;
2542 hints.ai_socktype = SOCK_STREAM;
2544 if (getaddrinfo(su->host, port, &hints, &res))
2545 goto done;
2547 for (ai = res; ai; ai = ai->ai_next) {
2548 if (ai->ai_family != AF_INET && ai->ai_family != AF_INET6)
2549 continue;
2551 s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
2552 if (s < 0)
2553 goto done;
2554 if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on,
2555 sizeof(on)) == -1)
2556 goto done;
2558 if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0)
2559 goto done;
2562 if (domain)
2563 strlcpy(domain, su->host, domain_sz);
2564 done:
2565 if (su)
2566 soup_uri_free(su);
2567 if (res)
2568 freeaddrinfo(res);
2570 return (s);
2574 stop_tls(gnutls_session_t gsession, gnutls_certificate_credentials_t xcred)
2576 if (gsession)
2577 gnutls_deinit(gsession);
2578 if (xcred)
2579 gnutls_certificate_free_credentials(xcred);
2581 return (0);
2585 start_tls(struct tab *t, int s, gnutls_session_t *gs,
2586 gnutls_certificate_credentials_t *xc)
2588 gnutls_certificate_credentials_t xcred;
2589 gnutls_session_t gsession;
2590 int rv = 1;
2592 if (gs == NULL || xc == NULL)
2593 goto done;
2595 *gs = NULL;
2596 *xc = NULL;
2598 gnutls_certificate_allocate_credentials(&xcred);
2599 gnutls_certificate_set_x509_trust_file(xcred, ssl_ca_file,
2600 GNUTLS_X509_FMT_PEM);
2601 gnutls_init(&gsession, GNUTLS_CLIENT);
2602 gnutls_priority_set_direct(gsession, "PERFORMANCE", NULL);
2603 gnutls_credentials_set(gsession, GNUTLS_CRD_CERTIFICATE, xcred);
2604 gnutls_transport_set_ptr(gsession, (gnutls_transport_ptr_t)(long)s);
2605 if ((rv = gnutls_handshake(gsession)) < 0) {
2606 show_oops(t, "gnutls_handshake failed %d fatal %d %s",
2608 gnutls_error_is_fatal(rv),
2609 gnutls_strerror_name(rv));
2610 stop_tls(gsession, xcred);
2611 goto done;
2614 gnutls_credentials_type_t cred;
2615 cred = gnutls_auth_get_type(gsession);
2616 if (cred != GNUTLS_CRD_CERTIFICATE) {
2617 stop_tls(gsession, xcred);
2618 goto done;
2621 *gs = gsession;
2622 *xc = xcred;
2623 rv = 0;
2624 done:
2625 return (rv);
2629 get_connection_certs(gnutls_session_t gsession, gnutls_x509_crt_t **certs,
2630 size_t *cert_count)
2632 unsigned int len;
2633 const gnutls_datum_t *cl;
2634 gnutls_x509_crt_t *all_certs;
2635 int i, rv = 1;
2637 if (certs == NULL || cert_count == NULL)
2638 goto done;
2639 if (gnutls_certificate_type_get(gsession) != GNUTLS_CRT_X509)
2640 goto done;
2641 cl = gnutls_certificate_get_peers(gsession, &len);
2642 if (len == 0)
2643 goto done;
2645 all_certs = g_malloc(sizeof(gnutls_x509_crt_t) * len);
2646 for (i = 0; i < len; i++) {
2647 gnutls_x509_crt_init(&all_certs[i]);
2648 if (gnutls_x509_crt_import(all_certs[i], &cl[i],
2649 GNUTLS_X509_FMT_PEM < 0)) {
2650 g_free(all_certs);
2651 goto done;
2655 *certs = all_certs;
2656 *cert_count = len;
2657 rv = 0;
2658 done:
2659 return (rv);
2662 void
2663 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
2665 int i;
2667 for (i = 0; i < cert_count; i++)
2668 gnutls_x509_crt_deinit(certs[i]);
2669 g_free(certs);
2672 void
2673 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
2674 size_t cert_count, char *domain)
2676 size_t cert_buf_sz;
2677 char cert_buf[64 * 1024], file[PATH_MAX];
2678 int i;
2679 FILE *f;
2680 GdkColor color;
2682 if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
2683 return;
2685 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2686 if ((f = fopen(file, "w")) == NULL) {
2687 show_oops(t, "Can't create cert file %s %s",
2688 file, strerror(errno));
2689 return;
2692 for (i = 0; i < cert_count; i++) {
2693 cert_buf_sz = sizeof cert_buf;
2694 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2695 cert_buf, &cert_buf_sz)) {
2696 show_oops(t, "gnutls_x509_crt_export failed");
2697 goto done;
2699 if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
2700 show_oops(t, "Can't write certs: %s", strerror(errno));
2701 goto done;
2705 /* not the best spot but oh well */
2706 gdk_color_parse("lightblue", &color);
2707 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
2708 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
2709 gdk_color_parse("black", &color);
2710 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
2711 done:
2712 fclose(f);
2716 load_compare_cert(struct tab *t, struct karg *args)
2718 WebKitWebFrame *frame;
2719 char *uri, domain[8182], file[PATH_MAX];
2720 char cert_buf[64 * 1024], r_cert_buf[64 * 1024];
2721 int s = -1, rv = 1, i;
2722 size_t cert_count;
2723 FILE *f = NULL;
2724 size_t cert_buf_sz;
2725 gnutls_session_t gsession;
2726 gnutls_x509_crt_t *certs;
2727 gnutls_certificate_credentials_t xcred;
2729 if (t == NULL)
2730 return (1);
2732 frame = webkit_web_view_get_main_frame(t->wv);
2733 uri = (char *)webkit_web_frame_get_uri(frame);
2734 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1)
2735 return (1);
2737 /* go ssl/tls */
2738 if (start_tls(t, s, &gsession, &xcred)) {
2739 show_oops(t, "Start TLS failed");
2740 goto done;
2743 /* get certs */
2744 if (get_connection_certs(gsession, &certs, &cert_count)) {
2745 show_oops(t, "Can't get connection certificates");
2746 goto done;
2749 snprintf(file, sizeof file, "%s/%s", certs_dir, domain);
2750 if ((f = fopen(file, "r")) == NULL)
2751 goto freeit;
2753 for (i = 0; i < cert_count; i++) {
2754 cert_buf_sz = sizeof cert_buf;
2755 if (gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
2756 cert_buf, &cert_buf_sz)) {
2757 goto freeit;
2759 if (fread(r_cert_buf, cert_buf_sz, 1, f) != 1) {
2760 rv = -1; /* critical */
2761 goto freeit;
2763 if (bcmp(r_cert_buf, cert_buf, sizeof cert_buf_sz)) {
2764 rv = -1; /* critical */
2765 goto freeit;
2769 rv = 0;
2770 freeit:
2771 if (f)
2772 fclose(f);
2773 free_connection_certs(certs, cert_count);
2774 done:
2775 /* we close the socket first for speed */
2776 if (s != -1)
2777 close(s);
2778 stop_tls(gsession, xcred);
2780 return (rv);
2784 cert_cmd(struct tab *t, struct karg *args)
2786 WebKitWebFrame *frame;
2787 char *uri, *action, domain[8182];
2788 int s = -1;
2789 size_t cert_count;
2790 gnutls_session_t gsession;
2791 gnutls_x509_crt_t *certs;
2792 gnutls_certificate_credentials_t xcred;
2794 if (t == NULL)
2795 return (1);
2797 if ((action = getparams(args->s, "cert")))
2799 else
2800 action = "show";
2802 frame = webkit_web_view_get_main_frame(t->wv);
2803 uri = (char *)webkit_web_frame_get_uri(frame);
2804 if (uri && strlen(uri) == 0) {
2805 show_oops(t, "Invalid URI");
2806 return (1);
2808 if ((s = connect_socket_from_uri(uri, domain, sizeof domain)) == -1) {
2809 show_oops(t, "Invalid certidicate URI: %s", uri);
2810 return (1);
2813 /* go ssl/tls */
2814 if (start_tls(t, s, &gsession, &xcred)) {
2815 show_oops(t, "Start TLS failed");
2816 goto done;
2819 /* get certs */
2820 if (get_connection_certs(gsession, &certs, &cert_count)) {
2821 show_oops(t, "get_connection_certs failed");
2822 goto done;
2825 if (!strcmp(action, "show"))
2826 show_certs(t, certs, cert_count, "Certificate Chain");
2827 else if (!strcmp(action, "save"))
2828 save_certs(t, certs, cert_count, domain);
2829 else
2830 show_oops(t, "Invalid command: %s", action);
2832 free_connection_certs(certs, cert_count);
2833 done:
2834 /* we close the socket first for speed */
2835 if (s != -1)
2836 close(s);
2837 stop_tls(gsession, xcred);
2839 return (0);
2843 remove_cookie(int index)
2845 int i, rv = 1;
2846 GSList *cf;
2847 SoupCookie *c;
2849 DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
2851 cf = soup_cookie_jar_all_cookies(s_cookiejar);
2853 for (i = 1; cf; cf = cf->next, i++) {
2854 if (i != index)
2855 continue;
2856 c = cf->data;
2857 print_cookie("remove cookie", c);
2858 soup_cookie_jar_delete_cookie(s_cookiejar, c);
2859 rv = 0;
2860 break;
2863 soup_cookies_free(cf);
2865 return (rv);
2869 wl_show(struct tab *t, char *args, char *title, struct domain_list *wl)
2871 struct domain *d;
2872 char *tmp, *header, *body, *footer;
2873 int p_js = 0, s_js = 0;
2875 /* we set this to indicate we want to manually do navaction */
2876 t->item = webkit_web_back_forward_list_get_current_item(t->bfl);
2878 if (g_str_has_prefix(args, "show a") ||
2879 !strcmp(args, "show")) {
2880 /* show all */
2881 p_js = 1;
2882 s_js = 1;
2883 } else if (g_str_has_prefix(args, "show p")) {
2884 /* show persistent */
2885 p_js = 1;
2886 } else if (g_str_has_prefix(args, "show s")) {
2887 /* show session */
2888 s_js = 1;
2889 } else
2890 return (1);
2892 header = g_strdup_printf("<title>%s</title><html><body><h1>%s</h1>",
2893 title, title);
2894 footer = g_strdup("</body></html>");
2895 body = g_strdup("");
2897 /* p list */
2898 if (p_js) {
2899 tmp = body;
2900 body = g_strdup_printf("%s<h2>Persistent</h2>", body);
2901 g_free(tmp);
2902 RB_FOREACH(d, domain_list, wl) {
2903 if (d->handy == 0)
2904 continue;
2905 tmp = body;
2906 body = g_strdup_printf("%s%s<br>", body, d->d);
2907 g_free(tmp);
2911 /* s list */
2912 if (s_js) {
2913 tmp = body;
2914 body = g_strdup_printf("%s<h2>Session</h2>", body);
2915 g_free(tmp);
2916 RB_FOREACH(d, domain_list, wl) {
2917 if (d->handy == 1)
2918 continue;
2919 tmp = body;
2920 body = g_strdup_printf("%s%s", body, d->d);
2921 g_free(tmp);
2925 tmp = g_strdup_printf("%s%s%s", header, body, footer);
2926 g_free(header);
2927 g_free(body);
2928 g_free(footer);
2929 load_webkit_string(t, tmp);
2930 g_free(tmp);
2931 return (0);
2935 wl_save(struct tab *t, struct karg *args, int js)
2937 char file[PATH_MAX];
2938 FILE *f;
2939 char *line = NULL, *lt = NULL;
2940 size_t linelen;
2941 WebKitWebFrame *frame;
2942 char *dom = NULL, *uri, *dom_save = NULL;
2943 struct karg a;
2944 struct domain *d;
2945 GSList *cf;
2946 SoupCookie *ci, *c;
2947 int flags;
2949 if (t == NULL || args == NULL)
2950 return (1);
2952 if (runtime_settings[0] == '\0')
2953 return (1);
2955 snprintf(file, sizeof file, "%s/%s", work_dir, runtime_settings);
2956 if ((f = fopen(file, "r+")) == NULL)
2957 return (1);
2959 frame = webkit_web_view_get_main_frame(t->wv);
2960 uri = (char *)webkit_web_frame_get_uri(frame);
2961 dom = find_domain(uri, 1);
2962 if (uri == NULL || dom == NULL) {
2963 show_oops(t, "Can't add domain to %s white list",
2964 js ? "JavaScript" : "cookie");
2965 goto done;
2968 if (g_str_has_prefix(args->s, "save d")) {
2969 /* save domain */
2970 if ((dom_save = get_toplevel_domain(dom)) == NULL) {
2971 show_oops(t, "invalid domain: %s", dom);
2972 goto done;
2974 flags = XT_WL_TOPLEVEL;
2975 } else if (g_str_has_prefix(args->s, "save f") ||
2976 !strcmp(args->s, "save")) {
2977 /* save fqdn */
2978 dom_save = dom;
2979 flags = XT_WL_FQDN;
2980 } else {
2981 show_oops(t, "invalid command: %s", args->s);
2982 goto done;
2985 lt = g_strdup_printf("%s=%s", js ? "js_wl" : "cookie_wl", dom_save);
2987 while (!feof(f)) {
2988 line = fparseln(f, &linelen, NULL, NULL, 0);
2989 if (line == NULL)
2990 continue;
2991 if (!strcmp(line, lt))
2992 goto done;
2993 free(line);
2994 line = NULL;
2997 fprintf(f, "%s\n", lt);
2999 a.i = XT_WL_ENABLE;
3000 a.i |= flags;
3001 if (js) {
3002 d = wl_find(dom_save, &js_wl);
3003 toggle_js(t, &a);
3004 } else {
3005 d = wl_find(dom_save, &c_wl);
3006 toggle_cwl(t, &a);
3008 /* find and add to persistent jar */
3009 cf = soup_cookie_jar_all_cookies(s_cookiejar);
3010 for (;cf; cf = cf->next) {
3011 ci = cf->data;
3012 if (!strcmp(dom_save, ci->domain) ||
3013 !strcmp(&dom_save[1], ci->domain)) /* deal with leading . */ {
3014 c = soup_cookie_copy(ci);
3015 _soup_cookie_jar_add_cookie(p_cookiejar, c);
3018 soup_cookies_free(cf);
3020 if (d)
3021 d->handy = 1;
3023 done:
3024 if (line)
3025 free(line);
3026 if (dom)
3027 g_free(dom);
3028 if (lt)
3029 g_free(lt);
3030 fclose(f);
3032 return (0);
3036 cookie_cmd(struct tab *t, struct karg *args)
3038 char *cmd;
3039 struct karg a;
3041 if ((cmd = getparams(args->s, "cookie")))
3043 else
3044 cmd = "show all";
3047 if (g_str_has_prefix(cmd, "show")) {
3048 wl_show(t, cmd, "Cookie White List", &c_wl);
3049 } else if (g_str_has_prefix(cmd, "save")) {
3050 a.s = cmd;
3051 wl_save(t, &a, 0);
3052 } else if (g_str_has_prefix(cmd, "toggle")) {
3053 a.i = XT_WL_TOGGLE;
3054 if (g_str_has_prefix(cmd, "toggle d"))
3055 a.i |= XT_WL_TOPLEVEL;
3056 else
3057 a.i |= XT_WL_FQDN;
3058 toggle_cwl(t, &a);
3059 } else if (g_str_has_prefix(cmd, "delete")) {
3060 show_oops(t, "'cookie delete' currently unimplemented");
3061 } else
3062 show_oops(t, "unknown cookie command: %s", cmd);
3064 return (0);
3068 js_cmd(struct tab *t, struct karg *args)
3070 char *cmd;
3071 struct karg a;
3073 if ((cmd = getparams(args->s, "js")))
3075 else
3076 cmd = "show all";
3078 if (g_str_has_prefix(cmd, "show")) {
3079 wl_show(t, cmd, "JavaScript White List", &js_wl);
3080 } else if (g_str_has_prefix(cmd, "save")) {
3081 a.s = cmd;
3082 wl_save(t, &a, 1);
3083 } else if (g_str_has_prefix(cmd, "toggle")) {
3084 a.i = XT_WL_TOGGLE;
3085 if (g_str_has_prefix(cmd, "toggle d"))
3086 a.i |= XT_WL_TOPLEVEL;
3087 else
3088 a.i |= XT_WL_FQDN;
3089 toggle_js(t, &a);
3090 } else if (g_str_has_prefix(cmd, "delete")) {
3091 show_oops(t, "'js delete' currently unimplemented");
3092 } else
3093 show_oops(t, "unknown js command: %s", cmd);
3095 return (0);
3099 add_favorite(struct tab *t, struct karg *args)
3101 char file[PATH_MAX];
3102 FILE *f;
3103 char *line = NULL;
3104 size_t urilen, linelen;
3105 WebKitWebFrame *frame;
3106 const gchar *uri, *title;
3108 if (t == NULL)
3109 return (1);
3111 /* don't allow adding of xtp pages to favorites */
3112 if (t->xtp_meaning != XT_XTP_TAB_MEANING_NORMAL) {
3113 show_oops(t, "%s: can't add xtp pages to favorites", __func__);
3114 return (1);
3117 snprintf(file, sizeof file, "%s/%s/%s",
3118 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
3119 if ((f = fopen(file, "r+")) == NULL) {
3120 show_oops(t, "Can't open favorites file: %s", strerror(errno));
3121 return (1);
3124 title = webkit_web_view_get_title(t->wv);
3125 frame = webkit_web_view_get_main_frame(t->wv);
3126 uri = webkit_web_frame_get_uri(frame);
3127 if (title == NULL)
3128 title = uri;
3130 if (title == NULL || uri == NULL) {
3131 show_oops(t, "can't add page to favorites");
3132 goto done;
3135 urilen = strlen(uri);
3137 while (!feof(f)) {
3138 line = fparseln(f, &linelen, NULL, NULL, 0);
3139 if (linelen == urilen && !strcmp(line, uri))
3140 goto done;
3141 free(line);
3142 line = NULL;
3145 fprintf(f, "\n%s\n%s", title, uri);
3146 done:
3147 if (line)
3148 free(line);
3149 fclose(f);
3151 update_favorite_tabs(NULL);
3153 return (0);
3157 navaction(struct tab *t, struct karg *args)
3159 WebKitWebHistoryItem *item;
3161 DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
3162 t->tab_id, args->i);
3164 if (t->item) {
3165 if (args->i == XT_NAV_BACK)
3166 item = webkit_web_back_forward_list_get_current_item(t->bfl);
3167 else
3168 item = webkit_web_back_forward_list_get_forward_item(t->bfl);
3169 if (item == NULL)
3170 return (XT_CB_PASSTHROUGH);;
3171 webkit_web_view_load_uri(t->wv, webkit_web_history_item_get_uri(item));
3172 t->item = NULL;
3173 return (XT_CB_PASSTHROUGH);
3176 switch (args->i) {
3177 case XT_NAV_BACK:
3178 webkit_web_view_go_back(t->wv);
3179 break;
3180 case XT_NAV_FORWARD:
3181 webkit_web_view_go_forward(t->wv);
3182 break;
3183 case XT_NAV_RELOAD:
3184 webkit_web_view_reload(t->wv);
3185 break;
3186 case XT_NAV_RELOAD_CACHE:
3187 webkit_web_view_reload_bypass_cache(t->wv);
3188 break;
3190 return (XT_CB_PASSTHROUGH);
3194 move(struct tab *t, struct karg *args)
3196 GtkAdjustment *adjust;
3197 double pi, si, pos, ps, upper, lower, max;
3199 switch (args->i) {
3200 case XT_MOVE_DOWN:
3201 case XT_MOVE_UP:
3202 case XT_MOVE_BOTTOM:
3203 case XT_MOVE_TOP:
3204 case XT_MOVE_PAGEDOWN:
3205 case XT_MOVE_PAGEUP:
3206 case XT_MOVE_HALFDOWN:
3207 case XT_MOVE_HALFUP:
3208 adjust = t->adjust_v;
3209 break;
3210 default:
3211 adjust = t->adjust_h;
3212 break;
3215 pos = gtk_adjustment_get_value(adjust);
3216 ps = gtk_adjustment_get_page_size(adjust);
3217 upper = gtk_adjustment_get_upper(adjust);
3218 lower = gtk_adjustment_get_lower(adjust);
3219 si = gtk_adjustment_get_step_increment(adjust);
3220 pi = gtk_adjustment_get_page_increment(adjust);
3221 max = upper - ps;
3223 DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
3224 "max %f si %f pi %f\n",
3225 args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
3226 pos, ps, upper, lower, max, si, pi);
3228 switch (args->i) {
3229 case XT_MOVE_DOWN:
3230 case XT_MOVE_RIGHT:
3231 pos += si;
3232 gtk_adjustment_set_value(adjust, MIN(pos, max));
3233 break;
3234 case XT_MOVE_UP:
3235 case XT_MOVE_LEFT:
3236 pos -= si;
3237 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3238 break;
3239 case XT_MOVE_BOTTOM:
3240 case XT_MOVE_FARRIGHT:
3241 gtk_adjustment_set_value(adjust, max);
3242 break;
3243 case XT_MOVE_TOP:
3244 case XT_MOVE_FARLEFT:
3245 gtk_adjustment_set_value(adjust, lower);
3246 break;
3247 case XT_MOVE_PAGEDOWN:
3248 pos += pi;
3249 gtk_adjustment_set_value(adjust, MIN(pos, max));
3250 break;
3251 case XT_MOVE_PAGEUP:
3252 pos -= pi;
3253 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3254 break;
3255 case XT_MOVE_HALFDOWN:
3256 pos += pi / 2;
3257 gtk_adjustment_set_value(adjust, MIN(pos, max));
3258 break;
3259 case XT_MOVE_HALFUP:
3260 pos -= pi / 2;
3261 gtk_adjustment_set_value(adjust, MAX(pos, lower));
3262 break;
3263 default:
3264 return (XT_CB_PASSTHROUGH);
3267 DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
3269 return (XT_CB_HANDLED);
3272 void
3273 url_set_visibility(void)
3275 struct tab *t;
3277 TAILQ_FOREACH(t, &tabs, entry) {
3278 if (show_url == 0) {
3279 gtk_widget_hide(t->toolbar);
3280 focus_webview(t);
3281 } else
3282 gtk_widget_show(t->toolbar);
3286 void
3287 notebook_tab_set_visibility(GtkNotebook *notebook)
3289 if (show_tabs == 0)
3290 gtk_notebook_set_show_tabs(notebook, FALSE);
3291 else
3292 gtk_notebook_set_show_tabs(notebook, TRUE);
3295 void
3296 statusbar_set_visibility(void)
3298 struct tab *t;
3300 TAILQ_FOREACH(t, &tabs, entry) {
3301 if (show_statusbar == 0) {
3302 gtk_widget_hide(t->statusbar);
3303 focus_webview(t);
3304 } else
3305 gtk_widget_show(t->statusbar);
3310 fullscreen(struct tab *t, struct karg *args)
3312 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3314 if (t == NULL)
3315 return (XT_CB_PASSTHROUGH);
3317 if (show_url == 0)
3318 show_url = show_tabs = 1;
3319 else
3320 show_url = show_tabs = 0;
3322 url_set_visibility();
3323 notebook_tab_set_visibility(notebook);
3325 return (XT_CB_HANDLED);
3329 statusaction(struct tab *t, struct karg *args)
3331 int rv = XT_CB_HANDLED;
3333 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3335 if (t == NULL)
3336 return (XT_CB_PASSTHROUGH);
3338 switch (args->i) {
3339 case XT_STATUSBAR_SHOW:
3340 if (show_statusbar == 0) {
3341 show_statusbar = 1;
3342 statusbar_set_visibility();
3344 break;
3345 case XT_STATUSBAR_HIDE:
3346 if (show_statusbar == 1) {
3347 show_statusbar = 0;
3348 statusbar_set_visibility();
3350 break;
3352 return (rv);
3356 urlaction(struct tab *t, struct karg *args)
3358 int rv = XT_CB_HANDLED;
3360 DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
3362 if (t == NULL)
3363 return (XT_CB_PASSTHROUGH);
3365 switch (args->i) {
3366 case XT_URL_SHOW:
3367 if (show_url == 0) {
3368 show_url = 1;
3369 url_set_visibility();
3371 break;
3372 case XT_URL_HIDE:
3373 if (show_url == 1) {
3374 show_url = 0;
3375 url_set_visibility();
3377 break;
3379 return (rv);
3383 tabaction(struct tab *t, struct karg *args)
3385 int rv = XT_CB_HANDLED;
3386 char *url = NULL;
3387 struct undo *u;
3389 DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
3391 if (t == NULL)
3392 return (XT_CB_PASSTHROUGH);
3394 switch (args->i) {
3395 case XT_TAB_NEW:
3396 if ((url = getparams(args->s, "tabnew")))
3397 create_new_tab(url, NULL, 1);
3398 else
3399 create_new_tab(NULL, NULL, 1);
3400 break;
3401 case XT_TAB_DELETE:
3402 delete_tab(t);
3403 break;
3404 case XT_TAB_DELQUIT:
3405 if (gtk_notebook_get_n_pages(notebook) > 1)
3406 delete_tab(t);
3407 else
3408 quit(t, args);
3409 break;
3410 case XT_TAB_OPEN:
3411 if ((url = getparams(args->s, "open")) ||
3412 ((url = getparams(args->s, "op"))) ||
3413 ((url = getparams(args->s, "o"))))
3415 else {
3416 rv = XT_CB_PASSTHROUGH;
3417 goto done;
3419 load_uri(t->wv, url);
3420 break;
3421 case XT_TAB_SHOW:
3422 if (show_tabs == 0) {
3423 show_tabs = 1;
3424 notebook_tab_set_visibility(notebook);
3426 break;
3427 case XT_TAB_HIDE:
3428 if (show_tabs == 1) {
3429 show_tabs = 0;
3430 notebook_tab_set_visibility(notebook);
3432 break;
3433 case XT_TAB_UNDO_CLOSE:
3434 if (undo_count == 0) {
3435 DNPRINTF(XT_D_TAB, "%s: no tabs to undo close", __func__);
3436 goto done;
3437 } else {
3438 undo_count--;
3439 u = TAILQ_FIRST(&undos);
3440 create_new_tab(u->uri, u, 1);
3442 TAILQ_REMOVE(&undos, u, entry);
3443 g_free(u->uri);
3444 /* u->history is freed in create_new_tab() */
3445 g_free(u);
3447 break;
3448 default:
3449 rv = XT_CB_PASSTHROUGH;
3450 goto done;
3453 done:
3454 if (args->s) {
3455 g_free(args->s);
3456 args->s = NULL;
3459 return (rv);
3463 resizetab(struct tab *t, struct karg *args)
3465 if (t == NULL || args == NULL) {
3466 show_oops_s("resizetab invalid parameters");
3467 return (XT_CB_PASSTHROUGH);
3470 DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
3471 t->tab_id, args->i);
3473 adjustfont_webkit(t, args->i);
3475 return (XT_CB_HANDLED);
3479 movetab(struct tab *t, struct karg *args)
3481 struct tab *tt;
3482 int x;
3484 if (t == NULL || args == NULL) {
3485 show_oops_s("movetab invalid parameters");
3486 return (XT_CB_PASSTHROUGH);
3489 DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
3490 t->tab_id, args->i);
3492 if (args->i == XT_TAB_INVALID)
3493 return (XT_CB_PASSTHROUGH);
3495 if (args->i < XT_TAB_INVALID) {
3496 /* next or previous tab */
3497 if (TAILQ_EMPTY(&tabs))
3498 return (XT_CB_PASSTHROUGH);
3500 switch (args->i) {
3501 case XT_TAB_NEXT:
3502 /* if at the last page, loop around to the first */
3503 if (gtk_notebook_get_current_page(notebook) ==
3504 gtk_notebook_get_n_pages(notebook) - 1)
3505 gtk_notebook_set_current_page(notebook, 0);
3506 else
3507 gtk_notebook_next_page(notebook);
3508 break;
3509 case XT_TAB_PREV:
3510 /* if at the first page, loop around to the last */
3511 if (gtk_notebook_current_page(notebook) == 0)
3512 gtk_notebook_set_current_page(notebook,
3513 gtk_notebook_get_n_pages(notebook) - 1);
3514 else
3515 gtk_notebook_prev_page(notebook);
3516 break;
3517 case XT_TAB_FIRST:
3518 gtk_notebook_set_current_page(notebook, 0);
3519 break;
3520 case XT_TAB_LAST:
3521 gtk_notebook_set_current_page(notebook, -1);
3522 break;
3523 default:
3524 return (XT_CB_PASSTHROUGH);
3527 return (XT_CB_HANDLED);
3530 /* jump to tab */
3531 x = args->i - 1;
3532 if (t->tab_id == x) {
3533 DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
3534 return (XT_CB_HANDLED);
3537 TAILQ_FOREACH(tt, &tabs, entry) {
3538 if (tt->tab_id == x) {
3539 gtk_notebook_set_current_page(notebook, x);
3540 DNPRINTF(XT_D_TAB, "movetab: going to %d\n", x);
3541 if (tt->focus_wv)
3542 focus_webview(tt);
3546 return (XT_CB_HANDLED);
3550 command(struct tab *t, struct karg *args)
3552 WebKitWebFrame *frame;
3553 char *s = NULL, *ss = NULL;
3554 GdkColor color;
3555 const gchar *uri;
3557 if (t == NULL || args == NULL) {
3558 show_oops_s("command invalid parameters");
3559 return (XT_CB_PASSTHROUGH);
3562 switch (args->i) {
3563 case '/':
3564 s = "/";
3565 break;
3566 case '?':
3567 s = "?";
3568 break;
3569 case ':':
3570 s = ":";
3571 break;
3572 case XT_CMD_OPEN:
3573 s = ":open ";
3574 break;
3575 case XT_CMD_TABNEW:
3576 s = ":tabnew ";
3577 break;
3578 case XT_CMD_OPEN_CURRENT:
3579 s = ":open ";
3580 /* FALL THROUGH */
3581 case XT_CMD_TABNEW_CURRENT:
3582 if (!s) /* FALL THROUGH? */
3583 s = ":tabnew ";
3584 frame = webkit_web_view_get_main_frame(t->wv);
3585 uri = webkit_web_frame_get_uri(frame);
3586 if (uri && strlen(uri)) {
3587 ss = g_strdup_printf("%s%s", s, uri);
3588 s = ss;
3590 break;
3591 default:
3592 show_oops(t, "command: invalid opcode %d", args->i);
3593 return (XT_CB_PASSTHROUGH);
3596 DNPRINTF(XT_D_CMD, "command: type %s\n", s);
3598 gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
3599 gdk_color_parse("white", &color);
3600 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
3601 show_cmd(t);
3602 gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
3603 gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
3605 if (ss)
3606 g_free(ss);
3608 return (XT_CB_HANDLED);
3612 * Return a new string with a download row (in html)
3613 * appended. Old string is freed.
3615 char *
3616 xtp_page_dl_row(struct tab *t, char *html, struct download *dl)
3619 WebKitDownloadStatus stat;
3620 char *status_html = NULL, *cmd_html = NULL, *new_html;
3621 gdouble progress;
3622 char cur_sz[FMT_SCALED_STRSIZE];
3623 char tot_sz[FMT_SCALED_STRSIZE];
3624 char *xtp_prefix;
3626 DNPRINTF(XT_D_DOWNLOAD, "%s: dl->id %d\n", __func__, dl->id);
3628 /* All actions wil take this form:
3629 * xxxt://class/seskey
3631 xtp_prefix = g_strdup_printf("%s%d/%s/",
3632 XT_XTP_STR, XT_XTP_DL, dl_session_key);
3634 stat = webkit_download_get_status(dl->download);
3636 switch (stat) {
3637 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
3638 status_html = g_strdup_printf("Finished");
3639 cmd_html = g_strdup_printf(
3640 "<a href='%s%d/%d'>Remove</a>",
3641 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3642 break;
3643 case WEBKIT_DOWNLOAD_STATUS_STARTED:
3644 /* gather size info */
3645 progress = 100 * webkit_download_get_progress(dl->download);
3647 fmt_scaled(
3648 webkit_download_get_current_size(dl->download), cur_sz);
3649 fmt_scaled(
3650 webkit_download_get_total_size(dl->download), tot_sz);
3652 status_html = g_strdup_printf(
3653 "<div style='width: 100%%' align='center'>"
3654 "<div class='progress-outer'>"
3655 "<div class='progress-inner' style='width: %.2f%%'>"
3656 "</div></div></div>"
3657 "<div class='dlstatus'>%s of %s (%.2f%%)</div>",
3658 progress, cur_sz, tot_sz, progress);
3660 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3661 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3663 break;
3664 /* LLL */
3665 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
3666 status_html = g_strdup_printf("Cancelled");
3667 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3668 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3669 break;
3670 case WEBKIT_DOWNLOAD_STATUS_ERROR:
3671 status_html = g_strdup_printf("Error!");
3672 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Remove</a>",
3673 xtp_prefix, XT_XTP_DL_REMOVE, dl->id);
3674 break;
3675 case WEBKIT_DOWNLOAD_STATUS_CREATED:
3676 cmd_html = g_strdup_printf("<a href='%s%d/%d'>Cancel</a>",
3677 xtp_prefix, XT_XTP_DL_CANCEL, dl->id);
3678 status_html = g_strdup_printf("Starting");
3679 break;
3680 default:
3681 show_oops(t, "%s: unknown download status", __func__);
3684 new_html = g_strdup_printf(
3685 "%s\n<tr><td>%s</td><td>%s</td>"
3686 "<td style='text-align:center'>%s</td></tr>\n",
3687 html, basename(webkit_download_get_uri(dl->download)),
3688 status_html, cmd_html);
3689 g_free(html);
3691 if (status_html)
3692 g_free(status_html);
3694 if (cmd_html)
3695 g_free(cmd_html);
3697 g_free(xtp_prefix);
3699 return new_html;
3703 * update all download tabs apart from one. Pass NULL if
3704 * you want to update all.
3706 void
3707 update_download_tabs(struct tab *apart_from)
3709 struct tab *t;
3710 if (!updating_dl_tabs) {
3711 updating_dl_tabs = 1; /* stop infinite recursion */
3712 TAILQ_FOREACH(t, &tabs, entry)
3713 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_DL)
3714 && (t != apart_from))
3715 xtp_page_dl(t, NULL);
3716 updating_dl_tabs = 0;
3721 * update all cookie tabs apart from one. Pass NULL if
3722 * you want to update all.
3724 void
3725 update_cookie_tabs(struct tab *apart_from)
3727 struct tab *t;
3728 if (!updating_cl_tabs) {
3729 updating_cl_tabs = 1; /* stop infinite recursion */
3730 TAILQ_FOREACH(t, &tabs, entry)
3731 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_CL)
3732 && (t != apart_from))
3733 xtp_page_cl(t, NULL);
3734 updating_cl_tabs = 0;
3739 * update all history tabs apart from one. Pass NULL if
3740 * you want to update all.
3742 void
3743 update_history_tabs(struct tab *apart_from)
3745 struct tab *t;
3747 if (!updating_hl_tabs) {
3748 updating_hl_tabs = 1; /* stop infinite recursion */
3749 TAILQ_FOREACH(t, &tabs, entry)
3750 if ((t->xtp_meaning == XT_XTP_TAB_MEANING_HL)
3751 && (t != apart_from))
3752 xtp_page_hl(t, NULL);
3753 updating_hl_tabs = 0;
3757 /* cookie management XTP page */
3759 xtp_page_cl(struct tab *t, struct karg *args)
3761 char *header, *body, *footer, *page, *tmp;
3762 int i = 1; /* all ids start 1 */
3763 GSList *sc, *pc, *pc_start;
3764 SoupCookie *c;
3765 char *type;
3767 DNPRINTF(XT_D_CMD, "%s", __func__);
3769 if (t == NULL) {
3770 show_oops_s("%s invalid parameters", __func__);
3771 return (1);
3773 /* mark this tab as cookie jar */
3774 t->xtp_meaning = XT_XTP_TAB_MEANING_CL;
3776 /* Generate a new session key */
3777 if (!updating_cl_tabs)
3778 generate_xtp_session_key(&cl_session_key);
3780 /* header */
3781 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
3782 "\n<head><title>Cookie Jar</title>\n" XT_PAGE_STYLE
3783 "</head><body><h1>Cookie Jar</h1>\n");
3785 /* body */
3786 body = g_strdup_printf("<div align='center'><table><tr>"
3787 "<th>Type</th>"
3788 "<th>Name</th>"
3789 "<th>Value</th>"
3790 "<th>Domain</th>"
3791 "<th>Path</th>"
3792 "<th>Expires</th>"
3793 "<th>Secure</th>"
3794 "<th>HTTP_only</th>"
3795 "<th>Remove</th></tr>\n");
3797 sc = soup_cookie_jar_all_cookies(s_cookiejar);
3798 pc = soup_cookie_jar_all_cookies(p_cookiejar);
3799 pc_start = pc;
3801 for (; sc; sc = sc->next) {
3802 c = sc->data;
3804 type = "Session";
3805 for (pc = pc_start; pc; pc = pc->next)
3806 if (soup_cookie_equal(pc->data, c)) {
3807 type = "Session + Persistent";
3808 break;
3811 tmp = body;
3812 body = g_strdup_printf(
3813 "%s\n<tr>"
3814 "<td style='width: 3%%; text-align: center'>%s</td>"
3815 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3816 "<td style='width: 20%%; word-break: break-all'>%s</td>"
3817 "<td style='width: 10%%; word-break: break-all'>%s</td>"
3818 "<td style='width: 8%%; word-break: break-all'>%s</td>"
3819 "<td style='width: 12%%; word-break: break-all'>%s</td>"
3820 "<td style='width: 3%%; text-align: center'>%d</td>"
3821 "<td style='width: 3%%; text-align: center'>%d</td>"
3822 "<td style='width: 3%%; text-align: center'>"
3823 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3824 body,
3825 type,
3826 c->name,
3827 c->value,
3828 c->domain,
3829 c->path,
3830 c->expires ?
3831 soup_date_to_string(c->expires, SOUP_DATE_HTTP) : "",
3832 c->secure,
3833 c->http_only,
3835 XT_XTP_STR,
3836 XT_XTP_CL,
3837 cl_session_key,
3838 XT_XTP_CL_REMOVE,
3842 g_free(tmp);
3843 i++;
3846 soup_cookies_free(sc);
3847 soup_cookies_free(pc);
3849 /* small message if there are none */
3850 if (i == 1) {
3851 tmp = body;
3852 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3853 "colspan='8'>No Cookies</td></tr>\n", body);
3854 g_free(tmp);
3857 /* footer */
3858 footer = g_strdup_printf("</table></div></body></html>");
3860 page = g_strdup_printf("%s%s%s", header, body, footer);
3862 g_free(header);
3863 g_free(body);
3864 g_free(footer);
3866 load_webkit_string(t, page);
3867 update_cookie_tabs(t);
3869 g_free(page);
3871 return (0);
3875 xtp_page_hl(struct tab *t, struct karg *args)
3877 char *header, *body, *footer, *page, *tmp;
3878 struct history *h;
3879 int i = 1; /* all ids start 1 */
3881 DNPRINTF(XT_D_CMD, "%s", __func__);
3883 if (t == NULL) {
3884 show_oops_s("%s invalid parameters", __func__);
3885 return (1);
3888 /* mark this tab as history manager */
3889 t->xtp_meaning = XT_XTP_TAB_MEANING_HL;
3891 /* Generate a new session key */
3892 if (!updating_hl_tabs)
3893 generate_xtp_session_key(&hl_session_key);
3895 /* header */
3896 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG "\n<head>"
3897 "<title>History</title>\n"
3898 "%s"
3899 "</head>"
3900 "<h1>History</h1>\n",
3901 XT_PAGE_STYLE);
3903 /* body */
3904 body = g_strdup_printf("<div align='center'><table><tr>"
3905 "<th>URI</th><th>Title</th><th style='width: 15%%'>Remove</th></tr>\n");
3907 RB_FOREACH_REVERSE(h, history_list, &hl) {
3908 tmp = body;
3909 body = g_strdup_printf(
3910 "%s\n<tr>"
3911 "<td><a href='%s'>%s</a></td>"
3912 "<td>%s</td>"
3913 "<td style='text-align: center'>"
3914 "<a href='%s%d/%s/%d/%d'>X</a></td></tr>\n",
3915 body, h->uri, h->uri, h->title,
3916 XT_XTP_STR, XT_XTP_HL, hl_session_key,
3917 XT_XTP_HL_REMOVE, i);
3919 g_free(tmp);
3920 i++;
3923 /* small message if there are none */
3924 if (i == 1) {
3925 tmp = body;
3926 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
3927 "colspan='3'>No History</td></tr>\n", body);
3928 g_free(tmp);
3931 /* footer */
3932 footer = g_strdup_printf("</table></div></body></html>");
3934 page = g_strdup_printf("%s%s%s", header, body, footer);
3937 * update all history manager tabs as the xtp session
3938 * key has now changed. No need to update the current tab.
3939 * Already did that above.
3941 update_history_tabs(t);
3943 g_free(header);
3944 g_free(body);
3945 g_free(footer);
3947 load_webkit_string(t, page);
3948 g_free(page);
3950 return (0);
3954 * Generate a web page detailing the status of any downloads
3957 xtp_page_dl(struct tab *t, struct karg *args)
3959 struct download *dl;
3960 char *header, *body, *footer, *page, *tmp;
3961 char *ref;
3962 int n_dl = 1;
3964 DNPRINTF(XT_D_DOWNLOAD, "%s", __func__);
3966 if (t == NULL) {
3967 show_oops_s("%s invalid parameters", __func__);
3968 return (1);
3970 /* mark as a download manager tab */
3971 t->xtp_meaning = XT_XTP_TAB_MEANING_DL;
3974 * Generate a new session key for next page instance.
3975 * This only happens for the top level call to xtp_page_dl()
3976 * in which case updating_dl_tabs is 0.
3978 if (!updating_dl_tabs)
3979 generate_xtp_session_key(&dl_session_key);
3981 /* header - with refresh so as to update */
3982 if (refresh_interval >= 1)
3983 ref = g_strdup_printf(
3984 "<meta http-equiv='refresh' content='%u"
3985 ";url=%s%d/%s/%d' />\n",
3986 refresh_interval,
3987 XT_XTP_STR,
3988 XT_XTP_DL,
3989 dl_session_key,
3990 XT_XTP_DL_LIST);
3991 else
3992 ref = g_strdup("");
3995 header = g_strdup_printf(
3996 "%s\n<head>"
3997 "<title>Downloads</title>\n%s%s</head>\n",
3998 XT_DOCTYPE XT_HTML_TAG,
3999 ref,
4000 XT_PAGE_STYLE);
4002 body = g_strdup_printf("<body><h1>Downloads</h1><div align='center'>"
4003 "<p>\n<a href='%s%d/%s/%d'>\n[ Refresh Downloads ]</a>\n"
4004 "</p><table><tr><th style='width: 60%%'>"
4005 "File</th>\n<th>Progress</th><th>Command</th></tr>\n",
4006 XT_XTP_STR, XT_XTP_DL, dl_session_key, XT_XTP_DL_LIST);
4008 RB_FOREACH_REVERSE(dl, download_list, &downloads) {
4009 body = xtp_page_dl_row(t, body, dl);
4010 n_dl++;
4013 /* message if no downloads in list */
4014 if (n_dl == 1) {
4015 tmp = body;
4016 body = g_strdup_printf("%s\n<tr><td colspan='3'"
4017 " style='text-align: center'>"
4018 "No downloads</td></tr>\n", body);
4019 g_free(tmp);
4022 /* footer */
4023 footer = g_strdup_printf("</table></div></body></html>");
4025 page = g_strdup_printf("%s%s%s", header, body, footer);
4029 * update all download manager tabs as the xtp session
4030 * key has now changed. No need to update the current tab.
4031 * Already did that above.
4033 update_download_tabs(t);
4035 g_free(ref);
4036 g_free(header);
4037 g_free(body);
4038 g_free(footer);
4040 load_webkit_string(t, page);
4041 g_free(page);
4043 return (0);
4047 search(struct tab *t, struct karg *args)
4049 gboolean d;
4051 if (t == NULL || args == NULL) {
4052 show_oops_s("search invalid parameters");
4053 return (1);
4055 if (t->search_text == NULL) {
4056 if (global_search == NULL)
4057 return (XT_CB_PASSTHROUGH);
4058 else {
4059 t->search_text = g_strdup(global_search);
4060 webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
4061 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
4065 DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
4066 t->tab_id, args->i, t->search_forward, t->search_text);
4068 switch (args->i) {
4069 case XT_SEARCH_NEXT:
4070 d = t->search_forward;
4071 break;
4072 case XT_SEARCH_PREV:
4073 d = !t->search_forward;
4074 break;
4075 default:
4076 return (XT_CB_PASSTHROUGH);
4079 webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
4081 return (XT_CB_HANDLED);
4084 struct settings_args {
4085 char **body;
4086 int i;
4089 void
4090 print_setting(struct settings *s, char *val, void *cb_args)
4092 char *tmp, *color;
4093 struct settings_args *sa = cb_args;
4095 if (sa == NULL)
4096 return;
4098 if (s->flags & XT_SF_RUNTIME)
4099 color = "#22cc22";
4100 else
4101 color = "#cccccc";
4103 tmp = *sa->body;
4104 *sa->body = g_strdup_printf(
4105 "%s\n<tr>"
4106 "<td style='background-color: %s; width: 10%%; word-break: break-all'>%s</td>"
4107 "<td style='background-color: %s; width: 20%%; word-break: break-all'>%s</td>",
4108 *sa->body,
4109 color,
4110 s->name,
4111 color,
4114 g_free(tmp);
4115 sa->i++;
4119 set(struct tab *t, struct karg *args)
4121 char *header, *body, *footer, *page, *tmp, *pars;
4122 int i = 1;
4123 struct settings_args sa;
4125 if ((pars = getparams(args->s, "set")) == NULL) {
4126 bzero(&sa, sizeof sa);
4127 sa.body = &body;
4129 /* header */
4130 header = g_strdup_printf(XT_DOCTYPE XT_HTML_TAG
4131 "\n<head><title>Settings</title>\n"
4132 "</head><body><h1>Settings</h1>\n");
4134 /* body */
4135 body = g_strdup_printf("<div align='center'><table><tr>"
4136 "<th align='left'>Setting</th>"
4137 "<th align='left'>Value</th></tr>\n");
4139 settings_walk(print_setting, &sa);
4140 i = sa.i;
4142 /* small message if there are none */
4143 if (i == 1) {
4144 tmp = body;
4145 body = g_strdup_printf("%s\n<tr><td style='text-align:center'"
4146 "colspan='2'>No settings</td></tr>\n", body);
4147 g_free(tmp);
4150 /* footer */
4151 footer = g_strdup_printf("</table></div></body></html>");
4153 page = g_strdup_printf("%s%s%s", header, body, footer);
4155 g_free(header);
4156 g_free(body);
4157 g_free(footer);
4159 load_webkit_string(t, page);
4160 } else
4161 show_oops(t, "Invalid command: %s", pars);
4163 return (XT_CB_PASSTHROUGH);
4167 session_save(struct tab *t, char *filename, char **ret)
4169 struct karg a;
4170 char *f = filename;
4171 int rv = 1;
4173 f += strlen("save");
4174 while (*f == ' ' && *f != '\0')
4175 f++;
4176 if (strlen(f) == 0)
4177 goto done;
4179 *ret = f;
4180 if (f[0] == '.' || f[0] == '/')
4181 goto done;
4183 a.s = f;
4184 if (save_tabs(t, &a))
4185 goto done;
4186 strlcpy(named_session, f, sizeof named_session);
4188 rv = 0;
4189 done:
4190 return (rv);
4194 session_open(struct tab *t, char *filename, char **ret)
4196 struct karg a;
4197 char *f = filename;
4198 int rv = 1;
4200 f += strlen("open");
4201 while (*f == ' ' && *f != '\0')
4202 f++;
4203 if (strlen(f) == 0)
4204 goto done;
4206 *ret = f;
4207 if (f[0] == '.' || f[0] == '/')
4208 goto done;
4210 a.s = f;
4211 a.i = XT_SES_CLOSETABS;
4212 if (open_tabs(t, &a))
4213 goto done;
4215 strlcpy(named_session, f, sizeof named_session);
4217 rv = 0;
4218 done:
4219 return (rv);
4223 session_delete(struct tab *t, char *filename, char **ret)
4225 char file[PATH_MAX];
4226 char *f = filename;
4227 int rv = 1;
4229 f += strlen("delete");
4230 while (*f == ' ' && *f != '\0')
4231 f++;
4232 if (strlen(f) == 0)
4233 goto done;
4235 *ret = f;
4236 if (f[0] == '.' || f[0] == '/')
4237 goto done;
4239 snprintf(file, sizeof file, "%s/%s", sessions_dir, f);
4240 if (unlink(file))
4241 goto done;
4243 if (!strcmp(f, named_session))
4244 strlcpy(named_session, XT_SAVED_TABS_FILE,
4245 sizeof named_session);
4247 rv = 0;
4248 done:
4249 return (rv);
4253 session_cmd(struct tab *t, struct karg *args)
4255 char *action = NULL;
4256 char *filename = NULL;
4258 if (t == NULL)
4259 return (1);
4261 if ((action = getparams(args->s, "session")))
4263 else
4264 action = "show";
4266 if (!strcmp(action, "show"))
4267 show_oops(t, "Current session: %s", named_session[0] == '\0' ?
4268 XT_SAVED_TABS_FILE : named_session);
4269 else if (g_str_has_prefix(action, "save ")) {
4270 if (session_save(t, action, &filename)) {
4271 show_oops(t, "Can't save session: %s",
4272 filename ? filename : "INVALID");
4273 goto done;
4275 } else if (g_str_has_prefix(action, "open ")) {
4276 if (session_open(t, action, &filename)) {
4277 show_oops(t, "Can't open session: %s",
4278 filename ? filename : "INVALID");
4279 goto done;
4281 } else if (g_str_has_prefix(action, "delete ")) {
4282 if (session_delete(t, action, &filename)) {
4283 show_oops(t, "Can't delete session: %s",
4284 filename ? filename : "INVALID");
4285 goto done;
4287 } else
4288 show_oops(t, "Invalid command: %s", action);
4289 done:
4290 return (XT_CB_PASSTHROUGH);
4294 * Make a hardcopy of the page
4297 print_page(struct tab *t, struct karg *args)
4299 WebKitWebFrame *frame;
4301 DNPRINTF(XT_D_PRINTING, "%s:", __func__);
4304 * for now we just call the GTK print box,
4305 * but later we might decide to hook in a command.
4307 frame = webkit_web_view_get_main_frame(t->wv);
4308 webkit_web_frame_print(frame);
4310 return (0);
4314 go_home(struct tab *t, struct karg *args)
4316 load_uri(t->wv, home);
4317 return (0);
4321 restart(struct tab *t, struct karg *args)
4323 struct karg a;
4325 a.s = XT_RESTART_TABS_FILE;
4326 save_tabs(t, &a);
4327 execvp(start_argv[0], start_argv);
4328 /* NOTREACHED */
4330 return (0);
4333 #define CTRL GDK_CONTROL_MASK
4334 #define MOD1 GDK_MOD1_MASK
4335 #define SHFT GDK_SHIFT_MASK
4337 /* inherent to GTK not all keys will be caught at all times */
4338 /* XXX sort key bindings */
4339 struct key_bindings {
4340 guint mask;
4341 guint use_in_entry;
4342 guint key;
4343 int (*func)(struct tab *, struct karg *);
4344 struct karg arg;
4345 } keys[] = {
4346 { MOD1, 0, GDK_j, xtp_page_cl, {0} },
4347 { MOD1, 0, GDK_d, xtp_page_dl, {0} },
4348 { MOD1, 0, GDK_h, xtp_page_hl, {0} },
4349 { CTRL, 0, GDK_p, print_page, {0}},
4350 { 0, 0, GDK_slash, command, {.i = '/'} },
4351 { SHFT, 0, GDK_question, command, {.i = '?'} },
4352 { SHFT, 0, GDK_colon, command, {.i = ':'} },
4353 { CTRL, 0, GDK_q, quit, {0} },
4354 { MOD1, 0, GDK_q, restart, {0} },
4355 { CTRL, 0, GDK_j, toggle_js, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4356 { MOD1, 0, GDK_c, toggle_cwl, {.i = XT_WL_TOGGLE | XT_WL_FQDN} },
4357 { CTRL, 0, GDK_s, toggle_src, {0} },
4358 { 0, 0, GDK_y, yank_uri, {0} },
4359 { 0, 0, GDK_p, paste_uri, {.i = XT_PASTE_CURRENT_TAB} },
4360 { SHFT, 0, GDK_P, paste_uri, {.i = XT_PASTE_NEW_TAB} },
4362 /* search */
4363 { 0, 0, GDK_n, search, {.i = XT_SEARCH_NEXT} },
4364 { SHFT, 0, GDK_N, search, {.i = XT_SEARCH_PREV} },
4366 /* focus */
4367 { 0, 0, GDK_F6, focus, {.i = XT_FOCUS_URI} },
4368 { 0, 0, GDK_F7, focus, {.i = XT_FOCUS_SEARCH} },
4370 /* command aliases (handy when -S flag is used) */
4371 { 0, 0, GDK_F9, command, {.i = XT_CMD_OPEN} },
4372 { 0, 0, GDK_F10, command, {.i = XT_CMD_OPEN_CURRENT} },
4373 { 0, 0, GDK_F11, command, {.i = XT_CMD_TABNEW} },
4374 { 0, 0, GDK_F12, command, {.i = XT_CMD_TABNEW_CURRENT} },
4376 /* hinting */
4377 { 0, 0, GDK_f, hint, {.i = 0} },
4379 /* navigation */
4380 { 0, 0, GDK_BackSpace, navaction, {.i = XT_NAV_BACK} },
4381 { MOD1, 0, GDK_Left, navaction, {.i = XT_NAV_BACK} },
4382 { SHFT, 0, GDK_BackSpace, navaction, {.i = XT_NAV_FORWARD} },
4383 { MOD1, 0, GDK_Right, navaction, {.i = XT_NAV_FORWARD} },
4384 { 0, 0, GDK_F5, navaction, {.i = XT_NAV_RELOAD} },
4385 { CTRL, 0, GDK_r, navaction, {.i = XT_NAV_RELOAD} },
4386 { CTRL|SHFT, 0, GDK_R, navaction, {.i = XT_NAV_RELOAD_CACHE} },
4387 { CTRL, 0, GDK_l, navaction, {.i = XT_NAV_RELOAD} },
4388 { MOD1, 1, GDK_f, xtp_page_fl, {0} },
4390 /* vertical movement */
4391 { 0, 0, GDK_j, move, {.i = XT_MOVE_DOWN} },
4392 { 0, 0, GDK_Down, move, {.i = XT_MOVE_DOWN} },
4393 { 0, 0, GDK_Up, move, {.i = XT_MOVE_UP} },
4394 { 0, 0, GDK_k, move, {.i = XT_MOVE_UP} },
4395 { SHFT, 0, GDK_G, move, {.i = XT_MOVE_BOTTOM} },
4396 { 0, 0, GDK_End, move, {.i = XT_MOVE_BOTTOM} },
4397 { 0, 0, GDK_Home, move, {.i = XT_MOVE_TOP} },
4398 { 0, 0, GDK_g, move, {.i = XT_MOVE_TOP} }, /* XXX make this work */
4399 { 0, 0, GDK_space, move, {.i = XT_MOVE_PAGEDOWN} },
4400 { CTRL, 0, GDK_f, move, {.i = XT_MOVE_PAGEDOWN} },
4401 { CTRL, 0, GDK_d, move, {.i = XT_MOVE_HALFDOWN} },
4402 { 0, 0, GDK_Page_Down, move, {.i = XT_MOVE_PAGEDOWN} },
4403 { 0, 0, GDK_Page_Up, move, {.i = XT_MOVE_PAGEUP} },
4404 { CTRL, 0, GDK_b, move, {.i = XT_MOVE_PAGEUP} },
4405 { CTRL, 0, GDK_u, move, {.i = XT_MOVE_HALFUP} },
4406 /* horizontal movement */
4407 { 0, 0, GDK_l, move, {.i = XT_MOVE_RIGHT} },
4408 { 0, 0, GDK_Right, move, {.i = XT_MOVE_RIGHT} },
4409 { 0, 0, GDK_Left, move, {.i = XT_MOVE_LEFT} },
4410 { 0, 0, GDK_h, move, {.i = XT_MOVE_LEFT} },
4411 { SHFT, 0, GDK_dollar, move, {.i = XT_MOVE_FARRIGHT} },
4412 { 0, 0, GDK_0, move, {.i = XT_MOVE_FARLEFT} },
4414 /* tabs */
4415 { CTRL, 0, GDK_t, tabaction, {.i = XT_TAB_NEW} },
4416 { CTRL, 1, GDK_w, tabaction, {.i = XT_TAB_DELETE} },
4417 { SHFT, 0, GDK_U, tabaction, {.i = XT_TAB_UNDO_CLOSE} },
4418 { CTRL, 0, GDK_1, movetab, {.i = 1} },
4419 { CTRL, 0, GDK_2, movetab, {.i = 2} },
4420 { CTRL, 0, GDK_3, movetab, {.i = 3} },
4421 { CTRL, 0, GDK_4, movetab, {.i = 4} },
4422 { CTRL, 0, GDK_5, movetab, {.i = 5} },
4423 { CTRL, 0, GDK_6, movetab, {.i = 6} },
4424 { CTRL, 0, GDK_7, movetab, {.i = 7} },
4425 { CTRL, 0, GDK_8, movetab, {.i = 8} },
4426 { CTRL, 0, GDK_9, movetab, {.i = 9} },
4427 { CTRL, 0, GDK_0, movetab, {.i = 10} },
4428 { CTRL|SHFT, 0, GDK_less, movetab, {.i = XT_TAB_FIRST} },
4429 { CTRL|SHFT, 0, GDK_greater, movetab, {.i = XT_TAB_LAST} },
4430 { CTRL, 0, GDK_Left, movetab, {.i = XT_TAB_PREV} },
4431 { CTRL, 0, GDK_Right, movetab, {.i = XT_TAB_NEXT} },
4432 { CTRL, 0, GDK_minus, resizetab, {.i = -1} },
4433 { CTRL|SHFT, 0, GDK_plus, resizetab, {.i = 1} },
4434 { CTRL, 0, GDK_equal, resizetab, {.i = 1} },
4437 struct cmd {
4438 char *cmd;
4439 int params;
4440 int (*func)(struct tab *, struct karg *);
4441 struct karg arg;
4442 } cmds[] = {
4443 { "q!", 0, quit, {0} },
4444 { "qa", 0, quit, {0} },
4445 { "qa!", 0, quit, {0} },
4446 { "w", 0, save_tabs, {0} },
4447 { "wq", 0, save_tabs_and_quit, {0} },
4448 { "wq!", 0, save_tabs_and_quit, {0} },
4449 { "help", 0, help, {0} },
4450 { "about", 0, about, {0} },
4451 { "stats", 0, stats, {0} },
4452 { "version", 0, about, {0} },
4453 { "cookies", 0, xtp_page_cl, {0} },
4454 { "fav", 0, xtp_page_fl, {0} },
4455 { "favadd", 0, add_favorite, {0} },
4456 { "js", 2, js_cmd, {0} },
4457 { "cookie", 2, cookie_cmd, {0} },
4458 { "cert", 1, cert_cmd, {0} },
4459 { "ca", 0, ca_cmd, {0} },
4460 { "dl", 0, xtp_page_dl, {0} },
4461 { "h", 0, xtp_page_hl, {0} },
4462 { "hist", 0, xtp_page_hl, {0} },
4463 { "history", 0, xtp_page_hl, {0} },
4464 { "home", 0, go_home, {0} },
4465 { "restart", 0, restart, {0} },
4466 { "urlhide", 0, urlaction, {.i = XT_URL_HIDE} },
4467 { "urlh", 0, urlaction, {.i = XT_URL_HIDE} },
4468 { "urlshow", 0, urlaction, {.i = XT_URL_SHOW} },
4469 { "urls", 0, urlaction, {.i = XT_URL_SHOW} },
4470 { "statushide", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4471 { "statush", 0, statusaction, {.i = XT_STATUSBAR_HIDE} },
4472 { "statusshow", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4473 { "statuss", 0, statusaction, {.i = XT_STATUSBAR_SHOW} },
4475 { "1", 0, move, {.i = XT_MOVE_TOP} },
4476 { "print", 0, print_page, {0} },
4478 /* tabs */
4479 { "o", 1, tabaction, {.i = XT_TAB_OPEN} },
4480 { "op", 1, tabaction, {.i = XT_TAB_OPEN} },
4481 { "open", 1, tabaction, {.i = XT_TAB_OPEN} },
4482 { "tabnew", 1, tabaction, {.i = XT_TAB_NEW} },
4483 { "tabedit", 1, tabaction, {.i = XT_TAB_NEW} },
4484 { "tabe", 1, tabaction, {.i = XT_TAB_NEW} },
4485 { "tabclose", 0, tabaction, {.i = XT_TAB_DELETE} },
4486 { "tabc", 0, tabaction, {.i = XT_TAB_DELETE} },
4487 { "tabshow", 1, tabaction, {.i = XT_TAB_SHOW} },
4488 { "tabs", 1, tabaction, {.i = XT_TAB_SHOW} },
4489 { "tabhide", 1, tabaction, {.i = XT_TAB_HIDE} },
4490 { "tabh", 1, tabaction, {.i = XT_TAB_HIDE} },
4491 { "quit", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4492 { "q", 0, tabaction, {.i = XT_TAB_DELQUIT} },
4493 /* XXX add count to these commands */
4494 { "tabfirst", 0, movetab, {.i = XT_TAB_FIRST} },
4495 { "tabfir", 0, movetab, {.i = XT_TAB_FIRST} },
4496 { "tabrewind", 0, movetab, {.i = XT_TAB_FIRST} },
4497 { "tabr", 0, movetab, {.i = XT_TAB_FIRST} },
4498 { "tablast", 0, movetab, {.i = XT_TAB_LAST} },
4499 { "tabl", 0, movetab, {.i = XT_TAB_LAST} },
4500 { "tabprevious", 0, movetab, {.i = XT_TAB_PREV} },
4501 { "tabp", 0, movetab, {.i = XT_TAB_PREV} },
4502 { "tabnext", 0, movetab, {.i = XT_TAB_NEXT} },
4503 { "tabn", 0, movetab, {.i = XT_TAB_NEXT} },
4505 /* settings */
4506 { "set", 1, set, {0} },
4507 { "fullscreen", 0, fullscreen, {0} },
4508 { "f", 0, fullscreen, {0} },
4510 /* sessions */
4511 { "session", 1, session_cmd, {0} },
4514 gboolean
4515 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4517 hide_oops(t);
4519 return (FALSE);
4522 gboolean
4523 tab_close_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
4525 DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
4527 if (e->type == GDK_BUTTON_PRESS && e->button == 1)
4528 delete_tab(t);
4530 return (FALSE);
4534 * cancel, remove, etc. downloads
4536 void
4537 xtp_handle_dl(struct tab *t, uint8_t cmd, int id)
4539 struct download find, *d;
4541 DNPRINTF(XT_D_DOWNLOAD, "download control: cmd %d, id %d\n", cmd, id);
4543 /* some commands require a valid download id */
4544 if (cmd != XT_XTP_DL_LIST) {
4545 /* lookup download in question */
4546 find.id = id;
4547 d = RB_FIND(download_list, &downloads, &find);
4549 if (d == NULL) {
4550 show_oops(t, "%s: no such download", __func__);
4551 return;
4555 /* decide what to do */
4556 switch (cmd) {
4557 case XT_XTP_DL_CANCEL:
4558 webkit_download_cancel(d->download);
4559 break;
4560 case XT_XTP_DL_REMOVE:
4561 webkit_download_cancel(d->download); /* just incase */
4562 g_object_unref(d->download);
4563 RB_REMOVE(download_list, &downloads, d);
4564 break;
4565 case XT_XTP_DL_LIST:
4566 /* Nothing */
4567 break;
4568 default:
4569 show_oops(t, "%s: unknown command", __func__);
4570 break;
4572 xtp_page_dl(t, NULL);
4576 * Actions on history, only does one thing for now, but
4577 * we provide the function for future actions
4579 void
4580 xtp_handle_hl(struct tab *t, uint8_t cmd, int id)
4582 struct history *h, *next;
4583 int i = 1;
4585 switch (cmd) {
4586 case XT_XTP_HL_REMOVE:
4587 /* walk backwards, as listed in reverse */
4588 for (h = RB_MAX(history_list, &hl); h != NULL; h = next) {
4589 next = RB_PREV(history_list, &hl, h);
4590 if (id == i) {
4591 RB_REMOVE(history_list, &hl, h);
4592 g_free((gpointer) h->title);
4593 g_free((gpointer) h->uri);
4594 g_free(h);
4595 break;
4597 i++;
4599 break;
4600 case XT_XTP_HL_LIST:
4601 /* Nothing - just xtp_page_hl() below */
4602 break;
4603 default:
4604 show_oops(t, "%s: unknown command", __func__);
4605 break;
4608 xtp_page_hl(t, NULL);
4611 /* remove a favorite */
4612 void
4613 remove_favorite(struct tab *t, int index)
4615 char file[PATH_MAX], *title, *uri;
4616 char *new_favs, *tmp;
4617 FILE *f;
4618 int i;
4619 size_t len, lineno;
4621 /* open favorites */
4622 snprintf(file, sizeof file, "%s/%s/%s",
4623 pwd->pw_dir, XT_DIR, XT_FAVS_FILE);
4625 if ((f = fopen(file, "r")) == NULL) {
4626 show_oops(t, "%s: can't open favorites: %s",
4627 __func__, strerror(errno));
4628 return;
4631 /* build a string which will become the new favroites file */
4632 new_favs = g_strdup_printf("%s", "");
4634 for (i = 1;;) {
4635 if ((title = fparseln(f, &len, &lineno, NULL, 0)) == NULL)
4636 if (feof(f) || ferror(f))
4637 break;
4638 /* XXX THIS IS NOT THE RIGHT HEURISTIC */
4639 if (len == 0) {
4640 free(title);
4641 title = NULL;
4642 continue;
4645 if ((uri = fparseln(f, &len, &lineno, NULL, 0)) == NULL) {
4646 if (feof(f) || ferror(f)) {
4647 show_oops(t, "%s: can't parse favorites %s",
4648 __func__, strerror(errno));
4649 goto clean;
4653 /* as long as this isn't the one we are deleting add to file */
4654 if (i != index) {
4655 tmp = new_favs;
4656 new_favs = g_strdup_printf("%s%s\n%s\n",
4657 new_favs, title, uri);
4658 g_free(tmp);
4661 free(uri);
4662 uri = NULL;
4663 free(title);
4664 title = NULL;
4665 i++;
4667 fclose(f);
4669 /* write back new favorites file */
4670 if ((f = fopen(file, "w")) == NULL) {
4671 show_oops(t, "%s: can't open favorites: %s",
4672 __func__, strerror(errno));
4673 goto clean;
4676 fwrite(new_favs, strlen(new_favs), 1, f);
4677 fclose(f);
4679 clean:
4680 if (uri)
4681 free(uri);
4682 if (title)
4683 free(title);
4685 g_free(new_favs);
4688 void
4689 xtp_handle_fl(struct tab *t, uint8_t cmd, int arg)
4691 switch (cmd) {
4692 case XT_XTP_FL_LIST:
4693 /* nothing, just the below call to xtp_page_fl() */
4694 break;
4695 case XT_XTP_FL_REMOVE:
4696 remove_favorite(t, arg);
4697 break;
4698 default:
4699 show_oops(t, "%s: invalid favorites command", __func__);
4700 break;
4703 xtp_page_fl(t, NULL);
4706 void
4707 xtp_handle_cl(struct tab *t, uint8_t cmd, int arg)
4709 switch (cmd) {
4710 case XT_XTP_CL_LIST:
4711 /* nothing, just xtp_page_cl() */
4712 break;
4713 case XT_XTP_CL_REMOVE:
4714 remove_cookie(arg);
4715 break;
4716 default:
4717 show_oops(t, "%s: unknown cookie xtp command", __func__);
4718 break;
4721 xtp_page_cl(t, NULL);
4724 /* link an XTP class to it's session key and handler function */
4725 struct xtp_despatch {
4726 uint8_t xtp_class;
4727 char **session_key;
4728 void (*handle_func)(struct tab *, uint8_t, int);
4731 struct xtp_despatch xtp_despatches[] = {
4732 { XT_XTP_DL, &dl_session_key, xtp_handle_dl },
4733 { XT_XTP_HL, &hl_session_key, xtp_handle_hl },
4734 { XT_XTP_FL, &fl_session_key, xtp_handle_fl },
4735 { XT_XTP_CL, &cl_session_key, xtp_handle_cl },
4736 { NULL, NULL, NULL }
4740 * is the url xtp protocol? (xxxt://)
4741 * if so, parse and despatch correct bahvior
4744 parse_xtp_url(struct tab *t, const char *url)
4746 char *dup = NULL, *p, *last;
4747 uint8_t n_tokens = 0;
4748 char *tokens[4] = {NULL, NULL, NULL, ""};
4749 struct xtp_despatch *dsp, *dsp_match = NULL;
4750 uint8_t req_class;
4753 * tokens array meaning:
4754 * tokens[0] = class
4755 * tokens[1] = session key
4756 * tokens[2] = action
4757 * tokens[3] = optional argument
4760 DNPRINTF(XT_D_URL, "%s: url %s\n", __func__, url);
4762 /*xtp tab meaning is normal unless proven special */
4763 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
4765 if (strncmp(url, XT_XTP_STR, strlen(XT_XTP_STR)))
4766 return 0;
4768 dup = g_strdup(url + strlen(XT_XTP_STR));
4770 /* split out the url */
4771 for ((p = strtok_r(dup, "/", &last)); p;
4772 (p = strtok_r(NULL, "/", &last))) {
4773 if (n_tokens < 4)
4774 tokens[n_tokens++] = p;
4777 /* should be atleast three fields 'class/seskey/command/arg' */
4778 if (n_tokens < 3)
4779 goto clean;
4781 dsp = xtp_despatches;
4782 req_class = atoi(tokens[0]);
4783 while (dsp->xtp_class != NULL) {
4784 if (dsp->xtp_class == req_class) {
4785 dsp_match = dsp;
4786 break;
4788 dsp++;
4791 /* did we find one atall? */
4792 if (dsp_match == NULL) {
4793 show_oops(t, "%s: no matching xtp despatch found", __func__);
4794 goto clean;
4797 /* check session key and call despatch function */
4798 if (validate_xtp_session_key(t, *(dsp_match->session_key), tokens[1])) {
4799 dsp_match->handle_func(t, atoi(tokens[2]), atoi(tokens[3]));
4802 clean:
4803 if (dup)
4804 g_free(dup);
4806 return 1;
4811 void
4812 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
4814 const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
4816 DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
4818 if (t == NULL) {
4819 show_oops_s("activate_uri_entry_cb invalid parameters");
4820 return;
4823 if (uri == NULL) {
4824 show_oops(t, "activate_uri_entry_cb no uri");
4825 return;
4828 uri += strspn(uri, "\t ");
4830 /* if xxxt:// treat specially */
4831 if (!parse_xtp_url(t, uri)) {
4832 load_uri(t->wv, (gchar *)uri);
4833 focus_webview(t);
4837 void
4838 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
4840 const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
4841 char *newuri = NULL;
4842 gchar *enc_search;
4844 DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
4846 if (t == NULL) {
4847 show_oops_s("activate_search_entry_cb invalid parameters");
4848 return;
4851 if (search_string == NULL) {
4852 show_oops(t, "no search_string");
4853 return;
4856 enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
4857 newuri = g_strdup_printf(search_string, enc_search);
4858 g_free(enc_search);
4860 webkit_web_view_load_uri(t->wv, newuri);
4861 focus_webview(t);
4863 if (newuri)
4864 g_free(newuri);
4867 void
4868 check_and_set_js(gchar *uri, struct tab *t)
4870 struct domain *d = NULL;
4871 int es = 0;
4873 if (uri == NULL || t == NULL)
4874 return;
4876 if ((d = wl_find_uri(uri, &js_wl)) == NULL)
4877 es = 0;
4878 else
4879 es = 1;
4881 DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
4882 es ? "enable" : "disable", uri);
4884 g_object_set(G_OBJECT(t->settings),
4885 "enable-scripts", es, (char *)NULL);
4886 webkit_web_view_set_settings(t->wv, t->settings);
4888 button_set_stockid(t->js_toggle,
4889 es ? GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE);
4892 void
4893 show_ca_status(struct tab *t, const char *uri)
4895 WebKitWebFrame *frame;
4896 WebKitWebDataSource *source;
4897 WebKitNetworkRequest *request;
4898 SoupMessage *message;
4899 GdkColor color;
4900 gchar *col_str = "white";
4901 int r;
4903 DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
4904 ssl_strict_certs, ssl_ca_file, uri);
4906 if (uri == NULL)
4907 goto done;
4908 if (ssl_ca_file == NULL) {
4909 if (g_str_has_prefix(uri, "http://"))
4910 goto done;
4911 if (g_str_has_prefix(uri, "https://")) {
4912 col_str = "red";
4913 goto done;
4915 return;
4917 if (g_str_has_prefix(uri, "http://") ||
4918 !g_str_has_prefix(uri, "https://"))
4919 goto done;
4921 frame = webkit_web_view_get_main_frame(t->wv);
4922 source = webkit_web_frame_get_data_source(frame);
4923 request = webkit_web_data_source_get_request(source);
4924 message = webkit_network_request_get_message(request);
4926 if (message && (soup_message_get_flags(message) &
4927 SOUP_MESSAGE_CERTIFICATE_TRUSTED)) {
4928 col_str = "green";
4929 goto done;
4930 } else {
4931 r = load_compare_cert(t, NULL);
4932 if (r == 0)
4933 col_str = "lightblue";
4934 else if (r == 1)
4935 col_str = "yellow";
4936 else
4937 col_str = "red";
4938 goto done;
4940 done:
4941 if (col_str) {
4942 gdk_color_parse(col_str, &color);
4943 gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
4945 if (!strcmp(col_str, "white")) {
4946 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
4947 &color);
4948 gdk_color_parse("black", &color);
4949 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
4950 &color);
4951 } else {
4952 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL,
4953 &color);
4954 gdk_color_parse("black", &color);
4955 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL,
4956 &color);
4961 void
4962 free_favicon(struct tab *t)
4964 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p pix %p\n",
4965 __func__, t->icon_download, t->icon_request, t->icon_pixbuf);
4967 if (t->icon_request)
4968 g_object_unref(t->icon_request);
4969 if (t->icon_pixbuf)
4970 g_object_unref(t->icon_pixbuf);
4971 if (t->icon_dest_uri)
4972 g_free(t->icon_dest_uri);
4974 t->icon_pixbuf = NULL;
4975 t->icon_request = NULL;
4976 t->icon_dest_uri = NULL;
4979 void
4980 abort_favicon_download(struct tab *t)
4982 DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
4984 if (t->icon_download)
4985 webkit_download_cancel(t->icon_download);
4986 else
4987 free_favicon(t);
4989 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
4990 GTK_ENTRY_ICON_PRIMARY, "text-html");
4993 void
4994 set_favicon_from_file(struct tab *t, char *file)
4996 gint width, height;
4997 GdkPixbuf *pixbuf, *scaled;
4998 struct stat sb;
5000 if (t == NULL || file == NULL)
5001 return;
5002 if (t->icon_pixbuf) {
5003 DNPRINTF(XT_D_DOWNLOAD, "%s: icon already set\n", __func__);
5004 return;
5007 if (g_str_has_prefix(file, "file://"))
5008 file += strlen("file://");
5009 DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
5011 if (!stat(file, &sb)) {
5012 if (sb.st_size == 0) {
5013 /* corrupt icon so trash it */
5014 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5015 __func__, file);
5016 unlink(file);
5017 /* no need to set icon to default here */
5018 return;
5022 pixbuf = gdk_pixbuf_new_from_file(file, NULL);
5023 if (pixbuf == NULL) {
5024 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
5025 GTK_ENTRY_ICON_PRIMARY, "text-html");
5026 return;
5029 g_object_get(pixbuf, "width", &width, "height", &height,
5030 (char *)NULL);
5031 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d icon size %dx%d\n",
5032 __func__, t->tab_id, width, height);
5034 if (width > 16 || height > 16) {
5035 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5036 GDK_INTERP_BILINEAR);
5037 g_object_unref(pixbuf);
5038 } else
5039 scaled = pixbuf;
5041 if (scaled == NULL) {
5042 scaled = gdk_pixbuf_scale_simple(pixbuf, 16, 16,
5043 GDK_INTERP_BILINEAR);
5044 return;
5047 t->icon_pixbuf = scaled;
5048 gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
5049 GTK_ENTRY_ICON_PRIMARY, t->icon_pixbuf);
5052 void
5053 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
5054 struct tab *t)
5056 WebKitDownloadStatus status = webkit_download_get_status(download);
5058 if (t == NULL)
5059 return;
5061 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
5062 __func__, t->tab_id, status);
5064 switch (status) {
5065 case WEBKIT_DOWNLOAD_STATUS_ERROR:
5066 /* -1 */
5067 t->icon_download = NULL;
5068 free_favicon(t);
5069 break;
5070 case WEBKIT_DOWNLOAD_STATUS_CREATED:
5071 /* 0 */
5072 break;
5073 case WEBKIT_DOWNLOAD_STATUS_STARTED:
5074 /* 1 */
5075 break;
5076 case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
5077 /* 2 */
5078 DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
5079 __func__, t->tab_id);
5080 t->icon_download = NULL;
5081 free_favicon(t);
5082 break;
5083 case WEBKIT_DOWNLOAD_STATUS_FINISHED:
5084 /* 3 */
5085 DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
5086 __func__, t->icon_dest_uri);
5087 set_favicon_from_file(t, t->icon_dest_uri);
5088 /* these will be freed post callback */
5089 t->icon_request = NULL;
5090 t->icon_download = NULL;
5091 break;
5092 default:
5093 break;
5097 void
5098 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
5100 gchar *name_hash, file[PATH_MAX];
5101 struct stat sb;
5103 DNPRINTF(XT_D_DOWNLOAD, "notify_icon_loaded_cb %s\n", uri);
5105 if (uri == NULL || t == NULL)
5106 return;
5108 if (t->icon_request) {
5109 DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
5110 return;
5113 /* check to see if we got the icon in cache */
5114 name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
5115 snprintf(file, sizeof file, "%s/%s.ico", cache_dir, name_hash);
5116 g_free(name_hash);
5118 if (!stat(file, &sb)) {
5119 if (sb.st_size > 0) {
5120 DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
5121 __func__, file);
5122 set_favicon_from_file(t, file);
5123 return;
5126 /* corrupt icon so trash it */
5127 DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
5128 __func__, file);
5129 unlink(file);
5132 /* create download for icon */
5133 t->icon_request = webkit_network_request_new(uri);
5134 if (t->icon_request == NULL) {
5135 DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
5136 __func__, uri);
5137 return;
5140 t->icon_download = webkit_download_new(t->icon_request);
5142 /* we have to free icon_dest_uri later */
5143 t->icon_dest_uri = g_strdup_printf("file://%s", file);
5144 webkit_download_set_destination_uri(t->icon_download,
5145 t->icon_dest_uri);
5147 g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
5148 G_CALLBACK(favicon_download_status_changed_cb), t);
5150 webkit_download_start(t->icon_download);
5153 void
5154 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
5156 WebKitWebFrame *frame;
5157 const gchar *set = NULL, *uri = NULL, *title = NULL;
5158 struct history *h, find;
5159 int add = 0;
5160 const gchar *s_loading;
5161 struct karg a;
5163 DNPRINTF(XT_D_URL, "notify_load_status_cb: %d\n",
5164 webkit_web_view_get_load_status(wview));
5166 if (t == NULL) {
5167 show_oops_s("notify_load_status_cb invalid paramters");
5168 return;
5171 switch (webkit_web_view_get_load_status(wview)) {
5172 case WEBKIT_LOAD_PROVISIONAL:
5173 /* 0 */
5174 abort_favicon_download(t);
5175 #if GTK_CHECK_VERSION(2, 20, 0)
5176 gtk_widget_show(t->spinner);
5177 gtk_spinner_start(GTK_SPINNER(t->spinner));
5178 #endif
5179 gtk_label_set_text(GTK_LABEL(t->label), "Loading");
5181 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
5183 break;
5185 case WEBKIT_LOAD_COMMITTED:
5186 /* 1 */
5187 frame = webkit_web_view_get_main_frame(wview);
5188 uri = webkit_web_frame_get_uri(frame);
5189 if (uri) {
5190 gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
5192 if (t->status) {
5193 g_free(t->status);
5194 t->status = NULL;
5196 set_status(t, (char *)uri, XT_STATUS_URI);
5199 /* check if js white listing is enabled */
5200 if (enable_js_whitelist) {
5201 frame = webkit_web_view_get_main_frame(wview);
5202 uri = webkit_web_frame_get_uri(frame);
5203 check_and_set_js((gchar *)uri, t);
5206 show_ca_status(t, uri);
5208 /* we know enough to autosave the session */
5209 if (session_autosave) {
5210 a.s = NULL;
5211 save_tabs(t, &a);
5213 break;
5215 case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
5216 /* 3 */
5217 title = webkit_web_view_get_title(wview);
5218 frame = webkit_web_view_get_main_frame(wview);
5219 uri = webkit_web_frame_get_uri(frame);
5220 if (title)
5221 set = title;
5222 else if (uri)
5223 set = uri;
5224 else
5225 break;
5227 gtk_label_set_text(GTK_LABEL(t->label), set);
5228 gtk_window_set_title(GTK_WINDOW(main_window), set);
5230 if (uri) {
5231 if (!strncmp(uri, "http://", strlen("http://")) ||
5232 !strncmp(uri, "https://", strlen("https://")) ||
5233 !strncmp(uri, "file://", strlen("file://")))
5234 add = 1;
5235 if (add == 0)
5236 break;
5237 find.uri = uri;
5238 h = RB_FIND(history_list, &hl, &find);
5239 if (h)
5240 break;
5242 h = g_malloc(sizeof *h);
5243 h->uri = g_strdup(uri);
5244 if (title)
5245 h->title = g_strdup(title);
5246 else
5247 h->title = g_strdup(uri);
5248 RB_INSERT(history_list, &hl, h);
5249 update_history_tabs(NULL);
5252 break;
5254 case WEBKIT_LOAD_FINISHED:
5255 /* 2 */
5256 #if WEBKIT_CHECK_VERSION(1, 1, 18)
5257 case WEBKIT_LOAD_FAILED:
5258 /* 4 */
5259 #endif
5260 #if GTK_CHECK_VERSION(2, 20, 0)
5261 gtk_spinner_stop(GTK_SPINNER(t->spinner));
5262 gtk_widget_hide(t->spinner);
5263 #endif
5264 s_loading = gtk_label_get_text(GTK_LABEL(t->label));
5265 if (s_loading && !strcmp(s_loading, "Loading"))
5266 gtk_label_set_text(GTK_LABEL(t->label), "(untitled)");
5267 default:
5268 gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
5269 break;
5272 if (t->item)
5273 gtk_widget_set_sensitive(GTK_WIDGET(t->backward), TRUE);
5274 else
5275 gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
5276 webkit_web_view_can_go_back(wview));
5278 gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
5279 webkit_web_view_can_go_forward(wview));
5281 /* take focus if we are visible */
5282 t->focus_wv = 1;
5283 focus_webview(t);
5286 void
5287 webview_load_finished_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5289 run_script(t, JS_HINTING);
5292 void
5293 webview_progress_changed_cb(WebKitWebView *wv, int progress, struct tab *t)
5295 gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
5296 progress == 100 ? 0 : (double)progress / 100);
5300 webview_nw_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5301 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5302 WebKitWebPolicyDecision *pd, struct tab *t)
5304 char *uri;
5306 if (t == NULL) {
5307 show_oops_s("webview_nw_cb invalid paramters");
5308 return (FALSE);
5311 DNPRINTF(XT_D_NAV, "webview_nw_cb: %s\n",
5312 webkit_network_request_get_uri(request));
5314 /* open in current tab */
5315 uri = (char *)webkit_network_request_get_uri(request);
5316 webkit_web_view_load_uri(t->wv, uri);
5317 webkit_web_policy_decision_ignore(pd);
5319 return (TRUE); /* we made the decission */
5323 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
5324 WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
5325 WebKitWebPolicyDecision *pd, struct tab *t)
5327 char *uri;
5329 if (t == NULL) {
5330 show_oops_s("webview_npd_cb invalid parameters");
5331 return (FALSE);
5334 DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
5335 t->ctrl_click,
5336 webkit_network_request_get_uri(request));
5338 uri = (char *)webkit_network_request_get_uri(request);
5340 if ((!parse_xtp_url(t, uri) && (t->ctrl_click))) {
5341 t->ctrl_click = 0;
5342 create_new_tab(uri, NULL, ctrl_click_focus);
5343 webkit_web_policy_decision_ignore(pd);
5344 return (TRUE); /* we made the decission */
5347 webkit_web_policy_decision_use(pd);
5348 return (TRUE); /* we made the decission */
5351 WebKitWebView *
5352 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
5354 DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
5355 webkit_web_view_get_uri(wv));
5357 return (wv);
5361 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
5363 /* we can not eat the event without throwing gtk off so defer it */
5365 /* catch middle click */
5366 if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
5367 t->ctrl_click = 1;
5368 goto done;
5371 /* catch ctrl click */
5372 if (e->type == GDK_BUTTON_RELEASE &&
5373 CLEAN(e->state) == GDK_CONTROL_MASK)
5374 t->ctrl_click = 1;
5375 else
5376 t->ctrl_click = 0;
5377 done:
5378 return (XT_CB_PASSTHROUGH);
5382 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
5384 struct mime_type *m;
5386 m = find_mime_type(mime_type);
5387 if (m == NULL)
5388 return (1);
5390 switch (fork()) {
5391 case -1:
5392 show_oops(t, "can't fork mime handler");
5393 /* NOTREACHED */
5394 case 0:
5395 break;
5396 default:
5397 return (0);
5400 /* child */
5401 execlp(m->mt_action, m->mt_action,
5402 webkit_network_request_get_uri(request), (void *)NULL);
5404 _exit(0);
5406 /* NOTREACHED */
5407 return (0);
5411 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
5412 WebKitNetworkRequest *request, char *mime_type,
5413 WebKitWebPolicyDecision *decision, struct tab *t)
5415 if (t == NULL) {
5416 show_oops_s("webview_mimetype_cb invalid parameters");
5417 return (FALSE);
5420 DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
5421 t->tab_id, mime_type);
5423 if (run_mimehandler(t, mime_type, request) == 0) {
5424 webkit_web_policy_decision_ignore(decision);
5425 focus_webview(t);
5426 return (TRUE);
5429 if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
5430 webkit_web_policy_decision_download(decision);
5431 return (TRUE);
5434 return (FALSE);
5438 webview_download_cb(WebKitWebView *wv, WebKitDownload *wk_download,
5439 struct tab *t)
5441 const gchar *filename;
5442 char *uri = NULL;
5443 struct download *download_entry;
5444 int ret = TRUE;
5446 if (wk_download == NULL || t == NULL) {
5447 show_oops_s("%s invalid parameters", __func__);
5448 return (FALSE);
5451 filename = webkit_download_get_suggested_filename(wk_download);
5452 if (filename == NULL)
5453 return (FALSE); /* abort download */
5455 uri = g_strdup_printf("file://%s/%s", download_dir, filename);
5457 DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d filename %s "
5458 "local %s\n", __func__, t->tab_id, filename, uri);
5460 webkit_download_set_destination_uri(wk_download, uri);
5462 if (webkit_download_get_status(wk_download) ==
5463 WEBKIT_DOWNLOAD_STATUS_ERROR) {
5464 show_oops(t, "%s: download failed to start", __func__);
5465 ret = FALSE;
5466 gtk_label_set_text(GTK_LABEL(t->label), "Download Failed");
5467 } else {
5468 download_entry = g_malloc(sizeof(struct download));
5469 download_entry->download = wk_download;
5470 download_entry->tab = t;
5471 download_entry->id = next_download_id++;
5472 RB_INSERT(download_list, &downloads, download_entry);
5473 /* get from history */
5474 g_object_ref(wk_download);
5475 gtk_label_set_text(GTK_LABEL(t->label), "Downloading");
5478 if (uri)
5479 g_free(uri);
5481 /* sync other download manager tabs */
5482 update_download_tabs(NULL);
5485 * NOTE: never redirect/render the current tab before this
5486 * function returns. This will cause the download to never start.
5488 return (ret); /* start download */
5491 void
5492 webview_hover_cb(WebKitWebView *wv, gchar *title, gchar *uri, struct tab *t)
5494 DNPRINTF(XT_D_KEY, "webview_hover_cb: %s %s\n", title, uri);
5496 if (t == NULL) {
5497 show_oops_s("webview_hover_cb");
5498 return;
5501 if (uri)
5502 set_status(t, uri, XT_STATUS_LINK);
5503 else {
5504 if (t->status)
5505 set_status(t, t->status, XT_STATUS_NOTHING);
5510 wv_keypress_after_cb(GtkWidget *w, GdkEventKey *e, struct tab *t)
5512 int i;
5513 char s[2], buf[128];
5514 const char *errstr = NULL;
5515 long long link;
5517 /* don't use w directly; use t->whatever instead */
5519 if (t == NULL) {
5520 show_oops_s("wv_keypress_after_cb");
5521 return (XT_CB_PASSTHROUGH);
5524 DNPRINTF(XT_D_KEY, "wv_keypress_after_cb: keyval 0x%x mask 0x%x t %p\n",
5525 e->keyval, e->state, t);
5527 if (t->hints_on) {
5528 /* ESC */
5529 if (CLEAN(e->state) == 0 && e->keyval == GDK_Escape) {
5530 disable_hints(t);
5531 return (XT_CB_HANDLED);
5534 /* RETURN */
5535 if (CLEAN(e->state) == 0 && e->keyval == GDK_Return) {
5536 link = strtonum(t->hint_num, 1, 1000, &errstr);
5537 if (errstr) {
5538 /* we have a string */
5539 } else {
5540 /* we have a number */
5541 snprintf(buf, sizeof buf, "vimprobable_fire(%s)",
5542 t->hint_num);
5543 run_script(t, buf);
5545 disable_hints(t);
5548 /* BACKSPACE */
5549 /* XXX unfuck this */
5550 if (CLEAN(e->state) == 0 && e->keyval == GDK_BackSpace) {
5551 if (t->hint_mode == XT_HINT_NUMERICAL) {
5552 /* last input was numerical */
5553 int l;
5554 l = strlen(t->hint_num);
5555 if (l > 0) {
5556 l--;
5557 if (l == 0) {
5558 disable_hints(t);
5559 enable_hints(t);
5560 } else {
5561 t->hint_num[l] = '\0';
5562 goto num;
5565 } else if (t->hint_mode == XT_HINT_ALPHANUM) {
5566 /* last input was alphanumerical */
5567 int l;
5568 l = strlen(t->hint_buf);
5569 if (l > 0) {
5570 l--;
5571 if (l == 0) {
5572 disable_hints(t);
5573 enable_hints(t);
5574 } else {
5575 t->hint_buf[l] = '\0';
5576 goto anum;
5579 } else {
5580 /* bogus */
5581 disable_hints(t);
5585 /* numerical input */
5586 if (CLEAN(e->state) == 0 &&
5587 ((e->keyval >= GDK_0 && e->keyval <= GDK_9) || (e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9))) {
5588 snprintf(s, sizeof s, "%c", e->keyval);
5589 strlcat(t->hint_num, s, sizeof t->hint_num);
5590 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: numerical %s\n",
5591 t->hint_num);
5592 num:
5593 link = strtonum(t->hint_num, 1, 1000, &errstr);
5594 if (errstr) {
5595 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: invalid link number\n");
5596 disable_hints(t);
5597 } else {
5598 snprintf(buf, sizeof buf, "vimprobable_update_hints(%s)",
5599 t->hint_num);
5600 t->hint_mode = XT_HINT_NUMERICAL;
5601 run_script(t, buf);
5604 /* empty the counter buffer */
5605 bzero(t->hint_buf, sizeof t->hint_buf);
5606 return (XT_CB_HANDLED);
5609 /* alphanumerical input */
5610 if (
5611 (CLEAN(e->state) == 0 && e->keyval >= GDK_a && e->keyval <= GDK_z) ||
5612 (CLEAN(e->state) == GDK_SHIFT_MASK && e->keyval >= GDK_A && e->keyval <= GDK_Z) ||
5613 (CLEAN(e->state) == 0 && ((e->keyval >= GDK_0 && e->keyval <= GDK_9) ||
5614 ((e->keyval >= GDK_KP_0 && e->keyval <= GDK_KP_9) && (t->hint_mode != XT_HINT_NUMERICAL))))) {
5615 snprintf(s, sizeof s, "%c", e->keyval);
5616 strlcat(t->hint_buf, s, sizeof t->hint_buf);
5617 DNPRINTF(XT_D_JS, "wv_keypress_after_cb: alphanumerical %s\n",
5618 t->hint_buf);
5619 anum:
5620 snprintf(buf, sizeof buf, "vimprobable_cleanup()");
5621 run_script(t, buf);
5623 snprintf(buf, sizeof buf, "vimprobable_show_hints('%s')",
5624 t->hint_buf);
5625 t->hint_mode = XT_HINT_ALPHANUM;
5626 run_script(t, buf);
5628 /* empty the counter buffer */
5629 bzero(t->hint_num, sizeof t->hint_num);
5630 return (XT_CB_HANDLED);
5633 return (XT_CB_HANDLED);
5636 for (i = 0; i < LENGTH(keys); i++)
5637 if (e->keyval == keys[i].key && CLEAN(e->state) ==
5638 keys[i].mask) {
5639 keys[i].func(t, &keys[i].arg);
5640 return (XT_CB_HANDLED);
5644 return (XT_CB_PASSTHROUGH);
5648 wv_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5650 hide_oops(t);
5652 return (XT_CB_PASSTHROUGH);
5656 cmd_keyrelease_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5658 const gchar *c = gtk_entry_get_text(w);
5659 GdkColor color;
5660 int forward = TRUE;
5662 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5663 e->keyval, e->state, t);
5665 if (t == NULL) {
5666 show_oops_s("cmd_keyrelease_cb invalid parameters");
5667 return (XT_CB_PASSTHROUGH);
5670 DNPRINTF(XT_D_CMD, "cmd_keyrelease_cb: keyval 0x%x mask 0x%x t %p\n",
5671 e->keyval, e->state, t);
5673 if (c[0] == ':')
5674 goto done;
5675 if (strlen(c) == 1) {
5676 webkit_web_view_unmark_text_matches(t->wv);
5677 goto done;
5680 if (c[0] == '/')
5681 forward = TRUE;
5682 else if (c[0] == '?')
5683 forward = FALSE;
5684 else
5685 goto done;
5687 /* search */
5688 if (webkit_web_view_search_text(t->wv, &c[1], FALSE, forward, TRUE) ==
5689 FALSE) {
5690 /* not found, mark red */
5691 gdk_color_parse("red", &color);
5692 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5693 /* unmark and remove selection */
5694 webkit_web_view_unmark_text_matches(t->wv);
5695 /* my kingdom for a way to unselect text in webview */
5696 } else {
5697 /* found, highlight all */
5698 webkit_web_view_unmark_text_matches(t->wv);
5699 webkit_web_view_mark_text_matches(t->wv, &c[1], FALSE, 0);
5700 webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
5701 gdk_color_parse("white", &color);
5702 gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL, &color);
5704 done:
5705 return (XT_CB_PASSTHROUGH);
5708 #if 0
5710 cmd_complete(struct tab *t, char *s)
5712 int i;
5713 GtkEntry *w = GTK_ENTRY(t->cmd);
5715 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: complete %s\n", s);
5717 for (i = 0; i < LENGTH(cmds); i++) {
5718 if (!strncasecmp(cmds[i].cmd, s, strlen(s))) {
5719 fprintf(stderr, "match %s %d\n", cmds[i].cmd, strcasecmp(cmds[i].cmd, s));
5720 #if 0
5721 gtk_entry_set_text(w, ":");
5722 gtk_entry_append_text(w, cmds[i].cmd);
5723 gtk_editable_set_position(GTK_EDITABLE(w), -1);
5724 #endif
5728 return (0);
5730 #endif
5733 entry_key_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5735 int i;
5737 if (t == NULL) {
5738 show_oops_s("entry_key_cb invalid parameters");
5739 return (XT_CB_PASSTHROUGH);
5742 DNPRINTF(XT_D_CMD, "entry_key_cb: keyval 0x%x mask 0x%x t %p\n",
5743 e->keyval, e->state, t);
5745 hide_oops(t);
5747 if (e->keyval == GDK_Escape) {
5748 /* don't use focus_webview(t) because we want to type :cmds */
5749 gtk_widget_grab_focus(GTK_WIDGET(t->wv));
5752 for (i = 0; i < LENGTH(keys); i++)
5753 if (e->keyval == keys[i].key &&
5754 CLEAN(e->state) == keys[i].mask &&
5755 keys[i].use_in_entry) {
5756 keys[i].func(t, &keys[i].arg);
5757 return (XT_CB_HANDLED);
5760 return (XT_CB_PASSTHROUGH);
5764 cmd_keypress_cb(GtkEntry *w, GdkEventKey *e, struct tab *t)
5766 int rv = XT_CB_HANDLED;
5767 const gchar *c = gtk_entry_get_text(w);
5769 if (t == NULL) {
5770 show_oops_s("cmd_keypress_cb parameters");
5771 return (XT_CB_PASSTHROUGH);
5774 DNPRINTF(XT_D_CMD, "cmd_keypress_cb: keyval 0x%x mask 0x%x t %p\n",
5775 e->keyval, e->state, t);
5777 /* sanity */
5778 if (c == NULL)
5779 e->keyval = GDK_Escape;
5780 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5781 e->keyval = GDK_Escape;
5783 switch (e->keyval) {
5784 #if 0
5785 case GDK_Tab:
5786 if (c[0] != ':')
5787 goto done;
5789 if (strchr (c, ' ')) {
5790 /* par completion */
5791 fprintf(stderr, "completeme par\n");
5792 goto done;
5795 cmd_complete(t, (char *)&c[1]);
5797 goto done;
5798 #endif
5799 case GDK_BackSpace:
5800 if (!(!strcmp(c, ":") || !strcmp(c, "/") || !strcmp(c, "?")))
5801 break;
5802 /* FALLTHROUGH */
5803 case GDK_Escape:
5804 hide_cmd(t);
5805 focus_webview(t);
5807 /* cancel search */
5808 if (c[0] == '/' || c[0] == '?')
5809 webkit_web_view_unmark_text_matches(t->wv);
5810 goto done;
5813 rv = XT_CB_PASSTHROUGH;
5814 done:
5815 return (rv);
5819 cmd_focusout_cb(GtkWidget *w, GdkEventFocus *e, struct tab *t)
5821 if (t == NULL) {
5822 show_oops_s("cmd_focusout_cb invalid parameters");
5823 return (XT_CB_PASSTHROUGH);
5825 DNPRINTF(XT_D_CMD, "cmd_focusout_cb: tab %d\n", t->tab_id);
5827 hide_cmd(t);
5828 hide_oops(t);
5830 if (show_url == 0 || t->focus_wv)
5831 focus_webview(t);
5832 else
5833 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
5835 return (XT_CB_PASSTHROUGH);
5838 void
5839 cmd_activate_cb(GtkEntry *entry, struct tab *t)
5841 int i;
5842 char *s;
5843 const gchar *c = gtk_entry_get_text(entry);
5845 if (t == NULL) {
5846 show_oops_s("cmd_activate_cb invalid parameters");
5847 return;
5850 DNPRINTF(XT_D_CMD, "cmd_activate_cb: tab %d %s\n", t->tab_id, c);
5852 /* sanity */
5853 if (c == NULL)
5854 goto done;
5855 else if (!(c[0] == ':' || c[0] == '/' || c[0] == '?'))
5856 goto done;
5857 if (strlen(c) < 2)
5858 goto done;
5859 s = (char *)&c[1];
5861 if (c[0] == '/' || c[0] == '?') {
5862 if (t->search_text) {
5863 g_free(t->search_text);
5864 t->search_text = NULL;
5867 t->search_text = g_strdup(s);
5868 if (global_search)
5869 g_free(global_search);
5870 global_search = g_strdup(s);
5871 t->search_forward = c[0] == '/';
5873 goto done;
5876 for (i = 0; i < LENGTH(cmds); i++)
5877 if (cmds[i].params) {
5878 if (!strncmp(s, cmds[i].cmd, strlen(cmds[i].cmd))) {
5879 cmds[i].arg.s = g_strdup(s);
5880 goto execute_command;
5882 } else {
5883 if (!strcmp(s, cmds[i].cmd))
5884 goto execute_command;
5886 show_oops(t, "Invalid command: %s", s);
5887 done:
5888 hide_cmd(t);
5889 return;
5891 execute_command:
5892 hide_cmd(t);
5893 cmds[i].func(t, &cmds[i].arg);
5895 void
5896 backward_cb(GtkWidget *w, struct tab *t)
5898 struct karg a;
5900 if (t == NULL) {
5901 show_oops_s("backward_cb invalid parameters");
5902 return;
5905 DNPRINTF(XT_D_NAV, "backward_cb: tab %d\n", t->tab_id);
5907 a.i = XT_NAV_BACK;
5908 navaction(t, &a);
5911 void
5912 forward_cb(GtkWidget *w, struct tab *t)
5914 struct karg a;
5916 if (t == NULL) {
5917 show_oops_s("forward_cb invalid parameters");
5918 return;
5921 DNPRINTF(XT_D_NAV, "forward_cb: tab %d\n", t->tab_id);
5923 a.i = XT_NAV_FORWARD;
5924 navaction(t, &a);
5927 void
5928 stop_cb(GtkWidget *w, struct tab *t)
5930 WebKitWebFrame *frame;
5932 if (t == NULL) {
5933 show_oops_s("stop_cb invalid parameters");
5934 return;
5937 DNPRINTF(XT_D_NAV, "stop_cb: tab %d\n", t->tab_id);
5939 frame = webkit_web_view_get_main_frame(t->wv);
5940 if (frame == NULL) {
5941 show_oops(t, "stop_cb: no frame");
5942 return;
5945 webkit_web_frame_stop_loading(frame);
5946 abort_favicon_download(t);
5949 void
5950 setup_webkit(struct tab *t)
5952 g_object_set(G_OBJECT(t->settings),
5953 "user-agent", t->user_agent, (char *)NULL);
5954 g_object_set(G_OBJECT(t->settings),
5955 "enable-scripts", enable_scripts, (char *)NULL);
5956 g_object_set(G_OBJECT(t->settings),
5957 "enable-plugins", enable_plugins, (char *)NULL);
5958 adjustfont_webkit(t, XT_FONT_SET);
5960 webkit_web_view_set_settings(t->wv, t->settings);
5963 GtkWidget *
5964 create_browser(struct tab *t)
5966 GtkWidget *w;
5967 gchar *strval;
5969 if (t == NULL) {
5970 show_oops_s("create_browser invalid parameters");
5971 return (NULL);
5974 t->sb_h = GTK_SCROLLBAR(gtk_hscrollbar_new(NULL));
5975 t->sb_v = GTK_SCROLLBAR(gtk_vscrollbar_new(NULL));
5976 t->adjust_h = gtk_range_get_adjustment(GTK_RANGE(t->sb_h));
5977 t->adjust_v = gtk_range_get_adjustment(GTK_RANGE(t->sb_v));
5979 w = gtk_scrolled_window_new(t->adjust_h, t->adjust_v);
5980 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(w),
5981 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
5983 t->wv = WEBKIT_WEB_VIEW(webkit_web_view_new());
5984 gtk_container_add(GTK_CONTAINER(w), GTK_WIDGET(t->wv));
5986 /* set defaults */
5987 t->settings = webkit_web_settings_new();
5989 if (user_agent == NULL) {
5990 g_object_get(G_OBJECT(t->settings), "user-agent", &strval,
5991 (char *)NULL);
5992 t->user_agent = g_strdup_printf("%s %s+", strval, version);
5993 g_free(strval);
5994 } else {
5995 t->user_agent = g_strdup(user_agent);
5998 setup_webkit(t);
6000 return (w);
6003 GtkWidget *
6004 create_window(void)
6006 GtkWidget *w;
6008 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
6009 gtk_window_set_default_size(GTK_WINDOW(w), window_width, window_height);
6010 gtk_widget_set_name(w, "xxxterm");
6011 gtk_window_set_wmclass(GTK_WINDOW(w), "xxxterm", "XXXTerm");
6012 g_signal_connect(G_OBJECT(w), "delete_event",
6013 G_CALLBACK (gtk_main_quit), NULL);
6015 return (w);
6018 GtkWidget *
6019 create_toolbar(struct tab *t)
6021 GtkWidget *toolbar = NULL, *b, *eb1;
6023 b = gtk_hbox_new(FALSE, 0);
6024 toolbar = b;
6025 gtk_container_set_border_width(GTK_CONTAINER(toolbar), 0);
6027 if (fancy_bar) {
6028 /* backward button */
6029 t->backward = create_button("GoBack", GTK_STOCK_GO_BACK, 0);
6030 gtk_widget_set_sensitive(t->backward, FALSE);
6031 g_signal_connect(G_OBJECT(t->backward), "clicked",
6032 G_CALLBACK(backward_cb), t);
6033 gtk_box_pack_start(GTK_BOX(b), t->backward, FALSE, FALSE, 0);
6035 /* forward button */
6036 t->forward = create_button("GoForward",GTK_STOCK_GO_FORWARD, 0);
6037 gtk_widget_set_sensitive(t->forward, FALSE);
6038 g_signal_connect(G_OBJECT(t->forward), "clicked",
6039 G_CALLBACK(forward_cb), t);
6040 gtk_box_pack_start(GTK_BOX(b), t->forward, FALSE,
6041 FALSE, 0);
6043 /* stop button */
6044 t->stop = create_button("Stop", GTK_STOCK_STOP, 0);
6045 gtk_widget_set_sensitive(t->stop, FALSE);
6046 g_signal_connect(G_OBJECT(t->stop), "clicked",
6047 G_CALLBACK(stop_cb), t);
6048 gtk_box_pack_start(GTK_BOX(b), t->stop, FALSE,
6049 FALSE, 0);
6051 /* JS button */
6052 t->js_toggle = create_button("JS-Toggle", enable_scripts ?
6053 GTK_STOCK_MEDIA_PLAY : GTK_STOCK_MEDIA_PAUSE, 0);
6054 gtk_widget_set_sensitive(t->js_toggle, TRUE);
6055 g_signal_connect(G_OBJECT(t->js_toggle), "clicked",
6056 G_CALLBACK(js_toggle_cb), t);
6057 gtk_box_pack_start(GTK_BOX(b), t->js_toggle, FALSE, FALSE, 0);
6060 t->uri_entry = gtk_entry_new();
6061 g_signal_connect(G_OBJECT(t->uri_entry), "activate",
6062 G_CALLBACK(activate_uri_entry_cb), t);
6063 g_signal_connect(G_OBJECT(t->uri_entry), "key-press-event",
6064 (GCallback)entry_key_cb, t);
6065 eb1 = gtk_hbox_new(FALSE, 0);
6066 gtk_container_set_border_width(GTK_CONTAINER(eb1), 1);
6067 gtk_box_pack_start(GTK_BOX(eb1), t->uri_entry, TRUE, TRUE, 0);
6068 gtk_box_pack_start(GTK_BOX(b), eb1, TRUE, TRUE, 0);
6070 /* set empty favicon */
6071 gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
6072 GTK_ENTRY_ICON_PRIMARY, "text-html");
6074 /* search entry */
6075 if (fancy_bar && search_string) {
6076 GtkWidget *eb2;
6077 t->search_entry = gtk_entry_new();
6078 gtk_entry_set_width_chars(GTK_ENTRY(t->search_entry), 30);
6079 g_signal_connect(G_OBJECT(t->search_entry), "activate",
6080 G_CALLBACK(activate_search_entry_cb), t);
6081 g_signal_connect(G_OBJECT(t->search_entry), "key-press-event",
6082 (GCallback)entry_key_cb, t);
6083 gtk_widget_set_size_request(t->search_entry, -1, -1);
6084 eb2 = gtk_hbox_new(FALSE, 0);
6085 gtk_container_set_border_width(GTK_CONTAINER(eb2), 1);
6086 gtk_box_pack_start(GTK_BOX(eb2), t->search_entry, TRUE, TRUE,
6088 gtk_box_pack_start(GTK_BOX(b), eb2, FALSE, FALSE, 0);
6090 return (toolbar);
6093 void
6094 recalc_tabs(void)
6096 struct tab *t;
6098 TAILQ_FOREACH(t, &tabs, entry)
6099 t->tab_id = gtk_notebook_page_num(notebook, t->vbox);
6103 undo_close_tab_save(struct tab *t)
6105 int m, n;
6106 const gchar *uri;
6107 struct undo *u1, *u2;
6108 WebKitWebFrame *frame;
6109 GList *items;
6110 WebKitWebHistoryItem *item;
6112 frame = webkit_web_view_get_main_frame(t->wv);
6113 uri = webkit_web_frame_get_uri(frame);
6115 if (uri && !strlen(uri))
6116 return (1);
6118 u1 = g_malloc0(sizeof(struct undo));
6119 u1->uri = g_strdup(uri);
6121 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6123 m = webkit_web_back_forward_list_get_forward_length(t->bfl);
6124 n = webkit_web_back_forward_list_get_back_length(t->bfl);
6125 u1->back = n;
6127 /* forward history */
6128 items = webkit_web_back_forward_list_get_forward_list_with_limit(t->bfl, m);
6130 while (items) {
6131 item = items->data;
6132 u1->history = g_list_prepend(u1->history,
6133 webkit_web_history_item_copy(item));
6134 items = g_list_next(items);
6137 /* current item */
6138 if (m) {
6139 item = webkit_web_back_forward_list_get_current_item(t->bfl);
6140 u1->history = g_list_prepend(u1->history,
6141 webkit_web_history_item_copy(item));
6144 /* back history */
6145 items = webkit_web_back_forward_list_get_back_list_with_limit(t->bfl, n);
6147 while (items) {
6148 item = items->data;
6149 u1->history = g_list_prepend(u1->history,
6150 webkit_web_history_item_copy(item));
6151 items = g_list_next(items);
6154 TAILQ_INSERT_HEAD(&undos, u1, entry);
6156 if (undo_count > XT_MAX_UNDO_CLOSE_TAB) {
6157 u2 = TAILQ_LAST(&undos, undo_tailq);
6158 TAILQ_REMOVE(&undos, u2, entry);
6159 g_free(u2->uri);
6160 g_list_free(u2->history);
6161 g_free(u2);
6162 } else
6163 undo_count++;
6165 return (0);
6168 void
6169 delete_tab(struct tab *t)
6171 struct karg a;
6173 DNPRINTF(XT_D_TAB, "delete_tab: %p\n", t);
6175 if (t == NULL)
6176 return;
6178 TAILQ_REMOVE(&tabs, t, entry);
6180 /* halt all webkit activity */
6181 abort_favicon_download(t);
6182 webkit_web_view_stop_loading(t->wv);
6183 undo_close_tab_save(t);
6185 gtk_widget_destroy(t->vbox);
6186 g_free(t->user_agent);
6187 g_free(t);
6189 recalc_tabs();
6190 if (TAILQ_EMPTY(&tabs))
6191 create_new_tab(NULL, NULL, 1);
6194 /* recreate session */
6195 if (session_autosave) {
6196 a.s = NULL;
6197 save_tabs(t, &a);
6201 void
6202 adjustfont_webkit(struct tab *t, int adjust)
6204 if (t == NULL) {
6205 show_oops_s("adjustfont_webkit invalid parameters");
6206 return;
6209 if (adjust == XT_FONT_SET)
6210 t->font_size = default_font_size;
6212 t->font_size += adjust;
6213 g_object_set(G_OBJECT(t->settings), "default-font-size",
6214 t->font_size, (char *)NULL);
6215 g_object_get(G_OBJECT(t->settings), "default-font-size",
6216 &t->font_size, (char *)NULL);
6219 void
6220 append_tab(struct tab *t)
6222 if (t == NULL)
6223 return;
6225 TAILQ_INSERT_TAIL(&tabs, t, entry);
6226 t->tab_id = gtk_notebook_append_page(notebook, t->vbox, t->tab_content);
6229 void
6230 create_new_tab(char *title, struct undo *u, int focus)
6232 struct tab *t, *tt;
6233 int load = 1, id, notfound;
6234 GtkWidget *b, *bb;
6235 WebKitWebHistoryItem *item;
6236 GList *items;
6237 GdkColor color;
6238 PangoFontDescription *fd = NULL;
6240 DNPRINTF(XT_D_TAB, "create_new_tab: title %s focus %d\n", title, focus);
6242 if (tabless && !TAILQ_EMPTY(&tabs)) {
6243 DNPRINTF(XT_D_TAB, "create_new_tab: new tab rejected\n");
6244 return;
6247 t = g_malloc0(sizeof *t);
6249 if (title == NULL) {
6250 title = "(untitled)";
6251 load = 0;
6254 t->vbox = gtk_vbox_new(FALSE, 0);
6256 /* label + button for tab */
6257 b = gtk_hbox_new(FALSE, 0);
6258 t->tab_content = b;
6260 #if GTK_CHECK_VERSION(2, 20, 0)
6261 t->spinner = gtk_spinner_new ();
6262 #endif
6263 t->label = gtk_label_new(title);
6264 bb = create_button("Close", GTK_STOCK_CLOSE, 1);
6265 gtk_widget_set_size_request(t->label, 100, 0);
6266 gtk_widget_set_size_request(b, 130, 0);
6268 gtk_box_pack_start(GTK_BOX(b), bb, FALSE, FALSE, 0);
6269 gtk_box_pack_start(GTK_BOX(b), t->label, FALSE, FALSE, 0);
6270 #if GTK_CHECK_VERSION(2, 20, 0)
6271 gtk_box_pack_start(GTK_BOX(b), t->spinner, FALSE, FALSE, 0);
6272 #endif
6274 /* toolbar */
6275 t->toolbar = create_toolbar(t);
6276 gtk_box_pack_start(GTK_BOX(t->vbox), t->toolbar, FALSE, FALSE, 0);
6278 /* browser */
6279 t->browser_win = create_browser(t);
6280 gtk_box_pack_start(GTK_BOX(t->vbox), t->browser_win, TRUE, TRUE, 0);
6282 /* oops message for user feedback */
6283 t->oops = gtk_entry_new();
6284 gtk_entry_set_inner_border(GTK_ENTRY(t->oops), NULL);
6285 gtk_entry_set_has_frame(GTK_ENTRY(t->oops), FALSE);
6286 gtk_widget_set_can_focus(GTK_WIDGET(t->oops), FALSE);
6287 gdk_color_parse("red", &color);
6288 gtk_widget_modify_base(t->oops, GTK_STATE_NORMAL, &color);
6289 gtk_box_pack_end(GTK_BOX(t->vbox), t->oops, FALSE, FALSE, 0);
6291 /* command entry */
6292 t->cmd = gtk_entry_new();
6293 gtk_entry_set_inner_border(GTK_ENTRY(t->cmd), NULL);
6294 gtk_entry_set_has_frame(GTK_ENTRY(t->cmd), FALSE);
6295 gtk_box_pack_end(GTK_BOX(t->vbox), t->cmd, FALSE, FALSE, 0);
6297 /* status bar */
6298 t->statusbar = gtk_entry_new();
6299 gtk_entry_set_inner_border(GTK_ENTRY(t->statusbar), NULL);
6300 gtk_entry_set_has_frame(GTK_ENTRY(t->statusbar), FALSE);
6301 gtk_widget_set_can_focus(GTK_WIDGET(t->statusbar), FALSE);
6302 gdk_color_parse("black", &color);
6303 gtk_widget_modify_base(t->statusbar, GTK_STATE_NORMAL, &color);
6304 gdk_color_parse("white", &color);
6305 gtk_widget_modify_text(t->statusbar, GTK_STATE_NORMAL, &color);
6306 fd = GTK_WIDGET(t->statusbar)->style->font_desc;
6307 pango_font_description_set_weight(fd, PANGO_WEIGHT_SEMIBOLD);
6308 pango_font_description_set_absolute_size(fd, 10 * PANGO_SCALE); /* 10 px font */
6309 gtk_widget_modify_font(t->statusbar, fd);
6310 gtk_box_pack_end(GTK_BOX(t->vbox), t->statusbar, FALSE, FALSE, 0);
6312 /* xtp meaning is normal by default */
6313 t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
6315 /* and show it all */
6316 gtk_widget_show_all(b);
6317 gtk_widget_show_all(t->vbox);
6319 if (append_next == 0 || gtk_notebook_get_n_pages(notebook) == 0)
6320 append_tab(t);
6321 else {
6322 notfound = 1;
6323 id = gtk_notebook_get_current_page(notebook);
6324 TAILQ_FOREACH(tt, &tabs, entry) {
6325 if (tt->tab_id == id) {
6326 notfound = 0;
6327 TAILQ_INSERT_AFTER(&tabs, tt, t, entry);
6328 gtk_notebook_insert_page(notebook, t->vbox, b,
6329 id + 1);
6330 recalc_tabs();
6331 break;
6334 if (notfound)
6335 append_tab(t);
6338 #if GTK_CHECK_VERSION(2, 20, 0)
6339 /* turn spinner off if we are a new tab without uri */
6340 if (!load) {
6341 gtk_spinner_stop(GTK_SPINNER(t->spinner));
6342 gtk_widget_hide(t->spinner);
6344 #endif
6345 /* make notebook tabs reorderable */
6346 gtk_notebook_set_tab_reorderable(notebook, t->vbox, TRUE);
6348 g_object_connect(G_OBJECT(t->cmd),
6349 "signal::key-press-event", (GCallback)cmd_keypress_cb, t,
6350 "signal::key-release-event", (GCallback)cmd_keyrelease_cb, t,
6351 "signal::focus-out-event", (GCallback)cmd_focusout_cb, t,
6352 "signal::activate", (GCallback)cmd_activate_cb, t,
6353 (char *)NULL);
6355 /* reuse wv_button_cb to hide oops */
6356 g_object_connect(G_OBJECT(t->oops),
6357 "signal::button_press_event", (GCallback)wv_button_cb, t,
6358 (char *)NULL);
6360 g_object_connect(G_OBJECT(t->wv),
6361 "signal::key-press-event", (GCallback)wv_keypress_cb, t,
6362 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
6363 "signal::hovering-over-link", (GCallback)webview_hover_cb, t,
6364 "signal::download-requested", (GCallback)webview_download_cb, t,
6365 "signal::mime-type-policy-decision-requested", (GCallback)webview_mimetype_cb, t,
6366 "signal::navigation-policy-decision-requested", (GCallback)webview_npd_cb, t,
6367 "signal::new-window-policy-decision-requested", (GCallback)webview_nw_cb, t,
6368 "signal::create-web-view", (GCallback)webview_cwv_cb, t,
6369 "signal::event", (GCallback)webview_event_cb, t,
6370 "signal::load-finished", (GCallback)webview_load_finished_cb, t,
6371 "signal::load-progress-changed", (GCallback)webview_progress_changed_cb, t,
6372 #if WEBKIT_CHECK_VERSION(1, 1, 18)
6373 "signal::icon-loaded", (GCallback)notify_icon_loaded_cb, t,
6374 #endif
6375 "signal::button_press_event", (GCallback)wv_button_cb, t,
6376 (char *)NULL);
6377 g_signal_connect(t->wv,
6378 "notify::load-status", G_CALLBACK(notify_load_status_cb), t);
6380 /* hijack the unused keys as if we were the browser */
6381 g_object_connect(G_OBJECT(t->toolbar),
6382 "signal-after::key-press-event", (GCallback)wv_keypress_after_cb, t,
6383 (char *)NULL);
6385 g_signal_connect(G_OBJECT(bb), "button_press_event",
6386 G_CALLBACK(tab_close_cb), t);
6388 /* hide stuff */
6389 hide_cmd(t);
6390 hide_oops(t);
6391 url_set_visibility();
6392 statusbar_set_visibility();
6394 if (focus) {
6395 gtk_notebook_set_current_page(notebook, t->tab_id);
6396 DNPRINTF(XT_D_TAB, "create_new_tab: going to tab: %d\n",
6397 t->tab_id);
6400 if (load)
6401 load_uri(t->wv, title);
6402 else {
6403 if (show_url == 1)
6404 gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
6405 else
6406 focus_webview(t);
6409 t->bfl = webkit_web_view_get_back_forward_list(t->wv);
6410 /* restore the tab's history */
6411 if (u && u->history) {
6412 items = u->history;
6413 while (items) {
6414 item = items->data;
6415 webkit_web_back_forward_list_add_item(t->bfl, item);
6416 items = g_list_next(items);
6419 item = g_list_nth_data(u->history, u->back);
6420 if (item)
6421 webkit_web_view_go_to_back_forward_item(t->wv, item);
6423 g_list_free(items);
6424 g_list_free(u->history);
6425 } else
6426 webkit_web_back_forward_list_clear(t->bfl);
6429 void
6430 notebook_switchpage_cb(GtkNotebook *nb, GtkNotebookPage *nbp, guint pn,
6431 gpointer *udata)
6433 struct tab *t;
6434 const gchar *uri;
6436 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: tab: %d\n", pn);
6438 TAILQ_FOREACH(t, &tabs, entry) {
6439 if (t->tab_id == pn) {
6440 DNPRINTF(XT_D_TAB, "notebook_switchpage_cb: going to "
6441 "%d\n", pn);
6443 uri = webkit_web_view_get_title(t->wv);
6444 if (uri == NULL)
6445 uri = XT_NAME;
6446 gtk_window_set_title(GTK_WINDOW(main_window), uri);
6448 hide_cmd(t);
6449 hide_oops(t);
6451 if (t->focus_wv)
6452 focus_webview(t);
6457 void
6458 menuitem_response(struct tab *t)
6460 gtk_notebook_set_current_page(notebook, t->tab_id);
6463 gboolean
6464 arrow_cb(GtkWidget *w, GdkEventButton *event, gpointer user_data)
6466 GtkWidget *menu, *menu_items;
6467 GdkEventButton *bevent;
6468 WebKitWebFrame *frame;
6469 const gchar *uri;
6470 struct tab *ti;
6472 if (event->type == GDK_BUTTON_PRESS) {
6473 bevent = (GdkEventButton *) event;
6474 menu = gtk_menu_new();
6476 TAILQ_FOREACH(ti, &tabs, entry) {
6477 frame = webkit_web_view_get_main_frame(ti->wv);
6478 uri = webkit_web_frame_get_uri(frame);
6479 /* XXX make sure there is something to print */
6480 /* XXX add gui pages in here to look purdy */
6481 if (uri == NULL)
6482 uri = "(untitled)";
6483 if (strlen(uri) == 0)
6484 uri = "(untitled)";
6485 menu_items = gtk_menu_item_new_with_label(uri);
6486 gtk_menu_append(GTK_MENU (menu), menu_items);
6487 gtk_widget_show(menu_items);
6489 gtk_signal_connect_object(GTK_OBJECT(menu_items),
6490 "activate", GTK_SIGNAL_FUNC(menuitem_response),
6491 (gpointer)ti);
6494 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL,
6495 bevent->button, bevent->time);
6497 /* unref object so it'll free itself when popped down */
6498 g_object_ref_sink(menu);
6499 g_object_unref(menu);
6501 return (TRUE /* eat event */);
6504 return (FALSE /* propagate */);
6508 icon_size_map(int icon_size)
6510 if (icon_size <= GTK_ICON_SIZE_INVALID ||
6511 icon_size > GTK_ICON_SIZE_DIALOG)
6512 return (GTK_ICON_SIZE_SMALL_TOOLBAR);
6514 return (icon_size);
6517 GtkWidget *
6518 create_button(char *name, char *stockid, int size)
6520 GtkWidget *button, *image;
6521 gchar *rcstring;
6522 int gtk_icon_size;
6523 rcstring = g_strdup_printf(
6524 "style \"%s-style\"\n"
6525 "{\n"
6526 " GtkWidget::focus-padding = 0\n"
6527 " GtkWidget::focus-line-width = 0\n"
6528 " xthickness = 0\n"
6529 " ythickness = 0\n"
6530 "}\n"
6531 "widget \"*.%s\" style \"%s-style\"",name,name,name);
6532 gtk_rc_parse_string(rcstring);
6533 g_free(rcstring);
6534 button = gtk_button_new();
6535 gtk_button_set_focus_on_click(GTK_BUTTON(button), FALSE);
6536 gtk_icon_size = icon_size_map(size?size:icon_size);
6538 image = gtk_image_new_from_stock(stockid, gtk_icon_size);
6539 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6540 gtk_container_set_border_width(GTK_CONTAINER(button), 1);
6541 gtk_container_add(GTK_CONTAINER(button), GTK_WIDGET(image));
6542 gtk_widget_set_name(button, name);
6543 gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
6544 gtk_widget_set_tooltip_text(button, name);
6546 return button;
6549 void
6550 button_set_stockid(GtkWidget *button, char *stockid)
6552 GtkWidget *image;
6553 image = gtk_image_new_from_stock(stockid, icon_size_map(icon_size));
6554 gtk_widget_set_size_request(GTK_WIDGET(image), -1, -1);
6555 gtk_button_set_image(GTK_BUTTON(button), image);
6558 void
6559 create_canvas(void)
6561 GtkWidget *vbox;
6562 GList *l = NULL;
6563 GdkPixbuf *pb;
6564 char file[PATH_MAX];
6565 int i;
6567 vbox = gtk_vbox_new(FALSE, 0);
6568 gtk_box_set_spacing(GTK_BOX(vbox), 0);
6569 notebook = GTK_NOTEBOOK(gtk_notebook_new());
6570 gtk_notebook_set_tab_hborder(notebook, 0);
6571 gtk_notebook_set_tab_vborder(notebook, 0);
6572 gtk_notebook_set_scrollable(notebook, TRUE);
6573 notebook_tab_set_visibility(notebook);
6574 gtk_notebook_set_show_border(notebook, FALSE);
6575 gtk_widget_set_can_focus(GTK_WIDGET(notebook), FALSE);
6577 abtn = gtk_button_new();
6578 arrow = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_NONE);
6579 gtk_widget_set_size_request(arrow, -1, -1);
6580 gtk_container_add(GTK_CONTAINER(abtn), arrow);
6581 gtk_widget_set_size_request(abtn, -1, 20);
6582 gtk_notebook_set_action_widget(notebook, abtn, GTK_PACK_END);
6584 gtk_widget_set_size_request(GTK_WIDGET(notebook), -1, -1);
6585 gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);
6586 gtk_widget_set_size_request(vbox, -1, -1);
6588 g_object_connect(G_OBJECT(notebook),
6589 "signal::switch-page", (GCallback)notebook_switchpage_cb, NULL,
6590 (char *)NULL);
6591 g_signal_connect(G_OBJECT(abtn), "button_press_event",
6592 G_CALLBACK(arrow_cb), NULL);
6594 main_window = create_window();
6595 gtk_container_add(GTK_CONTAINER(main_window), vbox);
6596 gtk_window_set_title(GTK_WINDOW(main_window), XT_NAME);
6598 /* icons */
6599 for (i = 0; i < LENGTH(icons); i++) {
6600 snprintf(file, sizeof file, "%s/%s", resource_dir, icons[i]);
6601 pb = gdk_pixbuf_new_from_file(file, NULL);
6602 l = g_list_append(l, pb);
6604 gtk_window_set_default_icon_list(l);
6606 gtk_widget_show_all(abtn);
6607 gtk_widget_show_all(main_window);
6610 void
6611 set_hook(void **hook, char *name)
6613 if (hook == NULL)
6614 errx(1, "set_hook");
6616 if (*hook == NULL) {
6617 *hook = dlsym(RTLD_NEXT, name);
6618 if (*hook == NULL)
6619 errx(1, "can't hook %s", name);
6623 /* override libsoup soup_cookie_equal because it doesn't look at domain */
6624 gboolean
6625 soup_cookie_equal(SoupCookie *cookie1, SoupCookie *cookie2)
6627 g_return_val_if_fail(cookie1, FALSE);
6628 g_return_val_if_fail(cookie2, FALSE);
6630 return (!strcmp (cookie1->name, cookie2->name) &&
6631 !strcmp (cookie1->value, cookie2->value) &&
6632 !strcmp (cookie1->path, cookie2->path) &&
6633 !strcmp (cookie1->domain, cookie2->domain));
6636 void
6637 transfer_cookies(void)
6639 GSList *cf;
6640 SoupCookie *sc, *pc;
6642 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6644 for (;cf; cf = cf->next) {
6645 pc = cf->data;
6646 sc = soup_cookie_copy(pc);
6647 _soup_cookie_jar_add_cookie(s_cookiejar, sc);
6650 soup_cookies_free(cf);
6653 void
6654 soup_cookie_jar_delete_cookie(SoupCookieJar *jar, SoupCookie *c)
6656 GSList *cf;
6657 SoupCookie *ci;
6659 print_cookie("soup_cookie_jar_delete_cookie", c);
6661 if (cookies_enabled == 0)
6662 return;
6664 if (jar == NULL || c == NULL)
6665 return;
6667 /* find and remove from persistent jar */
6668 cf = soup_cookie_jar_all_cookies(p_cookiejar);
6670 for (;cf; cf = cf->next) {
6671 ci = cf->data;
6672 if (soup_cookie_equal(ci, c)) {
6673 _soup_cookie_jar_delete_cookie(p_cookiejar, ci);
6674 break;
6678 soup_cookies_free(cf);
6680 /* delete from session jar */
6681 _soup_cookie_jar_delete_cookie(s_cookiejar, c);
6684 void
6685 soup_cookie_jar_add_cookie(SoupCookieJar *jar, SoupCookie *cookie)
6687 struct domain *d = NULL;
6688 SoupCookie *c;
6689 FILE *r_cookie_f;
6691 DNPRINTF(XT_D_COOKIE, "soup_cookie_jar_add_cookie: %p %p %p\n",
6692 jar, p_cookiejar, s_cookiejar);
6694 if (cookies_enabled == 0)
6695 return;
6697 /* see if we are up and running */
6698 if (p_cookiejar == NULL) {
6699 _soup_cookie_jar_add_cookie(jar, cookie);
6700 return;
6702 /* disallow p_cookiejar adds, shouldn't happen */
6703 if (jar == p_cookiejar)
6704 return;
6706 if (enable_cookie_whitelist &&
6707 (d = wl_find(cookie->domain, &c_wl)) == NULL) {
6708 blocked_cookies++;
6709 DNPRINTF(XT_D_COOKIE,
6710 "soup_cookie_jar_add_cookie: reject %s\n",
6711 cookie->domain);
6712 if (save_rejected_cookies) {
6713 if ((r_cookie_f = fopen(rc_fname, "a+")) == NULL)
6714 show_oops_s("can't open reject cookie file");
6715 fseek(r_cookie_f, 0, SEEK_END);
6716 fprintf(r_cookie_f, "%s%s\t%s\t%s\t%s\t%lu\t%s\t%s\n",
6717 cookie->http_only ? "#HttpOnly_" : "",
6718 cookie->domain,
6719 *cookie->domain == '.' ? "TRUE" : "FALSE",
6720 cookie->path,
6721 cookie->secure ? "TRUE" : "FALSE",
6722 cookie->expires ?
6723 (gulong)soup_date_to_time_t(cookie->expires) :
6725 cookie->name,
6726 cookie->value);
6727 fflush(r_cookie_f);
6728 fclose(r_cookie_f);
6730 if (!allow_volatile_cookies)
6731 return;
6734 if (cookie->expires == NULL && session_timeout) {
6735 soup_cookie_set_expires(cookie,
6736 soup_date_new_from_now(session_timeout));
6737 print_cookie("modified add cookie", cookie);
6740 /* see if we are white listed for persistence */
6741 if ((d && d->handy) || (enable_cookie_whitelist == 0)) {
6742 /* add to persistent jar */
6743 c = soup_cookie_copy(cookie);
6744 print_cookie("soup_cookie_jar_add_cookie p_cookiejar", c);
6745 _soup_cookie_jar_add_cookie(p_cookiejar, c);
6748 /* add to session jar */
6749 print_cookie("soup_cookie_jar_add_cookie s_cookiejar", cookie);
6750 _soup_cookie_jar_add_cookie(s_cookiejar, cookie);
6753 void
6754 setup_cookies(void)
6756 char file[PATH_MAX];
6758 set_hook((void *)&_soup_cookie_jar_add_cookie,
6759 "soup_cookie_jar_add_cookie");
6760 set_hook((void *)&_soup_cookie_jar_delete_cookie,
6761 "soup_cookie_jar_delete_cookie");
6763 if (cookies_enabled == 0)
6764 return;
6767 * the following code is intricate due to overriding several libsoup
6768 * functions.
6769 * do not alter order of these operations.
6772 /* rejected cookies */
6773 if (save_rejected_cookies)
6774 snprintf(rc_fname, sizeof file, "%s/rejected.txt", work_dir);
6776 /* persistent cookies */
6777 snprintf(file, sizeof file, "%s/cookies.txt", work_dir);
6778 p_cookiejar = soup_cookie_jar_text_new(file, read_only_cookies);
6780 /* session cookies */
6781 s_cookiejar = soup_cookie_jar_new();
6782 g_object_set(G_OBJECT(s_cookiejar), SOUP_COOKIE_JAR_ACCEPT_POLICY,
6783 cookie_policy, (void *)NULL);
6784 transfer_cookies();
6786 soup_session_add_feature(session, (SoupSessionFeature*)s_cookiejar);
6789 void
6790 setup_proxy(char *uri)
6792 if (proxy_uri) {
6793 g_object_set(session, "proxy_uri", NULL, (char *)NULL);
6794 soup_uri_free(proxy_uri);
6795 proxy_uri = NULL;
6797 if (http_proxy) {
6798 if (http_proxy != uri) {
6799 g_free(http_proxy);
6800 http_proxy = NULL;
6804 if (uri) {
6805 http_proxy = g_strdup(uri);
6806 DNPRINTF(XT_D_CONFIG, "setup_proxy: %s\n", uri);
6807 proxy_uri = soup_uri_new(http_proxy);
6808 g_object_set(session, "proxy-uri", proxy_uri, (char *)NULL);
6813 send_url_to_socket(char *url)
6815 int s, len, rv = -1;
6816 struct sockaddr_un sa;
6818 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6819 warnx("send_url_to_socket: socket");
6820 return (-1);
6823 sa.sun_family = AF_UNIX;
6824 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6825 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6826 len = SUN_LEN(&sa);
6828 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6829 warnx("send_url_to_socket: connect");
6830 goto done;
6833 if (send(s, url, strlen(url) + 1, 0) == -1) {
6834 warnx("send_url_to_socket: send");
6835 goto done;
6837 done:
6838 close(s);
6839 return (rv);
6842 void
6843 socket_watcher(gpointer data, gint fd, GdkInputCondition cond)
6845 int s, n;
6846 char str[XT_MAX_URL_LENGTH];
6847 socklen_t t = sizeof(struct sockaddr_un);
6848 struct sockaddr_un sa;
6849 struct passwd *p;
6850 uid_t uid;
6851 gid_t gid;
6853 if ((s = accept(fd, (struct sockaddr *)&sa, &t)) == -1) {
6854 warn("socket_watcher: accept");
6855 return;
6858 if (getpeereid(s, &uid, &gid) == -1) {
6859 warn("socket_watcher: getpeereid");
6860 return;
6862 if (uid != getuid() || gid != getgid()) {
6863 warnx("socket_watcher: unauthorized user");
6864 return;
6867 p = getpwuid(uid);
6868 if (p == NULL) {
6869 warnx("socket_watcher: not a valid user");
6870 return;
6873 n = recv(s, str, sizeof(str), 0);
6874 if (n <= 0)
6875 return;
6877 create_new_tab(str, NULL, 1);
6881 is_running(void)
6883 int s, len, rv = 1;
6884 struct sockaddr_un sa;
6886 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6887 warn("is_running: socket");
6888 return (-1);
6891 sa.sun_family = AF_UNIX;
6892 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6893 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6894 len = SUN_LEN(&sa);
6896 /* connect to see if there is a listener */
6897 if (connect(s, (struct sockaddr *)&sa, len) == -1)
6898 rv = 0; /* not running */
6899 else
6900 rv = 1; /* already running */
6902 close(s);
6904 return (rv);
6908 build_socket(void)
6910 int s, len;
6911 struct sockaddr_un sa;
6913 if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
6914 warn("build_socket: socket");
6915 return (-1);
6918 sa.sun_family = AF_UNIX;
6919 snprintf(sa.sun_path, sizeof(sa.sun_path), "%s/%s/%s",
6920 pwd->pw_dir, XT_DIR, XT_SOCKET_FILE);
6921 len = SUN_LEN(&sa);
6923 /* connect to see if there is a listener */
6924 if (connect(s, (struct sockaddr *)&sa, len) == -1) {
6925 /* no listener so we will */
6926 unlink(sa.sun_path);
6928 if (bind(s, (struct sockaddr *)&sa, len) == -1) {
6929 warn("build_socket: bind");
6930 goto done;
6933 if (listen(s, 1) == -1) {
6934 warn("build_socket: listen");
6935 goto done;
6938 return (s);
6941 done:
6942 close(s);
6943 return (-1);
6946 void
6947 usage(void)
6949 fprintf(stderr,
6950 "%s [-nSTVt][-f file][-s session] url ...\n", __progname);
6951 exit(0);
6955 main(int argc, char *argv[])
6957 struct stat sb;
6958 int c, s, optn = 0, focus = 1;
6959 char conf[PATH_MAX] = { '\0' };
6960 char file[PATH_MAX];
6961 char *env_proxy = NULL;
6962 FILE *f = NULL;
6963 struct karg a;
6964 struct sigaction sact;
6966 start_argv = argv;
6968 strlcpy(named_session, XT_SAVED_TABS_FILE, sizeof named_session);
6970 while ((c = getopt(argc, argv, "STVf:s:tn")) != -1) {
6971 switch (c) {
6972 case 'S':
6973 show_url = 0;
6974 break;
6975 case 'T':
6976 show_tabs = 0;
6977 break;
6978 case 'V':
6979 errx(0 , "Version: %s", version);
6980 break;
6981 case 'f':
6982 strlcpy(conf, optarg, sizeof(conf));
6983 break;
6984 case 's':
6985 strlcpy(named_session, optarg, sizeof(named_session));
6986 break;
6987 case 't':
6988 tabless = 1;
6989 break;
6990 case 'n':
6991 optn = 1;
6992 break;
6993 default:
6994 usage();
6995 /* NOTREACHED */
6998 argc -= optind;
6999 argv += optind;
7001 TAILQ_INIT(&tabs);
7002 RB_INIT(&hl);
7003 RB_INIT(&js_wl);
7004 RB_INIT(&downloads);
7005 TAILQ_INIT(&mtl);
7006 TAILQ_INIT(&aliases);
7007 TAILQ_INIT(&undos);
7009 gnutls_global_init();
7011 /* generate session keys for xtp pages */
7012 generate_xtp_session_key(&dl_session_key);
7013 generate_xtp_session_key(&hl_session_key);
7014 generate_xtp_session_key(&cl_session_key);
7015 generate_xtp_session_key(&fl_session_key);
7017 /* prepare gtk */
7018 gtk_init(&argc, &argv);
7019 if (!g_thread_supported())
7020 g_thread_init(NULL);
7022 /* signals */
7023 bzero(&sact, sizeof(sact));
7024 sigemptyset(&sact.sa_mask);
7025 sact.sa_handler = sigchild;
7026 sact.sa_flags = SA_NOCLDSTOP;
7027 sigaction(SIGCHLD, &sact, NULL);
7029 /* set download dir */
7030 pwd = getpwuid(getuid());
7031 if (pwd == NULL)
7032 errx(1, "invalid user %d", getuid());
7033 strlcpy(download_dir, pwd->pw_dir, sizeof download_dir);
7035 /* set default string settings */
7036 home = g_strdup("http://www.peereboom.us");
7037 search_string = g_strdup("https://ssl.scroogle.org/cgi-bin/nbbwssl.cgi?Gw=%s");
7038 resource_dir = g_strdup("/usr/local/share/xxxterm/");
7039 strlcpy(runtime_settings,"runtime", sizeof runtime_settings);
7041 /* read config file */
7042 if (strlen(conf) == 0)
7043 snprintf(conf, sizeof conf, "%s/.%s",
7044 pwd->pw_dir, XT_CONF_FILE);
7045 config_parse(conf, 0);
7047 /* working directory */
7048 snprintf(work_dir, sizeof work_dir, "%s/%s", pwd->pw_dir, XT_DIR);
7049 if (stat(work_dir, &sb)) {
7050 if (mkdir(work_dir, S_IRWXU) == -1)
7051 err(1, "mkdir work_dir");
7052 if (stat(work_dir, &sb))
7053 err(1, "stat work_dir");
7055 if (S_ISDIR(sb.st_mode) == 0)
7056 errx(1, "%s not a dir", work_dir);
7057 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7058 warnx("fixing invalid permissions on %s", work_dir);
7059 if (chmod(work_dir, S_IRWXU) == -1)
7060 err(1, "chmod");
7063 /* icon cache dir */
7064 snprintf(cache_dir, sizeof cache_dir, "%s/%s/%s",
7065 pwd->pw_dir, XT_DIR, XT_CACHE_DIR);
7066 if (stat(cache_dir, &sb)) {
7067 if (mkdir(cache_dir, S_IRWXU) == -1)
7068 err(1, "mkdir cache_dir");
7069 if (stat(cache_dir, &sb))
7070 err(1, "stat cache_dir");
7072 if (S_ISDIR(sb.st_mode) == 0)
7073 errx(1, "%s not a dir", cache_dir);
7074 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7075 warnx("fixing invalid permissions on %s", cache_dir);
7076 if (chmod(cache_dir, S_IRWXU) == -1)
7077 err(1, "chmod");
7080 /* certs dir */
7081 snprintf(certs_dir, sizeof certs_dir, "%s/%s/%s",
7082 pwd->pw_dir, XT_DIR, XT_CERT_DIR);
7083 if (stat(certs_dir, &sb)) {
7084 if (mkdir(certs_dir, S_IRWXU) == -1)
7085 err(1, "mkdir certs_dir");
7086 if (stat(certs_dir, &sb))
7087 err(1, "stat certs_dir");
7089 if (S_ISDIR(sb.st_mode) == 0)
7090 errx(1, "%s not a dir", certs_dir);
7091 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7092 warnx("fixing invalid permissions on %s", certs_dir);
7093 if (chmod(certs_dir, S_IRWXU) == -1)
7094 err(1, "chmod");
7097 /* sessions dir */
7098 snprintf(sessions_dir, sizeof sessions_dir, "%s/%s/%s",
7099 pwd->pw_dir, XT_DIR, XT_SESSIONS_DIR);
7100 if (stat(sessions_dir, &sb)) {
7101 if (mkdir(sessions_dir, S_IRWXU) == -1)
7102 err(1, "mkdir sessions_dir");
7103 if (stat(sessions_dir, &sb))
7104 err(1, "stat sessions_dir");
7106 if (S_ISDIR(sb.st_mode) == 0)
7107 errx(1, "%s not a dir", sessions_dir);
7108 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7109 warnx("fixing invalid permissions on %s", sessions_dir);
7110 if (chmod(sessions_dir, S_IRWXU) == -1)
7111 err(1, "chmod");
7113 /* runtime settings that can override config file */
7114 if (runtime_settings[0] != '\0')
7115 config_parse(runtime_settings, 1);
7117 /* download dir */
7118 if (!strcmp(download_dir, pwd->pw_dir))
7119 strlcat(download_dir, "/downloads", sizeof download_dir);
7120 if (stat(download_dir, &sb)) {
7121 if (mkdir(download_dir, S_IRWXU) == -1)
7122 err(1, "mkdir download_dir");
7123 if (stat(download_dir, &sb))
7124 err(1, "stat download_dir");
7126 if (S_ISDIR(sb.st_mode) == 0)
7127 errx(1, "%s not a dir", download_dir);
7128 if (((sb.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) != S_IRWXU) {
7129 warnx("fixing invalid permissions on %s", download_dir);
7130 if (chmod(download_dir, S_IRWXU) == -1)
7131 err(1, "chmod");
7134 /* favorites file */
7135 snprintf(file, sizeof file, "%s/%s", work_dir, XT_FAVS_FILE);
7136 if (stat(file, &sb)) {
7137 warnx("favorites file doesn't exist, creating it");
7138 if ((f = fopen(file, "w")) == NULL)
7139 err(1, "favorites");
7140 fclose(f);
7143 /* cookies */
7144 session = webkit_get_default_session();
7145 setup_cookies();
7147 /* certs */
7148 if (ssl_ca_file) {
7149 if (stat(ssl_ca_file, &sb)) {
7150 warn("no CA file: %s", ssl_ca_file);
7151 g_free(ssl_ca_file);
7152 ssl_ca_file = NULL;
7153 } else
7154 g_object_set(session,
7155 SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
7156 SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
7157 (void *)NULL);
7160 /* proxy */
7161 env_proxy = getenv("http_proxy");
7162 if (env_proxy)
7163 setup_proxy(env_proxy);
7164 else
7165 setup_proxy(http_proxy);
7167 /* see if there is already an xxxterm running */
7168 if (single_instance && is_running()) {
7169 optn = 1;
7170 warnx("already running");
7173 if (optn) {
7174 while (argc) {
7175 send_url_to_socket(argv[0]);
7177 argc--;
7178 argv++;
7180 exit(0);
7183 /* go graphical */
7184 create_canvas();
7186 if (save_global_history)
7187 restore_global_history();
7189 if (!strcmp(named_session, XT_SAVED_TABS_FILE))
7190 restore_saved_tabs();
7191 else {
7192 a.s = named_session;
7193 a.i = XT_SES_DONOTHING;
7194 open_tabs(NULL, &a);
7197 while (argc) {
7198 create_new_tab(argv[0], NULL, focus);
7199 focus = 0;
7201 argc--;
7202 argv++;
7205 if (TAILQ_EMPTY(&tabs))
7206 create_new_tab(home, NULL, 1);
7208 if (enable_socket)
7209 if ((s = build_socket()) != -1)
7210 gdk_input_add(s, GDK_INPUT_READ, socket_watcher, NULL);
7212 gtk_main();
7214 gnutls_global_deinit();
7216 return (0);