From 8e7c573c83acb2b58a05447f6fb0945a37d140ab Mon Sep 17 00:00:00 2001 From: "Andre A. Gomes" Date: Fri, 23 Feb 2024 12:14:20 +0200 Subject: [PATCH 01/19] foreign-interface: Add ffi-register-custom-scheme. --- source/foreign-interface.lisp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/foreign-interface.lisp b/source/foreign-interface.lisp index cdb2b1c6767..8897503e6cc 100644 --- a/source/foreign-interface.lisp +++ b/source/foreign-interface.lisp @@ -123,6 +123,10 @@ When URL is non-nil, relative URLs are resolved against it.") Like `ffi-buffer-load-html', except that it doesn't influence the BUFFER history or CSS/HTML cache.") +(define-ffi-generic ffi-register-custom-scheme (scheme) + "Register internal custom SCHEME. +See `scheme'.") + (define-ffi-generic ffi-buffer-evaluate-javascript (buffer javascript &optional world-name) "Evaluate JAVASCRIPT, encoded as a string, in BUFFER.") (define-ffi-generic ffi-buffer-evaluate-javascript-async (buffer javascript &optional world-name) From 552a4d456db1ae66d03f42b35f24b948fc2b0b4c Mon Sep 17 00:00:00 2001 From: "Andre A. Gomes" Date: Mon, 26 Feb 2024 15:31:06 +0200 Subject: [PATCH 02/19] urls: Refactor scheme class and define-internal-scheme. --- source/urls.lisp | 75 +++++++++++++----------------------------------- 1 file changed, 20 insertions(+), 55 deletions(-) diff --git a/source/urls.lisp b/source/urls.lisp index 97eabd53d1a..fe91551e6a3 100644 --- a/source/urls.lisp +++ b/source/urls.lisp @@ -92,48 +92,27 @@ The URL is fetched, which explains possible bottlenecks." Should be redefined by the renderer.")) (define-class scheme (renderer-scheme) - ((name (error "Scheme must have a name/scheme") - :documentation "Scheme/name of the internal scheme. -For instance, \"gopher\", \"irc\".") + ((name + (alex:required-argument 'name) + :documentation "The custom scheme name to handle. +HTTPS or FILE are examples of schemes.") (callback nil :type (or null function) - :documentation "Callback to get the page contents when accessing resource with this scheme. - -Takes two arguments: the URL with scheme and the buffer it was requested in. - -Returns up to five values: -- The data for page contents (either as string or as a unsigned byte array). -- The MIME type for the contents. -- The status code for the request. -- An alist of headers for the request. -- A status reason phrase.") + :documentation "A function called on URL load that returns the page contents. + +It takes the URL as an argument and returns up to 5 values: +- The data for page contents (either as string or as a unsigned byte array) +- The MIME type for the contents +- The status code for the request +- An alist of headers for the request +- A status reason phrase") (error-callback nil :type (or null function) :documentation "Callback to use when a condition is signaled. -Accepts only one argument: the signaled condition.") - (local-p - nil - :documentation "Local schemes are not accessible to the pages of other schemes.") - (no-access-p - nil - :documentation "No-access schemes cannot access pages with any other scheme.") - (secure-p - nil - :documentation "Secure schemes can access the Web, Web can access them too. - -Requires encryption or other means of security.") - (cors-enabled-p - nil - :documentation "Whether other pages can do requests to the resources with this scheme.") - (display-isolated-p - nil - :documentation "Display-isolated schemes cannot be displayed (in iframes, for example) by other schemes.") - (empty-document-p - nil - :documentation "Empty document schemes can be loaded synchronously by websites referring to them.")) +Accepts only one argument: the signaled condition.")) (:export-class-name-p t) (:export-accessor-names-p t) (:documentation "Representation of Nyxt-specific internal schemes. @@ -147,30 +126,16 @@ content. In case something goes wrong, runs `error-callback'.") (defvar *schemes* (sera:dict) "A table of internal schemes registered in Nyxt. -Keys are scheme strings, values are `scheme' objects.") +It maps scheme names as strings to `scheme' objects.") (export-always 'define-internal-scheme) -(defun define-internal-scheme (scheme-name callback - &rest keys - &key local-p - no-access-p - secure-p - cors-enabled-p - &allow-other-keys) - "Define a handler (running CALLBACK) for SCHEME-NAME `scheme'. - -CALLBACK is called with two arguments: -- the URL that was requested with this scheme, and -- buffer that it was requested in. - -For keyword arguments' meaning, see the corresponding `scheme' slot -documentation." - (declare (ignorable local-p no-access-p secure-p cors-enabled-p)) +(defun define-internal-scheme (scheme-name callback &optional error-callback) + "Define handler CALLBACK for SCHEME-NAME `scheme'. + +See the `callback' and `error-callback' slot documentation for their type +signatures." (setf (gethash scheme-name *schemes*) - (apply #'make-instance 'scheme - :name scheme-name - :callback callback - keys))) + (list callback error-callback))) (defmemo lookup-hostname (name) "Resolve hostname NAME and memoize the result." From a688e8cd91fbf049162d514730801cfe53d73117 Mon Sep 17 00:00:00 2001 From: "Andre A. Gomes" Date: Fri, 23 Feb 2024 15:09:21 +0200 Subject: [PATCH 03/19] renderer/gtk: Refactor scheme handling. The handling of custom schemes is implemented in a renderer-agnostic fashion. --- source/renderer/gtk.lisp | 153 +++++++++++++++++++++++++++------------ 1 file changed, 106 insertions(+), 47 deletions(-) diff --git a/source/renderer/gtk.lisp b/source/renderer/gtk.lisp index 6d17812ef94..60352db83ba 100644 --- a/source/renderer/gtk.lisp +++ b/source/renderer/gtk.lisp @@ -810,11 +810,98 @@ See `gtk-browser's `modifier-translator' slot." (funcall (input-dispatcher window) event sender window)))) (define-class gtk-scheme () - () + ((context + nil + :writer nil + :reader t + :documentation "See `webkit-web-context'.") + (local-p + nil + :writer nil + :reader t + :documentation "Whether pages of other URI schemes cannot access URIs of +this scheme.") + (no-access-p + nil + :writer nil + :reader t + :documentation "Whether pages of this URI scheme cannot access other URI schemes.") + (secure-p + nil + :writer nil + :reader t + :documentation "Whether mixed content warnings aren't generated for this +scheme when included by an HTTPS page. + +See https://developer.mozilla.org/en-US/docs/Web/Security/Mixed_content.") + (cors-enabled-p + nil + :writer nil + :reader t + :documentation "Whether CORS requests are allowed.") + (display-isolated-p + nil + :writer nil + :reader t + :documentation "Whether pages cannot display URIs unless they are from the +same scheme. +For example, pages in another origin cannot create iframes or hyperlinks to URIs +with this scheme.") + (empty-document-p + nil + :writer nil + :reader t + :documentation "Whether pages are allowed to be loaded synchronously.")) (:export-class-name-p t) (:export-accessor-names-p t) (:documentation "Related to WebKit's custom schemes.")) +(defmethod manager ((scheme gtk-scheme)) + (webkit:webkit-web-context-get-security-manager (context scheme))) + +(defmethod (setf local-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-local (manager scheme) + (name scheme))) + (setf (slot-value scheme 'local-p) value)) + +(defmethod (setf no-access-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-no-access (manager scheme) + (name scheme))) + (setf (slot-value scheme 'no-access-p) value)) + +(defmethod (setf secure-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-secure (manager scheme) + (name scheme))) + (setf (slot-value scheme 'secure-p) value)) + +(defmethod (setf cors-enabled-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-cors-enabled (manager scheme) + (name scheme))) + (setf (slot-value scheme 'cors-enabled-p) value)) + +(defmethod (setf display-isolated-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-display-isolated (manager scheme) + (name scheme))) + (setf (slot-value scheme 'display-isolated-p) value)) + +(defmethod (setf empty-document-p) (value (scheme gtk-scheme)) + (when value + (webkit:webkit-security-manager-register-uri-scheme-as-empty-document (manager scheme) + (name scheme))) + (setf (slot-value scheme 'empty-document-p) value)) + +(defmethod initialize-instance :after ((scheme gtk-scheme) &key) + (match (name scheme) + ("nyxt-resource" (setf (secure-p scheme) t)) + ("lisp" (setf (cors-enabled-p scheme) t)) + ("view-source" (setf (no-access-p scheme) t)) + (_ t))) + ;; From https://github.com/umpirsky/language-list/tree/master/data directory ;; listing $(ls /data) and processed with: ;; (defun iso-languages (languages-file-string) @@ -884,6 +971,17 @@ See `gtk-browser's `modifier-translator' slot." pointer) (cffi:foreign-free pointer))) +(defmethod ffi-register-custom-scheme ((scheme gtk-scheme)) + ;; FIXME If a define-internal-scheme is updated at runtime, it is not honored. + (webkit:webkit-web-context-register-uri-scheme-callback + (context scheme) + (name scheme) + (lambda (request) + (funcall* (callback scheme) + (webkit:webkit-uri-scheme-request-get-uri request))) + (or (error-callback scheme) + (lambda (c) (echo-warning "Error while routing ~s resource: ~a" scheme c))))) + (defun make-context (name &key ephemeral-p) (let* ((context (if ephemeral-p @@ -932,52 +1030,13 @@ See `gtk-browser's `modifier-translator' slot." (declare (ignore context)) (with-protect ("Error in \"download-started\" signal thread: ~a" :condition) (wrap-download download)))) - (maphash - (lambda (scheme scheme-object) - (webkit:webkit-web-context-register-uri-scheme-callback - context scheme - (lambda (request) - (let ((nyxt::*interactive-p* t) - ;; Look up the scheme-object again so that we can live-update - ;; the callback without having to create a new view with a new - ;; context. - (scheme-object (gethash scheme nyxt::*schemes*))) - (funcall* (callback scheme-object) - (webkit:webkit-uri-scheme-request-get-uri request) - (find (webkit:webkit-uri-scheme-request-get-web-view request) - (delete nil - (append (list (status-buffer (current-window)) - (message-buffer (current-window))) - (nyxt::active-prompt-buffers (current-window)) - (nyxt::panel-buffers (current-window)) - (buffer-list))) - :key #'gtk-object)))) - (or (error-callback scheme-object) - (lambda (condition) - (echo-warning "Error while routing ~s resource: ~a" scheme condition)))) - ;; We err on the side of caution, assigning the most restrictive policy - ;; out of those provided. Should it be the other way around? - (let ((manager (webkit:webkit-web-context-get-security-manager context))) - (cond - ((local-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-local - manager scheme)) - ((no-access-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-no-access - manager scheme)) - ((display-isolated-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-display-isolated - manager scheme)) - ((secure-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-secure - manager scheme)) - ((cors-enabled-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-cors-enabled - manager scheme)) - ((empty-document-p scheme-object) - (webkit:webkit-security-manager-register-uri-scheme-as-empty-document - manager scheme))))) - nyxt::*schemes*) + (maphash (lambda (scheme-name callbacks) + (ffi-register-custom-scheme (make-instance 'scheme + :name scheme-name + :context context + :callback (first callbacks) + :error-callback (second callbacks)))) + nyxt::*schemes*) (unless (or ephemeral-p (internal-context-p name)) (let ((cookies-path (files:expand (make-instance 'cookies-file :context-name name)))) From eb87fb7d08cd6ed003a4f00764b62aacd52cc56b Mon Sep 17 00:00:00 2001 From: "Andre A. Gomes" Date: Fri, 23 Feb 2024 15:46:17 +0200 Subject: [PATCH 04/19] Move nyxt scheme comment. --- source/renderer/gtk.lisp | 11 +++++++++++ source/urls.lisp | 14 -------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/source/renderer/gtk.lisp b/source/renderer/gtk.lisp index 60352db83ba..44d7a9edc70 100644 --- a/source/renderer/gtk.lisp +++ b/source/renderer/gtk.lisp @@ -896,6 +896,17 @@ with this scheme.") (setf (slot-value scheme 'empty-document-p) value)) (defmethod initialize-instance :after ((scheme gtk-scheme) &key) + ;; NOTE: No security settings for the nyxt scheme since: + ;; - :local-p makes it inaccessible from other schemes. + ;; - :display-isolated-p does not allow embedding a nyxt scheme page inside a + ;; page of the same scheme. + ;; - :secure-p and :cors-enabled-p are too permissive for a scheme that allows + ;; evaluating Lisp code. + ;; Therefore, no settings provide the best configuration so that: + ;; -