148 lines
6.3 KiB
Scheme
148 lines
6.3 KiB
Scheme
(module servlet-helpers mzscheme
|
|
(require (lib "contract.ss")
|
|
(lib "etc.ss")
|
|
(lib "plt-match.ss")
|
|
(lib "xml.ss" "xml")
|
|
(lib "base64.ss" "net")
|
|
(lib "url.ss" "net")
|
|
(lib "uri-codec.ss" "net"))
|
|
(require "util.ss"
|
|
"bindings.ss"
|
|
"../servlet-structs.ss"
|
|
"../request-structs.ss"
|
|
"../response-structs.ss")
|
|
(provide (all-from "bindings.ss")
|
|
(all-from "../request-structs.ss"))
|
|
|
|
(define (request-headers request)
|
|
(map (match-lambda
|
|
[(struct header (field value))
|
|
(cons (lowercase-symbol! (bytes->string/utf-8 field))
|
|
(bytes->string/utf-8 value))])
|
|
(request-headers/raw request)))
|
|
(define (request-bindings request)
|
|
(map (match-lambda
|
|
[(struct binding:form (id value))
|
|
(cons (lowercase-symbol! (bytes->string/utf-8 id))
|
|
(bytes->string/utf-8 value))]
|
|
[(struct binding:file (id fname value))
|
|
(cons (lowercase-symbol! (bytes->string/utf-8 id))
|
|
value)])
|
|
(request-bindings/raw request)))
|
|
|
|
;; get-host : Url (listof (cons Symbol String)) -> Symbol
|
|
;; host names are case insesitive---Internet RFC 1034
|
|
(define DEFAULT-HOST-NAME '<none>)
|
|
(define (get-host uri headers)
|
|
(cond
|
|
[(url-host uri) => string->symbol]
|
|
[(headers-assq* #"Host" headers)
|
|
=> (match-lambda
|
|
[(struct header (_ v))
|
|
(string->symbol (bytes->string/utf-8 v))])]
|
|
[else DEFAULT-HOST-NAME]))
|
|
|
|
; build-suspender : (listof html) (listof html) [(listof (cons sym str))] [(listof (cons sym str))] -> str -> response
|
|
(define build-suspender
|
|
(opt-lambda (title content [body-attributes '([bgcolor "white"])] [head-attributes null])
|
|
(lambda (k-url)
|
|
`(html (head ,head-attributes
|
|
(meta ([http-equiv "Pragma"] [content "no-cache"])) ; don't cache in netscape
|
|
(meta ([http-equiv "Expires"] [content "-1"])) ; don't cache in IE
|
|
; one site said to use -1, another said to use 0.
|
|
(title . ,title))
|
|
(body ,body-attributes
|
|
(form ([action ,k-url] [method "post"])
|
|
,@content))))))
|
|
|
|
; redirection-status = (make-redirection-status nat str)
|
|
(define-struct redirection-status (code message))
|
|
|
|
(define permanently (make-redirection-status 301 "Moved Permanently"))
|
|
(define temporarily (make-redirection-status 302 "Moved Temporarily"))
|
|
(define see-other (make-redirection-status 303 "See Other"))
|
|
|
|
; : str [redirection-status] -> response
|
|
(define redirect-to
|
|
(opt-lambda (uri [perm/temp permanently])
|
|
(make-response/full (redirection-status-code perm/temp)
|
|
(redirection-status-message perm/temp)
|
|
(current-seconds) #"text/html"
|
|
`((Location . ,uri)) (list (redirect-page uri)))))
|
|
|
|
; : str -> str
|
|
(define (redirect-page url)
|
|
(xexpr->string `(html (head (meta ((http-equiv "refresh") (url ,url)))
|
|
"Redirect to " ,url)
|
|
(body (p "Redirecting to " (a ([href ,url]) ,url))))))
|
|
|
|
; make-html-response/incremental : ((string -> void) -> void) -> response/incremental
|
|
(define (make-html-response/incremental chunk-maker)
|
|
(make-response/incremental
|
|
200 "Okay" (current-seconds) #"text/html" '()
|
|
chunk-maker))
|
|
|
|
; : (response -> doesn't) -> void
|
|
; to report exceptions that occur later to the browser
|
|
; this must be called at the begining of a servlet
|
|
(define (report-errors-to-browser send/finish-or-back)
|
|
(uncaught-exception-handler
|
|
(lambda (exn)
|
|
(send/finish-or-back
|
|
`(html (head (title "Servlet Error"))
|
|
(body ([bgcolor "white"])
|
|
(p "The following error occured: "
|
|
(pre ,(exn->string exn)))))))))
|
|
|
|
; Authentication
|
|
|
|
(define AUTHENTICATION-REGEXP (regexp "([^:]*):(.*)"))
|
|
(define (match-authentication x) (regexp-match AUTHENTICATION-REGEXP x))
|
|
;:(define match-authentication (type: (str -> (or/c false (list str str str)))))
|
|
|
|
; extract-user-pass : (listof (cons sym bytes)) -> (or/c #f (cons str str))
|
|
;; Notes (GregP)
|
|
;; 1. This is Basic Authentication (RFC 1945 SECTION 11.1)
|
|
;; e.g. an authorization header will look like this:
|
|
;; Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
|
|
;; 2. Headers should be read as bytes and then translated to unicode as appropriate.
|
|
;; 3. The Authorization header should have bytes (i.e. (cdr pass-pair) is bytes
|
|
(define (extract-user-pass headers)
|
|
(match (headers-assq* #"Authorization" headers)
|
|
[#f #f]
|
|
[(struct header (_ basic-credentials))
|
|
(cond
|
|
[(and (basic? basic-credentials)
|
|
(match-authentication
|
|
(base64-decode (subbytes basic-credentials 6 (bytes-length basic-credentials)))))
|
|
=> (lambda (user-pass)
|
|
(cons (cadr user-pass) (caddr user-pass)))]
|
|
[else #f])]))
|
|
|
|
;; basic?: bytes -> (or/c (listof bytes) #f)
|
|
;; does the second part of the authorization header start with #"Basic "
|
|
(define basic?
|
|
(let ([rx (byte-regexp #"^Basic .*")])
|
|
(lambda (a) (regexp-match rx a))))
|
|
|
|
|
|
|
|
(provide ; all-from
|
|
(rename uri-decode translate-escapes))
|
|
(provide/contract
|
|
[get-host (url? (listof header?) . -> . symbol?)]
|
|
; XXX contract maybe
|
|
[extract-user-pass ((listof header?) . -> . (or/c false/c (cons/c bytes? bytes?)))]
|
|
[build-suspender (((listof xexpr?) (listof xexpr?))
|
|
((listof (list/c symbol? string?)) (listof (list/c symbol? string?)))
|
|
. opt-> .
|
|
(k-url? . -> . xexpr?))]
|
|
[make-html-response/incremental (((string? . -> . void) . -> . void) . -> . response/incremental?)]
|
|
[report-errors-to-browser ((servlet-response? . -> . void) . -> . void)]
|
|
[redirect-to ((string?) (redirection-status?) . opt-> . response/full?)]
|
|
[permanently redirection-status?]
|
|
[temporarily redirection-status?]
|
|
[see-other redirection-status?]
|
|
[request-bindings (request? . -> . (listof (or/c (cons/c symbol? string?)
|
|
(cons/c symbol? bytes?))))]
|
|
[request-headers (request? . -> . (listof (cons/c symbol? string?)))])) |