-(defun elpher-get-node-download ()
- "Getter which retrieves the current node and writes the result to a file."
- (let* ((address (elpher-node-address elpher-current-node))
- (selector (elpher-gopher-address-selector address)))
- (elpher-visit-parent-node) ; Do first in case of non-local exits.
- (let* ((filename-proposal (file-name-nondirectory selector))
- (filename (read-file-name "Save file as: "
- nil nil nil
- (if (> (length filename-proposal) 0)
- filename-proposal
- "gopher.file"))))
- (message "Downloading...")
- (setq elpher-download-filename filename)
- (condition-case the-error
- (elpher-get-selector address
- (lambda (proc event)
- (let ((coding-system-for-write 'binary))
- (with-temp-file elpher-download-filename
- (insert elpher-selector-string)
- (message (format "Download complate, saved to file %s."
- elpher-download-filename)))))
- t)
- (error
- (error "Error downloading %s" elpher-download-filename))))))
-
-;; HTML node retrieval
-
-(defun elpher-insert-rendered-html (string)
- "Use shr to insert rendered view of html STRING into current buffer."
- (let ((dom (with-temp-buffer
- (insert string)
- (libxml-parse-html-region (point-min) (point-max)))))
- (shr-insert-document dom)))
-
-(defun elpher-get-html-node ()
- "Getter which retrieves and renders an HTML node."
+(defun elpher-render-download (data &optional mime-type-string)
+ "Save DATA to file, MIME-TYPE-STRING is unused."
+ (if (not data)
+ nil
+ (let* ((address (elpher-node-address elpher-current-node))
+ (selector (elpher-gopher-address-selector address)))
+ (elpher-visit-parent-node) ; Do first in case of non-local exits.
+ (let* ((filename-proposal (file-name-nondirectory selector))
+ (filename (read-file-name "Download complete. Save file as: "
+ nil nil nil
+ (if (> (length filename-proposal) 0)
+ filename-proposal
+ "download.file"))))
+ (let ((coding-system-for-write 'binary))
+ (with-temp-file filename
+ (insert data)))
+ (message (format "Saved to file %s." filename))))))
+
+;; HTML rendering
+
+(defun elpher-render-html (data &optional mime-type-string)
+ "Render DATA as HTML using shr, MIME-TYPE-STRING is unused."
+ (elpher-with-clean-buffer
+ (if (not data)
+ t
+ (let ((dom (with-temp-buffer
+ (insert string)
+ (libxml-parse-html-region (point-min) (point-max)))))
+ (shr-insert-document dom)))))
+
+;; Gemini node retrieval
+
+(defvar elpher-gemini-response)
+
+(defun elpher-get-gemini-response (address after)
+ "Retrieve gemini ADDRESS, then execute AFTER.
+The response is stored in the variable ‘elpher-gemini-response’."
+ (setq elpher-gemini-response "")
+ (if (not (gnutls-available-p))
+ (error "Cannot retrieve TLS selector: GnuTLS not available")
+ (let* ((kill-buffer-query-functions nil)
+ (proc (open-network-stream "elpher-process"
+ nil
+ (elpher-address-host address)
+ (if (> (elpher-address-port address) 0)
+ (elpher-address-port address)
+ 1965)
+ :type 'tls)))
+ (set-process-coding-system proc 'binary)
+ (set-process-filter proc
+ (lambda (proc string)
+ (setq elpher-gemini-response
+ (concat elpher-gemini-response string))))
+ (set-process-sentinel proc after)
+ (process-send-string proc
+ (concat (elpher-address-to-url address) "\r\n")))))
+
+
+(defun elpher-process-gemini-response (renderer)
+ "Process the gemini response found in the variable `elpher-gemini-response' and
+pass the result to RENDERER."
+ (condition-case the-error
+ (let* ((response-header (car (split-string elpher-gemini-response "\r\n")))
+ (response-body (substring elpher-gemini-response
+ (+ (string-match "\r\n" elpher-gemini-response) 2)))
+ (response-code (car (split-string response-header)))
+ (response-meta (string-trim
+ (substring response-header
+ (string-match "[ \t]+" response-header)))))
+ (pcase (elt response-code 0)
+ (?1 ; Input required
+ (elpher-with-clean-buffer
+ (insert "Gemini server is requesting input."))
+ (let* ((query-string (read-string (concat response-meta ": ")))
+ (url (elpher-address-to-url (elpher-node-address elpher-current-node)))
+ (query-address (elpher-address-from-url (concat url "?" query-string))))
+ (elpher-get-gemini-response query-address
+ (lambda (proc event)
+ (unless (string-prefix-p "deleted" event)
+ (funcall #'elpher-process-gemini-response
+ renderer)
+ (elpher-restore-pos))))))
+ (?2 ; Normal response
+ (message response-header)
+ (funcall renderer response-body response-meta))
+ (?3 ; Redirect
+ (message "Following redirect to %s" response-meta)
+ (let ((redirect-address (elpher-address-from-gemini-url response-meta)))
+ (elpher-get-gemini-response redirect-address
+ (lambda (proc event)
+ (unless (string-prefix-p "deleted" event)
+ (funcall #'elpher-process-gemini-response
+ renderer)
+ (elpher-restore-pos))))))
+ (?4 ; Temporary failure
+ (error "Gemini server reports TEMPORARY FAILURE for this request"))
+ (?5 ; Permanent failure
+ (error "Gemini server reports PERMANENT FAILURE for this request"))
+ (?6 ; Client certificate required
+ (error "Gemini server requires client certificate (unsupported at this time)"))
+ (other
+ (error "Gemini server responded with unknown response code %S"
+ response-code))))
+ (error
+ (elpher-network-error (elpher-node-address elpher-current-node) the-error))))
+
+(defun elpher-get-gemini-node (renderer)
+ "Getter which retrieves and renders a Gemini node and renders it using RENDERER."