;; Author: Tim Vaughan <plugd@thelambdalab.xyz>
;; Created: 11 April 2019
-;; Version: 2.10.0
+;; Version: 2.10.2
;; Keywords: comm gopher
;; Homepage: http://thelambdalab.xyz/elpher
;; Package-Requires: ((emacs "26.2"))
(require 'ansi-color)
(require 'nsm)
(require 'gnutls)
+(require 'socks)
;;; Global constants
;;
-(defconst elpher-version "2.10.0"
+(defconst elpher-version "2.10.2"
"Current version of elpher.")
(defconst elpher-margin-width 6
some servers which do not support IPv6 can take a long time to time-out."
:type '(boolean))
+(defcustom elpher-socks-always nil
+ "If non-nil, elpher will establish network connections over a SOCKS proxy.
+Otherwise, the SOCKS proxy is only used for connections to onion services."
+ :type '(boolean))
+
;; Face customizations
(defgroup elpher-faces nil
"Face used for html type directory records.")
(defface elpher-gemini
- '((t :inherit font-lock-regexp-grouping-backslash))
+ '((t :inherit font-lock-constant-face))
"Face used for Gemini type directory records.")
(defface elpher-other-url
(condition-case nil
(let* ((kill-buffer-query-functions nil)
(port (elpher-address-port address))
+ (service (if (> port 0) port default-port))
(host (elpher-address-host address))
+ (socks (or elpher-socks-always (string-suffix-p ".onion" host)))
(response-string-parts nil)
(bytes-received 0)
(hkbytes-received 0)
- (proc (make-network-process :name "elpher-process"
- :host host
- :family (and force-ipv4 'ipv4)
- :service (if (> port 0) port default-port)
- :buffer nil
- :coding 'binary
- :noquery t
- :nowait t
- :tls-parameters
- (and use-tls
- (cons 'gnutls-x509pki
- (gnutls-boot-parameters
- :type 'gnutls-x509pki
- :hostname host
- :keylist
- (elpher-get-current-keylist address))))))
(timer (run-at-time elpher-connection-timeout nil
(lambda ()
(elpher-process-cleanup)
(cond
; Try again with IPv4
- ((not force-ipv4)
+ ((not (or force-ipv4 socks))
(message "Connection timed out. Retrying with IPv4.")
(elpher-get-host-response address default-port
query-string
response-processor
nil force-ipv4))
(t
- (elpher-network-error address "Connection time-out.")))))))
+ (elpher-network-error address "Connection time-out."))))))
+ (gnutls-params (list :type 'gnutls-x509pki :hostname host
+ :keylist (elpher-get-current-keylist address)))
+ (proc (if socks (socks-open-network-stream "elpher-process" nil host service)
+ (make-network-process :name "elpher-process"
+ :host host
+ :family (and force-ipv4 'ipv4)
+ :service service
+ :buffer nil
+ :nowait t
+ :tls-parameters
+ (and use-tls
+ (cons 'gnutls-x509pki
+ (apply #'gnutls-boot-parameters
+ gnutls-params)))))))
(setq elpher-network-timer timer)
+ (set-process-coding-system proc 'binary 'binary)
+ (set-process-query-on-exit-flag proc nil)
(elpher-buffer-message (concat "Connecting to " host "..."
" (press 'u' to abort)"))
(set-process-filter proc
(process-send-string proc query-string)))
((string-prefix-p "deleted" event)) ; do nothing
((and (not response-string-parts)
- (not (or elpher-ipv4-always force-ipv4)))
+ (not (or elpher-ipv4-always force-ipv4 socks)))
; Try again with IPv4
(message "Connection failed. Retrying with IPv4.")
(elpher-get-host-response address default-port
(t
(error "No response from server")))
(error
- (elpher-network-error address the-error))))))
+ (elpher-network-error address the-error)))))
+ (when socks
+ (if use-tls (apply #'gnutls-negotiate :process proc gnutls-params))
+ (funcall (process-sentinel proc) proc "open\n")))
(error
(error "Error initiating connection to server")))))
(defun elpher-list-existing-certificates ()
"Return a list of the persistent certificates in `elpher-certificate-directory'."
+ (unless (file-directory-p elpher-certificate-directory)
+ (make-directory elpher-certificate-directory))
(mapcar
(lambda (file)
(file-name-sans-extension file))
The gemini map file line describing the header is given
by HEADER-LINE."
(when (string-match "^\\(#+\\)[ \t]*" header-line)
- (let ((level (length (match-string 1 header-line)))
- (header (substring header-line (match-end 0))))
+ (let* ((level (length (match-string 1 header-line)))
+ (header (substring header-line (match-end 0)))
+ (face (pcase level
+ (1 'elpher-gemini-heading1)
+ (2 'elpher-gemini-heading2)
+ (3 'elpher-gemini-heading3)
+ (_ 'default)))
+ (fill-column (/ (* fill-column
+ (font-get (font-spec :name (face-font 'default)) :size))
+ (font-get (font-spec :name (face-font face)) :size))))
(unless (display-graphic-p)
(insert (make-string level ?#) " "))
- (insert (propertize header 'face
- (pcase level
- (1 'elpher-gemini-heading1)
- (2 'elpher-gemini-heading2)
- (3 'elpher-gemini-heading3)
- (_ 'default)))
- "\n"))))
+ (insert (propertize header 'face face))
+ (newline))))
(defun elpher-gemini-insert-text (text-line)
"Insert a plain non-preformatted TEXT-LINE into a text/gemini document.