adji

Adji's a Decisive and Joyful Internet browser
git clone https://noxz.tech/git/adji.git
Log | Files | Tags | LICENSE

browser.h
1/**
2 * Copyright (C) 2023 Chris Noxz
3 * Author(s): Chris Noxz <chris@noxz.tech>
4 *
5 * This program is free software: you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation, either version 3 of the License, or (at your option)
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19#ifndef BROWSER_H
20#define BROWSER_H
21
22#include <fcntl.h>
23#include <limits.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <signal.h>
28#include <sys/stat.h>
29#include <sys/types.h>
30#include <sys/wait.h>
31
32#include <JavaScriptCore/JavaScript.h>
33#include <gdk/gdkkeysyms.h>
34#include <gio/gio.h>
35#include <gtk/gtk.h>
36#include <gtk/gtkx.h>
37#include <webkit2/webkit2.h>
38#include <wordexp.h>
39
40#define URI_MAX             2000
41#define SUFFIX_MAX          100
42#define ICON_DOWNLOAD       "network-receive-symbolic"
43#define ICON_FINISHED       "emblem-default-symbolic"
44#define ICON_GLOBE          "globe-symbolic"
45#define ICON_TLS            "channel-secure-symbolic"
46#define ICON_BAD_TLS        "channel-insecure-symbolic"
47#define ICON_JS_ON          "emblem-ok-symbolic"
48#define ICON_JS_OFF         "action-unavailable-symbolic"
49#define CB(C, E, F)                                                            \
50        {                                                                      \
51            g_signal_connect(G_OBJECT(C), (E), G_CALLBACK(F), NULL);           \
52        }
53#define CFG_B(X)            cfg[(X)].v.b
54#define CFG_F(X)            cfg[(X)].v.f
55#define CFG_I(X)            cfg[(X)].v.i
56#define CFG_L(X)            (const gchar* const*)cfg[(X)].v.l
57#define CFG_S(X)            cfg[(X)].v.s
58#define CFG_DIR             g_get_user_config_dir()
59
60#define TLS_MSG_FORMAT      "<h2>Could not validate TLS for: %s</h2><pre>%s"   \
61                            "</pre><pre>s:%s\ni:%s\nv:NotBefore:%s; NotAfter:" \
62                            "%s</pre><pre>%s</pre>"
63#define MSG_TLS_CERTIFICATE_UNKNOWN_CA                                         \
64                            ">  The signing certificate authority is not known"\
65                            ".<br>"
66#define MSG_TLS_CERTIFICATE_BAD_IDENTITY                                       \
67                            ">  The certificate does not match the expected"   \
68                            "identity of the site that it was retrieved from." \
69                            "<br>"
70#define MSG_TLS_CERTIFICATE_NOT_ACTIVATED                                      \
71                            ">  The certificate's activation time is still in" \
72                            " the future.<br>"
73#define MSG_TLS_CERTIFICATE_EXPIRED                                            \
74                            ">  The certificate has expired.<br>"
75#define MSG_TLS_CERTIFICATE_REVOKED                                            \
76                            ">  The certificate has been revoked according to" \
77                            " the GTlsConnection's certificate revocation list"\
78                            ".<br>"
79#define MSG_TLS_CERTIFICATE_INSECURE                                           \
80                            ">  The certificate's algorithm is considered "    \
81                            "insecure.<br>"
82#define MSG_TLS_CERTIFICATE_GENERIC_ERROR                                      \
83                            ">  Some error occurred validating the certificate"\
84                            ".<br>"
85#define XDG_OPEN            "xdg-open"
86#define STATE_CURRENT       1 << 0
87#define STATE_JAVASCRIPT    1 << 1
88#define BROWSER_ERROR       (browser_error_quark())
89#define ABOUT_SCHEME        __NAME__"-about"
90#define ABOUT_BLANK         "about:blank"
91#define SCRIPT_MAIN_FRAME   "window.onmessage = (e) => {"                      \
92                            "  window.adji_selection = e.data;"                \
93                            "};"
94#define SCRIPT_ALL_FRAMES   "window.onmessage = (e) => {"                      \
95                            "  if (e.data == 'selection') {"                   \
96                            "    let r = window.getSelection().toString();"    \
97                            "    e.source.postMessage(r, e.origin);"           \
98                            "  }"                                              \
99                            "};"
100#define SCRIPT_SEARCH_EXTRA "cw = document.activeElement.contentWindow;"       \
101                            "if (cw) {"                                        \
102                            "  cw.postMessage('selection','*');"               \
103                            "} else {"                                         \
104                            "  let r = window.getSelection().toString();"      \
105                            "  window.adji_selection = r;"                     \
106                            "} window.adji_selection;"
107#define SCRIPT_SEARCH       "window.adji_selection = '';"SCRIPT_SEARCH_EXTRA
108#define IUS(W, S, C)        /* Inject User Script */                           \
109        {                                                                      \
110            webkit_user_content_manager_add_script(                            \
111                webkit_web_view_get_user_content_manager((W)),                 \
112                    webkit_user_script_new(                                    \
113                        (S),                                                   \
114                        (C),                                                   \
115                        WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START,           \
116                        NULL,                                                  \
117                        NULL));                                                \
118        }
119#define IUSS(W, S, C)       /* Inject User Style Sheet */                      \
120        {                                                                      \
121            webkit_user_content_manager_add_style_sheet(                       \
122                webkit_web_view_get_user_content_manager((W)),                 \
123                    webkit_user_style_sheet_new(                               \
124                        (S),                                                   \
125                        (C),                                                   \
126                        WEBKIT_USER_STYLE_LEVEL_USER,                          \
127                        NULL,                                                  \
128                        NULL));                                                \
129        }
130
131/*** Name translations ***/
132/*      name                original name                                   */
133#define get_request(A)      webkit_navigation_action_get_request((A))
134#define request_path(R)     webkit_uri_scheme_request_get_path((R))
135#define get_socket_win(W)   gtk_plug_get_socket_window(GTK_PLUG((W)))
136#define get_widget_win(W)   gtk_widget_get_window((W))
137#define get_win_state(W)    gdk_window_get_state(get_widget_win(W))
138#define is_fullscreen()     get_win_state(gl.win) & GDK_WINDOW_STATE_FULLSCREEN
139#define to_gdk_win(D, W)    gdk_x11_window_foreign_new_for_display((D), (W))
140
141enum javascript_policy {
142	JSP_DISABLE             = 0,
143	JSP_ENABLE              = 1,
144	JSP_TOGGLE              = 2
145};
146
147enum inpage_search_type {
148	IPS_INIT,
149	IPS_FORWARD,
150	IPS_BACKWARD
151};
152
153enum config_type {
154	CFG_INT,
155	CFG_FLOAT,
156	CFG_BOOL,
157	CFG_STRING,
158	CFG_LIST
159};
160
161enum popup_policy {
162	PUP_BLOCK               = 0,
163	PUP_ALLOW               = 1,
164	PUP_EXTERNAL            = 2
165};
166
167/** Enumerators for configurables  */
168enum config_name {
169	CFN_ACCEPTED_LANGUAGES,
170	CFN_BLOB_FILE,
171	CFN_CONSOLE_TO_STDOUT,
172	CFN_COOKIE_FILE,
173	CFN_DNS_PREFETCHING,
174	CFN_DEFAULT_CHARSET,
175	CFN_DEFAULT_FONT,
176	CFN_DEFAULT_FONT_SIZE,
177	CFN_DEVELOPER_EXTRAS,
178	CFN_DISABLE_AUTOLOAD_IMAGES,
179	CFN_DISABLE_JAVASCRIPT,
180	CFN_ENCRYPTED_MEDIA,
181	CFN_EXTERNAL_HANDLER_FILE,
182	CFN_EXTERNAL_HANDLER_KEYS,
183	CFN_HISTORY_FILE,
184	CFN_HOME_URI,
185	CFN_HYPERLINK_AUDITING,
186	CFN_JS_ACCESS_CLIPBOARD,
187	CFN_JS_OPEN_WINDOWS,
188	CFN_MONOSPACE_FONT,
189	CFN_NORMAL_TAB_FORMAT,
190	CFN_POPUP_POLICY,
191	CFN_PROXY_IGNORE,
192	CFN_PROXY_URI,
193	CFN_SANS_SERIF_FONT,
194	CFN_SEARCH_ENGINE_URI_FORMAT,
195	CFN_SERIF_FONT,
196	CFN_SMOOTH_SCROLLING,
197	CFN_STATE_FILE,
198	CFN_TAB_HOST,
199	CFN_USER_CSS_DIR,
200	CFN_URI_SCHEMES,
201	CFN_USER_SCRIPT_DIR,
202	CFN_USER_AGENT,
203	CFN_WEB_EXTENSION_DIR,
204	CFN_WEB_RTC,
205	CFN_XDG_SCHEMES,
206	CFN_ZOOM_LEVEL,
207	/* must be last to represent number of config items */
208	CFN_LAST
209};
210
211enum browser_error {
212	BER_INVALID_ABOUT_PATH
213};
214
215typedef union {
216	gint                    i;
217	gdouble                 f;
218	gboolean                b;
219	gchar                  *s;
220	gchar                 **l;
221} Arg;
222
223typedef struct {
224	const char             *e;
225	const char             *s;
226	enum config_type        t;
227	gboolean                i;
228	Arg                     v;
229} Config;
230
231struct Global {
232	GTlsCertificate        *crt;
233	gboolean                error_page;
234	gboolean                first_search;
235	gchar                  *hover_uri;
236	GTlsCertificate        *failed_crt;
237	gboolean                state_lock;
238	gboolean                initialized;
239	gboolean                https;
240	Window                  embed;
241	GtkWidget              *win;
242	GtkWidget              *vbx;
243	GtkWidget              *wv;
244	GtkWidget              *entry;
245	gchar                  *arg0;
246	WebKitSettings         *settings;
247	gchar                  *search_text;
248	GTlsCertificateFlags    tls_error;
249} gl;
250
251
252/*** "Special" functions ***/
253static GQuark
254browser_error_quark()
255{
256	return g_quark_from_string(__NAME__"-quark");
257}
258
259
260/*** Function prototypes ***/
261static void about_scheme_request(
262	WebKitURISchemeRequest                     *request,
263	gpointer                                    data
264);
265
266static void attach_to_host(
267	GtkWidget                                  *self
268);
269
270static void attach_to_window(
271	GtkWidget                                  *self
272);
273
274static void initialize(
275	const gchar                                *uri
276);
277
278static void terminate(void);
279
280static gboolean command(
281	const gchar                                *t
282);
283
284static void create_context_menu(
285	WebKitContextMenu                          *context_menu,
286	WebKitHitTestResult                        *hit_test_result
287);
288
289static void create_context_menu_item(
290	WebKitContextMenu                          *context_menu,
291	const gchar                                *name,
292	const gchar                                *label,
293	void                                       *action
294);
295
296static void download_blob(
297	WebKitDownload                             *d
298);
299
300static void download_response(
301	WebKitURIResponse                          *r
302);
303
304static gchar *get_uri(
305	GtkWidget                                  *wv
306);
307
308static gboolean handle_fullscreen(
309	gboolean                                    fullscreen
310);
311
312static void handle_popup(
313	const gchar                                *uri
314);
315
316static gboolean key_common(
317	GdkEventKey                                *event
318);
319
320static gboolean key_entry(
321	GdkEventKey                                *event
322);
323
324static gboolean key_web_view(
325	GdkEvent                                   *event
326);
327
328static void load_changed(
329	WebKitLoadEvent                             event_type
330);
331
332static void load_configuration(void);
333
334static void load_stdin(void);
335
336static void load_user_scripts(
337	WebKitWebView                              *web_view
338);
339
340static void load_user_styles(
341	WebKitWebView                              *web_view
342);
343
344static void main_window_setup(void);
345
346static void open_external(
347	const gchar                                 s
348);
349
350static void quit(void);
351
352static void render_tls_error(
353	gchar                                      *uri,
354	GTlsCertificate                            *crt,
355	GTlsCertificateFlags                        crt_flags
356);
357
358static void save_history(
359	const gchar                                *t
360);
361
362static void search(
363	enum inpage_search_type                     type
364);
365
366static void selection_search(void);
367
368static void selection_search_finished(
369	GAsyncResult                               *result
370);
371
372static void set_default_web_context(void);
373
374static void set_hover_uri(
375	WebKitHitTestResult                        *hit_test_result
376);
377
378static void set_javascript_policy(
379	enum javascript_policy                      policy
380);
381
382static void toggle_inspector(void);
383
384static void toggle_tls_error_policy(void);
385
386static gchar *resolve_uri(
387	const gchar                                *t
388);
389
390static char *resolve_uri_words(
391	int                                         c,
392	char                                      **w
393);
394
395static void update_favicon(void);
396
397static void update_load_progress(void);
398
399static void update_title(void);
400
401static void uri_changed(void);
402
403static void web_view_crashed(void);
404
405static void xdg_open(
406	const gchar                                *s,
407	const gchar                                *t,
408	gboolean                                    keep
409);
410
411
412/*** Utility functions ***/
413void
414die(
415	const char                                 *msg)
416{
417	fprintf(stderr, msg);
418	exit(EXIT_FAILURE);
419}
420
421void
422g_free_all(
423	gpointer                                   *p)
424{
425	for (; *p; g_free(*(p++)));
426}
427
428void
429to_win(
430	Window                                     *i,
431	const char                                 *s)
432{
433	char *e;
434
435	*i = strtol(s, &e, 0);
436	if (e == s || *e != 0)
437		*i = -1;
438}
439
440void
441sigchld(
442	int                                        s)
443{
444	(void) s;
445
446	if (signal(SIGCHLD, sigchld) == SIG_ERR)
447		die("Failed to initialize SIGCHLD handler");
448	while (waitpid(-1, NULL, WNOHANG) > 0);
449}
450
451
452/*** Callback function wrappers ***/
453gboolean
454cb_context_menu(WebKitWebView                  *web_view,
455                WebKitContextMenu              *context_menu,
456                GdkEvent                       *event,
457                WebKitHitTestResult            *hit_test_result,
458                gpointer                        data)
459{
460	(void)data;
461	(void)event;
462	(void)web_view;
463
464	create_context_menu(context_menu, hit_test_result);
465	return FALSE;
466}
467
468void
469cb_favicon_changed(GObject                     *obj,
470                   GParamSpec                  *pspec,
471                   gpointer                     data)
472{
473	(void)obj;
474	(void)pspec;
475	(void)data;
476
477	update_favicon();
478}
479
480gboolean
481cb_entry_hid(GtkWidget                         *widget,
482             GdkEvent                          *event,
483             gpointer                           data)
484{
485	(void)widget;
486	(void)data;
487
488	/* only handle key presses */
489	if (event->type == GDK_KEY_PRESS)
490		return key_entry((GdkEventKey*)event);
491
492	return FALSE;
493
494}
495
496void
497cb_entry_icon_hid(GtkEntry                     *entry,
498                  GtkEntryIconPosition          icon_pos,
499                  GdkEvent                     *event,
500                  gpointer                      data)
501{
502	(void)entry;
503	(void)event;
504	(void)data;
505
506	if (icon_pos == GTK_ENTRY_ICON_PRIMARY)
507		toggle_tls_error_policy();
508	else if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
509		set_javascript_policy(JSP_TOGGLE);
510}
511
512void
513cb_download_prepare(WebKitDownload             *download,
514                   GParamSpec                  *param,
515                   gpointer                     data)
516{
517	(void)param;
518	(void)data;
519
520	/* check if blob  */
521	download_blob(download);
522
523	/* forward to external download handler */
524	download_response(webkit_download_get_response(download));
525	webkit_download_cancel(download);
526}
527
528void
529cb_download_start(WebKitWebContext             *context,
530                  WebKitDownload               *download,
531                  gpointer                      data)
532{
533	(void)context;
534	(void)data;
535
536	CB(download, "decide-destination", cb_download_prepare);
537}
538
539gboolean
540cb_open_external(GSimpleAction                 *action,
541                 GVariant                      *parameter,
542                 gpointer                       data)
543{
544	(void)action;
545	(void)parameter;
546	(void)data;
547
548	open_external(0);
549	return FALSE;
550}
551
552void
553cb_quit(GObject                                *obj,
554        gpointer                                data)
555{
556	(void)obj;
557	(void)data;
558
559	quit();
560}
561
562gboolean
563cb_selection_search(GSimpleAction              *action,
564                    GVariant                   *parameter,
565                    gpointer                    data)
566{
567	(void)action;
568	(void)parameter;
569	(void)data;
570
571	gl.first_search = TRUE;
572	selection_search();
573	return FALSE;
574}
575
576void
577cb_selection_search_finished(GObject           *object,
578                             GAsyncResult      *result,
579                             gpointer           data)
580{
581	(void)data;
582	(void)object;
583
584	selection_search_finished(result);
585}
586
587void
588cb_title_changed(GObject                       *obj,
589                 GParamSpec                    *pspec,
590                 gpointer                       data)
591{
592	(void)obj;
593	(void)pspec;
594	(void)data;
595
596	update_title();
597}
598
599void
600cb_map(GObject                                 *obj,
601      GdkEvent                                  event,
602      gpointer                                  data)
603{
604	(void)event;
605	(void)data;
606
607	attach_to_host((GtkWidget *)obj);
608}
609
610
611void
612cb_unmap(GObject                               *obj,
613        GdkEvent                                event,
614        gpointer                                data)
615{
616	(void)event;
617	(void)data;
618
619	attach_to_window((GtkWidget *)obj);
620}
621
622void
623cb_uri_changed(GObject                         *obj,
624               GParamSpec                      *pspec,
625               gpointer                         data)
626{
627	(void)obj;
628	(void)pspec;
629	(void)data;
630
631	uri_changed();
632}
633
634void
635cb_wv_close(GtkWidget                          *widget,
636            gpointer                            data)
637{
638	(void)widget;
639	(void)data;
640
641	terminate();
642}
643
644gboolean
645cb_wv_crashed(WebKitWebView                    *web_view,
646              gpointer                          data)
647{
648	(void)web_view;
649	(void)data;
650
651	web_view_crashed();
652	return TRUE;
653}
654
655WebKitWebView *
656cb_wv_create(WebKitWebView                     *web_view,
657             WebKitNavigationAction            *navigation_action,
658             gpointer                           data)
659{
660	(void)web_view;
661	(void)data;
662
663	handle_popup(webkit_uri_request_get_uri(get_request(navigation_action)));
664
665	return NULL;
666}
667
668gboolean
669cb_wv_decide_policy(WebKitWebView              *web_view,
670                    WebKitPolicyDecision       *decision,
671                    WebKitPolicyDecisionType    type,
672                    gpointer                    data)
673{
674	(void)web_view;
675	(void)data;
676
677	/* only handle policy decisions */
678	if (type != WEBKIT_POLICY_DECISION_TYPE_RESPONSE) {
679		return FALSE;
680	/* check if: 'HTTP/1.1 204 No Content'
681	 * this should be ignored, but isn't
682	 * see: https://bugs.webkit.org/show_bug.cgi?id=60206
683	 * */
684	} else if (webkit_uri_response_get_status_code(
685	            webkit_response_policy_decision_get_response(
686	                WEBKIT_RESPONSE_POLICY_DECISION(decision))) == 204
687	        ) {
688		webkit_policy_decision_ignore(decision);
689	/* continue as normal */
690	} else if (webkit_response_policy_decision_is_mime_type_supported(
691	            WEBKIT_RESPONSE_POLICY_DECISION(decision))
692	        ) {
693		webkit_policy_decision_use(decision);
694	} else {
695		webkit_policy_decision_ignore(decision);
696		download_response(
697		    webkit_response_policy_decision_get_response(
698		        WEBKIT_RESPONSE_POLICY_DECISION(decision)));
699	}
700	return TRUE;
701}
702
703gboolean
704cb_wv_fullscreen_enter(WebKitWebView           *web_view,
705                       gpointer                 data)
706{
707	(void)web_view;
708	(void)data;
709
710	return !handle_fullscreen(TRUE);
711}
712
713gboolean
714cb_wv_fullscreen_leave(WebKitWebView           *web_view,
715                       gpointer                 data)
716{
717	(void)web_view;
718	(void)data;
719
720	return !handle_fullscreen(FALSE);
721}
722
723gboolean
724cb_wv_hid(GtkWidget                            *widget,
725          GdkEvent                             *event,
726          gpointer                              data)
727{
728	(void)widget;
729	(void)data;
730
731	return key_web_view(event);
732}
733
734void
735cb_wv_hover(WebKitWebView                      *web_view,
736            WebKitHitTestResult                *hit_test_result,
737            guint                               modifiers,
738            gpointer                            data)
739{
740	(void)web_view;
741	(void)modifiers;
742	(void)data;
743
744	set_hover_uri(hit_test_result);
745}
746
747void
748cb_wv_load_changed(GObject                     *obj,
749                   GParamSpec                  *pspec,
750                   gpointer                     data)
751{
752	(void)obj;
753	(void)data;
754
755	load_changed((WebKitLoadEvent)pspec);
756}
757
758void
759cb_wv_load_progress_changed(GObject            *obj,
760                            GParamSpec         *pspec,
761                            gpointer            data)
762{
763	(void)obj;
764	(void)pspec;
765	(void)data;
766
767	update_load_progress();
768}
769
770gboolean
771cb_wv_tls_load_failed(GObject                  *obj,
772                      gchar                    *uri,
773                      GTlsCertificate          *crt,
774                      GTlsCertificateFlags      err,
775                      gpointer                  data)
776{
777	(void)obj;
778	(void)data;
779
780	render_tls_error(uri, crt, err);
781	return TRUE;
782}
783
784#endif /* !BROWSER_H */