tag, use urllib.quote().
+
+log(fmt, ...): write a line to a log file; see docs for initlog().
+
+
+Caring about security
+---------------------
+
+There's one important rule: if you invoke an external program (e.g.
+via the os.system() or os.popen() functions), make very sure you don't
+pass arbitrary strings received from the client to the shell. This is
+a well-known security hole whereby clever hackers anywhere on the web
+can exploit a gullible CGI script to invoke arbitrary shell commands.
+Even parts of the URL or field names cannot be trusted, since the
+request doesn't have to come from your form!
+
+To be on the safe side, if you must pass a string gotten from a form
+to a shell command, you should make sure the string contains only
+alphanumeric characters, dashes, underscores, and periods.
+
+
+Installing your CGI script on a Unix system
+-------------------------------------------
+
+Read the documentation for your HTTP server and check with your local
+system administrator to find the directory where CGI scripts should be
+installed; usually this is in a directory cgi-bin in the server tree.
+
+Make sure that your script is readable and executable by "others"; the
+Unix file mode should be 755 (use "chmod 755 filename"). Make sure
+that the first line of the script contains #! starting in column 1
+followed by the pathname of the Python interpreter, for instance:
+
+ #! /usr/local/bin/python
+
+Make sure the Python interpreter exists and is executable by "others".
+
+Note that it's probably not a good idea to use #! /usr/bin/env python
+here, since the Python interpreter may not be on the default path
+given to CGI scripts!!!
+
+Make sure that any files your script needs to read or write are
+readable or writable, respectively, by "others" -- their mode should
+be 644 for readable and 666 for writable. This is because, for
+security reasons, the HTTP server executes your script as user
+"nobody", without any special privileges. It can only read (write,
+execute) files that everybody can read (write, execute). The current
+directory at execution time is also different (it is usually the
+server's cgi-bin directory) and the set of environment variables is
+also different from what you get at login. in particular, don't count
+on the shell's search path for executables ($PATH) or the Python
+module search path ($PYTHONPATH) to be set to anything interesting.
+
+If you need to load modules from a directory which is not on Python's
+default module search path, you can change the path in your script,
+before importing other modules, e.g.:
+
+ import sys
+ sys.path.insert(0, "/usr/home/joe/lib/python")
+ sys.path.insert(0, "/usr/local/lib/python")
+
+This way, the directory inserted last will be searched first!
+
+Instructions for non-Unix systems will vary; check your HTTP server's
+documentation (it will usually have a section on CGI scripts).
+
+
+Testing your CGI script
+-----------------------
+
+Unfortunately, a CGI script will generally not run when you try it
+from the command line, and a script that works perfectly from the
+command line may fail mysteriously when run from the server. There's
+one reason why you should still test your script from the command
+line: if it contains a syntax error, the python interpreter won't
+execute it at all, and the HTTP server will most likely send a cryptic
+error to the client.
+
+Assuming your script has no syntax errors, yet it does not work, you
+have no choice but to read the next section:
+
+
+Debugging CGI scripts
+---------------------
+
+First of all, check for trivial installation errors -- reading the
+section above on installing your CGI script carefully can save you a
+lot of time. If you wonder whether you have understood the
+installation procedure correctly, try installing a copy of this module
+file (cgi.py) as a CGI script. When invoked as a script, the file
+will dump its environment and the contents of the form in HTML form.
+Give it the right mode etc, and send it a request. If it's installed
+in the standard cgi-bin directory, it should be possible to send it a
+request by entering a URL into your browser of the form:
+
+ http://yourhostname/cgi-bin/cgi.py?name=Joe+Blow&addr=At+Home
+
+If this gives an error of type 404, the server cannot find the script
+-- perhaps you need to install it in a different directory. If it
+gives another error (e.g. 500), there's an installation problem that
+you should fix before trying to go any further. If you get a nicely
+formatted listing of the environment and form content (in this
+example, the fields should be listed as "addr" with value "At Home"
+and "name" with value "Joe Blow"), the cgi.py script has been
+installed correctly. If you follow the same procedure for your own
+script, you should now be able to debug it.
+
+The next step could be to call the cgi module's test() function from
+your script: replace its main code with the single statement
+
+ cgi.test()
+
+This should produce the same results as those gotten from installing
+the cgi.py file itself.
+
+When an ordinary Python script raises an unhandled exception (e.g.,
+because of a typo in a module name, a file that can't be opened,
+etc.), the Python interpreter prints a nice traceback and exits.
+While the Python interpreter will still do this when your CGI script
+raises an exception, most likely the traceback will end up in one of
+the HTTP server's log file, or be discarded altogether.
+
+Fortunately, once you have managed to get your script to execute
+*some* code, it is easy to catch exceptions and cause a traceback to
+be printed. The test() function below in this module is an example.
+Here are the rules:
+
+ 1. Import the traceback module (before entering the
+ try-except!)
+
+ 2. Make sure you finish printing the headers and the blank
+ line early
+
+ 3. Assign sys.stderr to sys.stdout
+
+ 3. Wrap all remaining code in a try-except statement
+
+ 4. In the except clause, call traceback.print_exc()
+
+For example:
+
+ import sys
+ import traceback
+ print "Content-type: text/html"
+ print
+ sys.stderr = sys.stdout
+ try:
+ ...your code here...
+ except:
+ print "\n\n"
+ traceback.print_exc()
+
+Notes: The assignment to sys.stderr is needed because the traceback
+prints to sys.stderr. The print "\n\n" statement is necessary to
+disable the word wrapping in HTML.
+
+If you suspect that there may be a problem in importing the traceback
+module, you can use an even more robust approach (which only uses
+built-in modules):
+
+ import sys
+ sys.stderr = sys.stdout
+ print "Content-type: text/plain"
+ print
+ ...your code here...
+
+This relies on the Python interpreter to print the traceback. The
+content type of the output is set to plain text, which disables all
+HTML processing. If your script works, the raw HTML will be displayed
+by your client. If it raises an exception, most likely after the
+first two lines have been printed, a traceback will be displayed.
+Because no HTML interpretation is going on, the traceback will
+readable.
+
+When all else fails, you may want to insert calls to log() to your
+program or even to a copy of the cgi.py file. Note that this requires
+you to set cgi.logfile to the name of a world-writable file before the
+first call to log() is made!
+
+Good luck!
+
+
+Common problems and solutions
+-----------------------------
+
+- Most HTTP servers buffer the output from CGI scripts until the
+script is completed. This means that it is not possible to display a
+progress report on the client's display while the script is running.
+
+- Check the installation instructions above.
+
+- Check the HTTP server's log files. ("tail -f logfile" in a separate
+window may be useful!)
+
+- Always check a script for syntax errors first, by doing something
+like "python script.py".
+
+- When using any of the debugging techniques, don't forget to add
+"import sys" to the top of the script.
+
+- When invoking external programs, make sure they can be found.
+Usually, this means using absolute path names -- $PATH is usually not
+set to a very useful value in a CGI script.
+
+- When reading or writing external files, make sure they can be read
+or written by every user on the system.
+
+- Don't try to give a CGI script a set-uid mode. This doesn't work on
+most systems, and is a security liability as well.
+
+
+History
+-------
+
+Michael McLay started this module. Steve Majewski changed the
+interface to SvFormContentDict and FormContentDict. The multipart
+parsing was inspired by code submitted by Andreas Paepcke. Guido van
+Rossum rewrote, reformatted and documented the module and is currently
+responsible for its maintenance.
+
+
+XXX The module is getting pretty heavy with all those docstrings.
+Perhaps there should be a slimmed version that doesn't contain all those
+backwards compatible and debugging classes and functions?
+
+"""
+
+__version__ = "2.2"
+
+
+# Imports
+# =======
+
+import string
+import sys
+import os
+import urllib
+import mimetools
+import rfc822
+from StringIO import StringIO
+
+
+# Logging support
+# ===============
+
+logfile = "" # Filename to log to, if not empty
+logfp = None # File object to log to, if not None
+
+def initlog(*allargs):
+ """Write a log message, if there is a log file.
+
+ Even though this function is called initlog(), you should always
+ use log(); log is a variable that is set either to initlog
+ (initially), to dolog (once the log file has been opened), or to
+ nolog (when logging is disabled).
+
+ The first argument is a format string; the remaining arguments (if
+ any) are arguments to the % operator, so e.g.
+ log("%s: %s", "a", "b")
+ will write "a: b" to the log file, followed by a newline.
+
+ If the global logfp is not None, it should be a file object to
+ which log data is written.
+
+ If the global logfp is None, the global logfile may be a string
+ giving a filename to open, in append mode. This file should be
+ world writable!!! If the file can't be opened, logging is
+ silently disabled (since there is no safe place where we could
+ send an error message).
+
+ """
+ global logfp, log
+ if logfile and not logfp:
+ try:
+ logfp = open(logfile, "a")
+ except IOError:
+ pass
+ if not logfp:
+ log = nolog
+ else:
+ log = dolog
+ apply(log, allargs)
+
+def dolog(fmt, *args):
+ """Write a log message to the log file. See initlog() for docs."""
+ logfp.write(fmt%args + "\n")
+
+def nolog(*allargs):
+ """Dummy function, assigned to log when logging is disabled."""
+ pass
+
+log = initlog # The current logging function
+
+
+# Parsing functions
+# =================
+
+# Maximum input we will accept when REQUEST_METHOD is POST
+# 0 ==> unlimited input
+maxlen = 0
+
+def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
+ """Parse a query in the environment or from a file (default stdin)
+
+ Arguments, all optional:
+
+ fp : file pointer; default: sys.stdin
+
+ environ : environment dictionary; default: os.environ
+
+ keep_blank_values: flag indicating whether blank values in
+ URL encoded forms should be treated as blank strings.
+ A true value inicates that blanks should be retained as
+ blank strings. The default false value indicates that
+ blank values are to be ignored and treated as if they were
+ not included.
+
+ strict_parsing: flag indicating what to do with parsing errors.
+ If false (the default), errors are silently ignored.
+ If true, errors raise a ValueError exception.
+ """
+ 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, pdict)
+ elif ctype == 'application/x-www-form-urlencoded':
+ clength = string.atoi(environ['CONTENT_LENGTH'])
+ if maxlen and clength > maxlen:
+ raise ValueError, 'Maximum content length exceeded'
+ qs = fp.read(clength)
+ else:
+ qs = '' # Unknown content-type
+ if environ.has_key('QUERY_STRING'):
+ if qs: qs = qs + '&'
+ qs = qs + environ['QUERY_STRING']
+ elif sys.argv[1:]:
+ if qs: qs = qs + '&'
+ qs = qs + sys.argv[1]
+ 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, keep_blank_values, strict_parsing)
+
+
+def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
+ """Parse a query given as a string argument.
+
+ Arguments:
+
+ qs: URL-encoded query string to be parsed
+
+ keep_blank_values: flag indicating whether blank values in
+ URL encoded queries should be treated as blank strings.
+ A true value inicates that blanks should be retained as
+ blank strings. The default false value indicates that
+ blank values are to be ignored and treated as if they were
+ not included.
+
+ strict_parsing: flag indicating what to do with parsing errors.
+ If false (the default), errors are silently ignored.
+ If true, errors raise a ValueError exception.
+ """
+ name_value_pairs = string.splitfields(qs, '&')
+ dict = {}
+ for name_value in name_value_pairs:
+ nv = string.splitfields(name_value, '=')
+ if len(nv) != 2:
+ 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], '+', ' '))
+ if len(value) or keep_blank_values:
+ if dict.has_key (name):
+ dict[name].append(value)
+ else:
+ dict[name] = [value]
+ return dict
+
+
+def parse_multipart(fp, pdict):
+ """Parse multipart input.
+
+ Arguments:
+ fp : input file
+ 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. This is easy to use but not
+ much good if you are expecting megabytes to be uploaded -- in that case,
+ use the FieldStorage class instead which is much more flexible. Note
+ that content-type is the raw, unparsed contents of the content-type
+ header.
+
+ XXX This does not parse nested multipart parts -- use FieldStorage for
+ that.
+
+ XXX This should really be subsumed by FieldStorage altogether -- no
+ point in having two implementations of the same parsing algorithm.
+
+ """
+ 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:
+ if maxlen and bytes > maxlen:
+ raise ValueError, 'Maximum content length exceeded'
+ 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
+ lines.append(line)
+ # Done with part.
+ if data is None:
+ continue
+ if bytes < 0:
+ if lines:
+ # Strip final line terminator
+ line = lines[-1]
+ if line[-2:] == "\r\n":
+ line = line[:-2]
+ elif line[-1:] == "\n":
+ line = line[:-1]
+ lines[-1] = line
+ data = string.joinfields(lines, "")
+ 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 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
+
+
+# Classes for field storage
+# =========================
+
+class MiniFieldStorage:
+
+ """Like FieldStorage, for use when no file uploads are possible."""
+
+ # Dummy attributes
+ filename = None
+ list = None
+ type = None
+ file = None
+ type_options = {}
+ disposition = None
+ disposition_options = {}
+ headers = {}
+
+ def __init__(self, name, value):
+ """Constructor from field name and value."""
+ 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.
+
+ 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 file 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="",
+ environ=os.environ, keep_blank_values=0, strict_parsing=0):
+ """Constructor. Read multipart/* until last part.
+
+ Arguments, all optional:
+
+ fp : file pointer; default: sys.stdin
+ (not used when the request method is GET)
+
+ headers : header dictionary-like object; default:
+ taken from environ as per CGI spec
+
+ outerboundary : terminating multipart boundary
+ (for internal use only)
+
+ environ : environment dictionary; default: os.environ
+
+ keep_blank_values: flag indicating whether blank values in
+ URL encoded forms should be treated as blank strings.
+ A true value inicates that blanks should be retained as
+ blank strings. The default false value indicates that
+ blank values are to be ignored and treated as if they were
+ not included.
+
+ strict_parsing: flag indicating what to do with parsing errors.
+ If false (the default), errors are silently ignored.
+ If true, errors raise a ValueError exception.
+
+ """
+ method = 'GET'
+ self.keep_blank_values = keep_blank_values
+ self.strict_parsing = strict_parsing
+ if environ.has_key('REQUEST_METHOD'):
+ method = string.upper(environ['REQUEST_METHOD'])
+ if method == 'GET' or method == 'HEAD':
+ if environ.has_key('QUERY_STRING'):
+ qs = environ['QUERY_STRING']
+ elif sys.argv[1:]:
+ qs = sys.argv[1]
+ else:
+ qs = ""
+ fp = StringIO(qs)
+ if headers is None:
+ headers = {'content-type':
+ "application/x-www-form-urlencoded"}
+ if headers is None:
+ headers = {}
+ if method == 'POST':
+ # Set default content-type for POST to what's traditional
+ headers['content-type'] = "application/x-www-form-urlencoded"
+ 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
+ if maxlen and clen > maxlen:
+ raise ValueError, 'Maximum content length exceeded'
+ 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(environ, keep_blank_values, strict_parsing)
+ 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
+ if len(found) == 1:
+ return found[0]
+ else:
+ 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 has_key(self, key):
+ """Dictionary style has_key() method."""
+ if self.list is None:
+ raise TypeError, "not indexable"
+ for item in self.list:
+ if item.name == key: return 1
+ return 0
+
+ def __len__(self):
+ """Dictionary style len(x) support."""
+ return len(self.keys())
+
+ def read_urlencoded(self):
+ """Internal: read data in query string format."""
+ qs = self.fp.read(self.length)
+ dict = parse_qs(qs, self.keep_blank_values, self.strict_parsing)
+ self.list = []
+ for key, valuelist in dict.items():
+ for value in valuelist:
+ self.list.append(MiniFieldStorage(key, value))
+ self.skip_lines()
+
+ FieldStorageClass = None
+
+ def read_multi(self, environ, keep_blank_values, strict_parsing):
+ """Internal: read a part that is itself multipart."""
+ self.list = []
+ klass = self.FieldStorageClass or self.__class__
+ part = klass(self.fp, {}, self.innerboundary,
+ environ, keep_blank_values, strict_parsing)
+ # Throw first part away
+ while not part.done:
+ headers = rfc822.Message(self.fp)
+ part = klass(self.fp, headers, self.innerboundary,
+ environ, keep_blank_values, strict_parsing)
+ 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)
+ 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
+ odelim = delim
+ if line[-2:] == "\r\n":
+ delim = "\r\n"
+ line = line[:-2]
+ elif line[-1] == "\n":
+ delim = "\n"
+ line = line[:-1]
+ else:
+ delim = ""
+ self.file.write(odelim + line)
+
+ 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=None):
+ """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 unused -- the file is always opened
+ in binary mode.
+
+ This version opens a temporary file for reading and writing,
+ and immediately deletes (unlinks) it. The trick (on Unix!) is
+ that the file can still be used, but it can't be opened by
+ another process, and it will automatically be deleted when it
+ is closed or when the current process terminates.
+
+ If you want a more permanent file, you derive a class which
+ overrides this method. If you want a visible temporary file
+ that is nevertheless automatically deleted when the script
+ terminates, try defining a __del__ method in a derived class
+ which unlinks the temporary files you have created.
+
+ """
+ import tempfile
+ return tempfile.TemporaryFile("w+b")
+
+
+
+# Backwards Compatibility 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, environ=os.environ):
+ self.dict = 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):
+ """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
+
+
+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
+
+
+# Test/debug code
+# ===============
+
+def test(environ=os.environ):
+ """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:
+ form = FieldStorage() # Replace with other classes to test those
+ print_form(form)
+ print_environ(environ)
+ print_directory()
+ print_arguments()
+ print_environ_usage()
+ def f():
+ exec "testing print_exception() -- italics?"
+ def g(f=f):
+ f()
+ print "What follows is a test, not an actual exception:
"
+ g()
+ except:
+ print_exception()
+
+ # Second try with a small maxlen...
+ global maxlen
+ maxlen = 50
+ try:
+ form = FieldStorage() # Replace with other classes to test those
+ print_form(form)
+ print_environ(environ)
+ print_directory()
+ print_arguments()
+ print_environ_usage()
+ except:
+ print_exception()
+
+def print_exception(type=None, value=None, tb=None, limit=None):
+ if type is None:
+ type, value, tb = sys.exc_info()
+ import traceback
+ print
+ print "Traceback (innermost last):
"
+ list = traceback.format_tb(tb, limit) + \
+ traceback.format_exception_only(type, value)
+ print "%s%s
" % (
+ escape(string.join(list[:-1], "")),
+ escape(list[-1]),
+ )
+ del tb
+
+def print_environ(environ=os.environ):
+ """Dump the shell environment as HTML."""
+ keys = environ.keys()
+ keys.sort()
+ print
+ print "Shell Environment:
"
+ print "
"
+ for key in keys:
+ print "- ", escape(key), "
- ", escape(environ[key])
+ print "
"
+ print
+
+def print_form(form):
+ """Dump the contents of a form as HTML."""
+ keys = form.keys()
+ keys.sort()
+ print
+ print "Form Contents:
"
+ print ""
+ for key in keys:
+ print "- " + escape(key) + ":",
+ value = form[key]
+ print "" + escape(`type(value)`) + ""
+ print "
- " + escape(`value`)
+ print "
"
+ print
+
+def print_directory():
+ """Dump the current directory as HTML."""
+ print
+ print "Current Working Directory:
"
+ try:
+ pwd = os.getcwd()
+ except os.error, msg:
+ print "os.error:", escape(str(msg))
+ else:
+ print escape(pwd)
+ print
+
+def print_arguments():
+ print
+ print "Command Line Arguments:
"
+ print
+ print sys.argv
+ print
+
+def print_environ_usage():
+ """Dump a list of environment variables used by CGI as HTML."""
+ print """
+These environment variables could have been set:
+
+- AUTH_TYPE
+
- CONTENT_LENGTH
+
- CONTENT_TYPE
+
- DATE_GMT
+
- DATE_LOCAL
+
- DOCUMENT_NAME
+
- DOCUMENT_ROOT
+
- DOCUMENT_URI
+
- GATEWAY_INTERFACE
+
- LAST_MODIFIED
+
- PATH
+
- PATH_INFO
+
- PATH_TRANSLATED
+
- QUERY_STRING
+
- REMOTE_ADDR
+
- REMOTE_HOST
+
- REMOTE_IDENT
+
- REMOTE_USER
+
- REQUEST_METHOD
+
- SCRIPT_NAME
+
- SERVER_NAME
+
- SERVER_PORT
+
- SERVER_PROTOCOL
+
- SERVER_ROOT
+
- SERVER_SOFTWARE
+
+In addition, HTTP headers sent by the server may be passed in the
+environment as well. Here are some common variable names:
+
+- HTTP_ACCEPT
+
- HTTP_CONNECTION
+
- HTTP_HOST
+
- HTTP_PRAGMA
+
- HTTP_REFERER
+
- HTTP_USER_AGENT
+
+"""
+
+
+# Utilities
+# =========
+
+def escape(s, quote=None):
+ """Replace special characters '&', '<' and '>' by SGML entities."""
+ s = string.replace(s, "&", "&") # Must be done first!
+ s = string.replace(s, "<", "<")
+ s = string.replace(s, ">", ">",)
+ if quote:
+ s = string.replace(s, '"', """)
+ return s
+
+
+# Invoke mainline
+# ===============
+
+# Call test() when this file is run as a script (not imported as a module)
+if __name__ == '__main__':
+ test()
--
cgit v1.2.3-70-g09d2