From a5222b948186d80552ff70ee154c86719d1db758 Mon Sep 17 00:00:00 2001 From: Jon Zeppieri Date: Mon, 29 Aug 2011 21:16:03 -0400 Subject: [PATCH] Moved `net/cgi' code from unit to module. --- collects/net/cgi-sig.rkt | 1 - collects/net/cgi-unit.rkt | 209 +------------------------- collects/net/cgi.rkt | 227 ++++++++++++++++++++++++++++- collects/net/scribblings/cgi.scrbl | 4 + 4 files changed, 233 insertions(+), 208 deletions(-) diff --git a/collects/net/cgi-sig.rkt b/collects/net/cgi-sig.rkt index 8e54485ed5..6ec6aacadc 100644 --- a/collects/net/cgi-sig.rkt +++ b/collects/net/cgi-sig.rkt @@ -20,4 +20,3 @@ get-cgi-method ;; -- general HTML utilities -- string->html generate-link-text - diff --git a/collects/net/cgi-unit.rkt b/collects/net/cgi-unit.rkt index 295836e9de..ac90c64e84 100644 --- a/collects/net/cgi-unit.rkt +++ b/collects/net/cgi-unit.rkt @@ -1,207 +1,8 @@ -#lang racket/unit -(require "cgi-sig.rkt" "uri-codec.rkt") +#lang racket/base -(import) -(export cgi^) +(require racket/unit + "cgi-sig.rkt" "cgi.rkt") -;; type bindings = list ((symbol . string)) +(define-unit-from-context cgi@ cgi^) -;; -------------------------------------------------------------------- - -;; 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 "" title "") - "" - "" - ,(sa "") - "" - ,@body-lines - "" - "" - "")]) - (display l) - (newline)))) - -;; output-http-headers : -> void -(define (output-http-headers) - (printf "Content-type: text/html\r\n\r\n")) - -;; delimiter->predicate : symbol -> regexp -;; returns a regexp to read a chunk of text up to a delimiter (excluding it) -(define (delimiter->rx delimiter) - (case delimiter - [(amp) #rx#"^[^&]*"] - [(semi) #rx#"^[^;]*"] - [(amp-or-semi) #rx#"^[^&;]*"] - [else (error 'delimiter->rx - "internal-error, unknown delimiter: ~e" delimiter)])) - -;; get-bindings* : iport -> (listof (cons symbol string)) -;; Reads all bindings from the input port. The strings are processed to -;; remove the CGI spec "escape"s. -;; This code is _slightly_ lax: it allows an input to end in -;; (current-alist-separator-mode). It's not clear this is legal by the -;; CGI spec, which suggests that the last value binding must end in an -;; EOF. It doesn't look like this matters. -;; ELI: * Keeping this behavior for now, maybe better to remove it? -;; * Looks like `form-urlencoded->alist' is doing almost exactly -;; the same job this code does. -(define (get-bindings* method ip) - (define (err fmt . xs) - (generate-error-output - (list (format "Server generated malformed input for ~a method:" method) - (apply format fmt xs)))) - (define value-rx (delimiter->rx (current-alist-separator-mode))) - (define (process str) (query-string->string (bytes->string/utf-8 str))) - (let loop ([bindings '()]) - (if (eof-object? (peek-char ip)) - (reverse bindings) - (let () - (define name (car (or (regexp-match #rx"^[^=]+" ip) - (err "Missing field name before `='")))) - (unless (eq? #\= (read-char ip)) - (err "No binding for `~a' field." name)) - (define value (car (regexp-match value-rx ip))) - (read-char ip) ; consume the delimiter, possibly eof (retested above) - (loop (cons (cons (string->symbol (process name)) (process value)) - bindings)))))) - -;; get-bindings/post : () -> bindings -(define (get-bindings/post) - (get-bindings* "POST" (current-input-port))) - -;; get-bindings/get : () -> bindings -(define (get-bindings/get) - (get-bindings* "GET" (open-input-string (getenv "QUERY_STRING")))) - -;; get-bindings : () -> bindings -(define (get-bindings) - (if (string=? (get-cgi-method) "POST") - (get-bindings/post) - (get-bindings/get))) - -;; generate-error-output : list (html-string) -> -(define (generate-error-output error-message-lines) - (generate-html-output "Internal Error" error-message-lines) - (exit)) - -;; bindings-as-html : bindings -> list (html-string) -;; -- formats name-value bindings as HTML appropriate for displaying -(define (bindings-as-html bindings) - `("" - ,@(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':
" field-name) - (bindings-as-html bindings)))] - [(null? (cdr result)) - (car result)] - [else - (generate-error-output - (cons (format "Multiple bindings for field `~a' where one expected:
" - field-name) - (bindings-as-html bindings)))]))) - -;; get-cgi-method : () -> string -;; -- string is either GET or POST (though future extension is possible) -(define (get-cgi-method) - (or (getenv "REQUEST_METHOD") - (error 'get-cgi-method "no REQUEST_METHOD environment variable"))) - -;; generate-link-text : string x html-string -> html-string -(define (generate-link-text url anchor-text) - (string-append "" anchor-text "")) +(provide cgi@) diff --git a/collects/net/cgi.rkt b/collects/net/cgi.rkt index b848d16f0e..9612982942 100644 --- a/collects/net/cgi.rkt +++ b/collects/net/cgi.rkt @@ -1,6 +1,227 @@ #lang racket/base -(require racket/unit "cgi-sig.rkt" "cgi-unit.rkt") -(define-values/invoke-unit/infer cgi@) +(require "uri-codec.rkt") -(provide-signature-elements cgi^) +(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 "" title "") + "" + "" + ,(sa "") + "" + ,@body-lines + "" + "" + "")]) + (display l) + (newline)))) + +;; output-http-headers : -> void +(define (output-http-headers) + (printf "Content-type: text/html\r\n\r\n")) + +;; delimiter->predicate : symbol -> regexp +;; returns a regexp to read a chunk of text up to a delimiter (excluding it) +(define (delimiter->rx delimiter) + (case delimiter + [(amp) #rx#"^[^&]*"] + [(semi) #rx#"^[^;]*"] + [(amp-or-semi) #rx#"^[^&;]*"] + [else (error 'delimiter->rx + "internal-error, unknown delimiter: ~e" delimiter)])) + +;; get-bindings* : iport -> (listof (cons symbol string)) +;; Reads all bindings from the input port. The strings are processed to +;; remove the CGI spec "escape"s. +;; This code is _slightly_ lax: it allows an input to end in +;; (current-alist-separator-mode). It's not clear this is legal by the +;; CGI spec, which suggests that the last value binding must end in an +;; EOF. It doesn't look like this matters. +;; ELI: * Keeping this behavior for now, maybe better to remove it? +;; * Looks like `form-urlencoded->alist' is doing almost exactly +;; the same job this code does. +(define (get-bindings* method ip) + (define (err fmt . xs) + (generate-error-output + (list (format "Server generated malformed input for ~a method:" method) + (apply format fmt xs)))) + (define value-rx (delimiter->rx (current-alist-separator-mode))) + (define (process str) (query-string->string (bytes->string/utf-8 str))) + (let loop ([bindings '()]) + (if (eof-object? (peek-char ip)) + (reverse bindings) + (let () + (define name (car (or (regexp-match #rx"^[^=]+" ip) + (err "Missing field name before `='")))) + (unless (eq? #\= (read-char ip)) + (err "No binding for `~a' field." name)) + (define value (car (regexp-match value-rx ip))) + (read-char ip) ; consume the delimiter, possibly eof (retested above) + (loop (cons (cons (string->symbol (process name)) (process value)) + bindings)))))) + +;; get-bindings/post : () -> bindings +(define (get-bindings/post) + (get-bindings* "POST" (current-input-port))) + +;; get-bindings/get : () -> bindings +(define (get-bindings/get) + (get-bindings* "GET" (open-input-string (getenv "QUERY_STRING")))) + +;; get-bindings : () -> bindings +(define (get-bindings) + (if (string=? (get-cgi-method) "POST") + (get-bindings/post) + (get-bindings/get))) + +;; generate-error-output : list (html-string) -> +(define (generate-error-output error-message-lines) + (generate-html-output "Internal Error" error-message-lines) + (exit)) + +;; bindings-as-html : bindings -> list (html-string) +;; -- formats name-value bindings as HTML appropriate for displaying +(define (bindings-as-html bindings) + `("" + ,@(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':
" field-name) + (bindings-as-html bindings)))] + [(null? (cdr result)) + (car result)] + [else + (generate-error-output + (cons (format "Multiple bindings for field `~a' where one expected:
" + field-name) + (bindings-as-html bindings)))]))) + +;; get-cgi-method : () -> string +;; -- string is either GET or POST (though future extension is possible) +(define (get-cgi-method) + (or (getenv "REQUEST_METHOD") + (error 'get-cgi-method "no REQUEST_METHOD environment variable"))) + +;; generate-link-text : string x html-string -> html-string +(define (generate-link-text url anchor-text) + (string-append "" anchor-text "")) diff --git a/collects/net/scribblings/cgi.scrbl b/collects/net/scribblings/cgi.scrbl index 562d659d92..169ccef9b0 100644 --- a/collects/net/scribblings/cgi.scrbl +++ b/collects/net/scribblings/cgi.scrbl @@ -140,6 +140,10 @@ query is invalid.} @section{CGI Unit} +@margin-note{@racket[cgi@] and @racket[cgi^] are deprecated. +They exist for backward-compatibility and will likely be removed in +the future. New code should use the @racketmodname[net/cgi] module.} + @defmodule[net/cgi-unit] @defthing[cgi@ unit?]{