1 /**
2  * vimb - a webkit based vim like browser.
3  *
4  * Copyright (C) 2012-2018 Daniel Carl
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see http://www.gnu.org/licenses/.
18  */
19 
20 #include <gdk/gdkx.h>
21 #include <gtk/gtk.h>
22 #include <gtk/gtkx.h>
23 #include <libsoup/soup.h>
24 #include <limits.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29 #include <webkit2/webkit2.h>
30 
31 #include "../version.h"
32 #include "ascii.h"
33 #include "command.h"
34 #include "completion.h"
35 #include "config.h"
36 #include "ex.h"
37 #include "ext-proxy.h"
38 #include "handler.h"
39 #include "history.h"
40 #include "input.h"
41 #include "js.h"
42 #include "main.h"
43 #include "map.h"
44 #include "normal.h"
45 #include "setting.h"
46 #include "shortcut.h"
47 #include "util.h"
48 #include "autocmd.h"
49 #include "file-storage.h"
50 
51 static void client_destroy(Client *c);
52 static Client *client_new(WebKitWebView *webview);
53 static void client_show(WebKitWebView *webview, Client *c);
54 static GtkWidget *create_window(Client *c);
55 static gboolean input_clear(Client *c);
56 static void input_print(Client *c, MessageType type, gboolean hide,
57         const char *message);
58 static gboolean is_plausible_uri(const char *path);
59 static void marks_clear(Client *c);
60 static void mode_free(Mode *mode);
61 static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data);
62 static void on_webctx_download_started(WebKitWebContext *webctx,
63         WebKitDownload *download, Client *c);
64 static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data);
65 static gboolean on_webdownload_decide_destination(WebKitDownload *download,
66         gchar *suggested_filename, Client *c);
67 static void on_webdownload_response_received(WebKitDownload *download,
68         GParamSpec *ps, Client *c);
69 static void spawn_download_command(Client *c, WebKitURIResponse *response);
70 static void on_webdownload_failed(WebKitDownload *download,
71         GError *error, Client *c);
72 static void on_webdownload_finished(WebKitDownload *download, Client *c);
73 static void on_webdownload_received_data(WebKitDownload *download,
74         guint64 data_length, Client *c);
75 static void on_webview_close(WebKitWebView *webview, Client *c);
76 static WebKitWebView *on_webview_create(WebKitWebView *webview,
77         WebKitNavigationAction *navact, Client *c);
78 static gboolean on_webview_decide_policy(WebKitWebView *webview,
79         WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c);
80 static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec);
81 static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec);
82 static void decide_response(Client *c, WebKitPolicyDecision *dec);
83 static void on_webview_load_changed(WebKitWebView *webview,
84         WebKitLoadEvent event, Client *c);
85 static void on_webview_mouse_target_changed(WebKitWebView *webview,
86         WebKitHitTestResult *result, guint modifiers, Client *c);
87 static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
88         GParamSpec *spec, Client *c);
89 static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec,
90         Client *c);
91 static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec,
92         Client *c);
93 static void on_webview_ready_to_show(WebKitWebView *webview, Client *c);
94 static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c);
95 static gboolean on_webview_authenticate(WebKitWebView *webview,
96         WebKitAuthenticationRequest *request, Client *c);
97 static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c);
98 static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c);
99 static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c);
100 static void on_window_destroy(GtkWidget *window, Client *c);
101 static gboolean quit(Client *c);
102 static void read_from_stdin(Client *c);
103 static void register_cleanup(Client *c);
104 static void update_title(Client *c);
105 static void update_urlbar(Client *c);
106 static void set_statusbar_style(Client *c, StatusType type);
107 static void set_title(Client *c, const char *title);
108 static void spawn_new_instance(const char *uri);
109 #ifdef FREE_ON_QUIT
110 static void vimb_cleanup(void);
111 #endif
112 static void vimb_setup(void);
113 static WebKitWebView *webview_new(Client *c, WebKitWebView *webview);
114 static void on_counted_matches(WebKitFindController *finder, guint count, Client *c);
115 static gboolean on_permission_request(WebKitWebView *webview,
116         WebKitPermissionRequest *request, Client *c);
117 static void on_script_message_focus(WebKitUserContentManager *manager,
118         WebKitJavascriptResult *res, gpointer data);
119 static gboolean profileOptionArgFunc(const gchar *option_name,
120         const gchar *value, gpointer data, GError **error);
121 static gboolean autocmdOptionArgFunc(const gchar *option_name,
122         const gchar *value, gpointer data, GError **error);
123 
124 struct Vimb vb;
125 
126 /**
127  * Set the destination for a download according to suggested file name and
128  * possible given path.
129  */
vb_download_set_destination(Client * c,WebKitDownload * download,char * suggested_filename,const char * path)130 gboolean vb_download_set_destination(Client *c, WebKitDownload *download,
131     char *suggested_filename, const char *path)
132 {
133     char *download_path, *dir, *file, *uri, *basename = NULL,
134          *decoded_uri = NULL;
135     const char *download_uri;
136     download_path = GET_CHAR(c, "download-path");
137 
138     if (!suggested_filename || !*suggested_filename) {
139         /* Try to find a matching name if there is no suggested filename. */
140         download_uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
141         decoded_uri  = soup_uri_decode(download_uri);
142         basename     = g_filename_display_basename(decoded_uri);
143         g_free(decoded_uri);
144 
145         suggested_filename = basename;
146     }
147 
148     /* Prepare the path to save the download. */
149     if (path && *path) {
150         file = util_build_path(c->state, path, download_path);
151 
152         /* if file is an directory append a file name */
153         if (g_file_test(file, (G_FILE_TEST_IS_DIR))) {
154             dir  = file;
155             file = g_build_filename(dir, suggested_filename, NULL);
156             g_free(dir);
157         }
158     } else {
159         file = util_build_path(c->state, suggested_filename, download_path);
160     }
161 
162     g_free(basename);
163 
164     if (!file) {
165         return FALSE;
166     }
167 
168     /* If the filepath exists already insert numerical suffix before file
169      * extension. */
170     if (g_file_test(file, G_FILE_TEST_EXISTS)) {
171         const char *dot_pos;
172         char *num = NULL;
173         GString *tmp;
174         gssize suffix;
175         int i = 1;
176 
177         /* position on .tar. (special case, extension with two dots),
178          * position on last dot (if any) otherwise */
179         if (!(dot_pos = strstr(file, ".tar."))) {
180             dot_pos = strrchr(file, '.');
181         }
182 
183         /* the position to insert the suffix at */
184         if (dot_pos) {
185             suffix = dot_pos - file;
186         } else {
187             suffix = strlen(file);
188         }
189 
190         tmp = g_string_new(NULL);
191 
192         /* Construct a new complete download filepath with suffix before the
193          * file extension. */
194         do {
195             num = g_strdup_printf("_%d", i++);
196             g_string_assign(tmp, file);
197             g_string_insert(tmp, suffix, num);
198             g_free(num);
199         } while (g_file_test(tmp->str, G_FILE_TEST_EXISTS));
200 
201         file = g_strdup(tmp->str);
202         g_string_free(tmp, TRUE);
203     }
204 
205     /* Build URI from filepath. */
206     uri = g_filename_to_uri(file, NULL, NULL);
207     g_free(file);
208 
209     /* configure download */
210     g_assert(uri);
211     webkit_download_set_allow_overwrite(download, FALSE);
212     webkit_download_set_destination(download, uri);
213     g_free(uri);
214 
215     return TRUE;
216 }
217 
218 /**
219  * Write text to the inpubox if this isn't focused.
220  */
vb_echo(Client * c,MessageType type,gboolean hide,const char * error,...)221 void vb_echo(Client *c, MessageType type, gboolean hide, const char *error, ...)
222 {
223     char *buffer;
224     va_list args;
225     /* Don't write to input box in case this is focused, might be the user is
226      * typing in it. */
227     if (gtk_widget_is_focus(GTK_WIDGET(c->input))) {
228         return;
229     }
230 
231     va_start(args, error);
232     buffer = g_strdup_vprintf(error, args);
233     va_end(args);
234 
235     input_print(c, type, hide, buffer);
236     g_free(buffer);
237 }
238 
239 /**
240  * Write text to the inpubox independent if this is focused or not.
241  * Note that this could disturb the user during typing into inputbox.
242  */
vb_echo_force(Client * c,MessageType type,gboolean hide,const char * error,...)243 void vb_echo_force(Client *c, MessageType type, gboolean hide, const char *error, ...)
244 {
245     char *buffer;
246     va_list args;
247 
248     va_start(args, error);
249     buffer = g_strdup_vprintf(error, args);
250     va_end(args);
251 
252     input_print(c, type, hide, buffer);
253     g_free(buffer);
254 }
255 
256 /**
257  * Enter into the new given mode and leave possible active current mode.
258  */
vb_enter(Client * c,char id)259 void vb_enter(Client *c, char id)
260 {
261     Mode *new = g_hash_table_lookup(vb.modes, GINT_TO_POINTER(id));
262 
263     g_return_if_fail(new != NULL);
264 
265     if (c->mode) {
266         /* don't do anything if the mode isn't a new one */
267         if (c->mode == new) {
268             return;
269         }
270 
271         /* if there is a active mode, leave this first */
272         if (c->mode->leave) {
273             c->mode->leave(c);
274         }
275     }
276 
277     /* reset the flags of the new entered mode */
278     new->flags = 0;
279 
280     /* set the new mode so that it is available also in enter function */
281     c->mode = new;
282     /* call enter only if the new mode isn't the current mode */
283     if (new->enter) {
284         new->enter(c);
285     }
286 
287 #ifndef TESTLIB
288     vb_statusbar_update(c);
289 #endif
290 }
291 
292 /**
293  * Set the prompt chars and switch to new mode.
294  *
295  * @id:           Mode id.
296  * @prompt:       Prompt string to set as current prompt.
297  * @print_prompt: Indicates if the new set prompt should be put into inputbox
298  *                after switching the mode.
299  */
vb_enter_prompt(Client * c,char id,const char * prompt,gboolean print_prompt)300 void vb_enter_prompt(Client *c, char id, const char *prompt, gboolean print_prompt)
301 {
302     /* set the prompt to be accessible in vb_enter */
303     strncpy(c->state.prompt, prompt, PROMPT_SIZE - 1);
304     c->state.prompt[PROMPT_SIZE - 1] = '\0';
305 
306     vb_enter(c, id);
307 
308     if (print_prompt) {
309         /* set it after the mode was entered so that the modes input change
310          * event listener could grep the new prompt */
311         vb_echo_force(c, MSG_NORMAL, FALSE, c->state.prompt);
312     }
313 }
314 
315 /**
316  * Returns the client for given page id.
317  */
vb_get_client_for_page_id(guint64 pageid)318 Client *vb_get_client_for_page_id(guint64 pageid)
319 {
320     Client *c;
321     /* Search for the client with the same page id. */
322     for (c = vb.clients; c && c->page_id != pageid; c = c->next);
323 
324     if (c) {
325         return c;
326     }
327     return NULL;
328 }
329 
330 /**
331  * Retrieves the content of the command line.
332  * Returned string must be freed with g_free.
333  */
vb_input_get_text(Client * c)334 char *vb_input_get_text(Client *c)
335 {
336     GtkTextIter start, end;
337 
338     gtk_text_buffer_get_bounds(c->buffer, &start, &end);
339     return gtk_text_buffer_get_text(c->buffer, &start, &end, FALSE);
340 }
341 
342 /**
343  * Writes given text into the command line.
344  */
vb_input_set_text(Client * c,const char * text)345 void vb_input_set_text(Client *c, const char *text)
346 {
347     gtk_text_buffer_set_text(c->buffer, text, -1);
348     if (c->config.input_autohide) {
349         gtk_widget_set_visible(GTK_WIDGET(c->input), *text != '\0');
350     }
351 }
352 
353 /**
354  * Set the style of the inputbox according to current input type (normal or
355  * error).
356  */
vb_input_update_style(Client * c)357 void vb_input_update_style(Client *c)
358 {
359     MessageType type = c->state.input_type;
360 
361     if (type == MSG_ERROR) {
362         gtk_style_context_add_class(gtk_widget_get_style_context(c->input), "error");
363     } else {
364         gtk_style_context_remove_class(gtk_widget_get_style_context(c->input), "error");
365     }
366 }
367 
368 /**
369  * Load the a uri given in Arg. This function handles also shortcuts and local
370  * file paths.
371  *
372  * If arg.i = TARGET_CURRENT, the url is opened into the current webview.
373  * TARGET_RELATED causes the generation of a new window within the current
374  * instance of vimb with a own, but related webview. And TARGET_NEW spawns a
375  * new instance of vimb with the given uri.
376  */
vb_load_uri(Client * c,const Arg * arg)377 gboolean vb_load_uri(Client *c, const Arg *arg)
378 {
379     char *uri = NULL, *rp, *path = NULL;
380     struct stat st;
381 
382     if (arg->s) {
383         path = g_strstrip(arg->s);
384     }
385     if (!path || !*path) {
386         path = GET_CHAR(c, "home-page");
387     }
388 
389     /* If path contains :// but no space we open it direct. This is required
390      * to use :// also with shortcuts */
391     if ((strstr(path, "://") && !strchr(path, ' ')) || !strncmp(path, "about:", 6)) {
392         uri = g_strdup(path);
393     } else if (stat(path, &st) == 0) {
394         /* check if the path is a file path */
395         rp  = realpath(path, NULL);
396         uri = g_strconcat("file://", rp, NULL);
397         free(rp);
398     } else if (!is_plausible_uri(path)) {
399         /* use a shortcut if path contains spaces or doesn't contain typical
400          * tokens ('.', [:] for IPv6 addresses, 'localhost') */
401         uri = shortcut_get_uri(c->config.shortcuts, path);
402     }
403 
404     if (!uri) {
405         uri = g_strconcat("http://", path, NULL);
406     }
407 
408     if (arg->i == TARGET_CURRENT) {
409         /* Load the uri into the browser instance. */
410         webkit_web_view_load_uri(c->webview, uri);
411         set_title(c, uri);
412     } else if (arg->i == TARGET_NEW) {
413         spawn_new_instance(uri);
414     } else { /* TARGET_RELATED */
415         Client *newclient = client_new(c->webview);
416         /* Load the uri into the new client. */
417         webkit_web_view_load_uri(newclient->webview, uri);
418         set_title(c, uri);
419     }
420     g_free(uri);
421 
422     return TRUE;
423 }
424 
425 /**
426  * Creates and add a new mode with given callback functions.
427  */
vb_mode_add(char id,ModeTransitionFunc enter,ModeTransitionFunc leave,ModeKeyFunc keypress,ModeInputChangedFunc input_changed)428 void vb_mode_add(char id, ModeTransitionFunc enter, ModeTransitionFunc leave,
429     ModeKeyFunc keypress, ModeInputChangedFunc input_changed)
430 {
431     Mode *new = g_slice_new(Mode);
432     new->id            = id;
433     new->enter         = enter;
434     new->leave         = leave;
435     new->keypress      = keypress;
436     new->input_changed = input_changed;
437     new->flags         = 0;
438 
439     /* Initialize the hashmap if this was not done before */
440     if (!vb.modes) {
441         vb.modes = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, (GDestroyNotify)mode_free);
442     }
443     g_hash_table_insert(vb.modes, GINT_TO_POINTER(id), new);
444 }
445 
vb_mode_handle_key(Client * c,int key)446 VbResult vb_mode_handle_key(Client *c, int key)
447 {
448     VbResult res;
449 
450     if (c->state.ctrlv) {
451         c->state.processed_key = FALSE;
452         c->state.ctrlv         = FALSE;
453 
454         return RESULT_COMPLETE;
455     }
456     if (c->mode->id != 'p' && key == CTRL('V')) {
457         c->mode->flags |= FLAG_NOMAP;
458         c->state.ctrlv  = TRUE;
459 
460         return RESULT_MORE;
461     }
462 
463     if (c->mode && c->mode->keypress) {
464 #ifdef DEBUGDISABLED
465         int flags = c->mode->flags;
466         int id    = c->mode->id;
467         res = c->mode->keypress(c, key);
468         if (c->mode) {
469             PRINT_DEBUG(
470                 "%c[%d]: %#.2x '%c' -> %c[%d]",
471                 id - ' ', flags, key, (key >= 0x20 && key <= 0x7e) ? key : ' ',
472                 c->mode->id - ' ', c->mode->flags
473             );
474         }
475 #else
476         res = c->mode->keypress(c, key);
477 #endif
478         return res;
479     }
480     return RESULT_ERROR;
481 }
482 
483 /**
484  * Change the label for the current mode in inputbox or on the left of
485  * statusbar if inputbox is in autohide mode.
486  */
vb_modelabel_update(Client * c,const char * label)487 void vb_modelabel_update(Client *c, const char *label)
488 {
489     if (c->config.input_autohide) {
490         /* if the inputbox is potentially not shown write mode into statusbar */
491         gtk_label_set_text(GTK_LABEL(c->statusbar.mode), label);
492     } else {
493         vb_echo(c, MSG_NORMAL, FALSE, "%s", label);
494     }
495 }
496 
497 /**
498  * Close the given client instances window.
499  */
vb_quit(Client * c,gboolean force)500 gboolean vb_quit(Client *c, gboolean force)
501 {
502     /* if not forced quit - don't quit if there are still running downloads */
503     if (!force && c->state.downloads) {
504         vb_echo_force(c, MSG_ERROR, TRUE, "Can't quit: there are running downloads. Use :q! to force quit");
505         return FALSE;
506     }
507 
508     /* Don't run the quit synchronously, because this could lead to access of
509      * no more existing widget where some command response is written. */
510     g_idle_add((GSourceFunc)quit, c);
511 
512     return TRUE;
513 }
514 
515 /**
516  * Adds content to a named register.
517  */
vb_register_add(Client * c,char buf,const char * value)518 void vb_register_add(Client *c, char buf, const char *value)
519 {
520     char *mark;
521     int idx;
522 
523     if (!c->state.enable_register || !buf) {
524         return;
525     }
526 
527     /* make sure the mark is a valid mark char */
528     if ((mark = strchr(REG_CHARS, buf))) {
529         /* get the index of the mark char */
530         idx = mark - REG_CHARS;
531 
532         OVERWRITE_STRING(c->state.reg[idx], value);
533     }
534 }
535 
536 /**
537  * Lookup register entry by it's name.
538  */
vb_register_get(Client * c,char buf)539 const char *vb_register_get(Client *c, char buf)
540 {
541     char *mark;
542     int idx;
543 
544     /* make sure the mark is a valid mark char */
545     if ((mark = strchr(REG_CHARS, buf))) {
546         /* get the index of the mark char */
547         idx = mark - REG_CHARS;
548 
549         return c->state.reg[idx];
550     }
551 
552     return NULL;
553 }
554 
statusbar_update_downloads(Client * c,GString * status)555 static void statusbar_update_downloads(Client *c, GString *status)
556 {
557     GList *list;
558     guint list_length, remaining_max = 0;
559     gdouble progress, elapsed, total, remaining;
560     WebKitDownload *download;
561 
562     g_assert(c);
563     g_assert(status);
564 
565     if (c->state.downloads) {
566         list_length = g_list_length(c->state.downloads);
567         g_assert(list_length);
568 
569         /* get highest ETA value of all downloads based on each download's
570          * current progress fraction and time elapsed */
571         for (list = c->state.downloads; list != NULL; list = list->next) {
572             download = (WebKitDownload *)list->data;
573             g_assert(download);
574 
575             progress = webkit_download_get_estimated_progress(download);
576 
577             /* avoid dividing by zero */
578             if (progress == 0.0) {
579                 continue;
580             }
581 
582             elapsed = webkit_download_get_elapsed_time(download);
583             total = (1.0 / progress) * elapsed;
584             remaining = total - elapsed;
585 
586             remaining_max = MAX(remaining, remaining_max);
587         }
588 
589         g_string_append_printf(status, " %d %s (ETA %us)",
590                 list_length, list_length == 1? "dnld" : "dnlds", remaining_max);
591     }
592 }
593 
vb_statusbar_update(Client * c)594 void vb_statusbar_update(Client *c)
595 {
596     GString *status;
597 
598     if (!gtk_widget_get_visible(GTK_WIDGET(c->statusbar.box))) {
599         return;
600     }
601 
602     status = g_string_new("");
603 
604     /* show the number of matches search results */
605     if (c->state.search.matches) {
606         g_string_append_printf(status, " (%d)", c->state.search.matches);
607     }
608 
609     /* show load status of page or the downloads */
610     if (c->state.progress != 100) {
611 #ifdef FEATURE_WGET_PROGRESS_BAR
612         char bar[PROGRESS_BAR_LEN + 1];
613         int i, state;
614 
615         state = c->state.progress * PROGRESS_BAR_LEN / 100;
616         for (i = 0; i < state; i++) {
617             bar[i] = PROGRESS_BAR[0];
618         }
619         bar[i++] = PROGRESS_BAR[1];
620         for (; i < PROGRESS_BAR_LEN; i++) {
621             bar[i] = PROGRESS_BAR[2];
622         }
623         bar[i] = '\0';
624         g_string_append_printf(status, " [%s]", bar);
625 #else
626         g_string_append_printf(status, " [%i%%]", c->state.progress);
627 #endif
628     }
629 
630     statusbar_update_downloads(c, status);
631 
632     /* show the scroll status */
633     if (c->state.scroll_max == 0) {
634         g_string_append(status, " All");
635     } else if (c->state.scroll_percent == 0) {
636         g_string_append(status, " Top");
637     } else if (c->state.scroll_percent == 100) {
638         g_string_append(status, " Bot");
639     } else {
640         g_string_append_printf(status, " %d%%", c->state.scroll_percent);
641     }
642 
643     gtk_label_set_text(GTK_LABEL(c->statusbar.right), status->str);
644     g_string_free(status, TRUE);
645 }
646 
647 /**
648  * Show the given url on the left of statusbar.
649  */
vb_statusbar_show_hover_url(Client * c,VbLinkType type,const char * uri)650 void vb_statusbar_show_hover_url(Client *c, VbLinkType type, const char *uri)
651 {
652     char *sanitized_uri,
653          *msg;
654     const char *type_label;
655 
656     /* No uri given - show the current URI. */
657     if (!uri || !*uri) {
658         update_urlbar(c);
659         return;
660     }
661 
662     switch (type) {
663         case LINK_TYPE_LINK:
664             type_label = "Link: ";
665             break;
666         case LINK_TYPE_IMAGE:
667             type_label = "Image: ";
668             break;
669         default:
670             return;
671     }
672 
673     sanitized_uri = util_sanitize_uri(uri);
674     msg           = g_strconcat(type_label, uri, NULL);
675     gtk_label_set_text(GTK_LABEL(c->statusbar.left), msg);
676     g_free(msg);
677     g_free(sanitized_uri);
678 }
679 
680 /**
681  * Destroys given client and removed it from client queue. If no client is
682  * there in queue, quit the gtk main loop.
683  */
client_destroy(Client * c)684 static void client_destroy(Client *c)
685 {
686     Client *p;
687     webkit_web_view_stop_loading(c->webview);
688 
689     /* Write last URL into file for recreation.
690      * The URL is only stored if the closed-max-items is not 0 and the file
691      * exists. */
692     if (c->state.uri && vb.config.closed_max && vb.files[FILES_CLOSED]) {
693         util_file_prepend_line(vb.files[FILES_CLOSED], c->state.uri,
694                 vb.config.closed_max);
695     }
696 
697     gtk_widget_destroy(c->window);
698 
699     /* Look for the client in the list, if we searched through the list and
700      * didn't find it the client must be the first item. */
701     for (p = vb.clients; p && p->next != c; p = p->next);
702     if (p) {
703         p->next = c->next;
704     } else {
705         vb.clients = c->next;
706     }
707 
708     if (c->state.search.last_query) {
709         g_free(c->state.search.last_query);
710     }
711 
712     completion_cleanup(c);
713     map_cleanup(c);
714     register_cleanup(c);
715     setting_cleanup(c);
716 #ifdef FEATURE_AUTOCMD
717     autocmd_cleanup(c);
718 #endif
719     handler_free(c->handler);
720     shortcut_free(c->config.shortcuts);
721 
722     g_slice_free(Client, c);
723 
724     /* if there are no clients - quit the main loop */
725     if (!vb.clients) {
726         gtk_main_quit();
727     }
728 }
729 
730 /**
731  * Creates a new client instance with it's own window.
732  *
733  * @webview:    Related webview or NULL if a client with an independent
734  *              webview shoudl be created.
735  */
client_new(WebKitWebView * webview)736 static Client *client_new(WebKitWebView *webview)
737 {
738     Client *c;
739 
740     /* create the client */
741     /* Prepend the new client to the queue of clients. */
742     c          = g_slice_new0(Client);
743     c->next    = vb.clients;
744     vb.clients = c;
745 
746     c->state.progress = 100;
747     c->config.shortcuts = shortcut_new();
748 
749     completion_init(c);
750     map_init(c);
751     c->handler = handler_new();
752 #ifdef FEATURE_AUTOCMD
753     autocmd_init(c);
754 #endif
755 
756     /* webview */
757     c->webview   = webview_new(c, webview);
758     c->finder    = webkit_web_view_get_find_controller(c->webview);
759     g_signal_connect(c->finder, "counted-matches", G_CALLBACK(on_counted_matches), c);
760 
761     c->page_id   = webkit_web_view_get_page_id(c->webview);
762     c->inspector = webkit_web_view_get_inspector(c->webview);
763 
764     return c;
765 }
766 
client_show(WebKitWebView * webview,Client * c)767 static void client_show(WebKitWebView *webview, Client *c)
768 {
769     GtkWidget *box;
770     char *xid;
771 
772     c->window = create_window(c);
773 
774     /* statusbar */
775     c->statusbar.box   = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0));
776     c->statusbar.mode  = gtk_label_new(NULL);
777     c->statusbar.left  = gtk_label_new(NULL);
778     c->statusbar.right = gtk_label_new(NULL);
779     c->statusbar.cmd   = gtk_label_new(NULL);
780     gtk_widget_set_name(GTK_WIDGET(c->statusbar.box), "statusbar");
781     gtk_label_set_ellipsize(GTK_LABEL(c->statusbar.left), PANGO_ELLIPSIZE_MIDDLE);
782     gtk_widget_set_halign(c->statusbar.left, GTK_ALIGN_START);
783     gtk_widget_set_halign(c->statusbar.mode, GTK_ALIGN_START);
784 
785     gtk_box_pack_start(c->statusbar.box, c->statusbar.mode, FALSE, TRUE, 0);
786     gtk_box_pack_start(c->statusbar.box, c->statusbar.left, TRUE, TRUE, 2);
787     gtk_box_pack_start(c->statusbar.box, c->statusbar.cmd, FALSE, FALSE, 0);
788     gtk_box_pack_start(c->statusbar.box, c->statusbar.right, FALSE, FALSE, 2);
789 
790     /* inputbox */
791     c->input  = gtk_text_view_new();
792     gtk_widget_set_name(c->input, "input");
793     c->buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(c->input));
794     g_signal_connect(c->buffer, "changed", G_CALLBACK(on_textbuffer_changed), c);
795     /* Make sure the user can see the typed text. */
796     gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(c->input), GTK_WRAP_WORD_CHAR);
797 
798     /* pack the parts together */
799     box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
800     gtk_container_add(GTK_CONTAINER(c->window), box);
801     gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->webview), TRUE, TRUE, 0);
802     gtk_box_pack_start(GTK_BOX(box), GTK_WIDGET(c->statusbar.box), FALSE, FALSE, 0);
803     gtk_box_pack_end(GTK_BOX(box), GTK_WIDGET(c->input), FALSE, FALSE, 0);
804 
805     /* Set the default style for statusbar and inputbox. */
806     gtk_style_context_add_provider(gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box)),
807             GTK_STYLE_PROVIDER(vb.style_provider),
808             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
809     gtk_style_context_add_provider(gtk_widget_get_style_context(c->input),
810             GTK_STYLE_PROVIDER(vb.style_provider),
811             GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
812 
813     /* TODO separate initialization o setting from applying the values or
814      * allow to se the default values for different scopes. For now we can
815      * init the settings not in client_new because we need the access to some
816      * widget for some settings. */
817     setting_init(c);
818 
819     gtk_widget_show_all(c->window);
820     if (vb.embed) {
821         xid = g_strdup_printf("%d", (int)vb.embed);
822     } else {
823         xid = g_strdup_printf("%d", (int)GDK_WINDOW_XID(gtk_widget_get_window(c->window)));
824     }
825 
826     /* set the x window id to env */
827     g_setenv("VIMB_XID", xid, TRUE);
828     g_free(xid);
829 
830     /* start client in normal mode */
831     vb_enter(c, 'n');
832 
833     c->state.enable_register = TRUE;
834 
835     /* read the config file */
836     ex_run_file(c, vb.files[FILES_CONFIG]);
837 }
838 
create_window(Client * c)839 static GtkWidget *create_window(Client *c)
840 {
841     GtkWidget *window;
842 
843     if (vb.embed) {
844         window = gtk_plug_new(vb.embed);
845     } else {
846         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
847         gtk_window_set_role(GTK_WINDOW(window), PROJECT_UCFIRST);
848         gtk_window_set_default_size(GTK_WINDOW(window), WIN_WIDTH, WIN_HEIGHT);
849         if (!vb.no_maximize) {
850             gtk_window_maximize(GTK_WINDOW(window));
851         }
852     }
853 
854     g_object_connect(
855             G_OBJECT(window),
856             "signal::destroy", G_CALLBACK(on_window_destroy), c,
857             "signal::delete-event", G_CALLBACK(on_window_delete_event), c,
858             "signal::key-press-event", G_CALLBACK(on_map_keypress), c,
859             NULL);
860 
861     return window;
862 }
863 
864 /**
865  * Callback that clear the input box after a timeout if this was set on
866  * input_print.
867  */
input_clear(Client * c)868 static gboolean input_clear(Client *c)
869 {
870     if (!gtk_widget_is_focus(GTK_WIDGET(c->input))) {
871         return FALSE;
872     }
873     input_print(c, MSG_NORMAL, FALSE, "");
874 
875     return FALSE;
876 }
877 
878 /**
879  * Print a message to the input box.
880  *
881  * @type: Type of message normal or error
882  * @hide: If TRUE the inputbox is cleared after a short timeout.
883  * @message: The message to print.
884  */
input_print(Client * c,MessageType type,gboolean hide,const char * message)885 static void input_print(Client *c, MessageType type, gboolean hide,
886         const char *message)
887 {
888     /* apply input style only if the message type was changed */
889     if (type != c->state.input_type) {
890         c->state.input_type = type;
891         vb_input_update_style(c);
892     }
893 
894     vb_input_set_text(c, message);
895 
896     if (hide) {
897         /* add timeout function */
898         c->state.input_timer = g_timeout_add_seconds(MESSAGE_TIMEOUT, (GSourceFunc)input_clear, c);
899     } else if (c->state.input_timer > 0) {
900         /* If there is already a timeout function but the input box content is
901          * changed - remove the timeout. Seems the user started another
902          * command or typed into inputbox. */
903         g_source_remove(c->state.input_timer);
904         c->state.input_timer = 0;
905     }
906 }
907 
908 /**
909  * Tests if a path is likely intended to be an URI (given that it's not a file
910  * path or containing "://").
911  */
is_plausible_uri(const char * path)912 static gboolean is_plausible_uri(const char *path)
913 {
914     const char *i, *j;
915     if (strchr(path, ' ')) {
916         return FALSE;
917     }
918     if (strchr(path, '.')) {
919         return TRUE;
920     }
921     if ((i = strstr(path, "localhost")) &&
922         (i == path || i[-1] == '/' || i[-1] == '@') &&
923         (i[9] == 0 || i[9]  == '/' || i[9] == ':')
924     ) {
925         return TRUE;
926     }
927     return (i = strchr(path, '[')) && (j = strchr(i, ':')) && strchr(j, ']');
928 }
929 
930 /**
931  * Reinitializes or clears the set page marks.
932  */
marks_clear(Client * c)933 static void marks_clear(Client *c)
934 {
935     int i;
936 
937     /* init empty marks array */
938     for (i = 0; i < MARK_SIZE; i++) {
939         c->state.marks[i] = -1;
940     }
941 }
942 
943 /**
944  * Free the memory of given mode. This is used as destroy function of the
945  * modes hashmap.
946  */
mode_free(Mode * mode)947 static void mode_free(Mode *mode)
948 {
949     g_slice_free(Mode, mode);
950 }
951 
952 /**
953  * The ::changed signal is emitted when the content of a GtkTextBuffer has
954  * changed. This call back function is connected to the input box' text buffer.
955  */
on_textbuffer_changed(GtkTextBuffer * textbuffer,gpointer user_data)956 static void on_textbuffer_changed(GtkTextBuffer *textbuffer, gpointer user_data)
957 {
958     gchar *text;
959     GtkTextIter start, end;
960     Client *c = (Client *)user_data;
961 
962     g_assert(c);
963 
964     /* don't observe changes in completion mode */
965     if (c->mode->flags & FLAG_COMPLETION) {
966         return;
967     }
968 
969     /* don't process changes not typed by the user */
970     if (gtk_widget_is_focus(c->input) && c->mode && c->mode->input_changed) {
971 
972         gtk_text_buffer_get_bounds(textbuffer, &start, &end);
973         text = gtk_text_buffer_get_text(textbuffer, &start, &end, FALSE);
974 
975         c->mode->input_changed(c, text);
976 
977         g_free(text);
978     }
979 }
980 
981 /**
982  * Set the style of the statusbar.
983  */
set_statusbar_style(Client * c,StatusType type)984 static void set_statusbar_style(Client *c, StatusType type)
985 {
986     GtkStyleContext *ctx;
987     /* Do nothing if the new to set style is the same as the current. */
988     if (type == c->state.status_type) {
989         return;
990     }
991 
992     ctx = gtk_widget_get_style_context(GTK_WIDGET(c->statusbar.box));
993 
994     if (type == STATUS_SSL_VALID) {
995         gtk_style_context_remove_class(ctx, "unsecure");
996         gtk_style_context_add_class(ctx, "secure");
997     } else if (type == STATUS_SSL_INVALID) {
998         gtk_style_context_remove_class(ctx, "secure");
999         gtk_style_context_add_class(ctx, "unsecure");
1000     } else {
1001         gtk_style_context_remove_class(ctx, "secure");
1002         gtk_style_context_remove_class(ctx, "unsecure");
1003     }
1004     c->state.status_type = type;
1005 }
1006 
1007 /**
1008  * Update the window title of the main window.
1009  */
set_title(Client * c,const char * title)1010 static void set_title(Client *c, const char *title)
1011 {
1012     OVERWRITE_STRING(c->state.title, title);
1013     update_title(c);
1014     g_setenv("VIMB_TITLE", title ? title : "", TRUE);
1015 }
1016 
1017 /**
1018  * Spawns a new browser instance for given uri.
1019  *
1020  * @uri:    URI used for the new instance.
1021  */
spawn_new_instance(const char * uri)1022 static void spawn_new_instance(const char *uri)
1023 {
1024     guint i = 0;
1025     /* memory allocation */
1026     char **cmd = g_malloc_n(
1027         3                       /* basename + uri + ending NULL */
1028         + (vb.configfile ? 2 : 0)
1029 #ifndef FEATURE_NO_XEMBED
1030         + (vb.embed ? 2 : 0)
1031 #endif
1032         + (vb.incognito ? 1 : 0)
1033         + (vb.profile ? 2 : 0)
1034         + (vb.no_maximize ? 1 : 0)
1035         + g_slist_length(vb.cmdargs) * 2,
1036         sizeof(char *)
1037     );
1038 
1039     cmd[i++] = vb.argv0;
1040 
1041     if (vb.configfile) {
1042         cmd[i++] = "-c";
1043         cmd[i++] = vb.configfile;
1044     }
1045 #ifndef FEATURE_NO_XEMBED
1046     if (vb.embed) {
1047         char xid[64];
1048         cmd[i++] = "-e";
1049         snprintf(xid, LENGTH(xid), "%d", (int)vb.embed);
1050         cmd[i++] = xid;
1051     }
1052 #endif
1053     if (vb.incognito) {
1054         cmd[i++] = "-i";
1055     }
1056     if (vb.profile) {
1057         cmd[i++] = "-p";
1058         cmd[i++] = vb.profile;
1059     }
1060     if (vb.no_maximize) {
1061         cmd[i++] = "--no-maximize";
1062     }
1063     for (GSList *l = vb.cmdargs; l; l = l->next) {
1064         cmd[i++] = "-C";
1065         cmd[i++] = l->data;
1066     }
1067     cmd[i++] = (char*)uri;
1068     cmd[i++] = NULL;
1069 
1070     /* spawn a new browser instance */
1071     g_spawn_async(NULL, cmd, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL);
1072 
1073     /* free commandline */
1074     g_free(cmd);
1075 }
1076 
1077 /**
1078  * Callback for the web contexts download-started signal.
1079  */
on_webctx_download_started(WebKitWebContext * webctx,WebKitDownload * download,Client * c)1080 static void on_webctx_download_started(WebKitWebContext *webctx,
1081         WebKitDownload *download, Client *c)
1082 {
1083 #ifdef FEATURE_AUTOCMD
1084     const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1085     autocmd_run(c, AU_DOWNLOAD_STARTED, uri, NULL);
1086 #endif
1087 
1088     if (GET_BOOL(c, "download-use-external")) {
1089         g_signal_connect(download, "notify::response", G_CALLBACK(on_webdownload_response_received), c);
1090     } else {
1091         g_signal_connect(download, "decide-destination", G_CALLBACK(on_webdownload_decide_destination), c);
1092         g_signal_connect(download, "failed", G_CALLBACK(on_webdownload_failed), c);
1093         g_signal_connect(download, "finished", G_CALLBACK(on_webdownload_finished), c);
1094         g_signal_connect(download, "received-data", G_CALLBACK(on_webdownload_received_data), c);
1095 
1096         c->state.downloads = g_list_append(c->state.downloads, download);
1097 
1098         /* to reflect the correct download count */
1099         vb_statusbar_update(c);
1100     }
1101 }
1102 
1103 /**
1104  * Callback for the web contexts initialize-web-extensions signal.
1105  */
on_webctx_init_web_extension(WebKitWebContext * webctx,gpointer data)1106 static void on_webctx_init_web_extension(WebKitWebContext *webctx, gpointer data)
1107 {
1108     const char *name;
1109     GVariant *vdata;
1110 
1111 #if (CHECK_WEBEXTENSION_ON_STARTUP)
1112     char *extension = g_build_filename(EXTENSIONDIR,  "webext_main.so", NULL);
1113     if (!g_file_test(extension, G_FILE_TEST_IS_REGULAR)) {
1114         g_error("Cannot access web extension %s", extension);
1115     }
1116     g_free(extension);
1117 #endif
1118 
1119     name  = ext_proxy_init();
1120     vdata = g_variant_new("(ms)", name);
1121     webkit_web_context_set_web_extensions_initialization_user_data(webctx, vdata);
1122 
1123     /* Setup the extension directory. */
1124     webkit_web_context_set_web_extensions_directory(webctx, EXTENSIONDIR);
1125 }
1126 
1127 /**
1128  * Callback for the webkit download decide destination signal.
1129  * This signal is emitted after response is received to decide a destination
1130  * URI for the download.
1131  */
on_webdownload_decide_destination(WebKitDownload * download,gchar * suggested_filename,Client * c)1132 static gboolean on_webdownload_decide_destination(WebKitDownload *download,
1133         gchar *suggested_filename, Client *c)
1134 {
1135     if (webkit_download_get_destination(download)) {
1136         return TRUE;
1137     }
1138 
1139     return vb_download_set_destination(c, download, suggested_filename, NULL);
1140 }
1141 
on_webdownload_response_received(WebKitDownload * download,GParamSpec * ps,Client * c)1142 static void on_webdownload_response_received(WebKitDownload *download,
1143         GParamSpec *ps, Client *c)
1144 {
1145     spawn_download_command(c, webkit_download_get_response(download));
1146     webkit_download_cancel(download);
1147 }
1148 
spawn_download_command(Client * c,WebKitURIResponse * response)1149 static void spawn_download_command(Client *c, WebKitURIResponse *response)
1150 {
1151     char *cmd;
1152     char **argv, **envp;
1153     int argc;
1154     GError *error = NULL;
1155 
1156     cmd = g_strdup_printf(GET_CHAR(c, "download-command"),
1157             webkit_uri_response_get_uri(response));
1158 
1159     if (!g_shell_parse_argv(cmd, &argc, &argv, &error)) {
1160         g_warning("Could not parse download-command '%s': %s",
1161                 cmd,
1162                 error->message);
1163         g_error_free(error);
1164         g_free(cmd);
1165         return;
1166     }
1167 
1168     envp = g_get_environ();
1169     envp = g_environ_setenv(envp, "VIMB_DOWNLOAD_PATH",
1170             GET_CHAR(c, "download-path"), TRUE);
1171 
1172     if (g_spawn_async(NULL, argv, envp, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error)) {
1173         vb_echo(c, MSG_NORMAL, FALSE, "Download started");
1174     } else {
1175         vb_echo(c, MSG_ERROR, TRUE, "Could not start download");
1176         g_warning("%s", error->message);
1177         g_clear_error(&error);
1178     }
1179     g_free(cmd);
1180     g_strfreev(envp);
1181     g_strfreev(argv);
1182 }
1183 
1184 /**
1185  * Callback for the webkit download failed signal.
1186  * This signal is emitted when an error occurs during the download operation.
1187  */
on_webdownload_failed(WebKitDownload * download,GError * error,Client * c)1188 static void on_webdownload_failed(WebKitDownload *download,
1189                GError *error, Client *c)
1190 {
1191     gchar *destination = NULL, *filename = NULL, *basename = NULL;
1192 
1193     g_assert(download);
1194     g_assert(error);
1195     g_assert(c);
1196 
1197 #ifdef FEATURE_AUTOCMD
1198     const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1199     autocmd_run(c, AU_DOWNLOAD_FAILED, uri, NULL);
1200 #endif
1201 
1202     /* get the failed download's destination uri */
1203     g_object_get(download, "destination", &destination, NULL);
1204     g_assert(destination);
1205 
1206     /* filename from uri */
1207     if (destination) {
1208         filename = g_filename_from_uri(destination, NULL, NULL);
1209         g_free(destination);
1210     }
1211 
1212     /* basename from filename */
1213     if (filename) {
1214         basename = g_path_get_basename(filename);
1215         g_free(filename);
1216     }
1217 
1218     /* report the error to the user */
1219     if (basename) {
1220         vb_echo(c, MSG_ERROR, FALSE, "Download of %s failed (%s)", basename, error->message);
1221         g_free(basename);
1222     }
1223 }
1224 
1225 /**
1226  * Callback for the webkit download finished signal.
1227  * This signal is emitted when download finishes successfully or due to an
1228  * error. In case of errors “failed” signal is emitted before this one.
1229  */
on_webdownload_finished(WebKitDownload * download,Client * c)1230 static void on_webdownload_finished(WebKitDownload *download, Client *c)
1231 {
1232     gchar *destination = NULL, *filename = NULL, *basename = NULL;
1233 
1234     g_assert(download);
1235     g_assert(c);
1236 
1237 #ifdef FEATURE_AUTOCMD
1238     const char *uri = webkit_uri_request_get_uri(webkit_download_get_request(download));
1239     autocmd_run(c, AU_DOWNLOAD_FINISHED, uri, NULL);
1240 #endif
1241 
1242     c->state.downloads = g_list_remove(c->state.downloads, download);
1243 
1244     /* to reflect the correct download count */
1245     vb_statusbar_update(c);
1246 
1247     /* get the finished downloads destination uri */
1248     g_object_get(download, "destination", &destination, NULL);
1249     g_assert(destination);
1250 
1251     /* filename from uri */
1252     if (destination) {
1253         filename = g_filename_from_uri(destination, NULL, NULL);
1254         g_free(destination);
1255     }
1256 
1257     if (filename) {
1258         /* basename from filename */
1259         basename = g_path_get_basename(filename);
1260 
1261         if (basename) {
1262             /* Only report to the user if the downloaded file exists, so the
1263              * download was successful. Otherwise, this is a failed download
1264              * finished signal and it was reported to the user in
1265              * on_webdownload_failed() already. */
1266             if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
1267                 vb_echo(c, MSG_NORMAL, FALSE, "Download of %s finished", basename);
1268             }
1269 
1270             g_free(basename);
1271         }
1272 
1273         g_free(filename);
1274     }
1275 }
1276 
1277 /**
1278  * Callback for the webkit download received-data signal.
1279  * This signal is emitted after response is received, every time new data has
1280  * been written to the destination. It's useful to know the progress of the
1281  * download operation.
1282  */
on_webdownload_received_data(WebKitDownload * download,guint64 data_length,Client * c)1283 static void on_webdownload_received_data(WebKitDownload *download,
1284         guint64 data_length, Client *c)
1285 {
1286     /* rate limit statusbar updates */
1287     static gint64 statusbar_update_next = 0;
1288 
1289     if (g_get_monotonic_time() > statusbar_update_next) {
1290         statusbar_update_next = g_get_monotonic_time() + 1000000; /* 1 second */
1291 
1292         vb_statusbar_update(c);
1293     }
1294 }
1295 
1296 /**
1297  * Callback for the webview close signal.
1298  */
on_webview_close(WebKitWebView * webview,Client * c)1299 static void on_webview_close(WebKitWebView *webview, Client *c)
1300 {
1301     gtk_widget_destroy(c->window);
1302 }
1303 
1304 /**
1305  * Callback for the webview create signal.
1306  * This creates a new client - with it's own window with a related webview.
1307  */
on_webview_create(WebKitWebView * webview,WebKitNavigationAction * navact,Client * c)1308 static WebKitWebView *on_webview_create(WebKitWebView *webview,
1309         WebKitNavigationAction *navact, Client *c)
1310 {
1311     WebKitURIRequest *req;
1312     if (c->config.prevent_newwindow) {
1313         req = webkit_navigation_action_get_request(navact);
1314         vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
1315 
1316         return NULL;
1317     }
1318 
1319     Client *new = client_new(webview);
1320 
1321     return new->webview;
1322 }
1323 
1324 /**
1325  * Callback for the webview decide-policy signal.
1326  * Checks the reasons for some navigation actions and decides if the action is
1327  * allowed, or should go into a new instance of vimb.
1328  */
on_webview_decide_policy(WebKitWebView * webview,WebKitPolicyDecision * dec,WebKitPolicyDecisionType type,Client * c)1329 static gboolean on_webview_decide_policy(WebKitWebView *webview,
1330         WebKitPolicyDecision *dec, WebKitPolicyDecisionType type, Client *c)
1331 {
1332     switch (type) {
1333         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1334             decide_navigation_action(c, dec);
1335             break;
1336 
1337         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1338             decide_new_window_action(c, dec);
1339             break;
1340 
1341         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1342             decide_response(c, dec);
1343             break;
1344 
1345         default:
1346             webkit_policy_decision_ignore(dec);
1347             break;
1348     }
1349 
1350     return TRUE;
1351 }
1352 
decide_navigation_action(Client * c,WebKitPolicyDecision * dec)1353 static void decide_navigation_action(Client *c, WebKitPolicyDecision *dec)
1354 {
1355     guint button, mod;
1356     WebKitNavigationAction *a;
1357     WebKitURIRequest *req;
1358     const char *uri;
1359 
1360     a   = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
1361     req = webkit_navigation_action_get_request(a);
1362     uri = webkit_uri_request_get_uri(req);
1363 
1364     /* Try to handle with specific protocol handler. */
1365     if (handler_handle_uri(c->handler, uri)) {
1366         webkit_policy_decision_ignore(dec);
1367         return;
1368     }
1369 
1370     button = webkit_navigation_action_get_mouse_button(a);
1371     mod    = webkit_navigation_action_get_modifiers(a);
1372     /* Spawn new instance if the new win flag is set on the mode, or the
1373      * navigation was triggered by CTRL-LeftMouse or MiddleMouse. */
1374     if ((c->mode->flags & FLAG_NEW_WIN)
1375         || (webkit_navigation_action_get_navigation_type(a) == WEBKIT_NAVIGATION_TYPE_LINK_CLICKED
1376             && (button == 2 || (button == 1 && mod & GDK_CONTROL_MASK)))) {
1377 
1378         /* Remove the FLAG_NEW_WIN after the first use. */
1379         c->mode->flags &= ~FLAG_NEW_WIN;
1380 
1381         webkit_policy_decision_ignore(dec);
1382         spawn_new_instance(uri);
1383     } else {
1384 #ifdef FEATURE_AUTOCMD
1385         if (strcmp(uri, "about:blank"))
1386             autocmd_run(c, AU_LOAD_STARTING, uri, NULL);
1387 #endif
1388         webkit_policy_decision_use(dec);
1389     }
1390 }
1391 
decide_new_window_action(Client * c,WebKitPolicyDecision * dec)1392 static void decide_new_window_action(Client *c, WebKitPolicyDecision *dec)
1393 {
1394     WebKitNavigationAction *a;
1395     WebKitURIRequest *req;
1396 
1397     a = webkit_navigation_policy_decision_get_navigation_action(WEBKIT_NAVIGATION_POLICY_DECISION(dec));
1398 
1399     switch (webkit_navigation_action_get_navigation_type(a)) {
1400         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED:   /* fallthrough */
1401         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1402         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD:   /* fallthrough */
1403         case WEBKIT_NAVIGATION_TYPE_RELOAD:         /* fallthrough */
1404         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1405             /* This is triggered on link click for links with target="_blank".
1406              * Maybe it should be configurable if the page is opened as tab or
1407              * a new instance. Ignore opening new window if this was started
1408              * without user gesture. */
1409             if (webkit_navigation_action_is_user_gesture(a)) {
1410                 req = webkit_navigation_action_get_request(a);
1411                 if (c->config.prevent_newwindow) {
1412                     /* Load the uri into the browser instance. */
1413                     vb_load_uri(c, &(Arg){TARGET_CURRENT, (char*)webkit_uri_request_get_uri(req)});
1414                 } else {
1415                     spawn_new_instance(webkit_uri_request_get_uri(req));
1416                 }
1417             }
1418             break;
1419 
1420         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1421         default:
1422             break;
1423     }
1424     webkit_policy_decision_ignore(dec);
1425 }
1426 
decide_response(Client * c,WebKitPolicyDecision * dec)1427 static void decide_response(Client *c, WebKitPolicyDecision *dec)
1428 {
1429     guint status;
1430     WebKitURIResponse *res;
1431 
1432     res    = webkit_response_policy_decision_get_response(WEBKIT_RESPONSE_POLICY_DECISION(dec));
1433     status = webkit_uri_response_get_status_code(res);
1434 
1435     if (webkit_response_policy_decision_is_mime_type_supported(WEBKIT_RESPONSE_POLICY_DECISION(dec))) {
1436         webkit_policy_decision_use(dec);
1437     } else if (SOUP_STATUS_IS_SUCCESSFUL(status) || status == SOUP_STATUS_NONE) {
1438         webkit_policy_decision_download(dec);
1439     } else {
1440         webkit_policy_decision_ignore(dec);
1441     }
1442 }
1443 
on_webview_load_changed(WebKitWebView * webview,WebKitLoadEvent event,Client * c)1444 static void on_webview_load_changed(WebKitWebView *webview,
1445         WebKitLoadEvent event, Client *c)
1446 {
1447     GTlsCertificateFlags tlsflags;
1448     const char *raw_uri;
1449     char *uri = NULL;
1450 
1451     raw_uri = webkit_web_view_get_uri(webview);
1452     if (raw_uri) {
1453         uri = util_sanitize_uri(raw_uri);
1454     }
1455 
1456     switch (event) {
1457         case WEBKIT_LOAD_STARTED:
1458 #ifdef FEATURE_AUTOCMD
1459             autocmd_run(c, AU_LOAD_STARTED, raw_uri, NULL);
1460 #endif
1461             /* update load progress in statusbar */
1462             c->state.progress = 0;
1463             vb_statusbar_update(c);
1464             if (uri) {
1465                 set_title(c, uri);
1466             }
1467             /* Make sure hinting is cleared before the new page is loaded.
1468              * Without that vimb would still be in hinting mode after hinting
1469              * was started and some links was clicked my mouse. Even if there
1470              * could not hints be shown. */
1471             if (c->mode->flags & FLAG_HINTING) {
1472                 vb_enter(c, 'n');
1473             }
1474             break;
1475 
1476         case WEBKIT_LOAD_REDIRECTED:
1477             break;
1478 
1479         case WEBKIT_LOAD_COMMITTED:
1480             /* In case of HTTP authentication request we ignore the focus
1481              * changes so that the input mode can be set for the
1482              * authentication request. If the authentication dialog is filled
1483              * or aborted the load will be commited. So this seems to be the
1484              * right place to remove the flag. */
1485             c->mode->flags &= ~FLAG_IGNORE_FOCUS;
1486 #ifdef FEATURE_AUTOCMD
1487             autocmd_run(c, AU_LOAD_COMMITTED, raw_uri, NULL);
1488 #endif
1489             /* save the current URI in register % */
1490             vb_register_add(c, '%', uri);
1491             /* check if tls is on and the page is trusted */
1492             if (uri && g_str_has_prefix(uri, "https://")) {
1493                 if (webkit_web_view_get_tls_info(webview, NULL, &tlsflags) && tlsflags) {
1494                     set_statusbar_style(c, STATUS_SSL_INVALID);
1495                 } else {
1496                     set_statusbar_style(c, STATUS_SSL_VALID);
1497                 }
1498             } else {
1499                 set_statusbar_style(c, STATUS_NORMAL);
1500             }
1501 
1502             /* clear possible set marks */
1503             marks_clear(c);
1504 
1505             /* Unset possible last search. Use commit==TRUE to clear inputbox
1506              * in case a link was fired from highlighted link. */
1507             command_search(c, &(Arg){0, NULL}, TRUE);
1508 
1509             break;
1510 
1511         case WEBKIT_LOAD_FINISHED:
1512 #ifdef FEATURE_AUTOCMD
1513             autocmd_run(c, AU_LOAD_FINISHED, raw_uri, NULL);
1514 #endif
1515             c->state.progress = 100;
1516             if (uri && strncmp(uri, "about:", 6)) {
1517                 history_add(c, HISTORY_URL, uri, webkit_web_view_get_title(webview));
1518             }
1519             break;
1520     }
1521 
1522     if (uri) {
1523         g_free(uri);
1524     }
1525 }
1526 
1527 /**
1528  * Callback for the webview mouse-target-changed signal.
1529  * This is used to print the uri too statusbar if the user hovers over links
1530  * or images.
1531  */
on_webview_mouse_target_changed(WebKitWebView * webview,WebKitHitTestResult * result,guint modifiers,Client * c)1532 static void on_webview_mouse_target_changed(WebKitWebView *webview,
1533         WebKitHitTestResult *result, guint modifiers, Client *c)
1534 {
1535     /* Save the hitTestResult to have this later available for events that
1536      * don't support this. */
1537     if (c->state.hit_test_result) {
1538         g_object_unref(c->state.hit_test_result);
1539     }
1540     c->state.hit_test_result = g_object_ref(result);
1541 
1542     if (webkit_hit_test_result_context_is_link(result)) {
1543         vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
1544                 webkit_hit_test_result_get_link_uri(result));
1545     } else if (webkit_hit_test_result_context_is_image(result)) {
1546         vb_statusbar_show_hover_url(c, LINK_TYPE_LINK,
1547                 webkit_hit_test_result_get_image_uri(result));
1548     } else {
1549         /* No link under cursor - show the current URI. */
1550         vb_statusbar_show_hover_url(c, LINK_TYPE_NONE, NULL);
1551     }
1552 }
1553 
1554 /**
1555  * Called on webviews notify::estimated-load-progress event. This writes the
1556  * esitamted load progress in percent in a variable and updates the statusbar
1557  * to make the changes visible.
1558  */
on_webview_notify_estimated_load_progress(WebKitWebView * webview,GParamSpec * spec,Client * c)1559 static void on_webview_notify_estimated_load_progress(WebKitWebView *webview,
1560         GParamSpec *spec, Client *c)
1561 {
1562     c->state.progress = webkit_web_view_get_estimated_load_progress(webview) * 100;
1563     vb_statusbar_update(c);
1564     update_title(c);
1565 }
1566 
1567 /**
1568  * Callback for the webview notify::title signal.
1569  * Changes the window title according to the title of the current page.
1570  */
on_webview_notify_title(WebKitWebView * webview,GParamSpec * pspec,Client * c)1571 static void on_webview_notify_title(WebKitWebView *webview, GParamSpec *pspec, Client *c)
1572 {
1573     const char *title = webkit_web_view_get_title(webview);
1574 
1575     if (*title) {
1576         set_title(c, title);
1577     }
1578 }
1579 
1580 /**
1581  * Callback for the webview notify::uri signal.
1582  * Changes the current uri shown on left of statusbar.
1583  */
on_webview_notify_uri(WebKitWebView * webview,GParamSpec * pspec,Client * c)1584 static void on_webview_notify_uri(WebKitWebView *webview, GParamSpec *pspec, Client *c)
1585 {
1586     if (c->state.uri) {
1587         g_free(c->state.uri);
1588     }
1589 
1590     c->state.uri = util_sanitize_uri(webkit_web_view_get_uri(c->webview));
1591 
1592     update_urlbar(c);
1593     g_setenv("VIMB_URI", c->state.uri, TRUE);
1594 }
1595 
1596 /**
1597  * Callback for the webview ready-to-show signal.
1598  * Show the webview only if it's ready to be shown.
1599  */
on_webview_ready_to_show(WebKitWebView * webview,Client * c)1600 static void on_webview_ready_to_show(WebKitWebView *webview, Client *c)
1601 {
1602     client_show(webview, c);
1603 }
1604 
1605 /**
1606  * Callback for the webview web-process-crashed signal.
1607  */
on_webview_web_process_crashed(WebKitWebView * webview,Client * c)1608 static gboolean on_webview_web_process_crashed(WebKitWebView *webview, Client *c)
1609 {
1610     vb_echo(c, MSG_ERROR, FALSE, "Webview Crashed on %s", webkit_web_view_get_uri(webview));
1611 
1612     return TRUE;
1613 }
1614 
1615 /**
1616  * Callback in case HTTP authentication is requested by the server.
1617  */
on_webview_authenticate(WebKitWebView * webview,WebKitAuthenticationRequest * request,Client * c)1618 static gboolean on_webview_authenticate(WebKitWebView *webview,
1619         WebKitAuthenticationRequest *request, Client *c)
1620 {
1621     /* Don't change the mode if we are in pass through mode. */
1622     if (c->mode->id == 'n') {
1623         vb_enter(c, 'i');
1624         /* Make sure we do not switch back to normal mode in case a previos
1625          * page is open and looses the focus. */
1626         c->mode->flags |= FLAG_IGNORE_FOCUS;
1627     }
1628     return FALSE;
1629 }
1630 
1631 /**
1632  * Callback in case JS calls element.webkitRequestFullScreen.
1633  */
on_webview_enter_fullscreen(WebKitWebView * webview,Client * c)1634 static gboolean on_webview_enter_fullscreen(WebKitWebView *webview, Client *c)
1635 {
1636     c->state.is_fullscreen = TRUE;
1637     gtk_widget_hide(GTK_WIDGET(c->statusbar.box));
1638     gtk_widget_set_visible(GTK_WIDGET(c->input), FALSE);
1639     return FALSE;
1640 }
1641 
1642 /**
1643  * Callback to restore the window state after entering fullscreen.
1644  */
on_webview_leave_fullscreen(WebKitWebView * webview,Client * c)1645 static gboolean on_webview_leave_fullscreen(WebKitWebView *webview, Client *c)
1646 {
1647     c->state.is_fullscreen = FALSE;
1648     gtk_widget_show(GTK_WIDGET(c->statusbar.box));
1649     gtk_widget_set_visible(GTK_WIDGET(c->input), TRUE);
1650     return FALSE;
1651 }
1652 
1653 /**
1654  * Callback for window ::delete-event signal which is emitted if a user
1655  * requests that a toplevel window is closed. The default handler for this
1656  * signal destroys the window. Returns TRUE to stop other handlers from being
1657  * invoked for the event. FALSE to propagate the event further.
1658  */
on_window_delete_event(GtkWidget * window,GdkEvent * event,Client * c)1659 static gboolean on_window_delete_event(GtkWidget *window, GdkEvent *event, Client *c)
1660 {
1661     /* if vb_quit fails, do not propagate event further, keep window open */
1662     return !vb_quit(c, FALSE);
1663 }
1664 
1665 /**
1666  * Callback for the window destroy signal.
1667  * Destroys the client that is associated to the window.
1668  */
on_window_destroy(GtkWidget * window,Client * c)1669 static void on_window_destroy(GtkWidget *window, Client *c)
1670 {
1671     client_destroy(c);
1672 }
1673 
1674 /**
1675  * Callback for to quit given client as idle event source.
1676  */
quit(Client * c)1677 static gboolean quit(Client *c)
1678 {
1679     /* Destroy the main window to tirgger the destruction of the client. */
1680     gtk_widget_destroy(c->window);
1681 
1682     /* Remove this from the list of event sources. */
1683     return FALSE;
1684 }
1685 
1686 /**
1687  * Read string from stdin and pass it to webkit for html interpretation.
1688  */
read_from_stdin(Client * c)1689 static void read_from_stdin(Client *c)
1690 {
1691     GIOChannel *ch;
1692     gchar *buf = NULL;
1693     GError *err = NULL;
1694     gsize len = 0;
1695 
1696     g_assert(c);
1697 
1698     ch = g_io_channel_unix_new(fileno(stdin));
1699     g_io_channel_read_to_end(ch, &buf, &len, &err);
1700     g_io_channel_unref(ch);
1701 
1702     if (err) {
1703         g_warning("Error loading from stdin: %s", err->message);
1704         g_error_free(err);
1705     } else {
1706         webkit_web_view_load_html(c->webview, buf, NULL);
1707     }
1708     g_free(buf);
1709 }
1710 
1711 /**
1712  * Free the register contents memory.
1713  */
register_cleanup(Client * c)1714 static void register_cleanup(Client *c)
1715 {
1716     int i;
1717     for (i = 0; i < REG_SIZE; i++) {
1718         if (c->state.reg[i]) {
1719             g_free(c->state.reg[i]);
1720         }
1721     }
1722 }
1723 
update_title(Client * c)1724 static void update_title(Client *c)
1725 {
1726 #ifdef FEATURE_TITLE_PROGRESS
1727     /* Show load status of page or the downloads. */
1728     if (c->state.progress != 100) {
1729         char *title = g_strdup_printf(
1730                 "[%i%%] %s",
1731                 c->state.progress,
1732                 c->state.title ? c->state.title : "");
1733         gtk_window_set_title(GTK_WINDOW(c->window), title);
1734         g_free(title);
1735 
1736         return;
1737     }
1738 #endif
1739     if (c->state.title) {
1740         gtk_window_set_title(GTK_WINDOW(c->window), c->state.title);
1741     }
1742 }
1743 
1744 /**
1745  * Update the contents of the url bar on the left of the statu bar according
1746  * to current opened url and position in back forward history.
1747  */
update_urlbar(Client * c)1748 static void update_urlbar(Client *c)
1749 {
1750     GString *str;
1751     gboolean back, fwd;
1752 
1753     str = g_string_new("");
1754     /* show profile name */
1755     if (vb.profile) {
1756         g_string_append_printf(str, "[%s] ", vb.profile);
1757     }
1758 
1759     g_string_append_printf(str, "%s", c->state.uri);
1760 
1761     /* show history indicator only if there is something to show */
1762     back = webkit_web_view_can_go_back(c->webview);
1763     fwd  = webkit_web_view_can_go_forward(c->webview);
1764     if (back || fwd) {
1765         g_string_append_printf(str, " [%s]", back ? (fwd ? "-+" : "-") : "+");
1766     }
1767 
1768     gtk_label_set_text(GTK_LABEL(c->statusbar.left), str->str);
1769     g_string_free(str, TRUE);
1770 }
1771 
1772 #ifdef FREE_ON_QUIT
1773 /**
1774  * Free memory of the whole application.
1775  */
vimb_cleanup(void)1776 static void vimb_cleanup(void)
1777 {
1778     int i;
1779 
1780     while (vb.clients) {
1781         client_destroy(vb.clients);
1782     }
1783 
1784     /* free memory of other components */
1785     util_cleanup();
1786 
1787     for (i = 0; i < STORAGE_LAST; i++) {
1788         file_storage_free(vb.storage[i]);
1789     }
1790     for (i = 0; i < FILES_LAST; i++) {
1791         if (vb.files[i]) {
1792             g_free(vb.files[i]);
1793         }
1794     }
1795     g_free(vb.profile);
1796 
1797     g_slist_free_full(vb.cmdargs, g_free);
1798 }
1799 #endif
1800 
1801 /**
1802  * Setup resources used on application scope.
1803  */
vimb_setup(void)1804 static void vimb_setup(void)
1805 {
1806     WebKitWebContext *ctx;
1807     WebKitCookieManager *cm;
1808     char *path;
1809 
1810     /* prepare the file pathes */
1811     path = util_get_config_dir();
1812 
1813     if (vb.configfile) {
1814         char *rp = realpath(vb.configfile, NULL);
1815         vb.files[FILES_CONFIG] = g_strdup(rp);
1816         free(rp);
1817     } else {
1818         vb.files[FILES_CONFIG] = g_build_filename(path, "config", NULL);
1819     }
1820 
1821     /* Setup those files that are use multiple time during runtime */
1822     if (!vb.incognito) {
1823         vb.files[FILES_CLOSED] = g_build_filename(path, "closed", NULL);
1824         vb.files[FILES_COOKIE] = g_build_filename(path, "cookies.db", NULL);
1825     }
1826     vb.files[FILES_BOOKMARK]   = g_build_filename(path, "bookmark", NULL);
1827     vb.files[FILES_QUEUE]      = g_build_filename(path, "queue", NULL);
1828     vb.files[FILES_SCRIPT]     = g_build_filename(path, "scripts.js", NULL);
1829     vb.files[FILES_USER_STYLE] = g_build_filename(path, "style.css", NULL);
1830 
1831     vb.storage[STORAGE_HISTORY]  = file_storage_new(path, "history", vb.incognito);
1832     vb.storage[STORAGE_COMMAND]  = file_storage_new(path, "command", vb.incognito);
1833     vb.storage[STORAGE_SEARCH]   = file_storage_new(path, "search", vb.incognito);
1834     g_free(path);
1835 
1836     /* Use seperate rendering processed for the webview of the clients in the
1837      * current instance. This must be called as soon as possible according to
1838      * the documentation. */
1839     if (vb.incognito) {
1840         ctx = webkit_web_context_new_ephemeral();
1841     } else {
1842         ctx = webkit_web_context_get_default();
1843     }
1844     webkit_web_context_set_process_model(ctx, WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1845     webkit_web_context_set_cache_model(ctx, WEBKIT_CACHE_MODEL_WEB_BROWSER);
1846 
1847     g_signal_connect(ctx, "initialize-web-extensions", G_CALLBACK(on_webctx_init_web_extension), NULL);
1848 
1849     /* Add cookie support only if the cookie file exists. */
1850     if (vb.files[FILES_COOKIE]) {
1851         cm = webkit_web_context_get_cookie_manager(ctx);
1852         webkit_cookie_manager_set_persistent_storage(
1853                 cm,
1854                 vb.files[FILES_COOKIE],
1855                 WEBKIT_COOKIE_PERSISTENT_STORAGE_SQLITE);
1856     }
1857 
1858     /* initialize the modes */
1859     vb_mode_add('n', normal_enter, normal_leave, normal_keypress, NULL);
1860     vb_mode_add('c', ex_enter, ex_leave, ex_keypress, ex_input_changed);
1861     vb_mode_add('i', input_enter, input_leave, input_keypress, NULL);
1862     vb_mode_add('p', pass_enter, pass_leave, pass_keypress, NULL);
1863 
1864     /* Prepare the style provider to be used for the clients and completion. */
1865     vb.style_provider = gtk_css_provider_new();
1866 }
1867 
1868 /**
1869  * Update the gui style settings for client c, given a style setting name and a
1870  * style setting value to be updated. The complete style sheet document will be
1871  * regenerated and re-fed into gtk css provider.
1872  */
vb_gui_style_update(Client * c,const char * setting_name_new,const char * setting_value_new)1873 void vb_gui_style_update(Client *c, const char *setting_name_new, const char *setting_value_new)
1874 {
1875     g_assert(c);
1876     g_assert(setting_name_new);
1877     g_assert(setting_value_new);
1878 
1879     /* The css style sheet document being composed in this function */
1880     GString *style_sheet = g_string_new(GUI_STYLE_CSS_BASE);
1881     size_t i;
1882 
1883     /* Mapping from vimb config setting name to css style sheet string */
1884     static const char *setting_style_map[][2] = {
1885         {"completion-css",              " #completion{%s}"},
1886         {"completion-hover-css",        " #completion:hover{%s}"},
1887         {"completion-selected-css",     " #completion:selected{%s}"},
1888         {"input-css",                   " #input{%s}"},
1889         {"input-error-css",             " #input.error{%s}"},
1890         {"status-css",                  " #statusbar{%s}"},
1891         {"status-ssl-css",              " #statusbar.secure{%s}"},
1892         {"status-ssl-invalid-css",      " #statusbar.unsecure{%s}"},
1893         {0, 0},
1894     };
1895 
1896     /* For each supported style setting name */
1897     for (i = 0; setting_style_map[i][0]; i++) {
1898         const char *setting_name = setting_style_map[i][0];
1899         const char *style_string = setting_style_map[i][1];
1900 
1901         /* If the current style setting name is the one to be updated,
1902          * append the given value with appropriate css wrapping to the
1903          * style sheet document. */
1904         if (strcmp(setting_name, setting_name_new) == 0) {
1905             if (strlen(setting_value_new)) {
1906                 g_string_append_printf(style_sheet, style_string, setting_value_new);
1907             }
1908         }
1909         /* If the current style setting name is NOT the one being updated,
1910          * append the css string based on the current config setting. */
1911         else {
1912             Setting* setting_value = (Setting*)g_hash_table_lookup(c->config.settings, setting_name);
1913 
1914             /* If the current style setting name is not available via settings
1915              * yet - this happens during setting_init() - cleanup and return.
1916              * We are going to be called again. With the last setting_add(),
1917              * all style setting names are available. */
1918             if(!setting_value) {
1919                 goto cleanup;
1920             }
1921 
1922             if (strlen(setting_value->value.s)) {
1923                 g_string_append_printf(style_sheet, style_string, setting_value->value.s);
1924             }
1925         }
1926     }
1927 
1928     /* Feed style sheet document to gtk */
1929     gtk_css_provider_load_from_data(vb.style_provider, style_sheet->str, -1, NULL);
1930 
1931     /* WORKAROUND to always ensure correct size of input field
1932      *
1933      * The following line is required to apply the style defined font size on
1934      * the GtkTextView c->input. Without the call, the font size is updated on
1935      * first user input, leading to a sudden unpleasant widget size and layout
1936      * change. According to the GTK+ docs, this call should not be required as
1937      * style context invalidation is automatic.
1938      *
1939      * "gtk_style_context_invalidate has been deprecated since version 3.12
1940      * and should not be used in newly-written code. Style contexts are
1941      * invalidated automatically."
1942      * https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-style-context-invalidate
1943      *
1944      * Required settings in vimb config file:
1945      * set input-autohide=true
1946      * set input-font-normal=20pt monospace
1947      *
1948      * A bug has been filed at GTK+
1949      * https://bugzilla.gnome.org/show_bug.cgi?id=781158
1950      *
1951      * Tested on ARCH linux with gtk3 3.22.10-1
1952      */
1953     G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
1954     gtk_style_context_invalidate(gtk_widget_get_style_context(c->input));
1955     G_GNUC_END_IGNORE_DEPRECATIONS;
1956 
1957 cleanup:
1958     g_string_free(style_sheet, TRUE);
1959 }
1960 
1961 /**
1962  * Factory to create a new webview.
1963  *
1964  * @webview:    Relates webview or NULL. If given a related webview is
1965  *              generated.
1966  */
webview_new(Client * c,WebKitWebView * webview)1967 static WebKitWebView *webview_new(Client *c, WebKitWebView *webview)
1968 {
1969     WebKitWebView *new;
1970     WebKitUserContentManager *ucm;
1971     WebKitWebContext *webcontext;
1972 
1973     /* create a new webview */
1974     ucm = webkit_user_content_manager_new();
1975     if (webview) {
1976         new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(webview));
1977     } else {
1978         new = WEBKIT_WEB_VIEW(webkit_web_view_new_with_user_content_manager(ucm));
1979     }
1980 
1981     g_object_connect(
1982         G_OBJECT(new),
1983         "signal::close", G_CALLBACK(on_webview_close), c,
1984         "signal::create", G_CALLBACK(on_webview_create), c,
1985         "signal::decide-policy", G_CALLBACK(on_webview_decide_policy), c,
1986         "signal::load-changed", G_CALLBACK(on_webview_load_changed), c,
1987         "signal::mouse-target-changed", G_CALLBACK(on_webview_mouse_target_changed), c,
1988         "signal::notify::estimated-load-progress", G_CALLBACK(on_webview_notify_estimated_load_progress), c,
1989         "signal::notify::title", G_CALLBACK(on_webview_notify_title), c,
1990         "signal::notify::uri", G_CALLBACK(on_webview_notify_uri), c,
1991         "signal::permission-request", G_CALLBACK(on_permission_request), c,
1992         "signal::ready-to-show", G_CALLBACK(on_webview_ready_to_show), c,
1993         "signal::web-process-crashed", G_CALLBACK(on_webview_web_process_crashed), c,
1994         "signal::authenticate", G_CALLBACK(on_webview_authenticate), c,
1995         "signal::enter-fullscreen", G_CALLBACK(on_webview_enter_fullscreen), c,
1996         "signal::leave-fullscreen", G_CALLBACK(on_webview_leave_fullscreen), c,
1997         NULL
1998     );
1999 
2000     webcontext = webkit_web_view_get_context(new);
2001     g_signal_connect(webcontext, "download-started", G_CALLBACK(on_webctx_download_started), c);
2002 
2003     /* Setup script message handlers. */
2004     webkit_user_content_manager_register_script_message_handler(ucm, "focus");
2005     g_signal_connect(ucm, "script-message-received::focus", G_CALLBACK(on_script_message_focus), NULL);
2006 
2007     return new;
2008 }
2009 
on_counted_matches(WebKitFindController * finder,guint count,Client * c)2010 static void on_counted_matches(WebKitFindController *finder, guint count, Client *c)
2011 {
2012     c->state.search.matches = count;
2013     vb_statusbar_update(c);
2014 }
2015 
on_permission_request(WebKitWebView * webview,WebKitPermissionRequest * request,Client * c)2016 static gboolean on_permission_request(WebKitWebView *webview,
2017         WebKitPermissionRequest *request, Client *c)
2018 {
2019     GtkWidget *dialog;
2020     int result;
2021     char *msg = NULL;
2022 
2023     if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) {
2024         char* geolocation_setting = GET_CHAR(c, "geolocation");
2025         if (strcmp(geolocation_setting, "ask") == 0) {
2026             msg = "access your location";
2027         } else if (strcmp(geolocation_setting, "always") == 0) {
2028             webkit_permission_request_allow(request);
2029             return TRUE;
2030         } else if (strcmp(geolocation_setting, "never") == 0) {
2031             webkit_permission_request_deny(request);
2032             return TRUE;
2033         }
2034     } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) {
2035         if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
2036             msg = "access the microphone";
2037         } else if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) {
2038             msg = "access you webcam";
2039         }
2040     } else {
2041         return FALSE;
2042     }
2043 
2044     dialog = gtk_message_dialog_new(GTK_WINDOW(c->window), GTK_DIALOG_MODAL,
2045             GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "Page wants to %s",
2046             msg);
2047 
2048     gtk_widget_show(dialog);
2049     result = gtk_dialog_run(GTK_DIALOG(dialog));
2050     if (GTK_RESPONSE_YES == result) {
2051         webkit_permission_request_allow(request);
2052     } else {
2053         webkit_permission_request_deny(request);
2054     }
2055     gtk_widget_destroy(dialog);
2056 
2057     return TRUE;
2058 }
2059 
on_script_message_focus(WebKitUserContentManager * manager,WebKitJavascriptResult * res,gpointer data)2060 static void on_script_message_focus(WebKitUserContentManager *manager,
2061         WebKitJavascriptResult *res, gpointer data)
2062 {
2063     char *message;
2064     GVariant *variant;
2065     guint64 pageid;
2066     gboolean is_focused;
2067     Client *c;
2068 
2069     message = util_js_result_as_string(res);
2070     variant = g_variant_parse(G_VARIANT_TYPE("(tb)"), message, NULL, NULL, NULL);
2071     g_free(message);
2072 
2073     g_variant_get(variant, "(tb)", &pageid, &is_focused);
2074     g_variant_unref(variant);
2075 
2076     c = vb_get_client_for_page_id(pageid);
2077     if (!c || c->mode->flags & FLAG_IGNORE_FOCUS) {
2078         return;
2079     }
2080 
2081     /* Don't change the mode if we are in pass through mode. */
2082     if (c->mode->id == 'n' && is_focused) {
2083         vb_enter(c, 'i');
2084     } else if (c->mode->id == 'i' && !is_focused) {
2085         vb_enter(c, 'n');
2086     }
2087 }
2088 
profileOptionArgFunc(const gchar * option_name,const gchar * value,gpointer data,GError ** error)2089 static gboolean profileOptionArgFunc(const gchar *option_name,
2090         const gchar *value, gpointer data, GError **error)
2091 {
2092     vb.profile = util_sanitize_filename(g_strdup(value));
2093 
2094     return TRUE;
2095 }
2096 
autocmdOptionArgFunc(const gchar * option_name,const gchar * value,gpointer data,GError ** error)2097 static gboolean autocmdOptionArgFunc(const gchar *option_name,
2098         const gchar *value, gpointer data, GError **error)
2099 {
2100     vb.cmdargs = g_slist_append(vb.cmdargs, g_strdup(value));
2101     return TRUE;
2102 }
2103 
main(int argc,char * argv[])2104 int main(int argc, char* argv[])
2105 {
2106     Client *c;
2107     GError *err = NULL;
2108     char *pidstr, *winid = NULL;
2109     gboolean ver = FALSE, buginfo = FALSE;
2110 
2111     GOptionEntry opts[] = {
2112         {"cmd", 'C', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)autocmdOptionArgFunc, "Ex command run before first page is loaded", NULL},
2113         {"config", 'c', 0, G_OPTION_ARG_FILENAME, &vb.configfile, "Custom configuration file", NULL},
2114         {"embed", 'e', 0, G_OPTION_ARG_STRING, &winid, "Reparents to window specified by xid", NULL},
2115         {"incognito", 'i', 0, G_OPTION_ARG_NONE, &vb.incognito, "Run with user data read-only", NULL},
2116         {"profile", 'p', 0, G_OPTION_ARG_CALLBACK, (GOptionArgFunc*)profileOptionArgFunc, "Profile name", NULL},
2117         {"version", 'v', 0, G_OPTION_ARG_NONE, &ver, "Print version", NULL},
2118         {"no-maximize", 0, 0, G_OPTION_ARG_NONE, &vb.no_maximize, "Do no attempt to maximize window", NULL},
2119         {"bug-info", 0, 0, G_OPTION_ARG_NONE, &buginfo, "Print used library versions", NULL},
2120         {NULL}
2121     };
2122 
2123     /* initialize GTK+ */
2124     if (!gtk_init_with_args(&argc, &argv, "[URI]", opts, NULL, &err)) {
2125         fprintf(stderr, "can't init gtk: %s\n", err->message);
2126         g_error_free(err);
2127 
2128         return EXIT_FAILURE;
2129     }
2130 
2131     if (ver) {
2132         printf("%s, version %s\n", PROJECT, VERSION);
2133         return EXIT_SUCCESS;
2134     }
2135 
2136     if (buginfo) {
2137         printf("Version:         %s\n", VERSION);
2138         printf("WebKit compile:  %d.%d.%d\n",
2139                 WEBKIT_MAJOR_VERSION,
2140                 WEBKIT_MINOR_VERSION,
2141                 WEBKIT_MICRO_VERSION);
2142         printf("WebKit run:      %d.%d.%d\n",
2143                 webkit_get_major_version(),
2144                 webkit_get_minor_version(),
2145                 webkit_get_micro_version());
2146         printf("GTK compile:     %d.%d.%d\n",
2147                 GTK_MAJOR_VERSION,
2148                 GTK_MINOR_VERSION,
2149                 GTK_MICRO_VERSION);
2150         printf("GTK run:         %d.%d.%d\n",
2151                 gtk_major_version,
2152                 gtk_minor_version,
2153                 gtk_micro_version);
2154         printf("libsoup compile: %d.%d.%d\n",
2155                 SOUP_MAJOR_VERSION,
2156                 SOUP_MINOR_VERSION,
2157                 SOUP_MICRO_VERSION);
2158         printf("libsoup run:     %u.%u.%u\n",
2159                 soup_get_major_version(),
2160                 soup_get_minor_version(),
2161                 soup_get_micro_version());
2162         printf("Extension dir:   %s\n",
2163                 EXTENSIONDIR);
2164 
2165         return EXIT_SUCCESS;
2166     }
2167 
2168     /* Save the base name for spawning new instances. */
2169     vb.argv0 = argv[0];
2170 
2171     /* set the current pid in env */
2172     pidstr = g_strdup_printf("%d", (int)getpid());
2173     g_setenv("VIMB_PID", pidstr, TRUE);
2174     g_free(pidstr);
2175 
2176     vimb_setup();
2177 
2178     if (winid) {
2179         vb.embed = strtol(winid, NULL, 0);
2180     }
2181 
2182     c = client_new(NULL);
2183     client_show(NULL, c);
2184 
2185     /* process the --cmd if this was given */
2186     for (GSList *l = vb.cmdargs; l; l = l->next) {
2187         ex_run_string(c, l->data, false);
2188     }
2189     if (argc <= 1) {
2190         vb_load_uri(c, &(Arg){TARGET_CURRENT, NULL});
2191     } else if (!strcmp(argv[argc - 1], "-")) {
2192         /* read from stdin if uri is - */
2193         read_from_stdin(c);
2194     } else {
2195         vb_load_uri(c, &(Arg){TARGET_CURRENT, argv[argc - 1]});
2196     }
2197 
2198     gtk_main();
2199 #ifdef FREE_ON_QUIT
2200     vimb_cleanup();
2201 #endif
2202 
2203     return EXIT_SUCCESS;
2204 }
2205