Commit a1a4b591 authored by Moshe Zadka's avatar Moshe Zadka

Closing patch #101120 -- After everyone agreed.

parent dc3d606b
...@@ -45,7 +45,7 @@ telling the client what kind of data is following. Python code to ...@@ -45,7 +45,7 @@ telling the client what kind of data is following. Python code to
generate a minimal header section looks like this: generate a minimal header section looks like this:
\begin{verbatim} \begin{verbatim}
print "Content-type: text/html" # HTML is following print "Content-Type: text/html" # HTML is following
print # blank line, end of headers print # blank line, end of headers
\end{verbatim} \end{verbatim}
...@@ -59,9 +59,6 @@ print "<H1>This is my first CGI script</H1>" ...@@ -59,9 +59,6 @@ print "<H1>This is my first CGI script</H1>"
print "Hello, world!" print "Hello, world!"
\end{verbatim} \end{verbatim}
(It may not be fully legal HTML according to the letter of the
standard, but any browser will understand it.)
\subsection{Using the cgi module} \subsection{Using the cgi module}
\nodename{Using the cgi module} \nodename{Using the cgi module}
...@@ -77,9 +74,16 @@ value of various environment variables set according to the CGI ...@@ -77,9 +74,16 @@ value of various environment variables set according to the CGI
standard). Since it may consume standard input, it should be standard). Since it may consume standard input, it should be
instantiated only once. instantiated only once.
The \class{FieldStorage} instance can be accessed as if it were a Python The \class{FieldStorage} instance can be indexed like a Python
dictionary. For instance, the following code (which assumes that the dictionary, and also supports the standard dictionary methods
\code{content-type} header and blank line have already been printed) \function{has_key()} and \function{keys()}.
Form fields containing empty strings are ignored
and do not appear in the dictionary; to keep such values, provide
the optional \samp{keep_blank_values} argument when creating the
\class {FieldStorage} instance.
For instance, the following code (which assumes that the
\code{Content-Type} header and blank line have already been printed)
checks that the fields \code{name} and \code{addr} are both set to a checks that the fields \code{name} and \code{addr} are both set to a
non-empty string: non-empty string:
...@@ -87,23 +91,30 @@ non-empty string: ...@@ -87,23 +91,30 @@ non-empty string:
form = cgi.FieldStorage() form = cgi.FieldStorage()
form_ok = 0 form_ok = 0
if form.has_key("name") and form.has_key("addr"): if form.has_key("name") and form.has_key("addr"):
if form["name"].value != "" and form["addr"].value != "": form_ok = 1
form_ok = 1
if not form_ok: if not form_ok:
print "<H1>Error</H1>" print "<H1>Error</H1>"
print "Please fill in the name and addr fields." print "Please fill in the name and addr fields."
return return
print "<p>name:", form["name"].value
print "<p>addr:", form["addr"].value
...further form processing here... ...further form processing here...
\end{verbatim} \end{verbatim}
Here the fields, accessed through \samp{form[\var{key}]}, are Here the fields, accessed through \samp{form[\var{key}]}, are
themselves instances of \class{FieldStorage} (or themselves instances of \class{FieldStorage} (or
\class{MiniFieldStorage}, depending on the form encoding). \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 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 name, the object retrieved by \samp{form[\var{key}]} is not a
\class{FieldStorage} or \class{MiniFieldStorage} \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 (i.e., when your HTML form contains multiple fields with the same
name), use the \function{type()} function to determine whether you name), use the \function{type()} function to determine whether you
have a single instance or a list of instances. For example, here's 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 ...@@ -111,27 +122,21 @@ code that concatenates any number of username fields, separated by
commas: commas:
\begin{verbatim} \begin{verbatim}
username = form["username"] value = form.getvalue("username", "")
if type(username) is type([]): if type(value) is type([]):
# Multiple username fields specified # Multiple username fields specified
usernames = "" usernames = ",".join(value)
for item in username:
if usernames:
# Next item -- insert comma
usernames = usernames + "," + item.value
else:
# First item -- don't insert comma
usernames = item.value
else: else:
# Single username field specified # Single or no username field specified
usernames = username.value usernames = value
\end{verbatim} \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. 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 You can test for an uploaded file by testing either the \member{filename}
attribute or the file attribute. You can then read the data at attribute or the \member{file} attribute. You can then read the data at
leisure from the file attribute: leisure from the \member{file} attribute:
\begin{verbatim} \begin{verbatim}
fileitem = form["userfile"] fileitem = form["userfile"]
...@@ -157,7 +162,8 @@ When a form is submitted in the ``old'' format (as the query string or ...@@ -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 as a single data part of type
\mimetype{application/x-www-form-urlencoded}), the items will actually \mimetype{application/x-www-form-urlencoded}), the items will actually
be instances of the class \class{MiniFieldStorage}. In this case, the 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} \subsection{Old classes}
...@@ -233,23 +239,22 @@ exception. ...@@ -233,23 +239,22 @@ exception.
\begin{funcdesc}{parse_multipart}{fp, pdict} \begin{funcdesc}{parse_multipart}{fp, pdict}
Parse input of type \mimetype{multipart/form-data} (for Parse input of type \mimetype{multipart/form-data} (for
file uploads). Arguments are \var{fp} for the input file and file uploads). Arguments are \var{fp} for the input file and
\var{pdict} for the dictionary containing other parameters of \var{pdict} for a dictionary containing other parameters in
\code{content-type} header the \code{Content-Type} header.
Returns a dictionary just like \function{parse_qs()} keys are the 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 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 easy to use but not much good if you are expecting megabytes to be
uploaded --- in that case, use the \class{FieldStorage} class instead uploaded --- in that case, use the \class{FieldStorage} class instead
which is much more flexible. Note that \code{content-type} is the which is much more flexible.
raw, unparsed contents of the \code{content-type} header.
Note that this does not parse nested multipart parts --- use Note that this does not parse nested multipart parts --- use
\class{FieldStorage} for that. \class{FieldStorage} for that.
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{parse_header}{string} \begin{funcdesc}{parse_header}{string}
Parse a header like \code{content-type} into a main Parse a MIME header (such as \code{Content-Type}) into a main
content-type and a dictionary of parameters. value and a dictionary of parameters.
\end{funcdesc} \end{funcdesc}
\begin{funcdesc}{test}{} \begin{funcdesc}{test}{}
...@@ -432,7 +437,7 @@ For example: ...@@ -432,7 +437,7 @@ For example:
\begin{verbatim} \begin{verbatim}
import sys import sys
import traceback import traceback
print "Content-type: text/html" print "Content-Type: text/html"
print print
sys.stderr = sys.stdout sys.stderr = sys.stdout
try: try:
...@@ -454,7 +459,7 @@ built-in modules): ...@@ -454,7 +459,7 @@ built-in modules):
\begin{verbatim} \begin{verbatim}
import sys import sys
sys.stderr = sys.stdout sys.stderr = sys.stdout
print "Content-type: text/plain" print "Content-Type: text/plain"
print print
...your code here... ...your code here...
\end{verbatim} \end{verbatim}
......
...@@ -19,7 +19,7 @@ written in Python. ...@@ -19,7 +19,7 @@ written in Python.
# responsible for its maintenance. # responsible for its maintenance.
# #
__version__ = "2.2" __version__ = "2.3"
# Imports # Imports
...@@ -31,6 +31,7 @@ import os ...@@ -31,6 +31,7 @@ import os
import urllib import urllib
import mimetools import mimetools
import rfc822 import rfc822
import UserDict
from StringIO import StringIO from StringIO import StringIO
...@@ -166,11 +167,10 @@ def parse_qs(qs, keep_blank_values=0, strict_parsing=0): ...@@ -166,11 +167,10 @@ def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
""" """
dict = {} dict = {}
for name, value in parse_qsl(qs, keep_blank_values, strict_parsing): for name, value in parse_qsl(qs, keep_blank_values, strict_parsing):
if len(value) or keep_blank_values: if dict.has_key(name):
if dict.has_key(name): dict[name].append(value)
dict[name].append(value) else:
else: dict[name] = [value]
dict[name] = [value]
return dict return dict
def parse_qsl(qs, keep_blank_values=0, strict_parsing=0): 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): ...@@ -201,9 +201,10 @@ def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
if strict_parsing: if strict_parsing:
raise ValueError, "bad query field: %s" % `name_value` raise ValueError, "bad query field: %s" % `name_value`
continue continue
name = urllib.unquote(string.replace(nv[0], '+', ' ')) if len(nv[1]) or keep_blank_values:
value = urllib.unquote(string.replace(nv[1], '+', ' ')) name = urllib.unquote(string.replace(nv[0], '+', ' '))
r.append((name, value)) value = urllib.unquote(string.replace(nv[1], '+', ' '))
r.append((name, value))
return r return r
...@@ -537,6 +538,17 @@ class FieldStorage: ...@@ -537,6 +538,17 @@ class FieldStorage:
else: else:
return found 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): def keys(self):
"""Dictionary style keys() method.""" """Dictionary style keys() method."""
if self.list is None: if self.list is None:
...@@ -706,7 +718,7 @@ class FieldStorage: ...@@ -706,7 +718,7 @@ class FieldStorage:
# Backwards Compatibility Classes # Backwards Compatibility Classes
# =============================== # ===============================
class FormContentDict: class FormContentDict(UserDict.UserDict):
"""Basic (multiple values per field) form content as dictionary. """Basic (multiple values per field) form content as dictionary.
form = FormContentDict() form = FormContentDict()
...@@ -720,20 +732,8 @@ class FormContentDict: ...@@ -720,20 +732,8 @@ class FormContentDict:
""" """
def __init__(self, environ=os.environ): def __init__(self, environ=os.environ):
self.dict = parse(environ=environ) self.dict = self.data = parse(environ=environ)
self.query_string = environ['QUERY_STRING'] 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): class SvFormContentDict(FormContentDict):
......
...@@ -116,19 +116,27 @@ def main(): ...@@ -116,19 +116,27 @@ def main():
d = do_test(orig, "POST") d = do_test(orig, "POST")
assert d == expect, "Error parsing %s" % repr(orig) assert d == expect, "Error parsing %s" % repr(orig)
d = {'QUERY_STRING': orig} env = {'QUERY_STRING': orig}
fcd = cgi.FormContentDict(d) fcd = cgi.FormContentDict(env)
sd = cgi.SvFormContentDict(d) sd = cgi.SvFormContentDict(env)
fs = cgi.FieldStorage(environ=env)
if type(expect) == type({}): if type(expect) == type({}):
# test dict interface # test dict interface
assert len(expect) == len(fcd) assert len(expect) == len(fcd)
assert norm(expect.keys()) == norm(fcd.keys()) assert norm(expect.keys()) == norm(fcd.keys())
assert norm(expect.values()) == norm(fcd.values()) assert norm(expect.values()) == norm(fcd.values())
assert norm(expect.items()) == norm(fcd.items()) 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(): for key in expect.keys():
expect_val = expect[key] expect_val = expect[key]
assert fcd.has_key(key) assert fcd.has_key(key)
assert norm(fcd[key]) == norm(expect[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: if len(expect_val) > 1:
single_value = 0 single_value = 0
else: else:
...@@ -137,9 +145,11 @@ def main(): ...@@ -137,9 +145,11 @@ def main():
val = sd[key] val = sd[key]
except IndexError: except IndexError:
assert not single_value assert not single_value
assert fs.getvalue(key) == expect_val
else: else:
assert single_value assert single_value
assert val == expect_val[0] assert val == expect_val[0]
assert fs.getvalue(key) == expect_val[0]
assert norm(sd.getlist(key)) == norm(expect_val) assert norm(sd.getlist(key)) == norm(expect_val)
if single_value: if single_value:
assert norm(sd.values()) == \ assert norm(sd.values()) == \
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment