fix scm_from_stringn empty string case
[bpt/guile.git] / module / web / response.scm
CommitLineData
a9eeb2f4
AW
1;;; HTTP response objects
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
20;;; Code:
21
22(define-module (web response)
23 #:use-module (rnrs bytevectors)
24 #:use-module (rnrs io ports)
25 #:use-module (ice-9 rdelim)
26 #:use-module (srfi srfi-9)
27 #:use-module (web http)
28 #:export (response?
29 response-version
30 response-code
31 response-reason-phrase
32 response-headers
33 response-port
34 read-response
35 build-response
3d959779 36 extend-response
c6371902 37 adapt-response-version
a9eeb2f4
AW
38 write-response
39
40 read-response-body/latin-1
41 write-response-body/latin-1
42
43 read-response-body/bytevector
44 write-response-body/bytevector
45
46 ;; General headers
47 ;;
48 response-cache-control
49 response-connection
50 response-date
51 response-pragma
52 response-trailer
53 response-transfer-encoding
54 response-upgrade
55 response-via
56 response-warning
57
58 ;; Entity headers
59 ;;
60 response-allow
61 response-content-encoding
62 response-content-language
63 response-content-length
64 response-content-location
65 response-content-md5
66 response-content-range
67 response-content-type
68 response-expires
69 response-last-modified
70
71 ;; Response headers
72 ;;
73 response-accept-ranges
74 response-age
75 response-etag
76 response-location
77 response-proxy-authenticate
78 response-retry-after
79 response-server
80 response-vary
81 response-www-authenticate))
82
83
84(define-record-type <response>
85 (make-response version code reason-phrase headers port)
86 response?
87 (version response-version)
88 (code response-code)
89 (reason-phrase %response-reason-phrase)
90 (headers response-headers)
91 (port response-port))
92
93(define (bad-response message . args)
94 (throw 'bad-response message args))
95
e2d4bfea
AW
96(define (non-negative-integer? n)
97 (and (number? n) (>= n 0) (exact? n) (integer? n)))
98
99(define (validate-headers headers)
100 (if (pair? headers)
101 (let ((h (car headers)))
102 (if (pair? h)
103 (let ((k (car h)) (v (cdr h)))
104 (if (symbol? k)
105 (if (not (valid-header? k v))
106 (bad-response "Bad value for header ~a: ~s" k v))
107 (if (not (and (string? k) (string? v)))
108 (bad-response "Unknown header not a pair of strings: ~s"
109 h)))
110 (validate-headers (cdr headers)))
111 (bad-response "Header not a pair: ~a" h)))
112 (if (not (null? headers))
113 (bad-response "Headers not a list: ~a" headers))))
114
a9eeb2f4 115(define* (build-response #:key (version '(1 . 1)) (code 200) reason-phrase
e2d4bfea 116 (headers '()) port (validate-headers? #t))
cc1e26c2
AW
117 "Construct an HTTP response object. If @var{validate-headers?} is true,
118the headers are each run through their respective validators."
e2d4bfea
AW
119 (cond
120 ((not (and (pair? version)
121 (non-negative-integer? (car version))
122 (non-negative-integer? (cdr version))))
123 (bad-response "Bad version: ~a" version))
124 ((not (and (non-negative-integer? code) (< code 600)))
125 (bad-response "Bad code: ~a" code))
126 ((and reason-phrase (not (string? reason-phrase)))
127 (bad-response "Bad reason phrase" reason-phrase))
128 (else
129 (if validate-headers?
130 (validate-headers headers))))
a9eeb2f4
AW
131 (make-response version code reason-phrase headers port))
132
3d959779 133(define (extend-response r k v . additional)
cc1e26c2
AW
134 "Extend an HTTP response by setting additional HTTP headers @var{k},
135@var{v}. Returns a new HTTP response."
3d959779
AW
136 (let ((r (build-response #:version (response-version r)
137 #:code (response-code r)
138 #:reason-phrase (%response-reason-phrase r)
139 #:headers
140 (assoc-set! (copy-tree (response-headers r))
141 k v)
142 #:port (response-port r))))
143 (if (null? additional)
144 r
145 (apply extend-response r additional))))
146
a9eeb2f4
AW
147(define *reason-phrases*
148 '((100 . "Continue")
149 (101 . "Switching Protocols")
150 (200 . "OK")
151 (201 . "Created")
152 (202 . "Accepted")
153 (203 . "Non-Authoritative Information")
154 (204 . "No Content")
155 (205 . "Reset Content")
156 (206 . "Partial Content")
157 (300 . "Multiple Choices")
158 (301 . "Moved Permanently")
159 (302 . "Found")
160 (303 . "See Other")
161 (304 . "Not Modified")
162 (305 . "Use Proxy")
163 (307 . "Temporary Redirect")
164 (400 . "Bad Request")
165 (401 . "Unauthorized")
166 (402 . "Payment Required")
167 (403 . "Forbidden")
168 (404 . "Not Found")
169 (405 . "Method Not Allowed")
170 (406 . "Not Acceptable")
171 (407 . "Proxy Authentication Required")
172 (408 . "Request Timeout")
173 (409 . "Conflict")
174 (410 . "Gone")
175 (411 . "Length Required")
176 (412 . "Precondition Failed")
177 (413 . "Request Entity Too Large")
178 (414 . "Request-URI Too Long")
179 (415 . "Unsupported Media Type")
180 (416 . "Requested Range Not Satisfiable")
181 (417 . "Expectation Failed")
182 (500 . "Internal Server Error")
183 (501 . "Not Implemented")
184 (502 . "Bad Gateway")
185 (503 . "Service Unavailable")
186 (504 . "Gateway Timeout")
187 (505 . "HTTP Version Not Supported")))
188
189(define (code->reason-phrase code)
190 (or (assv-ref *reason-phrases* code)
191 "(Unknown)"))
192
193(define (response-reason-phrase response)
cc1e26c2
AW
194 "Return the reason phrase given in @var{response}, or the standard
195reason phrase for the response's code."
a9eeb2f4
AW
196 (or (%response-reason-phrase response)
197 (code->reason-phrase (response-code response))))
198
199(define (read-response port)
cc1e26c2
AW
200 "Read an HTTP response from @var{port}, optionally attaching the given
201metadata, @var{meta}.
202
203As a side effect, sets the encoding on @var{port} to
204ISO-8859-1 (latin-1), so that reading one character reads one byte. See
205the discussion of character sets in \"HTTP Responses\" in the manual,
206for more information."
a9eeb2f4
AW
207 (set-port-encoding! port "ISO-8859-1")
208 (call-with-values (lambda () (read-response-line port))
209 (lambda (version code reason-phrase)
210 (make-response version code reason-phrase (read-headers port) port))))
211
c6371902 212(define (adapt-response-version response version)
cc1e26c2
AW
213 "Adapt the given response to a different HTTP version. Returns a new
214HTTP response.
215
216The idea is that many applications might just build a response for the
217default HTTP version, and this method could handle a number of
218programmatic transformations to respond to older HTTP versions (0.9 and
2191.0). But currently this function is a bit heavy-handed, just updating
220the version field."
c6371902
AW
221 (build-response #:code (response-code response)
222 #:version version
223 #:headers (response-headers response)
224 #:port (response-port response)))
225
a9eeb2f4 226(define (write-response r port)
cc1e26c2
AW
227 "Write the given HTTP response to @var{port}.
228
229Returns a new response, whose @code{response-port} will continue writing
230on @var{port}, perhaps using some transfer encoding."
a9eeb2f4
AW
231 (write-response-line (response-version r) (response-code r)
232 (response-reason-phrase r) port)
233 (write-headers (response-headers r) port)
234 (display "\r\n" port)
235 (if (eq? port (response-port r))
236 r
237 (make-response (response-version r) (response-code r)
238 (response-reason-phrase r) (response-headers r) port)))
239
240;; Probably not what you want to use "in production". Relies on one byte
241;; per char because we are in latin-1 encoding.
242;;
243(define (read-response-body/latin-1 r)
cc1e26c2
AW
244 "Reads the response body from @var{r}, as a string.
245
246Assumes that the response port has ISO-8859-1 encoding, so that the
247number of characters to read is the same as the
248@code{response-content-length}. Returns @code{#f} if there was no
249response body."
e46f69e2
AW
250 (cond
251 ((response-content-length r) =>
252 (lambda (nbytes)
253 (let ((buf (make-string nbytes))
254 (port (response-port r)))
255 (let lp ((i 0))
256 (cond
257 ((< i nbytes)
258 (let ((c (read-char port)))
259 (cond
260 ((eof-object? c)
261 (bad-response "EOF while reading response body: ~a bytes of ~a"
262 i nbytes))
263 (else
264 (string-set! buf i c)
265 (lp (1+ i))))))
266 (else buf))))))
267 (else #f)))
a9eeb2f4
AW
268
269;; Likewise, assumes that body can be written in the latin-1 encoding,
cc1e26c2 270;; and that the latin-1 encoding is what is expected by the client.
a9eeb2f4
AW
271;;
272(define (write-response-body/latin-1 r body)
cc1e26c2
AW
273 "Write @var{body}, a string encodable in ISO-8859-1, to the port
274corresponding to the HTTP response @var{r}."
a9eeb2f4
AW
275 (display body (response-port r)))
276
277(define (read-response-body/bytevector r)
cc1e26c2
AW
278 "Reads the response body from @var{r}, as a bytevector. Returns
279@code{#f} if there was no response body."
a9eeb2f4
AW
280 (let ((nbytes (response-content-length r)))
281 (and nbytes
282 (let ((bv (get-bytevector-n (response-port r) nbytes)))
283 (if (= (bytevector-length bv) nbytes)
284 bv
285 (bad-response "EOF while reading response body: ~a bytes of ~a"
286 (bytevector-length bv) nbytes))))))
287
288(define (write-response-body/bytevector r bv)
cc1e26c2
AW
289 "Write @var{body}, a bytevector, to the port corresponding to the HTTP
290response @var{r}."
a9eeb2f4
AW
291 (put-bytevector (response-port r) bv))
292
293(define-syntax define-response-accessor
294 (lambda (x)
295 (syntax-case x ()
296 ((_ field)
297 #'(define-response-accessor field #f))
298 ((_ field def) (identifier? #'field)
299 #`(define* (#,(datum->syntax
300 #'field
301 (symbol-append 'response- (syntax->datum #'field)))
302 response
303 #:optional (default def))
304 (cond
305 ((assq 'field (response-headers response)) => cdr)
306 (else default)))))))
307
308;; General headers
309;;
310(define-response-accessor cache-control '())
311(define-response-accessor connection '())
312(define-response-accessor date #f)
313(define-response-accessor pragma '())
314(define-response-accessor trailer '())
315(define-response-accessor transfer-encoding '())
316(define-response-accessor upgrade '())
317(define-response-accessor via '())
318(define-response-accessor warning '())
319
320;; Entity headers
321;;
322(define-response-accessor allow '())
323(define-response-accessor content-encoding '())
324(define-response-accessor content-language '())
325(define-response-accessor content-length #f)
326(define-response-accessor content-location #f)
327(define-response-accessor content-md5 #f)
328(define-response-accessor content-range #f)
329(define-response-accessor content-type #f)
330(define-response-accessor expires #f)
331(define-response-accessor last-modified #f)
332
333;; Response headers
334;;
335(define-response-accessor accept-ranges #f)
336(define-response-accessor age #f)
337(define-response-accessor etag #f)
338(define-response-accessor location #f)
339(define-response-accessor proxy-authenticate #f)
340(define-response-accessor retry-after #f)
341(define-response-accessor server #f)
342(define-response-accessor vary '())
343(define-response-accessor www-authenticate #f)