Prev Up Next
In the example above, the parameter's name or the
argument it assumed did not themselves contain any
`&' or `=' characters. In general, they may.
To accommodate such characters, and not have them be
mistaken for separators, the CGI argument-passing
mechanism treats all characters other than letters,
digits, and the underscore, as special, and
transmits them in an encoded form. A space is encoded
as a `+'. For other special characters, the
encoding is a three-character sequence, and consists of
`%' followed the special character's hexadecimal
code. Thus, the character sequence `20% + 30% =
50%, &c.' will be encoded as
20%25+%2b+30%25+%3d+50%25%2c+%26c%2e
(Space become `+'; `%' becomes `%25';
`+' becomes `%2b'; `=' becomes `%3d';
`,' becomes `%2c'; `&' becomes `%26';
and `.' becomes `%2e'.)
Instead of dealing anew with the task of getting and
decoding the form data in each CGI script, it is
convenient to collect some helpful procedures into a
library file
cgi.scm. testcgi2.scm can then be written
more compactly as
#!/bin/sh
":";exec /usr/local/bin/mzscheme -r $0 "$@"
;Load the cgi utilities
(load-relatve "cgi.scm")
(display "content-type: text/plain") (newline)
(newline)
;Read the data input via the form
(parse-form-data)
;Get the envvar parameter
(define envvar (form-data-get/1 "envvar"))
;Display the value of the envvar
(display envvar)
(display " = ")
(display (getenv envvar))
(newline)
This shorter CGI script uses two utility procedures
defined in cgi.scm. parse-form-data to read
the data supplied by the user via the form. The data
consists of parameters and their associated values.
form-data-get/1 finds the value associated with a
particular parameter.
cgi.scm defines a global table called
*form-data-table* to store form data.
;Load our table definitions
(load-relative "table.scm")
;Define the *form-data-table*
(define *form-data-table* (make-table 'equ string=?))
An advantage of using a general mechanism such
as the parse-form-data procedure is that we can
hide the details of what method (get or
put) was used.
(define parse-form-data
(lambda ()
((if (string-ci=? (or (getenv "REQUEST_METHOD") "GET") "GET")
parse-form-data-using-query-string
parse-form-data-using-stdin))))
The environment variable
REQUEST_METHOD tells which method was used to transmit
the form data. If the method is GET, then the form
data was sent as the string available via another
environment variable, QUERY_STRING. The auxiliary
procedure
parse-form-data-using-query-string is used to pick
apart QUERY_STRING:
(define parse-form-data-using-query-string
(lambda ()
(let ((query-string (or (getenv "QUERY_STRING") "")))
(for-each
(lambda (par=arg)
(let ((par/arg (split #\= par=arg)))
(let ((par (url-decode (car par/arg)))
(arg (url-decode (cadr par/arg))))
(table-put!
*form-data-table* par
(cons arg
(table-get *form-data-table* par '()))))))
(split #\& query-string)))))
The helper procedure split, and its helper
string-index, are defined as in sec 17.2.
As noted, the incoming form data is a sequence of
name-value pairs separated by &s. Within each
pair, the name comes first, followed by an =
character, followed by the value. Each name-value
combination is collected into a global table, the
*form-data-table*.
Both name and value are encoded, so we
need to decode them using the url-decode procedure
to get their actual representation.
(define url-decode
(lambda (s)
(let ((s (string->list s)))
(list->string
(let loop ((s s))
(if (null? s) '()
(let ((a (car s)) (d (cdr s)))
(case a
((#\+) (cons #\space (loop d)))
((#\%) (cons (hex->char (car d) (cadr d))
(loop (cddr d))))
(else (cons a (loop d)))))))))))
`+' is converted into space. A triliteral of the
form `%xy'
is converted, using the procedure hex->char into
the character whose
ascii encoding is the hex number `xy'.
(define hex->char
(lambda (x y)
(integer->char
(string->number (string x y) 16))))
We still need a form-data parser for the case where the
request method is POST. The auxiliary procedure
parse-form-data-using-stdin does this.
(define parse-form-data-using-stdin
(lambda ()
(let* ((content-length (getenv "CONTENT_LENGTH"))
(content-length (if content-length
(string->number content-length) 0))
(i 0))
(let par-loop ((par '()))
(let ((c (read-char)))
(set! i (+ i 1))
(if (or (> i content-length) (eof-object? c) (char=? c #\=))
(let arg-loop ((arg '()))
(let ((c (read-char)))
(set! i (+ i 1))
(if (or (> i content-length) (eof-object? c) (char=? c #\&))
(let ((par (url-decode (list->string (reverse! par))))
(arg (url-decode (list->string (reverse! arg)))))
(table-put! *form-data-table* par
(cons arg (table-get *form-data-table*
par '())))
(unless (or (> i content-length)
(eof-object? c))
(par-loop '())))
(arg-loop (cons c arg)))))
(par-loop (cons c par))))))))
The POST method sends form data via the script's
stdin. The number of characters sent is placed in
the environment variable CONTENT_LENGTH.
parse-form-data-using-stdin reads the required
number of characters from stdin, and populates the
*form-data-table* as before, making sure to decode
the parameters' names and values.
It remains to retrieve the values for specific
parameters from the *form-data-table*. Note that
the table associates a list with each parameter, in
order to accommodate the possibility of multiple values
for a parameter. form-data-get retrieves all the
values assigned to a parameter. If there is only one
value, it returns a singleton containing that value.
(define form-data-get
(lambda (k)
(table-get *form-data-table* k '())))
form-data-get/1 returns the first (or most
significant) value associated with a parameter.
(define form-data-get/1
(lambda (k . default)
(let ((vv (form-data-get k)))
(cond ((pair? vv) (car vv))
((pair? default) (car default))
(else "")))))
In our examples so far, the CGI script has generated
plain text. Generally, though, we will want to
generate an HTML page. It is not uncommon for a
combination of HTML form and CGI script to trigger a
series of HTML pages with forms. It is also common to
code all the action corresponding to these various
forms in a single CGI script. In any case, it is
helpful to have a utility procedure that writes out strings
in HTML format, ie, with the HTML special characters
encoded appropriately:
(define display-html
(lambda (s . o)
(let ((o (if (null? o) (current-output-port)
(car o))))
(let ((n (string-length s)))
(let loop ((i 0))
(unless (>= i n)
(let ((c (string-ref s i)))
(display
(case c
((#\<) "<")
((#\>) ">")
((#\") """)
((#\&) "&")
(else c)) o)
(loop (+ i 1)))))))))
Prev Up Next