#lang racket/base (require "uri-codec.rkt") (provide ;; -- exceptions raised -- (struct-out cgi-error) (struct-out incomplete-%-suffix) (struct-out invalid-%-suffix) ;; -- cgi methods -- get-bindings get-bindings/post get-bindings/get output-http-headers generate-html-output generate-error-output bindings-as-html extract-bindings extract-binding/single get-cgi-method ;; -- general HTML utilities -- string->html generate-link-text) ;; type bindings = list ((symbol . string)) ;; -------------------------------------------------------------------- ;; Exceptions: (define-struct cgi-error ()) ;; chars : list (char) ;; -- gives the suffix which is invalid, not including the `%' (define-struct (incomplete-%-suffix cgi-error) (chars)) ;; char : char ;; -- an invalid character in a hex string (define-struct (invalid-%-suffix cgi-error) (char)) ;; -------------------------------------------------------------------- ;; query-string->string : string -> string ;; -- The input is the string post-processed as per Web specs, which ;; is as follows: ;; spaces are turned into "+"es and lots of things are turned into %XX, where ;; XX are hex digits, eg, %E7 for ~. The output is a regular Scheme string ;; with all the characters converted back. (define query-string->string form-urlencoded-decode) ;; string->html : string -> string ;; -- the input is raw text, the output is HTML appropriately quoted (define (string->html s) (apply string-append (map (lambda (c) (case c [(#\<) "<"] [(#\>) ">"] [(#\&) "&"] [else (string c)])) (string->list s)))) (define default-text-color "#000000") (define default-bg-color "#ffffff") (define default-link-color "#cc2200") (define default-vlink-color "#882200") (define default-alink-color "#444444") ;; generate-html-output : ;; html-string x list (html-string) x ... -> () (define (generate-html-output title body-lines [text-color default-text-color] [bg-color default-bg-color] [link-color default-link-color] [vlink-color default-vlink-color] [alink-color default-alink-color]) (let ([sa string-append]) (for ([l `("Content-type: text/html" "" "" "" "
" ,(sa ""
,@(map (lambda (bind)
(string-append (symbol->string (car bind))
" --> "
(cdr bind)
"
"))
bindings)
"
"))
;; extract-bindings : (string + symbol) x bindings -> list (string)
;; -- Extracts the bindings associated with a given name. The semantics of
;; forms states that a CHECKBOX may use the same NAME field multiple times.
;; Hence, a list of strings is returned. Note that the result may be the
;; empty list.
(define (extract-bindings field-name bindings)
(let ([field-name (if (symbol? field-name)
field-name (string->symbol field-name))])
(let loop ([found null] [bindings bindings])
(if (null? bindings)
found
(if (equal? field-name (caar bindings))
(loop (cons (cdar bindings) found) (cdr bindings))
(loop found (cdr bindings)))))))
;; extract-binding/single : (string + symbol) x bindings -> string
;; -- used in cases where only one binding is supposed to occur
(define (extract-binding/single field-name bindings)
(let* ([field-name (if (symbol? field-name)
field-name (string->symbol field-name))]
[result (extract-bindings field-name bindings)])
(cond
[(null? result)
(generate-error-output
(cons (format "No binding for field `~a':