Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
8926 lines (7544 sloc) 205 KB
/*
* Copyright (c) 2010, 2011 Marco Peereboom <marco@peereboom.us>
* Copyright (c) 2011 Stevan Andjelkovic <stevan@student.chalmers.se>
* Copyright (c) 2010, 2011, 2012 Edd Barrett <vext01@gmail.com>
* Copyright (c) 2011 Todd T. Fries <todd@fries.net>
* Copyright (c) 2011 Raphael Graf <r@undefined.ch>
* Copyright (c) 2011 Michal Mazurek <akfaew@jasminek.net>
* Copyright (c) 2012, 2013 Josh Rickmar <jrick@devio.us>
* Copyright (c) 2013 David Hill <dhill@mindcry.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <xombrero.h>
#include "version.h"
char *version = XOMBRERO_VERSION;
#ifdef XT_DEBUG
uint32_t swm_debug = 0
| XT_D_MOVE
| XT_D_KEY
| XT_D_TAB
| XT_D_URL
| XT_D_CMD
| XT_D_NAV
| XT_D_DOWNLOAD
| XT_D_CONFIG
| XT_D_JS
| XT_D_FAVORITE
| XT_D_PRINTING
| XT_D_COOKIE
| XT_D_KEYBINDING
| XT_D_CLIP
| XT_D_BUFFERCMD
| XT_D_INSPECTOR
| XT_D_VISITED
| XT_D_HISTORY
;
#endif
char *icons[] = {
"xombreroicon16.png",
"xombreroicon32.png",
"xombreroicon48.png",
"xombreroicon64.png",
"xombreroicon128.png"
};
struct session {
TAILQ_ENTRY(session) entry;
const gchar *name;
};
TAILQ_HEAD(session_list, session);
struct undo {
TAILQ_ENTRY(undo) entry;
gchar *uri;
GList *history;
int back; /* Keeps track of how many back
* history items there are. */
};
TAILQ_HEAD(undo_tailq, undo);
struct command_entry {
char *line;
TAILQ_ENTRY(command_entry) entry;
};
TAILQ_HEAD(command_list, command_entry);
/* defines */
#define XT_CACHE_DIR ("cache")
#define XT_CERT_DIR ("certs")
#define XT_CERT_CACHE_DIR ("certs_cache")
#define XT_JS_DIR ("js")
#define XT_SESSIONS_DIR ("sessions")
#define XT_TEMP_DIR ("tmp")
#define XT_QMARKS_FILE ("quickmarks")
#define XT_SAVED_TABS_FILE ("main_session")
#define XT_RESTART_TABS_FILE ("restart_tabs")
#define XT_SOCKET_FILE ("socket")
#define XT_SAVE_SESSION_ID ("SESSION_NAME=")
#define XT_SEARCH_FILE ("search_history")
#define XT_COMMAND_FILE ("command_history")
#define XT_DLMAN_REFRESH "10"
#define XT_MAX_URL_LENGTH (4096) /* 1 page is atomic, don't make bigger */
#define XT_MAX_UNDO_CLOSE_TAB (32)
#define XT_PRINT_EXTRA_MARGIN 10
#define XT_URL_REGEX ("^[[:blank:]]*[^[:blank:]]*([[:alnum:]-]+\\.)+[[:alnum:]-][^[:blank:]]*[[:blank:]]*$")
#define XT_INVALID_MARK (-1) /* XXX this is a double, maybe use something else, like a nan */
#define XT_MAX_CERTS (32)
/* colors */
#define XT_COLOR_RED "#cc0000"
#define XT_COLOR_YELLOW "#ffff66"
#define XT_COLOR_BLUE "lightblue"
#define XT_COLOR_GREEN "#99ff66"
#define XT_COLOR_WHITE "white"
#define XT_COLOR_BLACK "black"
#define XT_COLOR_CT_BACKGROUND "#000000"
#define XT_COLOR_CT_INACTIVE "#dddddd"
#define XT_COLOR_CT_ACTIVE "#bbbb00"
#define XT_COLOR_CT_SEPARATOR "#555555"
#define XT_COLOR_SB_SEPARATOR "#555555"
/* CSS element names */
#define XT_CSS_NORMAL ""
#define XT_CSS_RED "red"
#define XT_CSS_YELLOW "yellow"
#define XT_CSS_GREEN "green"
#define XT_CSS_BLUE "blue"
#define XT_CSS_HIDDEN "hidden"
#define XT_CSS_ACTIVE "active"
#define XT_PROTO_DELIM "://"
/* actions */
#define XT_MOVE_INVALID (0)
#define XT_MOVE_DOWN (1)
#define XT_MOVE_UP (2)
#define XT_MOVE_BOTTOM (3)
#define XT_MOVE_TOP (4)
#define XT_MOVE_PAGEDOWN (5)
#define XT_MOVE_PAGEUP (6)
#define XT_MOVE_HALFDOWN (7)
#define XT_MOVE_HALFUP (8)
#define XT_MOVE_LEFT (9)
#define XT_MOVE_FARLEFT (10)
#define XT_MOVE_RIGHT (11)
#define XT_MOVE_FARRIGHT (12)
#define XT_MOVE_PERCENT (13)
#define XT_MOVE_CENTER (14)
#define XT_QMARK_SET (0)
#define XT_QMARK_OPEN (1)
#define XT_QMARK_TAB (2)
#define XT_MARK_SET (0)
#define XT_MARK_GOTO (1)
#define XT_GO_UP_ROOT (999)
#define XT_NAV_INVALID (0)
#define XT_NAV_BACK (1)
#define XT_NAV_FORWARD (2)
#define XT_NAV_RELOAD (3)
#define XT_NAV_STOP (4)
#define XT_FOCUS_INVALID (0)
#define XT_FOCUS_URI (1)
#define XT_FOCUS_SEARCH (2)
#define XT_SEARCH_INVALID (0)
#define XT_SEARCH_NEXT (1)
#define XT_SEARCH_PREV (2)
#define XT_PASTE_CURRENT_TAB (0)
#define XT_PASTE_NEW_TAB (1)
#define XT_ZOOM_IN (-1)
#define XT_ZOOM_OUT (-2)
#define XT_ZOOM_NORMAL (100)
#define XT_SES_DONOTHING (0)
#define XT_SES_CLOSETABS (1)
#define XT_PREFIX (1<<0)
#define XT_USERARG (1<<1)
#define XT_URLARG (1<<2)
#define XT_INTARG (1<<3)
#define XT_SESSARG (1<<4)
#define XT_SETARG (1<<5)
#define XT_HINT_NEWTAB (1<<0)
#define XT_BUFCMD_SZ (8)
#define XT_EJS_SHOW (1<<0)
GtkWidget * create_button(const char *, const char *, int);
void recalc_tabs(void);
void recolor_compact_tabs(void);
void set_current_tab(int page_num);
gboolean update_statusbar_position(GtkAdjustment*, gpointer);
void marks_clear(struct tab *t);
/* globals */
extern char *__progname;
char * const *start_argv;
struct passwd *pwd;
GtkWidget *main_window;
GtkNotebook *notebook;
GtkWidget *tab_bar;
GtkWidget *tab_bar_box;
GtkWidget *arrow, *abtn;
GdkEvent *fevent = NULL;
struct tab_list tabs;
struct history_list hl;
int hl_purge_count = 0;
struct session_list sessions;
struct wl_list c_wl;
struct wl_list js_wl;
struct wl_list pl_wl;
struct wl_list force_https;
struct wl_list svil;
struct strict_transport_tree st_tree;
struct undo_tailq undos;
struct keybinding_list kbl;
struct sp_list spl;
struct user_agent_list ua_list;
struct http_accept_list ha_list;
struct domain_id_list di_list;
struct cmd_alias_list cal;
struct custom_uri_list cul;
struct command_list chl;
struct command_list shl;
struct command_entry *history_at;
struct command_entry *search_at;
struct secviolation_list svl;
struct set_reject_list srl;
int undo_count;
int cmd_history_count = 0;
int search_history_count = 0;
char *global_search;
uint64_t blocked_cookies = 0;
char named_session[PATH_MAX];
GtkListStore *completion_model;
GtkListStore *buffers_store;
char *stylesheet;
char *qmarks[XT_NOQMARKS];
int btn_down; /* M1 down in any wv */
regex_t url_re; /* guess_search regex */
/* starts from 1 to catch atoi() failures when calling xtp_handle_dl() */
int next_download_id = 1;
void xxx_dir(char *);
int icon_size_map(int);
void activate_uri_entry_cb(GtkWidget*, struct tab *);
void activate_search_entry_cb(GtkWidget*, struct tab *);
void
history_delete(struct command_list *l, int *counter)
{
struct command_entry *c;
if (l == NULL || counter == NULL)
return;
c = TAILQ_LAST(l, command_list);
if (c == NULL)
return;
TAILQ_REMOVE(l, c, entry);
*counter -= 1;
g_free(c->line);
g_free(c);
}
void
history_add(struct command_list *list, char *file, char *l, int *counter)
{
struct command_entry *c;
FILE *f;
if (list == NULL || l == NULL || counter == NULL)
return;
/* don't add the same line */
c = TAILQ_FIRST(list);
if (c)
if (!strcmp(c->line + 1 /* skip space */, l))
return;
c = g_malloc0(sizeof *c);
c->line = g_strdup_printf(" %s", l);
*counter += 1;
TAILQ_INSERT_HEAD(list, c, entry);
if (*counter > 1000)
history_delete(list, counter);
if (history_autosave && file) {
f = fopen(file, "w");
if (f == NULL) {
show_oops(NULL, "couldn't write history %s", file);
return;
}
TAILQ_FOREACH_REVERSE(c, list, command_list, entry) {
c->line[0] = ' ';
fprintf(f, "%s\n", c->line);
}
fclose(f);
}
}
int
history_read(struct command_list *list, char *file, int *counter)
{
FILE *f;
char *s, line[65536];
if (list == NULL || file == NULL)
return (1);
f = fopen(file, "r");
if (f == NULL) {
startpage_add("couldn't open history file %s", file);
return (1);
}
for (;;) {
s = fgets(line, sizeof line, f);
if (s == NULL || feof(f) || ferror(f))
break;
if ((s = strchr(line, '\n')) == NULL) {
startpage_add("invalid history file %s", file);
fclose(f);
return (1);
}
*s = '\0';
history_add(list, NULL, line + 1, counter);
}
fclose(f);
return (0);
}
/* marks array storage. */
char
indextomark(int i)
{
if (i < 0 || i >= XT_NOMARKS)
return (0);
return XT_MARKS[i];
}
int
marktoindex(char m)
{
char *ret;
if ((ret = strchr(XT_MARKS, m)) != NULL)
return ret - XT_MARKS;
return (-1);
}
/* quickmarks array storage. */
char
indextoqmark(int i)
{
if (i < 0 || i >= XT_NOQMARKS)
return (0);
return XT_QMARKS[i];
}
int
qmarktoindex(char m)
{
char *ret;
if ((ret = strchr(XT_QMARKS, m)) != NULL)
return ret - XT_QMARKS;
return (-1);
}
int
is_g_object_setting(GObject *o, char *str)
{
guint n_props = 0, i;
GParamSpec **proplist;
int rv = 0;
if (!G_IS_OBJECT(o))
return (0);
proplist = g_object_class_list_properties(G_OBJECT_GET_CLASS(o),
&n_props);
for (i = 0; i < n_props; i++) {
if (! strcmp(proplist[i]->name, str)) {
rv = 1;
break;
}
}
g_free(proplist);
return (rv);
}
struct tab *
get_current_tab(void)
{
struct tab *t;
TAILQ_FOREACH(t, &tabs, entry) {
if (t->tab_id == gtk_notebook_get_current_page(notebook))
return (t);
}
warnx("%s: no current tab", __func__);
return (NULL);
}
int
set_ssl_ca_file(struct settings *s, char *file)
{
struct stat sb;
if (file == NULL || strlen(file) == 0)
return (-1);
if (stat(file, &sb)) {
warnx("no CA file: %s", file);
return (-1);
}
expand_tilde(ssl_ca_file, sizeof ssl_ca_file, file);
g_object_set(session,
SOUP_SESSION_SSL_CA_FILE, ssl_ca_file,
SOUP_SESSION_SSL_STRICT, ssl_strict_certs,
(void *)NULL);
return (0);
}
void
set_status(struct tab *t, gchar *fmt, ...)
{
va_list ap;
gchar *status;
va_start(ap, fmt);
status = g_strdup_vprintf(fmt, ap);
gtk_entry_set_text(GTK_ENTRY(t->sbe.uri), status);
if (!t->status)
t->status = status;
else if (strcmp(t->status, status)) {
/* set new status */
g_free(t->status);
t->status = status;
} else
g_free(status);
va_end(ap);
}
void
hide_cmd(struct tab *t)
{
DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
history_at = NULL; /* just in case */
search_at = NULL; /* just in case */
gtk_widget_set_can_focus(t->cmd, FALSE);
gtk_widget_hide(t->cmd);
}
void
show_cmd(struct tab *t, const char *s)
{
DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
/* without this you can't middle click in t->cmd to paste */
gtk_entry_set_text(GTK_ENTRY(t->cmd), "");
history_at = NULL;
search_at = NULL;
gtk_widget_hide(t->oops);
gtk_widget_set_can_focus(t->cmd, TRUE);
gtk_widget_show(t->cmd);
gtk_widget_grab_focus(GTK_WIDGET(t->cmd));
#if GTK_CHECK_VERSION(3, 0, 0)
gtk_widget_set_name(t->cmd, XT_CSS_NORMAL);
#else
gtk_widget_modify_base(t->cmd, GTK_STATE_NORMAL,
&t->default_style->base[GTK_STATE_NORMAL]);
#endif
gtk_entry_set_text(GTK_ENTRY(t->cmd), s);
gtk_editable_set_position(GTK_EDITABLE(t->cmd), -1);
}
void
hide_buffers(struct tab *t)
{
gtk_widget_hide(t->buffers);
gtk_widget_set_can_focus(t->buffers, FALSE);
gtk_list_store_clear(buffers_store);
}
enum {
COL_ID = 0,
COL_FAVICON,
COL_TITLE,
NUM_COLS
};
int
sort_tabs_by_page_num(struct tab ***stabs)
{
int num_tabs = 0;
struct tab *t;
num_tabs = gtk_notebook_get_n_pages(notebook);
*stabs = g_malloc0(num_tabs * sizeof(struct tab *));
TAILQ_FOREACH(t, &tabs, entry)
(*stabs)[gtk_notebook_page_num(notebook, t->vbox)] = t;
return (num_tabs);
}
void
buffers_make_list(void)
{
GtkTreeIter iter;
const gchar *title = NULL;
struct tab **stabs = NULL;
int i, num_tabs;
num_tabs = sort_tabs_by_page_num(&stabs);
for (i = 0; i < num_tabs; i++)
if (stabs[i]) {
gtk_list_store_append(buffers_store, &iter);
title = get_title(stabs[i], FALSE);
gtk_list_store_set(buffers_store, &iter,
COL_ID, i + 1, /* Enumerate the tabs starting from 1
* rather than 0. */
COL_FAVICON, gtk_image_get_pixbuf
(GTK_IMAGE(stabs[i]->tab_elems.favicon)),
COL_TITLE, title,
-1);
}
g_free(stabs);
}
void
show_buffers(struct tab *t)
{
GtkTreeIter iter;
GtkTreeSelection *sel;
GtkTreePath *path;
int index;
if (gtk_widget_get_visible(GTK_WIDGET(t->buffers)))
return;
buffers_make_list();
sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(t->buffers));
index = gtk_notebook_get_current_page(notebook);
path = gtk_tree_path_new_from_indices(index, -1);
if (gtk_tree_model_get_iter(GTK_TREE_MODEL(buffers_store), &iter, path))
gtk_tree_selection_select_iter(sel, &iter);
gtk_tree_path_free(path);
gtk_widget_show(t->buffers);
gtk_widget_set_can_focus(t->buffers, TRUE);
gtk_widget_grab_focus(GTK_WIDGET(t->buffers));
}
void
toggle_buffers(struct tab *t)
{
if (gtk_widget_get_visible(t->buffers))
hide_buffers(t);
else
show_buffers(t);
}
int
buffers(struct tab *t, struct karg *args)
{
show_buffers(t);
return (0);
}
int
set_scrollbar_visibility(struct tab *t, int visible)
{
#if GTK_CHECK_VERSION(3, 0, 0)
GtkWidget *h_scrollbar, *v_scrollbar;
h_scrollbar = gtk_scrolled_window_get_hscrollbar(
GTK_SCROLLED_WINDOW(t->browser_win));
v_scrollbar = gtk_scrolled_window_get_vscrollbar(
GTK_SCROLLED_WINDOW(t->browser_win));
if (visible == 0) {
gtk_widget_set_name(h_scrollbar, XT_CSS_HIDDEN);
gtk_widget_set_name(v_scrollbar, XT_CSS_HIDDEN);
} else {
gtk_widget_set_name(h_scrollbar, "");
gtk_widget_set_name(v_scrollbar, "");
}
return (0);
#else
return (visible == 0);
#endif
}
void
hide_oops(struct tab *t)
{
gtk_widget_hide(t->oops);
}
void
show_oops(struct tab *at, const char *fmt, ...)
{
va_list ap;
char *msg = NULL;
struct tab *t = NULL;
if (fmt == NULL)
return;
if (at == NULL) {
if ((t = get_current_tab()) == NULL)
return;
} else
t = at;
va_start(ap, fmt);
if ((msg = g_strdup_vprintf(fmt, ap)) == NULL)
errx(1, "show_oops failed");
va_end(ap);
gtk_entry_set_text(GTK_ENTRY(t->oops), msg);
gtk_widget_hide(t->cmd);
gtk_widget_show(t->oops);
if (msg)
g_free(msg);
}
char work_dir[PATH_MAX];
char certs_dir[PATH_MAX];
char certs_cache_dir[PATH_MAX];
char js_dir[PATH_MAX];
char cache_dir[PATH_MAX];
char sessions_dir[PATH_MAX];
char temp_dir[PATH_MAX];
char cookie_file[PATH_MAX];
char *strict_transport_file = NULL;
SoupSession *session;
SoupCookieJar *s_cookiejar;
SoupCookieJar *p_cookiejar;
char rc_fname[PATH_MAX];
struct mime_type_list mtl;
struct alias_list aliases;
/* protos */
struct tab *create_new_tab(const char *, struct undo *, int, int);
void delete_tab(struct tab *);
void setzoom_webkit(struct tab *, int);
int download_rb_cmp(struct download *, struct download *);
gboolean cmd_execute(struct tab *t, char *str);
int
history_rb_cmp(struct history *h1, struct history *h2)
{
return (strcmp(h1->uri, h2->uri));
}
RB_GENERATE(history_list, history, entry, history_rb_cmp);
int
download_rb_cmp(struct download *e1, struct download *e2)
{
return (e1->id < e2->id ? -1 : e1->id > e2->id);
}
RB_GENERATE(download_list, download, entry, download_rb_cmp);
int
secviolation_rb_cmp(struct secviolation *s1, struct secviolation *s2)
{
return (s1->xtp_arg < s2->xtp_arg ? -1 : s1->xtp_arg > s2->xtp_arg);
}
RB_GENERATE(secviolation_list, secviolation, entry, secviolation_rb_cmp);
int
user_agent_rb_cmp(struct user_agent *ua1, struct user_agent *ua2)
{
return (ua1->id < ua2->id ? -1 : ua1->id > ua2->id);
}
RB_GENERATE(user_agent_list, user_agent, entry, user_agent_rb_cmp);
int
http_accept_rb_cmp(struct http_accept *ha1, struct http_accept *ha2)
{
return (ha1->id < ha2->id ? -1 : ha1->id > ha2->id);
}
RB_GENERATE(http_accept_list, http_accept, entry, http_accept_rb_cmp);
int
domain_id_rb_cmp(struct domain_id *d1, struct domain_id *d2)
{
return (strcmp(d1->domain, d2->domain));
}
RB_GENERATE(domain_id_list, domain_id, entry, domain_id_rb_cmp);
struct valid_url_types {
char *type;
} vut[] = {
{ "http://" },
{ "https://" },
{ "ftp://" },
{ "file://" },
{ XT_URI_ABOUT },
{ XT_XTP_STR },
};
int
valid_url_type(const char *url)
{
int i;
for (i = 0; i < LENGTH(vut); i++)
if (!strncasecmp(vut[i].type, url, strlen(vut[i].type)))
return (0);
return (1);
}
char *
match_alias(const char *url_in)
{
struct alias *a;
char *arg;
char *url_out = NULL, *search, *enc_arg;
char **sv;
search = g_strdup(url_in);
arg = search;
if (strsep(&arg, " \t") == NULL) {
show_oops(NULL, "match_alias: NULL URL");
goto done;
}
TAILQ_FOREACH(a, &aliases, entry) {
if (!strcmp(search, a->a_name))
break;
}
if (a != NULL) {
DNPRINTF(XT_D_URL, "match_alias: matched alias %s\n",
a->a_name);
if (arg == NULL)
arg = "";
enc_arg = soup_uri_encode(arg, XT_RESERVED_CHARS);
sv = g_strsplit(a->a_uri, "%s", 2);
if (arg != NULL)
url_out = g_strjoinv(enc_arg, sv);
else
url_out = g_strjoinv("", sv);
g_free(enc_arg);
g_strfreev(sv);
}
done:
g_free(search);
return (url_out);
}
char *
guess_url_type(const char *url_in)
{
struct stat sb;
char cwd[PATH_MAX] = {0};
char *url_out = NULL, *enc_search = NULL;
char *path = NULL;
char **sv = NULL;
int i;
/* substitute aliases */
url_out = match_alias(url_in);
if (url_out != NULL)
return (url_out);
/* see if we are an about page */
if (!strncmp(url_in, XT_URI_ABOUT, XT_URI_ABOUT_LEN))
for (i = 0; i < about_list_size(); i++)
if (!strcmp(&url_in[XT_URI_ABOUT_LEN],
about_list[i].name)) {
url_out = g_strdup(url_in);
goto done;
}
if (guess_search && url_regex &&
!(g_str_has_prefix(url_in, "http://") ||
g_str_has_prefix(url_in, "https://"))) {
if (regexec(&url_re, url_in, 0, NULL, 0)) {
/* invalid URI so search instead */
enc_search = soup_uri_encode(url_in, XT_RESERVED_CHARS);
sv = g_strsplit(search_string, "%s", 2);
url_out = g_strjoinv(enc_search, sv);
g_free(enc_search);
g_strfreev(sv);
goto done;
}
}
/* XXX not sure about this heuristic */
if (stat(url_in, &sb) == 0) {
if (url_in[0] == '/')
url_out = g_filename_to_uri(url_in, NULL, NULL);
else {
if (getcwd(cwd, PATH_MAX) != NULL) {
path = g_strdup_printf("%s" PS "%s", cwd,
url_in);
url_out = g_filename_to_uri(path, NULL, NULL);
g_free(path);
}
}
} else
url_out = g_strdup_printf("http://%s", url_in); /* guess http */
done:
DNPRINTF(XT_D_URL, "guess_url_type: guessed %s\n", url_out);
return (url_out);
}
void
set_normal_tab_meaning(struct tab *t)
{
if (t == NULL)
return;
set_xtp_meaning(t, XT_XTP_TAB_MEANING_NORMAL);
}
void
load_uri(struct tab *t, const gchar *uri)
{
struct karg args;
gchar *newuri = NULL;
int i;
if (uri == NULL)
return;
/* Strip leading spaces. */
while (*uri && isspace((unsigned char)*uri))
uri++;
if (strlen(uri) == 0) {
blank(t, NULL);
return;
}
set_normal_tab_meaning(t);
if (valid_url_type(uri)) {
if ((newuri = guess_url_type(uri)) != NULL)
uri = newuri;
else
uri = "";
}
/* clear :cert show host */
if (t->about_cert_host) {
g_free(t->about_cert_host);
t->about_cert_host = NULL;
}
if (!strncmp(uri, XT_URI_ABOUT, XT_URI_ABOUT_LEN)) {
for (i = 0; i < about_list_size(); i++)
if (!strcmp(&uri[XT_URI_ABOUT_LEN], about_list[i].name) &&
about_list[i].func != NULL) {
bzero(&args, sizeof args);
about_list[i].func(t, &args);
gtk_widget_set_sensitive(GTK_WIDGET(t->stop),
FALSE);
goto done;
}
show_oops(t, "invalid about page");
goto done;
}
/* remove old HTTPS cert chain (if any) */
if (t->pem) {
g_free(t->pem);
t->pem = NULL;
}
set_status(t, "Loading: %s", (char *)uri);
marks_clear(t);
webkit_web_view_load_uri(t->wv, uri);
done:
if (newuri)
g_free(newuri);
}
const gchar *
get_uri(struct tab *t)
{
const gchar *uri;
if (webkit_web_view_get_load_status(t->wv) == WEBKIT_LOAD_FAILED &&
!t->download_requested)
return (NULL);
if (t->xtp_meaning == XT_XTP_TAB_MEANING_NORMAL)
uri = webkit_web_view_get_uri(t->wv);
else
uri = t->tmp_uri;
return (uri);
}
void
set_xtp_meaning(struct tab *t, int tab_meaning)
{
t->xtp_meaning = tab_meaning;
if (tab_meaning == XT_XTP_TAB_MEANING_NORMAL) {
if (t->session_key != NULL) {
g_free(t->session_key);
t->session_key = NULL;
}
} else {
/* use tmp_uri to make sure it is g_freed */
if (t->tmp_uri)
g_free(t->tmp_uri);
t->tmp_uri = g_strdup_printf("%s%s", XT_URI_ABOUT,
about_list[t->xtp_meaning].name);
}
}
const gchar *
get_title(struct tab *t, bool window)
{
const gchar *set = NULL, *title = NULL;
WebKitLoadStatus status;
status = webkit_web_view_get_load_status(t->wv);
if (status == WEBKIT_LOAD_PROVISIONAL ||
(status == WEBKIT_LOAD_FAILED && !t->download_requested) ||
t->xtp_meaning == XT_XTP_TAB_MEANING_BL)
goto notitle;
title = webkit_web_view_get_title(t->wv);
if ((set = title ? title : get_uri(t)))
return set;
notitle:
set = window ? XT_NAME : "(untitled)";
return set;
}
struct mime_type *
find_mime_type(char *mime_type)
{
struct mime_type *m, *def = NULL, *rv = NULL;
TAILQ_FOREACH(m, &mtl, entry) {
if (m->mt_default &&
!strncmp(mime_type, m->mt_type, strlen(m->mt_type)))
def = m;
if (m->mt_default == 0 && !strcmp(mime_type, m->mt_type)) {
rv = m;
break;
}
}
if (rv == NULL)
rv = def;
return (rv);
}
/*
* This only escapes the & and < characters, as per the discussion found here:
* http://lists.apple.com/archives/Webkitsdk-dev/2007/May/msg00056.html
*/
gchar *
html_escape(const char *val)
{
char *s, *sp;
char **sv;
if (val == NULL)
return NULL;
sv = g_strsplit(val, "&", -1);
s = g_strjoinv("&amp;", sv);
g_strfreev(sv);
sp = s;
sv = g_strsplit(val, "<", -1);
s = g_strjoinv("&lt;", sv);
g_strfreev(sv);
g_free(sp);
return (s);
}
struct wl_entry *
wl_find_uri(const gchar *s, struct wl_list *wl)
{
int i;
char *ss;
struct wl_entry *w;
if (s == NULL || wl == NULL)
return (NULL);
if (!strncmp(s, "http://", strlen("http://")))
s = &s[strlen("http://")];
else if (!strncmp(s, "https://", strlen("https://")))
s = &s[strlen("https://")];
if (strlen(s) < 2)
return (NULL);
for (i = 0; i < strlen(s) + 1 /* yes er need this */; i++)
/* chop string at first slash */
if (s[i] == '/' || s[i] == ':' || s[i] == '\0') {
ss = g_strdup(s);
ss[i] = '\0';
w = wl_find(ss, wl);
g_free(ss);
return (w);
}
return (NULL);
}
char *
js_ref_to_string(JSContextRef context, JSValueRef ref)
{
char *s = NULL;
size_t l;
JSStringRef jsref;
jsref = JSValueToStringCopy(context, ref, NULL);
if (jsref == NULL)
return (NULL);
l = JSStringGetMaximumUTF8CStringSize(jsref);
s = g_malloc(l);
if (s)
JSStringGetUTF8CString(jsref, s, l);
JSStringRelease(jsref);
return (s);
}
#define XT_JS_DONE ("done;")
#define XT_JS_DONE_LEN (strlen(XT_JS_DONE))
#define XT_JS_INSERT ("insert;")
#define XT_JS_INSERT_LEN (strlen(XT_JS_INSERT))
int
run_script(struct tab *t, char *s)
{
JSGlobalContextRef ctx;
WebKitWebFrame *frame;
JSStringRef str;
JSValueRef val, exception;
char *es;
DNPRINTF(XT_D_JS, "%s: tab %d %s\n", __func__,
t->tab_id, s == (char *)JS_HINTING ? "JS_HINTING" : s);
frame = webkit_web_view_get_main_frame(t->wv);
ctx = webkit_web_frame_get_global_context(frame);
str = JSStringCreateWithUTF8CString(s);
val = JSEvaluateScript(ctx, str, JSContextGetGlobalObject(ctx),
NULL, 0, &exception);
JSStringRelease(str);
DNPRINTF(XT_D_JS, "%s: val %p\n", __func__, val);
if (val == NULL) {
es = js_ref_to_string(ctx, exception);
if (es) {
DNPRINTF(XT_D_JS, "%s: exception %s\n", __func__, es);
g_free(es);
}
return (1);
} else {
es = js_ref_to_string(ctx, val);
#if 0
/* return values */
if (!strncmp(es, XT_JS_DONE, XT_JS_DONE_LEN))
; /* do nothing */
if (!strncmp(es, XT_JS_INSERT, XT_JS_INSERT_LEN))
; /* do nothing */
#endif
if (es) {
DNPRINTF(XT_D_JS, "%s: val %s\n", __func__, es);
g_free(es);
}
}
return (0);
}
void
enable_hints(struct tab *t)
{
DNPRINTF(XT_D_JS, "%s: tab %d\n", __func__, t->tab_id);
if (t->new_tab)
run_script(t, "hints.createHints('', 'F');");
else
run_script(t, "hints.createHints('', 'f');");
t->mode = XT_MODE_HINT;
}
void
disable_hints(struct tab *t)
{
DNPRINTF(XT_D_JS, "%s: tab %d\n", __func__, t->tab_id);
run_script(t, "hints.clearHints();");
t->mode = XT_MODE_COMMAND;
t->new_tab = 0;
}
int
passthrough(struct tab *t, struct karg *args)
{
t->mode = XT_MODE_PASSTHROUGH;
return (0);
}
int
modurl(struct tab *t, struct karg *args)
{
const gchar *uri = NULL;
char *u = NULL;
/* XXX kind of a bad hack, but oh well */
if (gtk_widget_has_focus(t->uri_entry)) {
if ((uri = gtk_entry_get_text(GTK_ENTRY(t->uri_entry))) &&
(strlen(uri))) {
u = g_strdup_printf("www.%s.com", uri);
gtk_entry_set_text(GTK_ENTRY(t->uri_entry), u);
g_free(u);
activate_uri_entry_cb(t->uri_entry, t);
}
}
return (0);
}
int
modsearchentry(struct tab *t, struct karg *args)
{
const gchar *s = NULL;
struct tab *tt;
/* XXX kind of a bad hack (in honor of the modurl hack) */
if (gtk_widget_has_focus(t->search_entry)) {
if ((s = gtk_entry_get_text(GTK_ENTRY(t->search_entry))) &&
(strlen(s))) {
tt = create_new_tab(NULL, NULL, 1, -1);
gtk_entry_set_text(GTK_ENTRY(tt->search_entry), s);
activate_search_entry_cb(t->search_entry,tt);
gtk_entry_set_text(GTK_ENTRY(t->search_entry), "");
}
}
return (0);
}
int
hint(struct tab *t, struct karg *args)
{
DNPRINTF(XT_D_JS, "hint: tab %d args %d\n", t->tab_id, args->i);
if (t->mode == XT_MODE_HINT) {
if (args->i == XT_HINT_NEWTAB)
t->new_tab = 1;
enable_hints(t);
} else
disable_hints(t);
return (0);
}
void
apply_style(struct tab *t)
{
t->styled = 1;
g_object_set(G_OBJECT(t->settings),
"user-stylesheet-uri", t->stylesheet, (char *)NULL);
}
void
remove_style(struct tab *t)
{
t->styled = 0;
g_object_set(G_OBJECT(t->settings),
"user-stylesheet-uri", NULL, (char *)NULL);
}
int
userstyle_cmd(struct tab *t, struct karg *args)
{
char script[PATH_MAX] = {'\0'};
char *script_uri;
struct tab *tt;
DNPRINTF(XT_D_JS, "userstyle_cmd: tab %d\n", t->tab_id);
if (args->s != NULL && strlen(args->s)) {
expand_tilde(script, sizeof script, args->s);
script_uri = g_filename_to_uri(script, NULL, NULL);
} else
script_uri = g_strdup(userstyle);
if (script_uri == NULL)
return (1);
switch (args->i) {
case XT_STYLE_CURRENT_TAB:
if (t->styled && !strcmp(script_uri, t->stylesheet))
remove_style(t);
else {
if (t->stylesheet)
g_free(t->stylesheet);
t->stylesheet = g_strdup(script_uri);
apply_style(t);
}
break;
case XT_STYLE_GLOBAL:
if (userstyle_global && !strcmp(script_uri, t->stylesheet)) {
userstyle_global = 0;
TAILQ_FOREACH(tt, &tabs, entry)
remove_style(tt);
} else {
userstyle_global = 1;
/* need to save this stylesheet for new tabs */
if (stylesheet)
g_free(stylesheet);
stylesheet = g_strdup(script_uri);
TAILQ_FOREACH(tt, &tabs, entry) {
if (tt->stylesheet)
g_free(tt->stylesheet);
tt->stylesheet = g_strdup(script_uri);
apply_style(tt);
}
}
break;
}
g_free(script_uri);
return (0);
}
int
quit(struct tab *t, struct karg *args)
{
if (save_global_history)
save_global_history_to_disk(t);
gtk_main_quit();
return (1);
}
void
restore_sessions_list(void)
{
DIR *sdir = NULL;
struct dirent *dp = NULL;
struct session *s;
int reg;
sdir = opendir(sessions_dir);
if (sdir) {
while ((dp = readdir(sdir)) != NULL) {
#if defined __MINGW32__
reg = 1; /* windows only has regular files */
#else
reg = dp->d_type == DT_REG;
#endif
if (dp && reg) {
s = g_malloc(sizeof(struct session));
s->name = g_strdup(dp->d_name);
TAILQ_INSERT_TAIL(&sessions, s, entry);
}
}
closedir(sdir);
}
}
int
open_tabs(struct tab *t, struct karg *a)
{
char file[PATH_MAX];
FILE *f = NULL;
char *uri = NULL;
int rv = 1;
struct tab *ti, *tt;
if (a == NULL)
goto done;
ti = TAILQ_LAST(&tabs, tab_list);
snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, a->s);
if ((f = fopen(file, "r")) == NULL)
goto done;
for (;;) {
if ((uri = fparseln(f, NULL, NULL, "\0\0\0", 0)) == NULL) {
if (feof(f) || ferror(f))
break;
} else {
/* retrieve session name */
if (g_str_has_prefix(uri, XT_SAVE_SESSION_ID)) {
strlcpy(named_session,
&uri[strlen(XT_SAVE_SESSION_ID)],
sizeof named_session);
continue;
}
if (strlen(uri))
create_new_tab(uri, NULL, 1, -1);
free(uri);
}
}
/* close open tabs */
if (a->i == XT_SES_CLOSETABS && ti != NULL) {
for (;;) {
tt = TAILQ_FIRST(&tabs);
if (tt == NULL)
break;
if (tt != ti) {
delete_tab(tt);
continue;
}
delete_tab(tt);
break;
}
}
rv = 0;
done:
if (f)
fclose(f);
return (rv);
}
int
restore_saved_tabs(void)
{
char file[PATH_MAX];
int unlink_file = 0;
struct stat sb;
struct karg a;
int rv = 0;
snprintf(file, sizeof file, "%s" PS "%s",
sessions_dir, XT_RESTART_TABS_FILE);
if (stat(file, &sb) == -1)
a.s = XT_SAVED_TABS_FILE;
else {
unlink_file = 1;
a.s = XT_RESTART_TABS_FILE;
}
a.i = XT_SES_DONOTHING;
rv = open_tabs(NULL, &a);
if (unlink_file)
unlink(file);
return (rv);
}
int
save_tabs(struct tab *t, struct karg *a)
{
char file[PATH_MAX];
FILE *f;
int num_tabs = 0, i;
struct tab **stabs = NULL;
/* tab may be null here */
if (a == NULL)
return (1);
if (a->s == NULL)
snprintf(file, sizeof file, "%s" PS "%s",
sessions_dir, named_session);
else
snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, a->s);
if ((f = fopen(file, "w")) == NULL) {
show_oops(t, "Can't open save_tabs file: %s", strerror(errno));
return (1);
}
/* save session name */
fprintf(f, "%s%s\n", XT_SAVE_SESSION_ID, named_session);
/* Save tabs, in the order they are arranged in the notebook. */
num_tabs = sort_tabs_by_page_num(&stabs);
for (i = 0; i < num_tabs; i++)
if (stabs[i]) {
if (get_uri(stabs[i]) != NULL)
fprintf(f, "%s\n", get_uri(stabs[i]));
else if (gtk_entry_get_text(GTK_ENTRY(
stabs[i]->uri_entry)))
fprintf(f, "%s\n", gtk_entry_get_text(GTK_ENTRY(
stabs[i]->uri_entry)));
}
g_free(stabs);
/* try and make sure this gets to disk NOW. XXX Backup first? */
if (fflush(f) != 0 || fsync(fileno(f)) != 0) {
show_oops(t, "May not have managed to save session: %s",
strerror(errno));
}
fclose(f);
return (0);
}
int
save_tabs_and_quit(struct tab *t, struct karg *args)
{
struct karg a;
a.s = NULL;
save_tabs(t, &a);
quit(t, NULL);
return (1);
}
void
expand_tilde(char *path, size_t len, const char *s)
{
struct passwd *pwd;
int i;
char user[LOGIN_NAME_MAX];
const char *sc = s;
if (path == NULL || s == NULL)
errx(1, "expand_tilde");
if (s[0] != '~') {
strlcpy(path, sc, len);
return;
}
++s;
for (i = 0; s[i] != PSC && s[i] != '\0'; ++i)
user[i] = s[i];
user[i] = '\0';
s = &s[i];
pwd = strlen(user) == 0 ? getpwuid(getuid()) : getpwnam(user);
if (pwd == NULL)
strlcpy(path, sc, len);
else
snprintf(path, len, "%s%s", pwd->pw_dir, s);
}
int
run_page_script(struct tab *t, struct karg *args)
{
const gchar *uri;
char *tmp, script[PATH_MAX];
char *sv[3];
tmp = args->s != NULL && strlen(args->s) > 0 ? args->s : default_script;
if (tmp[0] == '\0') {
show_oops(t, "no script specified");
return (1);
}
if ((uri = get_uri(t)) == NULL) {
show_oops(t, "tab is empty, not running script");
return (1);
}
expand_tilde(script, sizeof script, tmp);
sv[0] = script;
sv[1] = (char *)uri;
sv[2] = NULL;
if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL,
NULL, NULL)) {
show_oops(t, "%s: could not spawn process: %s %s", __func__,
sv[0], sv[1]);
return (1);
} else
show_oops(t, "running: %s %s", sv[0], sv[1]);
return (0);
}
int
yank_uri(struct tab *t, struct karg *args)
{
const gchar *uri;
GtkClipboard *clipboard, *primary;
if ((uri = get_uri(t)) == NULL)
return (1);
primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text(primary, uri, -1);
gtk_clipboard_set_text(clipboard, uri, -1);
return (0);
}
int
paste_uri(struct tab *t, struct karg *args)
{
GtkClipboard *clipboard, *primary;
gchar *c = NULL, *p = NULL, *uri;
int i;
clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
primary = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
c = gtk_clipboard_wait_for_text(clipboard);
p = gtk_clipboard_wait_for_text(primary);
if (c || p) {
#ifdef __MINGW32__
/* Windows try clipboard first */
uri = c ? c : p;
#else
/* UNIX try primary first */
uri = p ? p : c;
#endif
/* replace all newlines with spaces */
for (i = 0; uri[i] != '\0'; ++i)
if (uri[i] == '\n')
uri[i] = ' ';
while (*uri && isspace((unsigned char)*uri))
uri++;
if (strlen(uri) == 0) {
show_oops(t, "empty paste buffer");
goto done;
}
if (guess_search == 0 && valid_url_type(uri)) {
/* we can be clever and paste this in search box */
show_oops(t, "not a valid URL");
goto done;
}
if (args->i == XT_PASTE_CURRENT_TAB)
load_uri(t, uri);
else if (args->i == XT_PASTE_NEW_TAB)
create_new_tab(uri, NULL, 1, -1);
}
done:
if (c)
g_free(c);
if (p)
g_free(p);
return (0);
}
void
js_toggle_cb(GtkWidget *w, struct tab *t)
{
struct karg a;
int es, set;
g_object_get(G_OBJECT(t->settings),
"enable-scripts", &es, (char *)NULL);
es = !es;
if (es)
set = XT_WL_ENABLE;
else
set = XT_WL_DISABLE;
a.i = set | XT_WL_TOPLEVEL;
toggle_pl(t, &a);
a.i = set | XT_WL_TOPLEVEL;
toggle_cwl(t, &a);
a.i = XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD;
toggle_js(t, &a);
}
void
proxy_toggle_cb(GtkWidget *w, struct tab *t)
{
struct karg args = {0};
args.i = XT_PRXY_TOGGLE;
proxy_cmd(t, &args);
}
int
toggle_src(struct tab *t, struct karg *args)
{
gboolean mode;
if (t == NULL)
return (0);
mode = webkit_web_view_get_view_source_mode(t->wv);
webkit_web_view_set_view_source_mode(t->wv, !mode);
webkit_web_view_reload(t->wv);
return (0);
}
void
focus_webview(struct tab *t)
{
if (t == NULL)
return;
/* only grab focus if we are visible */
if (gtk_notebook_get_current_page(notebook) == t->tab_id)
gtk_widget_grab_focus(GTK_WIDGET(t->wv));
}
int
focus(struct tab *t, struct karg *args)
{
if (t == NULL || args == NULL)
return (1);
if (show_url == 0)
return (0);
if (args->i == XT_FOCUS_URI)
gtk_widget_grab_focus(GTK_WIDGET(t->uri_entry));
else if (args->i == XT_FOCUS_SEARCH)
gtk_widget_grab_focus(GTK_WIDGET(t->search_entry));
return (0);
}
void
free_connection_certs(gnutls_x509_crt_t *certs, size_t cert_count)
{
int i;
for (i = 0; i < cert_count; i++)
gnutls_x509_crt_deinit(certs[i]);
gnutls_free(certs);
}
#if GTK_CHECK_VERSION(3, 0, 0)
void
statusbar_modify_attr(struct tab *t, const char *css_name)
{
gtk_widget_set_name(t->sbe.ebox, css_name);
}
#else
void
statusbar_modify_attr(struct tab *t, const char *text, const char *base)
{
GdkColor c_text, c_base;
gdk_color_parse(text, &c_text);
gdk_color_parse(base, &c_base);
gtk_widget_modify_bg(t->sbe.ebox, GTK_STATE_NORMAL, &c_base);
gtk_widget_modify_base(t->sbe.uri, GTK_STATE_NORMAL, &c_base);
gtk_widget_modify_text(t->sbe.uri, GTK_STATE_NORMAL, &c_text);
}
#endif
void
save_certs(struct tab *t, gnutls_x509_crt_t *certs,
size_t cert_count, const char *domain, const char *dir)
{
size_t cert_buf_sz;
char file[PATH_MAX];
char *cert_buf = NULL;
int rv;
int i;
FILE *f;
if (t == NULL || certs == NULL || cert_count <= 0 || domain == NULL)
return;
snprintf(file, sizeof file, "%s" PS "%s", dir, domain);
if ((f = fopen(file, "w")) == NULL) {
show_oops(t, "Can't create cert file %s %s",
file, strerror(errno));
return;
}
for (i = 0; i < cert_count; i++) {
/*
* Because we support old crap and can't use
* gnutls_x509_crt_export2(), we intentionally use an empty
* buffer and then make a second call with the known size
* needed.
*/
cert_buf_sz = 0;
gnutls_x509_crt_export(certs[i], GNUTLS_X509_FMT_PEM,
NULL, &cert_buf_sz);
if (cert_buf_sz == 0) {
show_oops(t, "no certs found");
goto done;
}
cert_buf = gnutls_malloc(cert_buf_sz * sizeof(char));
if (cert_buf == NULL) {
show_oops(t, "gnutls_x509_crt_export failed");
goto done;
}
rv = gnutls_x509_crt_export(certs[i],
GNUTLS_X509_FMT_PEM, cert_buf, &cert_buf_sz);
if (rv != 0) {
show_oops(t, "gnutls_x509_crt_export failure: %s",
gnutls_strerror(rv));
goto done;
}
cert_buf[cert_buf_sz] = '\0';
if (fwrite(cert_buf, cert_buf_sz, 1, f) != 1) {
show_oops(t, "Can't write certs: %s", strerror(errno));
goto done;
}
gnutls_free(cert_buf);
cert_buf = NULL;
}
done:
if (cert_buf)
gnutls_free(cert_buf);
fclose(f);
}
enum cert_trust {
CERT_LOCAL,
CERT_TRUSTED,
CERT_UNTRUSTED,
CERT_BAD
};
gnutls_x509_crt_t *
get_local_cert_chain(const char *uri, size_t *ncerts, const char **error_str,
const char *dir)
{
SoupURI *su;
unsigned char cert_buf[64 * 1024] = {0};
gnutls_datum_t data;
unsigned int max_certs = XT_MAX_CERTS;
int bytes_read;
char file[PATH_MAX];
FILE *f;
gnutls_x509_crt_t *certs;
if ((su = soup_uri_new(uri)) == NULL) {
*error_str = "Invalid URI";
return (NULL);
}
snprintf(file, sizeof file, "%s" PS "%s", dir, su->host);
soup_uri_free(su);
if ((f = fopen(file, "r")) == NULL) {
*error_str = "Could not read local cert";
return (NULL);
}
bytes_read = fread(cert_buf, sizeof *cert_buf, sizeof cert_buf, f);
if (bytes_read == 0) {
*error_str = "Could not read local cert";
return (NULL);
}
certs = gnutls_malloc(sizeof(*certs) * max_certs);
if (certs == NULL) {
*error_str = "Error allocating memory";
return (NULL);
}
data.data = cert_buf;
data.size = bytes_read;
if (gnutls_x509_crt_list_import(certs, &max_certs, &data,
GNUTLS_X509_FMT_PEM, 0) < 0) {
gnutls_free(certs);
*error_str = "Error reading local cert chain";
return (NULL);
}
*ncerts = max_certs;
return (certs);
}
/*
* This uses the pem-encoded cert chain saved in t->pem instead of
* grabbing the remote cert. We save it beforehand and read it here
* so as to not open a side channel that ignores proxy settings.
*/
gnutls_x509_crt_t *
get_chain_for_pem(char *pem, size_t *ncerts, const char **error_str)
{
gnutls_datum_t data;
unsigned int max_certs = XT_MAX_CERTS;
gnutls_x509_crt_t *certs;
if (pem == NULL) {
*error_str = "Error reading remote cert chain";
return (NULL);
}
certs = gnutls_malloc(sizeof(*certs) * max_certs);
if (certs == NULL) {
*error_str = "Error allocating memory";
return (NULL);
}
data.data = (unsigned char *)pem;
data.size = strlen(pem);
if (gnutls_x509_crt_list_import(certs, &max_certs, &data,
GNUTLS_X509_FMT_PEM, 0) < 0) {
gnutls_free(certs);
*error_str = "Error reading remote cert chain";
return (NULL);
}
*ncerts = max_certs;
return (certs);
}
int
cert_cmd(struct tab *t, struct karg *args)
{
const gchar *uri, *error_str = NULL;
size_t cert_count;
gnutls_x509_crt_t *certs;
SoupURI *su;
char *host;
#if !GTK_CHECK_VERSION(3, 0, 0)
GdkColor color;
#endif
if (t == NULL)
return (1);
if (args->s != NULL) {
uri = args->s;
} else if ((uri = get_uri(t)) == NULL) {
show_oops(t, "Invalid URI");
return (1);
}
if ((su = soup_uri_new(uri)) == NULL) {
show_oops(t, "Invalid URI");
return (1);
}
/*
* if we're only showing the local certs, don't open a socket and get
* the remote certs
*/
if (args->i & XT_SHOW && args->i & XT_CACHE) {
certs = get_local_cert_chain(uri, &cert_count, &error_str,
certs_cache_dir);
if (error_str == NULL) {
t->about_cert_host = g_strdup(su->host);
show_certs(t, certs, cert_count, "Certificate Chain");
free_connection_certs(certs, cert_count);
} else {
show_oops(t, "%s", error_str);
soup_uri_free(su);
return (1);
}
soup_uri_free(su);
return (0);
}
certs = get_chain_for_pem(t->pem, &cert_count, &error_str);
if (error_str)
goto done;
if (args->i & XT_SHOW) {
t->about_cert_host = g_strdup(su->host);
show_certs(t, certs, cert_count, "Certificate Chain");
} else if (args->i & XT_SAVE) {
host = t->about_cert_host ? t->about_cert_host : su->host;
save_certs(t, certs, cert_count, host, certs_dir);
#if GTK_CHECK_VERSION(3, 0, 0)
gtk_widget_set_name(t->uri_entry, XT_CSS_BLUE);
statusbar_modify_attr(t, XT_CSS_BLUE);
#else
gdk_color_parse(XT_COLOR_BLUE, &color);
gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
statusbar_modify_attr(t, XT_COLOR_BLACK, XT_COLOR_BLUE);
#endif
} else if (args->i & XT_CACHE)
save_certs(t, certs, cert_count, su->host, certs_cache_dir);
free_connection_certs(certs, cert_count);
done:
soup_uri_free(su);
if (error_str && strlen(error_str)) {
show_oops(t, "%s", error_str);
return (1);
}
return (0);
}
/*
* Checks whether the remote cert is identical to the local saved
* cert. Returns CERT_LOCAL if unchanged, CERT_UNTRUSTED if local
* cert does not exist, and CERT_BAD if different.
*
* Saves entire cert chain in pem encoding to chain for it to be
* cached later, if needed.
*/
enum cert_trust
check_local_certs(const char *file, GTlsCertificate *cert, char **chain)
{
char r_cert_buf[64 * 1024];
FILE *f;
GTlsCertificate *tmpcert = NULL;
GTlsCertificate *issuer = NULL;
char *pem = NULL;
char *tmp = NULL;
enum cert_trust rv = CERT_LOCAL;
int i;
if ((f = fopen(file, "r")) == NULL) {
/* no local cert to check */
rv = CERT_UNTRUSTED;
}
for (i = 0;;) {
if (i == 0) {
g_object_get(G_OBJECT(cert), "certificate-pem", &pem,
NULL);
g_object_get(G_OBJECT(cert), "issuer", &issuer, NULL);
} else {
if (issuer == NULL)
break;
g_object_get(G_OBJECT(tmpcert), "issuer", &issuer,
NULL);
if (issuer == NULL)
break;
g_object_get(G_OBJECT(tmpcert), "certificate-pem", &pem,
NULL);
}
i++;
if (tmpcert != NULL)
g_object_unref(G_OBJECT(tmpcert));
tmpcert = issuer;
if (f) {
if (fread(r_cert_buf, strlen(pem), 1, f) != 1 && !feof(f))
rv = CERT_BAD;
if (bcmp(r_cert_buf, pem, strlen(pem)))
rv = CERT_BAD;
}
tmp = g_strdup_printf("%s%s", *chain, pem);
g_free(pem);
g_free(*chain);
*chain = tmp;
}
if (issuer != NULL)
g_object_unref(G_OBJECT(issuer));
if (f != NULL)
fclose(f);
return (rv);
}
int
check_cert_changes(struct tab *t, GTlsCertificate *cert, const char *file, const char *uri)
{
SoupURI *soupuri = NULL;
struct karg args = {0};
struct wl_entry *w = NULL;
char *chain = NULL;
int ret = 0;
chain = g_strdup("");
switch(check_local_certs(file, cert, &chain)) {
case CERT_LOCAL:
/* The cached certificate is identical */
break;
case CERT_TRUSTED: /* FALLTHROUGH */
case CERT_UNTRUSTED:
/* cache new certificate */
args.i = XT_CACHE;
cert_cmd(t, &args);
break;
case CERT_BAD:
if ((soupuri = soup_uri_new(uri)) == NULL ||
soupuri->host == NULL)
break;
if ((w = wl_find(soupuri->host, &svil)) != NULL)
break;
set_xtp_meaning(t, XT_XTP_TAB_MEANING_SV);
args.s = g_strdup((char *)uri);
xtp_page_sv(t, &args);
ret = 1;
break;
}
if (soupuri)
soup_uri_free(soupuri);
if (chain)
g_free(chain);
return (ret);
}
int
remove_cookie(int index)
{
int i, rv = 1;
GSList *cf;
SoupCookie *c;
DNPRINTF(XT_D_COOKIE, "remove_cookie: %d\n", index);
cf = soup_cookie_jar_all_cookies(s_cookiejar);
for (i = 1; cf; cf = cf->next, i++) {
if (i != index)
continue;
c = cf->data;
print_cookie("remove cookie", c);
soup_cookie_jar_delete_cookie(s_cookiejar, c);
rv = 0;
break;
}
soup_cookies_free(cf);
return (rv);
}
int
remove_cookie_domain(int domain_id)
{
int domain_count, rv = 1;
GSList *cf;
SoupCookie *c;
char *last_domain;
DNPRINTF(XT_D_COOKIE, "remove_cookie_domain: %d\n", domain_id);
last_domain = "";
cf = soup_cookie_jar_all_cookies(s_cookiejar);
for (domain_count = 0; cf; cf = cf->next) {
c = cf->data;
if (strcmp(last_domain, c->domain) != 0) {
domain_count++;
last_domain = c->domain;
}
if (domain_count < domain_id)
continue;
else if (domain_count > domain_id)
break;
print_cookie("remove cookie", c);
soup_cookie_jar_delete_cookie(s_cookiejar, c);
rv = 0;
}
soup_cookies_free(cf);
return (rv);
}
int
remove_cookie_all()
{
int rv = 1;
GSList *cf;
SoupCookie *c;
DNPRINTF(XT_D_COOKIE, "remove_cookie_all\n");
cf = soup_cookie_jar_all_cookies(s_cookiejar);
for (; cf; cf = cf->next) {
c = cf->data;
print_cookie("remove cookie", c);
soup_cookie_jar_delete_cookie(s_cookiejar, c);
rv = 0;
}
soup_cookies_free(cf);
return (rv);
}
int
toplevel_cmd(struct tab *t, struct karg *args)
{
js_toggle_cb(t->js_toggle, t);
return (0);
}
int
can_go_back_for_real(struct tab *t)
{
int i;
WebKitWebHistoryItem *item;
const gchar *uri;
if (t == NULL)
return (FALSE);
if (t->item != NULL)
return (TRUE);
/* rely on webkit to make sure we can go backward when on an about page */
uri = get_uri(t);
if (uri == NULL || g_str_has_prefix(uri, "about:") ||
g_str_has_prefix(uri, "xxxt://"))
return (webkit_web_view_can_go_back(t->wv));
/* the back/forward list is stupid so help determine if we can go back */
for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
item != NULL;
i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
if (strcmp(webkit_web_history_item_get_uri(item), uri))
return (TRUE);
}
return (FALSE);
}
int
can_go_forward_for_real(struct tab *t)
{
int i;
WebKitWebHistoryItem *item;
const gchar *uri;
if (t == NULL)
return (FALSE);
/* rely on webkit to make sure we can go forward when on an about page */
uri = get_uri(t);
if (uri == NULL || g_str_has_prefix(uri, "about:") ||
g_str_has_prefix(uri, "xxxt://"))
return (webkit_web_view_can_go_forward(t->wv));
/* the back/forwars list is stupid so help selecting a different item */
for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
item != NULL;
i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
if (strcmp(webkit_web_history_item_get_uri(item), uri))
return (TRUE);
}
return (FALSE);
}
void
go_back_for_real(struct tab *t)
{
int i;
WebKitWebHistoryItem *item;
const gchar *uri;
if (t == NULL)
return;
uri = get_uri(t);
if (uri == NULL || g_str_has_prefix(uri, "about:") ||
g_str_has_prefix(uri, "xxxt://")) {
webkit_web_view_go_back(t->wv);
return;
}
/* the back/forwars list is stupid so help selecting a different item */
for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
item != NULL;
i--, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
webkit_web_view_go_to_back_forward_item(t->wv, item);
break;
}
}
}
void
go_forward_for_real(struct tab *t)
{
int i;
WebKitWebHistoryItem *item;
const gchar *uri;
if (t == NULL)
return;
uri = get_uri(t);
if (uri == NULL || g_str_has_prefix(uri, "about:") ||
g_str_has_prefix(uri, "xxxt://")) {
webkit_web_view_go_forward(t->wv);
return;
}
/* the back/forwars list is stupid so help selecting a different item */
for (i = 0, item = webkit_web_back_forward_list_get_current_item(t->bfl);
item != NULL;
i++, item = webkit_web_back_forward_list_get_nth_item(t->bfl, i)) {
if (strcmp(webkit_web_history_item_get_uri(item), uri)) {
webkit_web_view_go_to_back_forward_item(t->wv, item);
break;
}
}
}
int
navaction(struct tab *t, struct karg *args)
{
WebKitWebHistoryItem *item = NULL;
WebKitWebFrame *frame;
DNPRINTF(XT_D_NAV, "navaction: tab %d opcode %d\n",
t->tab_id, args->i);
hide_oops(t);
set_normal_tab_meaning(t);
if (t->item) {
if (args->i == XT_NAV_BACK)
item = webkit_web_back_forward_list_get_current_item(t->bfl);
else
item = webkit_web_back_forward_list_get_forward_item(t->bfl);
}
switch (args->i) {
case XT_NAV_BACK:
if (t->item) {
if (item == NULL)
return (XT_CB_PASSTHROUGH);
webkit_web_view_go_to_back_forward_item(t->wv, item);
t->item = NULL;
return (XT_CB_PASSTHROUGH);
}
marks_clear(t);
go_back_for_real(t);
break;
case XT_NAV_FORWARD:
if (t->item) {
if (item == NULL)
return (XT_CB_PASSTHROUGH);
webkit_web_view_go_to_back_forward_item(t->wv, item);
t->item = NULL;
return (XT_CB_PASSTHROUGH);
}
marks_clear(t);
go_forward_for_real(t);
break;
case XT_NAV_RELOAD:
frame = webkit_web_view_get_main_frame(t->wv);
webkit_web_frame_reload(frame);
break;
case XT_NAV_STOP:
frame = webkit_web_view_get_main_frame(t->wv);
webkit_web_frame_stop_loading(frame);
break;
}
return (XT_CB_PASSTHROUGH);
}
int
move(struct tab *t, struct karg *args)
{
GtkAdjustment *adjust;
double pi, si, pos, ps, upper, lower, max;
double percent;
switch (args->i) {
case XT_MOVE_DOWN:
case XT_MOVE_UP:
case XT_MOVE_BOTTOM:
case XT_MOVE_TOP:
case XT_MOVE_PAGEDOWN:
case XT_MOVE_PAGEUP:
case XT_MOVE_HALFDOWN:
case XT_MOVE_HALFUP:
case XT_MOVE_PERCENT:
case XT_MOVE_CENTER:
adjust = t->adjust_v;
break;
default:
adjust = t->adjust_h;
break;
}
pos = gtk_adjustment_get_value(adjust);
ps = gtk_adjustment_get_page_size(adjust);
upper = gtk_adjustment_get_upper(adjust);
lower = gtk_adjustment_get_lower(adjust);
si = gtk_adjustment_get_step_increment(adjust);
pi = gtk_adjustment_get_page_increment(adjust);
max = upper - ps;
DNPRINTF(XT_D_MOVE, "move: opcode %d %s pos %f ps %f upper %f lower %f "
"max %f si %f pi %f\n",
args->i, adjust == t->adjust_h ? "horizontal" : "vertical",
pos, ps, upper, lower, max, si, pi);
switch (args->i) {
case XT_MOVE_DOWN:
case XT_MOVE_RIGHT:
pos += si;
gtk_adjustment_set_value(adjust, MIN(pos, max));
break;
case XT_MOVE_UP:
case XT_MOVE_LEFT:
pos -= si;
gtk_adjustment_set_value(adjust, MAX(pos, lower));
break;
case XT_MOVE_BOTTOM:
case XT_MOVE_FARRIGHT:
t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
gtk_adjustment_set_value(adjust, max);
break;
case XT_MOVE_TOP:
case XT_MOVE_FARLEFT:
t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
gtk_adjustment_set_value(adjust, lower);
break;
case XT_MOVE_PAGEDOWN:
pos += pi;
gtk_adjustment_set_value(adjust, MIN(pos, max));
break;
case XT_MOVE_PAGEUP:
pos -= pi;
gtk_adjustment_set_value(adjust, MAX(pos, lower));
break;
case XT_MOVE_HALFDOWN:
pos += pi / 2;
gtk_adjustment_set_value(adjust, MIN(pos, max));
break;
case XT_MOVE_HALFUP:
pos -= pi / 2;
gtk_adjustment_set_value(adjust, MAX(pos, lower));
break;
case XT_MOVE_CENTER:
t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
args->s = g_strdup("50.0");
/* FALLTHROUGH */
case XT_MOVE_PERCENT:
t->mark[marktoindex('\'')] = gtk_adjustment_get_value(t->adjust_v);
percent = atoi(args->s) / 100.0;
pos = max * percent;
if (pos < 0.0 || pos > max)
break;
gtk_adjustment_set_value(adjust, pos);
break;
default:
return (XT_CB_PASSTHROUGH);
}
DNPRINTF(XT_D_MOVE, "move: new pos %f %f\n", pos, MIN(pos, max));
return (XT_CB_HANDLED);
}
void
url_set_visibility(void)
{
struct tab *t;
TAILQ_FOREACH(t, &tabs, entry)
if (show_url == 0) {
gtk_widget_hide(t->toolbar);
focus_webview(t);
} else
gtk_widget_show(t->toolbar);
}
void
notebook_tab_set_visibility(void)
{
if (show_tabs == 0) {
gtk_widget_hide(tab_bar);
gtk_notebook_set_show_tabs(notebook, FALSE);
} else {
if (tab_style == XT_TABS_NORMAL) {
gtk_widget_hide(tab_bar);
gtk_notebook_set_show_tabs(notebook, TRUE);
} else if (tab_style == XT_TABS_COMPACT) {
gtk_widget_show(tab_bar);
gtk_notebook_set_show_tabs(notebook, FALSE);
}
}
}
void
statusbar_set_visibility(void)
{
struct tab *t;
TAILQ_FOREACH(t, &tabs, entry){
if (show_statusbar == 0)
gtk_widget_hide(t->statusbar);
else
gtk_widget_show(t->statusbar);
focus_webview(t);
}
}
void
url_set(struct tab *t, int enable_url_entry)
{
GdkPixbuf *pixbuf;
int progress;
show_url = enable_url_entry;
if (enable_url_entry) {
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, NULL);
gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.uri), 0);
} else {
pixbuf = gtk_entry_get_icon_pixbuf(GTK_ENTRY(t->uri_entry),
GTK_ENTRY_ICON_PRIMARY);
progress =
gtk_entry_get_progress_fraction(GTK_ENTRY(t->uri_entry));
gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, pixbuf);
gtk_entry_set_progress_fraction(GTK_ENTRY(t->sbe.uri),
progress);
}
}
int
fullscreen(struct tab *t, struct karg *args)
{
DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
if (t == NULL)
return (XT_CB_PASSTHROUGH);
if (show_url == 0) {
url_set(t, 1);
show_tabs = 1;
} else {
url_set(t, 0);
show_tabs = 0;
}
url_set_visibility();
notebook_tab_set_visibility();
return (XT_CB_HANDLED);
}
int
statustoggle(struct tab *t, struct karg *args)
{
DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
if (show_statusbar == 1) {
show_statusbar = 0;
statusbar_set_visibility();
} else if (show_statusbar == 0) {
show_statusbar = 1;
statusbar_set_visibility();
}
return (XT_CB_HANDLED);
}
int
urlaction(struct tab *t, struct karg *args)
{
int rv = XT_CB_HANDLED;
DNPRINTF(XT_D_TAB, "%s: %p %d\n", __func__, t, args->i);
if (t == NULL)
return (XT_CB_PASSTHROUGH);
switch (args->i) {
case XT_URL_SHOW:
if (show_url == 0) {
url_set(t, 1);
url_set_visibility();
}
break;
case XT_URL_HIDE:
if (show_url == 1) {
url_set(t, 0);
url_set_visibility();
}
break;
}
return (rv);
}
int
tabaction(struct tab *t, struct karg *args)
{
int rv = XT_CB_HANDLED;
char *url = args->s;
struct undo *u;
struct tab *tt, *tv;
DNPRINTF(XT_D_TAB, "tabaction: %p %d\n", t, args->i);
if (t == NULL)
return (XT_CB_PASSTHROUGH);
switch (args->i) {
case XT_TAB_NEW:
if (strlen(url) > 0)
create_new_tab(url, NULL, 1, args->precount);
else
create_new_tab(NULL, NULL, 1, args->precount);
break;
case XT_TAB_DELETE:
if (args->precount < 0)
delete_tab(t);
else
TAILQ_FOREACH(tt, &tabs, entry)
if (tt->tab_id == args->precount - 1) {
delete_tab(tt);
break;
}
break;
case XT_TAB_DELQUIT:
if (gtk_notebook_get_n_pages(notebook) > 1)
delete_tab(t);
else
quit(t, args);
break;
case XT_TAB_ONLY:
TAILQ_FOREACH_SAFE(tt, &tabs, entry, tv)
if (t != tt)
delete_tab(tt);
break;
case XT_TAB_OPEN:
if (strlen(url) > 0)
;
else {
rv = XT_CB_PASSTHROUGH;
goto done;
}
load_uri(t, url);
break;
case XT_TAB_SHOW:
if (show_tabs == 0) {
show_tabs = 1;
notebook_tab_set_visibility();
}
break;
case XT_TAB_HIDE:
if (show_tabs == 1) {
show_tabs = 0;
notebook_tab_set_visibility();
}
break;
case XT_TAB_NEXTSTYLE:
if (tab_style == XT_TABS_NORMAL) {
tab_style = XT_TABS_COMPACT;
recolor_compact_tabs();
}
else
tab_style = XT_TABS_NORMAL;
notebook_tab_set_visibility();
break;
case XT_TAB_UNDO_CLOSE:
if (undo_count == 0) {
DNPRINTF(XT_D_TAB, "%s: no tabs to undo close",
__func__);
goto done;
} else {
undo_count--;
u = TAILQ_FIRST(&undos);
create_new_tab(u->uri, u, 1, -1);
TAILQ_REMOVE(&undos, u, entry);
g_free(u->uri);
/* u->history is freed in create_new_tab() */
g_free(u);
}
break;
case XT_TAB_LOAD_IMAGES:
if (!auto_load_images) {
/* Enable auto-load images (this will load all
* previously unloaded images). */
g_object_set(G_OBJECT(t->settings),
"auto-load-images", TRUE, (char *)NULL);
webkit_web_view_set_settings(t->wv, t->settings);
webkit_web_view_reload(t->wv);
/* Webkit triggers an event when we change the setting,
* so we can't disable the auto-loading at once.
*
* Unfortunately, webkit does not tell us when it's done.
* Instead, we wait until the next request, and then
* disable autoloading again.
*/
t->load_images = TRUE;
}
break;
default:
rv = XT_CB_PASSTHROUGH;
goto done;
}
done:
if (args->s) {
g_free(args->s);
args->s = NULL;
}
return (rv);
}
int
resizetab(struct tab *t, struct karg *args)
{
if (t == NULL || args == NULL) {
show_oops(NULL, "resizetab invalid parameters");
return (XT_CB_PASSTHROUGH);
}
DNPRINTF(XT_D_TAB, "resizetab: tab %d %d\n",
t->tab_id, args->i);
setzoom_webkit(t, args->i);
return (XT_CB_HANDLED);
}
int
movetab(struct tab *t, struct karg *args)
{
int n, dest;
if (t == NULL || args == NULL) {
show_oops(NULL, "movetab invalid parameters");
return (XT_CB_PASSTHROUGH);
}
DNPRINTF(XT_D_TAB, "movetab: tab %d opcode %d\n",
t->tab_id, args->i);
if (args->i >= XT_TAB_INVALID)
return (XT_CB_PASSTHROUGH);
if (TAILQ_EMPTY(&tabs))
return (XT_CB_PASSTHROUGH);
n = gtk_notebook_get_n_pages(notebook);
dest = gtk_notebook_get_current_page(notebook);
switch (args->i) {
case XT_TAB_NEXT:
if (args->precount < 0)
dest = dest == n - 1 ? 0 : dest + 1;
else
dest = args->precount - 1;
break;
case XT_TAB_PREV:
if (args->precount < 0)
dest -= 1;
else
dest -= args->precount % n;
if (dest < 0)
dest += n;
break;
case XT_TAB_FIRST:
dest = 0;
break;
case XT_TAB_LAST:
dest = n - 1;
break;
default:
return (XT_CB_PASSTHROUGH);
}
if (dest < 0 || dest >= n)
return (XT_CB_PASSTHROUGH);
if (t->tab_id == dest) {
DNPRINTF(XT_D_TAB, "movetab: do nothing\n");
return (XT_CB_HANDLED);
}
set_current_tab(dest);
return (XT_CB_HANDLED);
}
int cmd_prefix = 0;
struct prompt_sub {
const char *s;
const char *(*f)(struct tab *);
} subs[] = {
{ "<uri>", get_uri },
};
int
command(struct tab *t, struct karg *args)
{
struct karg a = {0};
int i, cmd_setup = 0;
char *s = NULL, *sp = NULL, *sl = NULL;
gchar **sv;
if (t == NULL || args == NULL) {
show_oops(NULL, "command invalid parameters");
return (XT_CB_PASSTHROUGH);
}
switch (args->i) {
case '/':
s = "/";
break;
case '?':
s = "?";
break;
case ':':
if (cmd_prefix == 0) {
if (args->s != NULL && strlen(args->s) != 0) {
sp = g_strdup_printf(":%s", args->s);
s = sp;
} else
s = ":";
} else {
sp = g_strdup_printf(":%d", cmd_prefix);
s = sp;
cmd_prefix = 0;
}
sl = g_strdup(s);
if (sp) {
g_free(sp);
sp = NULL;
}
s = sl;
for (i = 0; i < LENGTH(subs); ++i) {
sv = g_strsplit(sl, subs[i].s, -1);
if (sl)
g_free(sl);
sl = g_strjoinv(subs[i].f(t), sv);
g_strfreev(sv);
s = sl;
}
break;
case '.':
t->mode = XT_MODE_HINT;
a.i = 0;
s = ".";
/*
* js code will auto fire() if a single link is visible,
* causing the focus-out-event cb function to be called. Setup
* the cmd _before_ triggering hinting code so the cmd can get
* killed by the cb in this case.
*/
show_cmd(t, s);
cmd_setup = 1;
hint(t, &a);
break;
case ',':
t->mode = XT_MODE_HINT;
a.i = XT_HINT_NEWTAB;
s = ",";
show_cmd(t, s);
cmd_setup = 1;
hint(t, &a);
break;
default:
show_oops(t, "command: invalid opcode %d", args->i);
return (XT_CB_PASSTHROUGH);
}
DNPRINTF(XT_D_CMD, "%s: tab %d type %s\n", __func__, t->tab_id, s);
if (!cmd_setup)
show_cmd(t, s);
if (sp)
g_free(sp);
if (sl)
g_free(sl);
return (XT_CB_HANDLED);
}
int
search(struct tab *t, struct karg *args)
{
gboolean d;
if (t == NULL || args == NULL) {
show_oops(NULL, "search invalid parameters");
return (1);
}
switch (args->i) {
case XT_SEARCH_NEXT:
d = t->search_forward;
break;
case XT_SEARCH_PREV:
d = !t->search_forward;
break;
default:
return (XT_CB_PASSTHROUGH);
}
if (t->search_text == NULL) {
if (global_search == NULL)
return (XT_CB_PASSTHROUGH);
else {
d = t->search_forward = TRUE;
t->search_text = g_strdup(global_search);
webkit_web_view_mark_text_matches(t->wv, global_search, FALSE, 0);
webkit_web_view_set_highlight_text_matches(t->wv, TRUE);
}
}
DNPRINTF(XT_D_CMD, "search: tab %d opc %d forw %d text %s\n",
t->tab_id, args->i, t->search_forward, t->search_text);
webkit_web_view_search_text(t->wv, t->search_text, FALSE, d, TRUE);
return (XT_CB_HANDLED);
}
int
session_save(struct tab *t, char *filename)
{
struct karg a;
int rv = 1;
struct session *s;
if (strlen(filename) == 0)
goto done;
if (filename[0] == '.' || filename[0] == '/')
goto done;
a.s = filename;
if (save_tabs(t, &a))
goto done;
strlcpy(named_session, filename, sizeof named_session);
/* add the new session to the list of sessions */
s = g_malloc(sizeof(struct session));
s->name = g_strdup(filename);
TAILQ_INSERT_TAIL(&sessions, s, entry);
rv = 0;
done:
return (rv);
}
int
session_open(struct tab *t, char *filename)
{
struct karg a;
int rv = 1;
if (strlen(filename) == 0)
goto done;
if (filename[0] == '.' || filename[0] == '/')
goto done;
a.s = filename;
a.i = XT_SES_CLOSETABS;
if (open_tabs(t, &a))
goto done;
strlcpy(named_session, filename, sizeof named_session);
rv = 0;
done:
return (rv);
}
int
session_delete(struct tab *t, char *filename)
{
char file[PATH_MAX];
int rv = 1;
struct session *s;
if (strlen(filename) == 0)
goto done;
if (filename[0] == '.' || filename[0] == '/')
goto done;
snprintf(file, sizeof file, "%s" PS "%s", sessions_dir, filename);
if (unlink(file))
goto done;
if (!strcmp(filename, named_session))
strlcpy(named_session, XT_SAVED_TABS_FILE,
sizeof named_session);
/* remove session from sessions list */
TAILQ_FOREACH(s, &sessions, entry) {
if (!strcmp(s->name, filename))
break;
}
if (s == NULL)
goto done;
TAILQ_REMOVE(&sessions, s, entry);
g_free((gpointer) s->name);
g_free(s);
rv = 0;
done:
return (rv);
}
int
session_cmd(struct tab *t, struct karg *args)
{
char *filename = args->s;
if (t == NULL)
return (1);
if (args->i & XT_SHOW)
show_oops(t, "Current session: %s", named_session[0] == '\0' ?
XT_SAVED_TABS_FILE : named_session);
else if (args->i & XT_SAVE) {
if (session_save(t, filename)) {
show_oops(t, "Can't save session: %s",
filename ? filename : "INVALID");
goto done;
}
} else if (args->i & XT_OPEN) {
if (session_open(t, filename)) {
show_oops(t, "Can't open session: %s",
filename ? filename : "INVALID");
goto done;
}
} else if (args->i & XT_DELETE) {
if (session_delete(t, filename)) {
show_oops(t, "Can't delete session: %s",
filename ? filename : "INVALID");
goto done;
}
}
done:
return (XT_CB_PASSTHROUGH);
}
int
script_cmd(struct tab *t, struct karg *args)
{
struct stat sb;
FILE *f = NULL;
char *buf = NULL;
if (t == NULL)
goto done;
if ((f = fopen(args->s, "r")) == NULL) {
show_oops(t, "Can't open script file: %s", args->s);
goto done;
}
if (fstat(fileno(f), &sb) == -1) {
show_oops(t, "Can't stat script file: %s", args->s);
goto done;
}
buf = g_malloc0(sb.st_size + 1);
if (fread(buf, 1, sb.st_size, f) != sb.st_size) {
show_oops(t, "Can't read script file: %s", args->s);
goto done;
}
DNPRINTF(XT_D_JS, "%s: about to run script\n", __func__);
run_script(t, buf);
done:
if (f)
fclose(f);
if (buf)
g_free(buf);
return (XT_CB_PASSTHROUGH);
}
/*
* Make a hardcopy of the page
*/
int
print_page(struct tab *t, struct karg *args)
{
WebKitWebFrame *frame;
GtkPageSetup *ps;
GtkPrintOperation *op;
GtkPrintOperationAction action;
GtkPrintOperationResult print_res;
GError *g_err = NULL;
int marg_l, marg_r, marg_t, marg_b;
int ret = 0;
DNPRINTF(XT_D_PRINTING, "%s:", __func__);
ps = gtk_page_setup_new();
op = gtk_print_operation_new();
action = GTK_PRINT_OPERATION_ACTION_PRINT_DIALOG;
frame = webkit_web_view_get_main_frame(t->wv);
/* the default margins are too small, so we will bump them */
marg_l = gtk_page_setup_get_left_margin(ps, GTK_UNIT_MM) +
XT_PRINT_EXTRA_MARGIN;
marg_r = gtk_page_setup_get_right_margin(ps, GTK_UNIT_MM) +
XT_PRINT_EXTRA_MARGIN;
marg_t = gtk_page_setup_get_top_margin(ps, GTK_UNIT_MM) +
XT_PRINT_EXTRA_MARGIN;
marg_b = gtk_page_setup_get_bottom_margin(ps, GTK_UNIT_MM) +
XT_PRINT_EXTRA_MARGIN;
/* set margins */
gtk_page_setup_set_left_margin(ps, marg_l, GTK_UNIT_MM);
gtk_page_setup_set_right_margin(ps, marg_r, GTK_UNIT_MM);
gtk_page_setup_set_top_margin(ps, marg_t, GTK_UNIT_MM);
gtk_page_setup_set_bottom_margin(ps, marg_b, GTK_UNIT_MM);
gtk_print_operation_set_default_page_setup(op, ps);
print_res = webkit_web_frame_print_full(frame, op, action, &g_err);
/* check it worked */
if (print_res == GTK_PRINT_OPERATION_RESULT_ERROR) {
show_oops(NULL, "can't print: %s", g_err->message);
g_error_free (g_err);
ret = 1;
}
g_object_unref(G_OBJECT(ps));
g_object_unref(G_OBJECT(op));
return (ret);
}
int
go_home(struct tab *t, struct karg *args)
{
load_uri(t, home);
return (0);
}
int
set_encoding(struct tab *t, struct karg *args)
{
const gchar *e;
if (args->s && strlen(g_strstrip(args->s)) == 0) {
e = webkit_web_view_get_custom_encoding(t->wv);
if (e == NULL)
e = webkit_web_view_get_encoding(t->wv);
show_oops(t, "encoding: %s", e ? e : "N/A");
} else
webkit_web_view_set_custom_encoding(t->wv, args->s);
return (0);
}
int
restart(struct tab *t, struct karg *args)
{
struct karg a;
a.s = XT_RESTART_TABS_FILE;
save_tabs(t, &a);
execvp(start_argv[0], start_argv);
/* NOTREACHED */
return (0);
}
char *http_proxy_save; /* not a setting, used to toggle */
int
proxy_cmd(struct tab *t, struct karg *args)
{
struct tab *tt;
DNPRINTF(XT_D_CMD, "%s: tab %d\n", __func__, t->tab_id);
if (t == NULL)
return (1);
/* setup */
if (http_proxy) {
TAILQ_FOREACH(tt, &tabs, entry)
gtk_widget_show(t->proxy_toggle);
if (http_proxy_save)
g_free(http_proxy_save);
http_proxy_save = g_strdup(http_proxy);
}
if (args->i & XT_PRXY_SHOW) {
if (http_proxy)
show_oops(t, "http_proxy = %s", http_proxy);
else
show_oops(t, "proxy is currently disabled");
} else if (args->i & XT_PRXY_TOGGLE) {
if (http_proxy_save == NULL && http_proxy == NULL) {
show_oops(t, "can't toggle proxy");
goto done;
}
TAILQ_FOREACH(tt, &tabs, entry)
gtk_widget_show(t->proxy_toggle);
if (http_proxy) {
if (setup_proxy(NULL) == 0)
button_set_file(t->proxy_toggle,
"tordisabled.ico");
show_oops(t, "http proxy disabled");
} else {
if (setup_proxy(http_proxy_save) == 0 && http_proxy) {
button_set_file(t->proxy_toggle,
"torenabled.ico");
show_oops(t, "http_proxy = %s", http_proxy);
} else
show_oops(t, "invalid proxy: %s", http_proxy_save);
}
}
done:
return (XT_CB_PASSTHROUGH);
}
/*
* If you can read this functionthen you are a sick and twisted individual.
* I hope we never meet, it'll be violent.
*/
gboolean
eval_cb(const GMatchInfo *info, GString *res, gpointer data)
{
gchar *match, *num;
gint start = -1, end = -1, i;
struct karg *args = data;
/*
* match contains the string UP TO the match.
*
* res is what is returned, note that whatever remains in the sent in
* string is appended on the way out.
*
* for example /123/456/789/moo came in
* match contains /123/456/789/
* we assign that to res and replace /789/ with the replacement text
* then g_regex_replace_eval on the way out has /123/456/replacement/moo
*
*/
match = g_match_info_fetch(info, 0);
if (match == NULL)
goto done;
if (g_match_info_fetch_pos(info, 1, &start, &end) == FALSE)
goto freeit;
g_string_assign(res, match);
i = atoi(&match[start + 1]);
if (args->i == XT_URL_PLUS)
i++;
else
i--;
/* preserve whitespace when likely */
num = g_strdup_printf("%0*d", end - start - 2, i);
g_string_overwrite_len(res, start + 1, num, end - start - 2);
g_free(num);
freeit:
g_free(match);
done:
return (FALSE); /* doesn't matter */
}
int
urlmod_cmd(struct tab *t, struct karg *args)
{
const gchar *uri;
GRegex *reg;
gchar *res;
if (t == NULL)
goto done;
if ((uri = gtk_entry_get_text(GTK_ENTRY(t->uri_entry))) == NULL)
goto done;
if (strlen(uri) == 0)
goto done;
reg = g_regex_new(".*(/[0-9]+/)", 0, 0, NULL);
if (reg == NULL)
goto done;
res = g_regex_replace_eval(reg, uri, -1, 0, 0, eval_cb, args, NULL);
if (res == NULL)
goto free_reg;
if (!strcmp(res, uri))
goto free_res;
gtk_entry_set_text(GTK_ENTRY(t->uri_entry), res);
activate_uri_entry_cb(t->uri_entry, t);
free_res:
g_free(res);
free_reg:
g_regex_unref(reg);
done:
return (XT_CB_PASSTHROUGH);
}
struct cmd {
char *cmd;
int level;
int (*func)(struct tab *, struct karg *);
int arg;
int type;
} cmds[] = {
{ "command_mode", 0, command_mode, XT_MODE_COMMAND, 0 },
{ "insert_mode", 0, command_mode, XT_MODE_INSERT, 0 },
{ "command", 0, command, ':', 0 },
{ "search", 0, command, '/', 0 },
{ "searchb", 0, command, '?', 0 },
{ "hinting", 0, command, '.', 0 },
{ "hinting_newtab", 0, command, ',', 0 },
{ "togglesrc", 0, toggle_src, 0, 0 },
{ "editsrc", 0, edit_src, 0, 0 },
{ "editelement", 0, edit_element, 0, 0 },
{ "passthrough", 0, passthrough, 0, 0 },
{ "modurl", 0, modurl, 0, 0 },
{ "modsearchentry", 0, modsearchentry, 0, 0 },
/* yanking and pasting */
{ "yankuri", 0, yank_uri, 0, 0 },
{ "pasteuricur", 0, paste_uri, XT_PASTE_CURRENT_TAB, 0 },
{ "pasteurinew", 0, paste_uri, XT_PASTE_NEW_TAB, 0 },
/* search */
{ "searchnext", 0, search, XT_SEARCH_NEXT, 0 },
{ "searchprevious", 0, search, XT_SEARCH_PREV, 0 },
/* focus */
{ "focusaddress", 0, focus, XT_FOCUS_URI, 0 },
{ "focussearch", 0, focus, XT_FOCUS_SEARCH, 0 },
/* hinting */
{ "hinting", 0, hint, 0, 0 },
{ "hinting_newtab", 0, hint, XT_HINT_NEWTAB, 0 },
/* custom stylesheet */
{ "userstyle", 0, userstyle_cmd, XT_STYLE_CURRENT_TAB, XT_USERARG },
{ "userstyle_global", 0, userstyle_cmd, XT_STYLE_GLOBAL, XT_USERARG },
/* navigation */
{ "goback", 0, navaction, XT_NAV_BACK, 0 },
{ "goforward", 0, navaction, XT_NAV_FORWARD, 0 },
{ "reload", 0, navaction, XT_NAV_RELOAD, 0 },
{ "stop", 0, navaction, XT_NAV_STOP, 0 },
/* vertical movement */
{ "scrolldown", 0, move, XT_MOVE_DOWN, 0 },
{ "scrollup", 0, move, XT_MOVE_UP, 0 },
{ "scrollbottom", 0, move, XT_MOVE_BOTTOM, 0 },
{ "scrolltop", 0, move, XT_MOVE_TOP, 0 },
{ "1", 0, move, XT_MOVE_TOP, 0 },
{ "scrollhalfdown", 0, move, XT_MOVE_HALFDOWN, 0 },
{ "scrollhalfup", 0, move, XT_MOVE_HALFUP, 0 },
{ "scrollpagedown", 0, move, XT_MOVE_PAGEDOWN, 0 },
{ "scrollpageup", 0, move, XT_MOVE_PAGEUP, 0 },
/* horizontal movement */
{ "scrollright", 0, move, XT_MOVE_RIGHT, 0 },
{ "scrollleft", 0, move, XT_MOVE_LEFT, 0 },
{ "scrollfarright", 0, move, XT_MOVE_FARRIGHT, 0 },
{ "scrollfarleft", 0, move, XT_MOVE_FARLEFT, 0 },
{ "favorites", 0, xtp_page_fl, XT_SHOW, 0 },
{ "fav", 0, xtp_page_fl, XT_SHOW, 0 },
{ "favedit", 0, xtp_page_fl, XT_SHOW|XT_DELETE, 0 },
{ "favadd", 0, add_favorite, 0, XT_USERARG },
{ "qall", 0, quit, 0, 0 },
{ "quitall", 0, quit, 0, 0 },
{ "w", 0, save_tabs, 0, 0 },
{ "wq", 0, save_tabs_and_quit, 0, 0 },
{ "help", 0, help, 0, 0 },
{ "about", 0, xtp_page_ab, 0, 0 },
{ "stats", 0, stats, 0, 0 },
{ "version", 0, xtp_page_ab, 0, 0 },
/* js command */
{ "js", 0, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "save", 1, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "domain", 2, js_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, js_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "show", 1, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "all", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "persistent", 2, js_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
{ "session", 2, js_cmd, XT_SHOW | XT_WL_SESSION, 0 },
{ "toggle", 1, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
{ "domain", 2, js_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, js_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
/* cookie command */
{ "cookie", 0, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "save", 1, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "domain", 2, cookie_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, cookie_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "show", 1, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "all", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "persistent", 2, cookie_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
{ "session", 2, cookie_cmd, XT_SHOW | XT_WL_SESSION, 0 },
{ "toggle", 1, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
{ "domain", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, cookie_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
{ "purge", 1, cookie_cmd, XT_DELETE, 0 },
/* plugin command */
{ "plugin", 0, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "save", 1, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "domain", 2, pl_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, pl_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "show", 1, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "all", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "persistent", 2, pl_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
{ "session", 2, pl_cmd, XT_SHOW | XT_WL_SESSION, 0 },
{ "toggle", 1, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
{ "domain", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, pl_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
/* https command */
{ "https", 0, https_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "save", 1, https_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "domain", 2, https_cmd, XT_SAVE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, https_cmd, XT_SAVE | XT_WL_FQDN, 0 },
{ "show", 1, https_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "all", 2, https_cmd, XT_SHOW | XT_WL_PERSISTENT | XT_WL_SESSION, 0 },
{ "persistent", 2, https_cmd, XT_SHOW | XT_WL_PERSISTENT, 0 },
{ "session", 2, https_cmd, XT_SHOW | XT_WL_SESSION, 0 },
{ "toggle", 1, https_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
{ "domain", 2, https_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL, 0 },
{ "fqdn", 2, https_cmd, XT_WL_TOGGLE | XT_WL_FQDN, 0 },
/* toplevel (domain) command */
{ "toplevel", 0, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
{ "toggle", 1, toplevel_cmd, XT_WL_TOGGLE | XT_WL_TOPLEVEL | XT_WL_RELOAD, 0 },
/* cookie jar */
{ "cookiejar", 0, xtp_page_cl, 0, 0 },
/* cert command */
{ "cert", 0, cert_cmd, XT_SHOW, 0 },
{ "save", 1, cert_cmd, XT_SAVE, 0 },
{ "show", 1, cert_cmd, XT_SHOW, 0 },
{ "ca", 0, ca_cmd, 0, 0 },
{ "downloadmgr", 0, xtp_page_dl, 0, 0 },
{ "dl", 0, xtp_page_dl, 0, 0 },
{ "h", 0, xtp_page_hl, 0, 0 },
{ "history", 0, xtp_page_hl, 0, 0 },
{ "home", 0, go_home, 0, 0 },
{ "restart", 0, restart, 0, 0 },
{ "urlhide", 0, urlaction, XT_URL_HIDE, 0 },
{ "urlshow", 0, urlaction, XT_URL_SHOW, 0 },
{ "statustoggle", 0, statustoggle, 0, 0 },
{ "run_script", 0, run_page_script, 0, XT_USERARG },
{ "print", 0, print_page, 0, 0 },
/* tabs */
{ "focusin", 0, resizetab, XT_ZOOM_IN, 0 },
{ "focusout", 0, resizetab, XT_ZOOM_OUT, 0 },
{ "focusreset", 0, resizetab, XT_ZOOM_NORMAL, 0 },
{ "q", 0, tabaction, XT_TAB_DELQUIT, 0 },
{ "quit", 0, tabaction, XT_TAB_DELQUIT, 0 },
{ "open", 0, tabaction, XT_TAB_OPEN, XT_URLARG },
{ "tabclose", 0, tabaction, XT_TAB_DELETE, XT_PREFIX | XT_INTARG},
{ "tabedit", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
{ "tabfirst", 0, movetab, XT_TAB_FIRST, 0 },
{ "tabhide", 0, tabaction, XT_TAB_HIDE, 0 },
{ "tablast", 0, movetab, XT_TAB_LAST, 0 },
{ "tabnew", 0, tabaction, XT_TAB_NEW, XT_PREFIX | XT_URLARG },
{ "tabnext", 0, movetab, XT_TAB_NEXT, XT_PREFIX | XT_INTARG},
{ "tabnextstyle", 0, tabaction, XT_TAB_NEXTSTYLE, 0 },
{ "tabonly", 0, tabaction, XT_TAB_ONLY, 0 },
{ "tabprevious", 0, movetab, XT_TAB_PREV, XT_PREFIX | XT_INTARG},
{ "tabrewind", 0, movetab, XT_TAB_FIRST, 0 },
{ "tabshow", 0, tabaction, XT_TAB_SHOW, 0 },
{ "tabs", 0, buffers, 0, 0 },
{ "tabundoclose", 0, tabaction, XT_TAB_UNDO_CLOSE, 0 },
{ "buffers", 0, buffers, 0, 0 },
{ "ls", 0, buffers, 0, 0 },
{ "encoding", 0, set_encoding, 0, XT_USERARG },
{ "loadimages", 0, tabaction, XT_TAB_LOAD_IMAGES, 0 },
/* settings */
{ "set", 0, set, 0, XT_SETARG },
{ "runtime", 0, xtp_page_rt, 0, 0 },
{ "fullscreen", 0, fullscreen, 0, 0 },
{ "f", 0, fullscreen, 0, 0 },
/* sessions */
{ "session", 0, session_cmd, XT_SHOW, 0 },
{ "delete", 1, session_cmd, XT_DELETE, XT_SESSARG },
{ "open", 1, session_cmd, XT_OPEN, XT_SESSARG },
{ "save", 1, session_cmd, XT_SAVE, XT_USERARG },
{ "show", 1, session_cmd, XT_SHOW, 0 },
/* external javascript */
{ "script", 0, script_cmd, XT_EJS_SHOW, XT_USERARG },
/* inspector */
{ "inspector", 0, inspector_cmd, XT_INS_SHOW, 0 },
{ "show", 1, inspector_cmd, XT_INS_SHOW, 0 },
{ "hide", 1, inspector_cmd, XT_INS_HIDE, 0 },
/* proxy */
{ "proxy", 0, proxy_cmd, XT_PRXY_SHOW, 0 },
{ "show", 1, proxy_cmd, XT_PRXY_SHOW, 0 },
{ "toggle", 1, proxy_cmd, XT_PRXY_TOGGLE, 0 },
/* url mod */
{ "urlmod", 0, urlmod_cmd, XT_URL, 0 },
{ "plus", 1, urlmod_cmd, XT_URL_PLUS, 0 },
{ "min", 1, urlmod_cmd, XT_URL_MIN, 0 },
};
struct {
int index;
int len;
gchar *list[256];
} cmd_status = {-1, 0};
gboolean
wv_release_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
{
if (e->type == GDK_BUTTON_RELEASE && e->button == 1)
btn_down = 0;
return (FALSE);
}
gboolean
wv_button_cb(GtkWidget *btn, GdkEventButton *e, struct tab *t)
{
struct karg a;
WebKitHitTestResult *hit_test_result;
guint context;
hit_test_result = webkit_web_view_get_hit_test_result(t->wv, e);
g_object_get(hit_test_result, "context", &context, NULL);
g_object_unref(G_OBJECT(hit_test_result));
hide_oops(t);
hide_buffers(t);
if (context & WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE)
t->mode = XT_MODE_INSERT;
else
t->mode = XT_MODE_COMMAND;
if (e->type == GDK_BUTTON_PRESS && e->button == 1)
btn_down = 1;
else if (e->type == GDK_BUTTON_PRESS && e->button == 8 /* btn 4 */) {
/* go backward */
a.i = XT_NAV_BACK;
navaction(t, &a);
return (TRUE);
} else if (e->type == GDK_BUTTON_PRESS && e->button == 9 /* btn 5 */) {
/* go forward */
a.i = XT_NAV_FORWARD;
navaction(t, &a);
return (TRUE);
}
return (FALSE);
}
void
tab_close_cb(GtkWidget *btn, struct tab *t)
{
DNPRINTF(XT_D_TAB, "tab_close_cb: tab %d\n", t->tab_id);
delete_tab(t);
}
int
parse_custom_uri(struct tab *t, const char *uri)
{
struct custom_uri *u;
int handled = 0;
char *sv[3];
TAILQ_FOREACH(u, &cul, entry) {
if (strncmp(uri, u->uri, strlen(u->uri)))
continue;
handled = 1;
sv[0] = u->cmd;
sv[1] = (char *)uri;
sv[2] = NULL;
if (!g_spawn_async(NULL, sv, NULL, G_SPAWN_SEARCH_PATH, NULL,
NULL, NULL, NULL))
show_oops(t, "%s: could not spawn process", __func__);
}
return (handled);
}
void
activate_uri_entry_cb(GtkWidget* entry, struct tab *t)
{
const gchar *uri = gtk_entry_get_text(GTK_ENTRY(entry));
DNPRINTF(XT_D_URL, "activate_uri_entry_cb: %s\n", uri);
if (t == NULL) {
show_oops(NULL, "activate_uri_entry_cb invalid parameters");
return;
}
if (uri == NULL) {
show_oops(t, "activate_uri_entry_cb no uri");
return;
}
uri += strspn(uri, "\t ");
if (parse_custom_uri(t, uri))
return;
/* otherwise continue to load page normally */
load_uri(t, (gchar *)uri);
focus_webview(t);
}
void
activate_search_entry_cb(GtkWidget* entry, struct tab *t)
{
const gchar *search = gtk_entry_get_text(GTK_ENTRY(entry));
char *newuri = NULL;
gchar *enc_search;
char **sv;
DNPRINTF(XT_D_URL, "activate_search_entry_cb: %s\n", search);
if (t == NULL) {
show_oops(NULL, "activate_search_entry_cb invalid parameters");
return;
}
if (search_string == NULL || strlen(search_string) == 0) {
show_oops(t, "no search_string");
return;
}
set_normal_tab_meaning(t);
enc_search = soup_uri_encode(search, XT_RESERVED_CHARS);
sv = g_strsplit(search_string, "%s", 2);
newuri = g_strjoinv(enc_search, sv);
g_free(enc_search);
g_strfreev(sv);
marks_clear(t);
load_uri(t, newuri);
focus_webview(t);
if (newuri)
g_free(newuri);
}
void
check_and_set_cookie(const gchar *uri, struct tab *t)
{
struct wl_entry *w = NULL;
int es = 0;
if (uri == NULL || t == NULL)
return;
if ((w = wl_find_uri(uri, &c_wl)) == NULL)
es = 0;
else
es = 1;
DNPRINTF(XT_D_COOKIE, "check_and_set_cookie: %s %s\n",
es ? "enable" : "disable", uri);
g_object_set(G_OBJECT(t->settings),
"enable-html5-local-storage", es, (char *)NULL);
webkit_web_view_set_settings(t->wv, t->settings);
}
void
check_and_set_js(const gchar *uri, struct tab *t)
{
struct wl_entry *w = NULL;
int es = 0;
if (uri == NULL || t == NULL)
return;
if ((w = wl_find_uri(uri, &js_wl)) == NULL)
es = 0;
else
es = 1;
DNPRINTF(XT_D_JS, "check_and_set_js: %s %s\n",
es ? "enable" : "disable", uri);
g_object_set(G_OBJECT(t->settings),
"enable-scripts", es, (char *)NULL);
webkit_web_view_set_settings(t->wv, t->settings);
button_set_icon_name(t->js_toggle,
es ? "media-playback-start" : "media-playback-pause");
}
void
check_and_set_pl(const gchar *uri, struct tab *t)
{
struct wl_entry *w = NULL;
int es = 0;
if (uri == NULL || t == NULL)
return;
if ((w = wl_find_uri(uri, &pl_wl)) == NULL)
es = 0;
else
es = 1;
DNPRINTF(XT_D_JS, "check_and_set_pl: %s %s\n",
es ? "enable" : "disable", uri);
g_object_set(G_OBJECT(t->settings),
"enable-plugins", es, (char *)NULL);
webkit_web_view_set_settings(t->wv, t->settings);
}
#if GTK_CHECK_VERSION(3, 0, 0)
/* A lot of this can be removed when gtk2 is dropped on the floor */
char *
get_css_name(const char *col_str)
{
char *name = NULL;
if (!strcmp(col_str, XT_COLOR_WHITE))
name = g_strdup(XT_CSS_NORMAL);
else if (!strcmp(col_str, XT_COLOR_RED))
name = g_strdup(XT_CSS_RED);
else if (!strcmp(col_str, XT_COLOR_YELLOW))
name = g_strdup(XT_CSS_YELLOW);
else if (!strcmp(col_str, XT_COLOR_GREEN))
name = g_strdup(XT_CSS_GREEN);
else if (!strcmp(col_str, XT_COLOR_BLUE))
name = g_strdup(XT_CSS_BLUE);
return (name);
}
#endif
void
show_ca_status(struct tab *t, const char *uri)
{
char domain[8182], file[PATH_MAX];
SoupMessage *msg = NULL;
GTlsCertificate *cert = NULL;
GTlsCertificateFlags flags = 0;
gchar *col_str = XT_COLOR_RED;
char *cut_uri;
char *chain;
char *s;
#if GTK_CHECK_VERSION(3, 0, 0)
char *name;
#else
GdkColor color;
gchar *text, *base;
#endif
enum cert_trust trust;
int nocolor = 0;
int i;
DNPRINTF(XT_D_URL, "show_ca_status: %d %s %s\n",
ssl_strict_certs, ssl_ca_file, uri);
if (t == NULL)
return;
if (uri == NULL || g_str_has_prefix(uri, "http://") ||
!g_str_has_prefix(uri, "https://"))
return;
/*
* Cut the uri to get the certs off the homepage. We can't use the
* full URI here since it may include arguments and we don't want to make
* these requests multiple times.
*/
cut_uri = g_strdup(uri);
s = cut_uri;
for (i = 0; i < 3; ++i)
s = strchr(&(s[1]), '/');
s[1] = '\0';
msg = soup_message_new("HEAD", cut_uri);
g_free(cut_uri);
if (msg == NULL)
return;
soup_message_set_flags(msg, SOUP_MESSAGE_NO_REDIRECT);
soup_session_send_message(session, msg);
if (msg->status_code == SOUP_STATUS_SSL_FAILED ||
msg->status_code == SOUP_STATUS_TLS_FAILED) {
DNPRINTF(XT_D_URL, "%s: status not ok: %d\n", uri,
msg->status_code);
goto done;
}
if (!soup_message_get_https_status(msg, &cert, &flags)) {
DNPRINTF(XT_D_URL, "%s: invalid response\n", uri);
goto done;
}
if (!G_IS_TLS_CERTIFICATE(cert)) {
DNPRINTF(XT_D_URL, "%s: no cert\n", uri);
goto done;
}
if (flags == 0)
col_str = XT_COLOR_GREEN;
else
col_str = XT_COLOR_YELLOW;
strlcpy(domain, uri + strlen("https://"), sizeof domain);
for (i = 0; i < strlen(domain); i++)
if (domain[i] == '/') {
domain[i] = '\0';
break;
}
snprintf(file, sizeof file, "%s" PS "%s", certs_dir, domain);
chain = g_strdup("");
if ((trust = check_local_certs(file, cert, &chain)) == CERT_LOCAL)
col_str = XT_COLOR_BLUE;
if (t->pem)
g_free(t->pem);
t->pem = chain;
snprintf(file, sizeof file, "%s" PS "%s", certs_cache_dir, domain);
if (warn_cert_changes) {
if (check_cert_changes(t, cert, file, uri))
nocolor = 1;
}
done:
g_object_unref(msg);
if (!strcmp(col_str, XT_COLOR_WHITE) || nocolor) {
#if GTK_CHECK_VERSION(3, 0, 0)
gtk_widget_set_name(t->uri_entry, XT_CSS_NORMAL);
statusbar_modify_attr(t, XT_CSS_NORMAL);
#else
text = gdk_color_to_string(
&t->default_style->text[GTK_STATE_NORMAL]);
base = gdk_color_to_string(
&t->default_style->base[GTK_STATE_NORMAL]);
gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL,
&t->default_style->base[GTK_STATE_NORMAL]);
statusbar_modify_attr(t, text, base);
g_free(text);
g_free(base);
#endif
} else {
#if GTK_CHECK_VERSION(3, 0, 0)
name = get_css_name(col_str);
gtk_widget_set_name(t->uri_entry, name);
statusbar_modify_attr(t, name);
g_free(name);
#else
gdk_color_parse(col_str, &color);
gtk_widget_modify_base(t->uri_entry, GTK_STATE_NORMAL, &color);
statusbar_modify_attr(t, XT_COLOR_BLACK, col_str);
#endif
}
}
void
free_favicon(struct tab *t)
{
DNPRINTF(XT_D_DOWNLOAD, "%s: down %p req %p\n",
__func__, t->icon_download, t->icon_request);
if (t->icon_request)
g_object_unref(t->icon_request);
if (t->icon_dest_uri)
g_free(t->icon_dest_uri);
t->icon_request = NULL;
t->icon_dest_uri = NULL;
}
void
xt_icon_from_name(struct tab *t, gchar *name)
{
if (!enable_favicon_entry)
return;
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->uri_entry),
GTK_ENTRY_ICON_PRIMARY, "text-html");
if (show_url == 0)
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, "text-html");
else
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, NULL);
}
void
xt_icon_from_pixbuf(struct tab *t, GdkPixbuf *pb)
{
GdkPixbuf *pb_scaled;
if (gdk_pixbuf_get_width(pb) > 16 || gdk_pixbuf_get_height(pb) > 16)
pb_scaled = gdk_pixbuf_scale_simple(pb, 16, 16,
GDK_INTERP_BILINEAR);
else
pb_scaled = pb;
if (enable_favicon_entry) {
/* Classic tabs. */
gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->uri_entry),
GTK_ENTRY_ICON_PRIMARY, pb_scaled);
/* Minimal tabs. */
if (show_url == 0) {
gtk_entry_set_icon_from_pixbuf(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, pb_scaled);
} else
gtk_entry_set_icon_from_icon_name(GTK_ENTRY(t->sbe.uri),
GTK_ENTRY_ICON_PRIMARY, NULL);
}
/* XXX: Only supports the minimal tabs atm. */
if (enable_favicon_tabs)
gtk_image_set_from_pixbuf(GTK_IMAGE(t->tab_elems.favicon),
pb_scaled);
if (pb_scaled != pb)
g_object_unref(pb_scaled);
}
void
xt_icon_from_file(struct tab *t, char *uri)
{
GdkPixbuf *pb;
char *file;
if (g_str_has_prefix(uri, "file://"))
file = g_filename_from_uri(uri, NULL, NULL);
else
file = g_strdup(uri);
if (file == NULL)
return;
pb = gdk_pixbuf_new_from_file(file, NULL);
if (pb) {
xt_icon_from_pixbuf(t, pb);
g_object_unref(pb);
} else
xt_icon_from_name(t, "text-html");
g_free(file);
}
gboolean
is_valid_icon(char *file)
{
gboolean valid = 0;
const char *mime_type;
GFileInfo *fi;
GFile *gf;
gf = g_file_new_for_path(file);
fi = g_file_query_info(gf, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE, 0,
NULL, NULL);
mime_type = g_file_info_get_content_type(fi);
valid = g_strcmp0(mime_type, "image/x-ico") == 0 ||
g_strcmp0(mime_type, "image/vnd.microsoft.icon") == 0 ||
g_strcmp0(mime_type, "image/png") == 0 ||
g_strcmp0(mime_type, "image/gif") == 0 ||
g_strcmp0(mime_type, "application/octet-stream") == 0;
g_object_unref(fi);
g_object_unref(gf);
return (valid);
}
void
set_favicon_from_file(struct tab *t, char *uri)
{
struct stat sb;
char *file;
if (t == NULL || uri == NULL)
return;
if (g_str_has_prefix(uri, "file://"))
file = g_filename_from_uri(uri, NULL, NULL);
else
file = g_strdup(uri);
if (file == NULL)