request.scm 11 KB

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