(web uri) can uri-decode non-utf-8 payloads
[bpt/guile.git] / module / web / http.scm
CommitLineData
440840c1
AW
1;;; HTTP messages
2
3;; Copyright (C) 2010 Free Software Foundation, Inc.
4
5;; This library is free software; you can redistribute it and/or
6;; modify it under the terms of the GNU Lesser General Public
7;; License as published by the Free Software Foundation; either
8;; version 3 of the License, or (at your option) any later version.
9;;
10;; This library is distributed in the hope that it will be useful,
11;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13;; Lesser General Public License for more details.
14;;
15;; You should have received a copy of the GNU Lesser General Public
16;; License along with this library; if not, write to the Free Software
17;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18;; 02110-1301 USA
19
9eed1010
AW
20;;; Commentary:
21;;;
22;;; This module has a number of routines to parse textual
23;;; representations of HTTP data into native Scheme data structures.
24;;;
25;;; It tries to follow RFCs fairly strictly---the road to perdition
26;;; being paved with compatibility hacks---though some allowances are
27;;; made for not-too-divergent texts (like a quality of .2 which should
28;;; be 0.2, etc).
29;;;
440840c1
AW
30;;; Code:
31
32(define-module (web http)
33 #:use-module ((srfi srfi-1) #:select (append-map! map!))
34 #:use-module (srfi srfi-9)
35 #:use-module (srfi srfi-19)
36 #:use-module (ice-9 regex)
37 #:use-module (ice-9 rdelim)
38 #:use-module (web uri)
39 #:export (header-decl?
40 make-header-decl
41 header-decl-sym
42 header-decl-name
43 header-decl-multiple?
44 header-decl-parser
45 header-decl-validator
46 header-decl-writer
13b7e2a6 47 lookup-header-decl
440840c1
AW
48 declare-header!
49
50 read-header
51 parse-header
52 valid-header?
53 write-header
54
55 read-headers
56 write-headers
57
13b7e2a6
AW
58 parse-http-method
59 parse-http-version
60 parse-request-uri
61
440840c1
AW
62 read-request-line
63 write-request-line
64 read-response-line
65 write-response-line))
66
67
68;;; TODO
69;;;
70;;; Look at quality lists with more insight.
71;;; Think about `accept' a bit more.
72;;;
73
74
75(define-record-type <header-decl>
76 (make-header-decl sym name multiple? parser validator writer)
77 header-decl?
78 (sym header-decl-sym)
79 (name header-decl-name)
80 (multiple? header-decl-multiple?)
81 (parser header-decl-parser)
82 (validator header-decl-validator)
83 (writer header-decl-writer))
84
85;; sym -> header
86(define *declared-headers* (make-hash-table))
87;; downcased name -> header
88(define *declared-headers-by-name* (make-hash-table))
89
90(define* (declare-header! sym name #:key
91 multiple?
92 parser
93 validator
94 writer)
95 (if (and (symbol? sym) (string? name) parser validator writer)
96 (let ((decl (make-header-decl sym name
97 multiple? parser validator writer)))
98 (hashq-set! *declared-headers* sym decl)
99 (hash-set! *declared-headers-by-name* (string-downcase name) decl)
100 decl)
101 (error "bad header decl" sym name multiple? parser validator writer)))
102
103(define (read-line* port)
104 (let* ((pair (%read-line port))
105 (line (car pair))
106 (delim (cdr pair)))
107 (if (and (string? line) (char? delim))
108 (let ((orig-len (string-length line)))
109 (let lp ((len orig-len))
110 (if (and (> len 0)
111 (char-whitespace? (string-ref line (1- len))))
112 (lp (1- len))
113 (if (= len orig-len)
114 line
115 (substring line 0 len)))))
116 (bad-header '%read line))))
117
118(define (read-continuation-line port val)
119 (if (or (eqv? (peek-char port) #\space)
120 (eqv? (peek-char port) #\tab))
121 (read-continuation-line port
122 (string-append val
123 (begin
124 (read-line* port))))
125 val))
126
127(define (read-header port)
128 (let ((line (read-line* port)))
129 (if (or (string-null? line)
130 (string=? line "\r"))
131 (values #f #f)
132 (let ((delim (or (string-index line #\:)
133 (bad-header '%read line))))
134 (parse-header
135 (substring line 0 delim)
136 (read-continuation-line
137 port
138 (string-trim-both line char-whitespace? (1+ delim))))))))
139
13b7e2a6
AW
140(define (lookup-header-decl name)
141 (if (string? name)
142 (hash-ref *declared-headers-by-name* (string-downcase name))
143 (hashq-ref *declared-headers* name)))
144
440840c1
AW
145(define (parse-header name val)
146 (let* ((down (string-downcase name))
147 (decl (hash-ref *declared-headers-by-name* down)))
148 (if decl
149 (values (header-decl-sym decl)
150 ((header-decl-parser decl) val))
151 (values down val))))
152
153(define (valid-header? sym val)
154 (let ((decl (hashq-ref *declared-headers* sym)))
155 (if (not decl)
156 (error "Unknown header" sym)
157 ((header-decl-validator decl) val))))
158
159(define (write-header name val port)
160 (if (string? name)
161 ;; assume that it's a header we don't know about...
162 (begin
163 (display name port)
164 (display ": " port)
165 (display val port)
166 (display "\r\n" port))
167 (let ((decl (hashq-ref *declared-headers* name)))
168 (if (not decl)
169 (error "Unknown header" name)
170 (begin
171 (display (header-decl-name decl) port)
172 (display ": " port)
173 ((header-decl-writer decl) val port)
174 (display "\r\n" port))))))
175
176(define (read-headers port)
177 (let lp ((headers '()))
178 (call-with-values (lambda () (read-header port))
179 (lambda (k v)
180 (if k
181 (lp (acons k v headers))
182 (reverse! headers))))))
183
184;; Doesn't write the final \r\n, as the user might want to add another
185;; header.
186(define (write-headers headers port)
187 (let lp ((headers headers))
188 (if (pair? headers)
189 (begin
190 (write-header (caar headers) (cdar headers) port)
191 (lp (cdr headers))))))
192
193
194\f
195
196;;;
197;;; Utilities
198;;;
199
200(define (bad-header sym val)
201 (throw 'bad-header sym val))
202(define (bad-header-component sym val)
203 (throw 'bad-header sym val))
204
205(define (parse-opaque-string str)
206 str)
207(define (validate-opaque-string val)
208 (string? val))
209(define (write-opaque-string val port)
210 (display val port))
211
7aa54882
AW
212(define separators-without-slash
213 (string->char-set "[^][()<>@,;:\\\"?= \t]"))
214(define (validate-media-type str)
215 (let ((idx (string-index str #\/)))
216 (and idx (= idx (string-rindex str #\/))
217 (not (string-index str separators-without-slash)))))
440840c1 218(define (parse-media-type str)
7aa54882
AW
219 (if (validate-media-type str)
220 str
221 (bad-header-component 'media-type str)))
440840c1
AW
222
223(define* (skip-whitespace str #:optional (start 0) (end (string-length str)))
224 (let lp ((i start))
225 (if (and (< i end) (char-whitespace? (string-ref str i)))
226 (lp (1+ i))
227 i)))
228
229(define* (trim-whitespace str #:optional (start 0) (end (string-length str)))
230 (let lp ((i end))
231 (if (and (< start i) (char-whitespace? (string-ref str (1- i))))
232 (lp (1- i))
233 i)))
234
235(define* (split-and-trim str #:optional (delim #\,)
236 (start 0) (end (string-length str)))
237 (let lp ((i start))
238 (if (< i end)
239 (let* ((idx (string-index str delim i end))
240 (tok (string-trim-both str char-whitespace? i (or idx end))))
241 (cons tok (split-and-trim str delim (if idx (1+ idx) end) end)))
242 '())))
243
244(define (collect-escaped-string from start len escapes)
245 (let ((to (make-string len)))
246 (let lp ((start start) (i 0) (escapes escapes))
247 (if (null? escapes)
248 (begin
249 (substring-move! from start (+ start (- len i)) to i)
250 to)
251 (let* ((e (car escapes))
252 (next-start (+ start (- e i) 2)))
253 (substring-move! from start (- next-start 2) to i)
254 (string-set! to e (string-ref from (- next-start 1)))
255 (lp next-start (1+ e) (cdr escapes)))))))
256
257;; in incremental mode, returns two values: the string, and the index at
258;; which the string ended
259(define* (parse-qstring str #:optional
260 (start 0) (end (trim-whitespace str start))
261 #:key incremental?)
262 (if (and (< start end) (eqv? (string-ref str start) #\"))
263 (let lp ((i (1+ start)) (qi 0) (escapes '()))
264 (if (< i end)
265 (case (string-ref str i)
266 ((#\\)
267 (lp (+ i 2) (1+ qi) (cons qi escapes)))
268 ((#\")
269 (let ((out (collect-escaped-string str (1+ start) qi escapes)))
270 (if incremental?
271 (values out (1+ i))
272 (if (= (1+ i) end)
273 out
274 (bad-header-component 'qstring str)))))
275 (else
276 (lp (1+ i) (1+ qi) escapes)))
277 (bad-header-component 'qstring str)))
278 (bad-header-component 'qstring str)))
279
280(define (write-list l port write-item delim)
281 (if (pair? l)
282 (let lp ((l l))
283 (write-item (car l) port)
284 (if (pair? (cdr l))
285 (begin
286 (display delim port)
287 (lp (cdr l)))))))
288
289(define (write-qstring str port)
290 (display #\" port)
291 (if (string-index str #\")
292 ;; optimize me
293 (write-list (string-split str #\") port display "\\\"")
294 (display str port))
295 (display #\" port))
296
297(define* (parse-quality str #:optional (start 0) (end (string-length str)))
298 (define (char->decimal c)
299 (let ((i (- (char->integer c) (char->integer #\0))))
300 (if (and (<= 0 i) (< i 10))
301 i
302 (bad-header-component 'quality str))))
303 (cond
304 ((not (< start end))
305 (bad-header-component 'quality str))
306 ((eqv? (string-ref str start) #\1)
307 (if (or (string= str "1" start end)
308 (string= str "1." start end)
309 (string= str "1.0" start end)
310 (string= str "1.00" start end)
311 (string= str "1.000" start end))
312 1000
313 (bad-header-component 'quality str)))
314 ((eqv? (string-ref str start) #\0)
315 (if (or (string= str "0" start end)
316 (string= str "0." start end))
317 0
318 (if (< 2 (- end start) 6)
319 (let lp ((place 1) (i (+ start 4)) (q 0))
320 (if (= i (1+ start))
321 (if (eqv? (string-ref str (1+ start)) #\.)
322 q
323 (bad-header-component 'quality str))
324 (lp (* 10 place) (1- i)
325 (if (< i end)
326 (+ q (* place (char->decimal (string-ref str i))))
327 q))))
328 (bad-header-component 'quality str))))
9eed1010
AW
329 ;; Allow the nonstandard .2 instead of 0.2.
330 ((and (eqv? (string-ref str start) #\.)
331 (< 1 (- end start) 5))
332 (let lp ((place 1) (i (+ start 3)) (q 0))
333 (if (= i start)
334 q
335 (lp (* 10 place) (1- i)
336 (if (< i end)
337 (+ q (* place (char->decimal (string-ref str i))))
338 q)))))
440840c1
AW
339 (else
340 (bad-header-component 'quality str))))
341
342(define (valid-quality? q)
612aa5be 343 (and (non-negative-integer? q) (<= q 1000)))
440840c1
AW
344
345(define (write-quality q port)
346 (define (digit->char d)
347 (integer->char (+ (char->integer #\0) d)))
348 (display (digit->char (modulo (quotient q 1000) 10)) port)
349 (display #\. port)
350 (display (digit->char (modulo (quotient q 100) 10)) port)
351 (display (digit->char (modulo (quotient q 10) 10)) port)
352 (display (digit->char (modulo q 10)) port))
353
354(define (list-of? val pred)
355 (or (null? val)
356 (and (pair? val)
357 (pred (car val))
358 (list-of? (cdr val) pred))))
359
360(define* (parse-quality-list str)
361 (map (lambda (part)
362 (cond
363 ((string-rindex part #\;)
364 => (lambda (idx)
365 (let ((qpart (string-trim-both part char-whitespace? (1+ idx))))
366 (if (string-prefix? "q=" qpart)
367 (cons (parse-quality qpart 2)
368 (string-trim-both part char-whitespace? 0 idx))
369 (bad-header-component 'quality qpart)))))
370 (else
371 (cons 1000 (string-trim-both part char-whitespace?)))))
372 (string-split str #\,)))
373
374(define (validate-quality-list l)
375 (list-of? l
376 (lambda (elt)
377 (and (pair? elt)
378 (valid-quality? (car elt))
379 (string? (cdr elt))))))
380
381(define (write-quality-list l port)
382 (write-list l port
383 (lambda (x port)
384 (let ((q (car x))
385 (str (cdr x)))
386 (display str port)
387 (if (< q 1000)
388 (begin
389 (display ";q=" port)
390 (write-quality q port)))))
391 ","))
392
393(define* (parse-non-negative-integer val #:optional (start 0)
394 (end (string-length val)))
395 (define (char->decimal c)
396 (let ((i (- (char->integer c) (char->integer #\0))))
397 (if (and (<= 0 i) (< i 10))
398 i
399 (bad-header-component 'non-negative-integer val))))
400 (if (not (< start end))
401 (bad-header-component 'non-negative-integer val)
402 (let lp ((i start) (out 0))
403 (if (< i end)
404 (lp (1+ i)
405 (+ (* out 10) (char->decimal (string-ref val i))))
406 out))))
407
408(define (non-negative-integer? code)
409 (and (number? code) (>= code 0) (exact? code) (integer? code)))
410
411(define (default-kons k val)
412 (if val
413 (cons k val)
414 k))
415
416(define (default-kv-validator k val)
417 #t)
418
419(define (default-val-writer k val port)
420 (if (or (string-index val #\;)
421 (string-index val #\,)
422 (string-index val #\"))
423 (write-qstring val port)
424 (display val port)))
425
426(define* (parse-key-value-list str #:optional (kproc identity)
427 (kons default-kons)
428 (start 0) (end (string-length str)))
429 (let lp ((i start) (out '()))
430 (if (not (< i end))
431 (reverse! out)
432 (let* ((i (skip-whitespace str i end))
433 (eq (string-index str #\= i end))
434 (comma (string-index str #\, i end))
435 (delim (min (or eq end) (or comma end)))
436 (k (kproc (substring str i (trim-whitespace str i delim)))))
437 (call-with-values
438 (lambda ()
439 (if (and eq (or (not comma) (< eq comma)))
440 (let ((i (skip-whitespace str (1+ eq) end)))
441 (if (and (< i end) (eqv? (string-ref str i) #\"))
442 (parse-qstring str i end #:incremental? #t)
443 (values (substring str i
444 (trim-whitespace str i
445 (or comma end)))
446 (or comma end))))
447 (values #f delim)))
448 (lambda (v-str next-i)
449 (let ((i (skip-whitespace str next-i end)))
450 (if (or (= i end) (eqv? (string-ref str i) #\,))
451 (lp (1+ i) (cons (kons k v-str) out))
452 (bad-header-component 'key-value-list
453 (substring str start end))))))))))
454
455(define* (key-value-list? list #:optional
456 (valid? default-kv-validator))
457 (list-of? list
458 (lambda (elt)
459 (cond
460 ((pair? elt)
461 (let ((k (car elt))
462 (v (cdr elt)))
463 (and (or (string? k) (symbol? k))
464 (valid? k v))))
465 ((or (string? elt) (symbol? elt))
466 (valid? elt #f))
467 (else #f)))))
468
469(define* (write-key-value-list list port #:optional
470 (val-writer default-val-writer) (delim ", "))
471 (write-list
472 list port
473 (lambda (x port)
474 (let ((k (if (pair? x) (car x) x))
475 (v (if (pair? x) (cdr x) #f)))
476 (display k port)
477 (if v
478 (begin
479 (display #\= port)
480 (val-writer k v port)))))
481 delim))
482
483;; param-component = token [ "=" (token | quoted-string) ] \
484;; *(";" token [ "=" (token | quoted-string) ])
485;;
486(define* (parse-param-component str #:optional (kproc identity)
487 (kons default-kons)
488 (start 0) (end (string-length str)))
489 (let lp ((i start) (out '()))
490 (if (not (< i end))
491 (values (reverse! out) end)
492 (let ((delim (string-index str
493 (lambda (c) (memq c '(#\, #\; #\=)))
494 i)))
495 (let ((k (kproc
496 (substring str i (trim-whitespace str i (or delim end)))))
497 (delimc (and delim (string-ref str delim))))
498 (case delimc
499 ((#\=)
500 (call-with-values
501 (lambda ()
502 (let ((i (skip-whitespace str (1+ delim) end)))
503 (if (and (< i end) (eqv? (string-ref str i) #\"))
504 (parse-qstring str i end #:incremental? #t)
505 (let ((delim
506 (or (string-index
507 str
508 (lambda (c)
509 (or (eqv? c #\;)
510 (eqv? c #\,)
511 (char-whitespace? c)))
512 i end)
513 end)))
514 (values (substring str i delim)
515 delim)))))
516 (lambda (v-str next-i)
517 (let ((x (kons k v-str))
518 (i (skip-whitespace str next-i end)))
519 (case (and (< i end) (string-ref str i))
520 ((#f)
521 (values (reverse! (cons x out)) end))
522 ((#\;)
523 (lp (skip-whitespace str (1+ i) end)
524 (cons x out)))
525 (else ; including #\,
526 (values (reverse! (cons x out)) i)))))))
527 ((#\;)
528 (lp (skip-whitespace str (1+ delim) end)
529 (cons (kons k #f) out)))
530
531 (else ;; either the end of the string or a #\,
532 (values (reverse! (cons (kons k #f) out))
533 (or delim end)))))))))
534
535(define* (parse-param-list str #:optional
536 (kproc identity) (kons default-kons)
537 (start 0) (end (string-length str)))
538 (let lp ((i start) (out '()))
539 (call-with-values
540 (lambda () (parse-param-component str kproc kons i end))
541 (lambda (item i)
542 (if (< i end)
543 (if (eqv? (string-ref str i) #\,)
544 (lp (skip-whitespace str (1+ i) end)
545 (cons item out))
546 (bad-header-component 'param-list str))
547 (reverse! (cons item out)))))))
548
549(define* (validate-param-list list #:optional
550 (valid? default-kv-validator))
551 (list-of? list
552 (lambda (elt)
553 (key-value-list? list valid?))))
554
555(define* (write-param-list list port #:optional
556 (val-writer default-val-writer))
557 (write-list
558 list port
559 (lambda (item port)
560 (write-key-value-list item port val-writer ";"))
561 ","))
562
563(define (list-of-strings? val)
564 (list-of? val string?))
565
566(define (write-list-of-strings val port)
567 (write-list val port display ", "))
568
569(define (parse-date str)
570 ;; Unfortunately, there is no way to make string->date parse out the
571 ;; "GMT" bit, so we play string games to append a format it will
572 ;; understand (the +0000 bit).
573 (string->date
574 (if (string-suffix? " GMT" str)
575 (string-append (substring str 0 (- (string-length str) 4))
576 " +0000")
577 (bad-header-component 'date str))
578 "~a, ~d ~b ~Y ~H:~M:~S ~z"))
579
580(define (write-date date port)
581 (display (date->string date "~a, ~d ~b ~Y ~H:~M:~S GMT") port))
582
583(define (write-uri uri port)
584 (display (unparse-uri uri) port))
585
586(define (parse-entity-tag val)
587 (if (string-prefix? "W/" val)
588 (cons (parse-qstring val 2) #f)
589 (cons (parse-qstring val) #t)))
590
591(define (entity-tag? val)
592 (and (pair? val)
593 (string? (car val))))
594
595(define (write-entity-tag val port)
adcd5854 596 (if (not (cdr val))
440840c1
AW
597 (display "W/" port))
598 (write-qstring (car val) port))
599
600(define* (parse-entity-tag-list val #:optional
601 (start 0) (end (string-length val)))
602 (let ((strong? (not (string-prefix? "W/" val 0 2 start end))))
603 (call-with-values (lambda ()
604 (parse-qstring val (if strong? start (+ start 2))
605 end #:incremental? #t))
606 (lambda (tag next)
607 (acons tag strong?
608 (let ((next (skip-whitespace val next end)))
609 (if (< next end)
610 (if (eqv? (string-ref val next) #\,)
611 (parse-entity-tag-list
612 val
613 (skip-whitespace val (1+ next) end)
614 end)
615 (bad-header-component 'entity-tag-list val))
616 '())))))))
617
618(define (entity-tag-list? val)
619 (list-of? val entity-tag?))
620
621(define (write-entity-tag-list val port)
622 (write-list val port write-entity-tag ", "))
623
624
625\f
626
627;;;
628;;; Request-Line and Response-Line
629;;;
630
631;; Hmm.
632(define (bad-request message . args)
633 (throw 'bad-request message args))
634(define (bad-response message . args)
635 (throw 'bad-response message args))
636
637(define *known-versions* '())
638
13b7e2a6 639(define* (parse-http-version str #:optional (start 0) (end (string-length str)))
440840c1
AW
640 (or (let lp ((known *known-versions*))
641 (and (pair? known)
642 (if (string= str (caar known) start end)
643 (cdar known)
644 (lp (cdr known)))))
645 (let ((dot-idx (string-index str #\. start end)))
646 (if (and (string-prefix? "HTTP/" str 0 5 start end)
647 dot-idx
648 (= dot-idx (string-rindex str #\. start end)))
649 (cons (parse-non-negative-integer str (+ start 5) dot-idx)
650 (parse-non-negative-integer str (1+ dot-idx) end))
651 (bad-header-component 'http-version (substring str start end))))))
652
653(define (write-http-version val port)
654 (display "HTTP/" port)
655 (display (car val) port)
656 (display #\. port)
657 (display (cdr val) port))
658
659(for-each
660 (lambda (v)
661 (set! *known-versions*
662 (acons v (parse-http-version v 0 (string-length v))
663 *known-versions*)))
664 '("HTTP/1.0" "HTTP/1.1"))
665
666
667;; Request-URI = "*" | absoluteURI | abs_path | authority
668;;
669;; The `authority' form is only permissible for the CONNECT method, so
670;; because we don't expect people to implement CONNECT, we save
671;; ourselves the trouble of that case, and disallow the CONNECT method.
672;;
13b7e2a6 673(define* (parse-http-method str #:optional (start 0) (end (string-length str)))
440840c1
AW
674 (cond
675 ((string= str "GET" start end) 'GET)
676 ((string= str "HEAD" start end) 'HEAD)
677 ((string= str "POST" start end) 'POST)
678 ((string= str "PUT" start end) 'PUT)
679 ((string= str "DELETE" start end) 'DELETE)
680 ((string= str "OPTIONS" start end) 'OPTIONS)
681 ((string= str "TRACE" start end) 'TRACE)
682 (else (bad-request "Invalid method: ~a" (substring str start end)))))
683
13b7e2a6 684(define* (parse-request-uri str #:optional (start 0) (end (string-length str)))
440840c1
AW
685 (cond
686 ((= start end)
687 (bad-request "Missing Request-URI"))
688 ((string= str "*" start end)
689 #f)
690 ((eq? (string-ref str start) #\/)
691 (let* ((q (string-index str #\? start end))
692 (f (string-index str #\# start end))
693 (q (and q (or (not f) (< q f)) q)))
694 (build-uri 'http
695 #:path (substring str start (or q f end))
696 #:query (and q (substring str (1+ q) (or f end)))
697 #:fragment (and f (substring str (1+ f) end)))))
698 (else
699 (or (parse-uri (substring str start end))
700 (bad-request "Invalid URI: ~a" (substring str start end))))))
701
702(define (read-request-line port)
703 (let* ((line (read-line* port))
704 (d0 (string-index line char-whitespace?)) ; "delimiter zero"
705 (d1 (string-rindex line char-whitespace?)))
706 (if (and d0 d1 (< d0 d1))
13b7e2a6
AW
707 (values (parse-http-method line 0 d0)
708 (parse-request-uri line (skip-whitespace line (1+ d0) d1) d1)
440840c1
AW
709 (parse-http-version line (1+ d1) (string-length line)))
710 (bad-request "Bad Request-Line: ~s" line))))
711
712(define (write-uri uri port)
713 (if (uri-host uri)
714 (begin
715 (display (uri-scheme uri) port)
716 (display "://" port)
717 (if (uri-userinfo uri)
718 (begin
719 (display (uri-userinfo uri) port)
720 (display #\@ port)))
721 (display (uri-host uri) port)
722 (let ((p (uri-port uri)))
723 (if (and p (not (eqv? p 80)))
724 (begin
725 (display #\: port)
726 (display p port))))))
727 (let* ((path (uri-path uri))
728 (len (string-length path)))
729 (cond
730 ((and (> len 0) (not (eqv? (string-ref path 0) #\/)))
731 (bad-request "Non-absolute URI path: ~s" path))
732 ((and (zero? len) (not (uri-host uri)))
733 (bad-request "Empty path and no host for URI: ~s" uri))
734 (else
735 (display path port))))
736 (if (uri-query uri)
737 (begin
738 (display #\? port)
739 (display (uri-query uri) port))))
740
741(define (write-request-line method uri version port)
742 (display method port)
743 (display #\space port)
744 (write-uri uri port)
745 (display #\space port)
746 (write-http-version version port)
747 (display "\r\n" port))
748
749(define (read-response-line port)
750 (let* ((line (read-line* port))
751 (d0 (string-index line char-whitespace?)) ; "delimiter zero"
752 (d1 (and d0 (string-index line char-whitespace?
753 (skip-whitespace line d0)))))
754 (if (and d0 d1)
755 (values (parse-http-version line 0 d0)
756 (parse-non-negative-integer line (skip-whitespace line d0 d1)
757 d1)
758 (string-trim-both line char-whitespace? d1))
759 (bad-response "Bad Response-Line: ~s" line))))
760
761(define (write-response-line version code reason-phrase port)
762 (write-http-version version port)
763 (display #\space port)
764 (display code port)
765 (display #\space port)
766 (display reason-phrase port)
767 (display "\r\n" port))
768
769
770\f
771
772;;;
773;;; Syntax for declaring headers
774;;;
775
776;; emacs: (put 'declare-header 'scheme-indent-function 1)
777(define-syntax declare-header
778 (syntax-rules ()
779 ((_ sym name parser validator writer arg ...)
780 (declare-header!
781 'sym name
782 #:parser parser #:validator validator #:writer writer
783 arg ...))))
784
785;; emacs: (put 'declare-opaque-header 'scheme-indent-function 1)
786(define-syntax declare-opaque-header
787 (syntax-rules ()
788 ((_ sym name)
789 (declare-header sym
790 name
791 parse-opaque-string validate-opaque-string write-opaque-string))))
792
793;; emacs: (put 'declare-date-header 'scheme-indent-function 1)
794(define-syntax declare-date-header
795 (syntax-rules ()
796 ((_ sym name)
797 (declare-header sym
798 name
799 parse-date date? write-date))))
800
801;; emacs: (put 'declare-string-list-header 'scheme-indent-function 1)
802(define-syntax declare-string-list-header
803 (syntax-rules ()
804 ((_ sym name)
805 (declare-header sym
806 name
807 split-and-trim list-of-strings? write-list-of-strings))))
808
809;; emacs: (put 'declare-integer-header 'scheme-indent-function 1)
810(define-syntax declare-integer-header
811 (syntax-rules ()
812 ((_ sym name)
813 (declare-header sym
814 name
815 parse-non-negative-integer non-negative-integer? display))))
816
817;; emacs: (put 'declare-uri-header 'scheme-indent-function 1)
818(define-syntax declare-uri-header
819 (syntax-rules ()
820 ((_ sym name)
821 (declare-header sym
822 name
823 (lambda (str) (or (parse-uri str) (bad-header-component 'uri str)))
824 uri?
825 write-uri))))
826
827;; emacs: (put 'declare-quality-list-header 'scheme-indent-function 1)
828(define-syntax declare-quality-list-header
829 (syntax-rules ()
830 ((_ sym name)
831 (declare-header sym
832 name
833 parse-quality-list validate-quality-list write-quality-list))))
834
835;; emacs: (put 'declare-param-list-header 'scheme-indent-function 1)
836(define-syntax declare-param-list-header
837 (syntax-rules ()
838 ((_ sym name)
839 (declare-param-list-header sym name identity default-kons
840 default-kv-validator default-val-writer))
841 ((_ sym name kproc)
842 (declare-param-list-header sym name kproc default-kons
843 default-kv-validator default-val-writer))
844 ((_ sym name kproc kons val-validator val-writer)
845 (declare-header sym
846 name
847 (lambda (str) (parse-param-list str kproc kons))
848 (lambda (val) (validate-param-list val val-validator))
849 (lambda (val port) (write-param-list val port val-writer))))))
850
851;; emacs: (put 'declare-key-value-list-header 'scheme-indent-function 1)
852(define-syntax declare-key-value-list-header
853 (syntax-rules ()
854 ((_ sym name)
855 (declare-key-value-list-header sym name identity default-kons
856 default-kv-validator default-val-writer))
857 ((_ sym name kproc)
858 (declare-key-value-list-header sym name kproc default-kons
859 default-kv-validator default-val-writer))
860 ((_ sym name kproc kons val-validator val-writer)
861 (declare-header sym
862 name
863 (lambda (str) (parse-key-value-list str kproc kons))
864 (lambda (val) (key-value-list? val val-validator))
865 (lambda (val port) (write-key-value-list val port val-writer))))))
866
867;; emacs: (put 'declare-entity-tag-list-header 'scheme-indent-function 1)
868(define-syntax declare-entity-tag-list-header
869 (syntax-rules ()
870 ((_ sym name)
871 (declare-header sym
872 name
873 (lambda (str) (if (string=? str "*") '* (parse-entity-tag-list str)))
874 (lambda (val) (or (eq? val '*) (entity-tag-list? val)))
875 (lambda (val port)
876 (if (eq? val '*)
877 (display "*" port)
878 (write-entity-tag-list val port)))))))
879
880
881\f
882
883;;;
884;;; General headers
885;;;
886
887;; Cache-Control = 1#(cache-directive)
888;; cache-directive = cache-request-directive | cache-response-directive
889;; cache-request-directive =
890;; "no-cache" ; Section 14.9.1
891;; | "no-store" ; Section 14.9.2
892;; | "max-age" "=" delta-seconds ; Section 14.9.3, 14.9.4
893;; | "max-stale" [ "=" delta-seconds ] ; Section 14.9.3
894;; | "min-fresh" "=" delta-seconds ; Section 14.9.3
895;; | "no-transform" ; Section 14.9.5
896;; | "only-if-cached" ; Section 14.9.4
897;; | cache-extension ; Section 14.9.6
898;; cache-response-directive =
899;; "public" ; Section 14.9.1
900;; | "private" [ "=" <"> 1#field-name <"> ] ; Section 14.9.1
901;; | "no-cache" [ "=" <"> 1#field-name <"> ]; Section 14.9.1
902;; | "no-store" ; Section 14.9.2
903;; | "no-transform" ; Section 14.9.5
904;; | "must-revalidate" ; Section 14.9.4
905;; | "proxy-revalidate" ; Section 14.9.4
906;; | "max-age" "=" delta-seconds ; Section 14.9.3
907;; | "s-maxage" "=" delta-seconds ; Section 14.9.3
908;; | cache-extension ; Section 14.9.6
909;; cache-extension = token [ "=" ( token | quoted-string ) ]
910;;
911(declare-key-value-list-header cache-control
912 "Cache-Control"
913 (let ((known-directives (make-hash-table)))
914 (for-each (lambda (s)
915 (hash-set! known-directives s (string->symbol s)))
916 '("no-cache" "no-store" "max-age" "max-stale" "min-fresh"
917 "no-transform" "only-if-cached" "public" "private"
918 "must-revalidate" "proxy-revalidate" "s-maxage"))
919 (lambda (k-str)
920 (hash-ref known-directives k-str k-str)))
921 (lambda (k v-str)
922 (case k
923 ((max-age max-stale min-fresh s-maxage)
924 (cons k (parse-non-negative-integer v-str)))
925 ((private no-cache)
926 (cons k (if v-str (split-and-trim v-str) #t)))
927 (else (if v-str (cons k v-str) k))))
928 default-kv-validator
929 (lambda (k v port)
930 (cond
931 ((string? v) (display v port))
932 ((pair? v)
933 (write-qstring (string-join v ", ") port))
934 ((integer? v)
935 (display v port))
936 (else
937 (bad-header-component 'cache-control v)))))
938
939;; Connection = "Connection" ":" 1#(connection-token)
940;; connection-token = token
941;; e.g.
942;; Connection: close, foo-header
943;;
944(declare-string-list-header connection
945 "Connection")
946
947;; Date = "Date" ":" HTTP-date
948;; e.g.
949;; Date: Tue, 15 Nov 1994 08:12:31 GMT
950;;
951(declare-date-header date
952 "Date")
953
954;; Pragma = "Pragma" ":" 1#pragma-directive
955;; pragma-directive = "no-cache" | extension-pragma
956;; extension-pragma = token [ "=" ( token | quoted-string ) ]
957;;
958(declare-key-value-list-header pragma
959 "Pragma"
960 (lambda (k) (if (equal? k "no-cache") 'no-cache k)))
961
962;; Trailer = "Trailer" ":" 1#field-name
963;;
964(declare-string-list-header trailer
965 "Trailer")
966
967;; Transfer-Encoding = "Transfer-Encoding" ":" 1#transfer-coding
968;;
969(declare-param-list-header transfer-encoding
970 "Transfer-Encoding"
971 (lambda (k)
972 (if (equal? k "chunked") 'chunked k)))
973
974;; Upgrade = "Upgrade" ":" 1#product
975;;
976(declare-string-list-header upgrade
977 "Upgrade")
978
979;; Via = "Via" ":" 1#( received-protocol received-by [ comment ] )
980;; received-protocol = [ protocol-name "/" ] protocol-version
981;; protocol-name = token
982;; protocol-version = token
983;; received-by = ( host [ ":" port ] ) | pseudonym
984;; pseudonym = token
985;;
986(declare-header via
987 "Via"
988 split-and-trim
989 list-of-strings?
990 write-list-of-strings
991 #:multiple? #t)
992
993;; Warning = "Warning" ":" 1#warning-value
994;;
995;; warning-value = warn-code SP warn-agent SP warn-text
996;; [SP warn-date]
997;;
998;; warn-code = 3DIGIT
999;; warn-agent = ( host [ ":" port ] ) | pseudonym
1000;; ; the name or pseudonym of the server adding
1001;; ; the Warning header, for use in debugging
1002;; warn-text = quoted-string
1003;; warn-date = <"> HTTP-date <">
1004(declare-header warning
1005 "Warning"
1006 (lambda (str)
1007 (let ((len (string-length str)))
1008 (let lp ((i (skip-whitespace str 0)))
1009 (let* ((idx1 (string-index str #\space i))
1010 (idx2 (string-index str #\space (1+ idx1))))
1011 (if (and idx1 idx2)
1012 (let ((code (parse-non-negative-integer str i idx1))
1013 (agent (substring str (1+ idx1) idx2)))
1014 (call-with-values
1015 (lambda () (parse-qstring str (1+ idx2) #:incremental? #t))
1016 (lambda (text i)
1017 (call-with-values
1018 (lambda ()
1019 (let ((c (and (< i len) (string-ref str i))))
1020 (case c
1021 ((#\space)
1022 ;; we have a date.
1023 (call-with-values
1024 (lambda () (parse-qstring str (1+ i)
1025 #:incremental? #t))
1026 (lambda (date i)
1027 (values text (parse-date date) i))))
1028 (else
1029 (values text #f i)))))
1030 (lambda (text date i)
1031 (let ((w (list code agent text date))
1032 (c (and (< i len) (string-ref str i))))
1033 (case c
1034 ((#f) (list w))
1035 ((#\,) (cons w (lp (skip-whitespace str (1+ i)))))
1036 (else (bad-header 'warning str))))))))))))))
1037 (lambda (val)
1038 (list-of? val
1039 (lambda (elt)
1040 (and (list? elt)
1041 (= (length elt) 4)
1042 (apply (lambda (code host text date)
1043 (and (non-negative-integer? code) (< code 1000)
1044 (string? host)
1045 (string? text)
1046 (or (not date) (date? date))))
1047 elt)))))
1048 (lambda (val port)
1049 (write-list
1050 val port
1051 (lambda (w port)
1052 (apply
1053 (lambda (code host text date)
1054 (display code port)
1055 (display #\space port)
1056 (display host port)
1057 (display #\space port)
1058 (write-qstring text port)
1059 (if date
1060 (begin
1061 (display #\space port)
1062 (write-date date port))))
1063 w))
1064 ", "))
1065 #:multiple? #t)
1066
1067
1068\f
1069
1070;;;
1071;;; Entity headers
1072;;;
1073
1074;; Allow = #Method
1075;;
1076(declare-string-list-header allow
1077 "Allow")
1078
1079;; Content-Encoding = 1#content-coding
1080;;
1081(declare-string-list-header content-encoding
1082 "Content-Encoding")
1083
1084;; Content-Language = 1#language-tag
1085;;
1086(declare-string-list-header content-language
1087 "Content-Language")
1088
1089;; Content-Length = 1*DIGIT
1090;;
1091(declare-integer-header content-length
1092 "Content-Length")
1093
1094;; Content-Location = ( absoluteURI | relativeURI )
1095;;
1096(declare-uri-header content-location
1097 "Content-Location")
1098
1099;; Content-MD5 = <base64 of 128 bit MD5 digest as per RFC 1864>
1100;;
1101(declare-opaque-header content-md5
1102 "Content-MD5")
1103
1104;; Content-Range = content-range-spec
1105;; content-range-spec = byte-content-range-spec
1106;; byte-content-range-spec = bytes-unit SP
1107;; byte-range-resp-spec "/"
1108;; ( instance-length | "*" )
1109;; byte-range-resp-spec = (first-byte-pos "-" last-byte-pos)
1110;; | "*"
1111;; instance-length = 1*DIGIT
1112;;
1113(declare-header content-range
1114 "Content-Range"
1115 (lambda (str)
1116 (let ((dash (string-index str #\-))
1117 (slash (string-index str #\/)))
1118 (if (and (string-prefix? "bytes " str) slash)
1119 (list 'bytes
1120 (cond
1121 (dash
1122 (cons
1123 (parse-non-negative-integer str 6 dash)
1124 (parse-non-negative-integer str (1+ dash) slash)))
1125 ((string= str "*" 6 slash)
1126 '*)
1127 (else
1128 (bad-header 'content-range str)))
1129 (if (string= str "*" (1+ slash))
1130 '*
1131 (parse-non-negative-integer str (1+ slash))))
1132 (bad-header 'content-range str))))
1133 (lambda (val)
1134 (and (list? val) (= (length val) 3)
1135 (symbol? (car val))
1136 (let ((x (cadr val)))
1137 (or (eq? x '*)
1138 (and (pair? x)
1139 (non-negative-integer? (car x))
1140 (non-negative-integer? (cdr x)))))
1141 (let ((x (caddr val)))
1142 (or (eq? x '*)
1143 (non-negative-integer? x)))))
1144 (lambda (val port)
1145 (display (car val) port)
1146 (display #\space port)
1147 (if (eq? (cadr val) '*)
1148 (display #\* port)
1149 (begin
1150 (display (caadr val) port)
1151 (display #\- port)
1152 (display (caadr val) port)))
1153 (if (eq? (caddr val) '*)
1154 (display #\* port)
1155 (display (caddr val) port))))
1156
1157;; Content-Type = media-type
1158;;
1159(declare-header content-type
1160 "Content-Type"
1161 (lambda (str)
1162 (let ((parts (string-split str #\;)))
7aa54882
AW
1163 (cons (parse-media-type (car parts))
1164 (map (lambda (x)
1165 (let ((eq (string-index x #\=)))
1166 (if (and eq (= eq (string-rindex x #\=)))
1167 (cons (string-trim x char-whitespace? 0 eq)
1168 (string-trim-right x char-whitespace? (1+ eq)))
1169 (bad-header 'content-type str))))
1170 (cdr parts)))))
440840c1 1171 (lambda (val)
7aa54882
AW
1172 (and (pair? val)
1173 (string? (car val))
1174 (list-of? (cdr val)
1175 (lambda (x)
1176 (and (pair? x) (string? (car x)) (string? (cdr x)))))))
440840c1
AW
1177 (lambda (val port)
1178 (display (car val) port)
7aa54882
AW
1179 (if (pair? (cdr val))
1180 (begin
1181 (display ";" port)
1182 (write-list
1183 (cdr val) port
1184 (lambda (pair port)
1185 (display (car pair) port)
1186 (display #\= port)
1187 (display (cdr pair) port))
1188 ";")))))
440840c1
AW
1189
1190;; Expires = HTTP-date
1191;;
1192(declare-date-header expires
1193 "Expires")
1194
1195;; Last-Modified = HTTP-date
1196;;
1197(declare-date-header last-modified
1198 "Last-Modified")
1199
1200
1201\f
1202
1203;;;
1204;;; Request headers
1205;;;
1206
1207;; Accept = #( media-range [ accept-params ] )
1208;; media-range = ( "*/*" | ( type "/" "*" ) | ( type "/" subtype ) )
1209;; *( ";" parameter )
1210;; accept-params = ";" "q" "=" qvalue *( accept-extension )
1211;; accept-extension = ";" token [ "=" ( token | quoted-string ) ]
1212;;
1213(declare-param-list-header accept
1214 "Accept"
1215 ;; -> ("type/subtype" (str-prop . str-val) ...) ...)
1216 ;;
1217 ;; with the exception of prop = "q", in which case the prop will be
1218 ;; the symbol 'q, and the val will be a valid quality value
1219 ;;
1220 (lambda (k) (if (string=? k "q") 'q k))
1221 (lambda (k v)
1222 (if (eq? k 'q)
1223 (cons k (parse-quality v))
1224 (default-kons k v)))
1225 (lambda (k v)
1226 (if (eq? k 'q)
1227 (valid-quality? v)
1228 (default-kv-validator k v)))
1229 (lambda (k v port)
1230 (if (eq? k 'q)
1231 (write-quality v port)
1232 (default-val-writer k v port))))
1233
1234;; Accept-Charset = 1#( ( charset | "*" )[ ";" "q" "=" qvalue ] )
1235;;
1236(declare-quality-list-header accept-charset
1237 "Accept-Charset")
1238
1239;; Accept-Encoding = 1#( codings [ ";" "q" "=" qvalue ] )
1240;; codings = ( content-coding | "*" )
1241;;
1242(declare-quality-list-header accept-encoding
1243 "Accept-Encoding")
1244
1245;; Accept-Language = 1#( language-range [ ";" "q" "=" qvalue ] )
1246;; language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
1247;;
1248(declare-quality-list-header accept-language
1249 "Accept-Language")
1250
1251;; Authorization = credentials
1252;;
1253;; Authorization is basically opaque to this HTTP stack, we just pass
1254;; the string value through.
1255;;
1256(declare-opaque-header authorization
1257 "Authorization")
1258
1259;; Expect = 1#expectation
1260;; expectation = "100-continue" | expectation-extension
1261;; expectation-extension = token [ "=" ( token | quoted-string )
1262;; *expect-params ]
1263;; expect-params = ";" token [ "=" ( token | quoted-string ) ]
1264;;
1265(declare-param-list-header expect
1266 "Expect"
1267 (lambda (k)
1268 (if (equal? k "100-continue")
1269 '100-continue
1270 k)))
1271
1272;; From = mailbox
1273;;
1274;; Should be an email address; we just pass on the string as-is.
1275;;
1276(declare-opaque-header from
1277 "From")
1278
1279;; Host = host [ ":" port ]
1280;;
1281(declare-header host
1282 "Host"
1283 (lambda (str)
1284 (let ((colon (string-index str #\:)))
1285 (if colon
1286 (cons (substring str 0 colon)
1287 (parse-non-negative-integer str (1+ colon)))
1288 (cons str #f))))
1289 (lambda (val)
1290 (and (pair? val)
1291 (string? (car val))
1292 (or (not (cdr val))
1293 (non-negative-integer? (cdr val)))))
1294 (lambda (val port)
1295 (display (car val) port)
1296 (if (cdr val)
1297 (begin
1298 (display #\: port)
1299 (display (cdr val) port)))))
1300
1301;; If-Match = ( "*" | 1#entity-tag )
1302;;
1303(declare-entity-tag-list-header if-match
1304 "If-Match")
1305
1306;; If-Modified-Since = HTTP-date
1307;;
1308(declare-date-header if-modified-since
1309 "If-Modified-Since")
1310
1311;; If-None-Match = ( "*" | 1#entity-tag )
1312;;
1313(declare-entity-tag-list-header if-none-match
1314 "If-None-Match")
1315
1316;; If-Range = ( entity-tag | HTTP-date )
1317;;
1318(declare-header if-range
1319 "If-Range"
1320 (lambda (str)
1321 (if (or (string-prefix? "\"" str)
1322 (string-prefix? "W/" str))
1323 (parse-entity-tag str)
1324 (parse-date str)))
1325 (lambda (val)
1326 (or (date? val) (entity-tag? val)))
1327 (lambda (val port)
1328 (if (date? val)
1329 (write-date val port)
1330 (write-entity-tag val port))))
1331
1332;; If-Unmodified-Since = HTTP-date
1333;;
1334(declare-date-header if-unmodified-since
1335 "If-Unmodified-Since")
1336
1337;; Max-Forwards = 1*DIGIT
1338;;
1339(declare-integer-header max-forwards
1340 "Max-Forwards")
1341
1342;; Proxy-Authorization = credentials
1343;;
1344(declare-opaque-header proxy-authorization
1345 "Proxy-Authorization")
1346
1347;; Range = "Range" ":" ranges-specifier
1348;; ranges-specifier = byte-ranges-specifier
1349;; byte-ranges-specifier = bytes-unit "=" byte-range-set
1350;; byte-range-set = 1#( byte-range-spec | suffix-byte-range-spec )
1351;; byte-range-spec = first-byte-pos "-" [last-byte-pos]
1352;; first-byte-pos = 1*DIGIT
1353;; last-byte-pos = 1*DIGIT
1354;; suffix-byte-range-spec = "-" suffix-length
1355;; suffix-length = 1*DIGIT
1356;;
1357(declare-header range
1358 "Range"
1359 (lambda (str)
1360 (if (string-prefix? "bytes=" str)
1361 (cons
1362 'bytes
1363 (map (lambda (x)
1364 (let ((dash (string-index x #\-)))
1365 (cond
1366 ((not dash)
1367 (bad-header 'range str))
1368 ((zero? dash)
1369 (cons #f (parse-non-negative-integer x 1)))
1370 ((= dash (1- (string-length x)))
1371 (cons (parse-non-negative-integer x 0 dash) #f))
1372 (else
1373 (cons (parse-non-negative-integer x 0 dash)
1374 (parse-non-negative-integer x (1+ dash)))))))
1375 (string-split (substring str 6) #\,)))
1376 (bad-header 'range str)))
1377 (lambda (val)
1378 (and (pair? val)
1379 (symbol? (car val))
1380 (list-of? (cdr val)
1381 (lambda (elt)
1382 (and (pair? elt)
1383 (let ((x (car elt)) (y (cdr elt)))
1384 (and (or x y)
1385 (or (not x) (non-negative-integer? x))
1386 (or (not y) (non-negative-integer? y)))))))))
1387 (lambda (val port)
1388 (display (car val) port)
1389 (display #\= port)
1390 (write-list
1391 (cdr val) port
1392 (lambda (pair port)
1393 (if (car pair)
1394 (display (car pair) port))
1395 (display #\- port)
1396 (if (cdr pair)
1397 (display (cdr pair) port)))
1398 ",")))
1399
1400;; Referer = ( absoluteURI | relativeURI )
1401;;
1402(declare-uri-header referer
1403 "Referer")
1404
1405;; TE = #( t-codings )
1406;; t-codings = "trailers" | ( transfer-extension [ accept-params ] )
1407;;
1408(declare-param-list-header te
1409 "TE"
1410 (lambda (k) (if (equal? k "trailers") 'trailers k)))
1411
1412;; User-Agent = 1*( product | comment )
1413;;
1414(declare-opaque-header user-agent
1415 "User-Agent")
1416
1417
1418\f
1419
1420;;;
1421;;; Reponse headers
1422;;;
1423
1424;; Accept-Ranges = acceptable-ranges
1425;; acceptable-ranges = 1#range-unit | "none"
1426;;
1427(declare-string-list-header accept-ranges
1428 "Accept-Ranges")
1429
1430;; Age = age-value
1431;; age-value = delta-seconds
1432;;
1433(declare-integer-header age
1434 "Age")
1435
1436;; ETag = entity-tag
1437;;
1438(declare-header etag
1439 "ETag"
1440 parse-entity-tag
1441 entity-tag?
1442 write-entity-tag)
1443
1444;; Location = absoluteURI
1445;;
1446(declare-uri-header location
1447 "Location")
1448
1449;; Proxy-Authenticate = 1#challenge
1450;;
1451;; FIXME: split challenges ?
1452(declare-opaque-header proxy-authenticate
1453 "Proxy-Authenticate")
1454
1455;; Retry-After = ( HTTP-date | delta-seconds )
1456;;
1457(declare-header retry-after
1458 "Retry-After"
1459 (lambda (str)
1460 (if (and (not (string-null? str))
1461 (char-numeric? (string-ref str 0)))
1462 (parse-non-negative-integer str)
1463 (parse-date str)))
1464 (lambda (val)
1465 (or (date? val) (non-negative-integer? val)))
1466 (lambda (val port)
1467 (if (date? val)
1468 (write-date val port)
1469 (display val port))))
1470
1471;; Server = 1*( product | comment )
1472;;
1473(declare-opaque-header server
1474 "Server")
1475
1476;; Vary = ( "*" | 1#field-name )
1477;;
1478(declare-header vary
1479 "Vary"
1480 (lambda (str)
1481 (if (equal? str "*")
1482 '*
1483 (split-and-trim str)))
1484 (lambda (val)
1485 (or (eq? val '*) (list-of-strings? val)))
1486 (lambda (val port)
1487 (if (eq? val '*)
1488 (display "*" port)
1489 (write-list-of-strings val port))))
1490
1491;; WWW-Authenticate = 1#challenge
1492;;
1493;; Hum.
1494(declare-opaque-header www-authenticate
1495 "WWW-Authenticate")