diff --git a/Doc/lib/libcgi.tex b/Doc/lib/libcgi.tex index e90bf1cd8c9..285c08f66e4 100644 --- a/Doc/lib/libcgi.tex +++ b/Doc/lib/libcgi.tex @@ -45,7 +45,7 @@ telling the client what kind of data is following. Python code to generate a minimal header section looks like this: \begin{verbatim} -print "Content-type: text/html" # HTML is following +print "Content-Type: text/html" # HTML is following print # blank line, end of headers \end{verbatim} @@ -59,9 +59,6 @@ print "
name:", form["name"].value +print "
addr:", form["addr"].value ...further form processing here... \end{verbatim} Here the fields, accessed through \samp{form[\var{key}]}, are themselves instances of \class{FieldStorage} (or \class{MiniFieldStorage}, depending on the form encoding). +The \member{value} attribute of the instance yields the string value +of the field. The \function{getvalue()} method returns this string value +directly; it also accepts an optional second argument as a default to +return if the requested key is not present. If the submitted form data contains more than one field with the same name, the object retrieved by \samp{form[\var{key}]} is not a \class{FieldStorage} or \class{MiniFieldStorage} -instance but a list of such instances. If you expect this possibility +instance but a list of such instances. Similarly, in this situation, +\samp{form.getvalue(\var{key})} would return a list of strings. +If you expect this possibility (i.e., when your HTML form contains multiple fields with the same name), use the \function{type()} function to determine whether you have a single instance or a list of instances. For example, here's @@ -111,27 +122,21 @@ code that concatenates any number of username fields, separated by commas: \begin{verbatim} -username = form["username"] -if type(username) is type([]): +value = form.getvalue("username", "") +if type(value) is type([]): # Multiple username fields specified - usernames = "" - for item in username: - if usernames: - # Next item -- insert comma - usernames = usernames + "," + item.value - else: - # First item -- don't insert comma - usernames = item.value + usernames = ",".join(value) else: - # Single username field specified - usernames = username.value + # Single or no username field specified + usernames = value \end{verbatim} -If a field represents an uploaded file, the value attribute reads the +If a field represents an uploaded file, accessing the value via the +\member{value} attribute or the \function{getvalue()} method reads the entire file in memory as a string. This may not be what you want. -You can test for an uploaded file by testing either the filename -attribute or the file attribute. You can then read the data at -leisure from the file attribute: +You can test for an uploaded file by testing either the \member{filename} +attribute or the \member{file} attribute. You can then read the data at +leisure from the \member{file} attribute: \begin{verbatim} fileitem = form["userfile"] @@ -157,7 +162,8 @@ When a form is submitted in the ``old'' format (as the query string or as a single data part of type \mimetype{application/x-www-form-urlencoded}), the items will actually be instances of the class \class{MiniFieldStorage}. In this case, the -list, file and filename attributes are always \code{None}. +\member{list}, \member{file}, and \member{filename} attributes are +always \code{None}. \subsection{Old classes} @@ -233,23 +239,22 @@ exception. \begin{funcdesc}{parse_multipart}{fp, pdict} Parse input of type \mimetype{multipart/form-data} (for file uploads). Arguments are \var{fp} for the input file and -\var{pdict} for the dictionary containing other parameters of -\code{content-type} header +\var{pdict} for a dictionary containing other parameters in +the \code{Content-Type} header. Returns a dictionary just like \function{parse_qs()} keys are the field names, each value is a list of values for that field. This is easy to use but not much good if you are expecting megabytes to be uploaded --- in that case, use the \class{FieldStorage} class instead -which is much more flexible. Note that \code{content-type} is the -raw, unparsed contents of the \code{content-type} header. +which is much more flexible. Note that this does not parse nested multipart parts --- use \class{FieldStorage} for that. \end{funcdesc} \begin{funcdesc}{parse_header}{string} -Parse a header like \code{content-type} into a main -content-type and a dictionary of parameters. +Parse a MIME header (such as \code{Content-Type}) into a main +value and a dictionary of parameters. \end{funcdesc} \begin{funcdesc}{test}{} @@ -432,7 +437,7 @@ For example: \begin{verbatim} import sys import traceback -print "Content-type: text/html" +print "Content-Type: text/html" print sys.stderr = sys.stdout try: @@ -454,7 +459,7 @@ built-in modules): \begin{verbatim} import sys sys.stderr = sys.stdout -print "Content-type: text/plain" +print "Content-Type: text/plain" print ...your code here... \end{verbatim} diff --git a/Lib/cgi.py b/Lib/cgi.py index 0184472a531..2a214050c70 100755 --- a/Lib/cgi.py +++ b/Lib/cgi.py @@ -19,7 +19,7 @@ written in Python. # responsible for its maintenance. # -__version__ = "2.2" +__version__ = "2.3" # Imports @@ -31,6 +31,7 @@ import os import urllib import mimetools import rfc822 +import UserDict from StringIO import StringIO @@ -166,11 +167,10 @@ def parse_qs(qs, keep_blank_values=0, strict_parsing=0): """ dict = {} for name, value in parse_qsl(qs, keep_blank_values, strict_parsing): - if len(value) or keep_blank_values: - if dict.has_key(name): - dict[name].append(value) - else: - dict[name] = [value] + if dict.has_key(name): + dict[name].append(value) + else: + dict[name] = [value] return dict def parse_qsl(qs, keep_blank_values=0, strict_parsing=0): @@ -201,9 +201,10 @@ def parse_qsl(qs, keep_blank_values=0, strict_parsing=0): if strict_parsing: raise ValueError, "bad query field: %s" % `name_value` continue - name = urllib.unquote(string.replace(nv[0], '+', ' ')) - value = urllib.unquote(string.replace(nv[1], '+', ' ')) - r.append((name, value)) + if len(nv[1]) or keep_blank_values: + name = urllib.unquote(string.replace(nv[0], '+', ' ')) + value = urllib.unquote(string.replace(nv[1], '+', ' ')) + r.append((name, value)) return r @@ -537,6 +538,17 @@ class FieldStorage: else: return found + def getvalue(self, key, default=None): + """Dictionary style get() method, including 'value' lookup.""" + if self.has_key(key): + value = self[key] + if type(value) is type([]): + return map(lambda v: v.value, value) + else: + return value.value + else: + return default + def keys(self): """Dictionary style keys() method.""" if self.list is None: @@ -706,7 +718,7 @@ class FieldStorage: # Backwards Compatibility Classes # =============================== -class FormContentDict: +class FormContentDict(UserDict.UserDict): """Basic (multiple values per field) form content as dictionary. form = FormContentDict() @@ -720,20 +732,8 @@ class FormContentDict: """ def __init__(self, environ=os.environ): - self.dict = parse(environ=environ) + self.dict = self.data = parse(environ=environ) self.query_string = environ['QUERY_STRING'] - def __getitem__(self,key): - return self.dict[key] - def keys(self): - return self.dict.keys() - def has_key(self, key): - return self.dict.has_key(key) - def values(self): - return self.dict.values() - def items(self): - return self.dict.items() - def __len__( self ): - return len(self.dict) class SvFormContentDict(FormContentDict): diff --git a/Lib/test/test_cgi.py b/Lib/test/test_cgi.py index 61c3da78c82..29eb5a6c185 100644 --- a/Lib/test/test_cgi.py +++ b/Lib/test/test_cgi.py @@ -116,19 +116,27 @@ def main(): d = do_test(orig, "POST") assert d == expect, "Error parsing %s" % repr(orig) - d = {'QUERY_STRING': orig} - fcd = cgi.FormContentDict(d) - sd = cgi.SvFormContentDict(d) + env = {'QUERY_STRING': orig} + fcd = cgi.FormContentDict(env) + sd = cgi.SvFormContentDict(env) + fs = cgi.FieldStorage(environ=env) if type(expect) == type({}): # test dict interface assert len(expect) == len(fcd) assert norm(expect.keys()) == norm(fcd.keys()) assert norm(expect.values()) == norm(fcd.values()) assert norm(expect.items()) == norm(fcd.items()) + assert fcd.get("nonexistent field", "default") == "default" + assert len(sd) == len(fs) + assert norm(sd.keys()) == norm(fs.keys()) + assert fs.getvalue("nonexistent field", "default") == "default" + # test individual fields for key in expect.keys(): expect_val = expect[key] assert fcd.has_key(key) assert norm(fcd[key]) == norm(expect[key]) + assert fcd.get(key, "default") == fcd[key] + assert fs.has_key(key) if len(expect_val) > 1: single_value = 0 else: @@ -137,9 +145,11 @@ def main(): val = sd[key] except IndexError: assert not single_value + assert fs.getvalue(key) == expect_val else: assert single_value assert val == expect_val[0] + assert fs.getvalue(key) == expect_val[0] assert norm(sd.getlist(key)) == norm(expect_val) if single_value: assert norm(sd.values()) == \