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 <sys/stat.h>
28#include <sys/types.h>
29
30#include <JavaScriptCore/JavaScript.h>
31#include <gdk/gdkkeysyms.h>
32#include <gio/gio.h>
33#include <gtk/gtk.h>
34#include <gtk/gtkx.h>
35#include <webkit2/webkit2.h>
36#include <wordexp.h>
37
38#define URI_MAX 2000
39#define SUFFIX_MAX 100
40#define ICON_DOWNLOAD "network-receive-symbolic"
41#define ICON_FINISHED "emblem-default-symbolic"
42#define ICON_GLOBE "globe-symbolic"
43#define ICON_TLS "channel-secure-symbolic"
44#define ICON_BAD_TLS "channel-insecure-symbolic"
45#define ICON_JS_ON "emblem-ok-symbolic"
46#define ICON_JS_OFF "action-unavailable-symbolic"
47#define CB(C, E, F, D) \
48 { \
49 g_signal_connect(G_OBJECT(C), (E), G_CALLBACK(F), (D)); \
50 }
51#define CBA(C, E, F, D) \
52 { \
53 g_signal_connect_after(G_OBJECT(C), (E), G_CALLBACK(F), (D)); \
54 }
55#define CFG_B(X) cfg[(X)].v.b
56#define CFG_F(X) cfg[(X)].v.f
57#define CFG_I(X) cfg[(X)].v.i
58#define CFG_L(X) (const gchar* const*)cfg[(X)].v.l
59#define CFG_S(X) cfg[(X)].v.s
60
61#define TLS_MSG_FORMAT "<h2>Could not validate TLS for: %s</h2><pre>%s" \
62 "</pre><pre>s:%s\ni:%s\nv:NotBefore:%s; NotAfter:" \
63 "%s</pre><pre>%s</pre>"
64#define MSG_TLS_CERTIFICATE_UNKNOWN_CA \
65 "> The signing certificate authority is not known"\
66 ".<br>"
67#define MSG_TLS_CERTIFICATE_BAD_IDENTITY \
68 "> The certificate does not match the expected" \
69 "identity of the site that it was retrieved from." \
70 "<br>"
71#define MSG_TLS_CERTIFICATE_NOT_ACTIVATED \
72 "> The certificate's activation time is still in" \
73 " the future.<br>"
74#define MSG_TLS_CERTIFICATE_EXPIRED \
75 "> The certificate has expired.<br>"
76#define MSG_TLS_CERTIFICATE_REVOKED \
77 "> The certificate has been revoked according to" \
78 " the GTlsConnection's certificate revocation list"\
79 ".<br>"
80#define MSG_TLS_CERTIFICATE_INSECURE \
81 "> The certificate's algorithm is considered " \
82 "insecure.<br>"
83#define MSG_TLS_CERTIFICATE_GENERIC_ERROR \
84 "> Some error occurred validating the certificate"\
85 ".<br>"
86#define XDG_OPEN "xdg-open"
87#define STATE_CURRENT 1 << 0
88#define STATE_JAVASCRIPT 1 << 1
89#define BROWSER_ERROR (browser_error_quark())
90#define ABOUT_SCHEME __NAME__"-about"
91
92enum javascript_policy {
93 JSP_DISABLE = 0,
94 JSP_ENABLE = 1,
95 JSP_TOGGLE = 2
96};
97
98enum inpage_search_type {
99 IPS_INIT,
100 IPS_FORWARD,
101 IPS_BACKWARD
102};
103
104enum ipc_type {
105 IPC_NONE,
106 IPC_HOST,
107 IPC_CLIENT
108};
109
110enum config_type {
111 CFG_INT,
112 CFG_FLOAT,
113 CFG_BOOL,
114 CFG_STRING,
115 CFG_LIST,
116};
117
118enum config_name {
119 AcceptedLanguages,
120 BadTlsTabFormat,
121 ConsoleToStdout,
122 CookieFile,
123 DnsPrefetching,
124 DefaultCharset,
125 DefaultFont,
126 DefaultFontSize,
127 DeveloperExtras,
128 DisableAutoLoadImages,
129 DisableJavaScript,
130 DownloadDirectory,
131 EncryptedMedia,
132 ExternalHandlerFile,
133 ExternalHandlerKeys,
134 FifoName,
135 HistoryFile,
136 HomeUri,
137 HyperlinkAuditing,
138 JsAccessClipboard,
139 JsOpenWindows,
140 MonospaceFont,
141 NormalTabFormat,
142 ProxyIgnore,
143 ProxyUri,
144 SansSerifFont,
145 SearchEngineUriFormat,
146 SerifFont,
147 SmoothScrolling,
148 StateFile,
149 UcDir, /* user (cascading) style sheets directory */
150 UriSchemes,
151 UsDir, /* user scripts directory */
152 UserAgent,
153 WeDir, /* web extension directory */
154 WebRtc,
155 XdgSchemes,
156 ZoomLevel,
157 /* must be last to represent number of config items */
158 LastConfig
159};
160
161enum browser_error {
162 BROWSER_ERROR_INVALID_ABOUT_PATH
163};
164
165static GQuark browser_error_quark()
166{
167 return g_quark_from_string(__NAME__"-quark");
168}
169
170typedef union {
171 gint i; /* union integer */
172 gdouble f; /* union float/double */
173 gboolean b; /* union boolean */
174 gchar *s; /* union string */
175 gchar **l; /* union string list/array */
176} Arg;
177
178struct Client
179{
180 GTlsCertificate *crt; /* last certificate */
181 GtkWidget *entry; /* uri/command/entry */
182 gboolean error_page; /* flag if error page */
183 GTlsCertificate *failed_crt; /* last failed certificate */
184 gboolean focus_new_tab; /* flag for focusing tab */
185 gchar *hover_uri; /* uri last hovered */
186 gboolean https; /* flag if https site */
187 WebKitSettings *settings; /* reference to webkit settings */
188 GtkWidget *tab_icon; /* holder for favicon in tab */
189 GtkWidget *tab_label; /* holder for title in tab */
190 GTlsCertificateFlags tls_error; /* last tls error */
191 GtkWidget *vbx; /* vertical box (view + entry) */
192 GtkWidget *wv; /* web view */
193};
194
195typedef struct {
196 const char *e;
197 const char *s;
198 enum config_type t;
199 gboolean i;
200 Arg v;
201} Config;
202
203struct Global {
204 int clients;
205 gboolean state_lock;
206 enum ipc_type ipc;
207 int ipc_pipe_fd;
208 gchar *search_text;
209} gl;
210
211struct MainWindow
212{
213 GtkWidget *dbx;
214 GtkWidget *nb;
215 GtkWidget *win;
216} mw;
217
218/* "normal" functions */
219static void about_scheme_request(WebKitURISchemeRequest *, gpointer);
220static struct Client *client_create(const gchar *, WebKitWebView *, gboolean,
221 gboolean);
222static void client_destroy(struct Client *);
223static gboolean command(struct Client *, const gchar*);
224static void create_context_menu(struct Client *, WebKitContextMenu *,
225 WebKitHitTestResult *);
226static void create_context_menu_item(struct Client *, WebKitContextMenu *,
227 const gchar *, const gchar *, void *);
228static void die(const char *);
229static int get_memory(int *, int *, int *, int *);
230static gchar *get_uri(GtkWidget*);
231static gboolean ipc_request(GIOChannel *, GIOCondition, gpointer);
232static ssize_t ipc_send(char *);
233static void ipc_setup(void);
234static gboolean key_common(struct Client *, GdkEventKey *);
235static gboolean key_entry(struct Client *, GdkEventKey *);
236static gboolean key_tab(struct Client *, GdkEvent *);
237static gboolean key_web_view(struct Client *, GdkEvent *);
238static void load_changed(struct Client *, WebKitLoadEvent);
239static void load_configuration(void);
240static void load_state(void);
241static void load_stdin(void);
242static void load_user_styles(WebKitWebView *);
243static void main_window_setup(void);
244static void open_external(struct Client *, const gchar);
245static void prepare_download(WebKitDownload *, gchar *);
246static void quit(void);
247static void render_tls_error(struct Client *, gchar *, GTlsCertificate *,
248 GTlsCertificateFlags);
249static void run_user_scripts(WebKitWebView *);
250static void save_history(const gchar *);
251static void save_state(void);
252static void search(struct Client *, enum inpage_search_type);
253static void selection_search(struct Client *);
254static void selection_search_finished(struct Client *, GAsyncResult *);
255static void set_default_web_context(void);
256static void set_hover_uri(struct Client *, WebKitHitTestResult *);
257static void set_javascript_policy(struct Client *, enum javascript_policy);
258static void set_window_title(gint);
259static void show_web_view(struct Client *);
260static void toggle_inspector(struct Client *);
261static void toggle_tls_error_policy(struct Client *);
262static gchar *resolve_uri(const gchar *);
263static char *resolve_uri_words(int, char **);
264static void update_download_button(WebKitDownload *, GtkButton *, gboolean);
265static void update_favicon(struct Client *);
266static void update_load_progress(struct Client *);
267static void update_title(struct Client *);
268static void uri_changed(struct Client *);
269static void web_view_crashed(struct Client *);
270static void xdg_open(const gchar *, const gchar *);
271
272/* callback functions */
273static gboolean cb_context_menu(WebKitWebView *, WebKitContextMenu *,
274 GdkEvent *, WebKitHitTestResult *, gpointer);
275static void cb_download_changed_progress(GObject *, GParamSpec *, gpointer);
276static void cb_download_finished(GObject *, gpointer);
277static gboolean cb_download_prepare(WebKitDownload *, gchar *, gpointer);
278static void cb_download_press(GtkWidget *, GdkEventButton *, gpointer);
279static void cb_download_start(WebKitWebView *, WebKitDownload *, gpointer);
280static void cb_favicon_changed(GObject *, GParamSpec *, gpointer);
281static gboolean cb_entry_hid(GtkWidget *, GdkEvent *, gpointer);
282static void cb_entry_icon_hid(GtkEntry *, GtkEntryIconPosition, GdkEvent *,
283 gpointer);
284static void cb_notebook_modified(GtkNotebook *, GtkWidget *, guint, gpointer);
285static void cb_notebook_switch_page(GtkNotebook *, GtkWidget *, guint,
286 gpointer);
287static gboolean cb_open_external(GSimpleAction *, GVariant *, gpointer);
288static gboolean cb_selection_search(GSimpleAction *, GVariant *, gpointer);
289static void cb_selection_search_finished(GObject *, GAsyncResult *, gpointer);
290static void cb_quit(GObject *, gpointer);
291static gboolean cb_tab_hid(GtkWidget *, GdkEvent *, gpointer);
292static void cb_title_changed(GObject *, GParamSpec *, gpointer);
293static void cb_uri_changed(GObject *, GParamSpec *, gpointer);
294static void cb_wv_close(GtkWidget *, gpointer);
295static gboolean cb_wv_crashed(WebKitWebView *, gpointer);
296static WebKitWebView *cb_wv_create(WebKitWebView *, WebKitNavigationAction *,
297 gpointer);
298static gboolean cb_wv_decide_policy(WebKitWebView *, WebKitPolicyDecision *,
299 WebKitPolicyDecisionType, gpointer);
300static gboolean cb_wv_hid(GtkWidget *, GdkEvent *, gpointer);
301static void cb_wv_hover(WebKitWebView *, WebKitHitTestResult *, guint,
302 gpointer);
303static void cb_wv_load_changed(GObject *, GParamSpec *, gpointer);
304static void cb_wv_load_progress_changed(GObject *, GParamSpec *, gpointer);
305static void cb_wv_show(WebKitWebView *, gpointer);
306static gboolean cb_wv_tls_load_failed(GObject *, gchar *, GTlsCertificate *,
307 GTlsCertificateFlags, gpointer);
308
309
310gboolean
311cb_context_menu(WebKitWebView *web_view,
312 WebKitContextMenu *context_menu,
313 GdkEvent *event,
314 WebKitHitTestResult *hit_test_result,
315 gpointer data)
316{
317 (void)web_view;
318 (void)event;
319
320 create_context_menu((struct Client *)data, context_menu, hit_test_result);
321 return FALSE;
322}
323
324void
325cb_download_changed_progress(GObject *obj,
326 GParamSpec *pspec,
327 gpointer data)
328{
329 (void)pspec;
330
331 update_download_button(WEBKIT_DOWNLOAD(obj), GTK_BUTTON(data), FALSE);
332}
333
334void
335cb_download_finished(GObject *obj,
336 gpointer data)
337{
338 if (!GTK_IS_BUTTON(data))
339 return;
340
341 (*((gboolean*)g_object_get_data(data, __NAME__"-finished"))) = TRUE;
342
343 update_download_button(WEBKIT_DOWNLOAD(obj), GTK_BUTTON(data), TRUE);
344 gtk_button_set_image(
345 GTK_BUTTON(data),
346 gtk_image_new_from_icon_name(ICON_FINISHED, GTK_ICON_SIZE_BUTTON)
347 );
348}
349
350gboolean
351cb_download_prepare(WebKitDownload *download,
352 gchar *suggested_filename,
353 gpointer data)
354{
355 (void)data;
356
357 prepare_download(download, suggested_filename);
358 return FALSE;
359}
360
361void
362cb_download_press(GtkWidget *btn,
363 GdkEventButton *event,
364 gpointer data)
365{
366 switch (event->button) {
367 case 3: /* right click: */
368 case 2: /* middle click: cancel and/or remove */
369 if (!(*((gboolean*)g_object_get_data(G_OBJECT(btn), __NAME__"-finished"))))
370 webkit_download_cancel(WEBKIT_DOWNLOAD(data));
371 g_object_unref(WEBKIT_DOWNLOAD(data));
372 gtk_widget_destroy(btn);
373 break;
374 case 1: /* left click: open downloaded file */
375 if ((*((gboolean*)g_object_get_data(G_OBJECT(btn), __NAME__"-finished"))))
376 xdg_open("", webkit_download_get_destination(WEBKIT_DOWNLOAD(data)));
377 break;
378 }
379}
380
381void
382cb_download_start(WebKitWebView *web_view,
383 WebKitDownload *download,
384 gpointer data)
385{
386 (void)web_view;
387
388 CB(download, "decide-destination", cb_download_prepare, data);
389}
390
391void
392cb_favicon_changed(GObject *obj,
393 GParamSpec *pspec,
394 gpointer data)
395{
396 (void)obj;
397 (void)pspec;
398
399 update_favicon((struct Client *)data);
400}
401
402gboolean
403cb_entry_hid(GtkWidget *widget,
404 GdkEvent *event,
405 gpointer data)
406{
407 (void)widget;
408
409 /* only handle key presses */
410 if (event->type == GDK_KEY_PRESS)
411 return key_entry((struct Client *)data, (GdkEventKey*)event);
412
413 return FALSE;
414
415}
416
417void
418cb_entry_icon_hid(GtkEntry *entry,
419 GtkEntryIconPosition icon_pos,
420 GdkEvent *event,
421 gpointer data)
422{
423 (void)entry;
424 (void)event;
425
426 if (icon_pos == GTK_ENTRY_ICON_PRIMARY)
427 toggle_tls_error_policy((struct Client *)data);
428 else if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
429 set_javascript_policy((struct Client *)data, JSP_TOGGLE);
430}
431
432void
433cb_notebook_modified(GtkNotebook *notebook,
434 GtkWidget *child,
435 guint idx,
436 gpointer data)
437{
438 (void)notebook;
439 (void)child;
440 (void)idx;
441 (void)data;
442
443 save_state();
444}
445
446void
447cb_notebook_switch_page(GtkNotebook *notebook,
448 GtkWidget *child,
449 guint idx,
450 gpointer data)
451{
452 (void)notebook;
453 (void)child;
454 (void)data;
455
456 set_window_title(idx);
457}
458
459gboolean
460cb_open_external(GSimpleAction *action,
461 GVariant *parameter,
462 gpointer data)
463{
464 (void)action;
465 (void)parameter;
466
467 open_external((struct Client *)data, 0);
468 return FALSE;
469}
470
471void
472cb_quit(GObject *obj,
473 gpointer data)
474{
475 (void)obj;
476 (void)data;
477
478 quit();
479}
480
481gboolean
482cb_selection_search(GSimpleAction *action,
483 GVariant *parameter,
484 gpointer data)
485{
486 (void)action;
487 (void)parameter;
488
489 selection_search((struct Client *)data);
490 return FALSE;
491}
492
493void
494cb_selection_search_finished(GObject *object,
495 GAsyncResult *result,
496 gpointer data)
497{
498 struct Client *c = (struct Client *)data;
499
500 (void)object;
501
502 selection_search_finished(c, result);
503}
504
505gboolean
506cb_tab_hid(GtkWidget *widget,
507 GdkEvent *event,
508 gpointer data)
509{
510 (void)widget;
511
512 return key_tab((struct Client *)data, event);
513}
514
515void
516cb_title_changed(GObject *obj,
517 GParamSpec *pspec,
518 gpointer data)
519{
520 (void)obj;
521 (void)pspec;
522
523 update_title((struct Client *)data);
524}
525
526void
527cb_uri_changed(GObject *obj,
528 GParamSpec *pspec,
529 gpointer data)
530{
531 (void)obj;
532 (void)pspec;
533
534 uri_changed((struct Client *)data);
535}
536
537void
538cb_wv_close(GtkWidget *widget,
539 gpointer data)
540{
541 (void)widget;
542
543 client_destroy((struct Client *)data);
544}
545
546gboolean
547cb_wv_crashed(WebKitWebView *web_view,
548 gpointer data)
549{
550 (void)web_view;
551
552 web_view_crashed((struct Client *)data);
553 return TRUE;
554}
555
556WebKitWebView *
557cb_wv_create(WebKitWebView *web_view,
558 WebKitNavigationAction *navigation_action,
559 gpointer data)
560{
561 struct Client *c;
562
563 (void)navigation_action;
564 (void)data;
565
566 if ((c = client_create(NULL, web_view, FALSE, FALSE)) == NULL)
567 return NULL;
568
569 return WEBKIT_WEB_VIEW(c->wv);
570}
571
572gboolean
573cb_wv_decide_policy(WebKitWebView *web_view,
574 WebKitPolicyDecision *decision,
575 WebKitPolicyDecisionType type,
576 gpointer data)
577{
578 (void)web_view;
579 (void)data;
580
581 /* only handle policy decisions */
582 if (type != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
583 return FALSE;
584 /* check if: 'HTTP/1.1 204 No Content'
585 * this should be ignored, but isn't
586 * see: https://bugs.webkit.org/show_bug.cgi?id=60206
587 * */
588 else if (webkit_uri_response_get_status_code(
589 webkit_response_policy_decision_get_response(
590 WEBKIT_RESPONSE_POLICY_DECISION(decision))) == 204
591 )
592 webkit_policy_decision_ignore(decision);
593 /* continue as normal */
594 else if (!webkit_response_policy_decision_is_mime_type_supported(
595 WEBKIT_RESPONSE_POLICY_DECISION(decision))
596 )
597 webkit_policy_decision_download(decision);
598 else
599 webkit_policy_decision_use(decision);
600 return TRUE;
601}
602
603gboolean
604cb_wv_hid(GtkWidget *widget,
605 GdkEvent *event,
606 gpointer data)
607{
608 (void)widget;
609
610 return key_web_view((struct Client *)data, event);
611}
612
613void
614cb_wv_hover(WebKitWebView *web_view,
615 WebKitHitTestResult *hit_test_result,
616 guint modifiers,
617 gpointer data)
618{
619 (void)web_view;
620 (void)modifiers;
621
622 set_hover_uri((struct Client *)data, hit_test_result);
623}
624
625void
626cb_wv_load_changed(GObject *obj,
627 GParamSpec *pspec,
628 gpointer data)
629{
630 (void)obj;
631
632 load_changed((struct Client *)data, (WebKitLoadEvent)pspec);
633}
634
635void
636cb_wv_load_progress_changed(GObject *obj,
637 GParamSpec *pspec,
638 gpointer data)
639{
640 (void)obj;
641 (void)pspec;
642
643 update_load_progress((struct Client *)data);
644}
645
646void
647cb_wv_show(WebKitWebView *web_view,
648 gpointer data)
649{
650 (void)web_view;
651
652 show_web_view((struct Client *)data);
653}
654
655gboolean
656cb_wv_tls_load_failed(GObject *obj,
657 gchar *uri,
658 GTlsCertificate *crt,
659 GTlsCertificateFlags err,
660 gpointer data)
661{
662 (void)obj;
663
664 render_tls_error((struct Client *)data, uri, crt, err);
665 return TRUE;
666}
667
668#endif /* !BROWSER_H */