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 gchar *rebuild_filename(gchar *, int);
248static void render_tls_error(struct Client *, gchar *, GTlsCertificate *,
249 GTlsCertificateFlags);
250static void run_user_scripts(WebKitWebView *);
251static void save_history(const gchar *);
252static void save_state(void);
253static void search(struct Client *, enum inpage_search_type);
254static void selection_search(struct Client *);
255static void selection_search_finished(struct Client *, GAsyncResult *);
256static void set_default_web_context(void);
257static void set_hover_uri(struct Client *, WebKitHitTestResult *);
258static void set_javascript_policy(struct Client *, enum javascript_policy);
259static void set_window_title(gint);
260static void show_web_view(struct Client *);
261static void toggle_inspector(struct Client *);
262static void toggle_tls_error_policy(struct Client *);
263static gchar *resolve_uri(const gchar *);
264static char *resolve_uri_words(int, char **);
265static void update_download_button(WebKitDownload *, GtkButton *, gboolean);
266static void update_favicon(struct Client *);
267static void update_load_progress(struct Client *);
268static void update_title(struct Client *);
269static void uri_changed(struct Client *);
270static void web_view_crashed(struct Client *);
271static void xdg_open(const gchar *, const gchar *);
272
273/* callback functions */
274static gboolean cb_context_menu(WebKitWebView *, WebKitContextMenu *,
275 GdkEvent *, WebKitHitTestResult *, gpointer);
276static void cb_download_changed_progress(GObject *, GParamSpec *, gpointer);
277static void cb_download_finished(GObject *, gpointer);
278static gboolean cb_download_prepare(WebKitDownload *, gchar *, gpointer);
279static void cb_download_press(GtkWidget *, GdkEventButton *, gpointer);
280static void cb_download_start(WebKitWebView *, WebKitDownload *, gpointer);
281static void cb_favicon_changed(GObject *, GParamSpec *, gpointer);
282static gboolean cb_entry_hid(GtkWidget *, GdkEvent *, gpointer);
283static void cb_entry_icon_hid(GtkEntry *, GtkEntryIconPosition, GdkEvent *,
284 gpointer);
285static void cb_notebook_modified(GtkNotebook *, GtkWidget *, guint, gpointer);
286static void cb_notebook_switch_page(GtkNotebook *, GtkWidget *, guint,
287 gpointer);
288static gboolean cb_open_external(GSimpleAction *, GVariant *, gpointer);
289static gboolean cb_selection_search(GSimpleAction *, GVariant *, gpointer);
290static void cb_selection_search_finished(GObject *, GAsyncResult *, gpointer);
291static void cb_quit(GObject *, gpointer);
292static gboolean cb_tab_hid(GtkWidget *, GdkEvent *, gpointer);
293static void cb_title_changed(GObject *, GParamSpec *, gpointer);
294static void cb_uri_changed(GObject *, GParamSpec *, gpointer);
295static void cb_wv_close(GtkWidget *, gpointer);
296static gboolean cb_wv_crashed(WebKitWebView *, gpointer);
297static WebKitWebView *cb_wv_create(WebKitWebView *, WebKitNavigationAction *,
298 gpointer);
299static gboolean cb_wv_decide_policy(WebKitWebView *, WebKitPolicyDecision *,
300 WebKitPolicyDecisionType, gpointer);
301static gboolean cb_wv_hid(GtkWidget *, GdkEvent *, gpointer);
302static void cb_wv_hover(WebKitWebView *, WebKitHitTestResult *, guint,
303 gpointer);
304static void cb_wv_load_changed(GObject *, GParamSpec *, gpointer);
305static void cb_wv_load_progress_changed(GObject *, GParamSpec *, gpointer);
306static void cb_wv_show(WebKitWebView *, gpointer);
307static gboolean cb_wv_tls_load_failed(GObject *, gchar *, GTlsCertificate *,
308 GTlsCertificateFlags, gpointer);
309
310
311gboolean
312cb_context_menu(WebKitWebView *web_view,
313 WebKitContextMenu *context_menu,
314 GdkEvent *event,
315 WebKitHitTestResult *hit_test_result,
316 gpointer data)
317{
318 (void)web_view;
319 (void)event;
320
321 create_context_menu((struct Client *)data, context_menu, hit_test_result);
322 return FALSE;
323}
324
325void
326cb_download_changed_progress(GObject *obj,
327 GParamSpec *pspec,
328 gpointer data)
329{
330 (void)pspec;
331
332 update_download_button(WEBKIT_DOWNLOAD(obj), GTK_BUTTON(data), FALSE);
333}
334
335void
336cb_download_finished(GObject *obj,
337 gpointer data)
338{
339 if (!GTK_IS_BUTTON(data))
340 return;
341
342 (*((gboolean*)g_object_get_data(data, __NAME__"-finished"))) = TRUE;
343
344 update_download_button(WEBKIT_DOWNLOAD(obj), GTK_BUTTON(data), TRUE);
345 gtk_button_set_image(
346 GTK_BUTTON(data),
347 gtk_image_new_from_icon_name(ICON_FINISHED, GTK_ICON_SIZE_BUTTON)
348 );
349}
350
351gboolean
352cb_download_prepare(WebKitDownload *download,
353 gchar *suggested_filename,
354 gpointer data)
355{
356 (void)data;
357
358 prepare_download(download, suggested_filename);
359 return FALSE;
360}
361
362void
363cb_download_press(GtkWidget *btn,
364 GdkEventButton *event,
365 gpointer data)
366{
367 switch (event->button) {
368 case 3: /* right click: */
369 case 2: /* middle click: cancel and/or remove */
370 if (!(*((gboolean*)g_object_get_data(G_OBJECT(btn), __NAME__"-finished"))))
371 webkit_download_cancel(WEBKIT_DOWNLOAD(data));
372 g_object_unref(WEBKIT_DOWNLOAD(data));
373 gtk_widget_destroy(btn);
374 break;
375 case 1: /* left click: open downloaded file */
376 if ((*((gboolean*)g_object_get_data(G_OBJECT(btn), __NAME__"-finished"))))
377 xdg_open("", webkit_download_get_destination(WEBKIT_DOWNLOAD(data)));
378 break;
379 }
380}
381
382void
383cb_download_start(WebKitWebView *web_view,
384 WebKitDownload *download,
385 gpointer data)
386{
387 (void)web_view;
388
389 CB(download, "decide-destination", cb_download_prepare, data);
390}
391
392void
393cb_favicon_changed(GObject *obj,
394 GParamSpec *pspec,
395 gpointer data)
396{
397 (void)obj;
398 (void)pspec;
399
400 update_favicon((struct Client *)data);
401}
402
403gboolean
404cb_entry_hid(GtkWidget *widget,
405 GdkEvent *event,
406 gpointer data)
407{
408 (void)widget;
409
410 /* only handle key presses */
411 if (event->type == GDK_KEY_PRESS)
412 return key_entry((struct Client *)data, (GdkEventKey*)event);
413
414 return FALSE;
415
416}
417
418void
419cb_entry_icon_hid(GtkEntry *entry,
420 GtkEntryIconPosition icon_pos,
421 GdkEvent *event,
422 gpointer data)
423{
424 (void)entry;
425 (void)event;
426
427 if (icon_pos == GTK_ENTRY_ICON_PRIMARY)
428 toggle_tls_error_policy((struct Client *)data);
429 else if (icon_pos == GTK_ENTRY_ICON_SECONDARY)
430 set_javascript_policy((struct Client *)data, JSP_TOGGLE);
431}
432
433void
434cb_notebook_modified(GtkNotebook *notebook,
435 GtkWidget *child,
436 guint idx,
437 gpointer data)
438{
439 (void)notebook;
440 (void)child;
441 (void)idx;
442 (void)data;
443
444 save_state();
445}
446
447void
448cb_notebook_switch_page(GtkNotebook *notebook,
449 GtkWidget *child,
450 guint idx,
451 gpointer data)
452{
453 (void)notebook;
454 (void)child;
455 (void)data;
456
457 set_window_title(idx);
458}
459
460gboolean
461cb_open_external(GSimpleAction *action,
462 GVariant *parameter,
463 gpointer data)
464{
465 (void)action;
466 (void)parameter;
467
468 open_external((struct Client *)data, 0);
469 return FALSE;
470}
471
472void
473cb_quit(GObject *obj,
474 gpointer data)
475{
476 (void)obj;
477 (void)data;
478
479 quit();
480}
481
482gboolean
483cb_selection_search(GSimpleAction *action,
484 GVariant *parameter,
485 gpointer data)
486{
487 (void)action;
488 (void)parameter;
489
490 selection_search((struct Client *)data);
491 return FALSE;
492}
493
494void
495cb_selection_search_finished(GObject *object,
496 GAsyncResult *result,
497 gpointer data)
498{
499 struct Client *c = (struct Client *)data;
500
501 (void)object;
502
503 selection_search_finished(c, result);
504}
505
506gboolean
507cb_tab_hid(GtkWidget *widget,
508 GdkEvent *event,
509 gpointer data)
510{
511 (void)widget;
512
513 return key_tab((struct Client *)data, event);
514}
515
516void
517cb_title_changed(GObject *obj,
518 GParamSpec *pspec,
519 gpointer data)
520{
521 (void)obj;
522 (void)pspec;
523
524 update_title((struct Client *)data);
525}
526
527void
528cb_uri_changed(GObject *obj,
529 GParamSpec *pspec,
530 gpointer data)
531{
532 (void)obj;
533 (void)pspec;
534
535 uri_changed((struct Client *)data);
536}
537
538void
539cb_wv_close(GtkWidget *widget,
540 gpointer data)
541{
542 (void)widget;
543
544 client_destroy((struct Client *)data);
545}
546
547gboolean
548cb_wv_crashed(WebKitWebView *web_view,
549 gpointer data)
550{
551 (void)web_view;
552
553 web_view_crashed((struct Client *)data);
554 return TRUE;
555}
556
557WebKitWebView *
558cb_wv_create(WebKitWebView *web_view,
559 WebKitNavigationAction *navigation_action,
560 gpointer data)
561{
562 struct Client *c;
563
564 (void)navigation_action;
565 (void)data;
566
567 if ((c = client_create(NULL, web_view, FALSE, FALSE)) == NULL)
568 return NULL;
569
570 return WEBKIT_WEB_VIEW(c->wv);
571}
572
573gboolean
574cb_wv_decide_policy(WebKitWebView *web_view,
575 WebKitPolicyDecision *decision,
576 WebKitPolicyDecisionType type,
577 gpointer data)
578{
579 (void)web_view;
580 (void)data;
581
582 /* only handle policy decisions */
583 if (type != WEBKIT_POLICY_DECISION_TYPE_RESPONSE)
584 return FALSE;
585 /* check if: 'HTTP/1.1 204 No Content'
586 * this should be ignored, but isn't
587 * see: https://bugs.webkit.org/show_bug.cgi?id=60206
588 * */
589 else if (webkit_uri_response_get_status_code(
590 webkit_response_policy_decision_get_response(
591 WEBKIT_RESPONSE_POLICY_DECISION(decision))) == 204
592 )
593 webkit_policy_decision_ignore(decision);
594 /* continue as normal */
595 else if (!webkit_response_policy_decision_is_mime_type_supported(
596 WEBKIT_RESPONSE_POLICY_DECISION(decision))
597 )
598 webkit_policy_decision_download(decision);
599 else
600 webkit_policy_decision_use(decision);
601 return TRUE;
602}
603
604gboolean
605cb_wv_hid(GtkWidget *widget,
606 GdkEvent *event,
607 gpointer data)
608{
609 (void)widget;
610
611 return key_web_view((struct Client *)data, event);
612}
613
614void
615cb_wv_hover(WebKitWebView *web_view,
616 WebKitHitTestResult *hit_test_result,
617 guint modifiers,
618 gpointer data)
619{
620 (void)web_view;
621 (void)modifiers;
622
623 set_hover_uri((struct Client *)data, hit_test_result);
624}
625
626void
627cb_wv_load_changed(GObject *obj,
628 GParamSpec *pspec,
629 gpointer data)
630{
631 (void)obj;
632
633 load_changed((struct Client *)data, (WebKitLoadEvent)pspec);
634}
635
636void
637cb_wv_load_progress_changed(GObject *obj,
638 GParamSpec *pspec,
639 gpointer data)
640{
641 (void)obj;
642 (void)pspec;
643
644 update_load_progress((struct Client *)data);
645}
646
647void
648cb_wv_show(WebKitWebView *web_view,
649 gpointer data)
650{
651 (void)web_view;
652
653 show_web_view((struct Client *)data);
654}
655
656gboolean
657cb_wv_tls_load_failed(GObject *obj,
658 gchar *uri,
659 GTlsCertificate *crt,
660 GTlsCertificateFlags err,
661 gpointer data)
662{
663 (void)obj;
664
665 render_tls_error((struct Client *)data, uri, crt, err);
666 return TRUE;
667}
668
669#endif /* !BROWSER_H */