"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("&", sv);
1009 g_strfreev(sv);
1010 sp = s;
1011 sv = g_strsplit(val, "<", -1);
1012 s = g_strjoinv("<", 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