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