#lang scheme
(require (prefix-in log: (planet synx/log)))
(require srfi/19)
(require net/url)
(define-struct header (value raw) #:mutable)
(define-struct headers (dict) #:transparent)
(define parse-header
(let ([splitter #px"([^:]+)\\s*:\\s*(.*)"])
(λ (line headers)
(let ([match (cdr (regexp-match splitter line))])
(let ([name (car match)]
[raw (cadr match)])
(set-header headers name raw #t))))))
(define (join-header name raw)
(format "~a: ~a" name raw))
(define (make-headers-dict [pairs null])
(make-immutable-custom-hash (λ (a b) (equal? (string-downcase a) (string-downcase b)))
(λ (a) (equal-hash-code (string-downcase a)))
(λ (a) (equal-secondary-hash-code (string-downcase a)))))
(define token-headers
'("Transfer-Encoding" "Connection" "Accept-Ranges"))
(define number-headers
'("Content-Length" "Keep-alive"))
(define date-headers
'("Date" "Last-Modified" "If-Modified-Since" "If-Not-Modified-Since"))
(define uri-headers
'("Location" "Referer" "Referrer"))
(define (decode-date raw)
(string->date raw "~a, ~d ~b ~Y ~H:~M:~S GMT"))
(define (encode-date value)
(date->string value "~a, ~d ~b ~Y ~H:~M:~S GMT"))
(define (is-in? l a)
(foldl (λ (i rest) (or rest (equal? i a))) #f l))
(define (decode name raw)
(cond
[(is-in? token-headers name) (regexp-split #rx" " raw)]
[(is-in? number-headers name) (string->number raw)]
[(is-in? date-headers name) (decode-date raw)]
[(is-in? uri-headers name) (string->url raw)]
[else raw]))
(define (encode name value)
(cond
[(is-in? token-headers name) (string-join value " ")]
[(is-in? number-headers name) (number->string value)]
[(is-in? date-headers name) (encode-date value)]
[(is-in? uri-headers name) (url->string value)]
[else value]))
(define (get-header headers name)
(if headers
(let ([header (dict-ref (headers-dict headers) name (λ () #f))])
(if header
(or (header-value header)
(begin
(set-header-value! header (decode name (header-raw header)))
(header-value header)))
#f))
#f))
(define (set-header headerses name value [raw #f])
(let ([header
(if raw
(make-header #f value)
(make-header value #f))])
(if headerses
(struct-copy headers headerses (dict (dict-set (headers-dict headerses) name header)))
(make-headers (dict-set (make-headers-dict) name header)))))
(define (set-headers headers [pairs null])
(foldl (λ (i headers) (set-header headers (car i) (cdr i)))
headers
pairs))
(define (for-each-raw-header headers proc)
(dict-for-each
(headers-dict headers)
(λ (name header)
(proc
name
(or (header-raw header)
(begin
(set-header-raw! header (encode name (header-value header)))
(header-raw header)))))))
(define status-line
(let ([split #rx" "]
[front #rx"HTTP/([0-9]\\.[0-9])"])
(λ (line)
(let ([bits (regexp-split split line)])
(when (not (>= (length bits) 3))
(raise-user-error (format "Bad status line ~s must have at least 3 bits!" line)))
(let ([version (regexp-match front (car bits))])
(when (not version)
(raise-user-error "Bad version ~s" (car bits)))
(let ([version (string->number (cadr version))]
[code (string->number (cadr bits))])
(values version code (string-join (cddr bits) " "))))))))
(define headers/c (or/c headers? false?))
(provide/contract
[get-header (-> headers/c string? any/c)]
[set-header (->* (headers/c string? any/c) (boolean?) headers?)]
[set-headers (->* (headers/c) ((listof pair?)) headers?)]
[is-in? (-> (listof string?) string? boolean?)]
[status-line (-> string? (values number? integer? string?))]
[for-each-raw-header (-> headers? (-> string? string? any) void?)]
[parse-header (-> string? headers/c headers?)]
[join-header (-> string? string? string?)])
(provide headers/c headers?)