web http: fix Ma -> Mar
[bpt/guile.git] / module / web / http.scm
index 5245cca..879923f 100644 (file)
@@ -1,6 +1,6 @@
 ;;; HTTP messages
 
-;; Copyright (C)  2010 Free Software Foundation, Inc.
+;; Copyright (C)  2010, 2011, 2012 Free Software Foundation, Inc.
 
 ;; This library is free software; you can redistribute it and/or
 ;; modify it under the terms of the GNU Lesser General Public
 ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 ;; 02110-1301 USA
 
+;;; Commentary:
+;;;
+;;; This module has a number of routines to parse textual
+;;; representations of HTTP data into native Scheme data structures.
+;;;
+;;; It tries to follow RFCs fairly strictly---the road to perdition
+;;; being paved with compatibility hacks---though some allowances are
+;;; made for not-too-divergent texts (like a quality of .2 which should
+;;; be 0.2, etc).
+;;;
 ;;; Code:
 
 (define-module (web http)
   #:use-module ((srfi srfi-1) #:select (append-map! map!))
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-19)
-  #:use-module (ice-9 regex)
   #:use-module (ice-9 rdelim)
   #:use-module (web uri)
-  #:export (header-decl?
-            make-header-decl
-            header-decl-sym
-            header-decl-name
-            header-decl-multiple?
-            header-decl-parser
-            header-decl-validator
-            header-decl-writer
-            lookup-header-decl
+  #:export (string->header
+            header->string
+
             declare-header!
+            known-header?
+            header-parser
+            header-validator
+            header-writer
 
             read-header
             parse-header
 ;;; 
 
 
+(define (string->header name)
+  "Parse @var{name} to a symbolic header name."
+  (string->symbol (string-downcase name)))
+
 (define-record-type <header-decl>
-  (make-header-decl sym name multiple? parser validator writer)
+  (make-header-decl name parser validator writer multiple?)
   header-decl?
-  (sym header-decl-sym)
   (name header-decl-name)
-  (multiple? header-decl-multiple?)
   (parser header-decl-parser)
   (validator header-decl-validator)
-  (writer header-decl-writer))
+  (writer header-decl-writer)
+  (multiple? header-decl-multiple?))
 
 ;; sym -> header
 (define *declared-headers* (make-hash-table))
-;; downcased name -> header
-(define *declared-headers-by-name* (make-hash-table))
 
