Release to allow opening links in new buffer. master v3.6.0
authorplugd <plugd@thelambdalab.xyz>
Sun, 24 Mar 2024 13:07:33 +0000 (14:07 +0100)
committerplugd <plugd@thelambdalab.xyz>
Sun, 24 Mar 2024 13:15:55 +0000 (14:15 +0100)
ISSUES.org
RELEASE
config.mk
elpher-pkg.el
elpher.el
elpher.texi

index 60c6a99..363c25f 100644 (file)
@@ -4,7 +4,7 @@
 
 * Open Bugs
 
 
 * Open Bugs
 
-** OPEN Sanitize certificate names
+** OPEN Sanitize certificate names :gemini:
    :LOGBOOK:
    - State "OPEN"       from              [2020-06-22 Mon 10:32]
    :END:
    :LOGBOOK:
    - State "OPEN"       from              [2020-06-22 Mon 10:32]
    :END:
@@ -125,6 +125,20 @@ pop the stack, meaning that subsequent "u" commands would succeed.
 The fix is just to zero out the history list in the `elpher` function just as
 `elpher-current-page` is cleared.
 
 The fix is just to zero out the history list in the `elpher` function just as
 `elpher-current-page` is cleared.
 
+** CLOSED Improve client certificate scope :gemini:
+:LOGBOOK:
+- State "CLOSED"     from "OPEN"       [2023-05-05 Fri 10:09]
+- State "OPEN"       from              [2022-10-12 Wed 09:33]
+:END:
+
+Once activated, elpher continues to use a client certificate
+for any connections to the host on which it was activated.
+However, it's now common to restrict certificates also to paths
+_below_ the path where the certificate was activated.
+
+I.e. gemini://example.com/~userA/ certificates are not applied
+automatically to gemini://example.com/~userB/.
+
 * Open Enhancements
 
 ** OPEN Allow multiple elpher buffers [33%]
 * Open Enhancements
 
 ** OPEN Allow multiple elpher buffers [33%]
@@ -138,16 +152,6 @@ this can happen:
 - [X] shift history out of node tree and into separate stack
 - [ ] make history stack variables buffer-local
 - [ ] have elpher-with-clean-buffer select appropriate buffer 
 - [X] shift history out of node tree and into separate stack
 - [ ] make history stack variables buffer-local
 - [ ] have elpher-with-clean-buffer select appropriate buffer 
-   
-** OPEN Make installing existing certificates easier
-   :LOGBOOK:
-   - State "OPEN"       from "CLOSED"     [2020-06-22 Mon 10:34]
-   :END:
-
-It's naive to think that people don't have client certificates created
-outside of elpher. Thus we need some easy way to "install" these
-certificates, either by copying them or by referencing them in some
-way.
 
 * Closed Enhancements
   
 
 * Closed Enhancements
   
@@ -316,3 +320,15 @@ and there aren't any other hotspots.
 - State "CLOSED"     from "OPEN"       [2021-08-09 Mon 17:46]
 :END:
 This used to be available, but was removed during a refactor.
 - State "CLOSED"     from "OPEN"       [2021-08-09 Mon 17:46]
 :END:
 This used to be available, but was removed during a refactor.
+
+
+** CLOSED Make installing existing certificates easier
+   :LOGBOOK:
+   - State "CLOSED"     from "OPEN"       [2023-05-05 Fri 10:10]
+   - State "OPEN"       from "CLOSED"     [2020-06-22 Mon 10:34]
+   :END:
+
+It's naive to think that people don't have client certificates created
+outside of elpher. Thus we need some easy way to "install" these
+certificates, either by copying them or by referencing them in some
+way.
diff --git a/RELEASE b/RELEASE
index ade220d..1a08de8 100644 (file)
--- a/RELEASE
+++ b/RELEASE
@@ -7,9 +7,15 @@ When preparing a new release, set the version number:
 2. in elpher.el:     metadata at the top
 3. in elpher.el:     definition of elpher-version
 4. in elpher-pkg.el: second argument to 'define-package'
 2. in elpher.el:     metadata at the top
 3. in elpher.el:     definition of elpher-version
 4. in elpher-pkg.el: second argument to 'define-package'
+
+For anything besides a patch release, a note describing
+the changes should be added to the documentation.  In
+the instance that the documentation itself is significantly
+changed, also update the documentation version:
+
 5. in elpher.texi:   'settitle' declaration at the top
 
 5. in elpher.texi:   'settitle' declaration at the top
 
-Make sure the documentation is up to date and builds:
+After any documentation updates, make sure it builds:
 
     make elpher.info elpher.html elpher.pdf
 
 
     make elpher.info elpher.html elpher.pdf
 
index e406f6b..46598bf 100644 (file)
--- a/config.mk
+++ b/config.mk
@@ -1,5 +1,5 @@
 PKG     = elpher
 PKG     = elpher
-VERSION = 3.4.2
+VERSION = 3.6.0
 
 INSTALLINFO = install-info
 MAKEINFO    = makeinfo
 
 INSTALLINFO = install-info
 MAKEINFO    = makeinfo
