diff options
author | Guido van Rossum <guido@python.org> | 1996-03-07 18:00:44 (GMT) |
---|---|---|
committer | Guido van Rossum <guido@python.org> | 1996-03-07 18:00:44 (GMT) |
commit | 7aee384dbc7b1ac40c10140ef32d869c501f5d23 (patch) | |
tree | a4547f25edeadcabd462953664811b7a50da975a /Lib/cgi.py | |
parent | a0e2422615c1826fdbe963f4116eb6b3edccb951 (diff) | |
download | cpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.zip cpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.tar.gz cpython-7aee384dbc7b1ac40c10140ef32d869c501f5d23.tar.bz2 |
Reformatted with 4-space indentation. Added some quick docs to the
FieldStorage class.
Diffstat (limited to 'Lib/cgi.py')
-rwxr-xr-x | Lib/cgi.py | 1181 |
1 files changed, 630 insertions, 551 deletions
@@ -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("&", "&", s) # Must be done first! - s = regsub.gsub("<", "<", s) - s = regsub.gsub(">", ">", s) - return s + """Replace special characters '&', '<' and '>' by SGML entities.""" + s = regsub.gsub("&", "&", s) # Must be done first! + s = regsub.gsub("<", "<", s) + s = regsub.gsub(">", ">", 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() |