"Specify the name of the file where elpher bookmarks will be saved."
:type '(file))
+(defcustom elpher-ipv4-always nil
+ "If non-nil, elpher will always use IPv4 to establish network connections.
+This can be useful when browsing from a computer that supports IPv6, because
+some servers which do not support IPv6 can take a long time to time-out."
+ :type '(boolean))
+
;; Face customizations
(defgroup elpher-faces nil
(defun elpher-address-to-url (address)
"Get string representation of ADDRESS, or nil if ADDRESS is special."
- (if (not (elpher-address-special-p address))
- (url-encode-url (url-recreate-url address))
- nil))
+ (if (elpher-address-special-p address)
+ nil
+ (let* ((port (url-port address))
+ (address-to-convert
+ (if (= port 0)
+ address
+ (let ((address-copy (seq-copy address))
+ (protocol (url-type address)))
+ (if (or (and (equal protocol "gopher")
+ (= port 70))
+ (and (equal protocol "gemini")
+ (= port 1965))
+ (and (equal protocol "http")
+ (= port 80))
+ (and (equal protocol "finger")
+ (= port 79)))
+ (setf (url-port address-copy) 0))
+ address-copy))))
+ (url-encode-url (url-recreate-url address-to-convert)))))
(defun elpher-address-type (address)
"Retrieve type of ADDRESS object.
(error "Cannot retrieve TLS gopher selector: GnuTLS not available")))
(unless (< (elpher-address-port address) 65536)
(error "Cannot retrieve gopher selector: port number > 65536"))
+ (defvar gnutls-verify-error)
(condition-case nil
(let* ((kill-buffer-query-functions nil)
(gnutls-verify-error nil) ; We use the NSM for verification
(hkbytes-received 0)
(proc (open-network-stream "elpher-process"
nil
- (if force-ipv4 (dns-query host) host)
+ (if (or elpher-ipv4-always force-ipv4)
+ (dns-query host)
+ host)
(if (> port 0) port 70)
:type (if elpher-use-tls 'tls 'plain)
:nowait t))
(elpher-network-error address "Could not establish encrypted connection")))
('connect
(elpher-process-cleanup)
- (unless force-ipv4
+ (unless (or elpher-ipv4-always force-ipv4)
(message "Connection timed out. Retrying with IPv4 address.")
(elpher-get-selector address renderer t))))))))
(setq elpher-network-timer timer)
(hkbytes-received 0)
(proc (open-network-stream "elpher-process"
nil
- (if force-ipv4 (dns-query host) host)
+ (if (or elpher-ipv4-always force-ipv4)
+ (dns-query host)
+ host)
(if (> port 0) port 1965)
:type 'tls
:nowait t))
(timer (run-at-time elpher-connection-timeout nil
(lambda ()
(elpher-process-cleanup)
- (unless force-ipv4
+ (unless (or elpher-ipv4-always force-ipv4)
; Try again with IPv4
(message "Connection timed out. Retrying with IPv4.")
(elpher-get-gemini-response address renderer t))))))
"\r\n"))))
((string-prefix-p "deleted" event)) ; do nothing
((and (not response-string-parts)
- (not force-ipv4))
+ (not (or elpher-ipv4-always force-ipv4)))
; Try again with IPv4
(message "Connection failed. Retrying with IPv4.")
(cancel-timer timer)
(defun elpher-address-from-gemini-url (url)
"Extract address from URL with defaults as per gemini map files."
- (let ((address (url-generic-parse-url url)))
+ (let ((address (url-generic-parse-url url))
+ (current-address (elpher-page-address elpher-current-page)))
(unless (and (url-type address) (not (url-fullness address))) ;avoid mangling mailto: urls
(setf (url-fullness address) t)
(if (url-host address) ;if there is an explicit host, filenames are absolute
(if (string-empty-p (url-filename address))
(setf (url-filename address) "/")) ;ensure empty filename is marked as absolute
- (setf (url-host address) (url-host (elpher-page-address elpher-current-page)))
+ (setf (url-host address) (url-host current-address))
+ (setf (url-port address) (url-port current-address))
(unless (string-prefix-p "/" (url-filename address)) ;deal with relative links
(setf (url-filename address)
- (concat (file-name-directory
- (url-filename (elpher-page-address elpher-current-page)))
+ (concat (file-name-directory (url-filename current-address))
(url-filename address)))))
(unless (url-type address)
(setf (url-type address) "gemini"))
(defun elpher-get-finger-page (renderer &optional force-ipv4)
"Opens a finger connection to the current page address.
The result is rendered using RENDERER. When the optional argument
-FORCE-IPV4 is non-nil, the IPv4 address returned by a DNS lookup will
-be used explicitly in making the connection."
+FORCE-IPV4 or the variable `elpher-ipv4-always' are non-nil, the
+IPv4 address returned by a DNS lookup will be used explicitly in
+making the connection."
(let* ((address (elpher-page-address elpher-current-page))
(content (elpher-get-cached-content address)))
(if (and content (funcall renderer nil))
(selector-string-parts nil)
(proc (open-network-stream "elpher-process"
nil
- (if force-ipv4 (dns-query host) host)
+ (if (or elpher-ipv4-always force-ipv4)
+ (dns-query host)
+ host)
port
:type 'plain
:nowait t))
(pcase (process-status proc)
('connect
(elpher-process-cleanup)
- (unless force-ipv4
+ (unless (or elpher-ipv4-always force-ipv4)
(message "Connection timed out. Retrying with IPv4 address.")
(elpher-get-finger-page renderer t))))))))
(setq elpher-network-timer timer)
"Go to a particular gopher site HOST-OR-URL.
When run interactively HOST-OR-URL is read from the minibuffer."
(interactive "sGopher or Gemini URL: ")
- (let ((page (elpher-make-page host-or-url
- (elpher-address-from-url host-or-url))))
+ (let* ((cleaned-host-or-url (string-trim host-or-url))
+ (address (elpher-address-from-url cleaned-host-or-url))
+ (page (elpher-make-page cleaned-host-or-url address)))
(switch-to-buffer "*elpher*")
(elpher-visit-page page)
- '()))
+ nil))
(defun elpher-go-current ()
"Go to a particular site read from the minibuffer, initialized with the current URL."
"Remove bookmark for the current page."
(interactive)
(let ((address (elpher-page-address elpher-current-page)))
- (unless (elpher-address-special-p address)
+ (when (and (not (elpher-address-special-p address))
+ (y-or-n-p "Really remove bookmark for the current page? "))
(elpher-remove-address-bookmark address)
(message "Bookmark removed."))))
(interactive)
(let ((button (button-at (point))))
(if button
- (let ((page (button-get button 'elpher-page)))
- (elpher-remove-address-bookmark (elpher-page-address page))
- (elpher-reload-bookmarks)
- (message "Bookmark removed."))
+ (when (y-or-n-p "Really remove bookmark for this link? ")
+ (let ((page (button-get button 'elpher-page)))
+ (elpher-remove-address-bookmark (elpher-page-address page))
+ (elpher-reload-bookmarks)
+ (message "Bookmark removed.")))
(error "No link selected"))))
(defun elpher-bookmarks ()