index d7becef..8c9c6a6 100644 (file)
@@ -1,4 +1,4 @@
-(define-package "elpher" "3.4.2" "A friendly gopher and gemini client"
+(define-package "elpher" "3.6.0" "A friendly gopher and gemini client"
   '((emacs "27.1"))
   :keywords ("convenience")
   :authors (("Tim Vaughan" . "plugd@thelambdalab.xyz"))
   '((emacs "27.1"))
   :keywords ("convenience")
   :authors (("Tim Vaughan" . "plugd@thelambdalab.xyz"))
index b18a897..c3c3dc7 100644 (file)
--- a/elpher.el
+++ b/elpher.el
@@ -1,12 +1,12 @@
 ;;; elpher.el --- A friendly gopher and gemini client  -*- lexical-binding: t -*-
 
 ;;; elpher.el --- A friendly gopher and gemini client  -*- lexical-binding: t -*-
 
-;; Copyright (C) 2019-2022 Tim Vaughan <plugd@thelambdalab.xyz>
+;; Copyright (C) 2019-2023 Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Copyright (C) 2020-2022 Elpher contributors (See info manual for full list)
 
 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Created: 11 April 2019
 ;; Copyright (C) 2020-2022 Elpher contributors (See info manual for full list)
 
 ;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
 ;; Created: 11 April 2019
-;; Version: 3.4.2
-;; Keywords: comm gopher
+;; Version: 3.6.0
+;; Keywords: comm gopher gemini
 ;; Homepage: https://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "27.1"))
 
 ;; Homepage: https://thelambdalab.xyz/elpher
 ;; Package-Requires: ((emacs "27.1"))
 
@@ -71,7 +71,7 @@
 ;;; Global constants
 ;;
 
 ;;; Global constants
 ;;
 
-(defconst elpher-version "3.4.2"
+(defconst elpher-version "3.6.0"
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
   "Current version of elpher.")
 
 (defconst elpher-margin-width 6
@@ -241,6 +241,14 @@ meaningfully."
   "Label of button used to toggle formatted text."
   :type '(string))
 
   "Label of button used to toggle formatted text."
   :type '(string))
 
+(defcustom elpher-certificate-map nil
+  "Register client certificates to be used for gemini URLs.
+This variable contains an alist representing a mapping between gemini
+URLs and the names of client certificates which will be automatically
+activated for those URLs.  Beware that the certificates will also be
+active for all subdirectories of the given URLs."
+  :type '(alist :key-type string :value-type string))
+
 ;; Face customizations
 
 (defgroup elpher-faces nil
 ;; Face customizations
 
 (defgroup elpher-faces nil
@@ -368,7 +376,7 @@ is not explicitly given."
 
 (defun elpher-remove-redundant-ports (address)
   "Remove redundant port specifiers from ADDRESS.
 
 (defun elpher-remove-redundant-ports (address)
   "Remove redundant port specifiers from ADDRESS.
-Here 'redundant' means that the specified port matches the default
+Here `redundant' means that the specified port matches the default
 for that protocol, eg 70 for gopher."
   (if (and (not (elpher-address-about-p address))
            (eq (url-portspec address) ; (url-port) is too slow!
 for that protocol, eg 70 for gopher."
   (if (and (not (elpher-address-about-p address))
            (eq (url-portspec address) ; (url-port) is too slow!
@@ -691,6 +699,57 @@ If LINE is non-nil, replace that line instead."
               (replace-match string))
           (set-match-data data))))))
 
               (replace-match string))
           (set-match-data data))))))
 
+;;; Link button definitions
+;;
+
+(defvar elpher-link-keymap
+  (let ((map (make-sparse-keymap)))
+    (keymap-set map "S-<down-mouse-1>" 'ignore) ;Prevent buffer face popup
+    (keymap-set map "S-<mouse-1>" #'elpher--open-link-new-buffer-mouse)
+    (keymap-set map "S-<return>" #'elpher--open-link-new-buffer)
+    (set-keymap-parent map button-map)
+    map))
+
+(defun elpher--click-link (button)
+  "Function called when the gopher link BUTTON is activated."
+  (let ((page (button-get button 'elpher-page)))
+    (elpher-visit-page page)))
+
+(defun elpher--open-link-new-buffer ()
+  "Internal function used by Elpher to open links in a new buffer."
+  (interactive)
+  (let ((page (button-get (button-at (point)) 'elpher-page))
+        (new-buf (generate-new-buffer (default-value 'elpher-buffer-name))))
+    (pop-to-buffer new-buf)
+    (elpher-mode)
+    (elpher-visit-page page)))
+
+(defun elpher--open-link-new-buffer-mouse (event)
+  "Internal function used by Elpher to open links in a new buffer.
+The EVENT argument is the mouse event which caused this function to be
+called."
+  (interactive "e")
+  (mouse-set-point event)
+  (elpher--open-link-new-buffer))
+
+(defun elpher--page-button-help (_window buffer pos)
+  "Function called by Emacs to generate mouse-over text.
+The arguments specify the BUFFER and the POS within the buffer of the item
+for which help is required.  The function returns the help to be
+displayed.  The _WINDOW argument is currently unused."
+  (with-current-buffer buffer
+    (let ((button (button-at pos)))
+      (when button
+        (let* ((page (button-get button 'elpher-page))
+               (address (elpher-page-address page)))
+          (format "mouse-1, RET: open '%s'" (elpher-address-to-url address)))))))
+
+(define-button-type 'elpher-link
+  'action #'elpher--click-link
+  'keymap elpher-link-keymap
+  'follow-link t
+  'help-echo #'elpher--page-button-help
+  'face 'button)
 
 ;;; Text Processing
 ;;
 
 ;;; Text Processing
 ;;
@@ -727,11 +786,8 @@ away CRs and any terminating period."
       (let ((page (elpher-page-from-url (substring-no-properties (match-string 0)))))
         (make-text-button (match-beginning 0)
                           (match-end 0)
       (let ((page (elpher-page-from-url (substring-no-properties (match-string 0)))))
         (make-text-button (match-beginning 0)
                           (match-end 0)
-                          'elpher-page  page
-                          'action #'elpher-click-link
-                          'follow-link t
-                          'help-echo #'elpher--page-button-help
-                          'face 'button)))
+                          'elpher-page page
+                          :type 'elpher-link)))
     (buffer-string)))
 
 
     (buffer-string)))
 
 
@@ -951,7 +1007,8 @@ the host operating system and the local network capabilities.)"
 ;;; Client-side TLS Certificate Management
 ;;
 
 ;;; Client-side TLS Certificate Management
 ;;
 
-(defun elpher-generate-certificate (common-name key-file cert-file &optional temporary)
+(defun elpher-generate-certificate (common-name key-file cert-file url-prefix
+                                                &optional temporary)
   "Generate a key and a self-signed client TLS certificate using openssl.
 
 The Common Name field of the certificate is set to COMMON-NAME.  The
   "Generate a key and a self-signed client TLS certificate using openssl.
 
 The Common Name field of the certificate is set to COMMON-NAME.  The
@@ -965,7 +1022,8 @@ when the certificate is no longer needed for the current session.
 Otherwise, the certificate will be given a 100 year expiration period
 and the files will not be deleted.
 
 Otherwise, the certificate will be given a 100 year expiration period
 and the files will not be deleted.
 
-The function returns a list containing the current host name, the
+The function returns a list containing the URL-PREFIX of addresses
+for which the certificate should be used in this session, the
 temporary flag, and the key and cert file names in the form required
 by `gnutls-boot-parameters`."
   (let ((exp-key-file (expand-file-name key-file))
 temporary flag, and the key and cert file names in the form required
 by `gnutls-boot-parameters`."
   (let ((exp-key-file (expand-file-name key-file))
@@ -979,56 +1037,70 @@ by `gnutls-boot-parameters`."
                         "-subj" (concat "/CN=" common-name)
                         "-keyout" exp-key-file
                         "-out" exp-cert-file)
                         "-subj" (concat "/CN=" common-name)
                         "-keyout" exp-key-file
                         "-out" exp-cert-file)
-          (list (elpher-address-host (elpher-page-address elpher-current-page))
-                temporary exp-key-file exp-cert-file))
+          (list url-prefix temporary exp-key-file exp-cert-file))
       (error
        (message "Check that openssl is installed, or customize `elpher-openssl-command`.")
        (error "Program 'openssl', required for certificate generation, not found")))))
 
       (error
        (message "Check that openssl is installed, or customize `elpher-openssl-command`.")
        (error "Program 'openssl', required for certificate generation, not found")))))
 
-(defun elpher-generate-throwaway-certificate ()
+(defun elpher-generate-throwaway-certificate (url-prefix)
   "Generate and return details of a throwaway certificate.
 The key and certificate files will be deleted when they are no
   "Generate and return details of a throwaway certificate.
 The key and certificate files will be deleted when they are no
-longer needed for this session."
+longer needed for this session.
+
+The certificate will be marked as applying to all addresses with URLs
+starting with URL-PREFIX."
   (let* ((file-base (make-temp-name "elpher"))
          (key-file (concat temporary-file-directory file-base ".key"))
          (cert-file (concat temporary-file-directory file-base ".crt")))
   (let* ((file-base (make-temp-name "elpher"))
          (key-file (concat temporary-file-directory file-base ".key"))
          (cert-file (concat temporary-file-directory file-base ".crt")))
-    (elpher-generate-certificate file-base key-file cert-file t)))
+    (elpher-generate-certificate file-base key-file cert-file url-prefix t)))
 
 
-(defun elpher-generate-persistent-certificate (file-base common-name)
+(defun elpher-generate-persistent-certificate (file-base common-name url-prefix)
   "Generate and return details of a persistent certificate.
 The argument FILE-BASE is used as the base for the key and certificate
 files, while COMMON-NAME specifies the common name field of the
 certificate.
 
   "Generate and return details of a persistent certificate.
 The argument FILE-BASE is used as the base for the key and certificate
 files, while COMMON-NAME specifies the common name field of the
 certificate.
 
-The key and certificate files are written to in `elpher-certificate-directory'."
+The key and certificate files are written to in `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
-    (elpher-generate-certificate common-name key-file cert-file)))
+    (elpher-generate-certificate common-name key-file cert-file url-prefix)))
 
 
-(defun elpher-get-existing-certificate (file-base)
+(defun elpher-get-existing-certificate (file-base url-prefix)
   "Return a certificate object corresponding to an existing certificate.
 It is assumed that the key files FILE-BASE.key and FILE-BASE.crt exist in
   "Return a certificate object corresponding to an existing certificate.
 It is assumed that the key files FILE-BASE.key and FILE-BASE.crt exist in
-the directory `elpher-certificate-directory'."
+the directory `elpher-certificate-directory'.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
-    (list (elpher-address-host (elpher-page-address elpher-current-page))
+    (list url-prefix
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
 
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
 
-(defun elpher-install-and-use-existing-certificate (key-file-src cert-file-src file-base)
+(defun elpher-install-certificate (key-file-src cert-file-src file-base url-prefix)
   "Install a key+certificate file pair in `elpher-certificate-directory'.
 The strings KEY-FILE-SRC and CERT-FILE-SRC are the existing key and
 certificate files to install.  The argument FILE-BASE is used as the
   "Install a key+certificate file pair in `elpher-certificate-directory'.
 The strings KEY-FILE-SRC and CERT-FILE-SRC are the existing key and
 certificate files to install.  The argument FILE-BASE is used as the
-base for the installed key and certificate files."
+base for the installed key and certificate files.
+
+In this session, the certificate will remain active for all addresses
+having URLs starting with URL-PREFIX."
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
     (if (or (file-exists-p key-file)
             (file-exists-p cert-file))
         (error "A certificate with base name %s is already installed" file-base))
   (let* ((key-file (concat elpher-certificate-directory file-base ".key"))
          (cert-file (concat elpher-certificate-directory file-base ".crt")))
     (if (or (file-exists-p key-file)
             (file-exists-p cert-file))
         (error "A certificate with base name %s is already installed" file-base))
+    (unless (and (file-exists-p key-file-src)
+                 (file-exists-p cert-file-src))
+      (error "Either of the key or certificate files do not exist"))
     (copy-file key-file-src key-file)
     (copy-file cert-file-src cert-file)
     (copy-file key-file-src key-file)
     (copy-file cert-file-src cert-file)
-    (list (elpher-address-host (elpher-page-address elpher-current-page))
+    (list url-prefix
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
           nil
           (expand-file-name key-file)
           (expand-file-name cert-file))))
@@ -1054,7 +1126,7 @@ are also deleted."
       (when (cadr elpher-client-certificate)
         (delete-file (elt elpher-client-certificate 2))
         (delete-file (elt elpher-client-certificate 3)))
       (when (cadr elpher-client-certificate)
         (delete-file (elt elpher-client-certificate 2))
         (delete-file (elt elpher-client-certificate 3)))
-      (setq elpher-client-certificate nil)
+      (setq-local elpher-client-certificate nil)
       (if (called-interactively-p 'any)
           (message "Client certificate forgotten.")))))
 
       (if (called-interactively-p 'any)
           (message "Client certificate forgotten.")))))
 
@@ -1062,14 +1134,14 @@ are also deleted."
   "Retrieve the `gnutls-boot-parameters'-compatable keylist.
 
 This is obtained from the client certificate described by
   "Retrieve the `gnutls-boot-parameters'-compatable keylist.
 
 This is obtained from the client certificate described by
-`elpher-current-certificate', if one is available and the host for
-that certificate matches the host in ADDRESS.
+`elpher-current-certificate', if one is available and the
+URL prefix for that certificate matches ADDRESS.
 
 
-If `elpher-current-certificate' is non-nil, and its host name doesn't
+If `elpher-current-certificate' is non-nil, and its URL prefix doesn't
 match that of ADDRESS, the certificate is forgotten."
   (if elpher-client-certificate
 match that of ADDRESS, the certificate is forgotten."
   (if elpher-client-certificate
-      (if (string= (car elpher-client-certificate)
-                   (elpher-address-host address))
+      (if (string-prefix-p (car elpher-client-certificate)
+                           (elpher-address-to-url address))
           (list (cddr elpher-client-certificate))
         (elpher-forget-current-certificate)
         (message "Disabling client certificate for new host")
           (list (cddr elpher-client-certificate))
         (elpher-forget-current-certificate)
         (message "Disabling client certificate for new host")
@@ -1121,23 +1193,11 @@ once they are retrieved from the gopher server."
         (insert " "))
     (insert (make-string elpher-margin-width ?\s))))
 
         (insert " "))
     (insert (make-string elpher-margin-width ?\s))))
 
-(defun elpher--page-button-help (_window buffer pos)
-  "Function called by Emacs to generate mouse-over text.
-The arguments specify the BUFFER and the POS within the buffer of the item
-for which help is required.  The function returns the help to be
-displayed.  The _WINDOW argument is currently unused."
-  (with-current-buffer buffer
-    (let ((button (button-at pos)))
-      (when button
-        (let* ((page (button-get button 'elpher-page))
-               (address (elpher-page-address page)))
-          (format "mouse-1, RET: open '%s'" (elpher-address-to-url address)))))))
-
 (defun elpher-insert-index-record (display-string &optional address)
   "Function to insert an index record into the current buffer.
 The contents of the record are dictated by DISPLAY-STRING and ADDRESS.
 If ADDRESS is not supplied or nil the record is rendered as an
 (defun elpher-insert-index-record (display-string &optional address)
   "Function to insert an index record into the current buffer.
 The contents of the record are dictated by DISPLAY-STRING and ADDRESS.
 If ADDRESS is not supplied or nil the record is rendered as an
-'information' line."
+`information' line."
   (let* ((type (if address (elpher-address-type address) nil))
          (type-map-entry (cdr (assoc type elpher-type-map))))
     (if type-map-entry
   (let* ((type (if address (elpher-address-type address) nil))
          (type-map-entry (cdr (assoc type elpher-type-map))))
     (if type-map-entry
@@ -1149,9 +1209,7 @@ If ADDRESS is not supplied or nil the record is rendered as an
           (insert-text-button filtered-display-string
                               'face face
                               'elpher-page page
           (insert-text-button filtered-display-string
                               'face face
                               'elpher-page page
-                              'action #'elpher-click-link
-                              'follow-link t
-                              'help-echo #'elpher--page-button-help))
+                              :type 'elpher-link))
       (pcase type
         ('nil ;; Information
          (elpher-insert-margin)
       (pcase type
         ('nil ;; Information
          (elpher-insert-margin)
@@ -1164,11 +1222,6 @@ If ADDRESS is not supplied or nil the record is rendered as an
                              'face 'elpher-unknown)))))
     (insert "\n")))
 
                              'face 'elpher-unknown)))))
     (insert "\n")))
 
-(defun elpher-click-link (button)
-  "Function called when the gopher link BUTTON is activated."
-  (let ((page (button-get button 'elpher-page)))
-    (elpher-visit-page page)))
-
 (defun elpher-render-index (data &optional _mime-type-string)
   "Render DATA as an index.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
 (defun elpher-render-index (data &optional _mime-type-string)
   "Render DATA as an index.  MIME-TYPE-STRING is unused."
   (elpher-with-clean-buffer
@@ -1352,14 +1405,17 @@ that the response was malformed."
          (elpher-with-clean-buffer
           (insert "Gemini server is requesting input."))
          (let* ((query-string
          (elpher-with-clean-buffer
           (insert "Gemini server is requesting input."))
          (let* ((query-string
-                 (if (eq (elt response-code 1) ?1)
-                     (read-passwd (concat response-meta ": "))
-                   (read-string (concat response-meta ": "))))
+                 (with-local-quit
+                   (if (eq (elt response-code 1) ?1)
+                       (read-passwd (concat response-meta ": "))
+                     (read-string (concat response-meta ": ")))))
                 (query-address (seq-copy (elpher-page-address elpher-current-page)))
                 (old-fname (url-filename query-address)))
                 (query-address (seq-copy (elpher-page-address elpher-current-page)))
                 (old-fname (url-filename query-address)))
-           (setf (url-filename query-address)
-                 (concat old-fname "?" (url-build-query-string `((,query-string)))))
-           (elpher-get-gemini-response query-address renderer)))
+           (if (not query-string)
+               (elpher-visit-previous-page)
+             (setf (url-filename query-address)
+                   (concat old-fname "?" (url-build-query-string `((,query-string)))))
+             (elpher-get-gemini-response query-address renderer))))
         (?2 ; Normal response
          (funcall renderer response-body response-meta))
         (?3 ; Redirect
         (?2 ; Normal response
          (funcall renderer response-body response-meta))
         (?3 ; Redirect
@@ -1388,28 +1444,55 @@ that the response was malformed."
             (insert "Gemini server is requesting a valid TLS certificate:\n\n"))
           (auto-fill-mode 1)
           (elpher-gemini-insert-text response-meta))
             (insert "Gemini server is requesting a valid TLS certificate:\n\n"))
           (auto-fill-mode 1)
           (elpher-gemini-insert-text response-meta))
-         (let ((chosen-certificate (elpher-choose-client-certificate)))
+         (let ((chosen-certificate
+                (with-local-quit
+                  (elpher-acquire-client-certificate
+                   (elpher-address-to-url (elpher-page-address elpher-current-page))))))
            (unless chosen-certificate
              (error "Gemini server requires a client certificate and none was provided"))
            (unless chosen-certificate
              (error "Gemini server requires a client certificate and none was provided"))
-           (setq elpher-client-certificate chosen-certificate))
+           (setq-local elpher-client-certificate chosen-certificate))
          (elpher-with-clean-buffer)
          (elpher-get-gemini-response (elpher-page-address elpher-current-page) renderer))
         (_other
          (error "Gemini server response unknown: %s %s"
                 response-code response-meta))))))
 
          (elpher-with-clean-buffer)
          (elpher-get-gemini-response (elpher-page-address elpher-current-page) renderer))
         (_other
          (error "Gemini server response unknown: %s %s"
                 response-code response-meta))))))
 
+(defun elpher-acquire-client-certificate (url-prefix)
+  "Select a pre-defined client certificate or prompt for one.
+In this case, \"pre-defined\" means a certificate provided by
+the `elpher-certificate-map' variable.
+
+For this session, the certificate will remain active for all addresses
+having URLs begining with URL-PREFIX."
+  (let ((entry (assoc url-prefix
+                      elpher-certificate-map
+                      #'string-prefix-p)))
+    (if entry
+        (let ((cert-url-prefix (car entry))
+              (cert-name (cadr entry)))
+          (message "Using certificate \"%s\" specified in elpher-certificate-map with prefix \"%s\""
+                   cert-name cert-url-prefix)
+          (elpher-get-existing-certificate cert-name cert-url-prefix))
+      (elpher-prompt-for-client-certificate url-prefix))))
+
 (defun elpher--read-answer-polyfill (question answers)
   "Polyfill for `read-answer' in Emacs 26.1.
 QUESTION is a string containing a question, and ANSWERS
 (defun elpher--read-answer-polyfill (question answers)
   "Polyfill for `read-answer' in Emacs 26.1.
 QUESTION is a string containing a question, and ANSWERS
-is a list of possible answers."
-    (completing-read question (mapcar 'identity answers)))
+is a list of possible answers, or an alist whose keys
+are the possible answers."
+    (completing-read question answers))
 
 (if (fboundp 'read-answer)
     (defalias 'elpher-read-answer 'read-answer)
   (defalias 'elpher-read-answer 'elpher--read-answer-polyfill))
 
 
 (if (fboundp 'read-answer)
     (defalias 'elpher-read-answer 'read-answer)
   (defalias 'elpher-read-answer 'elpher--read-answer-polyfill))
 
-(defun elpher-choose-client-certificate ()
-  "Prompt for a client certificate to use to establish a TLS connection."
+
+
+(defun elpher-prompt-for-client-certificate (url-prefix)
+  "Prompt for a client certificate to use to establish a TLS connection.
+
+In this session, the chosen certificate will remain active for all
+addresses with URLs matching URL-PREFIX."
   (let* ((read-answer-short t))
     (pcase (read-answer "What do you want to do? "
                         '(("throwaway" ?t
   (let* ((read-answer-short t))
     (pcase (read-answer "What do you want to do? "
                         '(("throwaway" ?t
@@ -1419,7 +1502,7 @@ is a list of possible answers."
                           ("abort" ?a
                            "stop immediately")))
       ("throwaway"
                           ("abort" ?a
                            "stop immediately")))
       ("throwaway"
-       (setq elpher-client-certificate (elpher-generate-throwaway-certificate)))
+       (setq-local elpher-client-certificate (elpher-generate-throwaway-certificate url-prefix)))
       ("persistent"
        (let* ((existing-certificates (elpher-list-existing-certificates))
               (file-base (completing-read
       ("persistent"
        (let* ((existing-certificates (elpher-list-existing-certificates))
               (file-base (completing-read
@@ -1428,8 +1511,8 @@ is a list of possible answers."
          (if (string-empty-p (string-trim file-base))
              nil
            (if (member file-base existing-certificates)
          (if (string-empty-p (string-trim file-base))
              nil
            (if (member file-base existing-certificates)
-               (setq elpher-client-certificate
-                     (elpher-get-existing-certificate file-base))
+               (setq-local elpher-client-certificate
+                     (elpher-get-existing-certificate file-base url-prefix))
              (pcase (read-answer "Generate new certificate or install externally-generated one? "
                                  '(("new" ?n
                                     "generate new certificate")
              (pcase (read-answer "Generate new certificate or install externally-generated one? "
                                  '(("new" ?n
                                     "generate new certificate")
@@ -1442,15 +1525,16 @@ is a list of possible answers."
                                                 file-base)))
                   (message "New key and self-signed certificate written to %s"
                            elpher-certificate-directory)
                                                 file-base)))
                   (message "New key and self-signed certificate written to %s"
                            elpher-certificate-directory)
-                  (elpher-generate-persistent-certificate file-base common-name)))
+                  (elpher-generate-persistent-certificate file-base
+                                                          common-name
+                                                          url-prefix)))
                ("install"
                 (let* ((cert-file (read-file-name "Certificate file: " nil nil t))
                        (key-file (read-file-name "Key file: " nil nil t)))
                   (message "Key and certificate installed in %s for future use"
                            elpher-certificate-directory)
                ("install"
                 (let* ((cert-file (read-file-name "Certificate file: " nil nil t))
                        (key-file (read-file-name "Key file: " nil nil t)))
                   (message "Key and certificate installed in %s for future use"
                            elpher-certificate-directory)
-                  (elpher-install-and-use-existing-certificate key-file
-                                                               cert-file
-                                                               file-base)))
+                  (elpher-install-certificate key-file cert-file file-base
+                                              url-prefix)))
                ("abort" nil))))))
       ("abort" nil))))
 
                ("abort" nil))))))
       ("abort" nil))))
 
@@ -1552,12 +1636,18 @@ treatment that a separate function is warranted."
           (if (string-empty-p (url-filename address))
               (setf (url-filename address) "/")) ;ensure empty filename is marked as absolute
         (setf (url-host address) (url-host current-address))
           (if (string-empty-p (url-filename address))
               (setf (url-filename address) "/")) ;ensure empty filename is marked as absolute
         (setf (url-host address) (url-host current-address))
-        (setf (url-fullness address) (url-host address)) ; set fullness to t if host is set
-        (setf (url-portspec address) (url-portspec current-address)) ; (url-port) too slow!
-        (unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
+        (setf (url-fullness address) (url-host address)) ;set fullness to t if host is set
+        (setf (url-portspec address) (url-portspec current-address)) ;(url-port) too slow!
+        (cond
+         ((string-prefix-p "/" (url-filename address))) ;do nothing for absolute case
+         ((string-prefix-p "?" (url-filename address)) ;handle query-only links
+          (setf (url-filename address)
+                (concat (url-filename current-address)
+                        (url-filename address))))
+         (t ;deal with relative links
           (setf (url-filename address)
                 (concat (file-name-directory (url-filename current-address))
           (setf (url-filename address)
                 (concat (file-name-directory (url-filename current-address))
-                        (url-filename address)))))
+                        (url-filename address))))))
       (when (url-host address)
         (setf (url-host address) (puny-encode-domain (url-host address))))
       (unless (url-type address)
       (when (url-host address)
         (setf (url-host address) (puny-encode-domain (url-host address))))
       (unless (url-type address)
@@ -1586,9 +1676,7 @@ treatment that a separate function is warranted."
             (insert-text-button display-string
                                 'face face
                                 'elpher-page page
             (insert-text-button display-string
                                 'face face
                                 'elpher-page page
-                                'action #'elpher-click-link
-                                'follow-link t
-                                'help-echo #'elpher--page-button-help))
+                                :type 'elpher-link))
           (newline))))))
 
 (defun elpher-gemini-insert-header (header-line)
           (newline))))))
 
 (defun elpher-gemini-insert-header (header-line)
@@ -1849,7 +1937,7 @@ Assumes UTF-8 encoding for all text files."
            "Default bindings:\n"
            "\n"
            " - TAB/Shift-TAB: next/prev item on current page\n"
            "Default bindings:\n"
            "\n"
            " - TAB/Shift-TAB: next/prev item on current page\n"
-           " - RET/mouse-1: open item under cursor\n"
+           " - RET/mouse-1: open item under cursor (with Shift to open in new buffer)\n"
            " - m: select an item on current page by name (autocompletes)\n"
            " - u/mouse-3/U: return to previous page or to the start page\n"
            " - g: go to a particular address (gopher, gemini, finger)\n"
            " - m: select an item on current page by name (autocompletes)\n"
            " - u/mouse-3/U: return to previous page or to the start page\n"
            " - g: go to a particular address (gopher, gemini, finger)\n"
@@ -1871,7 +1959,7 @@ Assumes UTF-8 encoding for all text files."
    (elpher-insert-index-record "Floodgap Systems Gopher Server"
                                (elpher-make-gopher-address ?1 "" "gopher.floodgap.com" 70))
    (elpher-insert-index-record "Project Gemini home page"
    (elpher-insert-index-record "Floodgap Systems Gopher Server"
                                (elpher-make-gopher-address ?1 "" "gopher.floodgap.com" 70))
    (elpher-insert-index-record "Project Gemini home page"
-                               (elpher-address-from-url "gemini://gemini.circumlunar.space/"))
+                               (elpher-address-from-url "gemini://geminiprotocol.net/"))
    (insert "\n"
            "Alternatively, select a search engine and enter some search terms:\n")
    (elpher-insert-index-record "Gopher Search Engine (Veronica-2)"
    (insert "\n"
            "Alternatively, select a search engine and enter some search terms:\n")
    (elpher-insert-index-record "Gopher Search Engine (Veronica-2)"
@@ -1882,12 +1970,10 @@ Assumes UTF-8 encoding for all text files."
            "Your bookmarks are stored in your ")
    (insert-text-button "bookmark list"
                        'face 'link
            "Your bookmarks are stored in your ")
    (insert-text-button "bookmark list"
                        'face 'link
-                       'action #'elpher-click-link
-                       'follow-link t
-                       'help-echo #'elpher--page-button-help
                        'elpher-page
                        (elpher-make-page "Elpher Bookmarks"
                        'elpher-page
                        (elpher-make-page "Elpher Bookmarks"
-                                         (elpher-make-about-address 'bookmarks)))
+                                         (elpher-make-about-address 'bookmarks))
+                       :type 'elpher-link)
    (insert ".\n")
    (insert (propertize
             "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n"
    (insert ".\n")
    (insert (propertize
             "(Bookmarks from legacy elpher-bookmarks files will be automatically imported.)\n"
@@ -2188,6 +2274,11 @@ supports the old protocol elpher, where the link is self-contained."
    :export (lambda (link description format _plist)
              (elpher-org-export-link link description format "gopher"))
    :follow (lambda (link _arg) (elpher-org-follow-link link "gopher")))
    :export (lambda (link description format _plist)
              (elpher-org-export-link link description format "gopher"))
    :follow (lambda (link _arg) (elpher-org-follow-link link "gopher")))
+  (org-link-set-parameters
+   "gophers"
+   :export (lambda (link description format _plist)
+             (elpher-org-export-link link description format "gophers"))
+   :follow (lambda (link _arg) (elpher-org-follow-link link "gophers")))
   (org-link-set-parameters
    "finger"
    :export (lambda (link description format _plist)
   (org-link-set-parameters
    "finger"
    :export (lambda (link description format _plist)
@@ -2209,7 +2300,7 @@ supports the old protocol elpher, where the link is self-contained."
 (if (boundp 'browse-url-default-handlers)
     (add-to-list
      'browse-url-default-handlers
 (if (boundp 'browse-url-default-handlers)
     (add-to-list
      'browse-url-default-handlers
-     '("^\\(gopher\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
+     '("^\\(gopher\\|gophers\\|finger\\|gemini\\)://" . elpher-browse-url-elpher))
   ;; Patch `browse-url-browser-function' for older ones. The value of
   ;; that variable is `browse-url-default-browser' by default, so
   ;; that's the function that gets advised. If the value is an alist,
   ;; Patch `browse-url-browser-function' for older ones. The value of
   ;; that variable is `browse-url-default-browser' by default, so
   ;; that's the function that gets advised. If the value is an alist,
@@ -2220,7 +2311,7 @@ supports the old protocol elpher, where the link is self-contained."
                (lambda (url &rest _args)
                  "Handle gemini, gopher, and finger schemes using Elpher."
                   (let ((scheme (downcase (car (split-string url ":" t)))))
                (lambda (url &rest _args)
                  "Handle gemini, gopher, and finger schemes using Elpher."
                   (let ((scheme (downcase (car (split-string url ":" t)))))
-                    (if (member scheme '("gemini" "gopher" "finger"))
+                    (if (member scheme '("gemini" "gopher" "gophers" "finger"))
                        ;; `elpher-go' always returns nil, which will stop the
                        ;; advice chain here in a before-while
                        (elpher-go url)
                        ;; `elpher-go' always returns nil, which will stop the
                        ;; advice chain here in a before-while
                        (elpher-go url)
@@ -2235,13 +2326,13 @@ supports the old protocol elpher, where the link is self-contained."
 
 ;; Make mu4e aware of the gemini world
 (setq mu4e~view-beginning-of-url-regexp
 
 ;; Make mu4e aware of the gemini world
 (setq mu4e~view-beginning-of-url-regexp
-      "\\(?:https?\\|gopher\\|finger\\|gemini\\)://\\|mailto:")
+      "\\(?:https?\\|gopher\\|gophers\\|finger\\|gemini\\)://\\|mailto:")
 
 ;; eww:
 
 ;; Let elpher handle gemini, gopher links in eww buffer.
 (setq eww-use-browse-url
 
 ;; eww:
 
 ;; Let elpher handle gemini, gopher links in eww buffer.
 (setq eww-use-browse-url
-      "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`finger\\)://")
+      "\\`mailto:\\|\\(\\`gemini\\|\\`gopher\\|\\`gophers\\|\\`finger\\)://")
 
 
 ;;; Interactive procedures
 
 
 ;;; Interactive procedures
@@ -2260,14 +2351,20 @@ supports the old protocol elpher, where the link is self-contained."
 (defun elpher-follow-current-link ()
   "Open the link or url at point."
   (interactive)
 (defun elpher-follow-current-link ()
   "Open the link or url at point."
   (interactive)
-  (push-button))
+  (elpher--click-link (button-at (point))))
+
+(defun elpher-follow-current-link-new-buffer ()
+  "Open the link or url at point."
+  (interactive)
+  (elpher--open-link-new-buffer))
 
 ;;;###autoload
 (defun elpher-go (host-or-url)
   "Go to a particular gopher site HOST-OR-URL.
 When run interactively HOST-OR-URL is read from the minibuffer."
   (interactive (list
 
 ;;;###autoload
 (defun elpher-go (host-or-url)
   "Go to a particular gopher site HOST-OR-URL.
 When run interactively HOST-OR-URL is read from the minibuffer."
   (interactive (list
-                (read-string (format "Visit URL (default scheme %s): " (elpher-get-default-url-scheme)))))
+                (read-string (format "Visit URL (default scheme %s): "
+                                     (elpher-get-default-url-scheme)))))
   (let ((trimmed-host-or-url (string-trim host-or-url)))
     (unless (string-empty-p trimmed-host-or-url)
       (let ((page (elpher-page-from-url trimmed-host-or-url
   (let ((trimmed-host-or-url (string-trim host-or-url)))
     (unless (string-empty-p trimmed-host-or-url)
       (let ((page (elpher-page-from-url trimmed-host-or-url
@@ -2284,10 +2381,14 @@ Unlike `elpher-go', the reader is initialized with the URL of the
 current page."
   (interactive)
   (let* ((address (elpher-page-address elpher-current-page))
 current page."
   (interactive)
   (let* ((address (elpher-page-address elpher-current-page))
-         (url (read-string (format "Visit URL (default scheme %s): " (elpher-get-default-url-scheme))
+         (url (read-string (format "Visit URL (default scheme %s): "
+                                   (elpher-get-default-url-scheme))
                            (elpher-address-to-url address))))
                            (elpher-address-to-url address))))
-    (unless (string-empty-p (string-trim url))
-      (elpher-visit-page (elpher-page-from-url url)))))
+    (let ((trimmed-url (string-trim url)))
+      (unless (string-empty-p trimmed-url)
+        (elpher-with-clean-buffer
+         (elpher-visit-page
+          (elpher-page-from-url trimmed-url (elpher-get-default-url-scheme))))))))
 
 (defun elpher-redraw ()
   "Redraw current page."
 
 (defun elpher-redraw ()
   "Redraw current page."
@@ -2485,36 +2586,35 @@ current page."
     (define-key map (kbd "F") 'elpher-forget-current-certificate)
     (when (fboundp 'evil-define-key*)
       (evil-define-key*
     (define-key map (kbd "F") 'elpher-forget-current-certificate)
     (when (fboundp 'evil-define-key*)
       (evil-define-key*
-       'motion map
-       (kbd "TAB") 'elpher-next-link
-       (kbd "C-") 'elpher-follow-current-link
-       (kbd "C-t") 'elpher-back
-       (kbd "u") 'elpher-back
-       (kbd "-") 'elpher-back
-       (kbd "^") 'elpher-back
-       [mouse-3] 'elpher-back
-       (kbd "U") 'elpher-back-to-start
-       (kbd "g") 'elpher-go
-       (kbd "o") 'elpher-go-current
-       (kbd "O") 'elpher-root-dir
-       (kbd "s") 'elpher-show-history
-       (kbd "S") 'elpher-show-visited-pages
-       (kbd "r") 'elpher-redraw
-       (kbd "R") 'elpher-reload
-       (kbd "T") 'elpher-toggle-tls
-       (kbd ".") 'elpher-view-raw
-       (kbd "d") 'elpher-download
-       (kbd "D") 'elpher-download-current
-       (kbd "m") 'elpher-jump
-       (kbd "i") 'elpher-info-link
-       (kbd "I") 'elpher-info-current
-       (kbd "c") 'elpher-copy-link-url
-       (kbd "C") 'elpher-copy-current-url
-       (kbd "a") 'elpher-bookmark-link
-       (kbd "A") 'elpher-bookmark-current
-       (kbd "B") 'elpher-show-bookmarks
-       (kbd "!") 'elpher-set-gopher-coding-system
-       (kbd "F") 'elpher-forget-current-certificate))
+        'motion map
+        (kbd "TAB") 'elpher-next-link
+        (kbd "C-t") 'elpher-back
+        (kbd "u") 'elpher-back
+        (kbd "-") 'elpher-back
+        (kbd "^") 'elpher-back
+        [mouse-3] 'elpher-back
+        (kbd "U") 'elpher-back-to-start
+        (kbd "g") 'elpher-go
+        (kbd "o") 'elpher-go-current
+        (kbd "O") 'elpher-root-dir
+        (kbd "s") 'elpher-show-history
+        (kbd "S") 'elpher-show-visited-pages
+        (kbd "r") 'elpher-redraw
+        (kbd "R") 'elpher-reload
+        (kbd "T") 'elpher-toggle-tls
+        (kbd ".") 'elpher-view-raw
+        (kbd "d") 'elpher-download
+        (kbd "D") 'elpher-download-current
+        (kbd "m") 'elpher-jump
+        (kbd "i") 'elpher-info-link
+        (kbd "I") 'elpher-info-current
+        (kbd "c") 'elpher-copy-link-url
+        (kbd "C") 'elpher-copy-current-url
+        (kbd "a") 'elpher-bookmark-link
+        (kbd "A") 'elpher-bookmark-current
+        (kbd "B") 'elpher-show-bookmarks
+        (kbd "!") 'elpher-set-gopher-coding-system
+        (kbd "F") 'elpher-forget-current-certificate))
     map)
   "Keymap for gopher client.")
 
     map)
   "Keymap for gopher client.")
 
index ff2979a..56a5fcc 100644 (file)
@@ -1,7 +1,7 @@
 \input texinfo @c -*-texinfo-*-
 
 @setfilename elpher.info
 \input texinfo @c -*-texinfo-*-
 
 @setfilename elpher.info
-@settitle Elpher Manual v3.4.0
+@settitle Elpher Manual v3.6.0
 
 @dircategory Emacs
 @direntry
 
 @dircategory Emacs
 @direntry
@@ -11,7 +11,7 @@
 @copying
 This manual documents Elpher, a gopher and gemini client for Emacs.
 
 @copying
 This manual documents Elpher, a gopher and gemini client for Emacs.
 
-Copyright @copyright{} 2019-2022  Tim Vaughan@*
+Copyright @copyright{} 2019-2023  Tim Vaughan@*
 Copyright @copyright{} 2021 Daniel Semyonov@*
 Copyright @copyright{} 2021 Alex Schroeder
 
 Copyright @copyright{} 2021 Daniel Semyonov@*
 Copyright @copyright{} 2021 Alex Schroeder
 
@@ -66,12 +66,32 @@ the file COPYING in the same directory as this file for more details.
 @detailmenu
  --- The Detailed Node Listing ---
 
 @detailmenu
  --- The Detailed Node Listing ---
 
+Installation
+
+* Installing from ELPA or MELPA::  Installing from a package repository
+* Installing by hand::          Installing directly from the source
+
 Navigation
 
 * Within-page navigation::      Moving about within a page
 * Between-page navigation::     Commands for moving between pages
 * History and Caching::         Explanation of how Elpher represents history
 
 Navigation
 
 * Within-page navigation::      Moving about within a page
 * Between-page navigation::     Commands for moving between pages
 * History and Caching::         Explanation of how Elpher represents history
 
+Gemini support
+
+* Client Certificates for Gemini::  Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents::  An accessibility option
+
+News
+
+* v3.6.0::
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+
 @end detailmenu
 @end menu
 
 @end detailmenu
 @end menu
 
@@ -123,6 +143,12 @@ have some ideas.
 @node Installation, Quick Start, Introduction, Top
 @chapter Installation
 
 @node Installation, Quick Start, Introduction, Top
 @chapter Installation
 
+@menu
+* Installing from ELPA or MELPA::  Installing from a package repository
+* Installing by hand::          Installing directly from the source
+@end menu
+
+@node Installing from ELPA or MELPA, Installing by hand, Installation, Installation
 @section Installing from ELPA or MELPA
 
 Elpher is available on the non-GNU ELPA package archive.  If you are
 @section Installing from ELPA or MELPA
 
 Elpher is available on the non-GNU ELPA package archive.  If you are
@@ -147,6 +173,7 @@ install Elpher:
 @kbd{M-x package-delete @key{RET} elpher @key{RET}}.
 @end example
 
 @kbd{M-x package-delete @key{RET} elpher @key{RET}}.
 @end example
 
+@node Installing by hand,  , Installing from ELPA or MELPA, Installation
 @section Installing by hand
 
 It is also possible to install Elpher directly by downloading the file
 @section Installing by hand
 
 It is also possible to install Elpher directly by downloading the file
@@ -296,7 +323,7 @@ Moving to a different page can be accomplished in several ways,
 described by the following command:
 
 @table @asis
 described by the following command:
 
 @table @asis
-@keycmd{@key{RET}\, @key{mouse-1}, elpher-follow-link}
+@keycmd{@key{RET}\, @key{mouse-1}, elpher-follow-current-link}
 Follow the menu item or link at point (or selected with the mouse).
 
 Exactly what is meant by ``follow'' depends on the kind of item selected:
 Follow the menu item or link at point (or selected with the mouse).
 
 Exactly what is meant by ``follow'' depends on the kind of item selected:
@@ -331,6 +358,10 @@ Emacs' own EWW browser. (See @pxref{Customization}.)
 Once a text, menu or query response page has been displayed, its contents are
 cached for the duration of the Emacs session. 
 
 Once a text, menu or query response page has been displayed, its contents are
 cached for the duration of the Emacs session. 
 
+@keycmd{S-@key{RET}\, S-@key{mouse-1}, elpher-follow-current-link-new-buffer}
+Create a new Elpher buffer and follow the menu item or link at point
+in the new buffer.
+
 @keycmd{@key{g}, elpher-go}
 Open a particular page by specifying either its full URL or just
 entering a gopher host name. (The protocol defaults to gopher, so gemini
 @keycmd{@key{g}, elpher-go}
 Open a particular page by specifying either its full URL or just
 entering a gopher host name. (The protocol defaults to gopher, so gemini
@@ -574,6 +605,12 @@ I should emphasize however that, while it is definitely functional,
 Elpher's gemini support is still experimental, and various aspects will
 change as the protocol develops further.
 
 Elpher's gemini support is still experimental, and various aspects will
 change as the protocol develops further.
 
+@menu
+* Client Certificates for Gemini::  Accessing secure gemini pages
+* Hiding preformatted text in text/gemini documents::  An accessibility option
+@end menu
+
+@node Client Certificates for Gemini, Hiding preformatted text in text/gemini documents, Gemini support, Gemini support
 @section Client Certificates for Gemini
 
 Gemini makes explicit use of the client certificate mechanism that TLS
 @section Client Certificates for Gemini
 
 Gemini makes explicit use of the client certificate mechanism that TLS
@@ -627,10 +664,11 @@ Alternatively, pressing the @key{i} key will cause Elpher to ask for the
 locations of existing key and certificate files to add to
 @code{elpher-certificate-directory} under the chosen name.
 
 locations of existing key and certificate files to add to
 @code{elpher-certificate-directory} under the chosen name.
 
-Once a certificate is selected, it will be used for all subsequent TLS
-transactions to the host for which the certificate was created.
-It is immediately ``forgotten'' when a TLS connection to another host
-is attempted, or the following command is issued:
+Once a certificate is selected, it will be used for all subsequent
+gemini requests involving URLs begining with the URL for for which the
+certificate was created.  It is immediately ``forgotten'' when a TLS
+connection to a non-matching URL is attempted, or the following command
+is issued:
 
 @table @asis
 @keycmd{@key{F},elpher-forget-certificate}
 
 @table @asis
 @keycmd{@key{F},elpher-forget-certificate}
@@ -641,6 +679,12 @@ In either case, ``forgetting'' means that the details of the key and
 certificate file pair are erased from memory.  Furthermore, in the case
 of throw-away certificates, the corresponding files are deleted.
 
 certificate file pair are erased from memory.  Furthermore, in the case
 of throw-away certificates, the corresponding files are deleted.
 
+Persistant client certificates can be added to the alist contained in the
+customization variable @code{elpher-certificate-map} so that they are
+automatically activated whenever a gemini page with the matching URL
+prefix is visited.
+
+@node Hiding preformatted text in text/gemini documents,  , Client Certificates for Gemini, Gemini support
 @section Hiding preformatted text in text/gemini documents
 
 Preformatted text is often used to display ``ASCII art'' or other
 @section Hiding preformatted text in text/gemini documents
 
 Preformatted text is often used to display ``ASCII art'' or other
@@ -818,6 +862,39 @@ See the customization group itself for details.
 
 This chapter documents the major changes introduced by Elpher releases.
 
 
 This chapter documents the major changes introduced by Elpher releases.
 
+@menu
+* v3.6.0::
+* v3.5.0::
+* v3.4.0::
+* v3.3.0::
+* v3.2.0::
+* v3.1.0::
+* v3.0.0::
+@end menu
+
+@node v3.6.0, v3.5.0, News, News
+@section v3.6.0
+
+@subsection Easily open links in new buffer
+
+This version includes the ability to open all link types in
+Elpher documents in a new buffer.  By default, this is bound
+to S-@key{RET} and S-@key{mouse-1}, but this can be modified
+by adding adjusting the bindings in @code{elpher-link-keymap}.
+
+@node v3.5.0, v3.4.0, v3.6.0, News
+@section v3.5.0
+
+@subsection Automatic activation of client certificates in gemini
+
+This version introduces a new customization variable
+@code{elpher-certificate-map} which allows you to pre-specify
+a set of gemini URLs and the client certificates which should
+be used when accessing them.
+
+@xref{Client Certificates for Gemini} for more details.
+
+@node v3.4.0, v3.3.0, v3.5.0, News
 @section v3.4.0
 
 @subsection Toggling preformatted text visibility
 @section v3.4.0
 
 @subsection Toggling preformatted text visibility
@@ -832,6 +909,7 @@ block.
 This feature is intended to make it easier for people using screen
 readers to read text/gemini documents.
 
 This feature is intended to make it easier for people using screen
 readers to read text/gemini documents.
 
+@node v3.3.0, v3.2.0, v3.4.0, News
 @section v3.3.0
 
 This version includes lots of bug fixes, as well as a couple of new
 @section v3.3.0
 
 This version includes lots of bug fixes, as well as a couple of new
@@ -853,6 +931,7 @@ characters and displays the decoded IRI.  (For security reasons, the
 @code{elpher-info-current} command (@kbd{I}) always displays both the
 decoded IRI and the URI when they differ.)
 
 @code{elpher-info-current} command (@kbd{I}) always displays both the
 decoded IRI and the URI when they differ.)
 
+@node v3.2.0, v3.1.0, v3.3.0, News
 @section v3.2.0
 
 This version introduces several minor changes which, together, make it
 @section v3.2.0
 
 This version introduces several minor changes which, together, make it
@@ -879,6 +958,7 @@ of the document to be loaded as elpher's ``start page''.  By default
 this is set to @samp{about:welcome}, but any elpher-accessible URL is
 valid. @pxref{Customization} for suggestions.
 
 this is set to @samp{about:welcome}, but any elpher-accessible URL is
 valid. @pxref{Customization} for suggestions.
 
+@node v3.1.0, v3.0.0, v3.2.0, News
 @section v3.1.0
 
 @subsection Bookmarks system
 @section v3.1.0
 
 @subsection Bookmarks system
@@ -901,6 +981,7 @@ use the customization variable @code{elpher-use-emacs-bookmark-menu}
 to have the @key{B} key open the Emacs bookmark menu directly, as in
 the previous release.
 
 to have the @key{B} key open the Emacs bookmark menu directly, as in
 the previous release.
 
+@node v3.0.0,  , v3.1.0, News
 @section v3.0.0
 
 @subsection Bookmarks system
 @section v3.0.0
 
 @subsection Bookmarks system