diff --git a/client.rkt b/client.rkt new file mode 100644 index 0000000..f236e94 --- /dev/null +++ b/client.rkt @@ -0,0 +1,121 @@ +#lang racket + +(provide client%) + +(require (prefix-in net: "net.rkt") + (prefix-in gmi: "gmi.rkt")) + +(require net/url-string) + +(define temporary-failures + '((40 . "temporary failure") + (41 . "server unavailable") + (42 . "CGI error") + (43 . "proxy error") + (44 . "slow down"))) + +(define permanent-failures + '((50 . "permanent failure") + (51 . "not found") + (52 . "gone") + (53 . "proxy request refused") + (59 . "bad request"))) + +(define client% + (class object% + (define document-object '()) + (define document-buffer '()) + (define current-url '()) + + (super-new) + + (define/public (go-cmd url-string) + (if (non-empty-string? url-string) + (let () + (when (not (string-contains? url-string "://")) + (set! url-string (string-append "gemini://" url-string))) + + (get (string->url url-string))) + + (displayln "go where?"))) + + (define/public (next-cmd) + (define (iter depth) + (when (and (> depth 0) + (> (pipe-content-length document-buffer) 0)) + (let () + (displayln (read-line document-buffer)) + (iter (sub1 depth))))) + + (iter 10) + (newline) + (let ([remaining (pipe-content-length document-buffer)]) + (printf "~a bytes remaining\n" remaining))) + + (define/public (visit-cmd line) + (define url (gmi:match-link document-object line)) + + (get (combine-url/relative current-url (url->string url)))) + + (define/private (get url) + (define (iter url depth) + (let-values ([(status header c-in) (net:request url)]) + ;; TODO there are bunch of other status codes to deal with for + ;; compliance + (cond + ;; clients MUST reject status codes outside of the 10-69 range + [(or (< status 10) + (> status 69)) + (displayln "WARNING: server returned invalid status code")] + + ;; 10-19 INPUT REQUIRED + [(and (>= status 10) + (<= status 19)) + (display "input requested > ") + (get (string-append url "?" (read-line)))] + + ;; 20-29 SUCCESS + [(and (>= status 20) + (<= status 29)) + + (let-values ([(doc) (gmi:parse (port->lines c-in))] + [(db-in db-out) (make-pipe #f)]) + + (set! document-object doc) + + (set! document-buffer db-in) + (parameterize ([current-output-port db-out]) + (gmi:render doc)) + + (set! current-url url) + + (let ([remaining (pipe-content-length db-in)]) + (printf "document retrieved. ~a bytes\n" remaining)) + + (next-cmd))] + + ;; 30-39 REDIRECT + [(and (>= status 30) + (<= status 39)) + (if (> depth 5) + (displayln "WARNING: maximum redirection depth exceeded") + (iter (string->url header) (sub1 depth)))] + + ;; 40-49 TEMPORARY FAILURE + [(and (>= status 40) + (<= status 49)) + (printf "status ~a: ~a\n" status + (dict-ref temporary-failures status "temporary failure"))] + + ;; 50-59 PERMANENT FAILURE + [(and (>= status 50) + (<= status 59)) + (printf "status ~a: ~a\n" status + (dict-ref permanent-failures status "permanent failure"))] + + ;; 60-69 CERTIFICATE REQUIRED + [(and (>= status 60) + (<= status 69)) + (displayln "certificate handling not yet implemented")]))) + + (iter url 5))))