#lang racket (provide get) (require openssl) (require net/url) ;; sends a request to a gemini server, and returns the status, header, ;; and the input port for the rest of the body. ;; this procedure will fail if the response is malformed, however, it ;; is not up to it to validate the contents of the response. (define (send-request url-str) (define url (string->url url-str)) (define-values (c-in c-out) (ssl-connect (url-host url) (or (url-port url) 1965))) (write-string url-str c-out) (write-string "\r\n" c-out) (define-values (status header) (read-response c-in)) (values status header c-in)) (define (read-response (c-in (current-input-port))) (define maxlen 1027) (let ([header (peek-string maxlen 0 c-in)]) (if (not (string-contains? header "\r\n")) (error "header exceeds maximum length") (let ([header (read-line c-in 'return-linefeed)]) (define-values (status meta) (let ([status-meta (string-split header " ")]) (values (car status-meta) (string-join (cdr status-meta))))) (cond [(> (string-length status) 2) (error "status code exceeds maximum length")] [(andmap (compose not char-numeric?) (string->list status)) (error "status code is not numeric")] [else (values (string->number status) meta)]))))) (define (get url-str) (define (iter url-str depth) (let-values ([(status header c-in) (send-request url-str)]) ;; 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)) (error "server returned invalid status code")] ;; 30-39 redirection [(and (>= status 30) (<= status 39)) (if (> depth 5) (error "maximum redirection depth exceeded") (iter header (sub1 depth)))] [else (values status header c-in)]))) (iter url-str 5))