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 */