summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1996-03-07 18:00:44 (GMT)
committerGuido van Rossum <guido@python.org>1996-03-07 18:00:44 (GMT)
commit7aee384dbc7b1ac40c10140ef32d869c501f5d23 (patch)
treea4547f25edeadcabd462953664811b7a50da975a
parenta0e2422615c1826fdbe963f4116eb6b3edccb951 (diff)
downloadcpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.zip
cpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.tar.gz
cpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.tar.bz2
Reformatted with 4-space indentation. Added some quick docs to the
FieldStorage class.
-rwxr-xr-xLib/cgi.py1181
1 files changed, 630 insertions, 551 deletions
diff --git a/Lib/cgi.py b/Lib/cgi.py
index fd4ce32..37deabd 100755
--- a/Lib/cgi.py
+++ b/Lib/cgi.py
@@ -2,8 +2,8 @@
"""Support module for CGI (Common Gateway Interface) scripts.
-This module defines a number of utilities for use by CGI scripts written in
-Python.
+This module defines a number of utilities for use by CGI scripts
+written in Python.
Introduction
@@ -78,10 +78,15 @@ If you have an input item of type "file" in your form and the client
supports file uploads, the value for that field, if present in the
form, is not a string but a tuple of (filename, content-type, data).
+A more flexible alternative to [Sv]FormContentDict is the class
+FieldStorage. See that class's doc string.
+
Overview of classes
-------------------
+FieldStorage: new more flexible class; described above.
+
SvFormContentDict: single value form content as dictionary; described
above.
@@ -352,155 +357,155 @@ environ = os.environ
# =================
def parse(fp=None):
- """Parse a query in the environment or from a file (default stdin)"""
- if not fp:
- fp = sys.stdin
- if not environ.has_key('REQUEST_METHOD'):
- environ['REQUEST_METHOD'] = 'GET' # For testing
- if environ['REQUEST_METHOD'] == 'POST':
- ctype, pdict = parse_header(environ['CONTENT_TYPE'])
- if ctype == 'multipart/form-data':
- return parse_multipart(fp, ctype, pdict)
- elif ctype == 'application/x-www-form-urlencoded':
- clength = string.atoi(environ['CONTENT_LENGTH'])
- qs = fp.read(clength)
- else:
- qs = '' # Bad content-type
- environ['QUERY_STRING'] = qs
- elif environ.has_key('QUERY_STRING'):
- qs = environ['QUERY_STRING']
+ """Parse a query in the environment or from a file (default stdin)"""
+ if not fp:
+ fp = sys.stdin
+ if not environ.has_key('REQUEST_METHOD'):
+ environ['REQUEST_METHOD'] = 'GET' # For testing stand-alone
+ if environ['REQUEST_METHOD'] == 'POST':
+ ctype, pdict = parse_header(environ['CONTENT_TYPE'])
+ if ctype == 'multipart/form-data':
+ return parse_multipart(fp, ctype, pdict)
+ elif ctype == 'application/x-www-form-urlencoded':
+ clength = string.atoi(environ['CONTENT_LENGTH'])
+ qs = fp.read(clength)
else:
- if sys.argv[1:]:
- qs = sys.argv[1]
- else:
- qs = ""
- environ['QUERY_STRING'] = qs
- return parse_qs(qs)
+ qs = '' # Bad content-type
+ environ['QUERY_STRING'] = qs # XXX Shouldn't, really
+ elif environ.has_key('QUERY_STRING'):
+ qs = environ['QUERY_STRING']
+ else:
+ if sys.argv[1:]:
+ qs = sys.argv[1]
+ else:
+ qs = ""
+ environ['QUERY_STRING'] = qs # XXX Shouldn't, really
+ return parse_qs(qs)
def parse_qs(qs):
- """Parse a query given as a string argument"""
- name_value_pairs = string.splitfields(qs, '&')
- dict = {}
- for name_value in name_value_pairs:
- nv = string.splitfields(name_value, '=')
- if len(nv) != 2:
- continue
- name = nv[0]
- value = urllib.unquote(regsub.gsub('+', ' ', nv[1]))
- if len(value):
- if dict.has_key (name):
- dict[name].append(value)
- else:
- dict[name] = [value]
- return dict
+ """Parse a query given as a string argument"""
+ name_value_pairs = string.splitfields(qs, '&')
+ dict = {}
+ for name_value in name_value_pairs:
+ nv = string.splitfields(name_value, '=')
+ if len(nv) != 2:
+ continue
+ name = nv[0]
+ value = urllib.unquote(regsub.gsub('+', ' ', nv[1]))
+ if len(value):
+ if dict.has_key (name):
+ dict[name].append(value)
+ else:
+ dict[name] = [value]
+ return dict
def parse_multipart(fp, ctype, pdict):
- """Parse multipart input.
-
- Arguments:
- fp : input file
- ctype: content-type
- pdict: dictionary containing other parameters of conten-type header
-
- Returns a dictionary just like parse_qs() (keys are the field
- names, each value is a list of values for that field) except
- that if the value was an uploaded file, it is a tuple of the
- form (filename, content-type, data). Note that content-type
- is the raw, unparsed contents of the content-type header.
-
- XXX Should we parse further when the content-type is
- multipart/*?
-
- """
- import mimetools
- if pdict.has_key('boundary'):
- boundary = pdict['boundary']
+ """Parse multipart input.
+
+ Arguments:
+ fp : input file
+ ctype: content-type
+ pdict: dictionary containing other parameters of conten-type header
+
+ Returns a dictionary just like parse_qs() (keys are the field
+ names, each value is a list of values for that field) except that
+ if the value was an uploaded file, it is a tuple of the form
+ (filename, content-type, data). Note that content-type is the
+ raw, unparsed contents of the content-type header.
+
+ XXX Should we parse further when the content-type is
+ multipart/*?
+
+ """
+ import mimetools
+ if pdict.has_key('boundary'):
+ boundary = pdict['boundary']
+ else:
+ boundary = ""
+ nextpart = "--" + boundary
+ lastpart = "--" + boundary + "--"
+ partdict = {}
+ terminator = ""
+
+ while terminator != lastpart:
+ bytes = -1
+ data = None
+ if terminator:
+ # At start of next part. Read headers first.
+ headers = mimetools.Message(fp)
+ clength = headers.getheader('content-length')
+ if clength:
+ try:
+ bytes = string.atoi(clength)
+ except string.atoi_error:
+ pass
+ if bytes > 0:
+ data = fp.read(bytes)
+ else:
+ data = ""
+ # Read lines until end of part.
+ lines = []
+ while 1:
+ line = fp.readline()
+ if not line:
+ terminator = lastpart # End outer loop
+ break
+ if line[:2] == "--":
+ terminator = string.strip(line)
+ if terminator in (nextpart, lastpart):
+ break
+ if line[-2:] == '\r\n':
+ line = line[:-2]
+ elif line[-1:] == '\n':
+ line = line[:-1]
+ lines.append(line)
+ # Done with part.
+ if data is None:
+ continue
+ if bytes < 0:
+ data = string.joinfields(lines, "\n")
+ line = headers['content-disposition']
+ if not line:
+ continue
+ key, params = parse_header(line)
+ if key != 'form-data':
+ continue
+ if params.has_key('name'):
+ name = params['name']
else:
- boundary = ""
- nextpart = "--" + boundary
- lastpart = "--" + boundary + "--"
- partdict = {}
- terminator = ""
-
- while terminator != lastpart:
- bytes = -1
- data = None
- if terminator:
- # At start of next part. Read headers first.
- headers = mimetools.Message(fp)
- clength = headers.getheader('content-length')
- if clength:
- try:
- bytes = string.atoi(clength)
- except string.atoi_error:
- pass
- if bytes > 0:
- data = fp.read(bytes)
- else:
- data = ""
- # Read lines until end of part.
- lines = []
- while 1:
- line = fp.readline()
- if not line:
- terminator = lastpart # End outer loop
- break
- if line[:2] == "--":
- terminator = string.strip(line)
- if terminator in (nextpart, lastpart):
- break
- if line[-2:] == '\r\n':
- line = line[:-2]
- elif line[-1:] == '\n':
- line = line[:-1]
- lines.append(line)
- # Done with part.
- if data is None:
- continue
- if bytes < 0:
- data = string.joinfields(lines, "\n")
- line = headers['content-disposition']
- if not line:
- continue
- key, params = parse_header(line)
- if key != 'form-data':
- continue
- if params.has_key('name'):
- name = params['name']
- else:
- continue
- if params.has_key('filename'):
- data = (params['filename'],
- headers.getheader('content-type'), data)
- if partdict.has_key(name):
- partdict[name].append(data)
- else:
- partdict[name] = [data]
-
- return partdict
+ continue
+ if params.has_key('filename'):
+ data = (params['filename'],
+ headers.getheader('content-type'), data)
+ if partdict.has_key(name):
+ partdict[name].append(data)
+ else:
+ partdict[name] = [data]
+
+ return partdict
def parse_header(line):
- """Parse a Content-type like header.
-
- Return the main content-type and a dictionary of options.
-
- """
- plist = map(string.strip, string.splitfields(line, ';'))
- key = string.lower(plist[0])
- del plist[0]
- pdict = {}
- for p in plist:
- i = string.find(p, '=')
- if i >= 0:
- name = string.lower(string.strip(p[:i]))
- value = string.strip(p[i+1:])
- if len(value) >= 2 and value[0] == value[-1] == '"':
- value = value[1:-1]
- pdict[name] = value
- return key, pdict
+ """Parse a Content-type like header.
+
+ Return the main content-type and a dictionary of options.
+
+ """
+ plist = map(string.strip, string.splitfields(line, ';'))
+ key = string.lower(plist[0])
+ del plist[0]
+ pdict = {}
+ for p in plist:
+ i = string.find(p, '=')
+ if i >= 0:
+ name = string.lower(string.strip(p[:i]))
+ value = string.strip(p[i+1:])
+ if len(value) >= 2 and value[0] == value[-1] == '"':
+ value = value[1:-1]
+ pdict[name] = value
+ return key, pdict
# Classes for field storage
@@ -508,444 +513,508 @@ def parse_header(line):
class MiniFieldStorage:
- """Internal: dummy FieldStorage, used with query string format."""
+ """Internal: dummy FieldStorage, used with query string format."""
- def __init__(self, name, value):
- """Constructor from field name and value."""
- self.name = name
- self.value = value
- from StringIO import StringIO
- self.filename = None
- self.list = None
- self.file = StringIO(value)
+ # Dummy attributes
+ filename = None
+ list = None
+ type = None
+ typ_options = {}
+ disposition = None
+ disposition_options = {}
+ headers = {}
- def __repr__(self):
- """Return printable representation."""
- return "MiniFieldStorage(%s, %s)" % (`self.name`,
- `self.value`)
+ def __init__(self, name, value):
+ """Constructor from field name and value."""
+ from StringIO import StringIO
+ self.name = name
+ self.value = value
+ self.file = StringIO(value)
+
+ def __repr__(self):
+ """Return printable representation."""
+ return "MiniFieldStorage(%s, %s)" % (`self.name`, `self.value`)
class FieldStorage:
- """Store a sequence of fields, reading multipart/form-data."""
-
- def __init__(self, fp=None, headers=None, outerboundary=""):
- """Constructor. Read multipart/* until last part."""
- method = None
- if environ.has_key('REQUEST_METHOD'):
- method = string.upper(environ['REQUEST_METHOD'])
- if not fp and method == 'GET':
- qs = None
- if environ.has_key('QUERY_STRING'):
- qs = environ['QUERY_STRING']
- from StringIO import StringIO
- fp = StringIO(qs or "")
- if headers is None:
- headers = {'content-type':
- "application/x-www-form-urlencoded"}
- if headers is None:
- headers = {}
- if environ.has_key('CONTENT_TYPE'):
- headers['content-type'] = environ['CONTENT_TYPE']
- if environ.has_key('CONTENT_LENGTH'):
- headers['content-length'] = environ['CONTENT_LENGTH']
- self.fp = fp or sys.stdin
- self.headers = headers
- self.outerboundary = outerboundary
-
- # Process content-disposition header
- cdisp, pdict = "", {}
- if self.headers.has_key('content-disposition'):
- cdisp, pdict = parse_header(self.headers['content-disposition'])
- self.disposition = cdisp
- self.disposition_options = pdict
- self.name = None
- if pdict.has_key('name'):
- self.name = pdict['name']
- self.filename = None
- if pdict.has_key('filename'):
- self.filename = pdict['filename']
-
- # Process content-type header
- ctype, pdict = "text/plain", {}
- if self.headers.has_key('content-type'):
- ctype, pdict = parse_header(self.headers['content-type'])
- self.type = ctype
- self.type_options = pdict
- self.innerboundary = ""
- if pdict.has_key('boundary'):
- self.innerboundary = pdict['boundary']
- clen = -1
- if self.headers.has_key('content-length'):
- try:
- clen = string.atoi(self.headers['content-length'])
- except:
- pass
- self.length = clen
-
- self.list = self.file = None
- self.done = 0
- self.lines = []
- if ctype == 'application/x-www-form-urlencoded':
- self.read_urlencoded()
- elif ctype[:10] == 'multipart/':
- self.read_multi()
- else:
- self.read_single()
-
- def __repr__(self):
- """Return a printable representation."""
- return "FieldStorage(%s, %s, %s)" % (
- `self.name`, `self.filename`, `self.value`)
-
- def __getattr__(self, name):
- if name != 'value':
- raise AttributeError, name
- if self.file:
- self.file.seek(0)
- value = self.file.read()
- self.file.seek(0)
- elif self.list is not None:
- value = self.list
- else:
- value = None
- return value
-
- def __getitem__(self, key):
- """Dictionary style indexing."""
- if self.list is None:
- raise TypeError, "not indexable"
- found = []
- for item in self.list:
- if item.name == key: found.append(item)
- if not found:
- raise KeyError, key
- return found
-
- def keys(self):
- """Dictionary style keys() method."""
- if self.list is None:
- raise TypeError, "not indexable"
- keys = []
- for item in self.list:
- if item.name not in keys: keys.append(item.name)
- return keys
-
- def read_urlencoded(self):
- """Internal: read data in query string format."""
- qs = self.fp.read(self.length)
- dict = parse_qs(qs)
- self.list = []
- for key, valuelist in dict.items():
- for value in valuelist:
- self.list.append(MiniFieldStorage(key, value))
- self.skip_lines()
-
- def read_multi(self):
- """Internal: read a part that is itself multipart."""
- import rfc822
- self.list = []
- part = self.__class__(self.fp, {}, self.innerboundary)
- # Throw first part away
- while not part.done:
- headers = rfc822.Message(self.fp)
- part = self.__class__(self.fp, headers, self.innerboundary)
- self.list.append(part)
- self.skip_lines()
-
- def read_single(self):
- """Internal: read an atomic part."""
- if self.length >= 0:
- self.read_binary()
- self.skip_lines()
- else:
- self.read_lines()
- self.file.seek(0)
-
- bufsize = 8*1024 # I/O buffering size for copy to file
-
- def read_binary(self):
- """Internal: read binary data."""
- self.file = self.make_file('b')
- todo = self.length
- if todo >= 0:
- while todo > 0:
- data = self.fp.read(min(todo, self.bufsize))
- if not data:
- self.done = -1
- break
- self.file.write(data)
- todo = todo - len(data)
-
- def read_lines(self):
- """Internal: read lines until EOF or outerboundary."""
- self.file = self.make_file('')
- if self.outerboundary:
- self.read_lines_to_outerboundary()
- else:
- self.read_lines_to_eof()
-
- def read_lines_to_eof(self):
- """Internal: read lines until EOF."""
- while 1:
- line = self.fp.readline()
- if not line:
- self.done = -1
- break
- self.lines.append(line)
- if line[-2:] == '\r\n':
- line = line[:-2] + '\n'
- self.file.write(line)
-
- def read_lines_to_outerboundary(self):
- """Internal: read lines until outerboundary."""
- next = "--" + self.outerboundary
- last = next + "--"
- delim = ""
- while 1:
- line = self.fp.readline()
- if not line:
- self.done = -1
- break
- self.lines.append(line)
- if line[:2] == "--":
- strippedline = string.strip(line)
- if strippedline == next:
- break
- if strippedline == last:
- self.done = 1
- break
- if line[-2:] == "\r\n":
- line = line[:-2]
- elif line[-1] == "\n":
- line = line[:-1]
- self.file.write(delim + line)
- delim = "\n"
-
- def skip_lines(self):
- """Internal: skip lines until outer boundary if defined."""
- if not self.outerboundary or self.done:
- return
- next = "--" + self.outerboundary
- last = next + "--"
- while 1:
- line = self.fp.readline()
- if not line:
- self.done = -1
- break
- self.lines.append(line)
- if line[:2] == "--":
- strippedline = string.strip(line)
- if strippedline == next:
- break
- if strippedline == last:
- self.done = 1
- break
-
- def make_file(self, binary):
- """Overridable: return a readable & writable file.
-
- The file will be used as follows:
- - data is written to it
- - seek(0)
- - data is read from it
-
- The 'binary' argument is 'b' if the file should be created in
- binary mode (on non-Unix systems), '' otherwise.
-
- The intention is that you can override this method to selectively
- create a real (temporary) file or use a memory file dependent on
- the perceived size of the file or the presence of a filename, etc.
-
- """
-
- # Prefer ArrayIO over StringIO, if it's available
- try:
- from ArrayIO import ArrayIO
- ioclass = ArrayIO
- except ImportError:
- from StringIO import StringIO
- ioclass = StringIO
- return ioclass()
+ """Store a sequence of fields, reading multipart/form-data.
+
+ This class provides naming, typing, files stored on disk, and
+ more. At the top level, it is accessible like a dictionary, whose
+ keys are the field names. (Note: None can occur as a field name.)
+ The items are either a Python list (if there's multiple values) or
+ another FieldStorage or MiniFieldStorage object. If it's a single
+ object, it has the following attributes:
+
+ name: the field name, if specified; otherwise None
+
+ filename: the filename, if specified; otherwise None; this is the
+ client side filename, *not* the file name on which it is
+ stored (that's a temporary you don't deal with)
+
+ value: the value as a *string*; for file uploads, this
+ transparently reads the file every time you request the value
+
+ file: the file(-like) object from which you can read the data;
+ None if the data is stored a simple string
+
+ type: the content-type, or None if not specified
+
+ type_options: dictionary of options specified on the content-type
+ line
+
+ disposition: content-disposition, or None if not specified
+
+ disposition_options: dictionary of corresponding options
+
+ headers: a dictionary(-like) object (sometimes rfc822.Message or a
+ subclass thereof) containing *all* headers
+
+ The class is subclassable, mostly for the purpose of overriding
+ the make_file() method, which is called internally to come up with
+ a file open for reading and writing. This makes it possible to
+ override the default choice of storing all files in a temporary
+ directory and unlinking them as soon as they have been opened.
+
+ """
+
+ def __init__(self, fp=None, headers=None, outerboundary=""):
+ """Constructor. Read multipart/* until last part.
+
+ Arguments, all optional:
+
+ fp : file pointer; default: sys.stdin
+
+ headers : header dictionary-like object; default:
+ taken from environ as per CGI spec
+
+ outerboundary : optional terminating multipart boundary
+ (for internal use only)
+
+ """
+ method = None
+ if environ.has_key('REQUEST_METHOD'):
+ method = string.upper(environ['REQUEST_METHOD'])
+ if not fp and method == 'GET':
+ qs = None
+ if environ.has_key('QUERY_STRING'):
+ qs = environ['QUERY_STRING']
+ from StringIO import StringIO
+ fp = StringIO(qs or "")
+ if headers is None:
+ headers = {'content-type':
+ "application/x-www-form-urlencoded"}
+ if headers is None:
+ headers = {}
+ if environ.has_key('CONTENT_TYPE'):
+ headers['content-type'] = environ['CONTENT_TYPE']
+ if environ.has_key('CONTENT_LENGTH'):
+ headers['content-length'] = environ['CONTENT_LENGTH']
+ self.fp = fp or sys.stdin
+ self.headers = headers
+ self.outerboundary = outerboundary
+
+ # Process content-disposition header
+ cdisp, pdict = "", {}
+ if self.headers.has_key('content-disposition'):
+ cdisp, pdict = parse_header(self.headers['content-disposition'])
+ self.disposition = cdisp
+ self.disposition_options = pdict
+ self.name = None
+ if pdict.has_key('name'):
+ self.name = pdict['name']
+ self.filename = None
+ if pdict.has_key('filename'):
+ self.filename = pdict['filename']
+
+ # Process content-type header
+ ctype, pdict = "text/plain", {}
+ if self.headers.has_key('content-type'):
+ ctype, pdict = parse_header(self.headers['content-type'])
+ self.type = ctype
+ self.type_options = pdict
+ self.innerboundary = ""
+ if pdict.has_key('boundary'):
+ self.innerboundary = pdict['boundary']
+ clen = -1
+ if self.headers.has_key('content-length'):
+ try:
+ clen = string.atoi(self.headers['content-length'])
+ except:
+ pass
+ self.length = clen
+
+ self.list = self.file = None
+ self.done = 0
+ self.lines = []
+ if ctype == 'application/x-www-form-urlencoded':
+ self.read_urlencoded()
+ elif ctype[:10] == 'multipart/':
+ self.read_multi()
+ else:
+ self.read_single()
+
+ def __repr__(self):
+ """Return a printable representation."""
+ return "FieldStorage(%s, %s, %s)" % (
+ `self.name`, `self.filename`, `self.value`)
+
+ def __getattr__(self, name):
+ if name != 'value':
+ raise AttributeError, name
+ if self.file:
+ self.file.seek(0)
+ value = self.file.read()
+ self.file.seek(0)
+ elif self.list is not None:
+ value = self.list
+ else:
+ value = None
+ return value
+
+ def __getitem__(self, key):
+ """Dictionary style indexing."""
+ if self.list is None:
+ raise TypeError, "not indexable"
+ found = []
+ for item in self.list:
+ if item.name == key: found.append(item)
+ if not found:
+ raise KeyError, key
+ return found
+
+ def keys(self):
+ """Dictionary style keys() method."""
+ if self.list is None:
+ raise TypeError, "not indexable"
+ keys = []
+ for item in self.list:
+ if item.name not in keys: keys.append(item.name)
+ return keys
+
+ def read_urlencoded(self):
+ """Internal: read data in query string format."""
+ qs = self.fp.read(self.length)
+ dict = parse_qs(qs)
+ self.list = []
+ for key, valuelist in dict.items():
+ for value in valuelist:
+ self.list.append(MiniFieldStorage(key, value))
+ self.skip_lines()
+
+ def read_multi(self):
+ """Internal: read a part that is itself multipart."""
+ import rfc822
+ self.list = []
+ part = self.__class__(self.fp, {}, self.innerboundary)
+ # Throw first part away
+ while not part.done:
+ headers = rfc822.Message(self.fp)
+ part = self.__class__(self.fp, headers, self.innerboundary)
+ self.list.append(part)
+ self.skip_lines()
+
+ def read_single(self):
+ """Internal: read an atomic part."""
+ if self.length >= 0:
+ self.read_binary()
+ self.skip_lines()
+ else:
+ self.read_lines()
+ self.file.seek(0)
+
+ bufsize = 8*1024 # I/O buffering size for copy to file
+
+ def read_binary(self):
+ """Internal: read binary data."""
+ self.file = self.make_file('b')
+ todo = self.length
+ if todo >= 0:
+ while todo > 0:
+ data = self.fp.read(min(todo, self.bufsize))
+ if not data:
+ self.done = -1
+ break
+ self.file.write(data)
+ todo = todo - len(data)
+
+ def read_lines(self):
+ """Internal: read lines until EOF or outerboundary."""
+ self.file = self.make_file('')
+ if self.outerboundary:
+ self.read_lines_to_outerboundary()
+ else:
+ self.read_lines_to_eof()
+
+ def read_lines_to_eof(self):
+ """Internal: read lines until EOF."""
+ while 1:
+ line = self.fp.readline()
+ if not line:
+ self.done = -1
+ break
+ self.lines.append(line)
+ if line[-2:] == '\r\n':
+ line = line[:-2] + '\n'
+ self.file.write(line)
+
+ def read_lines_to_outerboundary(self):
+ """Internal: read lines until outerboundary."""
+ next = "--" + self.outerboundary
+ last = next + "--"
+ delim = ""
+ while 1:
+ line = self.fp.readline()
+ if not line:
+ self.done = -1
+ break
+ self.lines.append(line)
+ if line[:2] == "--":
+ strippedline = string.strip(line)
+ if strippedline == next:
+ break
+ if strippedline == last:
+ self.done = 1
+ break
+ if line[-2:] == "\r\n":
+ line = line[:-2]
+ elif line[-1] == "\n":
+ line = line[:-1]
+ self.file.write(delim + line)
+ delim = "\n"
+
+ def skip_lines(self):
+ """Internal: skip lines until outer boundary if defined."""
+ if not self.outerboundary or self.done:
+ return
+ next = "--" + self.outerboundary
+ last = next + "--"
+ while 1:
+ line = self.fp.readline()
+ if not line:
+ self.done = -1
+ break
+ self.lines.append(line)
+ if line[:2] == "--":
+ strippedline = string.strip(line)
+ if strippedline == next:
+ break
+ if strippedline == last:
+ self.done = 1
+ break
+
+ def make_file(self, binary):
+ """Overridable: return a readable & writable file.
+
+ The file will be used as follows:
+ - data is written to it
+ - seek(0)
+ - data is read from it
+
+ The 'binary' argument is 'b' if the file should be created in
+ binary mode (on non-Unix systems), '' otherwise.
+
+ The intention is that you can override this method to
+ selectively create a real (temporary) file or use a memory
+ file dependent on the perceived size of the file or the
+ presence of a filename, etc.
+
+ """
+
+ # Prefer ArrayIO over StringIO, if it's available
+ try:
+ from ArrayIO import ArrayIO
+ ioclass = ArrayIO
+ except ImportError:
+ from StringIO import StringIO
+ ioclass = StringIO
+ return ioclass()
# Main classes
# ============
class FormContentDict:
- """Basic (multiple values per field) form content as dictionary.
-
- form = FormContentDict()
-
- form[key] -> [value, value, ...]
- form.has_key(key) -> Boolean
- form.keys() -> [key, key, ...]
- form.values() -> [[val, val, ...], [val, val, ...], ...]
- form.items() -> [(key, [val, val, ...]), (key, [val, val, ...]), ...]
- form.dict == {key: [val, val, ...], ...}
-
- """
- def __init__( self ):
- self.dict = parse()
- 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)
+ """Basic (multiple values per field) form content as dictionary.
+
+ form = FormContentDict()
+
+ form[key] -> [value, value, ...]
+ form.has_key(key) -> Boolean
+ form.keys() -> [key, key, ...]
+ form.values() -> [[val, val, ...], [val, val, ...], ...]
+ form.items() -> [(key, [val, val, ...]), (key, [val, val, ...]), ...]
+ form.dict == {key: [val, val, ...], ...}
+
+ """
+ def __init__( self ):
+ self.dict = parse()
+ 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):
- """Strict single-value expecting form content as dictionary.
-
- IF you only expect a single value for each field, then
- form[key] will return that single value. It will raise an
- IndexError if that expectation is not true. IF you expect a
- field to have possible multiple values, than you can use
- form.getlist(key) to get all of the values. values() and
- items() are a compromise: they return single strings where
- there is a single value, and lists of strings otherwise.
-
- """
- def __getitem__(self, key):
- if len(self.dict[key]) > 1:
- raise IndexError, 'expecting a single value'
- return self.dict[key][0]
- def getlist(self, key):
- return self.dict[key]
- def values(self):
- lis = []
- for each in self.dict.values():
- if len( each ) == 1 :
- lis.append(each[0])
- else: lis.append(each)
- return lis
- def items(self):
- lis = []
- for key,value in self.dict.items():
- if len(value) == 1 :
- lis.append((key, value[0]))
- else: lis.append((key, value))
- return lis
+ """Strict single-value expecting form content as dictionary.
+
+ IF you only expect a single value for each field, then form[key]
+ will return that single value. It will raise an IndexError if
+ that expectation is not true. IF you expect a field to have
+ possible multiple values, than you can use form.getlist(key) to
+ get all of the values. values() and items() are a compromise:
+ they return single strings where there is a single value, and
+ lists of strings otherwise.
+
+ """
+ def __getitem__(self, key):
+ if len(self.dict[key]) > 1:
+ raise IndexError, 'expecting a single value'
+ return self.dict[key][0]
+ def getlist(self, key):
+ return self.dict[key]
+ def values(self):
+ lis = []
+ for each in self.dict.values():
+ if len( each ) == 1 :
+ lis.append(each[0])
+ else: lis.append(each)
+ return lis
+ def items(self):
+ lis = []
+ for key,value in self.dict.items():
+ if len(value) == 1 :
+ lis.append((key, value[0]))
+ else: lis.append((key, value))
+ return lis
class InterpFormContentDict(SvFormContentDict):
- """This class is present for backwards compatibility only."""
- def __getitem__( self, key ):
- v = SvFormContentDict.__getitem__( self, key )
- if v[0] in string.digits+'+-.' :
- try: return string.atoi( v )
- except ValueError:
- try: return string.atof( v )
- except ValueError: pass
- return string.strip(v)
- def values( self ):
- lis = []
- for key in self.keys():
- try:
- lis.append( self[key] )
- except IndexError:
- lis.append( self.dict[key] )
- return lis
- def items( self ):
- lis = []
- for key in self.keys():
- try:
- lis.append( (key, self[key]) )
- except IndexError:
- lis.append( (key, self.dict[key]) )
- return lis
+ """This class is present for backwards compatibility only."""
+ def __getitem__( self, key ):
+ v = SvFormContentDict.__getitem__( self, key )
+ if v[0] in string.digits+'+-.' :
+ try: return string.atoi( v )
+ except ValueError:
+ try: return string.atof( v )
+ except ValueError: pass
+ return string.strip(v)
+ def values( self ):
+ lis = []
+ for key in self.keys():
+ try:
+ lis.append( self[key] )
+ except IndexError:
+ lis.append( self.dict[key] )
+ return lis
+ def items( self ):
+ lis = []
+ for key in self.keys():
+ try:
+ lis.append( (key, self[key]) )
+ except IndexError:
+ lis.append( (key, self.dict[key]) )
+ return lis
class FormContent(FormContentDict):
- """This class is present for backwards compatibility only."""
- def values(self,key):
- if self.dict.has_key(key):return self.dict[key]
- else: return None
- def indexed_value(self,key, location):
- if self.dict.has_key(key):
- if len (self.dict[key]) > location:
- return self.dict[key][location]
- else: return None
- else: return None
- def value(self,key):
- if self.dict.has_key(key):return self.dict[key][0]
- else: return None
- def length(self,key):
- return len (self.dict[key])
- def stripped(self,key):
- if self.dict.has_key(key):return string.strip(self.dict[key][0])
- else: return None
- def pars(self):
- return self.dict
+ """This class is present for backwards compatibility only."""
+ def values(self,key):
+ if self.dict.has_key(key):return self.dict[key]
+ else: return None
+ def indexed_value(self,key, location):
+ if self.dict.has_key(key):
+ if len (self.dict[key]) > location:
+ return self.dict[key][location]
+ else: return None
+ else: return None
+ def value(self,key):
+ if self.dict.has_key(key):return self.dict[key][0]
+ else: return None
+ def length(self,key):
+ return len (self.dict[key])
+ def stripped(self,key):
+ if self.dict.has_key(key):return string.strip(self.dict[key][0])
+ else: return None
+ def pars(self):
+ return self.dict
# Test/debug code
# ===============
def test():
- """Robust test CGI script.
-
- Dump all information provided to the script in HTML form.
-
- """
- import traceback
- print "Content-type: text/html"
- print
- sys.stderr = sys.stdout
- try:
- print_environ()
- print_form(FieldStorage())
- print
- print "<H3>Current Working Directory:</H3>"
- try:
- pwd = os.getcwd()
- except os.error, msg:
- print "os.error:", escape(str(msg))
- else:
- print escape(pwd)
- print
- except:
- print "\n\n<PRE>" # Turn of word wrap
- traceback.print_exc()
+ """Robust test CGI script, usable as main program.
+
+ Write minimal HTTP headers and dump all information provided to
+ the script in HTML form.
+
+ """
+ import traceback
+ print "Content-type: text/html"
+ print
+ sys.stderr = sys.stdout
+ try:
+ print_form(FieldStorage())
+ print_environ()
+ print_directory()
+ print_environ_usage()
+ except:
+ print "\n\n<PRE>" # Turn off HTML word wrap
+ traceback.print_exc()
def print_environ():
- """Dump the shell environment in HTML form."""
- keys = environ.keys()
- keys.sort()
- print
- print "<H3>Shell environment:</H3>"
- print "<DL>"
- for key in keys:
- print "<DT>", escape(key), "<DD>", escape(environ[key])
- print "</DL>"
- print
+ """Dump the shell environment as HTML."""
+ keys = environ.keys()
+ keys.sort()
+ print
+ print "<H3>Shell environment:</H3>"
+ print "<DL>"
+ for key in keys:
+ print "<DT>", escape(key), "<DD>", escape(environ[key])
+ print "</DL>"
+ print
def print_form(form):
- """Dump the contents of a form in HTML form."""
- keys = form.keys()
- keys.sort()
- print
- print "<H3>Form contents:</H3>"
- print "<DL>"
- for key in keys:
- print "<DT>" + escape(key) + ":",
- value = form[key]
- print "<i>" + escape(`type(value)`) + "</i>"
- print "<DD>" + escape(`value`)
- print "</DL>"
- print
+ """Dump the contents of a form as HTML."""
+ keys = form.keys()
+ keys.sort()
+ print
+ print "<H3>Form contents:</H3>"
+ print "<DL>"
+ for key in keys:
+ print "<DT>" + escape(key) + ":",
+ value = form[key]
+ print "<i>" + escape(`type(value)`) + "</i>"
+ print "<DD>" + escape(`value`)
+ print "</DL>"
+ print
+
+def print_directory():
+ """Dump the current directory as HTML."""
+ print
+ print "<H3>Current Working Directory:</H3>"
+ try:
+ pwd = os.getcwd()
+ except os.error, msg:
+ print "os.error:", escape(str(msg))
+ else:
+ print escape(pwd)
+ print
def print_environ_usage():
- """Print a list of environment variables used by the CGI protocol."""
- print """
+ """Dump a list of environment variables used by CGI as HTML."""
+ print """
<H3>These environment variables could have been set:</H3>
<UL>
<LI>AUTH_TYPE
@@ -974,6 +1043,16 @@ def print_environ_usage():
<LI>SERVER_ROOT
<LI>SERVER_SOFTWARE
</UL>
+In addition, HTTP headers sent by the server may be passed in the
+environment as well. Here are some common variable names:
+<UL>
+<LI>HTTP_ACCEPT
+<LI>HTTP_CONNECTION
+<LI>HTTP_HOST
+<LI>HTTP_PRAGMA
+<LI>HTTP_REFERER
+<LI>HTTP_USER_AGENT
+</UL>
"""
@@ -981,11 +1060,11 @@ def print_environ_usage():
# =========
def escape(s):
- """Replace special characters '&', '<' and '>' by SGML entities."""
- s = regsub.gsub("&", "&amp;", s) # Must be done first!
- s = regsub.gsub("<", "&lt;", s)
- s = regsub.gsub(">", "&gt;", s)
- return s
+ """Replace special characters '&', '<' and '>' by SGML entities."""
+ s = regsub.gsub("&", "&amp;", s) # Must be done first!
+ s = regsub.gsub("<", "&lt;", s)
+ s = regsub.gsub(">", "&gt;", s)
+ return s
# Invoke mainline
@@ -993,4 +1072,4 @@ def escape(s):
# Call test() when this file is run as a script (not imported as a module)
if __name__ == '__main__':
- test()
+ test()