Display an error message and certificate on tls error
[surf.git] / surf.c
1 /* See LICENSE file for copyright and license details.
2  *
3  * To understand surf, start reading main().
4  */
5 #include <sys/file.h>
6 #include <sys/types.h>
7 #include <sys/wait.h>
8 #include <libgen.h>
9 #include <limits.h>
10 #include <pwd.h>
11 #include <regex.h>
12 #include <signal.h>
13 #include <stdarg.h>
14 #include <stdio.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <unistd.h>
18
19 #include <gdk/gdk.h>
20 #include <gdk/gdkkeysyms.h>
21 #include <gdk/gdkx.h>
22 #include <glib/gstdio.h>
23 #include <gtk/gtk.h>
24 #include <gtk/gtkx.h>
25 #include <gcr/gcr.h>
26 #include <JavaScriptCore/JavaScript.h>
27 #include <webkit2/webkit2.h>
28 #include <X11/X.h>
29 #include <X11/Xatom.h>
30
31 #include "arg.h"
32
33 #define LENGTH(x)               (sizeof(x) / sizeof(x[0]))
34 #define CLEANMASK(mask)         (mask & (MODKEY|GDK_SHIFT_MASK))
35 #define SETB(p, s)              [p] = { { .b = s }, }
36 #define SETI(p, s)              [p] = { { .i = s }, }
37 #define SETV(p, s)              [p] = { { .v = s }, }
38 #define SETF(p, s)              [p] = { { .f = s }, }
39 #define FSETB(p, s)             [p] = { { .b = s }, 1 }
40 #define FSETI(p, s)             [p] = { { .i = s }, 1 }
41 #define FSETV(p, s)             [p] = { { .v = s }, 1 }
42 #define FSETF(p, s)             [p] = { { .f = s }, 1 }
43 #define CSETB(p, s)             [p] = (Parameter){ { .b = s }, 1 }
44 #define CSETI(p, s)             [p] = (Parameter){ { .i = s }, 1 }
45 #define CSETV(p, s)             [p] = (Parameter){ { .v = s }, 1 }
46 #define CSETF(p, s)             [p] = (Parameter){ { .f = s }, 1 }
47
48 enum { AtomFind, AtomGo, AtomUri, AtomLast };
49
50 enum {
51         OnDoc   = WEBKIT_HIT_TEST_RESULT_CONTEXT_DOCUMENT,
52         OnLink  = WEBKIT_HIT_TEST_RESULT_CONTEXT_LINK,
53         OnImg   = WEBKIT_HIT_TEST_RESULT_CONTEXT_IMAGE,
54         OnMedia = WEBKIT_HIT_TEST_RESULT_CONTEXT_MEDIA,
55         OnEdit  = WEBKIT_HIT_TEST_RESULT_CONTEXT_EDITABLE,
56         OnBar   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SCROLLBAR,
57         OnSel   = WEBKIT_HIT_TEST_RESULT_CONTEXT_SELECTION,
58         OnAny   = OnDoc | OnLink | OnImg | OnMedia | OnEdit | OnBar | OnSel,
59 };
60
61 typedef enum {
62         AcceleratedCanvas,
63         CaretBrowsing,
64         Certificate,
65         CookiePolicies,
66         DiskCache,
67         DNSPrefetch,
68         FontSize,
69         FrameFlattening,
70         Geolocation,
71         HideBackground,
72         Inspector,
73         JavaScript,
74         KioskMode,
75         LoadImages,
76         MediaManualPlay,
77         Plugins,
78         PreferredLanguages,
79         RunInFullscreen,
80         ScrollBars,
81         ShowIndicators,
82         SiteQuirks,
83         SpellChecking,
84         SpellLanguages,
85         StrictTLS,
86         Style,
87         ZoomLevel,
88         ParameterLast,
89 } ParamName;
90
91 typedef union {
92         int b;
93         int i;
94         float f;
95         const void *v;
96 } Arg;
97
98 typedef struct {
99         Arg val;
100         int force;
101 } Parameter;
102
103 typedef struct Client {
104         GtkWidget *win;
105         WebKitWebView *view;
106         WebKitWebInspector *inspector;
107         WebKitFindController *finder;
108         WebKitHitTestResult *mousepos;
109         GTlsCertificateFlags tlserr;
110         Window xid;
111         int progress, fullscreen, https, insecure;
112         const char *title, *overtitle, *targeturi;
113         const char *needle;
114         struct Client *next;
115 } Client;
116
117 typedef struct {
118         guint mod;
119         guint keyval;
120         void (*func)(Client *c, const Arg *a);
121         const Arg arg;
122 } Key;
123
124 typedef struct {
125         unsigned int target;
126         unsigned int mask;
127         guint button;
128         void (*func)(Client *c, const Arg *a, WebKitHitTestResult *h);
129         const Arg arg;
130         unsigned int stopevent;
131 } Button;
132
133 typedef struct {
134         const char *uri;
135         Parameter config[ParameterLast];
136         regex_t re;
137 } UriParameters;
138
139 typedef struct {
140         char *regex;
141         char *file;
142         regex_t re;
143 } SiteSpecific;
144
145 /* Surf */
146 static void usage(void);
147 static void die(const char *errstr, ...);
148 static void setup(void);
149 static void sigchld(int unused);
150 static void sighup(int unused);
151 static char *buildfile(const char *path);
152 static char *buildpath(const char *path);
153 static const char *getuserhomedir(const char *user);
154 static const char *getcurrentuserhomedir(void);
155 static Client *newclient(Client *c);
156 static void loaduri(Client *c, const Arg *a);
157 static const char *geturi(Client *c);
158 static void setatom(Client *c, int a, const char *v);
159 static const char *getatom(Client *c, int a);
160 static void updatetitle(Client *c);
161 static void gettogglestats(Client *c);
162 static void getpagestats(Client *c);
163 static WebKitCookieAcceptPolicy cookiepolicy_get(void);
164 static char cookiepolicy_set(const WebKitCookieAcceptPolicy p);
165 static void seturiparameters(Client *c, const char *uri);
166 static void setparameter(Client *c, int refresh, ParamName p, const Arg *a);
167 static const char *getcert(const char *uri);
168 static void setcert(Client *c, const char *file);
169 static const char *getstyle(const char *uri);
170 static void setstyle(Client *c, const char *file);
171 static void runscript(Client *c);
172 static void evalscript(Client *c, const char *jsstr, ...);
173 static void updatewinid(Client *c);
174 static void handleplumb(Client *c, const char *uri);
175 static void newwindow(Client *c, const Arg *a, int noembed);
176 static void spawn(Client *c, const Arg *a);
177 static void destroyclient(Client *c);
178 static void cleanup(void);
179
180 /* GTK/WebKit */
181 static WebKitWebView *newview(Client *c, WebKitWebView *rv);
182 static void initwebextensions(WebKitWebContext *wc, Client *c);
183 static GtkWidget *createview(WebKitWebView *v, WebKitNavigationAction *a,
184                              Client *c);
185 static gboolean buttonreleased(GtkWidget *w, GdkEvent *e, Client *c);
186 static GdkFilterReturn processx(GdkXEvent *xevent, GdkEvent *event,
187                                 gpointer d);
188 static gboolean winevent(GtkWidget *w, GdkEvent *e, Client *c);
189 static void showview(WebKitWebView *v, Client *c);
190 static GtkWidget *createwindow(Client *c);
191 static gboolean loadfailedtls(WebKitWebView *v, gchar *uri,
192                               GTlsCertificate *cert,
193                               GTlsCertificateFlags err, Client *c);
194 static void loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c);
195 static void progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c);
196 static void titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c);
197 static void mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h,
198                                guint modifiers, Client *c);
199 static gboolean permissionrequested(WebKitWebView *v,
200                                     WebKitPermissionRequest *r, Client *c);
201 static gboolean decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
202                              WebKitPolicyDecisionType dt, Client *c);
203 static void decidenavigation(WebKitPolicyDecision *d, Client *c);
204 static void decidenewwindow(WebKitPolicyDecision *d, Client *c);
205 static void decideresource(WebKitPolicyDecision *d, Client *c);
206 static void insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e,
207                             Client *c);
208 static void downloadstarted(WebKitWebContext *wc, WebKitDownload *d,
209                             Client *c);
210 static void responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c);
211 static void download(Client *c, WebKitURIResponse *r);
212 static void closeview(WebKitWebView *v, Client *c);
213 static void destroywin(GtkWidget* w, Client *c);
214
215 /* Hotkeys */
216 static void pasteuri(GtkClipboard *clipboard, const char *text, gpointer d);
217 static void reload(Client *c, const Arg *a);
218 static void print(Client *c, const Arg *a);
219 static void clipboard(Client *c, const Arg *a);
220 static void zoom(Client *c, const Arg *a);
221 static void scroll(Client *c, const Arg *a);
222 static void navigate(Client *c, const Arg *a);
223 static void stop(Client *c, const Arg *a);
224 static void toggle(Client *c, const Arg *a);
225 static void togglefullscreen(Client *c, const Arg *a);
226 static void togglecookiepolicy(Client *c, const Arg *a);
227 static void toggleinspector(Client *c, const Arg *a);
228 static void find(Client *c, const Arg *a);
229
230 /* Buttons */
231 static void clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h);
232 static void clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h);
233 static void clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h);
234
235 static char winid[64];
236 static char togglestats[11];
237 static char pagestats[2];
238 static Atom atoms[AtomLast];
239 static Window embed;
240 static int showxid;
241 static int cookiepolicy;
242 static Display *dpy;
243 static Client *clients;
244 static GdkDevice *gdkkb;
245 static char *stylefile;
246 static const char *useragent;
247 static Parameter *curconfig;
248 char *argv0;
249
250 /* configuration, allows nested code to access above variables */
251 #include "config.h"
252
253 void
254 usage(void)
255 {
256         die("usage: %s [-bBdDfFgGiIkKmMnNpPsStTvx] [-a cookiepolicies ] "
257             "[-c cookiefile] [-C stylefile] [-e xid] [-r scriptfile] "
258             "[-u useragent] [-z zoomlevel] [uri]\n", basename(argv0));
259 }
260
261 void
262 die(const char *errstr, ...)
263 {
264         va_list ap;
265
266         va_start(ap, errstr);
267         vfprintf(stderr, errstr, ap);
268         va_end(ap);
269         exit(1);
270 }
271
272 void
273 setup(void)
274 {
275         GdkDisplay *gdpy;
276         int i, j;
277
278         /* clean up any zombies immediately */
279         sigchld(0);
280         if (signal(SIGHUP, sighup) == SIG_ERR)
281                 die("Can't install SIGHUP handler");
282
283         if (!(dpy = XOpenDisplay(NULL)))
284                 die("Can't open default display");
285
286         /* atoms */
287         atoms[AtomFind] = XInternAtom(dpy, "_SURF_FIND", False);
288         atoms[AtomGo] = XInternAtom(dpy, "_SURF_GO", False);
289         atoms[AtomUri] = XInternAtom(dpy, "_SURF_URI", False);
290
291         gtk_init(NULL, NULL);
292
293         gdpy = gdk_display_get_default();
294
295         curconfig = defconfig;
296
297         /* dirs and files */
298         cookiefile = buildfile(cookiefile);
299         scriptfile = buildfile(scriptfile);
300         cachedir   = buildpath(cachedir);
301         certdir    = buildpath(certdir);
302
303         gdkkb = gdk_seat_get_keyboard(gdk_display_get_default_seat(gdpy));
304
305         for (i = 0; i < LENGTH(certs); ++i) {
306                 if (!regcomp(&(certs[i].re), certs[i].regex, REG_EXTENDED)) {
307                         certs[i].file = g_strconcat(certdir, "/", certs[i].file,
308                                                     NULL);
309                 } else {
310                         fprintf(stderr, "Could not compile regex: %s\n",
311                                 certs[i].regex);
312                         certs[i].regex = NULL;
313                 }
314         }
315
316         if (!stylefile) {
317                 styledir = buildpath(styledir);
318                 for (i = 0; i < LENGTH(styles); ++i) {
319                         if (!regcomp(&(styles[i].re), styles[i].regex,
320                             REG_EXTENDED)) {
321                                 styles[i].file = g_strconcat(styledir, "/",
322                                                     styles[i].file, NULL);
323                         } else {
324                                 fprintf(stderr, "Could not compile regex: %s\n",
325                                         styles[i].regex);
326                                 styles[i].regex = NULL;
327                         }
328                 }
329                 g_free(styledir);
330         } else {
331                 stylefile = buildfile(stylefile);
332         }
333
334         for (i = 0; i < LENGTH(uriparams); ++i) {
335                 if (!regcomp(&(uriparams[i].re), uriparams[i].uri,
336                     REG_EXTENDED)) {
337                         /* copy default parameters if they are not already set
338                          * or if they are forced */
339                         for (j = 0; j < ParameterLast; ++j) {
340                                 if (!uriparams[i].config[j].force ||
341                                     defconfig[j].force)
342                                         uriparams[i].config[j] = defconfig[j];
343                         }
344                 } else {
345                         fprintf(stderr, "Could not compile regex: %s\n",
346                                 uriparams[i].uri);
347                         uriparams[i].uri = NULL;
348                 }
349         }
350 }
351
352 void
353 sigchld(int unused)
354 {
355         if (signal(SIGCHLD, sigchld) == SIG_ERR)
356                 die("Can't install SIGCHLD handler");
357         while (waitpid(-1, NULL, WNOHANG) > 0)
358                 ;
359 }
360
361 void
362 sighup(int unused)
363 {
364         Arg a = { .b = 0 };
365         Client *c;
366
367         for (c = clients; c; c = c->next)
368                 reload(c, &a);
369 }
370
371 char *
372 buildfile(const char *path)
373 {
374         char *dname, *bname, *bpath, *fpath;
375         FILE *f;
376
377         dname = g_path_get_dirname(path);
378         bname = g_path_get_basename(path);
379
380         bpath = buildpath(dname);
381         g_free(dname);
382
383         fpath = g_build_filename(bpath, bname, NULL);
384         g_free(bpath);
385         g_free(bname);
386
387         if (!(f = fopen(fpath, "a")))
388                 die("Could not open file: %s\n", fpath);
389
390         g_chmod(fpath, 0600); /* always */
391         fclose(f);
392
393         return fpath;
394 }
395
396 static const char*
397 getuserhomedir(const char *user)
398 {
399         struct passwd *pw = getpwnam(user);
400
401         if (!pw)
402                 die("Can't get user %s login information.\n", user);
403
404         return pw->pw_dir;
405 }
406
407 static const char*
408 getcurrentuserhomedir(void)
409 {
410         const char *homedir;
411         const char *user;
412         struct passwd *pw;
413
414         homedir = getenv("HOME");
415         if (homedir)
416                 return homedir;
417
418         user = getenv("USER");
419         if (user)
420                 return getuserhomedir(user);
421
422         pw = getpwuid(getuid());
423         if (!pw)
424                 die("Can't get current user home directory\n");
425
426         return pw->pw_dir;
427 }
428
429 char *
430 buildpath(const char *path)
431 {
432         char *apath, *name, *p, *fpath;
433         const char *homedir;
434
435         if (path[0] == '~') {
436                 if (path[1] == '/' || path[1] == '\0') {
437                         p = (char *)&path[1];
438                         homedir = getcurrentuserhomedir();
439                 } else {
440                         if ((p = strchr(path, '/')))
441                                 name = g_strndup(&path[1], --p - path);
442                         else
443                                 name = g_strdup(&path[1]);
444
445                         homedir = getuserhomedir(name);
446                         g_free(name);
447                 }
448                 apath = g_build_filename(homedir, p, NULL);
449         } else {
450                 apath = g_strdup(path);
451         }
452
453         /* creating directory */
454         if (g_mkdir_with_parents(apath, 0700) < 0)
455                 die("Could not access directory: %s\n", apath);
456
457         fpath = realpath(apath, NULL);
458         g_free(apath);
459
460         return fpath;
461 }
462
463 Client *
464 newclient(Client *rc)
465 {
466         Client *c;
467
468         if (!(c = calloc(1, sizeof(Client))))
469                 die("Cannot malloc!\n");
470
471         c->next = clients;
472         clients = c;
473
474         c->progress = 100;
475         c->view = newview(c, rc ? rc->view : NULL);
476
477         return c;
478 }
479
480 void
481 loaduri(Client *c, const Arg *a)
482 {
483         struct stat st;
484         char *url, *path;
485         const char *uri = a->v;
486
487         if (g_strcmp0(uri, "") == 0)
488                 return;
489
490         if (g_str_has_prefix(uri, "http://")  ||
491             g_str_has_prefix(uri, "https://") ||
492             g_str_has_prefix(uri, "file://")  ||
493             g_str_has_prefix(uri, "about:")) {
494                 url = g_strdup(uri);
495         } else if (!stat(uri, &st) && (path = realpath(uri, NULL))) {
496                 url = g_strdup_printf("file://%s", path);
497                 free(path);
498         } else {
499                 url = g_strdup_printf("http://%s", uri);
500         }
501
502         setatom(c, AtomUri, url);
503
504         if (strcmp(url, geturi(c)) == 0) {
505                 reload(c, a);
506         } else {
507                 webkit_web_view_load_uri(c->view, url);
508                 updatetitle(c);
509         }
510
511         g_free(url);
512 }
513
514 const char *
515 geturi(Client *c)
516 {
517         const char *uri;
518
519         if (!(uri = webkit_web_view_get_uri(c->view)))
520                 uri = "about:blank";
521         return uri;
522 }
523
524 void
525 setatom(Client *c, int a, const char *v)
526 {
527         XSync(dpy, False);
528         XChangeProperty(dpy, c->xid,
529                         atoms[a], XA_STRING, 8, PropModeReplace,
530                         (unsigned char *)v, strlen(v) + 1);
531 }
532
533 const char *
534 getatom(Client *c, int a)
535 {
536         static char buf[BUFSIZ];
537         Atom adummy;
538         int idummy;
539         unsigned long ldummy;
540         unsigned char *p = NULL;
541
542         XGetWindowProperty(dpy, c->xid, atoms[a], 0L, BUFSIZ, False, XA_STRING,
543                            &adummy, &idummy, &ldummy, &ldummy, &p);
544         if (p)
545                 strncpy(buf, (char *)p, LENGTH(buf) - 1);
546         else
547                 buf[0] = '\0';
548         XFree(p);
549
550         return buf;
551 }
552
553 void
554 updatetitle(Client *c)
555 {
556         char *title;
557         const char *name = c->overtitle ? c->overtitle :
558                            c->title ? c->title : "";
559
560         if (curconfig[ShowIndicators].val.b) {
561                 gettogglestats(c);
562                 getpagestats(c);
563
564                 if (c->progress != 100)
565                         title = g_strdup_printf("[%i%%] %s:%s | %s",
566                                 c->progress, togglestats, pagestats, name);
567                 else
568                         title = g_strdup_printf("%s:%s | %s",
569                                 togglestats, pagestats, name);
570
571                 gtk_window_set_title(GTK_WINDOW(c->win), title);
572                 g_free(title);
573         } else {
574                 gtk_window_set_title(GTK_WINDOW(c->win), name);
575         }
576 }
577
578 void
579 gettogglestats(Client *c)
580 {
581         togglestats[0] = cookiepolicy_set(cookiepolicy_get());
582         togglestats[1] = curconfig[CaretBrowsing].val.b ?   'C' : 'c';
583         togglestats[2] = curconfig[Geolocation].val.b ?     'G' : 'g';
584         togglestats[3] = curconfig[DiskCache].val.b ?       'D' : 'd';
585         togglestats[4] = curconfig[LoadImages].val.b ?      'I' : 'i';
586         togglestats[5] = curconfig[JavaScript].val.b ?      'S' : 's';
587         togglestats[6] = curconfig[Plugins].val.b ?         'V' : 'v';
588         togglestats[7] = curconfig[Style].val.b ?           'M' : 'm';
589         togglestats[8] = curconfig[FrameFlattening].val.b ? 'F' : 'f';
590         togglestats[9] = curconfig[StrictTLS].val.b ?       'T' : 't';
591         togglestats[10] = '\0';
592 }
593
594 void
595 getpagestats(Client *c)
596 {
597         if (c->https)
598                 pagestats[0] = (c->tlserr || c->insecure) ?  'U' : 'T';
599         else
600                 pagestats[0] = '-';
601         pagestats[1] = '\0';
602 }
603
604 WebKitCookieAcceptPolicy
605 cookiepolicy_get(void)
606 {
607         switch (((char *)curconfig[CookiePolicies].val.v)[cookiepolicy]) {
608         case 'a':
609                 return WEBKIT_COOKIE_POLICY_ACCEPT_NEVER;
610         case '@':
611                 return WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY;
612         default: /* fallthrough */
613         case 'A':
614                 return WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS;
615         }
616 }
617
618 char
619 cookiepolicy_set(const WebKitCookieAcceptPolicy p)
620 {
621         switch (p) {
622         case WEBKIT_COOKIE_POLICY_ACCEPT_NEVER:
623                 return 'a';
624         case WEBKIT_COOKIE_POLICY_ACCEPT_NO_THIRD_PARTY:
625                 return '@';
626         default: /* fallthrough */
627         case WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS:
628                 return 'A';
629         }
630 }
631
632 void
633 seturiparameters(Client *c, const char *uri)
634 {
635         int i;
636
637         for (i = 0; i < LENGTH(uriparams); ++i) {
638                 if (uriparams[i].uri &&
639                     !regexec(&(uriparams[i].re), uri, 0, NULL, 0)) {
640                         curconfig = uriparams[i].config;
641                         break;
642                 }
643         }
644
645         for (i = 0; i < ParameterLast; ++i)
646                 setparameter(c, 0, i, &curconfig[i].val);
647 }
648
649 void
650 setparameter(Client *c, int refresh, ParamName p, const Arg *a)
651 {
652         GdkRGBA bgcolor = { 0 };
653         WebKitSettings *s = webkit_web_view_get_settings(c->view);
654
655         switch (p) {
656         case AcceleratedCanvas:
657                 webkit_settings_set_enable_accelerated_2d_canvas(s, a->b);
658                 break;
659         case CaretBrowsing:
660                 webkit_settings_set_enable_caret_browsing(s, a->b);
661                 refresh = 0;
662                 break;
663         case Certificate:
664                 if (a->b)
665                         setcert(c, geturi(c));
666                 return; /* do not update */
667         case CookiePolicies:
668                 webkit_cookie_manager_set_accept_policy(
669                     webkit_web_context_get_cookie_manager(
670                     webkit_web_view_get_context(c->view)),
671                     cookiepolicy_get());
672                 refresh = 0;
673                 break;
674         case DiskCache:
675                 webkit_web_context_set_cache_model(
676                     webkit_web_view_get_context(c->view), a->b ?
677                     WEBKIT_CACHE_MODEL_WEB_BROWSER :
678                     WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
679                 return; /* do not update */
680         case DNSPrefetch:
681                 webkit_settings_set_enable_dns_prefetching(s, a->b);
682                 return; /* do not update */
683         case FontSize:
684                 webkit_settings_set_default_font_size(s, a->i);
685                 return; /* do not update */
686         case FrameFlattening:
687                 webkit_settings_set_enable_frame_flattening(s, a->b);
688                 break;
689         case Geolocation:
690                 refresh = 0;
691                 break;
692         case HideBackground:
693                 if (a->b)
694                         webkit_web_view_set_background_color(c->view, &bgcolor);
695                 return; /* do not update */
696         case Inspector:
697                 webkit_settings_set_enable_developer_extras(s, a->b);
698                 return; /* do not update */
699         case JavaScript:
700                 webkit_settings_set_enable_javascript(s, a->b);
701                 break;
702         case KioskMode:
703                 return; /* do nothing */
704         case LoadImages:
705                 webkit_settings_set_auto_load_images(s, a->b);
706                 break;
707         case MediaManualPlay:
708                 webkit_settings_set_media_playback_requires_user_gesture(s, a->b);
709                 break;
710         case Plugins:
711                 webkit_settings_set_enable_plugins(s, a->b);
712                 break;
713         case PreferredLanguages:
714                 return; /* do nothing */
715         case RunInFullscreen:
716                 return; /* do nothing */
717         case ScrollBars:
718                 /* Disabled until we write some WebKitWebExtension for
719                  * manipulating the DOM directly.
720                 enablescrollbars = !enablescrollbars;
721                 evalscript(c, "document.documentElement.style.overflow = '%s'",
722                     enablescrollbars ? "auto" : "hidden");
723                 */
724                 return; /* do not update */
725         case ShowIndicators:
726                 break;
727         case SiteQuirks:
728                 webkit_settings_set_enable_site_specific_quirks(s, a->b);
729                 break;
730         case SpellChecking:
731                 webkit_web_context_set_spell_checking_enabled(
732                     webkit_web_view_get_context(c->view), a->b);
733                 return; /* do not update */
734         case SpellLanguages:
735                 return; /* do nothing */
736         case StrictTLS:
737                 webkit_web_context_set_tls_errors_policy(
738                     webkit_web_view_get_context(c->view), a->b ?
739                     WEBKIT_TLS_ERRORS_POLICY_FAIL :
740                     WEBKIT_TLS_ERRORS_POLICY_IGNORE);
741                 break;
742         case Style:
743                 if (a->b)
744                         setstyle(c, getstyle(geturi(c)));
745                 else
746                         webkit_user_content_manager_remove_all_style_sheets(
747                             webkit_web_view_get_user_content_manager(c->view));
748                 refresh = 0;
749                 break;
750         case ZoomLevel:
751                 webkit_web_view_set_zoom_level(c->view, a->f);
752                 return; /* do not update */
753         default:
754                 return; /* do nothing */
755         }
756
757         updatetitle(c);
758         if (refresh)
759                 reload(c, a);
760 }
761
762 const char *
763 getcert(const char *uri)
764 {
765         int i;
766
767         for (i = 0; i < LENGTH(certs); ++i) {
768                 if (certs[i].regex &&
769                     !regexec(&(certs[i].re), uri, 0, NULL, 0))
770                         return certs[i].file;
771         }
772
773         return NULL;
774 }
775
776 void
777 setcert(Client *c, const char *uri)
778 {
779         const char *file = getcert(uri);
780         char *host;
781         GTlsCertificate *cert;
782
783         if (!file)
784                 return;
785
786         if (!(cert = g_tls_certificate_new_from_file(file, NULL))) {
787                 fprintf(stderr, "Could not read certificate file: %s\n", file);
788                 return;
789         }
790
791         uri = strstr(uri, "://") + sizeof("://") - 1;
792         host = strndup(uri, strstr(uri, "/") - uri);
793
794         webkit_web_context_allow_tls_certificate_for_host(
795             webkit_web_view_get_context(c->view), cert, host);
796         g_object_unref(cert);
797
798         free(host);
799 }
800
801 const char *
802 getstyle(const char *uri)
803 {
804         int i;
805
806         if (stylefile)
807                 return stylefile;
808
809         for (i = 0; i < LENGTH(styles); ++i) {
810                 if (styles[i].regex &&
811                     !regexec(&(styles[i].re), uri, 0, NULL, 0))
812                         return styles[i].file;
813         }
814
815         return "";
816 }
817
818 void
819 setstyle(Client *c, const char *file)
820 {
821         gchar *style;
822
823         if (!g_file_get_contents(file, &style, NULL, NULL)) {
824                 fprintf(stderr, "Could not read style file: %s\n", file);
825                 return;
826         }
827
828         webkit_user_content_manager_add_style_sheet(
829             webkit_web_view_get_user_content_manager(c->view),
830             webkit_user_style_sheet_new(style,
831             WEBKIT_USER_CONTENT_INJECT_ALL_FRAMES,
832             WEBKIT_USER_STYLE_LEVEL_USER,
833             NULL, NULL));
834
835         g_free(style);
836 }
837
838 void
839 runscript(Client *c)
840 {
841         gchar *script;
842         gsize l;
843
844         if (g_file_get_contents(scriptfile, &script, &l, NULL) && l)
845                 evalscript(c, script);
846         g_free(script);
847 }
848
849 void
850 evalscript(Client *c, const char *jsstr, ...)
851 {
852         va_list ap;
853         gchar *script;
854
855         va_start(ap, jsstr);
856         script = g_strdup_vprintf(jsstr, ap);
857         va_end(ap);
858
859         webkit_web_view_run_javascript(c->view, script, NULL, NULL, NULL);
860         g_free(script);
861 }
862
863 void
864 updatewinid(Client *c)
865 {
866         snprintf(winid, LENGTH(winid), "%lu", c->xid);
867 }
868
869 void
870 handleplumb(Client *c, const char *uri)
871 {
872         Arg a = (Arg)PLUMB(uri);
873         spawn(c, &a);
874 }
875
876 void
877 newwindow(Client *c, const Arg *a, int noembed)
878 {
879         int i = 0;
880         char tmp[64];
881         const char *cmd[28], *uri;
882         const Arg arg = { .v = cmd };
883
884         cmd[i++] = argv0;
885         cmd[i++] = "-a";
886         cmd[i++] = curconfig[CookiePolicies].val.v;
887         cmd[i++] = curconfig[ScrollBars].val.b ? "-B" : "-b";
888         if (cookiefile && g_strcmp0(cookiefile, "")) {
889                 cmd[i++] = "-c";
890                 cmd[i++] = cookiefile;
891         }
892         if (stylefile && g_strcmp0(stylefile, "")) {
893                 cmd[i++] = "-C";
894                 cmd[i++] = stylefile;
895         }
896         cmd[i++] = curconfig[DiskCache].val.b ? "-D" : "-d";
897         if (embed && !noembed) {
898                 cmd[i++] = "-e";
899                 snprintf(tmp, LENGTH(tmp), "%lu", embed);
900                 cmd[i++] = tmp;
901         }
902         cmd[i++] = curconfig[RunInFullscreen].val.b ? "-F" : "-f" ;
903         cmd[i++] = curconfig[Geolocation].val.b ?     "-G" : "-g" ;
904         cmd[i++] = curconfig[LoadImages].val.b ?      "-I" : "-i" ;
905         cmd[i++] = curconfig[KioskMode].val.b ?       "-K" : "-k" ;
906         cmd[i++] = curconfig[Style].val.b ?           "-M" : "-m" ;
907         cmd[i++] = curconfig[Inspector].val.b ?       "-N" : "-n" ;
908         cmd[i++] = curconfig[Plugins].val.b ?         "-P" : "-p" ;
909         if (scriptfile && g_strcmp0(scriptfile, "")) {
910                 cmd[i++] = "-r";
911                 cmd[i++] = scriptfile;
912         }
913         cmd[i++] = curconfig[JavaScript].val.b ? "-S" : "-s";
914         cmd[i++] = curconfig[StrictTLS].val.b ? "-T" : "-t";
915         if (fulluseragent && g_strcmp0(fulluseragent, "")) {
916                 cmd[i++] = "-u";
917                 cmd[i++] = fulluseragent;
918         }
919         if (showxid)
920                 cmd[i++] = "-x";
921         /* do not keep zoom level */
922         cmd[i++] = "--";
923         if ((uri = a->v))
924                 cmd[i++] = uri;
925         cmd[i] = NULL;
926
927         spawn(c, &arg);
928 }
929
930 void
931 spawn(Client *c, const Arg *a)
932 {
933         if (fork() == 0) {
934                 if (dpy)
935                         close(ConnectionNumber(dpy));
936                 setsid();
937                 execvp(((char **)a->v)[0], (char **)a->v);
938                 fprintf(stderr, "%s: execvp %s", argv0, ((char **)a->v)[0]);
939                 perror(" failed");
940                 exit(1);
941         }
942 }
943
944 void
945 destroyclient(Client *c)
946 {
947         Client *p;
948
949         webkit_web_view_stop_loading(c->view);
950         /* Not needed, has already been called
951         gtk_widget_destroy(c->win);
952          */
953
954         for (p = clients; p && p->next != c; p = p->next)
955                 ;
956         if (p)
957                 p->next = c->next;
958         else
959                 clients = c->next;
960         free(c);
961 }
962
963 void
964 cleanup(void)
965 {
966         while (clients)
967                 destroyclient(clients);
968         g_free(cookiefile);
969         g_free(scriptfile);
970         g_free(stylefile);
971         g_free(cachedir);
972         XCloseDisplay(dpy);
973 }
974
975 WebKitWebView *
976 newview(Client *c, WebKitWebView *rv)
977 {
978         WebKitWebView *v;
979         WebKitSettings *settings;
980         WebKitUserContentManager *contentmanager;
981         WebKitWebContext *context;
982
983         /* Webview */
984         if (rv) {
985                 v = WEBKIT_WEB_VIEW(
986                     webkit_web_view_new_with_related_view(rv));
987         } else {
988                 settings = webkit_settings_new_with_settings(
989                    "auto-load-images", curconfig[LoadImages].val.b,
990                    "default-font-size", curconfig[FontSize].val.i,
991                    "enable-caret-browsing", curconfig[CaretBrowsing].val.b,
992                    "enable-developer-extras", curconfig[Inspector].val.b,
993                    "enable-dns-prefetching", curconfig[DNSPrefetch].val.b,
994                    "enable-frame-flattening", curconfig[FrameFlattening].val.b,
995                    "enable-html5-database", curconfig[DiskCache].val.b,
996                    "enable-html5-local-storage", curconfig[DiskCache].val.b,
997                    "enable-javascript", curconfig[JavaScript].val.b,
998                    "enable-plugins", curconfig[Plugins].val.b,
999                    "enable-accelerated-2d-canvas", curconfig[AcceleratedCanvas].val.b,
1000                    "enable-site-specific-quirks", curconfig[SiteQuirks].val.b,
1001                    "media-playback-requires-user-gesture", curconfig[MediaManualPlay].val.b,
1002                    NULL);
1003 /* For mor interesting settings, have a look at
1004  * http://webkitgtk.org/reference/webkit2gtk/stable/WebKitSettings.html */
1005
1006                 if (strcmp(fulluseragent, "")) {
1007                         webkit_settings_set_user_agent(settings, fulluseragent);
1008                 } else if (surfuseragent) {
1009                         webkit_settings_set_user_agent_with_application_details(
1010                             settings, "Surf", VERSION);
1011                 }
1012                 useragent = webkit_settings_get_user_agent(settings);
1013
1014                 contentmanager = webkit_user_content_manager_new();
1015
1016                 context = webkit_web_context_new_with_website_data_manager(
1017                           webkit_website_data_manager_new(
1018                           "base-cache-directory", cachedir,
1019                           "base-data-directory", cachedir,
1020                           NULL));
1021
1022                 /* rendering process model, can be a shared unique one
1023                  * or one for each view */
1024                 webkit_web_context_set_process_model(context,
1025                     WEBKIT_PROCESS_MODEL_MULTIPLE_SECONDARY_PROCESSES);
1026                 /* TLS */
1027                 webkit_web_context_set_tls_errors_policy(context,
1028                     curconfig[StrictTLS].val.b ? WEBKIT_TLS_ERRORS_POLICY_FAIL :
1029                     WEBKIT_TLS_ERRORS_POLICY_IGNORE);
1030                 /* disk cache */
1031                 webkit_web_context_set_cache_model(context,
1032                     curconfig[DiskCache].val.b ? WEBKIT_CACHE_MODEL_WEB_BROWSER :
1033                     WEBKIT_CACHE_MODEL_DOCUMENT_VIEWER);
1034
1035                 /* Currently only works with text file to be compatible with curl */
1036                 webkit_cookie_manager_set_persistent_storage(
1037                     webkit_web_context_get_cookie_manager(context), cookiefile,
1038                     WEBKIT_COOKIE_PERSISTENT_STORAGE_TEXT);
1039                 /* cookie policy */
1040                 webkit_cookie_manager_set_accept_policy(
1041                     webkit_web_context_get_cookie_manager(context),
1042                     cookiepolicy_get());
1043                 /* languages */
1044                 webkit_web_context_set_preferred_languages(context,
1045                     curconfig[PreferredLanguages].val.v);
1046                 webkit_web_context_set_spell_checking_languages(context,
1047                     curconfig[SpellLanguages].val.v);
1048                 webkit_web_context_set_spell_checking_enabled(context,
1049                     curconfig[SpellChecking].val.b);
1050
1051                 g_signal_connect(G_OBJECT(context), "download-started",
1052                                  G_CALLBACK(downloadstarted), c);
1053                 g_signal_connect(G_OBJECT(context), "initialize-web-extensions",
1054                                  G_CALLBACK(initwebextensions), c);
1055
1056                 v = g_object_new(WEBKIT_TYPE_WEB_VIEW,
1057                     "settings", settings,
1058                     "user-content-manager", contentmanager,
1059                     "web-context", context,
1060                     NULL);
1061         }
1062
1063         g_signal_connect(G_OBJECT(v), "notify::estimated-load-progress",
1064                          G_CALLBACK(progresschanged), c);
1065         g_signal_connect(G_OBJECT(v), "notify::title",
1066                          G_CALLBACK(titlechanged), c);
1067         g_signal_connect(G_OBJECT(v), "button-release-event",
1068                          G_CALLBACK(buttonreleased), c);
1069         g_signal_connect(G_OBJECT(v), "close",
1070                         G_CALLBACK(closeview), c);
1071         g_signal_connect(G_OBJECT(v), "create",
1072                          G_CALLBACK(createview), c);
1073         g_signal_connect(G_OBJECT(v), "decide-policy",
1074                          G_CALLBACK(decidepolicy), c);
1075         g_signal_connect(G_OBJECT(v), "insecure-content-detected",
1076                          G_CALLBACK(insecurecontent), c);
1077         g_signal_connect(G_OBJECT(v), "load-failed-with-tls-errors",
1078                          G_CALLBACK(loadfailedtls), c);
1079         g_signal_connect(G_OBJECT(v), "load-changed",
1080                          G_CALLBACK(loadchanged), c);
1081         g_signal_connect(G_OBJECT(v), "mouse-target-changed",
1082                          G_CALLBACK(mousetargetchanged), c);
1083         g_signal_connect(G_OBJECT(v), "permission-request",
1084                          G_CALLBACK(permissionrequested), c);
1085         g_signal_connect(G_OBJECT(v), "ready-to-show",
1086                          G_CALLBACK(showview), c);
1087
1088         return v;
1089 }
1090
1091 void
1092 initwebextensions(WebKitWebContext *wc, Client *c)
1093 {
1094         webkit_web_context_set_web_extensions_directory(wc, WEBEXTDIR);
1095 }
1096
1097 GtkWidget *
1098 createview(WebKitWebView *v, WebKitNavigationAction *a, Client *c)
1099 {
1100         Client *n;
1101
1102         switch (webkit_navigation_action_get_navigation_type(a)) {
1103         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1104                 /*
1105                  * popup windows of type “other” are almost always triggered
1106                  * by user gesture, so inverse the logic here
1107                  */
1108 /* instead of this, compare destination uri to mouse-over uri for validating window */
1109                 if (webkit_navigation_action_is_user_gesture(a))
1110                         return NULL;
1111         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1112         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1113         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1114         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1115         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1116                 n = newclient(c);
1117                 break;
1118         default:
1119                 return NULL;
1120         }
1121
1122         return GTK_WIDGET(n->view);
1123 }
1124
1125 gboolean
1126 buttonreleased(GtkWidget *w, GdkEvent *e, Client *c)
1127 {
1128         WebKitHitTestResultContext element;
1129         int i;
1130
1131         element = webkit_hit_test_result_get_context(c->mousepos);
1132
1133         for (i = 0; i < LENGTH(buttons); ++i) {
1134                 if (element & buttons[i].target &&
1135                     e->button.button == buttons[i].button &&
1136                     CLEANMASK(e->button.state) == CLEANMASK(buttons[i].mask) &&
1137                     buttons[i].func) {
1138                         buttons[i].func(c, &buttons[i].arg, c->mousepos);
1139                         return buttons[i].stopevent;
1140                 }
1141         }
1142
1143         return FALSE;
1144 }
1145
1146 GdkFilterReturn
1147 processx(GdkXEvent *e, GdkEvent *event, gpointer d)
1148 {
1149         Client *c = (Client *)d;
1150         XPropertyEvent *ev;
1151         Arg a;
1152
1153         if (((XEvent *)e)->type == PropertyNotify) {
1154                 ev = &((XEvent *)e)->xproperty;
1155                 if (ev->state == PropertyNewValue) {
1156                         if (ev->atom == atoms[AtomFind]) {
1157                                 find(c, NULL);
1158
1159                                 return GDK_FILTER_REMOVE;
1160                         } else if (ev->atom == atoms[AtomGo]) {
1161                                 a.v = getatom(c, AtomGo);
1162                                 loaduri(c, &a);
1163
1164                                 return GDK_FILTER_REMOVE;
1165                         }
1166                 }
1167         }
1168         return GDK_FILTER_CONTINUE;
1169 }
1170
1171 gboolean
1172 winevent(GtkWidget *w, GdkEvent *e, Client *c)
1173 {
1174         int i;
1175
1176         switch (e->type) {
1177         case GDK_ENTER_NOTIFY:
1178                 c->overtitle = c->targeturi;
1179                 updatetitle(c);
1180                 break;
1181         case GDK_KEY_PRESS:
1182                 if (!curconfig[KioskMode].val.b) {
1183                         for (i = 0; i < LENGTH(keys); ++i) {
1184                                 if (gdk_keyval_to_lower(e->key.keyval) ==
1185                                     keys[i].keyval &&
1186                                     CLEANMASK(e->key.state) == keys[i].mod &&
1187                                     keys[i].func) {
1188                                         updatewinid(c);
1189                                         keys[i].func(c, &(keys[i].arg));
1190                                         return TRUE;
1191                                 }
1192                         }
1193                 }
1194         case GDK_LEAVE_NOTIFY:
1195                 c->overtitle = NULL;
1196                 updatetitle(c);
1197                 break;
1198         case GDK_WINDOW_STATE:
1199                 if (e->window_state.changed_mask ==
1200                     GDK_WINDOW_STATE_FULLSCREEN)
1201                         c->fullscreen = e->window_state.new_window_state &
1202                                         GDK_WINDOW_STATE_FULLSCREEN;
1203                 break;
1204         default:
1205                 break;
1206         }
1207
1208         return FALSE;
1209 }
1210
1211 void
1212 showview(WebKitWebView *v, Client *c)
1213 {
1214         GdkRGBA bgcolor = { 0 };
1215         GdkWindow *gwin;
1216
1217         c->finder = webkit_web_view_get_find_controller(c->view);
1218         c->inspector = webkit_web_view_get_inspector(c->view);
1219
1220         c->win = createwindow(c);
1221
1222         gtk_container_add(GTK_CONTAINER(c->win), GTK_WIDGET(c->view));
1223         gtk_widget_show_all(c->win);
1224         gtk_widget_grab_focus(GTK_WIDGET(c->view));
1225
1226         gwin = gtk_widget_get_window(GTK_WIDGET(c->win));
1227         c->xid = gdk_x11_window_get_xid(gwin);
1228         updatewinid(c);
1229         if (showxid) {
1230                 gdk_display_sync(gtk_widget_get_display(c->win));
1231                 puts(winid);
1232         }
1233
1234         if (curconfig[HideBackground].val.b)
1235                 webkit_web_view_set_background_color(c->view, &bgcolor);
1236
1237         if (!curconfig[KioskMode].val.b) {
1238                 gdk_window_set_events(gwin, GDK_ALL_EVENTS_MASK);
1239                 gdk_window_add_filter(gwin, processx, c);
1240         }
1241
1242         if (curconfig[RunInFullscreen].val.b)
1243                 togglefullscreen(c, NULL);
1244
1245         if (curconfig[ZoomLevel].val.f != 1.0)
1246                 webkit_web_view_set_zoom_level(c->view,
1247                                                curconfig[ZoomLevel].val.f);
1248
1249         setatom(c, AtomFind, "");
1250         setatom(c, AtomUri, "about:blank");
1251 }
1252
1253 GtkWidget *
1254 createwindow(Client *c)
1255 {
1256         char *wmstr;
1257         GtkWidget *w;
1258
1259         if (embed) {
1260                 w = gtk_plug_new(embed);
1261         } else {
1262                 w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1263
1264                 wmstr = g_path_get_basename(argv0);
1265                 gtk_window_set_wmclass(GTK_WINDOW(w), wmstr, "Surf");
1266                 g_free(wmstr);
1267
1268                 wmstr = g_strdup_printf("%s[%lu]", "Surf",
1269                         webkit_web_view_get_page_id(c->view));
1270                 gtk_window_set_role(GTK_WINDOW(w), wmstr);
1271                 g_free(wmstr);
1272
1273                 gtk_window_set_default_size(GTK_WINDOW(w), winsize[0], winsize[1]);
1274         }
1275
1276         g_signal_connect(G_OBJECT(w), "destroy",
1277                          G_CALLBACK(destroywin), c);
1278         g_signal_connect(G_OBJECT(w), "enter-notify-event",
1279                          G_CALLBACK(winevent), c);
1280         g_signal_connect(G_OBJECT(w), "key-press-event",
1281                          G_CALLBACK(winevent), c);
1282         g_signal_connect(G_OBJECT(w), "leave-notify-event",
1283                          G_CALLBACK(winevent), c);
1284         g_signal_connect(G_OBJECT(w), "window-state-event",
1285                          G_CALLBACK(winevent), c);
1286
1287         return w;
1288 }
1289
1290 gboolean
1291 loadfailedtls(WebKitWebView *v, gchar *uri, GTlsCertificate *cert,
1292               GTlsCertificateFlags err, Client *c)
1293 {
1294         GString *errmsg = g_string_new(NULL);
1295         gchar *html, *pem;
1296
1297         c->tlserr = err;
1298
1299         if (err & G_TLS_CERTIFICATE_UNKNOWN_CA)
1300                 g_string_append(errmsg,
1301                     "The signing certificate authority is not known.<br>");
1302         if (err & G_TLS_CERTIFICATE_BAD_IDENTITY)
1303                 g_string_append(errmsg,
1304                     "The certificate does not match the expected identity "
1305                     "of the site that it was retrieved from.<br>");
1306         if (err & G_TLS_CERTIFICATE_NOT_ACTIVATED)
1307                 g_string_append(errmsg,
1308                     "The certificate's activation time "
1309                     "is still in the future.<br>");
1310         if (err & G_TLS_CERTIFICATE_EXPIRED)
1311                 g_string_append(errmsg, "The certificate has expired.<br>");
1312         if (err & G_TLS_CERTIFICATE_REVOKED)
1313                 g_string_append(errmsg,
1314                     "The certificate has been revoked according to "
1315                     "the GTlsConnection's certificate revocation list.<br>");
1316         if (err & G_TLS_CERTIFICATE_INSECURE)
1317                 g_string_append(errmsg,
1318                     "The certificate's algorithm is considered insecure.<br>");
1319         if (err & G_TLS_CERTIFICATE_GENERIC_ERROR)
1320                 g_string_append(errmsg,
1321                     "Some error occurred validating the certificate.<br>");
1322
1323         g_object_get(cert, "certificate-pem", &pem, NULL);
1324         html = g_strdup_printf("<p>Could not validate TLS for “%s”<br>%s</p>"
1325                                "<p><pre>%s</pre><p>", uri, errmsg->str, pem);
1326         g_free(pem);
1327         g_string_free(errmsg, TRUE);
1328
1329         webkit_web_view_load_alternate_html(c->view, html, uri, NULL);
1330         g_free(html);
1331
1332         return TRUE;
1333 }
1334
1335 void
1336 loadchanged(WebKitWebView *v, WebKitLoadEvent e, Client *c)
1337 {
1338         const char *title = geturi(c);
1339
1340         switch (e) {
1341         case WEBKIT_LOAD_STARTED:
1342                 curconfig = defconfig;
1343                 setatom(c, AtomUri, title);
1344                 c->title = title;
1345                 c->https = c->insecure = 0;
1346                 seturiparameters(c, geturi(c));
1347                 break;
1348         case WEBKIT_LOAD_REDIRECTED:
1349                 setatom(c, AtomUri, title);
1350                 c->title = title;
1351                 seturiparameters(c, geturi(c));
1352                 break;
1353         case WEBKIT_LOAD_COMMITTED:
1354                 c->https = webkit_web_view_get_tls_info(c->view, NULL,
1355                                                         &c->tlserr);
1356                 break;
1357         case WEBKIT_LOAD_FINISHED:
1358                 /* Disabled until we write some WebKitWebExtension for
1359                  * manipulating the DOM directly.
1360                 evalscript(c, "document.documentElement.style.overflow = '%s'",
1361                     enablescrollbars ? "auto" : "hidden");
1362                 */
1363                 runscript(c);
1364                 break;
1365         }
1366         updatetitle(c);
1367 }
1368
1369 void
1370 progresschanged(WebKitWebView *v, GParamSpec *ps, Client *c)
1371 {
1372         c->progress = webkit_web_view_get_estimated_load_progress(c->view) *
1373                       100;
1374         updatetitle(c);
1375 }
1376
1377 void
1378 titlechanged(WebKitWebView *view, GParamSpec *ps, Client *c)
1379 {
1380         c->title = webkit_web_view_get_title(c->view);
1381         updatetitle(c);
1382 }
1383
1384 void
1385 mousetargetchanged(WebKitWebView *v, WebKitHitTestResult *h, guint modifiers,
1386     Client *c)
1387 {
1388         WebKitHitTestResultContext hc = webkit_hit_test_result_get_context(h);
1389
1390         /* Keep the hit test to know where is the pointer on the next click */
1391         c->mousepos = h;
1392
1393         if (hc & OnLink)
1394                 c->targeturi = webkit_hit_test_result_get_link_uri(h);
1395         else if (hc & OnImg)
1396                 c->targeturi = webkit_hit_test_result_get_image_uri(h);
1397         else if (hc & OnMedia)
1398                 c->targeturi = webkit_hit_test_result_get_media_uri(h);
1399         else
1400                 c->targeturi = NULL;
1401
1402         c->overtitle = c->targeturi;
1403         updatetitle(c);
1404 }
1405
1406 gboolean
1407 permissionrequested(WebKitWebView *v, WebKitPermissionRequest *r, Client *c)
1408 {
1409         if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(r)) {
1410                 if (curconfig[Geolocation].val.b)
1411                         webkit_permission_request_allow(r);
1412                 else
1413                         webkit_permission_request_deny(r);
1414                 return TRUE;
1415         }
1416
1417         return FALSE;
1418 }
1419
1420 gboolean
1421 decidepolicy(WebKitWebView *v, WebKitPolicyDecision *d,
1422     WebKitPolicyDecisionType dt, Client *c)
1423 {
1424         switch (dt) {
1425         case WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION:
1426                 decidenavigation(d, c);
1427                 break;
1428         case WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION:
1429                 decidenewwindow(d, c);
1430                 break;
1431         case WEBKIT_POLICY_DECISION_TYPE_RESPONSE:
1432                 decideresource(d, c);
1433                 break;
1434         default:
1435                 webkit_policy_decision_ignore(d);
1436                 break;
1437         }
1438         return TRUE;
1439 }
1440
1441 void
1442 decidenavigation(WebKitPolicyDecision *d, Client *c)
1443 {
1444         WebKitNavigationAction *a =
1445             webkit_navigation_policy_decision_get_navigation_action(
1446             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1447
1448         switch (webkit_navigation_action_get_navigation_type(a)) {
1449         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1450         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1451         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1452         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1453         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED: /* fallthrough */
1454         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1455         default:
1456                 /* Do not navigate to links with a "_blank" target (popup) */
1457                 if (webkit_navigation_policy_decision_get_frame_name(
1458                     WEBKIT_NAVIGATION_POLICY_DECISION(d))) {
1459                         webkit_policy_decision_ignore(d);
1460                 } else {
1461                         /* Filter out navigation to different domain ? */
1462                         /* get action→urirequest, copy and load in new window+view
1463                          * on Ctrl+Click ? */
1464                         webkit_policy_decision_use(d);
1465                 }
1466                 break;
1467         }
1468 }
1469
1470 void
1471 decidenewwindow(WebKitPolicyDecision *d, Client *c)
1472 {
1473         Arg arg;
1474         WebKitNavigationAction *a =
1475             webkit_navigation_policy_decision_get_navigation_action(
1476             WEBKIT_NAVIGATION_POLICY_DECISION(d));
1477
1478
1479         switch (webkit_navigation_action_get_navigation_type(a)) {
1480         case WEBKIT_NAVIGATION_TYPE_LINK_CLICKED: /* fallthrough */
1481         case WEBKIT_NAVIGATION_TYPE_FORM_SUBMITTED: /* fallthrough */
1482         case WEBKIT_NAVIGATION_TYPE_BACK_FORWARD: /* fallthrough */
1483         case WEBKIT_NAVIGATION_TYPE_RELOAD: /* fallthrough */
1484         case WEBKIT_NAVIGATION_TYPE_FORM_RESUBMITTED:
1485                 /* Filter domains here */
1486 /* If the value of “mouse-button” is not 0, then the navigation was triggered by a mouse event.
1487  * test for link clicked but no button ? */
1488                 arg.v = webkit_uri_request_get_uri(
1489                         webkit_navigation_action_get_request(a));
1490                 newwindow(c, &arg, 0);
1491                 break;
1492         case WEBKIT_NAVIGATION_TYPE_OTHER: /* fallthrough */
1493         default:
1494                 break;
1495         }
1496
1497         webkit_policy_decision_ignore(d);
1498 }
1499
1500 void
1501 decideresource(WebKitPolicyDecision *d, Client *c)
1502 {
1503         int i, isascii = 1;
1504         WebKitResponsePolicyDecision *r = WEBKIT_RESPONSE_POLICY_DECISION(d);
1505         WebKitURIResponse *res =
1506             webkit_response_policy_decision_get_response(r);
1507         const gchar *uri = webkit_uri_response_get_uri(res);
1508
1509         if (g_str_has_suffix(uri, "/favicon.ico")) {
1510                 webkit_policy_decision_ignore(d);
1511                 return;
1512         }
1513
1514         if (!g_str_has_prefix(uri, "http://")
1515             && !g_str_has_prefix(uri, "https://")
1516             && !g_str_has_prefix(uri, "about:")
1517             && !g_str_has_prefix(uri, "file://")
1518             && !g_str_has_prefix(uri, "data:")
1519             && !g_str_has_prefix(uri, "blob:")
1520             && strlen(uri) > 0) {
1521                 for (i = 0; i < strlen(uri); i++) {
1522                         if (!g_ascii_isprint(uri[i])) {
1523                                 isascii = 0;
1524                                 break;
1525                         }
1526                 }
1527                 if (isascii) {
1528                         handleplumb(c, uri);
1529                         webkit_policy_decision_ignore(d);
1530                         return;
1531                 }
1532         }
1533
1534         if (webkit_response_policy_decision_is_mime_type_supported(r)) {
1535                 webkit_policy_decision_use(d);
1536         } else {
1537                 webkit_policy_decision_ignore(d);
1538                 download(c, res);
1539         }
1540 }
1541
1542 void
1543 insecurecontent(WebKitWebView *v, WebKitInsecureContentEvent e, Client *c)
1544 {
1545         c->insecure = 1;
1546 }
1547
1548 void
1549 downloadstarted(WebKitWebContext *wc, WebKitDownload *d, Client *c)
1550 {
1551         g_signal_connect(G_OBJECT(d), "notify::response",
1552                          G_CALLBACK(responsereceived), c);
1553 }
1554
1555 void
1556 responsereceived(WebKitDownload *d, GParamSpec *ps, Client *c)
1557 {
1558         download(c, webkit_download_get_response(d));
1559         webkit_download_cancel(d);
1560 }
1561
1562 void
1563 download(Client *c, WebKitURIResponse *r)
1564 {
1565         Arg a = (Arg)DOWNLOAD(webkit_uri_response_get_uri(r), geturi(c));
1566         spawn(c, &a);
1567 }
1568
1569 void
1570 closeview(WebKitWebView *v, Client *c)
1571 {
1572         gtk_widget_destroy(c->win);
1573 }
1574
1575 void
1576 destroywin(GtkWidget* w, Client *c)
1577 {
1578         destroyclient(c);
1579         if (!clients)
1580                 gtk_main_quit();
1581 }
1582
1583 void
1584 pasteuri(GtkClipboard *clipboard, const char *text, gpointer d)
1585 {
1586         Arg a = {.v = text };
1587         if (text)
1588                 loaduri((Client *) d, &a);
1589 }
1590
1591 void
1592 reload(Client *c, const Arg *a)
1593 {
1594         if (a->b)
1595                 webkit_web_view_reload_bypass_cache(c->view);
1596         else
1597                 webkit_web_view_reload(c->view);
1598 }
1599
1600 void
1601 print(Client *c, const Arg *a)
1602 {
1603         webkit_print_operation_run_dialog(webkit_print_operation_new(c->view),
1604                                           GTK_WINDOW(c->win));
1605 }
1606
1607 void
1608 clipboard(Client *c, const Arg *a)
1609 {
1610         if (a->b) { /* load clipboard uri */
1611                 gtk_clipboard_request_text(gtk_clipboard_get(
1612                                            GDK_SELECTION_PRIMARY),
1613                                            pasteuri, c);
1614         } else { /* copy uri */
1615                 gtk_clipboard_set_text(gtk_clipboard_get(
1616                                        GDK_SELECTION_PRIMARY), c->targeturi
1617                                        ? c->targeturi : geturi(c), -1);
1618         }
1619 }
1620
1621 void
1622 zoom(Client *c, const Arg *a)
1623 {
1624         if (a->i > 0)
1625                 webkit_web_view_set_zoom_level(c->view,
1626                                                curconfig[ZoomLevel].val.f + 0.1);
1627         else if (a->i < 0)
1628                 webkit_web_view_set_zoom_level(c->view,
1629                                                curconfig[ZoomLevel].val.f - 0.1);
1630         else
1631                 webkit_web_view_set_zoom_level(c->view, 1.0);
1632
1633         curconfig[ZoomLevel].val.f = webkit_web_view_get_zoom_level(c->view);
1634 }
1635
1636 void
1637 scroll(Client *c, const Arg *a)
1638 {
1639         GdkEvent *ev = gdk_event_new(GDK_KEY_PRESS);
1640
1641         gdk_event_set_device(ev, gdkkb);
1642         ev->key.window = gtk_widget_get_window(GTK_WIDGET(c->win));
1643         ev->key.state = GDK_CONTROL_MASK;
1644         ev->key.time = GDK_CURRENT_TIME;
1645
1646         switch (a->i) {
1647         case 'd':
1648                 ev->key.keyval = GDK_KEY_Down;
1649                 break;
1650         case 'D':
1651                 ev->key.keyval = GDK_KEY_Page_Down;
1652                 break;
1653         case 'l':
1654                 ev->key.keyval = GDK_KEY_Left;
1655                 break;
1656         case 'r':
1657                 ev->key.keyval = GDK_KEY_Right;
1658                 break;
1659         case 'U':
1660                 ev->key.keyval = GDK_KEY_Page_Up;
1661                 break;
1662         case 'u':
1663                 ev->key.keyval = GDK_KEY_Up;
1664                 break;
1665         }
1666
1667         gdk_event_put(ev);
1668 }
1669
1670 void
1671 navigate(Client *c, const Arg *a)
1672 {
1673         if (a->i < 0)
1674                 webkit_web_view_go_back(c->view);
1675         else if (a->i > 0)
1676                 webkit_web_view_go_forward(c->view);
1677 }
1678
1679 void
1680 stop(Client *c, const Arg *a)
1681 {
1682         webkit_web_view_stop_loading(c->view);
1683 }
1684
1685 void
1686 toggle(Client *c, const Arg *a)
1687 {
1688         curconfig[a->i].val.b ^= 1;
1689         setparameter(c, 1, (ParamName)a->i, &curconfig[a->i].val);
1690 }
1691
1692 void
1693 togglefullscreen(Client *c, const Arg *a)
1694 {
1695         /* toggling value is handled in winevent() */
1696         if (c->fullscreen)
1697                 gtk_window_unfullscreen(GTK_WINDOW(c->win));
1698         else
1699                 gtk_window_fullscreen(GTK_WINDOW(c->win));
1700 }
1701
1702 void
1703 togglecookiepolicy(Client *c, const Arg *a)
1704 {
1705         ++cookiepolicy;
1706         cookiepolicy %= strlen(curconfig[CookiePolicies].val.v);
1707
1708         setparameter(c, 0, CookiePolicies, NULL);
1709 }
1710
1711 void
1712 toggleinspector(Client *c, const Arg *a)
1713 {
1714         if (webkit_web_inspector_is_attached(c->inspector))
1715                 webkit_web_inspector_close(c->inspector);
1716         else if (curconfig[Inspector].val.b)
1717                 webkit_web_inspector_show(c->inspector);
1718 }
1719
1720 void
1721 find(Client *c, const Arg *a)
1722 {
1723         const char *s, *f;
1724
1725         if (a && a->i) {
1726                 if (a->i > 0)
1727                         webkit_find_controller_search_next(c->finder);
1728                 else
1729                         webkit_find_controller_search_previous(c->finder);
1730         } else {
1731                 s = getatom(c, AtomFind);
1732                 f = webkit_find_controller_get_search_text(c->finder);
1733
1734                 if (g_strcmp0(f, s) == 0) /* reset search */
1735                         webkit_find_controller_search(c->finder, "", findopts,
1736                                                       G_MAXUINT);
1737
1738                 webkit_find_controller_search(c->finder, s, findopts,
1739                                               G_MAXUINT);
1740
1741                 if (strcmp(s, "") == 0)
1742                         webkit_find_controller_search_finish(c->finder);
1743         }
1744 }
1745
1746 void
1747 clicknavigate(Client *c, const Arg *a, WebKitHitTestResult *h)
1748 {
1749         navigate(c, a);
1750 }
1751
1752 void
1753 clicknewwindow(Client *c, const Arg *a, WebKitHitTestResult *h)
1754 {
1755         Arg arg;
1756
1757         arg.v = webkit_hit_test_result_get_link_uri(h);
1758         newwindow(c, &arg, a->b);
1759 }
1760
1761 void
1762 clickexternplayer(Client *c, const Arg *a, WebKitHitTestResult *h)
1763 {
1764         Arg arg;
1765
1766         arg = (Arg)VIDEOPLAY(webkit_hit_test_result_get_media_uri(h));
1767         spawn(c, &arg);
1768 }
1769
1770 int
1771 main(int argc, char *argv[])
1772 {
1773         Arg arg;
1774         Client *c;
1775
1776         memset(&arg, 0, sizeof(arg));
1777
1778         /* command line args */
1779         ARGBEGIN {
1780         case 'a':
1781                 defconfig CSETV(CookiePolicies, EARGF(usage()));
1782                 break;
1783         case 'b':
1784                 defconfig CSETB(ScrollBars, 0);
1785                 break;
1786         case 'B':
1787                 defconfig CSETB(ScrollBars, 1);
1788                 break;
1789         case 'c':
1790                 cookiefile = EARGF(usage());
1791                 break;
1792         case 'C':
1793                 stylefile = EARGF(usage());
1794                 break;
1795         case 'd':
1796                 defconfig CSETB(DiskCache, 0);
1797                 break;
1798         case 'D':
1799                 defconfig CSETB(DiskCache, 1);
1800                 break;
1801         case 'e':
1802                 embed = strtol(EARGF(usage()), NULL, 0);
1803                 break;
1804         case 'f':
1805                 defconfig CSETB(RunInFullscreen, 0);
1806                 break;
1807         case 'F':
1808                 defconfig CSETB(RunInFullscreen, 1);
1809                 break;
1810         case 'g':
1811                 defconfig CSETB(Geolocation, 0);
1812                 break;
1813         case 'G':
1814                 defconfig CSETB(Geolocation, 1);
1815                 break;
1816         case 'i':
1817                 defconfig CSETB(LoadImages, 0);
1818                 break;
1819         case 'I':
1820                 defconfig CSETB(LoadImages, 1);
1821                 break;
1822         case 'k':
1823                 defconfig CSETB(KioskMode, 0);
1824                 break;
1825         case 'K':
1826                 defconfig CSETB(KioskMode, 1);
1827                 break;
1828         case 'm':
1829                 defconfig CSETB(Style, 0);
1830                 break;
1831         case 'M':
1832                 defconfig CSETB(Style, 1);
1833                 break;
1834         case 'n':
1835                 defconfig CSETB(Inspector, 0);
1836                 break;
1837         case 'N':
1838                 defconfig CSETB(Inspector, 1);
1839                 break;
1840         case 'p':
1841                 defconfig CSETB(Plugins, 0);
1842                 break;
1843         case 'P':
1844                 defconfig CSETB(Plugins, 1);
1845                 break;
1846         case 'r':
1847                 scriptfile = EARGF(usage());
1848                 break;
1849         case 's':
1850                 defconfig CSETB(JavaScript, 0);
1851                 break;
1852         case 'S':
1853                 defconfig CSETB(JavaScript, 1);
1854                 break;
1855         case 't':
1856                 defconfig CSETB(StrictTLS, 0);
1857                 break;
1858         case 'T':
1859                 defconfig CSETB(StrictTLS, 1);
1860                 break;
1861         case 'u':
1862                 fulluseragent = EARGF(usage());
1863                 break;
1864         case 'v':
1865                 die("surf-"VERSION", ©2009-2017 surf engineers, "
1866                     "see LICENSE for details\n");
1867         case 'x':
1868                 showxid = 1;
1869                 break;
1870         case 'z':
1871                 defconfig CSETF(ZoomLevel, strtof(EARGF(usage()), NULL));
1872                 break;
1873         default:
1874                 usage();
1875         } ARGEND;
1876         if (argc > 0)
1877                 arg.v = argv[0];
1878         else
1879                 arg.v = "about:blank";
1880
1881         setup();
1882         c = newclient(NULL);
1883         showview(NULL, c);
1884
1885         loaduri(c, &arg);
1886         updatetitle(c);
1887
1888         gtk_main();
1889         cleanup();
1890
1891         return 0;
1892 }