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