"Fossies" - the Fresh Open Source Software Archive

Member "xombrero-1.6.4/xombrero.c" (17 Feb 2015, 209908 Bytes) of package /linux/www/old/xombrero-1.6.4.tgz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "xombrero.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.6.3_vs_1.6.4.

    1 /*
    2  * Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
    3  * Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
    4  * Copyright (c) 2010, 2011, 2012 Edd Barrett <vext01@gmail.com>
    5  * Copyright (c) 2011 Todd T. Fries <todd@fries.net>
    6  * Copyright (c) 2011 Raphael Graf <r@undefined.ch>
    7  * Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
    8  * Copyright (c) 2012, 2013 Josh Rickmar <jrick@devio.us>
    9  * Copyright (c) 2013 David Hill <dhill@mindcry.org>
   10  *
   11  * Permission to use, copy, modify, and distribute this software for any
   12  * purpose with or without fee is hereby granted, provided that the above
   13  * copyright notice and this permission notice appear in all copies.
   14  *
   15  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
   16  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
   17  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
   18  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
   19  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
   20  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
   21  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
   22  */
   23 
   24 #include <xombrero.h>
   25 #include "version.h"
   26 
   27 char        *version = XOMBRERO_VERSION;
   28 
   29 #ifdef XT_DEBUG
   30 uint32_t        swm_debug = 0
   31                 | XT_D_MOVE
   32                 | XT_D_KEY
   33                 | XT_D_TAB
   34                 | XT_D_URL
   35                 | XT_D_CMD
   36                 | XT_D_NAV
   37                 | XT_D_DOWNLOAD
   38                 | XT_D_CONFIG
   39                 | XT_D_JS
   40                 | XT_D_FAVORITE
   41                 | XT_D_PRINTING
   42                 | XT_D_COOKIE
   43                 | XT_D_KEYBINDING
   44                 | XT_D_CLIP
   45                 | XT_D_BUFFERCMD
   46                 | XT_D_INSPECTOR
   47                 | XT_D_VISITED
   48                 | XT_D_HISTORY
   49                 ;
   50 #endif
   51 
   52 char        *icons[] = {
   53     "xombreroicon16.png",
   54     "xombreroicon32.png",
   55     "xombreroicon48.png",
   56     "xombreroicon64.png",
   57     "xombreroicon128.png"
   58 };
   59 
   60 struct session {
   61     TAILQ_ENTRY(session)    entry;
   62     const gchar     *name;
   63 };
   64 TAILQ_HEAD(session_list, session);
   65 
   66 struct undo {
   67     TAILQ_ENTRY(undo)   entry;
   68     gchar           *uri;
   69     GList           *history;
   70     int         back; /* Keeps track of how many back
   71                        * history items there are. */
   72 };
   73 TAILQ_HEAD(undo_tailq, undo);
   74 
   75 struct command_entry {
   76     char                *line;
   77     TAILQ_ENTRY(command_entry)  entry;
   78 };
   79 TAILQ_HEAD(command_list, command_entry);
   80 
   81 /* defines */
   82 #define XT_CACHE_DIR        ("cache")
   83 #define XT_CERT_DIR     ("certs")
   84 #define XT_CERT_CACHE_DIR   ("certs_cache")
   85 #define XT_JS_DIR       ("js")
   86 #define XT_SESSIONS_DIR     ("sessions")
   87 #define XT_TEMP_DIR     ("tmp")
   88 #define XT_QMARKS_FILE      ("quickmarks")
   89 #define XT_SAVED_TABS_FILE  ("main_session")
   90 #define XT_RESTART_TABS_FILE    ("restart_tabs")
   91 #define XT_SOCKET_FILE      ("socket")
   92 #define XT_SAVE_SESSION_ID  ("SESSION_NAME=")
   93 #define XT_SEARCH_FILE      ("search_history")
   94 #define XT_COMMAND_FILE     ("command_history")
   95 #define XT_DLMAN_REFRESH    "10"
   96 #define XT_MAX_URL_LENGTH   (4096) /* 1 page is atomic, don't make bigger */
   97 #define XT_MAX_UNDO_CLOSE_TAB   (32)
   98 #define XT_PRINT_EXTRA_MARGIN   10
   99 #define XT_URL_REGEX        ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
  100 #define XT_INVALID_MARK     (-1) /* XXX this is a double, maybe use something else, like a nan */
  101 #define XT_MAX_CERTS        (32)
  102 
  103 /* colors */
  104 #define XT_COLOR_RED        "#cc0000"
  105 #define XT_COLOR_YELLOW     "#ffff66"
  106 #define XT_COLOR_BLUE       "lightblue"
  107 #define XT_COLOR_GREEN      "#99ff66"
  108 #define XT_COLOR_WHITE      "white"
  109 #define XT_COLOR_BLACK      "black"
  110 
  111 #define XT_COLOR_CT_BACKGROUND  "#000000"
  112 #define XT_COLOR_CT_INACTIVE    "#dddddd"
  113 #define XT_COLOR_CT_ACTIVE  "#bbbb00"
  114 #define XT_COLOR_CT_SEPARATOR   "#555555"
  115 
  116 #define XT_COLOR_SB_SEPARATOR   "#555555"
  117 
  118 /* CSS element names */
  119 #define XT_CSS_NORMAL       ""
  120 #define XT_CSS_RED      "red"
  121 #define XT_CSS_YELLOW       "yellow"
  122 #define XT_CSS_GREEN        "green"
  123 #define XT_CSS_BLUE     "blue"
  124 #define XT_CSS_HIDDEN       "hidden"
  125 #define XT_CSS_ACTIVE       "active"
  126 
  127 #define XT_PROTO_DELIM      "://"
  128 
  129 /* actions */
  130 #define XT_MOVE_INVALID     (0)
  131 #define XT_MOVE_DOWN        (1)
  132 #define XT_MOVE_UP      (2)
  133 #define XT_MOVE_BOTTOM      (3)
  134 #define XT_MOVE_TOP     (4)
  135 #define XT_MOVE_PAGEDOWN    (5)
  136 #define XT_MOVE_PAGEUP      (6)
  137 #define XT_MOVE_HALFDOWN    (7)
  138 #define XT_MOVE_HALFUP      (8)
  139 #define XT_MOVE_LEFT        (9)
  140 #define XT_MOVE_FARLEFT     (10)
  141 #define XT_MOVE_RIGHT       (11)
  142 #define XT_MOVE_FARRIGHT    (12)
  143 #define XT_MOVE_PERCENT     (13)
  144 #define XT_MOVE_CENTER      (14)
  145 
  146 #define XT_QMARK_SET        (0)
  147 #define XT_QMARK_OPEN       (1)
  148 #define XT_QMARK_TAB        (2)
  149 
  150 #define XT_MARK_SET     (0)
  151 #define XT_MARK_GOTO        (1)
  152 
  153 #define XT_GO_UP_ROOT       (999)
  154 
  155 #define XT_NAV_INVALID      (0)
  156 #define XT_NAV_BACK     (1)
  157 #define XT_NAV_FORWARD      (2)
  158 #define XT_NAV_RELOAD       (3)
  159 #define XT_NAV_STOP     (4)
  160 
  161 #define XT_FOCUS_INVALID    (0)
  162 #define XT_FOCUS_URI        (1)
  163 #define XT_FOCUS_SEARCH     (2)
  164 
  165 #define XT_SEARCH_INVALID   (0)
  166 #define XT_SEARCH_NEXT      (1)
  167 #define XT_SEARCH_PREV      (2)
  168 
  169 #define XT_PASTE_CURRENT_TAB    (0)
  170 #define XT_PASTE_NEW_TAB    (1)
  171 
  172 #define XT_ZOOM_IN      (-1)
  173 #define XT_ZOOM_OUT     (-2)
  174 #define XT_ZOOM_NORMAL      (100)
  175 
  176 #define XT_SES_DONOTHING    (0)
  177 #define XT_SES_CLOSETABS    (1)
  178 
  179 #define XT_PREFIX       (1<<0)
  180 #define XT_USERARG      (1<<1)
  181 #define XT_URLARG       (1<<2)
  182 #define XT_INTARG       (1<<3)
  183 #define XT_SESSARG      (1<<4)
  184 #define XT_SETARG       (1<<5)
  185 
  186 #define XT_HINT_NEWTAB      (1<<0)
  187 
  188 #define XT_BUFCMD_SZ        (8)
  189 
  190 #define XT_EJS_SHOW     (1<<0)
  191 
  192 GtkWidget * create_button(const char *, const char *, int);
  193 
  194 void        recalc_tabs(void);
  195 void        recolor_compact_tabs(void);
  196 void        set_current_tab(int page_num);
  197 gboolean    update_statusbar_position(GtkAdjustment*, gpointer);
  198 void        marks_clear(struct tab *t);
  199 
  200 /* globals */
  201 extern char     *__progname;
  202 char * const        *start_argv;
  203 struct passwd       *pwd;
  204 GtkWidget       *main_window;
  205 GtkNotebook     *notebook;
  206 GtkWidget       *tab_bar;
  207 GtkWidget       *tab_bar_box;
  208 GtkWidget       *arrow, *abtn;
  209 GdkEvent        *fevent = NULL;
  210 struct tab_list     tabs;
  211 struct history_list hl;
  212 int         hl_purge_count = 0;
  213 struct session_list sessions;
  214 struct wl_list      c_wl;
  215 struct wl_list      js_wl;
  216 struct wl_list      pl_wl;
  217 struct wl_list      force_https;
  218 struct wl_list      svil;
  219 struct strict_transport_tree    st_tree;
  220 struct undo_tailq   undos;
  221 struct keybinding_list  kbl;
  222 struct sp_list      spl;
  223 struct user_agent_list  ua_list;
  224 struct http_accept_list ha_list;
  225 struct domain_id_list   di_list;
  226 struct cmd_alias_list   cal;
  227 struct custom_uri_list  cul;
  228 struct command_list chl;
  229 struct command_list shl;
  230 struct command_entry    *history_at;
  231 struct command_entry    *search_at;
  232 struct secviolation_list    svl;
  233 struct set_reject_list  srl;
  234 int         undo_count;
  235 int         cmd_history_count = 0;
  236 int         search_history_count = 0;
  237 char            *global_search;
  238 uint64_t        blocked_cookies = 0;
  239 char            named_session[PATH_MAX];
  240 GtkListStore        *completion_model;
  241 GtkListStore        *buffers_store;
  242 char            *stylesheet;
  243 
  244 char            *qmarks[XT_NOQMARKS];
  245 int         btn_down;   /* M1 down in any wv */
  246 regex_t         url_re;     /* guess_search regex */
  247 
  248 /* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
  249 int         next_download_id = 1;
  250 
  251 void            xxx_dir(char *);
  252 int         icon_size_map(int);
  253 void            activate_uri_entry_cb(GtkWidget*, struct tab *);
  254 void            activate_search_entry_cb(GtkWidget*, struct tab *);
  255 
  256 void
  257 history_delete(struct command_list *l, int *counter)
  258 {
  259     struct command_entry    *c;
  260 
  261     if (l == NULL || counter == NULL)
  262         return;
  263 
  264     c = TAILQ_LAST(l, command_list);
  265     if (c == NULL)
  266         return;
  267 
  268     TAILQ_REMOVE(l, c, entry);
  269     *counter -= 1;
  270     g_free(c->line);
  271     g_free(c);
  272 }
  273 
  274 void
  275 history_add(struct command_list *list, char *file, char *l, int *counter)
  276 {
  277     struct command_entry    *c;
  278     FILE            *f;
  279 
  280     if (list == NULL || l == NULL || counter == NULL)
  281         return;
  282 
  283     /* don't add the same line */
  284     c = TAILQ_FIRST(list);
  285     if (c)
  286         if (!strcmp(c->line + 1 /* skip space */, l))
  287             return;
  288 
  289     c = g_malloc0(sizeof *c);
  290     c->line = g_strdup_printf(" %s", l);
  291 
  292     *counter += 1;
  293     TAILQ_INSERT_HEAD(list, c, entry);
  294 
  295     if (*counter > 1000)
  296         history_delete(list, counter);
  297 
  298     if (history_autosave && file) {
  299         f = fopen(file, "w");
  300         if (f == NULL) {
  301             show_oops(NULL, "couldn't write history %s", file);
  302             return;
  303         }
  304 
  305         TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
  306             c->line[0] = ' ';
  307             fprintf(f, "%s\n", c->line);
  308         }
  309 
  310         fclose(f);
  311     }
  312 }
  313 
  314 int
  315 history_read(struct command_list *list, char *file, int *counter)
  316 {
  317     FILE            *f;
  318     char            *s, line[65536];
  319 
  320     if (list == NULL || file == NULL)
  321         return (1);
  322 
  323     f = fopen(file, "r");
  324     if (f == NULL) {
  325         startpage_add("couldn't open history file %s", file);
  326         return (1);
  327     }
  328 
  329     for (;;) {
  330         s = fgets(line, sizeof line, f);
  331         if (s == NULL || feof(f) || ferror(f))
  332             break;
  333         if ((s = strchr(line, '\n')) == NULL) {
  334             startpage_add("invalid history file %s", file);
  335             fclose(f);
  336             return (1);
  337         }
  338         *s = '\0';
  339 
  340         history_add(list, NULL, line + 1, counter);
  341     }
  342 
  343     fclose(f);
  344 
  345     return (0);
  346 }
  347 
  348 /* marks array storage. */
  349 char
  350 indextomark(int i)
  351 {
  352     if (i < 0 || i >= XT_NOMARKS)
  353         return (0);
  354 
  355     return XT_MARKS[i];
  356 }
  357 
  358 int
  359 marktoindex(char m)
  360 {
  361     char *ret;
  362 
  363     if ((ret = strchr(XT_MARKS, m)) != NULL)
  364         return ret - XT_MARKS;
  365 
  366     return (-1);
  367 }
  368 
  369 /* quickmarks array storage. */
  370 char
  371 indextoqmark(int i)
  372 {
  373     if (i < 0 || i >= XT_NOQMARKS)
  374         return (0);
  375 
  376     return XT_QMARKS[i];
  377 }
  378 
  379 int
  380 qmarktoindex(char m)
  381 {
  382     char *ret;
  383 
  384     if ((ret = strchr(XT_QMARKS, m)) != NULL)
  385         return ret - XT_QMARKS;
  386 
  387     return (-1);
  388 }
  389 
  390 int
  391 is_g_object_setting(GObject *o, char *str)
  392 {
  393     guint           n_props = 0, i;
  394     GParamSpec      **proplist;
  395     int         rv = 0;
  396 
  397     if (!G_IS_OBJECT(o))
  398         return (0);
  399 
  400     proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
  401         &n_props);
  402 
  403     for (i = 0; i < n_props; i++) {
  404         if (! strcmp(proplist[i]->name, str)) {
  405             rv = 1;
  406             break;
  407         }
  408     }
  409 
  410     g_free(proplist);
  411     return (rv);
  412 }
  413 
  414 struct tab *
  415 get_current_tab(void)
  416 {
  417     struct tab      *t;
  418 
  419     TAILQ_FOREACH(t, &tabs, entry) {
  420         if (t->tab_id == gtk_notebook_get_current_page(notebook))
  421             return (t);
  422     }
  423 
  424     warnx("%s: no current tab", __func__);
  425 
  426     return (NULL);
  427 }
  428 
  429 int
  430 set_ssl_ca_file(struct settings *s, char *file)
  431 {
  432     struct stat     sb;
  433 
  434     if (file == NULL || strlen(file) == 0)
  435         return (-1);
  436     if (stat(file, &sb)) {
  437         warnx("no CA file: %s", file);
  438         return (-1);
  439     }
  440     expand_tilde(ssl_ca_file, sizeof ssl_ca_file, file);
  441     g_object_set(session,
  442         SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
  443         SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
  444         (void *)NULL);
  445     return (0);
  446 }
  447 
  448 void
  449 set_status(struct tab *t, gchar *fmt, ...)
  450 {
  451     va_list ap;
  452     gchar   *status;
  453 
  454     va_start(ap, fmt);
  455 
  456     status = g_strdup_vprintf(fmt, ap);
  457 
  458     gtk_entry_set_text(GTK_ENTRY(t->sbe.uri), status);
  459 
  460     if (!t->status)
  461         t->status = status;
  462     else if (strcmp(t->status, status)) {
  463         /* set new status */
  464         g_free(t->status);
  465         t->status = status;
  466     } else
  467         g_free(status);
  468 
  469     va_end(ap);
  470 }
  471 
  472 void
  473 hide_cmd(struct tab *t)
  474 {
  475     DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
  476 
  477     history_at = NULL; /* just in case */
  478     search_at = NULL; /* just in case */
  479     gtk_widget_set_can_focus(t->cmd, FALSE);
  480     gtk_widget_hide(t->cmd);
  481 }
  482 
  483 void
  484 show_cmd(struct tab *t, const char *s)
  485 {
  486     DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
  487 
  488     /* without this you can't middle click in t->cmd to paste */
  489     gtk_entry_set_text(GTK_ENTRY(t->cmd), "");
  490 
  491     history_at = NULL;
  492     search_at = NULL;
  493     gtk_widget_hide(t->oops);
  494     gtk_widget_set_can_focus(t->cmd, TRUE);
  495     gtk_widget_show(t->cmd);
  496 
  497     gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
  498 #if GTK_CHECK_VERSION(3, 0, 0)
  499     gtk_widget_set_name(t->cmd, XT_CSS_NORMAL);
  500 #else
  501     gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL,
  502         &t->default_style->base[GTK_STATE_NORMAL]);
  503 #endif
  504     gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
  505     gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
  506 }
  507 
  508 void
  509 hide_buffers(struct tab *t)
  510 {
  511     gtk_widget_hide(t->buffers);
  512     gtk_widget_set_can_focus(t->buffers, FALSE);
  513     gtk_list_store_clear(buffers_store);
  514 }
  515 
  516 enum {
  517     COL_ID      = 0,
  518     COL_FAVICON,
  519     COL_TITLE,
  520     NUM_COLS
  521 };
  522 
  523 int
  524 sort_tabs_by_page_num(struct tab ***stabs)
  525 {
  526     int         num_tabs = 0;
  527     struct tab      *t;
  528 
  529     num_tabs = gtk_notebook_get_n_pages(notebook);
  530 
  531     *stabs = g_malloc0(num_tabs * sizeof(struct tab *));
  532 
  533     TAILQ_FOREACH(t, &tabs, entry)
  534         (*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
  535 
  536     return (num_tabs);
  537 }
  538 
  539 void
  540 buffers_make_list(void)
  541 {
  542     GtkTreeIter     iter;
  543     const gchar     *title = NULL;
  544     struct tab      **stabs = NULL;
  545     int         i, num_tabs;
  546 
  547     num_tabs = sort_tabs_by_page_num(&stabs);
  548 
  549     for (i = 0; i < num_tabs; i++)
  550         if (stabs[i]) {
  551             gtk_list_store_append(buffers_store, &iter);
  552             title = get_title(stabs[i], FALSE);
  553             gtk_list_store_set(buffers_store, &iter,
  554                 COL_ID, i + 1, /* Enumerate the tabs starting from 1
  555                         * rather than 0. */
  556                 COL_FAVICON, gtk_image_get_pixbuf
  557                     (GTK_IMAGE(stabs[i]->tab_elems.favicon)),
  558                 COL_TITLE, title,
  559                 -1);
  560         }
  561 
  562     g_free(stabs);
  563 }
  564 
  565 void
  566 show_buffers(struct tab *t)
  567 {
  568     GtkTreeIter     iter;
  569     GtkTreeSelection    *sel;
  570     GtkTreePath     *path;
  571     int         index;
  572 
  573     if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)))
  574         return;
  575 
  576     buffers_make_list();
  577 
  578     sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(t->buffers));
  579     index = gtk_notebook_get_current_page(notebook);
  580     path = gtk_tree_path_new_from_indices(index, -1);
  581     if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path))
  582         gtk_tree_selection_select_iter(sel, &iter);
  583     gtk_tree_path_free(path);
  584 
  585     gtk_widget_show(t->buffers);
  586     gtk_widget_set_can_focus(t->buffers, TRUE);
  587     gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
  588 }
  589 
  590 void
  591 toggle_buffers(struct tab *t)
  592 {
  593     if (gtk_widget_get_visible(t->buffers))
  594         hide_buffers(t);
  595     else
  596         show_buffers(t);
  597 }
  598 
  599 int
  600 buffers(struct tab *t, struct karg *args)
  601 {
  602     show_buffers(t);
  603 
  604     return (0);
  605 }
  606 
  607 int
  608 set_scrollbar_visibility(struct tab *t, int visible)
  609 {
  610 #if GTK_CHECK_VERSION(3, 0, 0)
  611     GtkWidget       *h_scrollbar, *v_scrollbar;
  612 
  613     h_scrollbar = gtk_scrolled_window_get_hscrollbar(
  614         GTK_SCROLLED_WINDOW(t->browser_win));
  615     v_scrollbar = gtk_scrolled_window_get_vscrollbar(
  616         GTK_SCROLLED_WINDOW(t->browser_win));
  617 
  618     if (visible == 0) {
  619         gtk_widget_set_name(h_scrollbar, XT_CSS_HIDDEN);
  620         gtk_widget_set_name(v_scrollbar, XT_CSS_HIDDEN);
  621     } else {
  622         gtk_widget_set_name(h_scrollbar, "");
  623         gtk_widget_set_name(v_scrollbar, "");
  624     }
  625 
  626     return (0);
  627 #else
  628     return (visible == 0);
  629 #endif
  630 }
  631 
  632 void
  633 hide_oops(struct tab *t)
  634 {
  635     gtk_widget_hide(t->oops);
  636 }
  637 
  638 void
  639 show_oops(struct tab *at, const char *fmt, ...)
  640 {
  641     va_list         ap;
  642     char            *msg = NULL;
  643     struct tab      *t = NULL;
  644 
  645     if (fmt == NULL)
  646         return;
  647 
  648     if (at == NULL) {
  649         if ((t = get_current_tab()) == NULL)
  650             return;
  651     } else
  652         t = at;
  653 
  654     va_start(ap, fmt);
  655     if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
  656         errx(1, "show_oops failed");
  657     va_end(ap);
  658 
  659     gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
  660     gtk_widget_hide(t->cmd);
  661     gtk_widget_show(t->oops);
  662 
  663     if (msg)
  664         g_free(msg);
  665 }
  666 
  667 char            work_dir[PATH_MAX];
  668 char            certs_dir[PATH_MAX];
  669 char            certs_cache_dir[PATH_MAX];
  670 char            js_dir[PATH_MAX];
  671 char            cache_dir[PATH_MAX];
  672 char            sessions_dir[PATH_MAX];
  673 char            temp_dir[PATH_MAX];
  674 char            cookie_file[PATH_MAX];
  675 char            *strict_transport_file = NULL;
  676 SoupSession     *session;
  677 SoupCookieJar       *s_cookiejar;
  678 SoupCookieJar       *p_cookiejar;
  679 char            rc_fname[PATH_MAX];
  680 
  681 struct mime_type_list   mtl;
  682 struct alias_list   aliases;
  683 
  684 /* protos */
  685 struct tab      *create_new_tab(const char *, struct undo *, int, int);
  686 void            delete_tab(struct tab *);
  687 void            setzoom_webkit(struct tab *, int);
  688 int         download_rb_cmp(struct download *, struct download *);
  689 gboolean        cmd_execute(struct tab *t, char *str);
  690 
  691 int
  692 history_rb_cmp(struct history *h1, struct history *h2)
  693 {
  694     return (strcmp(h1->uri, h2->uri));
  695 }
  696 RB_GENERATE(history_list, history, entry, history_rb_cmp);
  697 
  698 int
  699 download_rb_cmp(struct download *e1, struct download *e2)
  700 {
  701     return (e1->id < e2->id ? -1 : e1->id > e2->id);
  702 }
  703 RB_GENERATE(download_list, download, entry, download_rb_cmp);
  704 
  705 int
  706 secviolation_rb_cmp(struct secviolation *s1, struct secviolation *s2)
  707 {
  708     return (s1->xtp_arg < s2->xtp_arg ? -1 : s1->xtp_arg > s2->xtp_arg);
  709 }
  710 RB_GENERATE(secviolation_list, secviolation, entry, secviolation_rb_cmp);
  711 
  712 int
  713 user_agent_rb_cmp(struct user_agent *ua1, struct user_agent *ua2)
  714 {
  715     return (ua1->id < ua2->id ? -1 : ua1->id > ua2->id);
  716 }
  717 RB_GENERATE(user_agent_list, user_agent, entry, user_agent_rb_cmp);
  718 
  719 int
  720 http_accept_rb_cmp(struct http_accept *ha1, struct http_accept *ha2)
  721 {
  722     return (ha1->id < ha2->id ? -1 : ha1->id > ha2->id);
  723 }
  724 RB_GENERATE(http_accept_list, http_accept, entry, http_accept_rb_cmp);
  725 
  726 int
  727 domain_id_rb_cmp(struct domain_id *d1, struct domain_id *d2)
  728 {
  729     return (strcmp(d1->domain, d2->domain));
  730 }
  731 RB_GENERATE(domain_id_list, domain_id, entry, domain_id_rb_cmp);
  732 
  733 struct valid_url_types {
  734     char        *type;
  735 } vut[] = {
  736     { "http://" },
  737     { "https://" },
  738     { "ftp://" },
  739     { "file://" },
  740     { XT_URI_ABOUT },
  741     { XT_XTP_STR },
  742 };
  743 
  744 int
  745 valid_url_type(const char *url)
  746 {
  747     int         i;
  748 
  749     for (i = 0; i < LENGTH(vut); i++)
  750         if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
  751             return (0);
  752 
  753     return (1);
  754 }
  755 
  756 char *
  757 match_alias(const char *url_in)
  758 {
  759     struct alias        *a;
  760     char            *arg;
  761     char            *url_out = NULL, *search, *enc_arg;
  762     char            **sv;
  763 
  764     search = g_strdup(url_in);
  765     arg = search;
  766     if (strsep(&arg, " \t") == NULL) {
  767         show_oops(NULL, "match_alias: NULL URL");
  768         goto done;
  769     }
  770 
  771     TAILQ_FOREACH(a, &aliases, entry) {
  772         if (!strcmp(search, a->a_name))
  773             break;
  774     }
  775 
  776     if (a != NULL) {
  777         DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
  778             a->a_name);
  779         if (arg == NULL)
  780             arg = "";
  781         enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
  782         sv = g_strsplit(a->a_uri, "%s", 2);
  783         if (arg != NULL)
  784             url_out = g_strjoinv(enc_arg, sv);
  785         else
  786             url_out = g_strjoinv("", sv);
  787         g_free(enc_arg);
  788         g_strfreev(sv);
  789     }
  790 done:
  791     g_free(search);
  792     return (url_out);
  793 }
  794 
  795 char *
  796 guess_url_type(const char *url_in)
  797 {
  798     struct stat     sb;
  799     char            cwd[PATH_MAX] = {0};
  800     char            *url_out = NULL, *enc_search = NULL;
  801     char            *path = NULL;
  802     char            **sv = NULL;
  803     int         i;
  804 
  805 
  806     /* substitute aliases */
  807     url_out = match_alias(url_in);
  808     if (url_out != NULL)
  809         return (url_out);
  810 
  811     /* see if we are an about page */
  812     if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
  813         for (i = 0; i < about_list_size(); i++)
  814             if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
  815                 about_list[i].name)) {
  816                 url_out = g_strdup(url_in);
  817                 goto done;
  818             }
  819 
  820     if (guess_search && url_regex &&
  821         !(g_str_has_prefix(url_in, "http://") ||
  822         g_str_has_prefix(url_in, "https://"))) {
  823         if (regexec(&url_re, url_in, 0, NULL, 0)) {
  824             /* invalid URI so search instead */
  825             enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
  826             sv = g_strsplit(search_string, "%s", 2);
  827             url_out = g_strjoinv(enc_search, sv);
  828             g_free(enc_search);
  829             g_strfreev(sv);
  830             goto done;
  831         }
  832     }
  833 
  834     /* XXX not sure about this heuristic */
  835     if (stat(url_in, &sb) == 0) {
  836         if (url_in[0] == '/')
  837             url_out = g_filename_to_uri(url_in, NULL, NULL);
  838         else {
  839             if (getcwd(cwd, PATH_MAX) != NULL) {
  840                 path = g_strdup_printf("%s" PS "%s", cwd,
  841                     url_in);
  842                 url_out = g_filename_to_uri(path, NULL, NULL);
  843                 g_free(path);
  844             }
  845         }
  846     } else
  847         url_out = g_strdup_printf("http://%s", url_in); /* guess http */
  848 done:
  849     DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
  850 
  851     return (url_out);
  852 }
  853 
  854 void
  855 set_normal_tab_meaning(struct tab *t)
  856 {
  857     if (t == NULL)
  858         return;
  859 
  860     t->xtp_meaning = XT_XTP_TAB_MEANING_NORMAL;
  861     if (t->session_key != NULL) {
  862         g_free(t->session_key);
  863         t->session_key = NULL;
  864     }
  865 }
  866 
  867 void
  868 load_uri(struct tab *t, const gchar *uri)
  869 {
  870     struct karg args;
  871     gchar       *newuri = NULL;
  872     int     i;
  873 
  874     if (uri == NULL)
  875         return;
  876 
  877     /* Strip leading spaces. */
  878     while (*uri && isspace((unsigned char)*uri))
  879         uri++;
  880 
  881     if (strlen(uri) == 0) {
  882         blank(t, NULL);
  883         return;
  884     }
  885 
  886     set_normal_tab_meaning(t);
  887 
  888     if (valid_url_type(uri)) {
  889         if ((newuri = guess_url_type(uri)) != NULL)
  890             uri = newuri;
  891         else
  892             uri = "";
  893     }
  894 
  895     /* clear :cert show host */
  896     if (t->about_cert_host) {
  897         g_free(t->about_cert_host);
  898         t->about_cert_host = NULL;
  899     }
  900 
  901     if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
  902         for (i = 0; i < about_list_size(); i++)
  903             if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name) &&
  904                 about_list[i].func != NULL) {
  905                 bzero(&args, sizeof args);
  906                 about_list[i].func(t, &args);
  907                 gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
  908                     FALSE);
  909                 goto done;
  910             }
  911         show_oops(t, "invalid about page");
  912         goto done;
  913     }
  914 
  915     /* remove old HTTPS cert chain (if any) */
  916     if (t->pem) {
  917         g_free(t->pem);
  918         t->pem = NULL;
  919     }
  920 
  921     set_status(t, "Loading: %s", (char *)uri);
  922     marks_clear(t);
  923     webkit_web_view_load_uri(t->wv, uri);
  924 done:
  925     if (newuri)
  926         g_free(newuri);
  927 }
  928 
  929 const gchar *
  930 get_uri(struct tab *t)
  931 {
  932     const gchar     *uri = NULL;
  933 
  934     if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED &&
  935         !t->download_requested)
  936         return (NULL);
  937     if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
  938         uri = webkit_web_view_get_uri(t->wv);
  939     else {
  940         /* use tmp_uri to make sure it is g_freed */
  941         if (t->tmp_uri)
  942             g_free(t->tmp_uri);
  943         t->tmp_uri = g_strdup_printf("%s%s", XT_URI_ABOUT,
  944             about_list[t->xtp_meaning].name);
  945         uri = t->tmp_uri;
  946     }
  947     return (uri);
  948 }
  949 
  950 const gchar *
  951 get_title(struct tab *t, bool window)
  952 {
  953     const gchar     *set = NULL, *title = NULL;
  954     WebKitLoadStatus    status;
  955 
  956     status = webkit_web_view_get_load_status(t->wv);
  957     if (status == WEBKIT_LOAD_PROVISIONAL ||
  958         (status == WEBKIT_LOAD_FAILED && !t->download_requested) ||
  959         t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
  960         goto notitle;
  961 
  962     title = webkit_web_view_get_title(t->wv);
  963     if ((set = title ? title : get_uri(t)))
  964         return set;
  965 
  966 notitle:
  967     set = window ? XT_NAME : "(untitled)";
  968 
  969     return set;
  970 }
  971 
  972 struct mime_type *
  973 find_mime_type(char *mime_type)
  974 {
  975     struct mime_type    *m, *def = NULL, *rv = NULL;
  976 
  977     TAILQ_FOREACH(m, &mtl, entry) {
  978         if (m->mt_default &&
  979             !strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
  980             def = m;
  981 
  982         if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
  983             rv = m;
  984             break;
  985         }
  986     }
  987 
  988     if (rv == NULL)
  989         rv = def;
  990 
  991     return (rv);
  992 }
  993 
  994 /*
  995  * This only escapes the & and < characters, as per the discussion found here:
  996  * http://lists.apple.com/archives/Webkitsdk-dev/2007/May/msg00056.html
  997  */
  998 gchar *
  999 html_escape(const char *val)
 1000 {
 1001     char            *s, *sp;
 1002     char            **sv;
 1003 
 1004     if (val == NULL)
 1005         return NULL;
 1006 
 1007     sv = g_strsplit(val, "&", -1);
 1008     s = g_strjoinv("&amp;", sv);
 1009     g_strfreev(sv);
 1010     sp = s;
 1011     sv = g_strsplit(val, "<", -1);
 1012     s = g_strjoinv("&lt;", sv);
 1013     g_strfreev(sv);
 1014     g_free(sp);
 1015     return (s);
 1016 }
 1017 
 1018 struct wl_entry *
 1019 wl_find_uri(const gchar *s, struct wl_list *wl)
 1020 {
 1021     int         i;
 1022     char            *ss;
 1023     struct wl_entry     *w;
 1024 
 1025     if (s == NULL || wl == NULL)
 1026         return (NULL);
 1027 
 1028     if (!strncmp(s, "http://", strlen("http://")))
 1029         s = &s[strlen("http://")];
 1030     else if (!strncmp(s, "https://", strlen("https://")))
 1031         s = &s[strlen("https://")];
 1032 
 1033     if (strlen(s) < 2)
 1034         return (NULL);
 1035 
 1036     for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
 1037         /* chop string at first slash */
 1038         if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
 1039             ss = g_strdup(s);
 1040             ss[i] = '\0';
 1041             w = wl_find(ss, wl);
 1042             g_free(ss);
 1043             return (w);
 1044         }
 1045 
 1046     return (NULL);
 1047 }
 1048 
 1049 char *
 1050 js_ref_to_string(JSContextRef context, JSValueRef ref)
 1051 {
 1052     char            *s = NULL;
 1053     size_t          l;
 1054     JSStringRef     jsref;
 1055 
 1056     jsref = JSValueToStringCopy(context, ref, NULL);
 1057     if (jsref == NULL)
 1058         return (NULL);
 1059 
 1060     l = JSStringGetMaximumUTF8CStringSize(jsref);
 1061     s = g_malloc(l);
 1062     if (s)
 1063         JSStringGetUTF8CString(jsref, s, l);
 1064     JSStringRelease(jsref);
 1065 
 1066     return (s);
 1067 }
 1068 
 1069 #define XT_JS_DONE      ("done;")
 1070 #define XT_JS_DONE_LEN      (strlen(XT_JS_DONE))
 1071 #define XT_JS_INSERT        ("insert;")
 1072 #define XT_JS_INSERT_LEN    (strlen(XT_JS_INSERT))
 1073 
 1074 int
 1075 run_script(struct tab *t, char *s)
 1076 {
 1077     JSGlobalContextRef  ctx;
 1078     WebKitWebFrame      *frame;
 1079     JSStringRef     str;
 1080     JSValueRef      val, exception;
 1081     char            *es;
 1082 
 1083     DNPRINTF(XT_D_JS, "%s: tab %d %s\n", __func__,
 1084         t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
 1085 
 1086     frame = webkit_web_view_get_main_frame(t->wv);
 1087     ctx = webkit_web_frame_get_global_context(frame);
 1088 
 1089     str = JSStringCreateWithUTF8CString(s);
 1090     val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
 1091         NULL, 0, &exception);
 1092     JSStringRelease(str);
 1093 
 1094     DNPRINTF(XT_D_JS, "%s: val %p\n", __func__, val);
 1095     if (val == NULL) {
 1096         es = js_ref_to_string(ctx, exception);
 1097         if (es) {
 1098             DNPRINTF(XT_D_JS, "%s: exception %s\n", __func__, es);
 1099             g_free(es);
 1100         }
 1101         return (1);
 1102     } else {
 1103         es = js_ref_to_string(ctx, val);
 1104 #if 0
 1105         /* return values */
 1106         if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
 1107             ; /* do nothing */
 1108         if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
 1109             ; /* do nothing */
 1110 #endif
 1111         if (es) {
 1112             DNPRINTF(XT_D_JS, "%s: val %s\n", __func__, es);
 1113             g_free(es);
 1114         }
 1115     }
 1116 
 1117     return (0);
 1118 }
 1119 
 1120 void
 1121 enable_hints(struct tab *t)
 1122 {
 1123     DNPRINTF(XT_D_JS, "%s: tab %d\n", __func__, t->tab_id);
 1124 
 1125     if (t->new_tab)
 1126         run_script(t, "hints.createHints('', 'F');");
 1127     else
 1128         run_script(t, "hints.createHints('', 'f');");
 1129     t->mode = XT_MODE_HINT;
 1130 }
 1131 
 1132 void
 1133 disable_hints(struct tab *t)
 1134 {
 1135     DNPRINTF(XT_D_JS, "%s: tab %d\n", __func__, t->tab_id);
 1136 
 1137     run_script(t, "hints.clearHints();");
 1138     t->mode = XT_MODE_COMMAND;
 1139     t->new_tab = 0;
 1140 }
 1141 
 1142 int
 1143 passthrough(struct tab *t, struct karg *args)
 1144 {
 1145     t->mode = XT_MODE_PASSTHROUGH;
 1146     return (0);
 1147 }
 1148 
 1149 int
 1150 modurl(struct tab *t, struct karg *args)
 1151 {
 1152     const gchar     *uri = NULL;
 1153     char            *u = NULL;
 1154 
 1155     /* XXX kind of a bad hack, but oh well */
 1156     if (gtk_widget_has_focus(t->uri_entry)) {
 1157         if ((uri = gtk_entry_get_text(GTK_ENTRY(t->uri_entry))) &&
 1158             (strlen(uri))) {
 1159             u = g_strdup_printf("www.%s.com", uri);
 1160             gtk_entry_set_text(GTK_ENTRY(t->uri_entry), u);
 1161             g_free(u);
 1162             activate_uri_entry_cb(t->uri_entry, t);
 1163         }
 1164     }
 1165     return (0);
 1166 }
 1167 
 1168 int
 1169 modsearchentry(struct tab *t, struct karg *args)
 1170 {
 1171     const gchar     *s = NULL;
 1172     struct tab      *tt;
 1173 
 1174     /* XXX kind of a bad hack (in honor of the modurl hack) */
 1175     if (gtk_widget_has_focus(t->search_entry)) {
 1176         if ((s = gtk_entry_get_text(GTK_ENTRY(t->search_entry))) &&
 1177             (strlen(s))) {
 1178             tt = create_new_tab(NULL, NULL, 1, -1);
 1179             gtk_entry_set_text(GTK_ENTRY(tt->search_entry), s);
 1180             activate_search_entry_cb(t->search_entry,tt);
 1181             gtk_entry_set_text(GTK_ENTRY(t->search_entry), "");
 1182         }
 1183     }
 1184     return (0);
 1185 }
 1186 
 1187 int
 1188 hint(struct tab *t, struct karg *args)
 1189 {
 1190 
 1191     DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
 1192 
 1193     if (t->mode == XT_MODE_HINT) {
 1194         if (args->i == XT_HINT_NEWTAB)
 1195             t->new_tab = 1;
 1196         enable_hints(t);
 1197     } else
 1198         disable_hints(t);
 1199 
 1200     return (0);
 1201 }
 1202 
 1203 void
 1204 apply_style(struct tab *t)
 1205 {
 1206     t->styled = 1;
 1207     g_object_set(G_OBJECT(t->settings),
 1208         "user-stylesheet-uri", t->stylesheet, (char *)NULL);
 1209 }
 1210 
 1211 void
 1212 remove_style(struct tab *t)
 1213 {
 1214     t->styled = 0;
 1215     g_object_set(G_OBJECT(t->settings),
 1216         "user-stylesheet-uri", NULL, (char *)NULL);
 1217 }
 1218 
 1219 int
 1220 userstyle_cmd(struct tab *t, struct karg *args)
 1221 {
 1222     char            script[PATH_MAX] = {'\0'};
 1223     char            *script_uri;
 1224     struct tab      *tt;
 1225 
 1226     DNPRINTF(XT_D_JS, "userstyle_cmd: tab %d\n", t->tab_id);
 1227 
 1228     if (args->s != NULL && strlen(args->s)) {
 1229         expand_tilde(script, sizeof script, args->s);
 1230         script_uri = g_filename_to_uri(script, NULL, NULL);
 1231     } else
 1232         script_uri = g_strdup(userstyle);
 1233 
 1234     if (script_uri == NULL)
 1235         return (1);
 1236 
 1237     switch (args->i) {
 1238     case XT_STYLE_CURRENT_TAB:
 1239         if (t->styled && !strcmp(script_uri, t->stylesheet))
 1240             remove_style(t);
 1241         else {
 1242             if (t->stylesheet)
 1243                 g_free(t->stylesheet);
 1244             t->stylesheet = g_strdup(script_uri);
 1245             apply_style(t);
 1246         }
 1247         break;
 1248     case XT_STYLE_GLOBAL:
 1249         if (userstyle_global && !strcmp(script_uri, t->stylesheet)) {
 1250             userstyle_global = 0;
 1251             TAILQ_FOREACH(tt, &tabs, entry)
 1252                 remove_style(tt);
 1253         } else {
 1254             userstyle_global = 1;
 1255 
 1256             /* need to save this stylesheet for new tabs */
 1257             if (stylesheet)
 1258                 g_free(stylesheet);
 1259             stylesheet = g_strdup(script_uri);
 1260 
 1261             TAILQ_FOREACH(tt, &tabs, entry) {
 1262                 if (tt->stylesheet)
 1263                     g_free(tt->stylesheet);
 1264                 tt->stylesheet = g_strdup(script_uri);
 1265                 apply_style(tt);
 1266             }
 1267         }
 1268         break;
 1269     }
 1270 
 1271     g_free(script_uri);
 1272 
 1273     return (0);
 1274 }
 1275 
 1276 int
 1277 quit(struct tab *t, struct karg *args)
 1278 {
 1279     if (save_global_history)
 1280         save_global_history_to_disk(t);
 1281 
 1282     gtk_main_quit();
 1283 
 1284     return (1);
 1285 }
 1286 
 1287 void
 1288 restore_sessions_list(void)
 1289 {
 1290     DIR     *sdir = NULL;
 1291     struct dirent   *dp = NULL;
 1292     struct session  *s;
 1293     int     reg;
 1294 
 1295     sdir = opendir(sessions_dir);
 1296     if (sdir) {
 1297         while ((dp = readdir(sdir)) != NULL) {
 1298 #if defined __MINGW32__
 1299             reg = 1; /* windows only has regular files */
 1300 #else
 1301             reg = dp->d_type == DT_REG;
 1302 #endif
 1303             if (dp && reg) {
 1304                 s = g_malloc(sizeof(struct session));
 1305                 s->name = g_strdup(dp->d_name);
 1306                 TAILQ_INSERT_TAIL(&sessions, s, entry);
 1307             }
 1308         }
 1309         closedir(sdir);
 1310     }
 1311 }
 1312 
 1313 int
 1314 open_tabs(struct tab *t, struct karg *a)
 1315 {
 1316     char        file[PATH_MAX];
 1317     FILE        *f = NULL;
 1318     char        *uri = NULL;
 1319     int     rv = 1;
 1320     struct tab  *ti, *tt;
 1321 
 1322     if (a == NULL)
 1323         goto done;
 1324 
 1325     ti = TAILQ_LAST(&tabs, tab_list);
 1326 
 1327     snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, a->s);
 1328     if ((f = fopen(file, "r")) == NULL)
 1329         goto done;
 1330 
 1331     for (;;) {
 1332         if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL) {
 1333             if (feof(f) || ferror(f))
 1334                 break;
 1335         } else {
 1336             /* retrieve session name */
 1337             if (g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
 1338                 strlcpy(named_session,
 1339                     &uri[strlen(XT_SAVE_SESSION_ID)],
 1340                     sizeof named_session);
 1341                 continue;
 1342             }
 1343 
 1344             if (strlen(uri))
 1345                 create_new_tab(uri, NULL, 1, -1);
 1346 
 1347             free(uri);
 1348         }
 1349     }
 1350 
 1351     /* close open tabs */
 1352     if (a->i == XT_SES_CLOSETABS && ti != NULL) {
 1353         for (;;) {
 1354             tt = TAILQ_FIRST(&tabs);
 1355             if (tt == NULL)
 1356                 break;
 1357             if (tt != ti) {
 1358                 delete_tab(tt);
 1359                 continue;
 1360             }
 1361             delete_tab(tt);
 1362             break;
 1363         }
 1364     }
 1365 
 1366     rv = 0;
 1367 done:
 1368     if (f)
 1369         fclose(f);
 1370 
 1371     return (rv);
 1372 }
 1373 
 1374 int
 1375 restore_saved_tabs(void)
 1376 {
 1377     char        file[PATH_MAX];
 1378     int     unlink_file = 0;
 1379     struct stat sb;
 1380     struct karg a;
 1381     int     rv = 0;
 1382 
 1383     snprintf(file, sizeof file, "%s" PS "%s",
 1384         sessions_dir, XT_RESTART_TABS_FILE);
 1385     if (stat(file, &sb) == -1)
 1386         a.s = XT_SAVED_TABS_FILE;
 1387     else {
 1388         unlink_file = 1;
 1389         a.s = XT_RESTART_TABS_FILE;
 1390     }
 1391 
 1392     a.i = XT_SES_DONOTHING;
 1393     rv = open_tabs(NULL, &a);
 1394 
 1395     if (unlink_file)
 1396         unlink(file);
 1397 
 1398     return (rv);
 1399 }
 1400 
 1401 int
 1402 save_tabs(struct tab *t, struct karg *a)
 1403 {
 1404     char            file[PATH_MAX];
 1405     FILE            *f;
 1406     int         num_tabs = 0, i;
 1407     struct tab      **stabs = NULL;
 1408 
 1409     /* tab may be null here */
 1410 
 1411     if (a == NULL)
 1412         return (1);
 1413     if (a->s == NULL)
 1414         snprintf(file, sizeof file, "%s" PS "%s",
 1415             sessions_dir, named_session);
 1416     else
 1417         snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, a->s);
 1418 
 1419     if ((f = fopen(file, "w")) == NULL) {
 1420         show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
 1421         return (1);
 1422     }
 1423 
 1424     /* save session name */
 1425     fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
 1426 
 1427     /* Save tabs, in the order they are arranged in the notebook. */
 1428     num_tabs = sort_tabs_by_page_num(&stabs);
 1429 
 1430     for (i = 0; i < num_tabs; i++)
 1431         if (stabs[i]) {
 1432             if (get_uri(stabs[i]) != NULL)
 1433                 fprintf(f, "%s\n", get_uri(stabs[i]));
 1434             else if (gtk_entry_get_text(GTK_ENTRY(
 1435                 stabs[i]->uri_entry)))
 1436                 fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
 1437                     stabs[i]->uri_entry)));
 1438         }
 1439 
 1440     g_free(stabs);
 1441 
 1442     /* try and make sure this gets to disk NOW. XXX Backup first? */
 1443     if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
 1444         show_oops(t, "May not have managed to save session: %s",
 1445             strerror(errno));
 1446     }
 1447 
 1448     fclose(f);
 1449 
 1450     return (0);
 1451 }
 1452 
 1453 int
 1454 save_tabs_and_quit(struct tab *t, struct karg *args)
 1455 {
 1456     struct karg     a;
 1457 
 1458     a.s = NULL;
 1459     save_tabs(t, &a);
 1460     quit(t, NULL);
 1461 
 1462     return (1);
 1463 }
 1464 
 1465 void
 1466 expand_tilde(char *path, size_t len, const char *s)
 1467 {
 1468     struct passwd       *pwd;
 1469     int         i;
 1470     char            user[LOGIN_NAME_MAX];
 1471     const char      *sc = s;
 1472 
 1473     if (path == NULL || s == NULL)
 1474         errx(1, "expand_tilde");
 1475 
 1476     if (s[0] != '~') {
 1477         strlcpy(path, sc, len);
 1478         return;
 1479     }
 1480 
 1481     ++s;
 1482     for (i = 0; s[i] != PSC && s[i] != '\0'; ++i)
 1483         user[i] = s[i];
 1484     user[i] = '\0';
 1485     s = &s[i];
 1486 
 1487     pwd = strlen(user) == 0 ? getpwuid(getuid()) : getpwnam(user);
 1488     if (pwd == NULL)
 1489         strlcpy(path, sc, len);
 1490     else
 1491         snprintf(path, len, "%s%s", pwd->pw_dir, s);
 1492 }
 1493 
 1494 int
 1495 run_page_script(struct tab *t, struct karg *args)
 1496 {
 1497     const gchar     *uri;
 1498     char            *tmp, script[PATH_MAX];
 1499     char            *sv[3];
 1500 
 1501     tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
 1502     if (tmp[0] == '\0') {
 1503         show_oops(t, "no script specified");
 1504         return (1);
 1505     }
 1506 
 1507     if ((uri = get_uri(t)) == NULL) {
 1508         show_oops(t, "tab is empty, not running script");
 1509         return (1);
 1510     }
 1511 
 1512     expand_tilde(script, sizeof script, tmp);
 1513 
 1514     sv[0] = script;
 1515     sv[1] = (char *)uri;
 1516     sv[2] = NULL;
 1517     if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
 1518         NULL, NULL)) {
 1519         show_oops(t, "%s: could not spawn process: %s %s", __func__,
 1520             sv[0], sv[1]);
 1521         return (1);
 1522     } else
 1523         show_oops(t, "running: %s %s", sv[0], sv[1]);
 1524 
 1525     return (0);
 1526 }
 1527 
 1528 int
 1529 yank_uri(struct tab *t, struct karg *args)
 1530 {
 1531     const gchar     *uri;
 1532     GtkClipboard        *clipboard, *primary;
 1533 
 1534     if ((uri = get_uri(t)) == NULL)
 1535         return (1);
 1536 
 1537     primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
 1538     clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
 1539     gtk_clipboard_set_text(primary, uri, -1);
 1540     gtk_clipboard_set_text(clipboard, uri, -1);
 1541 
 1542     return (0);
 1543 }
 1544 
 1545 int
 1546 paste_uri(struct tab *t, struct karg *args)
 1547 {
 1548     GtkClipboard        *clipboard, *primary;
 1549     gchar           *c = NULL, *p = NULL, *uri;
 1550     int         i;
 1551 
 1552     clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
 1553     primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
 1554     c = gtk_clipboard_wait_for_text(clipboard);
 1555     p = gtk_clipboard_wait_for_text(primary);
 1556 
 1557     if (c || p) {
 1558 #ifdef __MINGW32__
 1559         /* Windows try clipboard first */
 1560         uri = c ? c : p;
 1561 #else
 1562         /* UNIX try primary first */
 1563         uri = p ? p : c;
 1564 #endif
 1565         /* replace all newlines with spaces */
 1566         for (i = 0; uri[i] != '\0'; ++i)
 1567             if (uri[i] == '\n')
 1568                 uri[i] = ' ';
 1569 
 1570         while (*uri && isspace((unsigned char)*uri))
 1571             uri++;
 1572         if (strlen(uri) == 0) {
 1573             show_oops(t, "empty paste buffer");
 1574             goto done;
 1575         }
 1576         if (guess_search == 0 && valid_url_type(uri)) {
 1577             /* we can be clever and paste this in search box */
 1578             show_oops(t, "not a valid URL");
 1579             goto done;
 1580         }
 1581 
 1582         if (args->i == XT_PASTE_CURRENT_TAB)
 1583             load_uri(t, uri);
 1584         else if (args->i == XT_PASTE_NEW_TAB)
 1585             create_new_tab(uri, NULL, 1, -1);
 1586     }
 1587 
 1588 done:
 1589     if (c)
 1590         g_free(c);
 1591     if (p)
 1592         g_free(p);
 1593 
 1594     return (0);
 1595 }
 1596 
 1597 void
 1598 js_toggle_cb(GtkWidget *w, struct tab *t)
 1599 {
 1600     struct karg     a;
 1601     int         es, set;
 1602 
 1603     g_object_get(G_OBJECT(t->settings),
 1604         "enable-scripts", &es, (char *)NULL);
 1605     es = !es;
 1606     if (es)
 1607         set = XT_WL_ENABLE;
 1608     else
 1609         set = XT_WL_DISABLE;
 1610 
 1611     a.i = set | XT_WL_TOPLEVEL;
 1612     toggle_pl(t, &a);
 1613 
 1614     a.i = set | XT_WL_TOPLEVEL;
 1615     toggle_cwl(t, &a);
 1616 
 1617     a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
 1618     toggle_js(t, &a);
 1619 }
 1620 
 1621 void
 1622 proxy_toggle_cb(GtkWidget *w, struct tab *t)
 1623 {
 1624     struct karg     args = {0};
 1625 
 1626     args.i = XT_PRXY_TOGGLE;
 1627     proxy_cmd(t, &args);
 1628 }
 1629 
 1630 int
 1631 toggle_src(struct tab *t, struct karg *args)
 1632 {
 1633     gboolean        mode;
 1634 
 1635     if (t == NULL)
 1636         return (0);
 1637 
 1638     mode = webkit_web_view_get_view_source_mode(t->wv);
 1639     webkit_web_view_set_view_source_mode(t->wv, !mode);
 1640     webkit_web_view_reload(t->wv);
 1641 
 1642     return (0);
 1643 }
 1644 
 1645 void
 1646 focus_webview(struct tab *t)
 1647 {
 1648     if (t == NULL)
 1649         return;
 1650 
 1651     /* only grab focus if we are visible */
 1652     if (gtk_notebook_get_current_page(notebook) == t->tab_id)
 1653         gtk_widget_grab_focus(GTK_WIDGET(t->wv));
 1654 }
 1655 
 1656 int
 1657 focus(struct tab *t, struct karg *args)
 1658 {
 1659     if (t == NULL || args == NULL)
 1660         return (1);
 1661 
 1662     if (show_url == 0)
 1663         return (0);
 1664 
 1665     if (args->i == XT_FOCUS_URI)
 1666         gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
 1667     else if (args->i == XT_FOCUS_SEARCH)
 1668         gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
 1669 
 1670     return (0);
 1671 }
 1672 
 1673 void
 1674 free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
 1675 {
 1676     int         i;
 1677 
 1678     for (i = 0; i < cert_count; i++)
 1679         gnutls_x509_crt_deinit(certs[i]);
 1680     gnutls_free(certs);
 1681 }
 1682 
 1683 #if GTK_CHECK_VERSION(3, 0, 0)
 1684 void
 1685 statusbar_modify_attr(struct tab *t, const char *css_name)
 1686 {
 1687     gtk_widget_set_name(t->sbe.ebox, css_name);
 1688 }
 1689 #else
 1690 void
 1691 statusbar_modify_attr(struct tab *t, const char *text, const char *base)
 1692 {
 1693     GdkColor                c_text, c_base;
 1694 
 1695     gdk_color_parse(text, &c_text);
 1696     gdk_color_parse(base, &c_base);
 1697 
 1698     gtk_widget_modify_bg(t->sbe.ebox, GTK_STATE_NORMAL, &c_base);
 1699     gtk_widget_modify_base(t->sbe.uri, GTK_STATE_NORMAL, &c_base);
 1700     gtk_widget_modify_text(t->sbe.uri, GTK_STATE_NORMAL, &c_text);
 1701 }
 1702 #endif
 1703 
 1704 void
 1705 save_certs(struct tab *t, gnutls_x509_crt_t *certs,
 1706     size_t cert_count, const char *domain, const char *dir)
 1707 {
 1708     size_t          cert_buf_sz;
 1709     char            file[PATH_MAX];
 1710     char            *cert_buf = NULL;
 1711     int         rv;
 1712     int         i;
 1713     FILE            *f;
 1714 
 1715     if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
 1716         return;
 1717 
 1718     snprintf(file, sizeof file, "%s" PS "%s", dir, domain);
 1719     if ((f = fopen(file, "w")) == NULL) {
 1720         show_oops(t, "Can't create cert file %s %s",
 1721             file, strerror(errno));
 1722         return;
 1723     }
 1724 
 1725     for (i = 0; i < cert_count; i++) {
 1726         /*
 1727          * Because we support old crap and can't use 
 1728          * gnutls_x509_crt_export2(), we intentionally use an empty
 1729          * buffer and then make a second call with the known size
 1730          * needed.
 1731          */
 1732         cert_buf_sz = 0;
 1733         gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
 1734             NULL, &cert_buf_sz);
 1735         if (cert_buf_sz == 0) {
 1736             show_oops(t, "no certs found");
 1737             goto done;
 1738         }
 1739         cert_buf = gnutls_malloc(cert_buf_sz * sizeof(char));
 1740         if (cert_buf == NULL) {
 1741             show_oops(t, "gnutls_x509_crt_export failed");
 1742             goto done;
 1743         }
 1744         rv = gnutls_x509_crt_export(certs[i],
 1745             GNUTLS_X509_FMT_PEM, cert_buf, &cert_buf_sz);
 1746         if (rv != 0) {
 1747             show_oops(t, "gnutls_x509_crt_export failure: %s",
 1748                 gnutls_strerror(rv));
 1749             goto done;
 1750         }
 1751         cert_buf[cert_buf_sz] = '\0';
 1752         if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
 1753             show_oops(t, "Can't write certs: %s", strerror(errno));
 1754             goto done;
 1755         }
 1756         gnutls_free(cert_buf);
 1757         cert_buf = NULL;
 1758     }
 1759 
 1760 done:
 1761     if (cert_buf)
 1762         gnutls_free(cert_buf);
 1763     fclose(f);
 1764 }
 1765 
 1766 enum cert_trust {
 1767     CERT_LOCAL,
 1768     CERT_TRUSTED,
 1769     CERT_UNTRUSTED,
 1770     CERT_BAD
 1771 };
 1772 
 1773 gnutls_x509_crt_t *
 1774 get_local_cert_chain(const char *uri, size_t *ncerts, const char **error_str,
 1775     const char *dir)
 1776 {
 1777     SoupURI         *su;
 1778     unsigned char       cert_buf[64 * 1024] = {0};
 1779     gnutls_datum_t      data;
 1780     unsigned int        max_certs = XT_MAX_CERTS;
 1781     int         bytes_read;
 1782     char            file[PATH_MAX];
 1783     FILE            *f;
 1784     gnutls_x509_crt_t   *certs;
 1785 
 1786     if ((su = soup_uri_new(uri)) == NULL) {
 1787         *error_str = "Invalid URI";
 1788         return (NULL);
 1789     }
 1790 
 1791     snprintf(file, sizeof file, "%s" PS "%s", dir, su->host);
 1792     soup_uri_free(su);
 1793     if ((f = fopen(file, "r")) == NULL) {
 1794         *error_str = "Could not read local cert";
 1795         return (NULL);
 1796     }
 1797 
 1798     bytes_read = fread(cert_buf, sizeof *cert_buf, sizeof cert_buf, f);
 1799     if (bytes_read == 0) {
 1800         *error_str = "Could not read local cert";
 1801         return (NULL);
 1802     }
 1803 
 1804     certs = gnutls_malloc(sizeof(*certs) * max_certs);
 1805     if (certs == NULL) {
 1806         *error_str = "Error allocating memory";
 1807         return (NULL);
 1808     }
 1809     data.data = cert_buf;
 1810     data.size = bytes_read;
 1811     if (gnutls_x509_crt_list_import(certs, &max_certs, &data,
 1812         GNUTLS_X509_FMT_PEM, 0) < 0) {
 1813         gnutls_free(certs);
 1814         *error_str = "Error reading local cert chain";
 1815         return (NULL);
 1816     }
 1817 
 1818     *ncerts = max_certs;
 1819     return (certs);
 1820 }
 1821 
 1822 /*
 1823  * This uses the pem-encoded cert chain saved in t->pem instead of
 1824  * grabbing the remote cert.  We save it beforehand and read it here
 1825  * so as to not open a side channel that ignores proxy settings.
 1826  */
 1827 gnutls_x509_crt_t *
 1828 get_chain_for_pem(char *pem, size_t *ncerts, const char **error_str)
 1829 {
 1830     gnutls_datum_t      data;
 1831     unsigned int        max_certs = XT_MAX_CERTS;
 1832     gnutls_x509_crt_t   *certs;
 1833 
 1834     if (pem == NULL) {
 1835         *error_str = "Error reading remote cert chain";
 1836         return (NULL);
 1837     }
 1838 
 1839     certs = gnutls_malloc(sizeof(*certs) * max_certs);
 1840     if (certs == NULL) {
 1841         *error_str = "Error allocating memory";
 1842         return (NULL);
 1843     }
 1844     data.data = (unsigned char *)pem;
 1845     data.size = strlen(pem);
 1846     if (gnutls_x509_crt_list_import(certs, &max_certs, &data,
 1847         GNUTLS_X509_FMT_PEM, 0) < 0) {
 1848         gnutls_free(certs);
 1849         *error_str = "Error reading remote cert chain";
 1850         return (NULL);
 1851     }
 1852 
 1853     *ncerts = max_certs;
 1854     return (certs);
 1855 }
 1856 
 1857 
 1858 int
 1859 cert_cmd(struct tab *t, struct karg *args)
 1860 {
 1861     const gchar     *uri, *error_str = NULL;
 1862     size_t          cert_count;
 1863     gnutls_x509_crt_t   *certs;
 1864     SoupURI         *su;
 1865     char            *host;
 1866 #if !GTK_CHECK_VERSION(3, 0, 0)
 1867     GdkColor        color;
 1868 #endif
 1869 
 1870     if (t == NULL)
 1871         return (1);
 1872 
 1873     if (args->s != NULL) {
 1874         uri = args->s;
 1875     } else if ((uri = get_uri(t)) == NULL) {
 1876         show_oops(t, "Invalid URI");
 1877         return (1);
 1878     }
 1879     if ((su = soup_uri_new(uri)) == NULL) {
 1880         show_oops(t, "Invalid URI");
 1881         return (1);
 1882     }
 1883 
 1884     /*
 1885      * if we're only showing the local certs, don't open a socket and get
 1886      * the remote certs
 1887      */
 1888     if (args->i & XT_SHOW && args->i & XT_CACHE) {
 1889         certs = get_local_cert_chain(uri, &cert_count, &error_str,
 1890             certs_cache_dir);
 1891         if (error_str == NULL) {
 1892             t->about_cert_host = g_strdup(su->host);
 1893             show_certs(t, certs, cert_count, "Certificate Chain");
 1894             free_connection_certs(certs, cert_count);
 1895         } else {
 1896             show_oops(t, "%s", error_str);
 1897             soup_uri_free(su);
 1898             return (1);
 1899         }
 1900         soup_uri_free(su);
 1901         return (0);
 1902     }
 1903 
 1904     certs = get_chain_for_pem(t->pem, &cert_count, &error_str);
 1905     if (error_str)
 1906         goto done;
 1907 
 1908     if (args->i & XT_SHOW) {
 1909         t->about_cert_host = g_strdup(su->host);
 1910         show_certs(t, certs, cert_count, "Certificate Chain");
 1911     } else if (args->i & XT_SAVE) {
 1912         host = t->about_cert_host ? t->about_cert_host : su->host;
 1913         save_certs(t, certs, cert_count, host, certs_dir);
 1914 #if GTK_CHECK_VERSION(3, 0, 0)
 1915         gtk_widget_set_name(t->uri_entry, XT_CSS_BLUE);
 1916         statusbar_modify_attr(t, XT_CSS_BLUE);
 1917 #else
 1918         gdk_color_parse(XT_COLOR_BLUE, &color);
 1919         gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
 1920         statusbar_modify_attr(t, XT_COLOR_BLACK, XT_COLOR_BLUE);
 1921 #endif
 1922     } else if (args->i & XT_CACHE)
 1923         save_certs(t, certs, cert_count, su->host, certs_cache_dir);
 1924 
 1925     free_connection_certs(certs, cert_count);
 1926 
 1927 done:
 1928     soup_uri_free(su);
 1929 
 1930     if (error_str && strlen(error_str)) {
 1931         show_oops(t, "%s", error_str);
 1932         return (1);
 1933     }
 1934     return (0);
 1935 }
 1936 
 1937 /*
 1938  * Checks whether the remote cert is identical to the local saved
 1939  * cert.  Returns CERT_LOCAL if unchanged, CERT_UNTRUSTED if local
 1940  * cert does not exist, and CERT_BAD if different.
 1941  *
 1942  * Saves entire cert chain in pem encoding to chain for it to be
 1943  * cached later, if needed.
 1944  */
 1945 enum cert_trust
 1946 check_local_certs(const char *file, GTlsCertificate *cert, char **chain)
 1947 {
 1948     char            r_cert_buf[64 * 1024];
 1949     FILE            *f;
 1950     GTlsCertificate     *tmpcert = NULL;
 1951     GTlsCertificate     *issuer = NULL;
 1952     char            *pem = NULL;
 1953     char            *tmp = NULL;
 1954     enum cert_trust     rv = CERT_LOCAL;
 1955     int         i;
 1956 
 1957     if ((f = fopen(file, "r")) == NULL) {
 1958         /* no local cert to check */
 1959         rv = CERT_UNTRUSTED;
 1960     }
 1961 
 1962     for (i = 0;;) {
 1963         if (i == 0) {
 1964             g_object_get(G_OBJECT(cert), "certificate-pem", &pem,
 1965                 NULL);
 1966             g_object_get(G_OBJECT(cert), "issuer", &issuer, NULL);
 1967         } else {
 1968             if (issuer == NULL)
 1969                 break;
 1970             g_object_get(G_OBJECT(tmpcert), "issuer", &issuer,
 1971                 NULL);
 1972             if (issuer == NULL)
 1973                 break;
 1974 
 1975             g_object_get(G_OBJECT(tmpcert), "certificate-pem", &pem,
 1976                 NULL);
 1977         }
 1978         i++;
 1979 
 1980         if (tmpcert != NULL)
 1981             g_object_unref(G_OBJECT(tmpcert));
 1982         tmpcert = issuer;
 1983         if (f) {
 1984             if (fread(r_cert_buf, strlen(pem), 1, f) != 1 && !feof(f))
 1985                 rv = CERT_BAD;
 1986             if (bcmp(r_cert_buf, pem, strlen(pem)))
 1987                 rv = CERT_BAD;
 1988         }
 1989         tmp = g_strdup_printf("%s%s", *chain, pem);
 1990         g_free(pem);
 1991         g_free(*chain);
 1992         *chain = tmp;
 1993     }
 1994 
 1995     if (issuer != NULL)
 1996         g_object_unref(G_OBJECT(issuer));
 1997     if (f != NULL)
 1998         fclose(f);
 1999     return (rv);
 2000 }
 2001 
 2002 int
 2003 check_cert_changes(struct tab *t, GTlsCertificate *cert, const char *file, const char *uri)
 2004 {
 2005     SoupURI         *soupuri = NULL;
 2006     struct karg     args = {0};
 2007     struct wl_entry     *w = NULL;
 2008     char            *chain = NULL;
 2009     int         ret = 0;
 2010 
 2011     chain = g_strdup("");
 2012     switch(check_local_certs(file, cert, &chain)) {
 2013     case CERT_LOCAL:
 2014         /* The cached certificate is identical */
 2015         break;
 2016     case CERT_TRUSTED:  /* FALLTHROUGH */
 2017     case CERT_UNTRUSTED:
 2018         /* cache new certificate */
 2019         args.i = XT_CACHE;
 2020         cert_cmd(t, &args);
 2021         break;
 2022     case CERT_BAD:
 2023         if ((soupuri = soup_uri_new(uri)) == NULL ||
 2024             soupuri->host == NULL)
 2025             break;
 2026         if ((w = wl_find(soupuri->host, &svil)) != NULL)
 2027             break;
 2028         t->xtp_meaning = XT_XTP_TAB_MEANING_SV;
 2029         args.s = g_strdup((char *)uri);
 2030         xtp_page_sv(t, &args);
 2031         ret = 1;
 2032         break;
 2033     }
 2034 
 2035     if (soupuri)
 2036         soup_uri_free(soupuri);
 2037     if (chain)
 2038         g_free(chain);
 2039     return (ret);
 2040 }
 2041 
 2042 int
 2043 remove_cookie(int index)
 2044 {
 2045     int         i, rv = 1;
 2046     GSList          *cf;
 2047     SoupCookie      *c;
 2048 
 2049     DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
 2050 
 2051     cf = soup_cookie_jar_all_cookies(s_cookiejar);
 2052 
 2053     for (i = 1; cf; cf = cf->next, i++) {
 2054         if (i != index)
 2055             continue;
 2056         c = cf->data;
 2057         print_cookie("remove cookie", c);
 2058         soup_cookie_jar_delete_cookie(s_cookiejar, c);
 2059         rv = 0;
 2060         break;
 2061     }
 2062 
 2063     soup_cookies_free(cf);
 2064 
 2065     return (rv);
 2066 }
 2067 
 2068 int
 2069 remove_cookie_domain(int domain_id)
 2070 {
 2071     int         domain_count, rv = 1;
 2072     GSList          *cf;
 2073     SoupCookie      *c;
 2074     char *last_domain;
 2075 
 2076     DNPRINTF(XT_D_COOKIE, "remove_cookie_domain: %d\n", domain_id);
 2077 
 2078     last_domain = "";
 2079     cf = soup_cookie_jar_all_cookies(s_cookiejar);
 2080 
 2081     for (domain_count = 0; cf; cf = cf->next) {
 2082         c = cf->data;
 2083 
 2084         if (strcmp(last_domain, c->domain) != 0) {
 2085             domain_count++;
 2086             last_domain = c->domain;
 2087         }
 2088 
 2089         if (domain_count < domain_id)
 2090             continue;
 2091         else if (domain_count > domain_id)
 2092             break;
 2093 
 2094         print_cookie("remove cookie", c);
 2095         soup_cookie_jar_delete_cookie(s_cookiejar, c);
 2096         rv = 0;
 2097     }
 2098 
 2099     soup_cookies_free(cf);
 2100 
 2101     return (rv);
 2102 }
 2103 
 2104 int
 2105 remove_cookie_all()
 2106 {
 2107     int         rv = 1;
 2108     GSList          *cf;
 2109     SoupCookie      *c;
 2110 
 2111     DNPRINTF(XT_D_COOKIE, "remove_cookie_all\n");
 2112 
 2113     cf = soup_cookie_jar_all_cookies(s_cookiejar);
 2114 
 2115     for (; cf; cf = cf->next) {
 2116         c = cf->data;
 2117 
 2118         print_cookie("remove cookie", c);
 2119         soup_cookie_jar_delete_cookie(s_cookiejar, c);
 2120         rv = 0;
 2121     }
 2122 
 2123     soup_cookies_free(cf);
 2124 
 2125     return (rv);
 2126 }
 2127 
 2128 int
 2129 toplevel_cmd(struct tab *t, struct karg *args)
 2130 {
 2131     js_toggle_cb(t->js_toggle, t);
 2132 
 2133     return (0);
 2134 }
 2135 
 2136 int
 2137 can_go_back_for_real(struct tab *t)
 2138 {
 2139     int         i;
 2140     WebKitWebHistoryItem    *item;
 2141     const gchar     *uri;
 2142 
 2143     if (t == NULL)
 2144         return (FALSE);
 2145 
 2146     if (t->item != NULL)
 2147         return (TRUE);
 2148 
 2149     /* rely on webkit to make sure we can go backward when on an about page */
 2150     uri = get_uri(t);
 2151     if (uri == NULL || g_str_has_prefix(uri, "about:") ||
 2152         g_str_has_prefix(uri, "xxxt://"))
 2153         return (webkit_web_view_can_go_back(t->wv));
 2154 
 2155     /* the back/forward list is stupid so help determine if we can go back */
 2156     for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
 2157         item != NULL;
 2158         i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
 2159         if (strcmp(webkit_web_history_item_get_uri(item), uri))
 2160             return (TRUE);
 2161     }
 2162 
 2163     return (FALSE);
 2164 }
 2165 
 2166 int
 2167 can_go_forward_for_real(struct tab *t)
 2168 {
 2169     int         i;
 2170     WebKitWebHistoryItem    *item;
 2171     const gchar     *uri;
 2172 
 2173     if (t == NULL)
 2174         return (FALSE);
 2175 
 2176     /* rely on webkit to make sure we can go forward when on an about page */
 2177     uri = get_uri(t);
 2178     if (uri == NULL || g_str_has_prefix(uri, "about:") ||
 2179         g_str_has_prefix(uri, "xxxt://"))
 2180         return (webkit_web_view_can_go_forward(t->wv));
 2181 
 2182     /* the back/forwars list is stupid so help selecting a different item */
 2183     for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
 2184         item != NULL;
 2185         i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
 2186         if (strcmp(webkit_web_history_item_get_uri(item), uri))
 2187             return (TRUE);
 2188     }
 2189 
 2190     return (FALSE);
 2191 }
 2192 
 2193 void
 2194 go_back_for_real(struct tab *t)
 2195 {
 2196     int         i;
 2197     WebKitWebHistoryItem    *item;
 2198     const gchar     *uri;
 2199 
 2200     if (t == NULL)
 2201         return;
 2202 
 2203     uri = get_uri(t);
 2204     if (uri == NULL || g_str_has_prefix(uri, "about:") ||
 2205         g_str_has_prefix(uri, "xxxt://")) {
 2206         webkit_web_view_go_back(t->wv);
 2207         return;
 2208     }
 2209     /* the back/forwars list is stupid so help selecting a different item */
 2210     for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
 2211         item != NULL;
 2212         i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
 2213         if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
 2214             webkit_web_view_go_to_back_forward_item(t->wv, item);
 2215             break;
 2216         }
 2217     }
 2218 }
 2219 
 2220 void
 2221 go_forward_for_real(struct tab *t)
 2222 {
 2223     int         i;
 2224     WebKitWebHistoryItem    *item;
 2225     const gchar     *uri;
 2226 
 2227     if (t == NULL)
 2228         return;
 2229 
 2230     uri = get_uri(t);
 2231     if (uri == NULL || g_str_has_prefix(uri, "about:") ||
 2232         g_str_has_prefix(uri, "xxxt://")) {
 2233         webkit_web_view_go_forward(t->wv);
 2234         return;
 2235     }
 2236     /* the back/forwars list is stupid so help selecting a different item */
 2237     for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
 2238         item != NULL;
 2239         i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
 2240         if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
 2241             webkit_web_view_go_to_back_forward_item(t->wv, item);
 2242             break;
 2243         }
 2244     }
 2245 }
 2246 
 2247 int
 2248 navaction(struct tab *t, struct karg *args)
 2249 {
 2250     WebKitWebHistoryItem    *item = NULL;
 2251     WebKitWebFrame      *frame;
 2252 
 2253     DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
 2254         t->tab_id, args->i);
 2255 
 2256     hide_oops(t);
 2257     set_normal_tab_meaning(t);
 2258     if (t->item) {
 2259         if (args->i == XT_NAV_BACK)
 2260             item = webkit_web_back_forward_list_get_current_item(t->bfl);
 2261         else
 2262             item = webkit_web_back_forward_list_get_forward_item(t->bfl);
 2263     }
 2264 
 2265     switch (args->i) {
 2266     case XT_NAV_BACK:
 2267         if (t->item) {
 2268             if (item == NULL)
 2269                 return (XT_CB_PASSTHROUGH);
 2270             webkit_web_view_go_to_back_forward_item(t->wv, item);
 2271             t->item = NULL;
 2272             return (XT_CB_PASSTHROUGH);
 2273         }
 2274         marks_clear(t);
 2275         go_back_for_real(t);
 2276         break;
 2277     case XT_NAV_FORWARD:
 2278         if (t->item) {
 2279             if (item == NULL)
 2280                 return (XT_CB_PASSTHROUGH);
 2281             webkit_web_view_go_to_back_forward_item(t->wv, item);
 2282             t->item = NULL;
 2283             return (XT_CB_PASSTHROUGH);
 2284         }
 2285         marks_clear(t);
 2286         go_forward_for_real(t);
 2287         break;
 2288     case XT_NAV_RELOAD:
 2289         frame = webkit_web_view_get_main_frame(t->wv);
 2290         webkit_web_frame_reload(frame);
 2291         break;
 2292     case XT_NAV_STOP:
 2293         frame = webkit_web_view_get_main_frame(t->wv);
 2294         webkit_web_frame_stop_loading(frame);
 2295         break;
 2296     }
 2297     return (XT_CB_PASSTHROUGH);
 2298 }
 2299 
 2300 int
 2301 move(struct tab *t, struct karg *args)
 2302 {
 2303     GtkAdjustment       *adjust;
 2304     double          pi, si, pos, ps, upper, lower, max;
 2305     double          percent;
 2306 
 2307     switch (args->i) {
 2308     case XT_MOVE_DOWN:
 2309     case XT_MOVE_UP:
 2310     case XT_MOVE_BOTTOM:
 2311     case XT_MOVE_TOP:
 2312     case XT_MOVE_PAGEDOWN:
 2313     case XT_MOVE_PAGEUP:
 2314     case XT_MOVE_HALFDOWN:
 2315     case XT_MOVE_HALFUP:
 2316     case XT_MOVE_PERCENT:
 2317     case XT_MOVE_CENTER:
 2318         adjust = t->adjust_v;
 2319         break;
 2320     default:
 2321         adjust = t->adjust_h;
 2322         break;
 2323     }
 2324 
 2325     pos = gtk_adjustment_get_value(adjust);
 2326     ps = gtk_adjustment_get_page_size(adjust);
 2327     upper = gtk_adjustment_get_upper(adjust);
 2328     lower = gtk_adjustment_get_lower(adjust);
 2329     si = gtk_adjustment_get_step_increment(adjust);
 2330     pi = gtk_adjustment_get_page_increment(adjust);
 2331     max = upper - ps;
 2332 
 2333     DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
 2334         "max %f si %f pi %f\n",
 2335         args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
 2336         pos, ps, upper, lower, max, si, pi);
 2337 
 2338     switch (args->i) {
 2339     case XT_MOVE_DOWN:
 2340     case XT_MOVE_RIGHT:
 2341         pos += si;
 2342         gtk_adjustment_set_value(adjust, MIN(pos, max));
 2343         break;
 2344     case XT_MOVE_UP:
 2345     case XT_MOVE_LEFT:
 2346         pos -= si;
 2347         gtk_adjustment_set_value(adjust, MAX(pos, lower));
 2348         break;
 2349     case XT_MOVE_BOTTOM:
 2350     case XT_MOVE_FARRIGHT:
 2351         t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
 2352         gtk_adjustment_set_value(adjust, max);
 2353         break;
 2354     case XT_MOVE_TOP:
 2355     case XT_MOVE_FARLEFT:
 2356         t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
 2357         gtk_adjustment_set_value(adjust, lower);
 2358         break;
 2359     case XT_MOVE_PAGEDOWN:
 2360         pos += pi;
 2361         gtk_adjustment_set_value(adjust, MIN(pos, max));
 2362         break;
 2363     case XT_MOVE_PAGEUP:
 2364         pos -= pi;
 2365         gtk_adjustment_set_value(adjust, MAX(pos, lower));
 2366         break;
 2367     case XT_MOVE_HALFDOWN:
 2368         pos += pi / 2;
 2369         gtk_adjustment_set_value(adjust, MIN(pos, max));
 2370         break;
 2371     case XT_MOVE_HALFUP:
 2372         pos -= pi / 2;
 2373         gtk_adjustment_set_value(adjust, MAX(pos, lower));
 2374         break;
 2375     case XT_MOVE_CENTER:
 2376         t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
 2377         args->s = g_strdup("50.0");
 2378         /* FALLTHROUGH */
 2379     case XT_MOVE_PERCENT:
 2380         t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
 2381         percent = atoi(args->s) / 100.0;
 2382         pos = max * percent;
 2383         if (pos < 0.0 || pos > max)
 2384             break;
 2385         gtk_adjustment_set_value(adjust, pos);
 2386         break;
 2387     default:
 2388         return (XT_CB_PASSTHROUGH);
 2389     }
 2390 
 2391     DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
 2392 
 2393     return (XT_CB_HANDLED);
 2394 }
 2395 
 2396 void
 2397 url_set_visibility(void)
 2398 {
 2399     struct tab      *t;
 2400 
 2401     TAILQ_FOREACH(t, &tabs, entry)
 2402         if (show_url == 0) {
 2403             gtk_widget_hide(t->toolbar);
 2404             focus_webview(t);
 2405         } else
 2406             gtk_widget_show(t->toolbar);
 2407 }
 2408 
 2409 void
 2410 notebook_tab_set_visibility(void)
 2411 {
 2412     if (show_tabs == 0) {
 2413         gtk_widget_hide(tab_bar);
 2414         gtk_notebook_set_show_tabs(notebook, FALSE);
 2415     } else {
 2416         if (tab_style == XT_TABS_NORMAL) {
 2417             gtk_widget_hide(tab_bar);
 2418             gtk_notebook_set_show_tabs(notebook, TRUE);
 2419         } else if (tab_style == XT_TABS_COMPACT) {
 2420             gtk_widget_show(tab_bar);
 2421             gtk_notebook_set_show_tabs(notebook, FALSE);
 2422         }
 2423     }
 2424 }
 2425 
 2426 void
 2427 statusbar_set_visibility(void)
 2428 {
 2429     struct tab      *t;
 2430 
 2431     TAILQ_FOREACH(t, &tabs, entry){
 2432         if (show_statusbar == 0)
 2433             gtk_widget_hide(t->statusbar);
 2434         else
 2435             gtk_widget_show(t->statusbar);
 2436 
 2437         focus_webview(t);
 2438     }
 2439 }
 2440 
 2441 void
 2442 url_set(struct tab *t, int enable_url_entry)
 2443 {
 2444     GdkPixbuf   *pixbuf;
 2445     int     progress;
 2446 
 2447     show_url = enable_url_entry;
 2448 
 2449     if (enable_url_entry) {
 2450         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
 2451             GTK_ENTRY_ICON_PRIMARY, NULL);
 2452         gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.uri), 0);
 2453     } else {
 2454         pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
 2455             GTK_ENTRY_ICON_PRIMARY);
 2456         progress =
 2457             gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
 2458         gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.uri),
 2459             GTK_ENTRY_ICON_PRIMARY, pixbuf);
 2460         gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.uri),
 2461             progress);
 2462     }
 2463 }
 2464 
 2465 int
 2466 fullscreen(struct tab *t, struct karg *args)
 2467 {
 2468     DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
 2469 
 2470     if (t == NULL)
 2471         return (XT_CB_PASSTHROUGH);
 2472 
 2473     if (show_url == 0) {
 2474         url_set(t, 1);
 2475         show_tabs = 1;
 2476     } else {
 2477         url_set(t, 0);
 2478         show_tabs = 0;
 2479     }
 2480 
 2481     url_set_visibility();
 2482     notebook_tab_set_visibility();
 2483 
 2484     return (XT_CB_HANDLED);
 2485 }
 2486 
 2487 int
 2488 statustoggle(struct tab *t, struct karg *args)
 2489 {
 2490     DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
 2491 
 2492     if (show_statusbar == 1) {
 2493         show_statusbar = 0;
 2494         statusbar_set_visibility();
 2495     } else if (show_statusbar == 0) {
 2496         show_statusbar = 1;
 2497         statusbar_set_visibility();
 2498     }
 2499     return (XT_CB_HANDLED);
 2500 }
 2501 
 2502 int
 2503 urlaction(struct tab *t, struct karg *args)
 2504 {
 2505     int         rv = XT_CB_HANDLED;
 2506 
 2507     DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
 2508 
 2509     if (t == NULL)
 2510         return (XT_CB_PASSTHROUGH);
 2511 
 2512     switch (args->i) {
 2513     case XT_URL_SHOW:
 2514         if (show_url == 0) {
 2515             url_set(t, 1);
 2516             url_set_visibility();
 2517         }
 2518         break;
 2519     case XT_URL_HIDE:
 2520         if (show_url == 1) {
 2521             url_set(t, 0);
 2522             url_set_visibility();
 2523         }
 2524         break;
 2525     }
 2526     return (rv);
 2527 }
 2528 
 2529 int
 2530 tabaction(struct tab *t, struct karg *args)
 2531 {
 2532     int         rv = XT_CB_HANDLED;
 2533     char            *url = args->s;
 2534     struct undo     *u;
 2535     struct tab      *tt, *tv;
 2536 
 2537     DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
 2538 
 2539     if (t == NULL)
 2540         return (XT_CB_PASSTHROUGH);
 2541 
 2542     switch (args->i) {
 2543     case XT_TAB_NEW:
 2544         if (strlen(url) > 0)
 2545             create_new_tab(url, NULL, 1, args->precount);
 2546         else
 2547             create_new_tab(NULL, NULL, 1, args->precount);
 2548         break;
 2549     case XT_TAB_DELETE:
 2550         if (args->precount < 0)
 2551             delete_tab(t);
 2552         else
 2553             TAILQ_FOREACH(tt, &tabs, entry)
 2554                 if (tt->tab_id == args->precount - 1) {
 2555                     delete_tab(tt);
 2556                     break;
 2557                 }
 2558         break;
 2559     case XT_TAB_DELQUIT:
 2560         if (gtk_notebook_get_n_pages(notebook) > 1)
 2561             delete_tab(t);
 2562         else
 2563             quit(t, args);
 2564         break;
 2565     case XT_TAB_ONLY:
 2566         TAILQ_FOREACH_SAFE(tt, &tabs, entry, tv)
 2567             if (t != tt)
 2568                 delete_tab(tt);
 2569         break;
 2570     case XT_TAB_OPEN:
 2571         if (strlen(url) > 0)
 2572             ;
 2573         else {
 2574             rv = XT_CB_PASSTHROUGH;
 2575             goto done;
 2576         }
 2577         load_uri(t, url);
 2578         break;
 2579     case XT_TAB_SHOW:
 2580         if (show_tabs == 0) {
 2581             show_tabs = 1;
 2582             notebook_tab_set_visibility();
 2583         }
 2584         break;
 2585     case XT_TAB_HIDE:
 2586         if (show_tabs == 1) {
 2587             show_tabs = 0;
 2588             notebook_tab_set_visibility();
 2589         }
 2590         break;
 2591     case XT_TAB_NEXTSTYLE:
 2592         if (tab_style == XT_TABS_NORMAL) {
 2593             tab_style = XT_TABS_COMPACT;
 2594             recolor_compact_tabs();
 2595         }
 2596         else
 2597             tab_style = XT_TABS_NORMAL;
 2598         notebook_tab_set_visibility();
 2599         break;
 2600     case XT_TAB_UNDO_CLOSE:
 2601         if (undo_count == 0) {
 2602             DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
 2603                 __func__);
 2604             goto done;
 2605         } else {
 2606             undo_count--;
 2607             u = TAILQ_FIRST(&undos);
 2608             create_new_tab(u->uri, u, 1, -1);
 2609 
 2610             TAILQ_REMOVE(&undos, u, entry);
 2611             g_free(u->uri);
 2612             /* u->history is freed in create_new_tab() */
 2613             g_free(u);
 2614         }
 2615         break;
 2616     case XT_TAB_LOAD_IMAGES:
 2617 
 2618         if (!auto_load_images) {
 2619 
 2620             /* Enable auto-load images (this will load all
 2621              * previously unloaded images). */
 2622             g_object_set(G_OBJECT(t->settings),
 2623                 "auto-load-images", TRUE, (char *)NULL);
 2624             webkit_web_view_set_settings(t->wv, t->settings);
 2625 
 2626             webkit_web_view_reload(t->wv);
 2627 
 2628             /* Webkit triggers an event when we change the setting,
 2629              * so we can't disable the auto-loading at once.
 2630              *
 2631              * Unfortunately, webkit does not tell us when it's done.
 2632              * Instead, we wait until the next request, and then
 2633              * disable autoloading again.
 2634              */
 2635             t->load_images = TRUE;
 2636         }
 2637         break;
 2638     default:
 2639         rv = XT_CB_PASSTHROUGH;
 2640         goto done;
 2641     }
 2642 
 2643 done:
 2644     if (args->s) {
 2645         g_free(args->s);
 2646         args->s = NULL;
 2647     }
 2648 
 2649     return (rv);
 2650 }
 2651 
 2652 int
 2653 resizetab(struct tab *t, struct karg *args)
 2654 {
 2655     if (t == NULL || args == NULL) {
 2656         show_oops(NULL, "resizetab invalid parameters");
 2657         return (XT_CB_PASSTHROUGH);
 2658     }
 2659 
 2660     DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
 2661         t->tab_id, args->i);
 2662 
 2663     setzoom_webkit(t, args->i);
 2664 
 2665     return (XT_CB_HANDLED);
 2666 }
 2667 
 2668 int
 2669 movetab(struct tab *t, struct karg *args)
 2670 {
 2671     int         n, dest;
 2672 
 2673     if (t == NULL || args == NULL) {
 2674         show_oops(NULL, "movetab invalid parameters");
 2675         return (XT_CB_PASSTHROUGH);
 2676     }
 2677 
 2678     DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
 2679         t->tab_id, args->i);
 2680 
 2681     if (args->i >= XT_TAB_INVALID)
 2682         return (XT_CB_PASSTHROUGH);
 2683 
 2684     if (TAILQ_EMPTY(&tabs))
 2685         return (XT_CB_PASSTHROUGH);
 2686 
 2687     n = gtk_notebook_get_n_pages(notebook);
 2688     dest = gtk_notebook_get_current_page(notebook);
 2689 
 2690     switch (args->i) {
 2691     case XT_TAB_NEXT:
 2692         if (args->precount < 0)
 2693             dest = dest == n - 1 ? 0 : dest + 1;
 2694         else
 2695             dest = args->precount - 1;
 2696 
 2697         break;
 2698     case XT_TAB_PREV:
 2699         if (args->precount < 0)
 2700             dest -= 1;
 2701         else
 2702             dest -= args->precount % n;
 2703 
 2704         if (dest < 0)
 2705             dest += n;
 2706 
 2707         break;
 2708     case XT_TAB_FIRST:
 2709         dest = 0;
 2710         break;
 2711     case XT_TAB_LAST:
 2712         dest = n - 1;
 2713         break;
 2714     default:
 2715         return (XT_CB_PASSTHROUGH);
 2716     }
 2717 
 2718     if (dest < 0 || dest >= n)
 2719         return (XT_CB_PASSTHROUGH);
 2720     if (t->tab_id == dest) {
 2721         DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
 2722         return (XT_CB_HANDLED);
 2723     }
 2724 
 2725     set_current_tab(dest);
 2726 
 2727     return (XT_CB_HANDLED);
 2728 }
 2729 
 2730 int cmd_prefix = 0;
 2731 
 2732 struct prompt_sub {
 2733     const char      *s;
 2734     const char      *(*f)(struct tab *);
 2735 } subs[] = {
 2736     { "<uri>",  get_uri },
 2737 };
 2738 
 2739 int
 2740 command(struct tab *t, struct karg *args)
 2741 {
 2742     struct karg     a = {0};
 2743     int         i, cmd_setup = 0;
 2744     char            *s = NULL, *sp = NULL, *sl = NULL;
 2745     gchar           **sv;
 2746 
 2747     if (t == NULL || args == NULL) {
 2748         show_oops(NULL, "command invalid parameters");
 2749         return (XT_CB_PASSTHROUGH);
 2750     }
 2751 
 2752     switch (args->i) {
 2753     case '/':
 2754         s = "/";
 2755         break;
 2756     case '?':
 2757         s = "?";
 2758         break;
 2759     case ':':
 2760         if (cmd_prefix == 0) {
 2761             if (args->s != NULL && strlen(args->s) != 0) {
 2762                 sp = g_strdup_printf(":%s", args->s);
 2763                 s = sp;
 2764             } else
 2765                 s = ":";
 2766         } else {
 2767             sp = g_strdup_printf(":%d", cmd_prefix);
 2768             s = sp;
 2769             cmd_prefix = 0;
 2770         }
 2771         sl = g_strdup(s);
 2772         if (sp) {
 2773             g_free(sp);
 2774             sp = NULL;
 2775         }
 2776         s = sl;
 2777         for (i = 0; i < LENGTH(subs); ++i) {
 2778             sv = g_strsplit(sl, subs[i].s, -1);
 2779             if (sl)
 2780                 g_free(sl);
 2781             sl = g_strjoinv(subs[i].f(t), sv);
 2782             g_strfreev(sv);
 2783             s = sl;
 2784         }
 2785         break;
 2786     case '.':
 2787         t->mode = XT_MODE_HINT;
 2788         a.i = 0;
 2789         s = ".";
 2790         /*
 2791          * js code will auto fire() if a single link is visible,
 2792          * causing the focus-out-event cb function to be called. Setup
 2793          * the cmd _before_ triggering hinting code so the cmd can get
 2794          * killed by the cb in this case.
 2795          */
 2796         show_cmd(t, s);
 2797         cmd_setup = 1;
 2798         hint(t, &a);
 2799         break;
 2800     case ',':
 2801         t->mode = XT_MODE_HINT;
 2802         a.i = XT_HINT_NEWTAB;
 2803         s = ",";
 2804         show_cmd(t, s);
 2805         cmd_setup = 1;
 2806         hint(t, &a);
 2807         break;
 2808     default:
 2809         show_oops(t, "command: invalid opcode %d", args->i);
 2810         return (XT_CB_PASSTHROUGH);
 2811     }
 2812 
 2813     DNPRINTF(XT_D_CMD, "%s: tab %d type %s\n", __func__, t->tab_id, s);
 2814 
 2815     if (!cmd_setup)
 2816         show_cmd(t, s);
 2817 
 2818     if (sp)
 2819         g_free(sp);
 2820     if (sl)
 2821         g_free(sl);
 2822 
 2823     return (XT_CB_HANDLED);
 2824 }
 2825 
 2826 int
 2827 search(struct tab *t, struct karg *args)
 2828 {
 2829     gboolean    d;
 2830 
 2831     if (t == NULL || args == NULL) {
 2832         show_oops(NULL, "search invalid parameters");
 2833         return (1);
 2834     }
 2835 
 2836     switch (args->i) {
 2837     case  XT_SEARCH_NEXT:
 2838         d = t->search_forward;
 2839         break;
 2840     case  XT_SEARCH_PREV:
 2841         d = !t->search_forward;
 2842         break;
 2843     default:
 2844         return (XT_CB_PASSTHROUGH);
 2845     }
 2846 
 2847     if (t->search_text == NULL) {
 2848         if (global_search == NULL)
 2849             return (XT_CB_PASSTHROUGH);
 2850         else {
 2851             d = t->search_forward = TRUE;
 2852             t->search_text = g_strdup(global_search);
 2853             webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
 2854             webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
 2855         }
 2856     }
 2857 
 2858     DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
 2859         t->tab_id, args->i, t->search_forward, t->search_text);
 2860 
 2861     webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
 2862 
 2863     return (XT_CB_HANDLED);
 2864 }
 2865 
 2866 int
 2867 session_save(struct tab *t, char *filename)
 2868 {
 2869     struct karg     a;
 2870     int         rv = 1;
 2871     struct session      *s;
 2872 
 2873     if (strlen(filename) == 0)
 2874         goto done;
 2875 
 2876     if (filename[0] == '.' || filename[0] == '/')
 2877         goto done;
 2878 
 2879     a.s = filename;
 2880     if (save_tabs(t, &a))
 2881         goto done;
 2882     strlcpy(named_session, filename, sizeof named_session);
 2883 
 2884     /* add the new session to the list of sessions */
 2885     s = g_malloc(sizeof(struct session));
 2886     s->name = g_strdup(filename);
 2887     TAILQ_INSERT_TAIL(&sessions, s, entry);
 2888 
 2889     rv = 0;
 2890 done:
 2891     return (rv);
 2892 }
 2893 
 2894 int
 2895 session_open(struct tab *t, char *filename)
 2896 {
 2897     struct karg     a;
 2898     int         rv = 1;
 2899 
 2900     if (strlen(filename) == 0)
 2901         goto done;
 2902 
 2903     if (filename[0] == '.' || filename[0] == '/')
 2904         goto done;
 2905 
 2906     a.s = filename;
 2907     a.i = XT_SES_CLOSETABS;
 2908     if (open_tabs(t, &a))
 2909         goto done;
 2910 
 2911     strlcpy(named_session, filename, sizeof named_session);
 2912 
 2913     rv = 0;
 2914 done:
 2915     return (rv);
 2916 }
 2917 
 2918 int
 2919 session_delete(struct tab *t, char *filename)
 2920 {
 2921     char            file[PATH_MAX];
 2922     int         rv = 1;
 2923     struct session      *s;
 2924 
 2925     if (strlen(filename) == 0)
 2926         goto done;
 2927 
 2928     if (filename[0] == '.' || filename[0] == '/')
 2929         goto done;
 2930 
 2931     snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, filename);
 2932     if (unlink(file))
 2933         goto done;
 2934 
 2935     if (!strcmp(filename, named_session))
 2936         strlcpy(named_session, XT_SAVED_TABS_FILE,
 2937             sizeof named_session);
 2938 
 2939     /* remove session from sessions list */
 2940     TAILQ_FOREACH(s, &sessions, entry) {
 2941         if (!strcmp(s->name, filename))
 2942             break;
 2943     }
 2944     if (s == NULL)
 2945         goto done;
 2946     TAILQ_REMOVE(&sessions, s, entry);
 2947     g_free((gpointer) s->name);
 2948     g_free(s);
 2949 
 2950     rv = 0;
 2951 done:
 2952     return (rv);
 2953 }
 2954 
 2955 int
 2956 session_cmd(struct tab *t, struct karg *args)
 2957 {
 2958     char            *filename = args->s;
 2959 
 2960     if (t == NULL)
 2961         return (1);
 2962 
 2963     if (args->i & XT_SHOW)
 2964         show_oops(t, "Current session: %s", named_session[0] == '\0' ?
 2965             XT_SAVED_TABS_FILE : named_session);
 2966     else if (args->i & XT_SAVE) {
 2967         if (session_save(t, filename)) {
 2968             show_oops(t, "Can't save session: %s",
 2969                 filename ? filename : "INVALID");
 2970             goto done;
 2971         }
 2972     } else if (args->i & XT_OPEN) {
 2973         if (session_open(t, filename)) {
 2974             show_oops(t, "Can't open session: %s",
 2975                 filename ? filename : "INVALID");
 2976             goto done;
 2977         }
 2978     } else if (args->i & XT_DELETE) {
 2979         if (session_delete(t, filename)) {
 2980             show_oops(t, "Can't delete session: %s",
 2981                 filename ? filename : "INVALID");
 2982             goto done;
 2983         }
 2984     }
 2985 done:
 2986     return (XT_CB_PASSTHROUGH);
 2987 }
 2988 
 2989 int
 2990 script_cmd(struct tab *t, struct karg *args)
 2991 {
 2992     struct stat     sb;
 2993     FILE            *f = NULL;
 2994     char            *buf = NULL;
 2995 
 2996     if (t == NULL)
 2997         goto done;
 2998 
 2999     if ((f = fopen(args->s, "r")) == NULL) {
 3000         show_oops(t, "Can't open script file: %s", args->s);
 3001         goto done;
 3002     }
 3003 
 3004     if (fstat(fileno(f), &sb) == -1) {
 3005         show_oops(t, "Can't stat script file: %s", args->s);
 3006         goto done;
 3007     }
 3008 
 3009     buf = g_malloc0(sb.st_size + 1);
 3010     if (fread(buf, 1, sb.st_size, f) != sb.st_size) {
 3011         show_oops(t, "Can't read script file: %s", args->s);
 3012         goto done;
 3013     }
 3014 
 3015     DNPRINTF(XT_D_JS, "%s: about to run script\n", __func__);
 3016     run_script(t, buf);
 3017 
 3018 done:
 3019     if (f)
 3020         fclose(f);
 3021     if (buf)
 3022         g_free(buf);
 3023 
 3024     return (XT_CB_PASSTHROUGH);
 3025 }
 3026 
 3027 /*
 3028  * Make a hardcopy of the page
 3029  */
 3030 int
 3031 print_page(struct tab *t, struct karg *args)
 3032 {
 3033     WebKitWebFrame          *frame;
 3034     GtkPageSetup            *ps;
 3035     GtkPrintOperation       *op;
 3036     GtkPrintOperationAction     action;
 3037     GtkPrintOperationResult     print_res;
 3038     GError              *g_err = NULL;
 3039     int             marg_l, marg_r, marg_t, marg_b;
 3040     int             ret = 0;
 3041 
 3042     DNPRINTF(XT_D_PRINTING, "%s:", __func__);
 3043 
 3044     ps = gtk_page_setup_new();
 3045     op = gtk_print_operation_new();
 3046     action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
 3047     frame = webkit_web_view_get_main_frame(t->wv);
 3048 
 3049     /* the default margins are too small, so we will bump them */
 3050     marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
 3051         XT_PRINT_EXTRA_MARGIN;
 3052     marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
 3053         XT_PRINT_EXTRA_MARGIN;
 3054     marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
 3055         XT_PRINT_EXTRA_MARGIN;
 3056     marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
 3057         XT_PRINT_EXTRA_MARGIN;
 3058 
 3059     /* set margins */
 3060     gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
 3061     gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
 3062     gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
 3063     gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
 3064 
 3065     gtk_print_operation_set_default_page_setup(op, ps);
 3066 
 3067     print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
 3068 
 3069     /* check it worked */
 3070     if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
 3071         show_oops(NULL, "can't print: %s", g_err->message);
 3072         g_error_free (g_err);
 3073         ret = 1;
 3074     }
 3075 
 3076     g_object_unref(G_OBJECT(ps));
 3077     g_object_unref(G_OBJECT(op));
 3078     return (ret);
 3079 }
 3080 
 3081 int
 3082 go_home(struct tab *t, struct karg *args)
 3083 {
 3084     load_uri(t, home);
 3085     return (0);
 3086 }
 3087 
 3088 int
 3089 set_encoding(struct tab *t, struct karg *args)
 3090 {
 3091     const gchar *e;
 3092 
 3093     if (args->s && strlen(g_strstrip(args->s)) == 0) {
 3094         e = webkit_web_view_get_custom_encoding(t->wv);
 3095         if (e == NULL)
 3096             e = webkit_web_view_get_encoding(t->wv);
 3097         show_oops(t, "encoding: %s", e ? e : "N/A");
 3098     } else
 3099         webkit_web_view_set_custom_encoding(t->wv, args->s);
 3100 
 3101     return (0);
 3102 }
 3103 
 3104 int
 3105 restart(struct tab *t, struct karg *args)
 3106 {
 3107     struct karg     a;
 3108 
 3109     a.s = XT_RESTART_TABS_FILE;
 3110     save_tabs(t, &a);
 3111     execvp(start_argv[0], start_argv);
 3112     /* NOTREACHED */
 3113 
 3114     return (0);
 3115 }
 3116 
 3117 char        *http_proxy_save; /* not a setting, used to toggle */
 3118 
 3119 int
 3120 proxy_cmd(struct tab *t, struct karg *args)
 3121 {
 3122     struct tab      *tt;
 3123 
 3124     DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
 3125 
 3126     if (t == NULL)
 3127         return (1);
 3128 
 3129     /* setup */
 3130     if (http_proxy) {
 3131         TAILQ_FOREACH(tt, &tabs, entry)
 3132             gtk_widget_show(t->proxy_toggle);
 3133         if (http_proxy_save)
 3134             g_free(http_proxy_save);
 3135         http_proxy_save = g_strdup(http_proxy);
 3136     }
 3137 
 3138     if (args->i & XT_PRXY_SHOW) {
 3139         if (http_proxy)
 3140             show_oops(t, "http_proxy = %s", http_proxy);
 3141         else
 3142             show_oops(t, "proxy is currently disabled");
 3143     } else if (args->i & XT_PRXY_TOGGLE) {
 3144         if (http_proxy_save == NULL && http_proxy == NULL) {
 3145             show_oops(t, "can't toggle proxy");
 3146             goto done;
 3147         }
 3148         TAILQ_FOREACH(tt, &tabs, entry)
 3149             gtk_widget_show(t->proxy_toggle);
 3150         if (http_proxy) {
 3151             if (setup_proxy(NULL) == 0)
 3152                 button_set_file(t->proxy_toggle,
 3153                     "tordisabled.ico");
 3154             show_oops(t, "http proxy disabled");
 3155         } else {
 3156             if (setup_proxy(http_proxy_save) == 0 && http_proxy) {
 3157                 button_set_file(t->proxy_toggle,
 3158                     "torenabled.ico");
 3159                 show_oops(t, "http_proxy = %s", http_proxy);
 3160             } else
 3161                 show_oops(t, "invalid proxy: %s", http_proxy_save);
 3162         }
 3163     }
 3164 done:
 3165     return (XT_CB_PASSTHROUGH);
 3166 }
 3167 
 3168 /*
 3169  * If you can read this functionthen you are a sick and twisted individual.
 3170  * I hope we never meet, it'll be violent.
 3171  */
 3172 gboolean
 3173 eval_cb(const GMatchInfo *info, GString *res, gpointer data)
 3174 {
 3175     gchar           *match, *num;
 3176     gint            start = -1, end = -1, i;
 3177     struct karg     *args = data;
 3178 
 3179     /*
 3180      * match contains the string UP TO the match.
 3181      *
 3182      * res is what is returned, note that whatever remains in the sent in
 3183      * string is appended on the way out.
 3184      *
 3185      * for example /123/456/789/moo came in
 3186      * match contains /123/456/789/
 3187      * we assign that to res and replace /789/ with the replacement text
 3188      * then g_regex_replace_eval on the way out has /123/456/replacement/moo
 3189      *
 3190      */
 3191     match = g_match_info_fetch(info, 0);
 3192     if (match == NULL)
 3193         goto done;
 3194 
 3195     if (g_match_info_fetch_pos(info, 1, &start, &end) == FALSE)
 3196         goto freeit;
 3197 
 3198     g_string_assign(res, match);
 3199 
 3200     i = atoi(&match[start + 1]);
 3201     if (args->i == XT_URL_PLUS)
 3202         i++;
 3203     else
 3204         i--;
 3205 
 3206     /* preserve whitespace when likely */
 3207     num = g_strdup_printf("%0*d",  end - start - 2, i);
 3208     g_string_overwrite_len(res, start + 1, num, end - start - 2);
 3209     g_free(num);
 3210 
 3211 freeit:
 3212     g_free(match);
 3213 done:
 3214     return (FALSE); /* doesn't matter */
 3215 }
 3216 
 3217 int
 3218 urlmod_cmd(struct tab *t, struct karg *args)
 3219 {
 3220     const gchar     *uri;
 3221     GRegex          *reg;
 3222     gchar           *res;
 3223 
 3224     if (t == NULL)
 3225         goto done;
 3226     if ((uri = gtk_entry_get_text(GTK_ENTRY(t->uri_entry))) == NULL)
 3227         goto done;
 3228     if (strlen(uri) == 0)
 3229         goto done;
 3230 
 3231     reg = g_regex_new(".*(/[0-9]+/)", 0, 0, NULL);
 3232     if (reg == NULL)
 3233         goto done;
 3234     res = g_regex_replace_eval(reg, uri, -1, 0, 0, eval_cb, args, NULL);
 3235     if (res == NULL)
 3236         goto free_reg;
 3237 
 3238     if (!strcmp(res, uri))
 3239         goto free_res;
 3240 
 3241     gtk_entry_set_text(GTK_ENTRY(t->uri_entry), res);
 3242     activate_uri_entry_cb(t->uri_entry, t);
 3243 
 3244 free_res:
 3245     g_free(res);
 3246 free_reg:
 3247     g_regex_unref(reg);
 3248 done:
 3249     return (XT_CB_PASSTHROUGH);
 3250 }
 3251 
 3252 struct cmd {
 3253     char        *cmd;
 3254     int     level;
 3255     int     (*func)(struct tab *, struct karg *);
 3256     int     arg;
 3257     int     type;
 3258 } cmds[] = {
 3259     { "command_mode",   0,  command_mode,       XT_MODE_COMMAND,    0 },
 3260     { "insert_mode",    0,  command_mode,       XT_MODE_INSERT,     0 },
 3261     { "command",        0,  command,        ':',            0 },
 3262     { "search",     0,  command,        '/',            0 },
 3263     { "searchb",        0,  command,        '?',            0 },
 3264     { "hinting",        0,  command,        '.',            0 },
 3265     { "hinting_newtab", 0,  command,        ',',            0 },
 3266     { "togglesrc",      0,  toggle_src,     0,          0 },
 3267     { "editsrc",        0,  edit_src,       0,          0 },
 3268     { "editelement",    0,  edit_element,       0,          0 },
 3269     { "passthrough",    0,  passthrough,        0,          0 },
 3270     { "modurl",     0,  modurl,         0,          0 },
 3271     { "modsearchentry", 0,  modsearchentry,     0,          0 },
 3272 
 3273     /* yanking and pasting */
 3274     { "yankuri",        0,  yank_uri,       0,          0 },
 3275     { "pasteuricur",    0,  paste_uri,      XT_PASTE_CURRENT_TAB,   0 },
 3276     { "pasteurinew",    0,  paste_uri,      XT_PASTE_NEW_TAB,   0 },
 3277 
 3278     /* search */
 3279     { "searchnext",     0,  search,         XT_SEARCH_NEXT,     0 },
 3280     { "searchprevious", 0,  search,         XT_SEARCH_PREV,     0 },
 3281 
 3282     /* focus */
 3283     { "focusaddress",   0,  focus,          XT_FOCUS_URI,       0 },
 3284     { "focussearch",    0,  focus,          XT_FOCUS_SEARCH,    0 },
 3285 
 3286     /* hinting */
 3287     { "hinting",        0,  hint,           0,          0 },
 3288     { "hinting_newtab", 0,  hint,           XT_HINT_NEWTAB,     0 },
 3289 
 3290     /* custom stylesheet */
 3291     { "userstyle",      0,  userstyle_cmd,      XT_STYLE_CURRENT_TAB,   XT_USERARG },
 3292     { "userstyle_global",   0,  userstyle_cmd,      XT_STYLE_GLOBAL,    XT_USERARG },
 3293 
 3294     /* navigation */
 3295     { "goback",     0,  navaction,      XT_NAV_BACK,        0 },
 3296     { "goforward",      0,  navaction,      XT_NAV_FORWARD,     0 },
 3297     { "reload",     0,  navaction,      XT_NAV_RELOAD,      0 },
 3298     { "stop",       0,  navaction,      XT_NAV_STOP,        0 },
 3299 
 3300     /* vertical movement */
 3301     { "scrolldown",     0,  move,           XT_MOVE_DOWN,       0 },
 3302     { "scrollup",       0,  move,           XT_MOVE_UP,     0 },
 3303     { "scrollbottom",   0,  move,           XT_MOVE_BOTTOM,     0 },
 3304     { "scrolltop",      0,  move,           XT_MOVE_TOP,        0 },
 3305     { "1",          0,  move,           XT_MOVE_TOP,        0 },
 3306     { "scrollhalfdown", 0,  move,           XT_MOVE_HALFDOWN,   0 },
 3307     { "scrollhalfup",   0,  move,           XT_MOVE_HALFUP,     0 },
 3308     { "scrollpagedown", 0,  move,           XT_MOVE_PAGEDOWN,   0 },
 3309     { "scrollpageup",   0,  move,           XT_MOVE_PAGEUP,     0 },
 3310     /* horizontal movement */
 3311     { "scrollright",    0,  move,           XT_MOVE_RIGHT,      0 },
 3312     { "scrollleft",     0,  move,           XT_MOVE_LEFT,       0 },
 3313     { "scrollfarright", 0,  move,           XT_MOVE_FARRIGHT,   0 },
 3314     { "scrollfarleft",  0,  move,           XT_MOVE_FARLEFT,    0 },
 3315 
 3316     { "favorites",      0,  xtp_page_fl,        XT_SHOW,        0 },
 3317     { "fav",        0,  xtp_page_fl,        XT_SHOW,        0 },
 3318     { "favedit",        0,  xtp_page_fl,        XT_SHOW|XT_DELETE,  0 },
 3319     { "favadd",     0,  add_favorite,       0,          XT_USERARG },
 3320 
 3321     { "qall",       0,  quit,           0,          0 },
 3322     { "quitall",        0,  quit,           0,          0 },
 3323     { "w",          0,  save_tabs,      0,          0 },
 3324     { "wq",         0,  save_tabs_and_quit, 0,          0 },
 3325     { "help",       0,  help,           0,          0 },
 3326     { "about",      0,  xtp_page_ab,        0,          0 },
 3327     { "stats",      0,  stats,          0,          0 },
 3328     { "version",        0,  xtp_page_ab,        0,          0 },
 3329 
 3330     /* js command */
 3331     { "js",         0,  js_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3332     { "save",       1,  js_cmd,         XT_SAVE | XT_WL_FQDN,               0 },
 3333     { "domain",     2,  js_cmd,         XT_SAVE | XT_WL_TOPLEVEL,           0 },
 3334     { "fqdn",       2,  js_cmd,         XT_SAVE | XT_WL_FQDN,               0 },
 3335     { "show",       1,  js_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3336     { "all",        2,  js_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3337     { "persistent",     2,  js_cmd,         XT_SHOW | XT_WL_PERSISTENT,         0 },
 3338     { "session",        2,  js_cmd,         XT_SHOW | XT_WL_SESSION,            0 },
 3339     { "toggle",     1,  js_cmd,         XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3340     { "domain",     2,  js_cmd,         XT_WL_TOGGLE | XT_WL_TOPLEVEL,          0 },
 3341     { "fqdn",       2,  js_cmd,         XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3342 
 3343     /* cookie command */
 3344     { "cookie",     0,  cookie_cmd,     XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3345     { "save",       1,  cookie_cmd,     XT_SAVE | XT_WL_FQDN,               0 },
 3346     { "domain",     2,  cookie_cmd,     XT_SAVE | XT_WL_TOPLEVEL,           0 },
 3347     { "fqdn",       2,  cookie_cmd,     XT_SAVE | XT_WL_FQDN,               0 },
 3348     { "show",       1,  cookie_cmd,     XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3349     { "all",        2,  cookie_cmd,     XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3350     { "persistent",     2,  cookie_cmd,     XT_SHOW | XT_WL_PERSISTENT,         0 },
 3351     { "session",        2,  cookie_cmd,     XT_SHOW | XT_WL_SESSION,            0 },
 3352     { "toggle",     1,  cookie_cmd,     XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3353     { "domain",     2,  cookie_cmd,     XT_WL_TOGGLE | XT_WL_TOPLEVEL,          0 },
 3354     { "fqdn",       2,  cookie_cmd,     XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3355     { "purge",      1,  cookie_cmd,     XT_DELETE,                  0 },
 3356 
 3357     /* plugin command */
 3358     { "plugin",     0,  pl_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3359     { "save",       1,  pl_cmd,         XT_SAVE | XT_WL_FQDN,               0 },
 3360     { "domain",     2,  pl_cmd,         XT_SAVE | XT_WL_TOPLEVEL,           0 },
 3361     { "fqdn",       2,  pl_cmd,         XT_SAVE | XT_WL_FQDN,               0 },
 3362     { "show",       1,  pl_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3363     { "all",        2,  pl_cmd,         XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3364     { "persistent",     2,  pl_cmd,         XT_SHOW | XT_WL_PERSISTENT,         0 },
 3365     { "session",        2,  pl_cmd,         XT_SHOW | XT_WL_SESSION,            0 },
 3366     { "toggle",     1,  pl_cmd,         XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3367     { "domain",     2,  pl_cmd,         XT_WL_TOGGLE | XT_WL_TOPLEVEL,          0 },
 3368     { "fqdn",       2,  pl_cmd,         XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3369 
 3370     /* https command */
 3371     { "https",      0,  https_cmd,      XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3372     { "save",       1,  https_cmd,      XT_SAVE | XT_WL_FQDN,               0 },
 3373     { "domain",     2,  https_cmd,      XT_SAVE | XT_WL_TOPLEVEL,           0 },
 3374     { "fqdn",       2,  https_cmd,      XT_SAVE | XT_WL_FQDN,               0 },
 3375     { "show",       1,  https_cmd,      XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3376     { "all",        2,  https_cmd,      XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
 3377     { "persistent",     2,  https_cmd,      XT_SHOW | XT_WL_PERSISTENT,         0 },
 3378     { "session",        2,  https_cmd,      XT_SHOW | XT_WL_SESSION,            0 },
 3379     { "toggle",     1,  https_cmd,      XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3380     { "domain",     2,  https_cmd,      XT_WL_TOGGLE | XT_WL_TOPLEVEL,          0 },
 3381     { "fqdn",       2,  https_cmd,      XT_WL_TOGGLE | XT_WL_FQDN,          0 },
 3382 
 3383     /* toplevel (domain) command */
 3384     { "toplevel",       0,  toplevel_cmd,       XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD,   0 },
 3385     { "toggle",     1,  toplevel_cmd,       XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD,   0 },
 3386 
 3387     /* cookie jar */
 3388     { "cookiejar",      0,  xtp_page_cl,        0,          0 },
 3389 
 3390     /* cert command */
 3391     { "cert",       0,  cert_cmd,       XT_SHOW,        0 },
 3392     { "save",       1,  cert_cmd,       XT_SAVE,        0 },
 3393     { "show",       1,  cert_cmd,       XT_SHOW,        0 },
 3394 
 3395     { "ca",         0,  ca_cmd,         0,          0 },
 3396     { "downloadmgr",    0,  xtp_page_dl,        0,          0 },
 3397     { "dl",         0,  xtp_page_dl,        0,          0 },
 3398     { "h",          0,  xtp_page_hl,        0,          0 },
 3399     { "history",        0,  xtp_page_hl,        0,          0 },
 3400     { "home",       0,  go_home,        0,          0 },
 3401     { "restart",        0,  restart,        0,          0 },
 3402     { "urlhide",        0,  urlaction,      XT_URL_HIDE,        0 },
 3403     { "urlshow",        0,  urlaction,      XT_URL_SHOW,        0 },
 3404     { "statustoggle",   0,  statustoggle,       0,          0 },
 3405     { "run_script",     0,  run_page_script,    0,          XT_USERARG },
 3406 
 3407     { "print",      0,  print_page,     0,          0 },
 3408 
 3409     /* tabs */
 3410     { "focusin",        0,  resizetab,      XT_ZOOM_IN,     0 },
 3411     { "focusout",       0,  resizetab,      XT_ZOOM_OUT,        0 },
 3412     { "focusreset",     0,  resizetab,      XT_ZOOM_NORMAL,     0 },
 3413     { "q",          0,  tabaction,      XT_TAB_DELQUIT,     0 },
 3414     { "quit",       0,  tabaction,      XT_TAB_DELQUIT,     0 },
 3415     { "open",       0,  tabaction,      XT_TAB_OPEN,        XT_URLARG },
 3416     { "tabclose",       0,  tabaction,      XT_TAB_DELETE,      XT_PREFIX | XT_INTARG},
 3417     { "tabedit",        0,  tabaction,      XT_TAB_NEW,     XT_PREFIX | XT_URLARG },
 3418     { "tabfirst",       0,  movetab,        XT_TAB_FIRST,       0 },
 3419     { "tabhide",        0,  tabaction,      XT_TAB_HIDE,        0 },
 3420     { "tablast",        0,  movetab,        XT_TAB_LAST,        0 },
 3421     { "tabnew",     0,  tabaction,      XT_TAB_NEW,     XT_PREFIX | XT_URLARG },
 3422     { "tabnext",        0,  movetab,        XT_TAB_NEXT,        XT_PREFIX | XT_INTARG},
 3423     { "tabnextstyle",   0,  tabaction,      XT_TAB_NEXTSTYLE,   0 },
 3424     { "tabonly",        0,  tabaction,      XT_TAB_ONLY,        0 },
 3425     { "tabprevious",    0,  movetab,        XT_TAB_PREV,        XT_PREFIX | XT_INTARG},
 3426     { "tabrewind",      0,  movetab,        XT_TAB_FIRST,       0 },
 3427     { "tabshow",        0,  tabaction,      XT_TAB_SHOW,        0 },
 3428     { "tabs",       0,  buffers,        0,          0 },
 3429     { "tabundoclose",   0,  tabaction,      XT_TAB_UNDO_CLOSE,  0 },
 3430     { "buffers",        0,  buffers,        0,          0 },
 3431     { "ls",         0,  buffers,        0,          0 },
 3432     { "encoding",       0,  set_encoding,       0,          XT_USERARG },
 3433     { "loadimages",     0,  tabaction,      XT_TAB_LOAD_IMAGES, 0 },
 3434 
 3435     /* settings */
 3436     { "set",        0,  set,            0,          XT_SETARG },
 3437     { "runtime",        0,  xtp_page_rt,        0,          0 },
 3438 
 3439     { "fullscreen",     0,  fullscreen,     0,          0 },
 3440     { "f",          0,  fullscreen,     0,          0 },
 3441 
 3442     /* sessions */
 3443     { "session",        0,  session_cmd,        XT_SHOW,        0 },
 3444     { "delete",     1,  session_cmd,        XT_DELETE,      XT_SESSARG },
 3445     { "open",       1,  session_cmd,        XT_OPEN,        XT_SESSARG },
 3446     { "save",       1,  session_cmd,        XT_SAVE,        XT_USERARG },
 3447     { "show",       1,  session_cmd,        XT_SHOW,        0 },
 3448 
 3449     /* external javascript */
 3450     { "script",     0,  script_cmd,     XT_EJS_SHOW,        XT_USERARG },
 3451 
 3452     /* inspector */
 3453     { "inspector",      0,  inspector_cmd,      XT_INS_SHOW,        0 },
 3454     { "show",       1,  inspector_cmd,      XT_INS_SHOW,        0 },
 3455     { "hide",       1,  inspector_cmd,      XT_INS_HIDE,        0 },
 3456 
 3457     /* proxy */
 3458     { "proxy",      0,  proxy_cmd,      XT_PRXY_SHOW,       0 },
 3459     { "show",       1,  proxy_cmd,      XT_PRXY_SHOW,       0 },
 3460     { "toggle",     1,  proxy_cmd,      XT_PRXY_TOGGLE,     0 },
 3461 
 3462     /* url mod */
 3463     { "urlmod",     0,  urlmod_cmd,     XT_URL,         0 },
 3464     { "plus",       1,  urlmod_cmd,     XT_URL_PLUS,        0 },
 3465     { "min",        1,  urlmod_cmd,     XT_URL_MIN,     0 },
 3466 };
 3467 
 3468 struct {
 3469     int         index;
 3470     int         len;
 3471     gchar           *list[256];
 3472 } cmd_status = {-1, 0};
 3473 
 3474 gboolean
 3475 wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
 3476 {
 3477 
 3478     if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
 3479         btn_down = 0;
 3480 
 3481     return (FALSE);
 3482 }
 3483 
 3484 gboolean
 3485 wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
 3486 {
 3487     struct karg     a;
 3488     WebKitHitTestResult *hit_test_result;
 3489     guint           context;
 3490 
 3491     hit_test_result = webkit_web_view_get_hit_test_result(t->wv, e);
 3492     g_object_get(hit_test_result, "context", &context, NULL);
 3493     g_object_unref(G_OBJECT(hit_test_result));
 3494 
 3495     hide_oops(t);
 3496     hide_buffers(t);
 3497 
 3498     if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
 3499         t->mode = XT_MODE_INSERT;
 3500     else
 3501         t->mode = XT_MODE_COMMAND;
 3502 
 3503     if (e->type == GDK_BUTTON_PRESS && e->button == 1)
 3504         btn_down = 1;
 3505     else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
 3506         /* go backward */
 3507         a.i = XT_NAV_BACK;
 3508         navaction(t, &a);
 3509 
 3510         return (TRUE);
 3511     } else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
 3512         /* go forward */
 3513         a.i = XT_NAV_FORWARD;
 3514         navaction(t, &a);
 3515 
 3516         return (TRUE);
 3517     }
 3518 
 3519     return (FALSE);
 3520 }
 3521 
 3522 void
 3523 tab_close_cb(GtkWidget *btn, struct tab *t)
 3524 {
 3525     DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
 3526 
 3527     delete_tab(t);
 3528 }
 3529 
 3530 int
 3531 parse_custom_uri(struct tab *t, const char *uri)
 3532 {
 3533     struct custom_uri   *u;
 3534     int         handled = 0;
 3535     char            *sv[3];
 3536 
 3537     TAILQ_FOREACH(u, &cul, entry) {
 3538         if (strncmp(uri, u->uri, strlen(u->uri)))
 3539             continue;
 3540 
 3541         handled = 1;
 3542         sv[0] = u->cmd;
 3543         sv[1] = (char *)uri;
 3544         sv[2] = NULL;
 3545         if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL,
 3546             NULL, NULL, NULL))
 3547             show_oops(t, "%s: could not spawn process", __func__);
 3548     }
 3549 
 3550     return (handled);
 3551 }
 3552 
 3553 void
 3554 activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
 3555 {
 3556     const gchar     *uri = gtk_entry_get_text(GTK_ENTRY(entry));
 3557 
 3558     DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
 3559 
 3560     if (t == NULL) {
 3561         show_oops(NULL, "activate_uri_entry_cb invalid parameters");
 3562         return;
 3563     }
 3564 
 3565     if (uri == NULL) {
 3566         show_oops(t, "activate_uri_entry_cb no uri");
 3567         return;
 3568     }
 3569 
 3570     uri += strspn(uri, "\t ");
 3571 
 3572     if (parse_custom_uri(t, uri))
 3573         return;
 3574 
 3575     /* otherwise continue to load page normally */
 3576     load_uri(t, (gchar *)uri);
 3577     focus_webview(t);
 3578 }
 3579 
 3580 void
 3581 activate_search_entry_cb(GtkWidget* entry, struct tab *t)
 3582 {
 3583     const gchar     *search = gtk_entry_get_text(GTK_ENTRY(entry));
 3584     char            *newuri = NULL;
 3585     gchar           *enc_search;
 3586     char            **sv;
 3587 
 3588     DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
 3589 
 3590     if (t == NULL) {
 3591         show_oops(NULL, "activate_search_entry_cb invalid parameters");
 3592         return;
 3593     }
 3594 
 3595     if (search_string == NULL || strlen(search_string) == 0) {
 3596         show_oops(t, "no search_string");
 3597         return;
 3598     }
 3599 
 3600     set_normal_tab_meaning(t);
 3601 
 3602     enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
 3603     sv = g_strsplit(search_string, "%s", 2);
 3604     newuri = g_strjoinv(enc_search, sv);
 3605     g_free(enc_search);
 3606     g_strfreev(sv);
 3607 
 3608     marks_clear(t);
 3609     load_uri(t, newuri);
 3610     focus_webview(t);
 3611 
 3612     if (newuri)
 3613         g_free(newuri);
 3614 }
 3615 
 3616 void
 3617 check_and_set_cookie(const gchar *uri, struct tab *t)
 3618 {
 3619     struct wl_entry     *w = NULL;
 3620     int         es = 0;
 3621 
 3622     if (uri == NULL || t == NULL)
 3623         return;
 3624 
 3625     if ((w = wl_find_uri(uri, &c_wl)) == NULL)
 3626         es = 0;
 3627     else
 3628         es = 1;
 3629 
 3630     DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
 3631         es ? "enable" : "disable", uri);
 3632 
 3633     g_object_set(G_OBJECT(t->settings),
 3634         "enable-html5-local-storage", es, (char *)NULL);
 3635     webkit_web_view_set_settings(t->wv, t->settings);
 3636 }
 3637 
 3638 void
 3639 check_and_set_js(const gchar *uri, struct tab *t)
 3640 {
 3641     struct wl_entry     *w = NULL;
 3642     int         es = 0;
 3643 
 3644     if (uri == NULL || t == NULL)
 3645         return;
 3646 
 3647     if ((w = wl_find_uri(uri, &js_wl)) == NULL)
 3648         es = 0;
 3649     else
 3650         es = 1;
 3651 
 3652     DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
 3653         es ? "enable" : "disable", uri);
 3654 
 3655     g_object_set(G_OBJECT(t->settings),
 3656         "enable-scripts", es, (char *)NULL);
 3657     webkit_web_view_set_settings(t->wv, t->settings);
 3658 
 3659     button_set_icon_name(t->js_toggle,
 3660         es ? "media-playback-start" : "media-playback-pause");
 3661 }
 3662 
 3663 void
 3664 check_and_set_pl(const gchar *uri, struct tab *t)
 3665 {
 3666     struct wl_entry     *w = NULL;
 3667     int         es = 0;
 3668 
 3669     if (uri == NULL || t == NULL)
 3670         return;
 3671 
 3672     if ((w = wl_find_uri(uri, &pl_wl)) == NULL)
 3673         es = 0;
 3674     else
 3675         es = 1;
 3676 
 3677     DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
 3678         es ? "enable" : "disable", uri);
 3679 
 3680     g_object_set(G_OBJECT(t->settings),
 3681         "enable-plugins", es, (char *)NULL);
 3682     webkit_web_view_set_settings(t->wv, t->settings);
 3683 }
 3684 
 3685 #if GTK_CHECK_VERSION(3, 0, 0)
 3686 /* A lot of this can be removed when gtk2 is dropped on the floor */
 3687 char *
 3688 get_css_name(const char *col_str)
 3689 {
 3690     char            *name = NULL;
 3691 
 3692     if (!strcmp(col_str, XT_COLOR_WHITE))
 3693         name = g_strdup(XT_CSS_NORMAL);
 3694     else if (!strcmp(col_str, XT_COLOR_RED))
 3695         name = g_strdup(XT_CSS_RED);
 3696     else if (!strcmp(col_str, XT_COLOR_YELLOW))
 3697         name = g_strdup(XT_CSS_YELLOW);
 3698     else if (!strcmp(col_str, XT_COLOR_GREEN))
 3699         name = g_strdup(XT_CSS_GREEN);
 3700     else if (!strcmp(col_str, XT_COLOR_BLUE))
 3701         name = g_strdup(XT_CSS_BLUE);
 3702     return (name);
 3703 }
 3704 #endif
 3705 
 3706 void
 3707 show_ca_status(struct tab *t, const char *uri)
 3708 {
 3709     char            domain[8182], file[PATH_MAX];
 3710     SoupMessage     *msg = NULL;
 3711     GTlsCertificate     *cert = NULL;
 3712     GTlsCertificateFlags    flags = 0;
 3713     gchar           *col_str = XT_COLOR_RED;
 3714     char            *cut_uri;
 3715     char            *chain;
 3716     char            *s;
 3717 #if GTK_CHECK_VERSION(3, 0, 0)
 3718     char            *name;
 3719 #else
 3720     GdkColor        color;
 3721     gchar           *text, *base;
 3722 #endif
 3723     enum cert_trust     trust;
 3724     int         nocolor = 0;
 3725     int         i;
 3726 
 3727     DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
 3728         ssl_strict_certs, ssl_ca_file, uri);
 3729 
 3730     if (t == NULL)
 3731         return;
 3732 
 3733     if (uri == NULL || g_str_has_prefix(uri, "http://") ||
 3734         !g_str_has_prefix(uri, "https://"))
 3735         return;
 3736 
 3737     /*
 3738      * Cut the uri to get the certs off the homepage. We can't use the
 3739      * full URI here since it may include arguments and we don't want to make
 3740      * these requests multiple times.
 3741      */
 3742     cut_uri = g_strdup(uri);
 3743     s = cut_uri;
 3744     for (i = 0; i < 3; ++i)
 3745         s = strchr(&(s[1]), '/');
 3746     s[1] = '\0';
 3747 
 3748     msg = soup_message_new("HEAD", cut_uri);
 3749     g_free(cut_uri);
 3750     if (msg == NULL)
 3751         return;
 3752     soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT);
 3753     soup_session_send_message(session, msg);
 3754     if (msg->status_code == SOUP_STATUS_SSL_FAILED ||
 3755         msg->status_code == SOUP_STATUS_TLS_FAILED) {
 3756         DNPRINTF(XT_D_URL, "%s: status not ok: %d\n", uri,
 3757             msg->status_code);
 3758         goto done;
 3759     }
 3760     if (!soup_message_get_https_status(msg, &cert, &flags)) {
 3761         DNPRINTF(XT_D_URL, "%s: invalid response\n", uri);
 3762         goto done;
 3763     }
 3764     if (!G_IS_TLS_CERTIFICATE(cert)) {
 3765         DNPRINTF(XT_D_URL, "%s: no cert\n", uri);
 3766         goto done;
 3767     }
 3768 
 3769     if (flags == 0)
 3770         col_str = XT_COLOR_GREEN;
 3771     else
 3772         col_str = XT_COLOR_YELLOW;
 3773 
 3774     strlcpy(domain, uri + strlen("https://"), sizeof domain);
 3775     for (i = 0; i < strlen(domain); i++)
 3776         if (domain[i] == '/') {
 3777             domain[i] = '\0';
 3778             break;
 3779         }
 3780 
 3781     snprintf(file, sizeof file, "%s" PS "%s", certs_dir, domain);
 3782     chain = g_strdup("");
 3783     if ((trust = check_local_certs(file, cert, &chain)) == CERT_LOCAL)
 3784         col_str = XT_COLOR_BLUE;
 3785     if (t->pem)
 3786         g_free(t->pem);
 3787     t->pem = chain;
 3788 
 3789     snprintf(file, sizeof file, "%s" PS "%s", certs_cache_dir, domain);
 3790     if (warn_cert_changes) {
 3791         if (check_cert_changes(t, cert, file, uri))
 3792             nocolor = 1;
 3793     }
 3794 
 3795 done:
 3796     g_object_unref(msg);
 3797     if (!strcmp(col_str, XT_COLOR_WHITE) || nocolor) {
 3798 #if GTK_CHECK_VERSION(3, 0, 0)
 3799         gtk_widget_set_name(t->uri_entry, XT_CSS_NORMAL);
 3800         statusbar_modify_attr(t, XT_CSS_NORMAL);
 3801 #else
 3802         text = gdk_color_to_string(
 3803             &t->default_style->text[GTK_STATE_NORMAL]);
 3804         base = gdk_color_to_string(
 3805             &t->default_style->base[GTK_STATE_NORMAL]);
 3806         gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL,
 3807             &t->default_style->base[GTK_STATE_NORMAL]);
 3808         statusbar_modify_attr(t, text, base);
 3809         g_free(text);
 3810         g_free(base);
 3811 #endif
 3812     } else {
 3813 #if GTK_CHECK_VERSION(3, 0, 0)
 3814         name = get_css_name(col_str);
 3815         gtk_widget_set_name(t->uri_entry, name);
 3816         statusbar_modify_attr(t, name);
 3817         g_free(name);
 3818 #else
 3819         gdk_color_parse(col_str, &color);
 3820         gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
 3821         statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
 3822 #endif
 3823     }
 3824 }
 3825 
 3826 void
 3827 free_favicon(struct tab *t)
 3828 {
 3829     DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
 3830         __func__, t->icon_download, t->icon_request);
 3831 
 3832     if (t->icon_request)
 3833         g_object_unref(t->icon_request);
 3834     if (t->icon_dest_uri)
 3835         g_free(t->icon_dest_uri);
 3836 
 3837     t->icon_request = NULL;
 3838     t->icon_dest_uri = NULL;
 3839 }
 3840 
 3841 void
 3842 xt_icon_from_name(struct tab *t, gchar *name)
 3843 {
 3844     if (!enable_favicon_entry)
 3845         return;
 3846 
 3847     gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
 3848         GTK_ENTRY_ICON_PRIMARY, "text-html");
 3849     if (show_url == 0)
 3850         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
 3851             GTK_ENTRY_ICON_PRIMARY, "text-html");
 3852     else
 3853         gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
 3854             GTK_ENTRY_ICON_PRIMARY, NULL);
 3855 }
 3856 
 3857 void
 3858 xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
 3859 {
 3860     GdkPixbuf       *pb_scaled;
 3861 
 3862     if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
 3863         pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
 3864             GDK_INTERP_BILINEAR);
 3865     else
 3866         pb_scaled = pb;
 3867 
 3868     if (enable_favicon_entry) {
 3869 
 3870         /* Classic tabs. */
 3871         gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
 3872             GTK_ENTRY_ICON_PRIMARY, pb_scaled);
 3873 
 3874         /* Minimal tabs. */
 3875         if (show_url == 0) {
 3876             gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.uri),
 3877                 GTK_ENTRY_ICON_PRIMARY, pb_scaled);
 3878         } else
 3879             gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
 3880                 GTK_ENTRY_ICON_PRIMARY, NULL);
 3881     }
 3882 
 3883     /* XXX: Only supports the minimal tabs atm. */
 3884     if (enable_favicon_tabs)
 3885         gtk_image_set_from_pixbuf(GTK_IMAGE(t->tab_elems.favicon),
 3886             pb_scaled);
 3887 
 3888     if (pb_scaled != pb)
 3889         g_object_unref(pb_scaled);
 3890 }
 3891 
 3892 void
 3893 xt_icon_from_file(struct tab *t, char *uri)
 3894 {
 3895     GdkPixbuf       *pb;
 3896     char            *file;
 3897 
 3898     if (g_str_has_prefix(uri, "file://"))
 3899         file = g_filename_from_uri(uri, NULL, NULL);
 3900     else
 3901         file = g_strdup(uri);
 3902 
 3903     if (file == NULL)
 3904         return;
 3905 
 3906     pb = gdk_pixbuf_new_from_file(file, NULL);
 3907     if (pb) {
 3908         xt_icon_from_pixbuf(t, pb);
 3909         g_object_unref(pb);
 3910     } else
 3911         xt_icon_from_name(t, "text-html");
 3912 
 3913     g_free(file);
 3914 }
 3915 
 3916 gboolean
 3917 is_valid_icon(char *file)
 3918 {
 3919     gboolean        valid = 0;
 3920     const char      *mime_type;
 3921     GFileInfo       *fi;
 3922     GFile           *gf;
 3923 
 3924     gf = g_file_new_for_path(file);
 3925     fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
 3926         NULL, NULL);
 3927     mime_type = g_file_info_get_content_type(fi);
 3928     valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
 3929         g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
 3930         g_strcmp0(mime_type, "image/png") == 0 ||
 3931         g_strcmp0(mime_type, "image/gif") == 0 ||
 3932         g_strcmp0(mime_type, "application/octet-stream") == 0;
 3933     g_object_unref(fi);
 3934     g_object_unref(gf);
 3935 
 3936     return (valid);
 3937 }
 3938 
 3939 void
 3940 set_favicon_from_file(struct tab *t, char *uri)
 3941 {
 3942     struct stat     sb;
 3943     char            *file;
 3944 
 3945     if (t == NULL || uri == NULL)
 3946         return;
 3947 
 3948     if (g_str_has_prefix(uri, "file://"))
 3949         file = g_filename_from_uri(uri, NULL, NULL);
 3950     else
 3951         file = g_strdup(uri);
 3952 
 3953     if (file == NULL)
 3954         return;
 3955 
 3956     DNPRINTF(XT_D_DOWNLOAD, "%s: loading %s\n", __func__, file);
 3957 
 3958     if (!stat(file, &sb)) {
 3959         if (sb.st_size == 0 || !is_valid_icon(file)) {
 3960             /* corrupt icon so trash it */
 3961             DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
 3962                 __func__, file);
 3963             unlink(file);
 3964             /* no need to set icon to default here */
 3965             goto done;
 3966         }
 3967     }
 3968     xt_icon_from_file(t, file);
 3969 done:
 3970     g_free(file);
 3971 }
 3972 
 3973 void
 3974 favicon_download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
 3975     WebKitWebView *wv)
 3976 {
 3977     WebKitDownloadStatus    status = webkit_download_get_status(download);
 3978     struct tab      *tt = NULL, *t = NULL;
 3979 
 3980     /*
 3981      * find the webview instead of passing in the tab as it could have been
 3982      * deleted from underneath us.
 3983      */
 3984     TAILQ_FOREACH(tt, &tabs, entry) {
 3985         if (tt->wv == wv) {
 3986             t = tt;
 3987             break;
 3988         }
 3989     }
 3990     if (t == NULL)
 3991         return;
 3992 
 3993     DNPRINTF(XT_D_DOWNLOAD, "%s: tab %d status %d\n",
 3994         __func__, t->tab_id, status);
 3995 
 3996     switch (status) {
 3997     case WEBKIT_DOWNLOAD_STATUS_ERROR:
 3998         /* -1 */
 3999         t->icon_download = NULL;
 4000         free_favicon(t);
 4001         break;
 4002     case WEBKIT_DOWNLOAD_STATUS_CREATED:
 4003         /* 0 */
 4004         break;
 4005     case WEBKIT_DOWNLOAD_STATUS_STARTED:
 4006         /* 1 */
 4007         break;
 4008     case WEBKIT_DOWNLOAD_STATUS_CANCELLED:
 4009         /* 2 */
 4010         DNPRINTF(XT_D_DOWNLOAD, "%s: freeing favicon %d\n",
 4011             __func__, t->tab_id);
 4012         t->icon_download = NULL;
 4013         free_favicon(t);
 4014         break;
 4015     case WEBKIT_DOWNLOAD_STATUS_FINISHED:
 4016         /* 3 */
 4017 
 4018         DNPRINTF(XT_D_DOWNLOAD, "%s: setting icon to %s\n",
 4019             __func__, t->icon_dest_uri);
 4020         set_favicon_from_file(t, t->icon_dest_uri);
 4021         /* these will be freed post callback */
 4022         t->icon_request = NULL;
 4023         t->icon_download = NULL;
 4024         break;
 4025     default:
 4026         break;
 4027     }
 4028 }
 4029 
 4030 void
 4031 abort_favicon_download(struct tab *t)
 4032 {
 4033     DNPRINTF(XT_D_DOWNLOAD, "%s: down %p\n", __func__, t->icon_download);
 4034 
 4035 #if !WEBKIT_CHECK_VERSION(1, 4, 0)
 4036     if (t->icon_download) {
 4037         g_signal_handlers_disconnect_by_func(G_OBJECT(t->icon_download),
 4038             G_CALLBACK(favicon_download_status_changed_cb), t->wv);
 4039         webkit_download_cancel(t->icon_download);
 4040         t->icon_download = NULL;
 4041     } else
 4042         free_favicon(t);
 4043 #endif
 4044 
 4045     xt_icon_from_name(t, "text-html");
 4046 }
 4047 
 4048 void
 4049 notify_icon_loaded_cb(WebKitWebView *wv, gchar *uri, struct tab *t)
 4050 {
 4051     DNPRINTF(XT_D_DOWNLOAD, "%s %s\n", __func__, uri);
 4052 
 4053     if (uri == NULL || t == NULL)
 4054         return;
 4055 
 4056 #if WEBKIT_CHECK_VERSION(1, 4, 0)
 4057     /* take icon from WebKitIconDatabase */
 4058     GdkPixbuf       *pb = NULL;
 4059 
 4060 /* webkit_web_view_get_icon_pixbuf is depreciated in 1.8 */
 4061 #if WEBKIT_CHECK_VERSION(1, 8, 0)
 4062     /*
 4063      * If the page was not loaded (for example, via ssl_strict_certs), do
 4064      * not attempt to get the webview's pixbuf.  This prevents a CRITICAL
 4065      * glib warning.
 4066      */
 4067     if (wv && webkit_web_view_get_uri(wv))
 4068         pb = webkit_web_view_try_get_favicon_pixbuf(wv, 0, 0);
 4069 #else
 4070     if (wv && webkit_web_view_get_uri(wv))
 4071         pb = webkit_web_view_get_icon_pixbuf(wv);
 4072 #endif
 4073     if (pb) {
 4074         xt_icon_from_pixbuf(t, pb);
 4075         g_object_unref(pb);
 4076     } else
 4077         xt_icon_from_name(t, "text-html");
 4078 #elif WEBKIT_CHECK_VERSION(1, 1, 18)
 4079     /* download icon to cache dir */
 4080     gchar           *name_hash, file[PATH_MAX];
 4081     struct stat     sb;
 4082 
 4083     if (t->icon_request) {
 4084         DNPRINTF(XT_D_DOWNLOAD, "%s: download in progress\n", __func__);
 4085         return;
 4086     }
 4087 
 4088     /* check to see if we got the icon in cache */
 4089     name_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, uri, -1);
 4090     snprintf(file, sizeof file, "%s" PS "%s.ico", cache_dir, name_hash);
 4091     g_free(name_hash);
 4092 
 4093     if (!stat(file, &sb)) {
 4094         if (sb.st_size > 0) {
 4095             DNPRINTF(XT_D_DOWNLOAD, "%s: loading from cache %s\n",
 4096                 __func__, file);
 4097             set_favicon_from_file(t, file);
 4098             return;
 4099         }
 4100 
 4101         /* corrupt icon so trash it */
 4102         DNPRINTF(XT_D_DOWNLOAD, "%s: corrupt icon %s\n",
 4103             __func__, file);
 4104         unlink(file);
 4105     }
 4106 
 4107     /* create download for icon */
 4108     t->icon_request = webkit_network_request_new(uri);
 4109     if (t->icon_request == NULL) {
 4110         DNPRINTF(XT_D_DOWNLOAD, "%s: invalid uri %s\n",
 4111             __func__, uri);
 4112         return;
 4113     }
 4114 
 4115     t->icon_download = webkit_download_new(t->icon_request);
 4116     if (t->icon_download == NULL)
 4117         return;
 4118 
 4119     /* we have to free icon_dest_uri later */
 4120     if ((t->icon_dest_uri = g_filename_to_uri(file, NULL, NULL)) == NULL)
 4121         return;
 4122     webkit_download_set_destination_uri(t->icon_download,
 4123         t->icon_dest_uri);
 4124 
 4125     if (webkit_download_get_status(t->icon_download) ==
 4126         WEBKIT_DOWNLOAD_STATUS_ERROR) {
 4127         g_object_unref(t->icon_request);
 4128         g_free(t->icon_dest_uri);
 4129         t->icon_request = NULL;
 4130         t->icon_dest_uri = NULL;
 4131         return;
 4132     }
 4133 
 4134     g_signal_connect(G_OBJECT(t->icon_download), "notify::status",
 4135         G_CALLBACK(favicon_download_status_changed_cb), t->wv);
 4136 
 4137     webkit_download_start(t->icon_download);
 4138 #endif
 4139 }
 4140 
 4141 void
 4142 notify_load_status_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
 4143 {
 4144     const gchar     *uri = NULL;
 4145     struct history      *h, find;
 4146     struct karg     a;
 4147     gchar           *tmp_uri = NULL;
 4148 #if !GTK_CHECK_VERSION(3, 0, 0)
 4149     gchar           *text, *base;
 4150 #endif
 4151 
 4152     DNPRINTF(XT_D_URL, "notify_load_status_cb: %d  %s\n",
 4153         webkit_web_view_get_load_status(wview),
 4154         get_uri(t) ? get_uri(t) : "NOTHING");
 4155 
 4156     if (t == NULL) {
 4157         show_oops(NULL, "notify_load_status_cb invalid parameters");
 4158         return;
 4159     }
 4160 
 4161     switch (webkit_web_view_get_load_status(wview)) {
 4162     case WEBKIT_LOAD_PROVISIONAL:
 4163         /* 0 */
 4164         abort_favicon_download(t);
 4165 #if GTK_CHECK_VERSION(2, 20, 0)
 4166         gtk_widget_show(t->spinner);
 4167         gtk_spinner_start(GTK_SPINNER(t->spinner));
 4168 #endif
 4169         t->download_requested = 0;
 4170 
 4171         gtk_widget_set_sensitive(GTK_WIDGET(t->stop), TRUE);
 4172 
 4173         /* assume we are a new address */
 4174 #if GTK_CHECK_VERSION(3, 0, 0)
 4175         gtk_widget_set_name(t->uri_entry, XT_CSS_NORMAL);
 4176         statusbar_modify_attr(t, XT_CSS_NORMAL);
 4177 #else
 4178         text = gdk_color_to_string(
 4179             &t->default_style->text[GTK_STATE_NORMAL]);
 4180         base = gdk_color_to_string(
 4181             &t->default_style->base[GTK_STATE_NORMAL]);
 4182         gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL,
 4183             &t->default_style->base[GTK_STATE_NORMAL]);
 4184         statusbar_modify_attr(t, text, base);
 4185         g_free(text);
 4186         g_free(base);
 4187 #endif
 4188 
 4189         /* DOM is changing, unreference the previous focused element */
 4190 #if WEBKIT_CHECK_VERSION(1, 5, 0)
 4191         if (t->active)
 4192             g_object_unref(t->active);
 4193         t->active = NULL;
 4194         if (t->active_text) {
 4195             g_free(t->active_text);
 4196             t->active_text = NULL;
 4197         }
 4198 #endif
 4199 
 4200         /* take focus if we are visible */
 4201         focus_webview(t);
 4202         t->focus_wv = 1;
 4203 
 4204         marks_clear(t);
 4205         break;
 4206 
 4207     case WEBKIT_LOAD_COMMITTED:
 4208         /* 1 */
 4209         uri = get_uri(t);
 4210         if (uri == NULL)
 4211             return;
 4212         gtk_entry_set_text(GTK_ENTRY(t->uri_entry), uri);
 4213 
 4214         if (t->status) {
 4215             g_free(t->status);
 4216             t->status = NULL;
 4217         }
 4218         set_status(t, "Loading: %s", (char *)uri);
 4219 
 4220         /* clear t->item, except if we're switching to an about: page */
 4221         if (t->item && !g_str_has_prefix(uri, "xxxt://") &&
 4222             !g_str_has_prefix(uri, "about:")) {
 4223             g_object_unref(t->item);
 4224             t->item = NULL;
 4225         }
 4226 
 4227         /* check if js white listing is enabled */
 4228         if (enable_plugin_whitelist)
 4229             check_and_set_pl(uri, t);
 4230         if (enable_cookie_whitelist)
 4231             check_and_set_cookie(uri, t);
 4232         if (enable_js_whitelist)
 4233             check_and_set_js(uri, t);
 4234 
 4235         if (t->styled)
 4236             apply_style(t);
 4237 
 4238 
 4239         /* we know enough to autosave the session */
 4240         if (session_autosave) {
 4241             a.s = NULL;
 4242             save_tabs(t, &a);
 4243         }
 4244 
 4245         show_ca_status(t, uri);
 4246         run_script(t, JS_HINTING);
 4247         if (enable_autoscroll)
 4248             run_script(t, JS_AUTOSCROLL);
 4249         break;
 4250 
 4251     case WEBKIT_LOAD_FIRST_VISUALLY_NON_EMPTY_LAYOUT:
 4252         /* 3 */
 4253         if (color_visited_uris) {
 4254             color_visited(t, color_visited_helper());
 4255 
 4256             /*
 4257              * This colors the links you middle-click (open in new
 4258              * tab) in the current tab.
 4259              */
 4260             if (t->tab_id != gtk_notebook_get_current_page(notebook) &&
 4261                 (uri = get_uri(t)) != NULL)
 4262                 color_visited(get_current_tab(),
 4263                     g_strdup_printf("{'%s' : 'dummy'}", uri));
 4264         }
 4265         break;
 4266 
 4267     case WEBKIT_LOAD_FINISHED:
 4268         /* 2 */
 4269         if ((uri = get_uri(t)) == NULL)
 4270             return;
 4271         /* 
 4272          * js_autorun calls get_uri which frees t->tmp_uri if on an
 4273          * "about:" page. On "about:" pages, uri points to t->tmp_uri.
 4274          * I.e. we will use freed memory. Prevent that.
 4275          */
 4276         tmp_uri = g_strdup(uri);
 4277 
 4278         /* autorun some js if enabled */
 4279         js_autorun(t);
 4280 
 4281         input_autofocus(t);
 4282 
 4283         if (!strncmp(tmp_uri, "http://", strlen("http://")) ||
 4284             !strncmp(tmp_uri, "https://", strlen("https://")) ||
 4285             !strncmp(tmp_uri, "file://", strlen("file://"))) {
 4286             find.uri = (gchar *)tmp_uri;
 4287             h = RB_FIND(history_list, &hl, &find);
 4288             if (!h)
 4289                 insert_history_item(tmp_uri,
 4290                     get_title(t, FALSE), time(NULL));
 4291             else
 4292                 h->time = time(NULL);
 4293         }
 4294 
 4295         if (statusbar_style == XT_STATUSBAR_URL)
 4296             set_status(t, "%s", (char *)tmp_uri);
 4297         else
 4298             set_status(t, "%s", get_title(t, FALSE));
 4299         gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
 4300 #if GTK_CHECK_VERSION(2, 20, 0)
 4301         gtk_spinner_stop(GTK_SPINNER(t->spinner));
 4302         gtk_widget_hide(t->spinner);
 4303 #endif
 4304         g_free(tmp_uri);
 4305         break;
 4306 
 4307 #if WEBKIT_CHECK_VERSION(1, 1, 18)
 4308     case WEBKIT_LOAD_FAILED:
 4309         /* 4 */
 4310         if (!t->download_requested) {
 4311             gtk_label_set_text(GTK_LABEL(t->label),
 4312                 get_title(t, FALSE));
 4313             gtk_label_set_text(GTK_LABEL(t->tab_elems.label),
 4314                 get_title(t, FALSE));
 4315             set_status(t, "%s", (char *)get_title(t, FALSE));
 4316             gtk_window_set_title(GTK_WINDOW(main_window),
 4317                 get_title(t, TRUE));
 4318         } else {
 4319 
 4320         }
 4321 #endif
 4322     default:
 4323 #if GTK_CHECK_VERSION(2, 20, 0)
 4324         gtk_spinner_stop(GTK_SPINNER(t->spinner));
 4325         gtk_widget_hide(t->spinner);
 4326 #endif
 4327         gtk_widget_set_sensitive(GTK_WIDGET(t->stop), FALSE);
 4328         break;
 4329     }
 4330 
 4331     gtk_widget_set_sensitive(GTK_WIDGET(t->backward),
 4332         can_go_back_for_real(t));
 4333 
 4334     gtk_widget_set_sensitive(GTK_WIDGET(t->forward),
 4335         can_go_forward_for_real(t));
 4336 }
 4337 
 4338 void
 4339 notify_title_cb(WebKitWebView* wview, GParamSpec* pspec, struct tab *t)
 4340 {
 4341     const gchar     *title = NULL, *win_title = NULL;
 4342 
 4343     title = get_title(t, FALSE);
 4344     win_title = get_title(t, TRUE);
 4345     if (title) {
 4346         gtk_label_set_text(GTK_LABEL(t->label), title);
 4347         gtk_label_set_text(GTK_LABEL(t->tab_elems.label), title);
 4348     }
 4349 
 4350     if (win_title && t->tab_id == gtk_notebook_get_current_page(notebook))
 4351         gtk_window_set_title(GTK_WINDOW(main_window), win_title);
 4352 }
 4353 
 4354 char *
 4355 get_domain(const gchar *host)
 4356 {
 4357     size_t      x;
 4358     char        *p;
 4359 
 4360     /* handle silly domains like .co.uk */
 4361 
 4362     if ((x = strlen(host)) <= 6)
 4363         return (g_strdup(host));
 4364 
 4365     if (host[x - 3] == '.' && host[x - 6] == '.') {
 4366         x = x - 7;
 4367         while (x > 0)
 4368             if (host[x] != '.')
 4369                 x--;
 4370             else
 4371                 return (g_strdup(&host[x + 1]));
 4372     }
 4373 
 4374     /* find first . */
 4375     p = g_strrstr(host, ".");
 4376     if (p == NULL)
 4377         return (g_strdup(""));
 4378     p--;
 4379     while (p > host)
 4380         if (*p != '.')
 4381             p--;
 4382         else
 4383             return (g_strdup(p + 1));
 4384 
 4385     return (g_strdup(host));
 4386 }
 4387 
 4388 void
 4389 js_autorun(struct tab *t)
 4390 {
 4391     SoupURI         *su = NULL;
 4392     const gchar     *uri;
 4393     size_t          got_default = 0, got_host = 0;
 4394     struct stat     sb;
 4395     char            deff[PATH_MAX], hostf[PATH_MAX];
 4396     char            *js = NULL, *jsat, *domain = NULL;
 4397     FILE            *deffile = NULL, *hostfile = NULL;
 4398 
 4399     if (enable_js_autorun == 0)
 4400         return;
 4401 
 4402     uri = get_uri(t);
 4403     if (uri &&
 4404         !(g_str_has_prefix(uri, "http://") ||
 4405         g_str_has_prefix(uri, "https://")))
 4406         goto done;
 4407 
 4408     su = soup_uri_new(uri);
 4409     if (su == NULL)
 4410         goto done;
 4411     if (!SOUP_URI_VALID_FOR_HTTP(su))
 4412         goto done;
 4413 
 4414     DNPRINTF(XT_D_JS, "%s: host: %s domain: %s\n", __func__,
 4415         su->host, domain);
 4416     domain = get_domain(su->host);
 4417 
 4418     snprintf(deff, sizeof deff, "%s" PS "default.js", js_dir);
 4419     if ((deffile = fopen(deff, "r")) != NULL) {
 4420         if (fstat(fileno(deffile), &sb) == -1) {
 4421             show_oops(t, "can't stat default JS file");
 4422             goto done;
 4423         }
 4424         got_default = sb.st_size;
 4425     }
 4426 
 4427     /* try host first followed by domain */
 4428     snprintf(hostf, sizeof hostf, "%s" PS "%s.js", js_dir, su->host);
 4429     DNPRINTF(XT_D_JS, "trying file: %s\n", hostf);
 4430     if ((hostfile = fopen(hostf, "r")) == NULL) {
 4431         snprintf(hostf, sizeof hostf, "%s" PS "%s.js", js_dir, domain);
 4432         DNPRINTF(XT_D_JS, "trying file: %s\n", hostf);
 4433         if ((hostfile = fopen(hostf, "r")) == NULL)
 4434             goto nofile;
 4435     }
 4436     DNPRINTF(XT_D_JS, "file: %s\n", hostf);
 4437     if (fstat(fileno(hostfile), &sb) == -1) {
 4438         show_oops(t, "can't stat %s JS file", hostf);
 4439         goto done;
 4440     }
 4441     got_host = sb.st_size;
 4442 
 4443 nofile:
 4444     if (got_default + got_host == 0)
 4445         goto done;
 4446 
 4447     js = g_malloc0(got_default + got_host + 1);
 4448     jsat = js;
 4449 
 4450     if (got_default) {
 4451         if (fread(js, got_default, 1, deffile) != 1) {
 4452             show_oops(t, "default file read error");
 4453             goto done;
 4454         }
 4455         jsat = js + got_default;
 4456     }
 4457 
 4458     if (got_host) {
 4459         if (fread(jsat, got_host, 1, hostfile) != 1) {
 4460             show_oops(t, "host file read error");
 4461             goto done;
 4462         }
 4463     }
 4464 
 4465     DNPRINTF(XT_D_JS, "%s: about to run script\n", __func__);
 4466     run_script(t, js);
 4467 
 4468 done:
 4469     if (su)
 4470         soup_uri_free(su);
 4471     if (js)
 4472         g_free(js);
 4473     if (deffile)
 4474         fclose(deffile);
 4475     if (hostfile)
 4476         fclose(hostfile);
 4477     if (domain)
 4478         g_free(domain);
 4479 }
 4480 
 4481 void
 4482 webview_progress_changed_cb(WebKitWebView *wv, GParamSpec *pspec, struct tab *t)
 4483 {
 4484     gdouble         progress;
 4485 
 4486     progress = webkit_web_view_get_progress(wv);
 4487     gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.uri),
 4488         progress > (1.0 - 0.0001) ? 0 : progress);
 4489     gtk_entry_set_progress_fraction(GTK_ENTRY(t->uri_entry),
 4490         progress > (1.0 - 0.0001) ? 0 : progress);
 4491 
 4492     update_statusbar_position(NULL, NULL);
 4493 }
 4494 
 4495 int
 4496 strict_transport_rb_cmp(struct strict_transport *a, struct strict_transport *b)
 4497 {
 4498     char            *p1, *p2;
 4499     int         l1, l2;
 4500 
 4501     /* compare strings from the end */
 4502     l1 = strlen(a->host);
 4503     l2 = strlen(b->host);
 4504 
 4505     p1 = a->host + l1;
 4506     p2 = b->host + l2;
 4507     for (; *p1 == *p2 && p1 > a->host && p2 > b->host;
 4508         p1--, p2--)
 4509         ;
 4510 
 4511     /*
 4512      * Check if we need to do pattern expansion,
 4513      * or if we're just keeping the tree in order
 4514      */
 4515     if (a->flags & XT_STS_FLAGS_EXPAND &&
 4516         b->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS) {
 4517         /* Check if we're matching the
 4518          * 'host.xyz' part in '*.host.xyz'
 4519          */
 4520         if (p2 == b->host && (p1 == a->host || *(p1-1) == '.')) {
 4521             return (0);
 4522         }
 4523     }
 4524 
 4525     if (p1 == a->host && p2 == b->host)
 4526         return (0);
 4527     if (p1 == a->host)
 4528         return (1);
 4529     if (p2 == b->host)
 4530         return (-1);
 4531 
 4532     if (*p1 < *p2)
 4533         return (-1);
 4534     if (*p1 > *p2)
 4535         return (1);
 4536 
 4537     return (0);
 4538 }
 4539 RB_GENERATE(strict_transport_tree, strict_transport, entry,
 4540     strict_transport_rb_cmp);
 4541 
 4542 int
 4543 strict_transport_add(const char *domain, time_t timeout, int subdomains)
 4544 {
 4545     struct strict_transport *d, find;
 4546     time_t          now;
 4547     FILE            *f;
 4548 
 4549     if (enable_strict_transport == FALSE)
 4550         return (0);
 4551 
 4552     DPRINTF("strict_transport_add(%s,%" PRIi64 ",%d)\n", domain,
 4553         (uint64_t)timeout, subdomains);
 4554 
 4555     now = time(NULL);
 4556     if (timeout < now)
 4557         return (0);
 4558 
 4559     find.host = (char *)domain;
 4560     find.flags = 0;
 4561     d = RB_FIND(strict_transport_tree, &st_tree, &find);
 4562 
 4563     /* update flags */
 4564     if (d) {
 4565         /* check if update is needed */
 4566         if (d->timeout == timeout &&
 4567             (d->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS) == subdomains)
 4568             return (0);
 4569 
 4570         d->timeout = timeout;
 4571         if (subdomains)
 4572             d->flags |= XT_STS_FLAGS_INCLUDE_SUBDOMAINS;
 4573 
 4574         /* We're still initializing */
 4575         if (strict_transport_file == NULL)
 4576             return (0);
 4577 
 4578         if ((f = fopen(strict_transport_file, "w")) == NULL) {
 4579             show_oops(NULL,
 4580                 "can't open strict-transport rules file");
 4581             return (1);
 4582         }
 4583 
 4584         fprintf(f, "# Generated file - do not update unless you know "
 4585             "what you're doing\n");
 4586         RB_FOREACH(d, strict_transport_tree, &st_tree) {
 4587             if (d->timeout < now)
 4588                 continue;
 4589             fprintf(f, "%s\t%" PRIi64 "\t%d\n", d->host,
 4590                 (uint64_t)d->timeout,
 4591                 d->flags & XT_STS_FLAGS_INCLUDE_SUBDOMAINS);
 4592         }
 4593         fclose(f);
 4594     } else {
 4595         d = g_malloc(sizeof *d);
 4596         d->host= g_strdup(domain);
 4597         d->timeout = timeout;
 4598         if (subdomains)
 4599             d->flags = XT_STS_FLAGS_INCLUDE_SUBDOMAINS;
 4600         else
 4601             d->flags = 0;
 4602         RB_INSERT(strict_transport_tree, &st_tree, d);
 4603 
 4604         /* We're still initializing */
 4605         if (strict_transport_file == NULL)
 4606             return (0);
 4607 
 4608         if ((f = fopen(strict_transport_file, "a+")) == NULL) {
 4609             show_oops(NULL,
 4610                 "can't open strict-transport rules file");
 4611             return (1);
 4612         }
 4613 
 4614         fseek(f, 0, SEEK_END);
 4615         fprintf(f,"%s\t%" PRIi64 "\t%d\n", d->host, (uint64_t)timeout,
 4616             subdomains);
 4617         fclose(f);
 4618     }
 4619     return (0);
 4620 }
 4621 
 4622 int
 4623 strict_transport_check(const char *host)
 4624 {
 4625     static struct strict_transport  *d = NULL;
 4626     struct strict_transport     find;
 4627 
 4628     if (enable_strict_transport == FALSE)
 4629         return (0);
 4630 
 4631     find.host = (char *)host;
 4632 
 4633     /* match for domains that include subdomains */
 4634     find.flags = XT_STS_FLAGS_EXPAND;
 4635 
 4636     /* First, check if we're already at the right node */
 4637     if (d != NULL && strict_transport_rb_cmp(&find, d) == 0) {
 4638         return (1);
 4639     }
 4640 
 4641     d = RB_FIND(strict_transport_tree, &st_tree, &find);
 4642     if (d != NULL)
 4643         return (1);
 4644 
 4645     return (0);
 4646 }
 4647 
 4648 int
 4649 strict_transport_init()
 4650 {
 4651     char            file[PATH_MAX];
 4652     char            delim[3];
 4653     char            *rule;
 4654     char            *ptr;
 4655     FILE            *f;
 4656     size_t          len;
 4657     time_t          timeout, now;
 4658     int         subdomains;
 4659 
 4660     snprintf(file, sizeof file, "%s" PS "%s", work_dir, XT_STS_FILE);
 4661     if ((f = fopen(file, "r")) == NULL) {
 4662         strict_transport_file = g_strdup(file);
 4663         return (0);
 4664     }
 4665 
 4666     delim[0] = '\\';
 4667     delim[1] = '\\';
 4668     delim[2] = '#';
 4669     rule = NULL;
 4670     now = time(NULL);
 4671 
 4672     for (;;) {
 4673         if ((rule = fparseln(f, &len, NULL, delim, 0)) == NULL) {
 4674             if (!feof(f) || ferror(f))
 4675                 goto corrupt_file;
 4676             else
 4677                 break;
 4678         }
 4679 
 4680         /* get second entry */
 4681         if ((ptr = strpbrk(rule, " \t")) == NULL)
 4682             goto corrupt_file;
 4683 
 4684         *ptr++ = '\0';
 4685         timeout = atoi(ptr);
 4686 
 4687         /* get third entry */
 4688         if ((ptr = strpbrk(ptr, " \t")) == NULL)
 4689             goto corrupt_file;
 4690 
 4691         ptr ++;
 4692         subdomains = atoi(ptr);
 4693 
 4694         if (timeout > now)
 4695             strict_transport_add(rule, timeout, subdomains);
 4696         free(rule);
 4697     }
 4698 
 4699     fclose(f);
 4700     strict_transport_file = g_strdup(file);
 4701     return (0);
 4702 
 4703 corrupt_file:
 4704     startpage_add("strict-transport rules file ('%s') is corrupt", file);
 4705     if (rule)
 4706         free(rule);
 4707     fclose(f);
 4708     return (1);
 4709 }
 4710 
 4711 int
 4712 force_https_check(const char *uri)
 4713 {
 4714     struct wl_entry     *w = NULL;
 4715 
 4716     if (uri == NULL)
 4717         return (0);
 4718 
 4719     if ((w = wl_find_uri(uri, &force_https)) == NULL)
 4720         return (0);
 4721     else
 4722         return (1);
 4723 }
 4724 
 4725 void
 4726 strict_transport_security_cb(SoupMessage *msg, gpointer data)
 4727 {
 4728     SoupURI     *uri;
 4729     const char  *sts;
 4730     char        *ptr;
 4731     time_t      timeout = 0;
 4732     int     subdomains = FALSE;
 4733 
 4734     if (msg == NULL)
 4735         return;
 4736 
 4737     sts = soup_message_headers_get_one(msg->response_headers,
 4738         "Strict-Transport-Security");
 4739     uri = soup_message_get_uri(msg);
 4740 
 4741     if (sts == NULL || uri == NULL)
 4742         return;
 4743 
 4744     if ((ptr = strcasestr(sts, "max-age="))) {
 4745         ptr += strlen("max-age=");
 4746         timeout = atoll(ptr);
 4747     } else
 4748         return; /* malformed header - max-age must be included */
 4749 
 4750     if ((ptr = strcasestr(sts, "includeSubDomains")))
 4751         subdomains = TRUE;
 4752 
 4753     strict_transport_add(uri->host, timeout + time(NULL), subdomains);
 4754 }
 4755 
 4756 void
 4757 session_rq_cb(SoupSession *s, SoupMessage *msg, SoupSocket *socket,
 4758     gpointer data)
 4759 {
 4760     SoupURI         *dest;
 4761     SoupURI         *ref_uri;
 4762     const char      *ref;
 4763 
 4764     char            *ref_suffix;
 4765     char            *dest_suffix;
 4766 
 4767     if (s == NULL || msg == NULL)
 4768         return;
 4769 
 4770     if (enable_strict_transport) {
 4771         soup_message_add_header_handler(msg, "finished",
 4772             "Strict-Transport-Security",
 4773             G_CALLBACK(strict_transport_security_cb), NULL);
 4774     }
 4775 
 4776     if (referer_mode == XT_REFERER_ALWAYS)
 4777         return;
 4778 
 4779     /* Check if referer is set - and what the user requested for referers */
 4780     ref = soup_message_headers_get_one(msg->request_headers, "Referer");
 4781     if (ref) {
 4782         DNPRINTF(XT_D_NAV, "session_rq_cb: Referer: %s\n", ref);
 4783         switch (referer_mode) {
 4784         case XT_REFERER_NEVER:
 4785             DNPRINTF(XT_D_NAV, "session_rq_cb: removing referer\n");
 4786             soup_message_headers_remove(msg->request_headers,
 4787                 "Referer");
 4788             break;
 4789         case XT_REFERER_SAME_DOMAIN:
 4790             ref_uri = soup_uri_new(ref);
 4791             dest = soup_message_get_uri(msg);
 4792 
 4793             if (ref_uri == NULL || dest == NULL)
 4794                 return;
 4795 
 4796             ref_suffix = tld_get_suffix(ref_uri->host);
 4797             dest_suffix = tld_get_suffix(dest->host);
 4798 
 4799             if (ref_suffix && dest_suffix &&
 4800                 strcmp(ref_suffix, dest_suffix) != 0) {
 4801                 soup_message_headers_remove(msg->request_headers,
 4802                     "Referer");
 4803                 DNPRINTF(XT_D_NAV, "session_rq_cb: removing "
 4804                     "referer (not same domain) (suffixes: %s - %s)\n",
 4805                     ref_suffix, dest_suffix);
 4806             }
 4807             soup_uri_free(ref_uri);
 4808             break;
 4809         case XT_REFERER_SAME_FQDN:
 4810             ref_uri = soup_uri_new(ref);
 4811             dest = soup_message_get_uri(msg);
 4812 
 4813             if (ref_uri == NULL || dest == NULL)
 4814                 return;
 4815 
 4816             if (strcmp(ref_uri->host, dest->host) != 0) {
 4817                 soup_message_headers_remove(msg->request_headers,
 4818                     "Referer");
 4819                 DNPRINTF(XT_D_NAV, "session_rq_cb: removing "
 4820                     "referer (not same fqdn) (should be %s)\n",
 4821                     dest->host);
 4822             }
 4823             soup_uri_free(ref_uri);
 4824             break;
 4825         case XT_REFERER_CUSTOM:
 4826             DNPRINTF(XT_D_NAV, "session_rq_cb: setting referer "
 4827                 "to %s\n", referer_custom);
 4828             soup_message_headers_replace(msg->request_headers,
 4829                 "Referer", referer_custom);
 4830             break;
 4831         }
 4832     }
 4833 }
 4834 
 4835 int
 4836 webview_npd_cb(WebKitWebView *wv, WebKitWebFrame *wf,
 4837     WebKitNetworkRequest *request, WebKitWebNavigationAction *na,
 4838     WebKitWebPolicyDecision *pd, struct tab *t)
 4839 {
 4840     WebKitWebNavigationReason   reason;
 4841     char                *uri;
 4842 
 4843     if (t == NULL) {
 4844         show_oops(NULL, "webview_npd_cb invalid parameters");
 4845         return (FALSE);
 4846     }
 4847 
 4848     DNPRINTF(XT_D_NAV, "webview_npd_cb: ctrl_click %d %s\n",
 4849         t->ctrl_click,
 4850         webkit_network_request_get_uri(request));
 4851 
 4852     uri = (char *)webkit_network_request_get_uri(request);
 4853 
 4854     if (!auto_load_images && t->load_images) {
 4855 
 4856         /* Disable autoloading of images, now that we're done loading
 4857          * them. */
 4858         g_object_set(G_OBJECT(t->settings),
 4859             "auto-load-images", FALSE, (char *)NULL);
 4860         webkit_web_view_set_settings(t->wv, t->settings);
 4861 
 4862         t->load_images = FALSE;
 4863     }
 4864 
 4865     /* If this is an xtp url, we don't load anything else. */
 4866     if (parse_xtp_url(t, uri)) {
 4867         webkit_web_policy_decision_ignore(pd);
 4868         return (TRUE);
 4869     }
 4870 
 4871     if (parse_custom_uri(t, uri)) {
 4872         webkit_web_policy_decision_ignore(pd);
 4873         return (TRUE);
 4874     }
 4875 
 4876     if (valid_url_type(uri)) {
 4877         show_oops(t, "Stopping attempt to load an invalid URI (possible"
 4878             " bait and switch attack)");
 4879         webkit_web_policy_decision_ignore(pd);
 4880         return (TRUE);
 4881     }
 4882 
 4883     if ((t->mode == XT_MODE_HINT && t->new_tab) || t->ctrl_click) {
 4884         t->ctrl_click = 0;
 4885         create_new_tab(uri, NULL, ctrl_click_focus, -1);
 4886         webkit_web_policy_decision_ignore(pd);
 4887         return (TRUE); /* we made the decission */
 4888     }
 4889 
 4890     /*
 4891      * This is a little hairy but it comes down to this:
 4892      * when we run in whitelist mode we have to assist the browser in
 4893      * opening the URL that it would have opened in a new tab.
 4894      */
 4895     reason = webkit_web_navigation_action_get_reason(na);
 4896     if (reason == WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED) {
 4897         set_normal_tab_meaning(t);
 4898         if (enable_scripts == 0 && enable_cookie_whitelist == 1)
 4899             load_uri(t, uri);
 4900         webkit_web_policy_decision_use(pd);
 4901         return (TRUE); /* we made the decision */
 4902     }
 4903 
 4904     return (FALSE);
 4905 }
 4906 
 4907 void
 4908 webview_rrs_cb(WebKitWebView *wv, WebKitWebFrame *wf, WebKitWebResource *res,
 4909     WebKitNetworkRequest *request, WebKitNetworkResponse *response,
 4910     struct tab *t)
 4911 {
 4912     SoupMessage     *msg = NULL;
 4913     SoupURI         *uri = NULL;
 4914     struct http_accept  ha_find, *ha = NULL;
 4915     struct user_agent   ua_find, *ua = NULL;
 4916     struct domain_id    di_find, *di = NULL;
 4917     char            *uri_s = NULL;
 4918 
 4919     msg = webkit_network_request_get_message(request);
 4920     if (!msg)
 4921         return;
 4922 
 4923     uri = soup_message_get_uri(msg);
 4924     if (!uri)
 4925         return;
 4926     uri_s = soup_uri_to_string(uri, FALSE);
 4927 
 4928     if (strcmp(uri->scheme, SOUP_URI_SCHEME_HTTP) == 0) {
 4929         if (strict_transport_check(uri->host) ||
 4930             force_https_check(uri_s)) {
 4931             DNPRINTF(XT_D_NAV, "webview_rrs_cb: force https for %s\n",
 4932                     uri->host);
 4933             soup_uri_set_scheme(uri, SOUP_URI_SCHEME_HTTPS);
 4934         }
 4935     }
 4936 
 4937     if (do_not_track)
 4938         soup_message_headers_append(msg->request_headers, "DNT", "1");
 4939 
 4940     /*
 4941      * Check if resources on this domain have been loaded before.  If
 4942      * not, add the current tab's http-accept and user-agent id's to a
 4943      * new domain_id and insert into the RB tree.  Use these http headers
 4944      * for all resources loaded from this domain for the lifetime of the
 4945      * browser.
 4946      */
 4947     if ((di_find.domain = uri->host) == NULL)
 4948         goto done;
 4949     if ((di = RB_FIND(domain_id_list, &di_list, &di_find)) == NULL) {
 4950         di = g_malloc(sizeof *di);
 4951         di->domain = g_strdup(uri->host);
 4952         di->ua_id = t->user_agent_id++;
 4953         di->ha_id = t->http_accept_id++;
 4954         RB_INSERT(domain_id_list, &di_list, di);
 4955 
 4956         ua_find.id = t->user_agent_id;
 4957         ua = RB_FIND(user_agent_list, &ua_list, &ua_find);
 4958         if (ua == NULL)
 4959             t->user_agent_id = 0;
 4960 
 4961         ha_find.id = t->http_accept_id;
 4962         ha = RB_FIND(http_accept_list, &ha_list, &ha_find);
 4963         if (ha == NULL)
 4964             t->http_accept_id = 0;
 4965     }
 4966 
 4967     ua_find.id = di->ua_id;
 4968     ua = RB_FIND(user_agent_list, &ua_list, &ua_find);
 4969     ha_find.id = di->ha_id;
 4970     ha = RB_FIND(http_accept_list, &ha_list, &ha_find);
 4971 
 4972     if (ua != NULL)
 4973         soup_message_headers_replace(msg->request_headers,
 4974             "User-Agent", ua->value);
 4975     if (ha != NULL)
 4976         soup_message_headers_replace(msg->request_headers,
 4977             "Accept", ha->value);
 4978 
 4979 done:
 4980     if (uri_s)
 4981         g_free(uri_s);
 4982 }
 4983 
 4984 WebKitWebView *
 4985 webview_cwv_cb(WebKitWebView *wv, WebKitWebFrame *wf, struct tab *t)
 4986 {
 4987     struct tab      *tt;
 4988     struct wl_entry     *w = NULL;
 4989     const gchar     *uri;
 4990     WebKitWebView       *webview = NULL;
 4991     int         x = 1;
 4992 
 4993     DNPRINTF(XT_D_NAV, "webview_cwv_cb: %s\n",
 4994         webkit_web_view_get_uri(wv));
 4995 
 4996     if (tabless) {
 4997         /* open in current tab */
 4998         webview = t->wv;
 4999     } else if (enable_scripts == 0 && enable_js_whitelist == 1) {
 5000         uri = webkit_web_view_get_uri(wv);
 5001         if (uri && (w = wl_find_uri(uri, &js_wl)) == NULL)
 5002             return (NULL);
 5003 
 5004         if (t->ctrl_click) {
 5005             x = ctrl_click_focus;
 5006             t->ctrl_click = 0;
 5007         }
 5008         tt = create_new_tab(NULL, NULL, x, -1);
 5009         webview = tt->wv;
 5010     } else if (enable_scripts == 1) {
 5011         if (t->ctrl_click) {
 5012             x = ctrl_click_focus;
 5013             t->ctrl_click = 0;
 5014         }
 5015         tt = create_new_tab(NULL, NULL, x, -1);
 5016         webview = tt->wv;
 5017     }
 5018 
 5019     return (webview);
 5020 }
 5021 
 5022 gboolean
 5023 webview_closewv_cb(WebKitWebView *wv, struct tab *t)
 5024 {
 5025     const gchar     *uri;
 5026     struct wl_entry     *w = NULL;
 5027 
 5028     DNPRINTF(XT_D_NAV, "webview_close_cb: %d\n", t->tab_id);
 5029 
 5030     if (enable_scripts == 0 && enable_cookie_whitelist == 1) {
 5031         uri = webkit_web_view_get_uri(wv);
 5032         if (uri && (w = wl_find_uri(uri, &js_wl)) == NULL)
 5033             return (FALSE);
 5034 
 5035         delete_tab(t);
 5036     } else if (enable_scripts == 1)
 5037         delete_tab(t);
 5038 
 5039     return (TRUE);
 5040 }
 5041 
 5042 int
 5043 webview_event_cb(GtkWidget *w, GdkEventButton *e, struct tab *t)
 5044 {
 5045     /* we can not eat the event without throwing gtk off so defer it */
 5046 
 5047     /* catch middle click */
 5048     if (e->type == GDK_BUTTON_RELEASE && e->button == 2) {
 5049         t->ctrl_click = 1;
 5050         goto done;
 5051     }
 5052 
 5053     /* catch ctrl click */
 5054     if (e->type == GDK_BUTTON_RELEASE &&
 5055         CLEAN(e->state) == GDK_CONTROL_MASK)
 5056         t->ctrl_click = 1;
 5057     else
 5058         t->ctrl_click = 0;
 5059 done:
 5060     return (XT_CB_PASSTHROUGH);
 5061 }
 5062 
 5063 int
 5064 run_mimehandler(struct tab *t, char *mime_type, WebKitNetworkRequest *request)
 5065 {
 5066     struct mime_type    *m;
 5067     char            *sv[3];
 5068     GError          *gerr = NULL;
 5069 
 5070     m = find_mime_type(mime_type);
 5071     if (m == NULL)
 5072         return (1);
 5073     if (m->mt_download)
 5074         return (1);
 5075 
 5076     sv[0] = m->mt_action;
 5077     sv[1] = (char *)webkit_network_request_get_uri(request);
 5078     sv[2] = NULL;
 5079 
 5080     /* ignore donothing from example config */
 5081     if (m->mt_action && !strcmp(m->mt_action, "donothing"))
 5082         return (0);
 5083 
 5084     if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
 5085         NULL, &gerr))
 5086         show_oops(t, "%s: could not spawn process (%s)", __func__,
 5087             gerr ? gerr->message : "N/A");
 5088     return (0);
 5089 }
 5090 
 5091 char *
 5092 get_mime_type(const char *uri)
 5093 {
 5094     GFileInfo       *fi;
 5095     GFile           *gf;
 5096     const gchar     *m;
 5097     char            *mime_type = NULL;
 5098     char            *file;
 5099 
 5100     if (uri == NULL) {
 5101         show_oops(NULL, "%s: invalid parameters", __func__);
 5102         return (NULL);
 5103     }
 5104 
 5105     if (g_str_has_prefix(uri, "file://"))
 5106         file = g_filename_from_uri(uri, NULL, NULL);
 5107     else
 5108         file = g_strdup(uri);
 5109 
 5110     if (file == NULL)
 5111         return (NULL);
 5112 
 5113     gf = g_file_new_for_path(file);
 5114     fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
 5115         NULL, NULL);
 5116     if ((m = g_file_info_get_content_type(fi)) != NULL)
 5117         mime_type = g_strdup(m);
 5118     g_object_unref(fi);
 5119     g_object_unref(gf);
 5120     g_free(file);
 5121 
 5122     return (mime_type);
 5123 }
 5124 
 5125 int
 5126 run_download_mimehandler(char *mime_type, char *file)
 5127 {
 5128     struct mime_type    *m;
 5129     char            *sv[3];
 5130 
 5131     m = find_mime_type(mime_type);
 5132     if (m == NULL)
 5133         return (1);
 5134 
 5135     sv[0] = m->mt_action;
 5136     sv[1] = file;
 5137     sv[2] = NULL;
 5138     if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
 5139         NULL, NULL)) {
 5140         show_oops(NULL, "%s: could not spawn process: %s %s", __func__,
 5141             sv[0], sv[1]);
 5142         return (1);
 5143     }
 5144     return (0);
 5145 }
 5146 
 5147 void
 5148 download_status_changed_cb(WebKitDownload *download, GParamSpec *spec,
 5149     WebKitWebView *wv)
 5150 {
 5151     WebKitDownloadStatus    status;
 5152     const char      *uri;
 5153     char            *file = NULL;
 5154     char            *mime = NULL;
 5155     char            *destination;
 5156 
 5157     if (download == NULL)
 5158         return;
 5159     status = webkit_download_get_status(download);
 5160     if (status != WEBKIT_DOWNLOAD_STATUS_FINISHED)
 5161         return;
 5162 
 5163     if (download_notifications) {
 5164         /* because basename() takes a char * on linux */
 5165         destination = g_strdup(
 5166             webkit_download_get_destination_uri(download));
 5167         show_oops(NULL, "Download of '%s' finished",
 5168             basename(destination));
 5169         g_free(destination);
 5170     }
 5171     uri = webkit_download_get_destination_uri(download);
 5172     if (uri == NULL)
 5173         return;
 5174     mime = get_mime_type(uri);
 5175     if (mime == NULL)
 5176         return;
 5177 
 5178     if (g_str_has_prefix(uri, "file://"))
 5179         file = g_filename_from_uri(uri, NULL, NULL);
 5180     else
 5181         file = g_strdup(uri);
 5182 
 5183     if (file == NULL)
 5184         goto done;
 5185 
 5186     run_download_mimehandler((char *)mime, file);
 5187 
 5188 done:
 5189     g_free(mime);
 5190 }
 5191 
 5192 int
 5193 webview_mimetype_cb(WebKitWebView *wv, WebKitWebFrame *frame,
 5194     WebKitNetworkRequest *request, char *mime_type,
 5195     WebKitWebPolicyDecision *decision, struct tab *t)
 5196 {
 5197     if (t == NULL) {
 5198         show_oops(NULL, "webview_mimetype_cb invalid parameters");
 5199         return (FALSE);
 5200     }
 5201 
 5202     DNPRINTF(XT_D_DOWNLOAD, "webview_mimetype_cb: tab %d mime %s\n",
 5203         t->tab_id, mime_type);
 5204 
 5205     if (run_mimehandler(t, mime_type, request) == 0) {
 5206         webkit_web_policy_decision_ignore(decision);
 5207         focus_webview(t);
 5208         return (TRUE);
 5209     }
 5210 
 5211     if (webkit_web_view_can_show_mime_type(wv, mime_type) == FALSE) {
 5212         webkit_web_policy_decision_download(decision);
 5213         return (TRUE);
 5214     }
 5215 
 5216