Commit | Line | Data |
---|---|---|
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") |