mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-29 19:30:36 +02:00
These were found with: make GUILE_WARNINGS='-W1 -Wunused-module' * module/ice-9/copy-tree.scm: * module/ice-9/eval-string.scm: * module/ice-9/getopt-long.scm: * module/ice-9/poll.scm: * module/ice-9/popen.scm: * module/ice-9/sandbox.scm: * module/ice-9/threads.scm: * module/sxml/apply-templates.scm: * module/sxml/simple.scm: * module/system/base/types.scm: * module/system/repl/command.scm: * module/system/repl/common.scm: * module/system/repl/coop-server.scm: * module/system/repl/debug.scm: * module/system/repl/error-handling.scm: * module/system/repl/repl.scm: * module/system/repl/server.scm: * module/system/vm/assembler.scm: * module/system/vm/disassembler.scm: * module/system/vm/dwarf.scm: * module/system/vm/elf.scm: * module/system/vm/frame.scm: * module/system/vm/inspect.scm: * module/system/vm/linker.scm: * module/system/vm/program.scm: * module/system/vm/trace.scm: * module/system/vm/trap-state.scm: * module/system/vm/traps.scm: * module/system/xref.scm: * module/texinfo/indexing.scm: * module/texinfo/plain-text.scm: * module/texinfo/reflection.scm: * module/texinfo/string-utils.scm: * module/web/client.scm: * module/web/http.scm: * module/web/request.scm: * module/web/response.scm: Remove imports of unused modules.
325 lines
11 KiB
Scheme
325 lines
11 KiB
Scheme
;;; 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))))))
|