Bump version to 1.0.
[python/dscho.git] / Lib / cgi.py
blobdd5bee6a9d1a32bb1e3b3b0780c9436aafb227d3
1 #! /usr/local/bin/python
3 """Support module for CGI (Common Gateway Interface) scripts.
5 This module defines a number of utilities for use by CGI scripts
6 written in Python.
7 """
9 # XXX Perhaps there should be a slimmed version that doesn't contain
10 # all those backwards compatible and debugging classes and functions?
12 # History
13 # -------
15 # Michael McLay started this module. Steve Majewski changed the
16 # interface to SvFormContentDict and FormContentDict. The multipart
17 # parsing was inspired by code submitted by Andreas Paepcke. Guido van
18 # Rossum rewrote, reformatted and documented the module and is currently
19 # responsible for its maintenance.
22 __version__ = "2.4"
25 # Imports
26 # =======
28 import string
29 import sys
30 import os
31 import urllib
32 import mimetools
33 import rfc822
34 import UserDict
35 from StringIO import StringIO
38 # Logging support
39 # ===============
41 logfile = "" # Filename to log to, if not empty
42 logfp = None # File object to log to, if not None
44 def initlog(*allargs):
45 """Write a log message, if there is a log file.
47 Even though this function is called initlog(), you should always
48 use log(); log is a variable that is set either to initlog
49 (initially), to dolog (once the log file has been opened), or to
50 nolog (when logging is disabled).
52 The first argument is a format string; the remaining arguments (if
53 any) are arguments to the % operator, so e.g.
54 log("%s: %s", "a", "b")
55 will write "a: b" to the log file, followed by a newline.
57 If the global logfp is not None, it should be a file object to
58 which log data is written.
60 If the global logfp is None, the global logfile may be a string
61 giving a filename to open, in append mode. This file should be
62 world writable!!! If the file can't be opened, logging is
63 silently disabled (since there is no safe place where we could
64 send an error message).
66 """
67 global logfp, log
68 if logfile and not logfp:
69 try:
70 logfp = open(logfile, "a")
71 except IOError:
72 pass
73 if not logfp:
74 log = nolog
75 else:
76 log = dolog
77 apply(log, allargs)
79 def dolog(fmt, *args):
80 """Write a log message to the log file. See initlog() for docs."""
81 logfp.write(fmt%args + "\n")
83 def nolog(*allargs):
84 """Dummy function, assigned to log when logging is disabled."""
85 pass
87 log = initlog # The current logging function
90 # Parsing functions
91 # =================
93 # Maximum input we will accept when REQUEST_METHOD is POST
94 # 0 ==> unlimited input
95 maxlen = 0
97 def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
98 """Parse a query in the environment or from a file (default stdin)
100 Arguments, all optional:
102 fp : file pointer; default: sys.stdin
104 environ : environment dictionary; default: os.environ
106 keep_blank_values: flag indicating whether blank values in
107 URL encoded forms should be treated as blank strings.
108 A true value indicates that blanks should be retained as
109 blank strings. The default false value indicates that
110 blank values are to be ignored and treated as if they were
111 not included.
113 strict_parsing: flag indicating what to do with parsing errors.
114 If false (the default), errors are silently ignored.
115 If true, errors raise a ValueError exception.
117 if not fp:
118 fp = sys.stdin
119 if not environ.has_key('REQUEST_METHOD'):
120 environ['REQUEST_METHOD'] = 'GET' # For testing stand-alone
121 if environ['REQUEST_METHOD'] == 'POST':
122 ctype, pdict = parse_header(environ['CONTENT_TYPE'])
123 if ctype == 'multipart/form-data':
124 return parse_multipart(fp, pdict)
125 elif ctype == 'application/x-www-form-urlencoded':
126 clength = string.atoi(environ['CONTENT_LENGTH'])
127 if maxlen and clength > maxlen:
128 raise ValueError, 'Maximum content length exceeded'
129 qs = fp.read(clength)
130 else:
131 qs = '' # Unknown content-type
132 if environ.has_key('QUERY_STRING'):
133 if qs: qs = qs + '&'
134 qs = qs + environ['QUERY_STRING']
135 elif sys.argv[1:]:
136 if qs: qs = qs + '&'
137 qs = qs + sys.argv[1]
138 environ['QUERY_STRING'] = qs # XXX Shouldn't, really
139 elif environ.has_key('QUERY_STRING'):
140 qs = environ['QUERY_STRING']
141 else:
142 if sys.argv[1:]:
143 qs = sys.argv[1]
144 else:
145 qs = ""
146 environ['QUERY_STRING'] = qs # XXX Shouldn't, really
147 return parse_qs(qs, keep_blank_values, strict_parsing)
150 def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
151 """Parse a query given as a string argument.
153 Arguments:
155 qs: URL-encoded query string to be parsed
157 keep_blank_values: flag indicating whether blank values in
158 URL encoded queries should be treated as blank strings.
159 A true value indicates that blanks should be retained as
160 blank strings. The default false value indicates that
161 blank values are to be ignored and treated as if they were
162 not included.
164 strict_parsing: flag indicating what to do with parsing errors.
165 If false (the default), errors are silently ignored.
166 If true, errors raise a ValueError exception.
168 dict = {}
169 for name, value in parse_qsl(qs, keep_blank_values, strict_parsing):
170 if dict.has_key(name):
171 dict[name].append(value)
172 else:
173 dict[name] = [value]
174 return dict
176 def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
177 """Parse a query given as a string argument.
179 Arguments:
181 qs: URL-encoded query string to be parsed
183 keep_blank_values: flag indicating whether blank values in
184 URL encoded queries should be treated as blank strings. A
185 true value indicates that blanks should be retained as blank
186 strings. The default false value indicates that blank values
187 are to be ignored and treated as if they were not included.
189 strict_parsing: flag indicating what to do with parsing errors. If
190 false (the default), errors are silently ignored. If true,
191 errors raise a ValueError exception.
193 Returns a list, as G-d intended.
195 pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')]
196 r = []
197 for name_value in pairs:
198 nv = name_value.split('=', 1)
199 if len(nv) != 2:
200 if strict_parsing:
201 raise ValueError, "bad query field: %s" % `name_value`
202 continue
203 if len(nv[1]) or keep_blank_values:
204 name = urllib.unquote(string.replace(nv[0], '+', ' '))
205 value = urllib.unquote(string.replace(nv[1], '+', ' '))
206 r.append((name, value))
208 return r
211 def parse_multipart(fp, pdict):
212 """Parse multipart input.
214 Arguments:
215 fp : input file
216 pdict: dictionary containing other parameters of conten-type header
218 Returns a dictionary just like parse_qs(): keys are the field names, each
219 value is a list of values for that field. This is easy to use but not
220 much good if you are expecting megabytes to be uploaded -- in that case,
221 use the FieldStorage class instead which is much more flexible. Note
222 that content-type is the raw, unparsed contents of the content-type
223 header.
225 XXX This does not parse nested multipart parts -- use FieldStorage for
226 that.
228 XXX This should really be subsumed by FieldStorage altogether -- no
229 point in having two implementations of the same parsing algorithm.
232 if pdict.has_key('boundary'):
233 boundary = pdict['boundary']
234 else:
235 boundary = ""
236 nextpart = "--" + boundary
237 lastpart = "--" + boundary + "--"
238 partdict = {}
239 terminator = ""
241 while terminator != lastpart:
242 bytes = -1
243 data = None
244 if terminator:
245 # At start of next part. Read headers first.
246 headers = mimetools.Message(fp)
247 clength = headers.getheader('content-length')
248 if clength:
249 try:
250 bytes = string.atoi(clength)
251 except string.atoi_error:
252 pass
253 if bytes > 0:
254 if maxlen and bytes > maxlen:
255 raise ValueError, 'Maximum content length exceeded'
256 data = fp.read(bytes)
257 else:
258 data = ""
259 # Read lines until end of part.
260 lines = []
261 while 1:
262 line = fp.readline()
263 if not line:
264 terminator = lastpart # End outer loop
265 break
266 if line[:2] == "--":
267 terminator = string.strip(line)
268 if terminator in (nextpart, lastpart):
269 break
270 lines.append(line)
271 # Done with part.
272 if data is None:
273 continue
274 if bytes < 0:
275 if lines:
276 # Strip final line terminator
277 line = lines[-1]
278 if line[-2:] == "\r\n":
279 line = line[:-2]
280 elif line[-1:] == "\n":
281 line = line[:-1]
282 lines[-1] = line
283 data = string.joinfields(lines, "")
284 line = headers['content-disposition']
285 if not line:
286 continue
287 key, params = parse_header(line)
288 if key != 'form-data':
289 continue
290 if params.has_key('name'):
291 name = params['name']
292 else:
293 continue
294 if partdict.has_key(name):
295 partdict[name].append(data)
296 else:
297 partdict[name] = [data]
299 return partdict
302 def parse_header(line):
303 """Parse a Content-type like header.
305 Return the main content-type and a dictionary of options.
308 plist = map(string.strip, string.splitfields(line, ';'))
309 key = string.lower(plist[0])
310 del plist[0]
311 pdict = {}
312 for p in plist:
313 i = string.find(p, '=')
314 if i >= 0:
315 name = string.lower(string.strip(p[:i]))
316 value = string.strip(p[i+1:])
317 if len(value) >= 2 and value[0] == value[-1] == '"':
318 value = value[1:-1]
319 pdict[name] = value
320 return key, pdict
323 # Classes for field storage
324 # =========================
326 class MiniFieldStorage:
328 """Like FieldStorage, for use when no file uploads are possible."""
330 # Dummy attributes
331 filename = None
332 list = None
333 type = None
334 file = None
335 type_options = {}
336 disposition = None
337 disposition_options = {}
338 headers = {}
340 def __init__(self, name, value):
341 """Constructor from field name and value."""
342 self.name = name
343 self.value = value
344 # self.file = StringIO(value)
346 def __repr__(self):
347 """Return printable representation."""
348 return "MiniFieldStorage(%s, %s)" % (`self.name`, `self.value`)
351 class FieldStorage:
353 """Store a sequence of fields, reading multipart/form-data.
355 This class provides naming, typing, files stored on disk, and
356 more. At the top level, it is accessible like a dictionary, whose
357 keys are the field names. (Note: None can occur as a field name.)
358 The items are either a Python list (if there's multiple values) or
359 another FieldStorage or MiniFieldStorage object. If it's a single
360 object, it has the following attributes:
362 name: the field name, if specified; otherwise None
364 filename: the filename, if specified; otherwise None; this is the
365 client side filename, *not* the file name on which it is
366 stored (that's a temporary file you don't deal with)
368 value: the value as a *string*; for file uploads, this
369 transparently reads the file every time you request the value
371 file: the file(-like) object from which you can read the data;
372 None if the data is stored a simple string
374 type: the content-type, or None if not specified
376 type_options: dictionary of options specified on the content-type
377 line
379 disposition: content-disposition, or None if not specified
381 disposition_options: dictionary of corresponding options
383 headers: a dictionary(-like) object (sometimes rfc822.Message or a
384 subclass thereof) containing *all* headers
386 The class is subclassable, mostly for the purpose of overriding
387 the make_file() method, which is called internally to come up with
388 a file open for reading and writing. This makes it possible to
389 override the default choice of storing all files in a temporary
390 directory and unlinking them as soon as they have been opened.
394 def __init__(self, fp=None, headers=None, outerboundary="",
395 environ=os.environ, keep_blank_values=0, strict_parsing=0):
396 """Constructor. Read multipart/* until last part.
398 Arguments, all optional:
400 fp : file pointer; default: sys.stdin
401 (not used when the request method is GET)
403 headers : header dictionary-like object; default:
404 taken from environ as per CGI spec
406 outerboundary : terminating multipart boundary
407 (for internal use only)
409 environ : environment dictionary; default: os.environ
411 keep_blank_values: flag indicating whether blank values in
412 URL encoded forms should be treated as blank strings.
413 A true value indicates that blanks should be retained as
414 blank strings. The default false value indicates that
415 blank values are to be ignored and treated as if they were
416 not included.
418 strict_parsing: flag indicating what to do with parsing errors.
419 If false (the default), errors are silently ignored.
420 If true, errors raise a ValueError exception.
423 method = 'GET'
424 self.keep_blank_values = keep_blank_values
425 self.strict_parsing = strict_parsing
426 if environ.has_key('REQUEST_METHOD'):
427 method = string.upper(environ['REQUEST_METHOD'])
428 if method == 'GET' or method == 'HEAD':
429 if environ.has_key('QUERY_STRING'):
430 qs = environ['QUERY_STRING']
431 elif sys.argv[1:]:
432 qs = sys.argv[1]
433 else:
434 qs = ""
435 fp = StringIO(qs)
436 if headers is None:
437 headers = {'content-type':
438 "application/x-www-form-urlencoded"}
439 if headers is None:
440 headers = {}
441 if method == 'POST':
442 # Set default content-type for POST to what's traditional
443 headers['content-type'] = "application/x-www-form-urlencoded"
444 if environ.has_key('CONTENT_TYPE'):
445 headers['content-type'] = environ['CONTENT_TYPE']
446 if environ.has_key('CONTENT_LENGTH'):
447 headers['content-length'] = environ['CONTENT_LENGTH']
448 self.fp = fp or sys.stdin
449 self.headers = headers
450 self.outerboundary = outerboundary
452 # Process content-disposition header
453 cdisp, pdict = "", {}
454 if self.headers.has_key('content-disposition'):
455 cdisp, pdict = parse_header(self.headers['content-disposition'])
456 self.disposition = cdisp
457 self.disposition_options = pdict
458 self.name = None
459 if pdict.has_key('name'):
460 self.name = pdict['name']
461 self.filename = None
462 if pdict.has_key('filename'):
463 self.filename = pdict['filename']
465 # Process content-type header
467 # Honor any existing content-type header. But if there is no
468 # content-type header, use some sensible defaults. Assume
469 # outerboundary is "" at the outer level, but something non-false
470 # inside a multi-part. The default for an inner part is text/plain,
471 # but for an outer part it should be urlencoded. This should catch
472 # bogus clients which erroneously forget to include a content-type
473 # header.
475 # See below for what we do if there does exist a content-type header,
476 # but it happens to be something we don't understand.
477 if self.headers.has_key('content-type'):
478 ctype, pdict = parse_header(self.headers['content-type'])
479 elif self.outerboundary or method != 'POST':
480 ctype, pdict = "text/plain", {}
481 else:
482 ctype, pdict = 'application/x-www-form-urlencoded', {}
483 self.type = ctype
484 self.type_options = pdict
485 self.innerboundary = ""
486 if pdict.has_key('boundary'):
487 self.innerboundary = pdict['boundary']
488 clen = -1
489 if self.headers.has_key('content-length'):
490 try:
491 clen = string.atoi(self.headers['content-length'])
492 except:
493 pass
494 if maxlen and clen > maxlen:
495 raise ValueError, 'Maximum content length exceeded'
496 self.length = clen
498 self.list = self.file = None
499 self.done = 0
500 self.lines = []
501 if ctype == 'application/x-www-form-urlencoded':
502 self.read_urlencoded()
503 elif ctype[:10] == 'multipart/':
504 self.read_multi(environ, keep_blank_values, strict_parsing)
505 else:
506 self.read_single()
508 def __repr__(self):
509 """Return a printable representation."""
510 return "FieldStorage(%s, %s, %s)" % (
511 `self.name`, `self.filename`, `self.value`)
513 def __getattr__(self, name):
514 if name != 'value':
515 raise AttributeError, name
516 if self.file:
517 self.file.seek(0)
518 value = self.file.read()
519 self.file.seek(0)
520 elif self.list is not None:
521 value = self.list
522 else:
523 value = None
524 return value
526 def __getitem__(self, key):
527 """Dictionary style indexing."""
528 if self.list is None:
529 raise TypeError, "not indexable"
530 found = []
531 for item in self.list:
532 if item.name == key: found.append(item)
533 if not found:
534 raise KeyError, key
535 if len(found) == 1:
536 return found[0]
537 else:
538 return found
540 def getvalue(self, key, default=None):
541 """Dictionary style get() method, including 'value' lookup."""
542 if self.has_key(key):
543 value = self[key]
544 if type(value) is type([]):
545 return map(lambda v: v.value, value)
546 else:
547 return value.value
548 else:
549 return default
551 def keys(self):
552 """Dictionary style keys() method."""
553 if self.list is None:
554 raise TypeError, "not indexable"
555 keys = []
556 for item in self.list:
557 if item.name not in keys: keys.append(item.name)
558 return keys
560 def has_key(self, key):
561 """Dictionary style has_key() method."""
562 if self.list is None:
563 raise TypeError, "not indexable"
564 for item in self.list:
565 if item.name == key: return 1
566 return 0
568 def __len__(self):
569 """Dictionary style len(x) support."""
570 return len(self.keys())
572 def read_urlencoded(self):
573 """Internal: read data in query string format."""
574 qs = self.fp.read(self.length)
575 self.list = list = []
576 for key, value in parse_qsl(qs, self.keep_blank_values,
577 self.strict_parsing):
578 list.append(MiniFieldStorage(key, value))
579 self.skip_lines()
581 FieldStorageClass = None
583 def read_multi(self, environ, keep_blank_values, strict_parsing):
584 """Internal: read a part that is itself multipart."""
585 self.list = []
586 klass = self.FieldStorageClass or self.__class__
587 part = klass(self.fp, {}, self.innerboundary,
588 environ, keep_blank_values, strict_parsing)
589 # Throw first part away
590 while not part.done:
591 headers = rfc822.Message(self.fp)
592 part = klass(self.fp, headers, self.innerboundary,
593 environ, keep_blank_values, strict_parsing)
594 self.list.append(part)
595 self.skip_lines()
597 def read_single(self):
598 """Internal: read an atomic part."""
599 if self.length >= 0:
600 self.read_binary()
601 self.skip_lines()
602 else:
603 self.read_lines()
604 self.file.seek(0)
606 bufsize = 8*1024 # I/O buffering size for copy to file
608 def read_binary(self):
609 """Internal: read binary data."""
610 self.file = self.make_file('b')
611 todo = self.length
612 if todo >= 0:
613 while todo > 0:
614 data = self.fp.read(min(todo, self.bufsize))
615 if not data:
616 self.done = -1
617 break
618 self.file.write(data)
619 todo = todo - len(data)
621 def read_lines(self):
622 """Internal: read lines until EOF or outerboundary."""
623 self.file = self.make_file('')
624 if self.outerboundary:
625 self.read_lines_to_outerboundary()
626 else:
627 self.read_lines_to_eof()
629 def read_lines_to_eof(self):
630 """Internal: read lines until EOF."""
631 while 1:
632 line = self.fp.readline()
633 if not line:
634 self.done = -1
635 break
636 self.lines.append(line)
637 self.file.write(line)
639 def read_lines_to_outerboundary(self):
640 """Internal: read lines until outerboundary."""
641 next = "--" + self.outerboundary
642 last = next + "--"
643 delim = ""
644 while 1:
645 line = self.fp.readline()
646 if not line:
647 self.done = -1
648 break
649 self.lines.append(line)
650 if line[:2] == "--":
651 strippedline = string.strip(line)
652 if strippedline == next:
653 break
654 if strippedline == last:
655 self.done = 1
656 break
657 odelim = delim
658 if line[-2:] == "\r\n":
659 delim = "\r\n"
660 line = line[:-2]
661 elif line[-1] == "\n":
662 delim = "\n"
663 line = line[:-1]
664 else:
665 delim = ""
666 self.file.write(odelim + line)
668 def skip_lines(self):
669 """Internal: skip lines until outer boundary if defined."""
670 if not self.outerboundary or self.done:
671 return
672 next = "--" + self.outerboundary
673 last = next + "--"
674 while 1:
675 line = self.fp.readline()
676 if not line:
677 self.done = -1
678 break
679 self.lines.append(line)
680 if line[:2] == "--":
681 strippedline = string.strip(line)
682 if strippedline == next:
683 break
684 if strippedline == last:
685 self.done = 1
686 break
688 def make_file(self, binary=None):
689 """Overridable: return a readable & writable file.
691 The file will be used as follows:
692 - data is written to it
693 - seek(0)
694 - data is read from it
696 The 'binary' argument is unused -- the file is always opened
697 in binary mode.
699 This version opens a temporary file for reading and writing,
700 and immediately deletes (unlinks) it. The trick (on Unix!) is
701 that the file can still be used, but it can't be opened by
702 another process, and it will automatically be deleted when it
703 is closed or when the current process terminates.
705 If you want a more permanent file, you derive a class which
706 overrides this method. If you want a visible temporary file
707 that is nevertheless automatically deleted when the script
708 terminates, try defining a __del__ method in a derived class
709 which unlinks the temporary files you have created.
712 import tempfile
713 return tempfile.TemporaryFile("w+b")
717 # Backwards Compatibility Classes
718 # ===============================
720 class FormContentDict(UserDict.UserDict):
721 """Form content as dictionary with a list of values per field.
723 form = FormContentDict()
725 form[key] -> [value, value, ...]
726 form.has_key(key) -> Boolean
727 form.keys() -> [key, key, ...]
728 form.values() -> [[val, val, ...], [val, val, ...], ...]
729 form.items() -> [(key, [val, val, ...]), (key, [val, val, ...]), ...]
730 form.dict == {key: [val, val, ...], ...}
733 def __init__(self, environ=os.environ):
734 self.dict = self.data = parse(environ=environ)
735 self.query_string = environ['QUERY_STRING']
738 class SvFormContentDict(FormContentDict):
739 """Form content as dictionary expecting a single value per field.
741 If you only expect a single value for each field, then form[key]
742 will return that single value. It will raise an IndexError if
743 that expectation is not true. If you expect a field to have
744 possible multiple values, than you can use form.getlist(key) to
745 get all of the values. values() and items() are a compromise:
746 they return single strings where there is a single value, and
747 lists of strings otherwise.
750 def __getitem__(self, key):
751 if len(self.dict[key]) > 1:
752 raise IndexError, 'expecting a single value'
753 return self.dict[key][0]
754 def getlist(self, key):
755 return self.dict[key]
756 def values(self):
757 result = []
758 for value in self.dict.values():
759 if len(value) == 1:
760 result.append(value[0])
761 else: result.append(value)
762 return result
763 def items(self):
764 result = []
765 for key, value in self.dict.items():
766 if len(value) == 1:
767 result.append((key, value[0]))
768 else: result.append((key, value))
769 return result
772 class InterpFormContentDict(SvFormContentDict):
773 """This class is present for backwards compatibility only."""
774 def __getitem__(self, key):
775 v = SvFormContentDict.__getitem__(self, key)
776 if v[0] in string.digits + '+-.':
777 try: return string.atoi(v)
778 except ValueError:
779 try: return string.atof(v)
780 except ValueError: pass
781 return string.strip(v)
782 def values(self):
783 result = []
784 for key in self.keys():
785 try:
786 result.append(self[key])
787 except IndexError:
788 result.append(self.dict[key])
789 return result
790 def items(self):
791 result = []
792 for key in self.keys():
793 try:
794 result.append((key, self[key]))
795 except IndexError:
796 result.append((key, self.dict[key]))
797 return result
800 class FormContent(FormContentDict):
801 """This class is present for backwards compatibility only."""
802 def values(self, key):
803 if self.dict.has_key(key) :return self.dict[key]
804 else: return None
805 def indexed_value(self, key, location):
806 if self.dict.has_key(key):
807 if len(self.dict[key]) > location:
808 return self.dict[key][location]
809 else: return None
810 else: return None
811 def value(self, key):
812 if self.dict.has_key(key): return self.dict[key][0]
813 else: return None
814 def length(self, key):
815 return len(self.dict[key])
816 def stripped(self, key):
817 if self.dict.has_key(key): return string.strip(self.dict[key][0])
818 else: return None
819 def pars(self):
820 return self.dict
823 # Test/debug code
824 # ===============
826 def test(environ=os.environ):
827 """Robust test CGI script, usable as main program.
829 Write minimal HTTP headers and dump all information provided to
830 the script in HTML form.
833 import traceback
834 print "Content-type: text/html"
835 print
836 sys.stderr = sys.stdout
837 try:
838 form = FieldStorage() # Replace with other classes to test those
839 print_directory()
840 print_arguments()
841 print_form(form)
842 print_environ(environ)
843 print_environ_usage()
844 def f():
845 exec "testing print_exception() -- <I>italics?</I>"
846 def g(f=f):
848 print "<H3>What follows is a test, not an actual exception:</H3>"
850 except:
851 print_exception()
853 print "<H1>Second try with a small maxlen...</H1>"
855 global maxlen
856 maxlen = 50
857 try:
858 form = FieldStorage() # Replace with other classes to test those
859 print_directory()
860 print_arguments()
861 print_form(form)
862 print_environ(environ)
863 except:
864 print_exception()
866 def print_exception(type=None, value=None, tb=None, limit=None):
867 if type is None:
868 type, value, tb = sys.exc_info()
869 import traceback
870 print
871 print "<H3>Traceback (innermost last):</H3>"
872 list = traceback.format_tb(tb, limit) + \
873 traceback.format_exception_only(type, value)
874 print "<PRE>%s<B>%s</B></PRE>" % (
875 escape(string.join(list[:-1], "")),
876 escape(list[-1]),
878 del tb
880 def print_environ(environ=os.environ):
881 """Dump the shell environment as HTML."""
882 keys = environ.keys()
883 keys.sort()
884 print
885 print "<H3>Shell Environment:</H3>"
886 print "<DL>"
887 for key in keys:
888 print "<DT>", escape(key), "<DD>", escape(environ[key])
889 print "</DL>"
890 print
892 def print_form(form):
893 """Dump the contents of a form as HTML."""
894 keys = form.keys()
895 keys.sort()
896 print
897 print "<H3>Form Contents:</H3>"
898 if not keys:
899 print "<P>No form fields."
900 print "<DL>"
901 for key in keys:
902 print "<DT>" + escape(key) + ":",
903 value = form[key]
904 print "<i>" + escape(`type(value)`) + "</i>"
905 print "<DD>" + escape(`value`)
906 print "</DL>"
907 print
909 def print_directory():
910 """Dump the current directory as HTML."""
911 print
912 print "<H3>Current Working Directory:</H3>"
913 try:
914 pwd = os.getcwd()
915 except os.error, msg:
916 print "os.error:", escape(str(msg))
917 else:
918 print escape(pwd)
919 print
921 def print_arguments():
922 print
923 print "<H3>Command Line Arguments:</H3>"
924 print
925 print sys.argv
926 print
928 def print_environ_usage():
929 """Dump a list of environment variables used by CGI as HTML."""
930 print """
931 <H3>These environment variables could have been set:</H3>
932 <UL>
933 <LI>AUTH_TYPE
934 <LI>CONTENT_LENGTH
935 <LI>CONTENT_TYPE
936 <LI>DATE_GMT
937 <LI>DATE_LOCAL
938 <LI>DOCUMENT_NAME
939 <LI>DOCUMENT_ROOT
940 <LI>DOCUMENT_URI
941 <LI>GATEWAY_INTERFACE
942 <LI>LAST_MODIFIED
943 <LI>PATH
944 <LI>PATH_INFO
945 <LI>PATH_TRANSLATED
946 <LI>QUERY_STRING
947 <LI>REMOTE_ADDR
948 <LI>REMOTE_HOST
949 <LI>REMOTE_IDENT
950 <LI>REMOTE_USER
951 <LI>REQUEST_METHOD
952 <LI>SCRIPT_NAME
953 <LI>SERVER_NAME
954 <LI>SERVER_PORT
955 <LI>SERVER_PROTOCOL
956 <LI>SERVER_ROOT
957 <LI>SERVER_SOFTWARE
958 </UL>
959 In addition, HTTP headers sent by the server may be passed in the
960 environment as well. Here are some common variable names:
961 <UL>
962 <LI>HTTP_ACCEPT
963 <LI>HTTP_CONNECTION
964 <LI>HTTP_HOST
965 <LI>HTTP_PRAGMA
966 <LI>HTTP_REFERER
967 <LI>HTTP_USER_AGENT
968 </UL>
972 # Utilities
973 # =========
975 def escape(s, quote=None):
976 """Replace special characters '&', '<' and '>' by SGML entities."""
977 s = string.replace(s, "&", "&amp;") # Must be done first!
978 s = string.replace(s, "<", "&lt;")
979 s = string.replace(s, ">", "&gt;",)
980 if quote:
981 s = string.replace(s, '"', "&quot;")
982 return s
985 # Invoke mainline
986 # ===============
988 # Call test() when this file is run as a script (not imported as a module)
989 if __name__ == '__main__':
990 test()