fix bad-request-printer
[bpt/guile.git] / module / web / request.scm
CommitLineData
ad05d4e8
AW
1;;; HTTP request objects
2
e0dc4978 3;; Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
ad05d4e8
AW
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
20;;; Code:
21
22(define-module (web request)
23 #:use-module (rnrs bytevectors)
6854c324 24 #:use-module (ice-9 binary-ports)
ad05d4e8
AW
25 #:use-module (ice-9 rdelim)
26 #:use-module (srfi srfi-9)
27 #:use-module (web uri)
28 #:use-module (web http)
29 #:export (request?
30 request-method
31 request-uri
32 request-version
33 request-headers
d4b6200a 34 request-meta
ad05d4e8
AW
35 request-port
36
37 read-request
38 build-request
39 write-request
40
3475fbb5
AW
41 read-request-body
42 write-request-body
ad05d4e8
AW
43
44 ;; General headers
45 ;;
46 request-cache-control
47 request-connection
48 request-date
49 request-pragma
50 request-trailer
51 request-transfer-encoding
52 request-upgrade
53 request-via
54 request-warning
55
56 ;; Entity headers
57 ;;
58 request-allow
59 request-content-encoding
60 request-content-language
61 request-content-length
62 request-content-location
63 request-content-md5
64 request-content-range
65 request-content-type
66 request-expires
67 request-last-modified
68
69 ;; Request headers
70 ;;
71 request-accept
72 request-accept-charset
73 request-accept-encoding
74 request-accept-language
75 request-authorization
76 request-expect
77 request-from
78 request-host
79 request-if-match
80 request-if-modified-since
81 request-if-none-match
82 request-if-range
83 request-if-unmodified-since
84 request-max-forwards
85 request-proxy-authorization
86 request-range
87 request-referer
88 request-te
89 request-user-agent
90
91 ;; Misc
92 request-absolute-uri))
93
94
95;;; {Character Encodings, Strings, and Bytevectors}
96;;;
97;;; Requests are read from over the wire, and as such have to be treated
98;;; very carefully.
99;;;
100;;; The header portion of the message is defined to be in a subset of
101;;; ASCII, and may be processed either byte-wise (using bytevectors and
102;;; binary I/O) or as characters in a single-byte ASCII-compatible
103;;; encoding.
104;;;
105;;; We choose the latter, processing as strings in the latin-1
106;;; encoding. This allows us to use all the read-delimited machinery,
107;;; character sets, and regular expressions, shared substrings, etc.
108;;;
109;;; The characters in the header values may themselves encode other
110;;; bytes or characters -- basically each header has its own parser. We
111;;; leave that as a header-specific topic.
112;;;
113;;; The body is present if the content-length header is present. Its
114;;; format and, if textual, encoding is determined by the headers, but
115;;; its length is encoded in bytes. So we just slurp that number of
116;;; characters in latin-1, knowing that the number of characters
117;;; corresponds to the number of bytes, and then convert to a
118;;; bytevector, perhaps for later decoding.
119;;;
120
121(define-record-type <request>
d4b6200a 122 (make-request method uri version headers meta port)
ad05d4e8
AW
123 request?
124 (method request-method)
125 (uri request-uri)
126 (version request-version)
127 (headers request-headers)
d4b6200a 128 (meta request-meta)
ad05d4e8
AW
129 (port request-port))
130
131(define (bad-request message . args)
132 (throw 'bad-request message args))
133
e0dc4978
AW
134(define (bad-request-printer port key args default-printer)
135 (apply (case-lambda
9effafa4 136 ((msg args)
e0dc4978
AW
137 (display "Bad request: " port)
138 (apply format port msg args)
139 (newline port))
140 (_ (default-printer)))
141 args))
142
143(set-exception-printer! 'bad-request bad-request-printer)
144
ad05d4e8
AW
145(define (non-negative-integer? n)
146 (and (number? n) (>= n 0) (exact? n) (integer? n)))
147
148(define (validate-headers headers)
149 (if (pair? headers)
150 (let ((h (car headers)))
151 (if (pair? h)
152 (let ((k (car h)) (v (cdr h)))
be1be3e5
AW
153 (if (valid-header? k v)
154 (validate-headers (cdr headers))
155 (bad-request "Bad value for header ~a: ~s" k v)))
ad05d4e8
AW
156 (bad-request "Header not a pair: ~a" h)))
157 (if (not (null? headers))
158 (bad-request "Headers not a list: ~a" headers))))
159
f944ee8f 160(define* (build-request uri #:key (method 'GET) (version '(1 . 1))
d4b6200a
AW
161 (headers '()) port (meta '())
162 (validate-headers? #t))
cc1e26c2
AW
163 "Construct an HTTP request object. If @var{validate-headers?} is true,
164the headers are each run through their respective validators."
037a6803
AW
165 (let ((needs-host? (and (equal? version '(1 . 1))
166 (not (assq-ref headers 'host)))))
167 (cond
168 ((not (and (pair? version)
169 (non-negative-integer? (car version))
170 (non-negative-integer? (cdr version))))
171 (bad-request "Bad version: ~a" version))
172 ((not (uri? uri))
173 (bad-request "Bad uri: ~a" uri))
174 ((and (not port) (memq method '(POST PUT)))
175 (bad-request "Missing port for message ~a" method))
176 ((not (list? meta))
177 (bad-request "Bad metadata alist" meta))
178 ((and needs-host? (not (uri-host uri)))
179 (bad-request "HTTP/1.1 request without Host header and no host in URI: ~a"
180 uri))
181 (else
182 (if validate-headers?
183 (validate-headers headers))))
184 (make-request method uri version
185 (if needs-host?
186 (acons 'host (cons (uri-host uri) (uri-port uri))
187 headers)
188 headers)
189 meta port)))
ad05d4e8 190
d4b6200a 191(define* (read-request port #:optional (meta '()))
cc1e26c2
AW
192 "Read an HTTP request from @var{port}, optionally attaching the given
193metadata, @var{meta}.
194
195As a side effect, sets the encoding on @var{port} to
196ISO-8859-1 (latin-1), so that reading one character reads one byte. See
197the discussion of character sets in \"HTTP Requests\" in the manual, for
198more information."
ad05d4e8
AW
199 (set-port-encoding! port "ISO-8859-1")
200 (call-with-values (lambda () (read-request-line port))
201 (lambda (method uri version)
d4b6200a 202 (make-request method uri version (read-headers port) meta port))))
ad05d4e8 203
d4b6200a 204;; FIXME: really return a new request?
ad05d4e8 205(define (write-request r port)
cc1e26c2
AW
206 "Write the given HTTP request to @var{port}.
207
208Returns a new request, whose @code{request-port} will continue writing
209on @var{port}, perhaps using some transfer encoding."
ad05d4e8
AW
210 (write-request-line (request-method r) (request-uri r)
211 (request-version r) port)
212 (write-headers (request-headers r) port)
213 (display "\r\n" port)
214 (if (eq? port (request-port r))
215 r
216 (make-request (request-method r) (request-uri r) (request-version r)
d4b6200a 217 (request-headers r) (request-meta r) port)))
ad05d4e8 218
3475fbb5 219(define (read-request-body r)
cc1e26c2
AW
220 "Reads the request body from @var{r}, as a bytevector. Returns
221@code{#f} if there was no request body."
ad05d4e8
AW
222 (let ((nbytes (request-content-length r)))
223 (and nbytes
224 (let ((bv (get-bytevector-n (request-port r) nbytes)))
225 (if (= (bytevector-length bv) nbytes)
226 bv
227 (bad-request "EOF while reading request body: ~a bytes of ~a"
228 (bytevector-length bv) nbytes))))))
229
3475fbb5 230(define (write-request-body r bv)
91a214eb 231 "Write @var{bv}, a bytevector, to the port corresponding to the HTTP
cc1e26c2 232request @var{r}."
ad05d4e8
AW
233 (put-bytevector (request-port r) bv))
234
235(define-syntax define-request-accessor
236 (lambda (x)
237 (syntax-case x ()
238 ((_ field)
239 #'(define-request-accessor field #f))
240 ((_ field def) (identifier? #'field)
241 #`(define* (#,(datum->syntax
242 #'field
243 (symbol-append 'request- (syntax->datum #'field)))
244 request
245 #:optional (default def))
246 (cond
247 ((assq 'field (request-headers request)) => cdr)
248 (else default)))))))
249
250;; General headers
251;;
252(define-request-accessor cache-control '())
253(define-request-accessor connection '())
254(define-request-accessor date #f)
255(define-request-accessor pragma '())
256(define-request-accessor trailer '())
257(define-request-accessor transfer-encoding '())
258(define-request-accessor upgrade '())
259(define-request-accessor via '())
260(define-request-accessor warning '())
261
262;; Entity headers
263;;
264(define-request-accessor allow '())
265(define-request-accessor content-encoding '())
266(define-request-accessor content-language '())
267(define-request-accessor content-length #f)
268(define-request-accessor content-location #f)
269(define-request-accessor content-md5 #f)
270(define-request-accessor content-range #f)
271(define-request-accessor content-type #f)
272(define-request-accessor expires #f)
273(define-request-accessor last-modified #f)
274
275;; Request headers
276;;
277(define-request-accessor accept '())
278(define-request-accessor accept-charset '())
279(define-request-accessor accept-encoding '())
280(define-request-accessor accept-language '())
281(define-request-accessor authorization #f)
282(define-request-accessor expect '())
283(define-request-accessor from #f)
284(define-request-accessor host #f)
285;; Absence of an if-directive appears to be different from `*'.
286(define-request-accessor if-match #f)
287(define-request-accessor if-modified-since #f)
288(define-request-accessor if-none-match #f)
289(define-request-accessor if-range #f)
290(define-request-accessor if-unmodified-since #f)
291(define-request-accessor max-forwards #f)
292(define-request-accessor proxy-authorization #f)
293(define-request-accessor range #f)
294(define-request-accessor referer #f)
295(define-request-accessor te '())
296(define-request-accessor user-agent #f)
297
298;; Misc accessors
299(define* (request-absolute-uri r #:optional default-host default-port)
300 (let ((uri (request-uri r)))
301 (if (uri-host uri)
302 uri
303 (let ((host
304 (or (request-host r)
305 (if default-host
306 (cons default-host default-port)
307 (bad-request
308 "URI not absolute, no Host header, and no default: ~s"
309 uri)))))
310 (build-uri (uri-scheme uri)
311 #:host (car host)
312 #:port (cdr host)
313 #:path (uri-path uri)
314 #:query (uri-query uri)
315 #:fragment (uri-fragment uri))))))