+
+;; Spartan page retrieval
+
+(defvar elpher-spartan-redirect-chain)
+
+(defun elpher-get-spartan-page (renderer)
+ "Getter which retrieves and renders a Spartan page and renders it using RENDERER."
+ (let* ((address (elpher-page-address elpher-current-page))
+ (content (elpher-get-cached-content address)))
+ (condition-case the-error
+ (if (and content (funcall renderer nil))
+ (elpher-with-clean-buffer
+ (insert content)
+ (elpher-restore-pos))
+ (elpher-with-clean-buffer
+ (insert "LOADING SPARTAN... (use 'u' to cancel)\n"))
+ (setq elpher-spartan-redirect-chain nil)
+ (elpher-get-spartan-response address renderer))
+ (error
+ (elpher-network-error address the-error)))))
+
+(defun elpher-get-spartan-response (address renderer)
+ "Get response string from spartan server at ADDRESS and render using RENDERER."
+ (let* ((host (elpher-address-host address))
+ (path-and-query (url-path-and-query address))
+ (filename (car path-and-query))
+ (data (cdr path-and-query))
+ (data-len (length data)))
+ (elpher-get-host-response address 300
+ (concat host " "
+ filename " "
+ (number-to-string data-len) "\r\n"
+ data)
+ (lambda (response-string)
+ (elpher-process-spartan-response response-string renderer)))))
+
+(defun elpher-parse-spartan-response (response)
+ "Parse the RESPONSE string and return a list of components.
+The list is of the form (code meta body). A response of nil implies
+that the response was malformed."
+ (let ((header-end-idx (string-match "\r\n" response)))
+ (if header-end-idx
+ (let ((header (string-trim (substring response 0 header-end-idx)))
+ (body (substring response (+ header-end-idx 2))))
+ (if (>= (length header) 2)
+ (let ((code (substring header 0 1))
+ (meta (string-trim (substring header 1))))
+ (list code meta body))
+ (error "Malformed response: No response status found in header %s" header)))
+ (error "Malformed response: No CRLF-delimited header found in response %s" response))))
+
+(defun elpher-process-spartan-response (response-string renderer)
+ "Process the spartan response RESPONSE-STRING and pass the result to RENDERER."
+ (let ((response-components (elpher-parse-spartan-response response-string)))
+ (let ((response-code (elt response-components 0))
+ (response-meta (elt response-components 1))
+ (response-body (elt response-components 2)))
+ (pcase (elt response-code 0)
+ (?2 ; Normal response
+ (funcall renderer response-body response-meta))
+ (?3 ; Redirect
+ (message "Following redirect to %s" response-meta)
+ (if (>= (length elpher-spartan-redirect-chain) 5)
+ (error "More than 5 consecutive redirects followed"))
+ (let* ((current-address (elpher-page-address elpher-current-page))
+ (redirect-address (elpher-address-from-url
+ (concat "spartan://"
+ (elpher-address-host current-address)
+ ":"
+ (elpher-address-port current-address)
+ "/"
+ response-meta))))
+ (if (member redirect-address elpher-spartan-redirect-chain)
+ (error "Redirect loop detected"))
+ (elpher-page-set-address elpher-current-page redirect-address)
+ (add-to-list 'elpher-spartan-redirect-chain redirect-address)
+ (elpher-get-spartan-response redirect-address renderer)))
+ (?4 ; Client error
+ (error "Spartan server reports CLIENT ERROR for this request: %s %s"
+ response-code response-meta))
+ (?5 ; Server error
+ (error "Spartan server reports SERVER ERROR for this request: %s %s"
+ response-code response-meta))
+ (_other
+ (error "Spartan server response unknown: %s %s"
+ response-code response-meta))))))
+
+(defun elpher-spartan-insert-query (query-line)
+ "Insert link described by QUERY-LINE into a text/gemini document."
+ (let ((url (elpher-gemini-get-link-url query-line)))
+ (when url
+ (let* ((address (elpher-address-from-gemini-url url))
+ (given-display-string (elpher-gemini-get-link-display-string query-line))
+ (fill-prefix (make-string (+ 1 (length elpher-gemini-link-string)) ?\s)))
+ (insert elpher-gemini-link-string)
+ (let ((display-string (or given-display-string url)))
+ (insert-text-button display-string
+ 'face 'elpher-spartan-post
+ 'display-string display-string
+ 'url (elpher-address-to-url address)
+ 'action #'elpher-spartan-post
+ 'follow-link t
+ 'help-echo #'elpher--page-button-help))
+ (newline)))))
+
+(defun elpher-spartan-post (button)
+ "Function called when the spartan post link BUTTON is clicked."
+ (let* ((display-string (button-get button 'display-string))
+ (url (button-get button 'url))
+ (post-url (concat url "?" (url-hexify-string (read-string "Text to post: ")))))
+ (elpher-visit-page (elpher-make-page
+ display-string
+ (elpher-address-from-url post-url)))))
+