-(define* (declare-header! sym name #:key 
-                          multiple?
+(define (lookup-header-decl sym)
+  (hashq-ref *declared-headers* sym))
+
+(define* (declare-header! name
                           parser
                           validator
-                          writer)
-  (if (and (symbol? sym) (string? name) parser validator writer)
-      (let ((decl (make-header-decl sym name
-                                    multiple? parser validator writer)))
-        (hashq-set! *declared-headers* sym decl)
-        (hash-set! *declared-headers-by-name* (string-downcase name) decl)
+                          writer
+                          #:key multiple?)
+  "Define a parser, validator, and writer for the HTTP header, @var{name}.
+
+@var{parser} should be a procedure that takes a string and returns a
+Scheme value.  @var{validator} is a predicate for whether the given
+Scheme value is valid for this header.  @var{writer} takes a value and a
+port, and writes the value to the port."
+  (if (and (string? name) parser validator writer)
+      (let ((decl (make-header-decl name parser validator writer multiple?)))
+        (hashq-set! *declared-headers* (string->header name) decl)
         decl)
-      (error "bad header decl" sym name multiple? parser validator writer)))
+      (error "bad header decl" name parser validator writer multiple?)))
+
+(define (header->string sym)
+  "Return the string form for the header named @var{sym}."
+  (let ((decl (lookup-header-decl sym)))
+    (if decl
+        (header-decl-name decl)
+        (string-titlecase (symbol->string sym)))))
+
+(define (known-header? sym)
+  "Return @code{#t} if there are parsers and writers registered for this
+header, otherwise @code{#f}."
+  (and (lookup-header-decl sym) #t))
+
+(define (header-parser sym)
+  "Returns a procedure to parse values for the given header."
+  (let ((decl (lookup-header-decl sym)))
+    (if decl
+        (header-decl-parser decl)
+        (lambda (x) x))))
+
+(define (header-validator sym)
+  "Returns a procedure to validate values for the given header."
+  (let ((decl (lookup-header-decl sym)))
+    (if decl
+        (header-decl-validator decl)
+        string?)))
+
+(define (header-writer sym)
+  "Returns a procedure to write values for the given header to a given
+port."
+  (let ((decl (lookup-header-decl sym)))
+    (if decl
+        (header-decl-writer decl)
+        display)))
 
 (define (read-line* port)
   (let* ((pair (%read-line port))
                                                (read-line* port))))
       val))
 
+(define *eof* (call-with-input-string "" read))
+
 (define (read-header port)
+  "Reads one HTTP header from @var{port}. Returns two values: the header
+name and the parsed Scheme value. May raise an exception if the header
+was known but the value was invalid.
+
+Returns the end-of-file object for both values if the end of the message
+body was reached (i.e., a blank line)."
   (let ((line (read-line* port)))
     (if (or (string-null? line)
             (string=? line "\r"))
-        (values #f #f)
-        (let ((delim (or (string-index line #\:)
-                         (bad-header '%read line))))
-          (parse-header
-           (substring line 0 delim)
-           (read-continuation-line
-            port
-            (string-trim-both line char-whitespace? (1+ delim))))))))
-
-(define (lookup-header-decl name)
-  (if (string? name)
-      (hash-ref *declared-headers-by-name* (string-downcase name))
-      (hashq-ref *declared-headers* name)))
-
-(define (parse-header name val)
-  (let* ((down (string-downcase name))
-         (decl (hash-ref *declared-headers-by-name* down)))
-    (if decl
-        (values (header-decl-sym decl)
-                ((header-decl-parser decl) val))
-        (values down val))))
+        (values *eof* *eof*)
+        (let* ((delim (or (string-index line #\:)
+                          (bad-header '%read line)))
+               (sym (string->header (substring line 0 delim))))
+          (values
+           sym
+           (parse-header
+            sym
+            (read-continuation-line
+             port
+             (string-trim-both line char-whitespace? (1+ delim)))))))))
+
+(define (parse-header sym val)
+  "Parse @var{val}, a string, with the parser registered for the header
+named @var{sym}.
+
+Returns the parsed value.  If a parser was not found, the value is
+returned as a string."
+  ((header-parser sym) val))
 
 (define (valid-header? sym val)
-  (let ((decl (hashq-ref *declared-headers* sym)))
-    (if (not decl)
-        (error "Unknown header" sym)
-        ((header-decl-validator decl) val))))
-
-(define (write-header name val port)
-  (if (string? name)
-      ;; assume that it's a header we don't know about...
-      (begin
-        (display name port)
-        (display ": " port)
-        (display val port)
-        (display "\r\n" port))
-      (let ((decl (hashq-ref *declared-headers* name)))
-        (if (not decl)
-            (error "Unknown header" name)
-            (begin
-              (display (header-decl-name decl) port)
-              (display ": " port)
-              ((header-decl-writer decl) val port)
-              (display "\r\n" port))))))
+  "Returns a true value iff @var{val} is a valid Scheme value for the
+header with name @var{sym}."
+  (if (symbol? sym)
+      ((header-validator sym) val)
+      (error "header name not a symbol" sym)))
+
+(define (write-header sym val port)
+  "Writes the given header name and value to @var{port}.  If @var{sym}
+is a known header, uses the specific writer registered for that header.
+Otherwise the value is written using @code{display}."
+  (display (header->string sym) port)
+  (display ": " port)
+  ((header-writer sym) val port)
+  (display "\r\n" port))
 
 (define (read-headers port)
+  "Read an HTTP message from @var{port}, returning the headers as an
+ordered alist."
   (let lp ((headers '()))
     (call-with-values (lambda () (read-header port))
       (lambda (k v)
-        (if k
-            (lp (acons k v headers))
-            (reverse! headers))))))
+        (if (eof-object? k)
+            (reverse! headers)
+            (lp (acons k v headers)))))))
 
-;; Doesn't write the final \r\n, as the user might want to add another
-;; header.
 (define (write-headers headers port)
+  "Write the given header alist to @var{port}.  Doesn't write the final
+\\r\\n, as the user might want to add another header."
   (let lp ((headers headers))
     (if (pair? headers)
         (begin
 (define (write-opaque-string val port)
   (display val port))
 
-(define not-separator
-  "[^][()<>@,;:\\\"/?= \t]")
-(define media-type-re
-  (make-regexp (format #f "^(~a+)/(~a+)$" not-separator not-separator)))
+(define separators-without-slash
+  (string->char-set "[^][()<>@,;:\\\"?= \t]"))
+(define (validate-media-type str)
+  (let ((idx (string-index str #\/)))
+    (and idx (= idx (string-rindex str #\/))
+         (not (string-index str separators-without-slash)))))
 (define (parse-media-type str)
-  (let ((m (regexp-exec media-type-re str)))
-    (if m
-        (values (match:substring m 1) (match:substring m 2))
-        (bad-header-component 'media-type str))))
+  (if (validate-media-type str)
+      (string->symbol str)
+      (bad-header-component 'media-type str)))
 
 (define* (skip-whitespace str #:optional (start 0) (end (string-length str)))
   (let lp ((i start))
           (cons tok (split-and-trim str delim (if idx (1+ idx) end) end)))
         '())))
 
+(define (list-of-strings? val)
+  (list-of? val string?))
+
+(define (write-list-of-strings val port)
+  (write-list val port display ", "))
+
+(define (split-header-names str)
+  (map string->header (split-and-trim str)))
+
+(define (list-of-header-names? val)
+  (list-of? val symbol?))
+
+(define (write-header-list val port)
+  (write-list val port
+              (lambda (x port)
+                (display (header->string x) port))
+              ", "))
+
 (define (collect-escaped-string from start len escapes)
   (let ((to (make-string len)))
     (let lp ((start start) (i 0) (escapes escapes))
                           (+ q (* place (char->decimal (string-ref str i))))
                           q))))
             (bad-header-component 'quality str))))
+   ;; Allow the nonstandard .2 instead of 0.2.
+   ((and (eqv? (string-ref str start) #\.)
+         (< 1 (- end start) 5))
+    (let lp ((place 1) (i (+ start 3)) (q 0))
+      (if (= i start)
+          q
+          (lp (* 10 place) (1- i)
+              (if (< i end)
+                  (+ q (* place (char->decimal (string-ref str i))))
+                  q)))))
    (else
     (bad-header-component 'quality str))))
 
 (define (valid-quality? q)
-  (and (non-negative-integer? q) (<= 1000 q)))
+  (and (non-negative-integer? q) (<= q 1000)))
 
 (define (write-quality q port)
   (define (digit->char d)
 (define (non-negative-integer? code)
   (and (number? code) (>= code 0) (exact? code) (integer? code)))
                                     
-(define (default-kons k val)
-  (if val
-      (cons k val)
-      k))
+(define (default-val-parser k val)
+  val)
 
-(define (default-kv-validator k val)
-  #t)
+(define (default-val-validator k val)
+  (or (not val) (string? val)))
 
 (define (default-val-writer k val port)
   (if (or (string-index val #\;)
       (write-qstring val port)
       (display val port)))
 
-(define* (parse-key-value-list str #:optional (kproc identity)
-                               (kons default-kons)
+(define* (parse-key-value-list str #:optional
+                               (val-parser default-val-parser)
                                (start 0) (end (string-length str)))
   (let lp ((i start) (out '()))
     (if (not (< i end))
                (eq (string-index str #\= i end))
                (comma (string-index str #\, i end))
                (delim (min (or eq end) (or comma end)))
-               (k (kproc (substring str i (trim-whitespace str i delim)))))
+               (k (string->symbol
+                   (substring str i (trim-whitespace str i delim)))))
           (call-with-values
               (lambda ()
                 (if (and eq (or (not comma) (< eq comma)))
                                   (or comma end))))
                     (values #f delim)))
             (lambda (v-str next-i)
-              (let ((i (skip-whitespace str next-i end)))
+              (let ((v (val-parser k v-str))
+                    (i (skip-whitespace str next-i end)))
                 (if (or (= i end) (eqv? (string-ref str i) #\,))
-                    (lp (1+ i) (cons (kons k v-str) out))
+                    (lp (1+ i) (cons (if v (cons k v) k) out))
                     (bad-header-component 'key-value-list
                                           (substring str start end))))))))))
 
 (define* (key-value-list? list #:optional
-                          (valid? default-kv-validator))
+                          (valid? default-val-validator))
   (list-of? list
             (lambda (elt)
               (cond
                ((pair? elt)
                 (let ((k (car elt))
                       (v (cdr elt)))
-                  (and (or (string? k) (symbol? k))
+                  (and (symbol? k)
                        (valid? k v))))
-               ((or (string? elt) (symbol? elt))
+               ((symbol? elt)
                 (valid? elt #f))
                (else #f)))))
 
 ;; param-component = token [ "=" (token | quoted-string) ] \
 ;;    *(";" token [ "=" (token | quoted-string) ])
 ;;
-(define* (parse-param-component str #:optional (kproc identity)
-                                (kons default-kons)
+(define* (parse-param-component str #:optional
+                                (val-parser default-val-parser)
                                 (start 0) (end (string-length str)))
   (let lp ((i start) (out '()))
     (if (not (< i end))
         (let ((delim (string-index str
                                    (lambda (c) (memq c '(#\, #\; #\=)))
                                    i)))
-          (let ((k (kproc
+          (let ((k (string->symbol
                     (substring str i (trim-whitespace str i (or delim end)))))
                 (delimc (and delim (string-ref str delim))))
             (case delimc
                              (values (substring str i delim)
                                      delim)))))
                  (lambda (v-str next-i)
-                   (let ((x (kons k v-str))
-                         (i (skip-whitespace str next-i end)))
+                   (let* ((v (val-parser k v-str))
+                          (x (if v (cons k v) k))
+                          (i (skip-whitespace str next-i end)))
                      (case (and (< i end) (string-ref str i))
                        ((#f)
                         (values (reverse! (cons x out)) end))
                        (else            ; including #\,
                         (values (reverse! (cons x out)) i)))))))
               ((#\;)
-               (lp (skip-whitespace str (1+ delim) end)
-                   (cons (kons k #f) out)))
+               (let ((v (val-parser k #f)))
+                 (lp (skip-whitespace str (1+ delim) end)
+                     (cons (if v (cons k v) k) out))))
              
               (else ;; either the end of the string or a #\,
-               (values (reverse! (cons (kons k #f) out))
-                       (or delim end)))))))))
+               (let ((v (val-parser k #f)))
+                 (values (reverse! (cons (if v (cons k v) k) out))
+                         (or delim end))))))))))
 
 (define* (parse-param-list str #:optional
-                           (kproc identity) (kons default-kons)
+                           (val-parser default-val-parser)
                            (start 0) (end (string-length str)))
   (let lp ((i start) (out '()))
     (call-with-values
-        (lambda () (parse-param-component str kproc kons i end))
+        (lambda () (parse-param-component str val-parser i end))
       (lambda (item i)
         (if (< i end)
             (if (eqv? (string-ref str i) #\,)
             (reverse! (cons item out)))))))
 
 (define* (validate-param-list list #:optional
-                              (valid? default-kv-validator))
+                              (valid? default-val-validator))
   (list-of? list
             (lambda (elt)
-              (key-value-list? list valid?))))
+              (key-value-list? elt valid?))))
 
 (define* (write-param-list list port #:optional
                            (val-writer default-val-writer))
      (write-key-value-list item port val-writer ";"))
    ","))
 
-(define (list-of-strings? val)
-  (list-of? val string?))
-
-(define (write-list-of-strings val port)
-  (write-list val port display ", "))
+(define-syntax string-match?
+  (lambda (x)
+    (syntax-case x ()
+      ((_ str pat) (string? (syntax->datum #'pat))
+       (let ((p (syntax->datum #'pat)))
+         #`(let ((s str))
+             (and
+              (= (string-length s) #,(string-length p))
+              #,@(let lp ((i 0) (tests '()))
+                   (if (< i (string-length p))
+                       (let ((c (string-ref p i)))
+                         (lp (1+ i)
+                             (case c
+                               ((#\.)   ; Whatever.
+                                tests)
+                               ((#\d)   ; Digit.
+                                (cons #`(char-numeric? (string-ref s #,i))
+                                      tests))
+                               ((#\a)   ; Alphabetic.
+                                (cons #`(char-alphabetic? (string-ref s #,i))
+                                      tests))
+                               (else    ; Literal.
+                                (cons #`(eqv? (string-ref s #,i) #,c)
+                                      tests)))))
+                       tests)))))))))
+
+;; "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun"
+;; "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
+
+(define (parse-month str start end)
+  (define (bad)
+    (bad-header-component 'month (substring str start end)))
+  (if (not (= (- end start) 3))
+      (bad)
+      (let ((a (string-ref str (+ start 0)))
+            (b (string-ref str (+ start 1)))
+            (c (string-ref str (+ start 2))))
+        (case a
+          ((#\J)
+           (case b
+             ((#\a) (case c ((#\n) 1) (else (bad))))
+             ((#\u) (case c ((#\n) 6) ((#\l) 7) (else (bad))))
+             (else (bad))))
+          ((#\F)
+           (case b
+             ((#\e) (case c ((#\b) 2) (else (bad))))
+             (else (bad))))
+          ((#\M)
+           (case b
+             ((#\a) (case c ((#\r) 3) ((#\y) 5) (else (bad))))
+             (else (bad))))
+          ((#\A)
+           (case b
+             ((#\p) (case c ((#\r) 4) (else (bad))))
+             ((#\u) (case c ((#\g) 8) (else (bad))))
+             (else (bad))))
+          ((#\S)
+           (case b
+             ((#\e) (case c ((#\p) 9) (else (bad))))
+             (else (bad))))
+          ((#\O)
+           (case b
+             ((#\c) (case c ((#\t) 10) (else (bad))))
+             (else (bad))))
+          ((#\N)
+           (case b
+             ((#\o) (case c ((#\v) 11) (else (bad))))
+             (else (bad))))
+          ((#\D)
+           (case b
+             ((#\e) (case c ((#\c) 12) (else (bad))))
+             (else (bad))))
+          (else (bad))))))
+
+;; RFC 822, updated by RFC 1123
+;; 
+;; Sun, 06 Nov 1994 08:49:37 GMT
+;; 01234567890123456789012345678
+;; 0         1         2
+(define (parse-rfc-822-date str)
+  ;; We could verify the day of the week but we don't.
+  (cond ((string-match? str "aaa, dd aaa dddd dd:dd:dd GMT")
+         (let ((date (parse-non-negative-integer str 5 7))
+               (month (parse-month str 8 11))
+               (year (parse-non-negative-integer str 12 16))
+               (hour (parse-non-negative-integer str 17 19))
+               (minute (parse-non-negative-integer str 20 22))
+               (second (parse-non-negative-integer str 23 25)))
+           (make-date 0 second minute hour date month year 0)))
+        ((string-match? str "aaa, d aaa dddd dd:dd:dd GMT")
+         (let ((date (parse-non-negative-integer str 5 6))
+               (month (parse-month str 7 10))
+               (year (parse-non-negative-integer str 11 15))
+               (hour (parse-non-negative-integer str 16 18))
+               (minute (parse-non-negative-integer str 19 21))
+               (second (parse-non-negative-integer str 22 24)))
+           (make-date 0 second minute hour date month year 0)))
+        (else
+         (bad-header 'date str)         ; prevent tail call
+         #f)))
+
+;; RFC 850, updated by RFC 1036
+;; Sunday, 06-Nov-94 08:49:37 GMT
+;;        0123456789012345678901
+;;        0         1         2
+(define (parse-rfc-850-date str comma)
+  ;; We could verify the day of the week but we don't.
+  (let ((tail (substring str (1+ comma))))
+    (if (not (string-match? tail " dd-aaa-dd dd:dd:dd GMT"))
+        (bad-header 'date str))
+    (let ((date (parse-non-negative-integer tail 1 3))
+          (month (parse-month tail 4 7))
+          (year (parse-non-negative-integer tail 8 10))
+          (hour (parse-non-negative-integer tail 11 13))
+          (minute (parse-non-negative-integer tail 14 16))
+          (second (parse-non-negative-integer tail 17 19)))
+      (make-date 0 second minute hour date month
+                 (let* ((now (date-year (current-date)))
+                        (then (+ now year (- (modulo now 100)))))
+                   (cond ((< (+ then 50) now) (+ then 100))
+                         ((< (+ now 50) then) (- then 100))
+                         (else then)))
+                 0))))
+
+;; ANSI C's asctime() format
+;; Sun Nov  6 08:49:37 1994
+;; 012345678901234567890123
+;; 0         1         2
+(define (parse-asctime-date str)
+  (if (not (string-match? str "aaa aaa .d dd:dd:dd dddd"))
+      (bad-header 'date str))
+  (let ((date (parse-non-negative-integer
+               str
+               (if (eqv? (string-ref str 8) #\space) 9 8)
+               10))
+        (month (parse-month str 4 7))
+        (year (parse-non-negative-integer str 20 24))
+        (hour (parse-non-negative-integer str 11 13))
+        (minute (parse-non-negative-integer str 14 16))
+        (second (parse-non-negative-integer str 17 19)))
+    (make-date 0 second minute hour date month year 0)))
 
 (define (parse-date str)
-  ;; Unfortunately, there is no way to make string->date parse out the
-  ;; "GMT" bit, so we play string games to append a format it will
-  ;; understand (the +0000 bit).
-  (string->date
-   (if (string-suffix? " GMT" str)
-       (string-append (substring str 0 (- (string-length str) 4))
-                      " +0000")
-       (bad-header-component 'date str))
-   "~a, ~d ~b ~Y ~H:~M:~S ~z"))
+  (if (string-suffix? " GMT" str)
+      (let ((comma (string-index str #\,)))
+        (cond ((not comma) (bad-header 'date str))
+              ((= comma 3) (parse-rfc-822-date str))
+              (else (parse-rfc-850-date str comma))))
+      (parse-asctime-date str)))
 
 (define (write-date date port)
-  (display (date->string date "~a, ~d ~b ~Y ~H:~M:~S GMT") port))
-
-(define (write-uri uri port)
-  (display (unparse-uri uri) port))
+  (define (display-digits n digits port)
+    (define zero (char->integer #\0))
+    (let lp ((tens (expt 10 (1- digits))))
+      (if (> tens 0)
+          (begin
+            (display (integer->char (+ zero (modulo (truncate/ n tens) 10)))
+                    port)
+            (lp (floor/ tens 10))))))
+  (let ((date (if (zero? (date-zone-offset date))
+                  date
+                  (time-tai->date (date->time-tai date) 0))))
+    (display (case (date-week-day date)
+               ((0) "Sun, ") ((1) "Mon, ") ((2) "Tue, ")
+               ((3) "Wed, ") ((4) "Thu, ") ((5) "Fri, ")
+               ((6) "Sat, ") (else (error "bad date" date)))
+             port)
+    (display-digits (date-day date) 2 port)
+    (display (case (date-month date)
+               ((1)  " Jan ") ((2)  " Feb ") ((3)  " Mar ")
+               ((4)  " Apr ") ((5)  " May ") ((6)  " Jun ")
+               ((7)  " Jul ") ((8)  " Aug ") ((9)  " Sep ")
+               ((10) " Oct ") ((11) " Nov ") ((12) " Dec ")
+               (else (error "bad date" date)))
+             port)
+    (display-digits (date-year date) 4 port)
+    (display #\space port)
+    (display-digits (date-hour date) 2 port)
+    (display #\: port)
+    (display-digits (date-minute date) 2 port)
+    (display #\: port)
+    (display-digits (date-second date) 2 port)
+    (display " GMT" port)))
 
 (define (parse-entity-tag val)
   (if (string-prefix? "W/" val)
        (string? (car val))))
 
 (define (write-entity-tag val port)
-  (if (cdr val)
+  (if (not (cdr val))
       (display "W/" port))
   (write-qstring (car val) port))
 
 (define (write-entity-tag-list val port)
   (write-list val port write-entity-tag  ", "))
 
+;; credentials = auth-scheme #auth-param
+;; auth-scheme = token
+;; auth-param = token "=" ( token | quoted-string )
+;;
+;; That's what the spec says. In reality the Basic scheme doesn't have
+;; k-v pairs, just one auth token, so we give that token as a string.
+;;
+(define* (parse-credentials str #:optional (val-parser default-val-parser)
+                            (start 0) (end (string-length str)))
+  (let* ((start (skip-whitespace str start end))
+         (delim (or (string-index str char-whitespace? start end) end)))
+    (if (= start end)
+        (bad-header-component 'authorization str))
+    (let ((scheme (string->symbol
+                   (string-downcase (substring str start (or delim end))))))
+      (case scheme
+        ((basic)
+         (let* ((start (skip-whitespace str delim end)))
+           (if (< start end)
+               (cons scheme (substring str start end))
+               (bad-header-component 'credentials str))))
+        (else
+         (cons scheme (parse-key-value-list str default-val-parser delim end)))))))
+
+(define (validate-credentials val)
+  (and (pair? val) (symbol? (car val))
+       (case (car val)
+         ((basic) (string? (cdr val)))
+         (else (key-value-list? (cdr val))))))
+
+(define (write-credentials val port)
+  (display (car val) port)
+  (if (pair? (cdr val))
+      (begin
+        (display #\space port)
+        (write-key-value-list (cdr val) port))))
+
+;; challenges = 1#challenge
+;; challenge = auth-scheme 1*SP 1#auth-param
+;;
+;; A pain to parse, as both challenges and auth params are delimited by
+;; commas, and qstrings can contain anything. We rely on auth params
+;; necessarily having "=" in them.
+;;
+(define* (parse-challenge str #:optional
+                          (start 0) (end (string-length str)))
+  (let* ((start (skip-whitespace str start end))
+         (sp (string-index str #\space start end))
+         (scheme (if sp
+                     (string->symbol (string-downcase (substring str start sp)))
+                     (bad-header-component 'challenge str))))
+    (let lp ((i sp) (out (list scheme)))
+      (if (not (< i end))
+          (values (reverse! out) end)
+          (let* ((i (skip-whitespace str i end))
+                 (eq (string-index str #\= i end))
+                 (comma (string-index str #\, i end))
+                 (delim (min (or eq end) (or comma end)))
+                 (token-end (trim-whitespace str i delim)))
+            (if (string-index str #\space i token-end)
+                (values (reverse! out) i)
+                (let ((k (string->symbol (substring str i token-end))))
+                  (call-with-values
+                      (lambda ()
+                        (if (and eq (or (not comma) (< eq comma)))
+                            (let ((i (skip-whitespace str (1+ eq) end)))
+                              (if (and (< i end) (eqv? (string-ref str i) #\"))
+                                  (parse-qstring str i end #:incremental? #t)
+                                  (values (substring
+                                           str i
+                                           (trim-whitespace str i
+                                                            (or comma end)))
+                                          (or comma end))))
+                            (values #f delim)))
+                    (lambda (v next-i)
+                      (let ((i (skip-whitespace str next-i end)))
+                        (if (or (= i end) (eqv? (string-ref str i) #\,))
+                            (lp (1+ i) (cons (if v (cons k v) k) out))
+                            (bad-header-component
+                             'challenge
+                             (substring str start end)))))))))))))
+
+(define* (parse-challenges str #:optional (val-parser default-val-parser)
+                           (start 0) (end (string-length str)))
+  (let lp ((i start) (ret '()))
+    (let ((i (skip-whitespace str i end)))
+      (if (< i end)
+          (call-with-values (lambda () (parse-challenge str i end))
+            (lambda (challenge i)
+              (lp i (cons challenge ret))))
+          (reverse ret)))))
+
+(define (validate-challenges val)
+  (list-of? val (lambda (x)
+                  (and (pair? x) (symbol? (car x))
+                       (key-value-list? (cdr x))))))
+
+(define (write-challenge val port)
+  (display (car val) port)
+  (display #\space port)
+  (write-key-value-list (cdr val) port))
+
+(define (write-challenges val port)
+  (write-list val port write-challenge ", "))
+
 
 \f
 
 (define *known-versions* '())
 
 (define* (parse-http-version str #:optional (start 0) (end (string-length str)))
+  "Parse an HTTP version from @var{str}, returning it as a major-minor
+pair. For example, @code{HTTP/1.1} parses as the pair of integers,
+@code{(1 . 1)}."
   (or (let lp ((known *known-versions*))
         (and (pair? known)
              (if (string= str (caar known) start end)
             (bad-header-component 'http-version (substring str start end))))))
 
 (define (write-http-version val port)
+  "Write the given major-minor version pair to @var{port}."
   (display "HTTP/" port)
   (display (car val) port)
   (display #\. port)
 ;; ourselves the trouble of that case, and disallow the CONNECT method.
 ;;
 (define* (parse-http-method str #:optional (start 0) (end (string-length str)))
+  "Parse an HTTP method from @var{str}.  The result is an upper-case
+symbol, like @code{GET}."
   (cond
    ((string= str "GET" start end) 'GET)
    ((string= str "HEAD" start end) 'HEAD)
    (else (bad-request "Invalid method: ~a" (substring str start end)))))
 
 (define* (parse-request-uri str #:optional (start 0) (end (string-length str)))
+  "Parse a URI from an HTTP request line.  Note that URIs in requests do
+not have to have a scheme or host name.  The result is a URI object."
   (cond
    ((= start end)
     (bad-request "Missing Request-URI"))
                  #:query (and q (substring str (1+ q) (or f end)))
                  #:fragment (and f (substring str (1+ f) end)))))
    (else
-    (or (parse-uri (substring str start end))
+    (or (string->uri (substring str start end))
         (bad-request "Invalid URI: ~a" (substring str start end))))))
 
 (define (read-request-line port)
+  "Read the first line of an HTTP request from @var{port}, returning
+three values: the method, the URI, and the version."
   (let* ((line (read-line* port))
          (d0 (string-index line char-whitespace?)) ; "delimiter zero"
          (d1 (string-rindex line char-whitespace?)))
         (display (uri-query uri) port))))
 
 (define (write-request-line method uri version port)
+  "Write the first line of an HTTP request to @var{port}."
   (display method port)
   (display #\space port)
-  (write-uri uri port)
+  (let ((path (uri-path uri))
+        (query (uri-query uri)))
+    (if (not (string-null? path))
+        (display path port))
+    (if query
+        (begin
+          (display "?" port)
+          (display query port)))
+    (if (and (string-null? path)
+             (not query))
+        ;; Make sure we display something.
+        (display "/" port)))
   (display #\space port)
   (write-http-version version port)
   (display "\r\n" port))
 
 (define (read-response-line port)
+  "Read the first line of an HTTP response from @var{port}, returning
+three values: the HTTP version, the response code, and the \"reason
+phrase\"."
   (let* ((line (read-line* port))
          (d0 (string-index line char-whitespace?)) ; "delimiter zero"
          (d1 (and d0 (string-index line char-whitespace?
         (bad-response "Bad Response-Line: ~s" line))))
 
 (define (write-response-line version code reason-phrase port)
+  "Write the first line of an HTTP response to @var{port}."
   (write-http-version version port)
   (display #\space port)
   (display code port)
 \f
 
 ;;;
-;;; Syntax for declaring headers
+;;; Helpers for declaring headers
 ;;;
 
-;; emacs: (put 'declare-header 'scheme-indent-function 1)
-(define-syntax declare-header
-  (syntax-rules ()
-    ((_ sym name parser validator writer arg ...)
-     (declare-header!
-      'sym name
-      #:parser parser #:validator validator #:writer writer
-      arg ...))))
-
-;; emacs: (put 'declare-opaque-header 'scheme-indent-function 1)
-(define-syntax declare-opaque-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       parse-opaque-string validate-opaque-string write-opaque-string))))
-
-;; emacs: (put 'declare-date-header 'scheme-indent-function 1)
-(define-syntax declare-date-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       parse-date date? write-date))))
-
-;; emacs: (put 'declare-string-list-header 'scheme-indent-function 1)
-(define-syntax declare-string-list-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       split-and-trim list-of-strings? write-list-of-strings))))
-
-;; emacs: (put 'declare-integer-header 'scheme-indent-function 1)
-(define-syntax declare-integer-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       parse-non-negative-integer non-negative-integer? display))))
-
-;; emacs: (put 'declare-uri-header 'scheme-indent-function 1)
-(define-syntax declare-uri-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       (lambda (str) (or (parse-uri str) (bad-header-component 'uri str)))
-       uri?
-       write-uri))))
-
-;; emacs: (put 'declare-quality-list-header 'scheme-indent-function 1)
-(define-syntax declare-quality-list-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       parse-quality-list validate-quality-list write-quality-list))))
-
-;; emacs: (put 'declare-param-list-header 'scheme-indent-function 1)
-(define-syntax declare-param-list-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-param-list-header sym name identity default-kons
-                                default-kv-validator default-val-writer))
-    ((_ sym name kproc)
-     (declare-param-list-header sym name kproc default-kons
-                                default-kv-validator default-val-writer))
-    ((_ sym name kproc kons val-validator val-writer)
-     (declare-header sym
-       name
-       (lambda (str) (parse-param-list str kproc kons))
-       (lambda (val) (validate-param-list val val-validator))
-       (lambda (val port) (write-param-list val port val-writer))))))
-
-;; emacs: (put 'declare-key-value-list-header 'scheme-indent-function 1)
-(define-syntax declare-key-value-list-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-key-value-list-header sym name identity default-kons
-                                    default-kv-validator default-val-writer))
-    ((_ sym name kproc)
-     (declare-key-value-list-header sym name kproc default-kons
-                                    default-kv-validator default-val-writer))
-    ((_ sym name kproc kons val-validator val-writer)
-     (declare-header sym
-       name
-       (lambda (str) (parse-key-value-list str kproc kons))
-       (lambda (val) (key-value-list? val val-validator))
-       (lambda (val port) (write-key-value-list val port val-writer))))))
-
-;; emacs: (put 'declare-entity-tag-list-header 'scheme-indent-function 1)
-(define-syntax declare-entity-tag-list-header
-  (syntax-rules ()
-    ((_ sym name)
-     (declare-header sym
-       name
-       (lambda (str) (if (string=? str "*") '* (parse-entity-tag-list str)))
-       (lambda (val) (or (eq? val '*) (entity-tag-list? val)))
-       (lambda (val port)
-         (if (eq? val '*)
-             (display "*" port)
-             (write-entity-tag-list val port)))))))
+;; emacs: (put 'declare-header! 'scheme-indent-function 1)
+;; emacs: (put 'declare-opaque!-header 'scheme-indent-function 1)
+(define (declare-opaque-header! name)
+  (declare-header! name
+    parse-opaque-string validate-opaque-string write-opaque-string))
+
+;; emacs: (put 'declare-date-header! 'scheme-indent-function 1)
+(define (declare-date-header! name)
+  (declare-header! name
+    parse-date date? write-date))
+
+;; emacs: (put 'declare-string-list-header! 'scheme-indent-function 1)
+(define (declare-string-list-header! name)
+  (declare-header! name
+    split-and-trim list-of-strings? write-list-of-strings))
+
+;; emacs: (put 'declare-symbol-list-header! 'scheme-indent-function 1)
+(define (declare-symbol-list-header! name)
+  (declare-header! name
+    (lambda (str)
+      (map string->symbol (split-and-trim str)))
+    (lambda (v)
+      (list-of? v symbol?))
+    (lambda (v port)
+      (write-list v port display ", "))))
+
+;; emacs: (put 'declare-header-list-header! 'scheme-indent-function 1)
+(define (declare-header-list-header! name)
+  (declare-header! name
+    split-header-names list-of-header-names? write-header-list))
+
+;; emacs: (put 'declare-integer-header! 'scheme-indent-function 1)
+(define (declare-integer-header! name)
+  (declare-header! name
+    parse-non-negative-integer non-negative-integer? display))
+
+;; emacs: (put 'declare-uri-header! 'scheme-indent-function 1)
+(define (declare-uri-header! name)
+  (declare-header! name
+    (lambda (str) (or (string->uri str) (bad-header-component 'uri str)))
+    uri?
+    write-uri))
+
+;; emacs: (put 'declare-quality-list-header! 'scheme-indent-function 1)
+(define (declare-quality-list-header! name)
+  (declare-header! name
+    parse-quality-list validate-quality-list write-quality-list))
+
+;; emacs: (put 'declare-param-list-header! 'scheme-indent-function 1)
+(define* (declare-param-list-header! name #:optional
+                                     (val-parser default-val-parser)
+                                     (val-validator default-val-validator)
+                                     (val-writer default-val-writer))
+  (declare-header! name
+    (lambda (str) (parse-param-list str val-parser))
+    (lambda (val) (validate-param-list val val-validator))
+    (lambda (val port) (write-param-list val port val-writer))))
+
+;; emacs: (put 'declare-key-value-list-header! 'scheme-indent-function 1)
+(define* (declare-key-value-list-header! name #:optional
+                                         (val-parser default-val-parser)
+                                         (val-validator default-val-validator)
+                                         (val-writer default-val-writer))
+  (declare-header! name
+    (lambda (str) (parse-key-value-list str val-parser))
+    (lambda (val) (key-value-list? val val-validator))
+    (lambda (val port) (write-key-value-list val port val-writer))))
+
+;; emacs: (put 'declare-entity-tag-list-header! 'scheme-indent-function 1)
+(define (declare-entity-tag-list-header! name)
+  (declare-header! name
+    (lambda (str) (if (string=? str "*") '* (parse-entity-tag-list str)))
+    (lambda (val) (or (eq? val '*) (entity-tag-list? val)))
+    (lambda (val port)
+      (if (eq? val '*)
+          (display "*" port)
+          (write-entity-tag-list val port)))))
+
+;; emacs: (put 'declare-credentials-header! 'scheme-indent-function 1)
+(define (declare-credentials-header! name)
+  (declare-header! name
+    parse-credentials validate-credentials write-credentials))
+
+;; emacs: (put 'declare-challenge-list-header! 'scheme-indent-function 1)
+(define (declare-challenge-list-header! name)
+  (declare-header! name
+    parse-challenges validate-challenges write-challenges))
 
 
 \f
 ;;      | cache-extension                        ; Section 14.9.6
 ;; cache-extension = token [ "=" ( token | quoted-string ) ]
 ;;
-(declare-key-value-list-header cache-control
-  "Cache-Control"
-  (let ((known-directives (make-hash-table)))
-    (for-each (lambda (s) 
-                (hash-set! known-directives s (string->symbol s)))
-              '("no-cache" "no-store" "max-age" "max-stale" "min-fresh"
-                "no-transform" "only-if-cached" "public" "private"
-                "must-revalidate" "proxy-revalidate" "s-maxage"))
-    (lambda (k-str)
-      (hash-ref known-directives k-str k-str)))
+(declare-key-value-list-header! "Cache-Control"
   (lambda (k v-str)
     (case k
-      ((max-age max-stale min-fresh s-maxage)
-       (cons k (parse-non-negative-integer v-str)))
+      ((max-age min-fresh s-maxage)
+       (parse-non-negative-integer v-str))
+      ((max-stale)
+       (and v-str (parse-non-negative-integer v-str)))
       ((private no-cache)
-       (cons k (if v-str (split-and-trim v-str) #t)))
-      (else (if v-str (cons k v-str) k))))
-  default-kv-validator
+       (and v-str (split-header-names v-str)))
+      (else v-str)))
+  (lambda (k v)
+    (case k
+      ((max-age min-fresh s-maxage)
+       (non-negative-integer? v))
+      ((max-stale)
+       (or (not v) (non-negative-integer? v)))
+      ((private no-cache)
+       (or (not v) (list-of-header-names? v)))
+      ((no-store no-transform only-if-cache must-revalidate proxy-revalidate)
+       (not v))
+      (else
+       (or (not v) (string? v)))))
   (lambda (k v port)
     (cond
-     ((string? v) (display v port))
+     ((string? v) (default-val-writer k v port))
      ((pair? v)
-      (write-qstring (string-join v ", ") port))
+      (display #\" port)
+      (write-header-list v port)
+      (display #\" port))
      ((integer? v)
       (display v port))
      (else
 ;; e.g.
 ;;     Connection: close, foo-header
 ;; 
-(declare-string-list-header connection
-  "Connection")
+(declare-header-list-header! "Connection")
 
 ;; Date  = "Date" ":" HTTP-date
 ;; e.g.
 ;;     Date: Tue, 15 Nov 1994 08:12:31 GMT
 ;;
-(declare-date-header date
-  "Date")
+(declare-date-header! "Date")
 
 ;; Pragma            = "Pragma" ":" 1#pragma-directive
 ;; pragma-directive  = "no-cache" | extension-pragma
 ;; extension-pragma  = token [ "=" ( token | quoted-string ) ]
 ;;
-(declare-key-value-list-header pragma
-  "Pragma"
-  (lambda (k) (if (equal? k "no-cache") 'no-cache k)))
+(declare-key-value-list-header! "Pragma")
 
 ;; Trailer  = "Trailer" ":" 1#field-name
 ;;
-(declare-string-list-header trailer
-  "Trailer")
+(declare-header-list-header! "Trailer")
 
 ;; Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding
 ;;
-(declare-param-list-header transfer-encoding
-  "Transfer-Encoding"
-  (lambda (k)
-    (if (equal? k "chunked") 'chunked k)))
+(declare-param-list-header! "Transfer-Encoding")
 
 ;; Upgrade = "Upgrade" ":" 1#product
 ;;
-(declare-string-list-header upgrade
-  "Upgrade")
+(declare-string-list-header! "Upgrade")
 
 ;; Via =  "Via" ":" 1#( received-protocol received-by [ comment ] )
 ;; received-protocol = [ protocol-name "/" ] protocol-version
 ;; received-by       = ( host [ ":" port ] ) | pseudonym
 ;; pseudonym         = token
 ;;
-(declare-header via
-  "Via"
+(declare-header! "Via"
   split-and-trim
   list-of-strings?
   write-list-of-strings
 ;;                 ; the Warning header, for use in debugging
 ;; warn-text  = quoted-string
 ;; warn-date  = <"> HTTP-date <">
-(declare-header warning
-  "Warning"
+(declare-header! "Warning"
   (lambda (str)
     (let ((len (string-length str)))
       (let lp ((i (skip-whitespace str 0)))
 
 ;; Allow = #Method
 ;;
-(declare-string-list-header allow
-  "Allow")
+(declare-symbol-list-header! "Allow")
 
 ;; Content-Encoding = 1#content-coding
 ;;
-(declare-string-list-header content-encoding
-  "Content-Encoding")
+(declare-symbol-list-header! "Content-Encoding")
 
 ;; Content-Language = 1#language-tag
 ;;
-(declare-string-list-header content-language
-  "Content-Language")
+(declare-string-list-header! "Content-Language")
 
 ;; Content-Length = 1*DIGIT
 ;;
-(declare-integer-header content-length
-  "Content-Length")
+(declare-integer-header! "Content-Length")
 
 ;; Content-Location = ( absoluteURI | relativeURI )
 ;;
-(declare-uri-header content-location
-  "Content-Location")
+(declare-uri-header! "Content-Location")
 
 ;; Content-MD5 = <base64 of 128 bit MD5 digest as per RFC 1864>
 ;;
-(declare-opaque-header content-md5
-  "Content-MD5")
+(declare-opaque-header! "Content-MD5")
 
 ;; Content-Range = content-range-spec
 ;; content-range-spec      = byte-content-range-spec
 ;;                                | "*"
 ;; instance-length           = 1*DIGIT
 ;;
-(declare-header content-range
-  "Content-Range"
+(declare-header! "Content-Range"
   (lambda (str)
     (let ((dash (string-index str #\-))
           (slash (string-index str #\/)))
 
 ;; Content-Type = media-type
 ;;
-(declare-header content-type
-  "Content-Type"
+(declare-header! "Content-Type"
   (lambda (str)
     (let ((parts (string-split str #\;)))
-      (call-with-values (lambda () (parse-media-type (car parts)))
-        (lambda (type subtype)
-          (cons* type subtype
-                 (map (lambda (x)
-                        (let ((eq (string-index x #\=)))
-                          (if (and eq (= eq (string-rindex x #\=)))
-                              (cons (string-trim x 0 eq)
-                                    (string-trim-right x (1+ eq)))
-                              (bad-header 'content-type str))))
-                      (cdr parts)))))))
+      (cons (parse-media-type (car parts))
+            (map (lambda (x)
+                   (let ((eq (string-index x #\=)))
+                     (if (and eq (= eq (string-rindex x #\=)))
+                         (cons (string->symbol
+                                (string-trim x char-whitespace? 0 eq))
+                               (string-trim-right x char-whitespace? (1+ eq)))
+                         (bad-header 'content-type str))))
+                 (cdr parts)))))
   (lambda (val)
-    (and (list-of? val string?)
-         (let ((len (length val)))
-           (and (>= len 2)
-                (even? len)))))
+    (and (pair? val)
+         (symbol? (car val))
+         (list-of? (cdr val)
+                   (lambda (x)
+                     (and (pair? x) (symbol? (car x)) (string? (cdr x)))))))
   (lambda (val port)
     (display (car val) port)
-    (display #\/ port)
-    (display (cadr val) port)
-    (write-list
-     (cddr val) port
-     (lambda (pair port)
-       (display (car pair) port)
-       (display #\= port)
-       (display (cdr pair) port))
-     ";")))
+    (if (pair? (cdr val)) 
+       (begin
+          (display ";" port)
+          (write-list
+           (cdr val) port
+           (lambda (pair port)
+             (display (car pair) port)
+             (display #\= port)
+             (display (cdr pair) port))
+           ";")))))
 
 ;; Expires = HTTP-date
 ;;
-(declare-date-header expires
-  "Expires")
+(define *date-in-the-past* (parse-date "Thu, 01 Jan 1970 00:00:00 GMT"))
+
+(declare-header! "Expires"
+  (lambda (str)
+    (if (member str '("0" "-1"))
+        *date-in-the-past*
+        (parse-date str)))
+  date?
+  write-date)
 
 ;; Last-Modified = HTTP-date
 ;;
-(declare-date-header last-modified
-  "Last-Modified")
+(declare-date-header! "Last-Modified")
 
 
 \f
 ;; accept-params = ";" "q" "=" qvalue *( accept-extension )
 ;; accept-extension = ";" token [ "=" ( token | quoted-string ) ]
 ;;
-(declare-param-list-header accept
-  "Accept"
-  ;; -> ("type/subtype" (str-prop . str-val) ...) ...)
+(declare-param-list-header! "Accept"
+  ;; -> (type/subtype (sym-prop . str-val) ...) ...)
   ;;
-  ;; with the exception of prop = "q", in which case the prop will be
-  ;; the symbol 'q, and the val will be a valid quality value
+  ;; with the exception of prop `q', in which case the val will be a
+  ;; valid quality value
   ;;
-  (lambda (k) (if (string=? k "q") 'q k))
   (lambda (k v)
-    (if (eq? k 'q)
-        (cons k (parse-quality v))
-        (default-kons k v)))
+    (if (eq? k 'q) 
+        (parse-quality v)
+        v))
   (lambda (k v)
     (if (eq? k 'q)
         (valid-quality? v)
-        (default-kv-validator k v)))
+        (or (not v) (string? v))))
   (lambda (k v port)
     (if (eq? k 'q)
         (write-quality v port)
 
 ;; Accept-Charset = 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] )
 ;;
-(declare-quality-list-header accept-charset
-  "Accept-Charset")
+(declare-quality-list-header! "Accept-Charset")
 
 ;; Accept-Encoding = 1#( codings [ ";" "q" "=" qvalue ] )
 ;; codings = ( content-coding | "*" )
 ;;
-(declare-quality-list-header accept-encoding
-  "Accept-Encoding")
+(declare-quality-list-header! "Accept-Encoding")
 
 ;; Accept-Language = 1#( language-range [ ";" "q" "=" qvalue ] )
 ;; language-range  = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
 ;;
-(declare-quality-list-header accept-language
-  "Accept-Language")
+(declare-quality-list-header! "Accept-Language")
 
 ;; Authorization = credentials
+;; credentials = auth-scheme #auth-param
+;; auth-scheme = token
+;; auth-param = token "=" ( token | quoted-string )
 ;;
-;; Authorization is basically opaque to this HTTP stack, we just pass
-;; the string value through.
-;; 
-(declare-opaque-header authorization
-  "Authorization")
+(declare-credentials-header! "Authorization")
 
 ;; Expect = 1#expectation
 ;; expectation = "100-continue" | expectation-extension
 ;;                         *expect-params ]
 ;; expect-params = ";" token [ "=" ( token | quoted-string ) ]
 ;;
-(declare-param-list-header expect
-  "Expect"
-  (lambda (k)
-    (if (equal? k "100-continue")
-        '100-continue
-        k)))
+(declare-param-list-header! "Expect")
 
 ;; From = mailbox
 ;;
 ;; Should be an email address; we just pass on the string as-is.
 ;;
-(declare-opaque-header from
-  "From")
+(declare-opaque-header! "From")
 
 ;; Host = host [ ":" port ]
 ;; 
-(declare-header host
-  "Host"
+(declare-header! "Host"
   (lambda (str)
     (let ((colon (string-index str #\:)))
       (if colon
 
 ;; If-Match = ( "*" | 1#entity-tag )
 ;;
-(declare-entity-tag-list-header if-match
-  "If-Match")
+(declare-entity-tag-list-header! "If-Match")
 
 ;; If-Modified-Since = HTTP-date
 ;;
-(declare-date-header if-modified-since
-  "If-Modified-Since")
+(declare-date-header! "If-Modified-Since")
 
 ;; If-None-Match = ( "*" | 1#entity-tag )
 ;;
-(declare-entity-tag-list-header if-none-match
-  "If-None-Match")
+(declare-entity-tag-list-header! "If-None-Match")
 
 ;; If-Range = ( entity-tag | HTTP-date )
 ;;
-(declare-header if-range
-  "If-Range"
+(declare-header! "If-Range"
   (lambda (str)
     (if (or (string-prefix? "\"" str)
             (string-prefix? "W/" str))
 
 ;; If-Unmodified-Since = HTTP-date
 ;;
-(declare-date-header if-unmodified-since
-  "If-Unmodified-Since")
+(declare-date-header! "If-Unmodified-Since")
 
 ;; Max-Forwards = 1*DIGIT
 ;;
-(declare-integer-header max-forwards
-  "Max-Forwards")
+(declare-integer-header! "Max-Forwards")
 
 ;; Proxy-Authorization = credentials
 ;;
-(declare-opaque-header proxy-authorization
-  "Proxy-Authorization")
+(declare-credentials-header! "Proxy-Authorization")
 
 ;; Range = "Range" ":" ranges-specifier
 ;; ranges-specifier = byte-ranges-specifier
 ;; suffix-byte-range-spec = "-" suffix-length
 ;; suffix-length = 1*DIGIT
 ;;
-(declare-header range
-  "Range"
+(declare-header! "Range"
   (lambda (str)
     (if (string-prefix? "bytes=" str)
         (cons
 
 ;; Referer = ( absoluteURI | relativeURI )
 ;;
-(declare-uri-header referer
-  "Referer")
+(declare-uri-header! "Referer")
 
 ;; TE = #( t-codings )
 ;; t-codings = "trailers" | ( transfer-extension [ accept-params ] )
 ;;
-(declare-param-list-header te
-  "TE"
-  (lambda (k) (if (equal? k "trailers") 'trailers k)))
+(declare-param-list-header! "TE")
 
 ;; User-Agent = 1*( product | comment )
 ;;
-(declare-opaque-header user-agent
-  "User-Agent")
+(declare-opaque-header! "User-Agent")
 
 
 \f
 ;; Accept-Ranges = acceptable-ranges
 ;; acceptable-ranges = 1#range-unit | "none"
 ;;
-(declare-string-list-header accept-ranges
-  "Accept-Ranges")
+(declare-symbol-list-header! "Accept-Ranges")
 
 ;; Age = age-value
 ;; age-value = delta-seconds
 ;;
-(declare-integer-header age
-  "Age")
+(declare-integer-header! "Age")
 
 ;; ETag = entity-tag
 ;;
-(declare-header etag
-  "ETag"
+(declare-header! "ETag"
   parse-entity-tag
   entity-tag?
   write-entity-tag)
 
 ;; Location = absoluteURI
 ;; 
-(declare-uri-header location
-  "Location")
+(declare-uri-header! "Location")
 
 ;; Proxy-Authenticate = 1#challenge
 ;;
-;; FIXME: split challenges ?
-(declare-opaque-header proxy-authenticate
-  "Proxy-Authenticate")
+(declare-challenge-list-header! "Proxy-Authenticate")
 
 ;; Retry-After  = ( HTTP-date | delta-seconds )
 ;;
-(declare-header retry-after
-  "Retry-After"
+(declare-header! "Retry-After"
   (lambda (str)
     (if (and (not (string-null? str))
              (char-numeric? (string-ref str 0)))
 
 ;; Server = 1*( product | comment )
 ;;
-(declare-opaque-header server
-  "Server")
+(declare-opaque-header! "Server")
 
 ;; Vary = ( "*" | 1#field-name )
 ;;
-(declare-header vary
-  "Vary"
+(declare-header! "Vary"
   (lambda (str)
     (if (equal? str "*")
         '*
-        (split-and-trim str)))
+        (split-header-names str)))
   (lambda (val)
-    (or (eq? val '*) (list-of-strings? val)))
+    (or (eq? val '*) (list-of-header-names? val)))
   (lambda (val port)
     (if (eq? val '*)
         (display "*" port)
-        (write-list-of-strings val port))))
+        (write-header-list val port))))
 
 ;; WWW-Authenticate = 1#challenge
 ;;
-;; Hum.
-(declare-opaque-header www-authenticate
-  "WWW-Authenticate")
+(declare-challenge-list-header! "WWW-Authenticate")