123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326 |
- ;;; HTTP request objects
- ;; Copyright (C) 2010, 2011, 2012 Free Software Foundation, Inc.
- ;; This library is free software; you can redistribute it and/or
- ;; modify it under the terms of the GNU Lesser General Public
- ;; License as published by the Free Software Foundation; either
- ;; version 3 of the License, or (at your option) any later version.
- ;;
- ;; This library is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- ;; Lesser General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU Lesser General Public
- ;; License along with this library; if not, write to the Free Software
- ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- ;; 02110-1301 USA
- ;;; Code:
- (define-module (web request)
- #:use-module (rnrs bytevectors)
- #:use-module (ice-9 binary-ports)
- #:use-module (ice-9 textual-ports)
- #:use-module (srfi srfi-9)
- #:use-module (web uri)
- #:use-module (web http)
- #:export (request?
- request-method
- request-uri
- request-version
- request-headers
- request-meta
- request-port
-
- read-request
- build-request
- write-request
- read-request-body
- write-request-body
- ;; General headers
- ;;
- request-cache-control
- request-connection
- request-date
- request-pragma
- request-trailer
- request-transfer-encoding
- request-upgrade
- request-via
- request-warning
-
- ;; Entity headers
- ;;
- request-allow
- request-content-encoding
- request-content-language
- request-content-length
- request-content-location
- request-content-md5
- request-content-range
- request-content-type
- request-expires
- request-last-modified
-
- ;; Request headers
- ;;
- request-accept
- request-accept-charset
- request-accept-encoding
- request-accept-language
- request-authorization
- request-expect
- request-from
- request-host
- request-if-match
- request-if-modified-since
- request-if-none-match
- request-if-range
- request-if-unmodified-since
- request-max-forwards
- request-proxy-authorization
- request-range
- request-referer
- request-te
- request-user-agent
- ;; Misc
- request-absolute-uri))
- ;;; {Character Encodings, Strings, and Bytevectors}
- ;;;
- ;;; Requests are read from over the wire, and as such have to be treated
- ;;; very carefully.
- ;;;
- ;;; The header portion of the message is defined to be in a subset of
- ;;; ASCII, and may be processed either byte-wise (using bytevectors and
- ;;; binary I/O) or as characters in a single-byte ASCII-compatible
- ;;; encoding.
- ;;;
- ;;; We choose the latter, processing as strings in the latin-1
- ;;; encoding. This allows us to use all the read-delimited machinery,
- ;;; character sets, and regular expressions, shared substrings, etc.
- ;;;
- ;;; The characters in the header values may themselves encode other
- ;;; bytes or characters -- basically each header has its own parser. We
- ;;; leave that as a header-specific topic.
- ;;;
- ;;; The body is present if the content-length header is present. Its
- ;;; format and, if textual, encoding is determined by the headers, but
- ;;; its length is encoded in bytes. So we just slurp that number of
- ;;; characters in latin-1, knowing that the number of characters
- ;;; corresponds to the number of bytes, and then convert to a
- ;;; bytevector, perhaps for later decoding.
- ;;;
- (define-record-type <request>
- (make-request method uri version headers meta port)
- request?
- (method request-method)
- (uri request-uri)
- (version request-version)
- (headers request-headers)
- (meta request-meta)
- (port request-port))
- (define (bad-request message . args)
- (throw 'bad-request message args))
- (define (bad-request-printer port key args default-printer)
- (apply (case-lambda
- ((msg args)
- (display "Bad request: " port)
- (apply format port msg args)
- (newline port))
- (_ (default-printer)))
- args))
- (set-exception-printer! 'bad-request bad-request-printer)
- (define (non-negative-integer? n)
- (and (number? n) (>= n 0) (exact? n) (integer? n)))
-
- (define (validate-headers headers)
- (if (pair? headers)
- (let ((h (car headers)))
- (if (pair? h)
- (let ((k (car h)) (v (cdr h)))
- (if (valid-header? k v)
- (validate-headers (cdr headers))
- (bad-request "Bad value for header ~a: ~s" k v)))
- (bad-request "Header not a pair: ~a" h)))
- (if (not (null? headers))
- (bad-request "Headers not a list: ~a" headers))))
- (define* (build-request uri #:key (method 'GET) (version '(1 . 1))
- (headers '()) port (meta '())
- (validate-headers? #t))
- "Construct an HTTP request object. If VALIDATE-HEADERS? is true,
- the headers are each run through their respective validators."
- (let ((needs-host? (and (equal? version '(1 . 1))
- (not (assq-ref headers 'host)))))
- (cond
- ((not (and (pair? version)
- (non-negative-integer? (car version))
- (non-negative-integer? (cdr version))))
- (bad-request "Bad version: ~a" version))
- ((not (uri-reference? uri))
- (bad-request "Bad uri: ~a" uri))
- ((and (not port) (memq method '(POST PUT)))
- (bad-request "Missing port for message ~a" method))
- ((not (list? meta))
- (bad-request "Bad metadata alist" meta))
- ((and needs-host? (not (uri-host uri)))
- (bad-request "HTTP/1.1 request without Host header and no host in URI: ~a"
- uri))
- (else
- (if validate-headers?
- (validate-headers headers))))
- (make-request method uri version
- (if needs-host?
- (acons 'host (cons (uri-host uri) (uri-port uri))
- headers)
- headers)
- meta port)))
- (define* (read-request port #:optional (meta '()))
- "Read an HTTP request from PORT, optionally attaching the given
- metadata, META.
- As a side effect, sets the encoding on PORT to
- ISO-8859-1 (latin-1), so that reading one character reads one byte. See
- the discussion of character sets in \"HTTP Requests\" in the manual, for
- more information.
- Note that the body is not part of the request. Once you have read a
- request, you may read the body separately, and likewise for writing
- requests."
- (set-port-encoding! port "ISO-8859-1")
- (call-with-values (lambda () (read-request-line port))
- (lambda (method uri version)
- (make-request method uri version (read-headers port) meta port))))
- ;; FIXME: really return a new request?
- (define (write-request r port)
- "Write the given HTTP request to PORT.
- Return a new request, whose ‘request-port’ will continue writing
- on PORT, perhaps using some transfer encoding."
- (write-request-line (request-method r) (request-uri r)
- (request-version r) port)
- (write-headers (request-headers r) port)
- (put-string port "\r\n")
- (if (eq? port (request-port r))
- r
- (make-request (request-method r) (request-uri r) (request-version r)
- (request-headers r) (request-meta r) port)))
- (define (read-request-body r)
- "Reads the request body from R, as a bytevector. Return ‘#f’
- if there was no request body."
- (let ((nbytes (request-content-length r)))
- (and nbytes
- (let ((bv (get-bytevector-n (request-port r) nbytes)))
- (if (= (bytevector-length bv) nbytes)
- bv
- (bad-request "EOF while reading request body: ~a bytes of ~a"
- (bytevector-length bv) nbytes))))))
- (define (write-request-body r bv)
- "Write BV, a bytevector, to the port corresponding to the HTTP
- request R."
- (put-bytevector (request-port r) bv))
- (define-syntax define-request-accessor
- (lambda (x)
- (syntax-case x ()
- ((_ field)
- #'(define-request-accessor field #f))
- ((_ field def) (identifier? #'field)
- #`(define* (#,(datum->syntax
- #'field
- (symbol-append 'request- (syntax->datum #'field)))
- request
- #:optional (default def))
- (cond
- ((assq 'field (request-headers request)) => cdr)
- (else default)))))))
- ;; General headers
- ;;
- (define-request-accessor cache-control '())
- (define-request-accessor connection '())
- (define-request-accessor date #f)
- (define-request-accessor pragma '())
- (define-request-accessor trailer '())
- (define-request-accessor transfer-encoding '())
- (define-request-accessor upgrade '())
- (define-request-accessor via '())
- (define-request-accessor warning '())
- ;; Entity headers
- ;;
- (define-request-accessor allow '())
- (define-request-accessor content-encoding '())
- (define-request-accessor content-language '())
- (define-request-accessor content-length #f)
- (define-request-accessor content-location #f)
- (define-request-accessor content-md5 #f)
- (define-request-accessor content-range #f)
- (define-request-accessor content-type #f)
- (define-request-accessor expires #f)
- (define-request-accessor last-modified #f)
- ;; Request headers
- ;;
- (define-request-accessor accept '())
- (define-request-accessor accept-charset '())
- (define-request-accessor accept-encoding '())
- (define-request-accessor accept-language '())
- (define-request-accessor authorization #f)
- (define-request-accessor expect '())
- (define-request-accessor from #f)
- (define-request-accessor host #f)
- ;; Absence of an if-directive appears to be different from `*'.
- (define-request-accessor if-match #f)
- (define-request-accessor if-modified-since #f)
- (define-request-accessor if-none-match #f)
- (define-request-accessor if-range #f)
- (define-request-accessor if-unmodified-since #f)
- (define-request-accessor max-forwards #f)
- (define-request-accessor proxy-authorization #f)
- (define-request-accessor range #f)
- (define-request-accessor referer #f)
- (define-request-accessor te '())
- (define-request-accessor user-agent #f)
- ;; Misc accessors
- (define* (request-absolute-uri r #:optional default-host default-port
- default-scheme)
- "A helper routine to determine the absolute URI of a request, using the
- ‘host’ header and the default host and port."
- (let ((uri (request-uri r)))
- (if (uri-host uri)
- uri
- (let ((host
- (or (request-host r)
- (if default-host
- (cons default-host default-port)
- (bad-request
- "URI not absolute, no Host header, and no default: ~s"
- uri)))))
- (build-uri (or (uri-scheme uri)
- default-scheme
- (bad-request "URI not absolute and no default-port"
- uri))
- #:host (car host)
- #:port (cdr host)
- #:path (uri-path uri)
- #:query (uri-query uri)
- #:fragment (uri-fragment uri))))))
|