update pydoc topics
[python/dscho.git] / Lib / cgi.py
blob9e16a1df6237d20f567990c6f70793538503b3b6
1 #! /usr/local/bin/python
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.
12 """Support module for CGI (Common Gateway Interface) scripts.
14 This module defines a number of utilities for use by CGI scripts
15 written in Python.
16 """
18 # History
19 # -------
21 # Michael McLay started this module. Steve Majewski changed the
22 # interface to SvFormContentDict and FormContentDict. The multipart
23 # parsing was inspired by code submitted by Andreas Paepcke. Guido van
24 # Rossum rewrote, reformatted and documented the module and is currently
25 # responsible for its maintenance.
28 __version__ = "2.6"
31 # Imports
32 # =======
34 from operator import attrgetter
35 from io import StringIO
36 import sys
37 import os
38 import urllib.parse
39 import email.parser
40 from warnings import warn
42 __all__ = ["MiniFieldStorage", "FieldStorage",
43 "parse", "parse_qs", "parse_qsl", "parse_multipart",
44 "parse_header", "print_exception", "print_environ",
45 "print_form", "print_directory", "print_arguments",
46 "print_environ_usage", "escape"]
48 # Logging support
49 # ===============
51 logfile = "" # Filename to log to, if not empty
52 logfp = None # File object to log to, if not None
54 def initlog(*allargs):
55 """Write a log message, if there is a log file.
57 Even though this function is called initlog(), you should always
58 use log(); log is a variable that is set either to initlog
59 (initially), to dolog (once the log file has been opened), or to
60 nolog (when logging is disabled).
62 The first argument is a format string; the remaining arguments (if
63 any) are arguments to the % operator, so e.g.
64 log("%s: %s", "a", "b")
65 will write "a: b" to the log file, followed by a newline.
67 If the global logfp is not None, it should be a file object to
68 which log data is written.
70 If the global logfp is None, the global logfile may be a string
71 giving a filename to open, in append mode. This file should be
72 world writable!!! If the file can't be opened, logging is
73 silently disabled (since there is no safe place where we could
74 send an error message).
76 """
77 global logfp, log
78 if logfile and not logfp:
79 try:
80 logfp = open(logfile, "a")
81 except IOError:
82 pass
83 if not logfp:
84 log = nolog
85 else:
86 log = dolog
87 log(*allargs)
89 def dolog(fmt, *args):
90 """Write a log message to the log file. See initlog() for docs."""
91 logfp.write(fmt%args + "\n")
93 def nolog(*allargs):
94 """Dummy function, assigned to log when logging is disabled."""
95 pass
97 log = initlog # The current logging function
100 # Parsing functions
101 # =================
103 # Maximum input we will accept when REQUEST_METHOD is POST
104 # 0 ==> unlimited input
105 maxlen = 0
107 def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
108 """Parse a query in the environment or from a file (default stdin)
110 Arguments, all optional:
112 fp : file pointer; default: sys.stdin
114 environ : environment dictionary; default: os.environ
116 keep_blank_values: flag indicating whether blank values in
117 URL encoded forms should be treated as blank strings.
118 A true value indicates that blanks should be retained as
119 blank strings. The default false value indicates that
120 blank values are to be ignored and treated as if they were
121 not included.
123 strict_parsing: flag indicating what to do with parsing errors.
124 If false (the default), errors are silently ignored.
125 If true, errors raise a ValueError exception.
127 if fp is None:
128 fp = sys.stdin
129 if not 'REQUEST_METHOD' in environ:
130 environ['REQUEST_METHOD'] = 'GET' # For testing stand-alone
131 if environ['REQUEST_METHOD'] == 'POST':
132 ctype, pdict = parse_header(environ['CONTENT_TYPE'])
133 if ctype == 'multipart/form-data':
134 return parse_multipart(fp, pdict)
135 elif ctype == 'application/x-www-form-urlencoded':
136 clength = int(environ['CONTENT_LENGTH'])
137 if maxlen and clength > maxlen:
138 raise ValueError('Maximum content length exceeded')
139 qs = fp.read(clength)
140 else:
141 qs = '' # Unknown content-type
142 if 'QUERY_STRING' in environ:
143 if qs: qs = qs + '&'
144 qs = qs + environ['QUERY_STRING']
145 elif sys.argv[1:]:
146 if qs: qs = qs + '&'
147 qs = qs + sys.argv[1]
148 environ['QUERY_STRING'] = qs # XXX Shouldn't, really
149 elif 'QUERY_STRING' in environ:
150 qs = environ['QUERY_STRING']
151 else:
152 if sys.argv[1:]:
153 qs = sys.argv[1]
154 else:
155 qs = ""
156 environ['QUERY_STRING'] = qs # XXX Shouldn't, really
157 return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
160 # parse query string function called from urlparse,
161 # this is done in order to maintain backward compatiblity.
163 def parse_qs(qs, keep_blank_values=0, strict_parsing=0):
164 """Parse a query given as a string argument."""
165 warn("cgi.parse_qs is deprecated, use urllib.parse.parse_qs instead",
166 DeprecationWarning, 2)
167 return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
169 def parse_qsl(qs, keep_blank_values=0, strict_parsing=0):
170 """Parse a query given as a string argument."""
171 warn("cgi.parse_qsl is deprecated, use urllib.parse.parse_qsl instead",
172 DeprecationWarning, 2)
173 return urllib.parse.parse_qsl(qs, keep_blank_values, strict_parsing)
175 def parse_multipart(fp, pdict):
176 """Parse multipart input.
178 Arguments:
179 fp : input file
180 pdict: dictionary containing other parameters of content-type header
182 Returns a dictionary just like parse_qs(): keys are the field names, each
183 value is a list of values for that field. This is easy to use but not
184 much good if you are expecting megabytes to be uploaded -- in that case,
185 use the FieldStorage class instead which is much more flexible. Note
186 that content-type is the raw, unparsed contents of the content-type
187 header.
189 XXX This does not parse nested multipart parts -- use FieldStorage for
190 that.
192 XXX This should really be subsumed by FieldStorage altogether -- no
193 point in having two implementations of the same parsing algorithm.
194 Also, FieldStorage protects itself better against certain DoS attacks
195 by limiting the size of the data read in one chunk. The API here
196 does not support that kind of protection. This also affects parse()
197 since it can call parse_multipart().
200 import http.client
202 boundary = ""
203 if 'boundary' in pdict:
204 boundary = pdict['boundary']
205 if not valid_boundary(boundary):
206 raise ValueError('Invalid boundary in multipart form: %r'
207 % (boundary,))
209 nextpart = "--" + boundary
210 lastpart = "--" + boundary + "--"
211 partdict = {}
212 terminator = ""
214 while terminator != lastpart:
215 bytes = -1
216 data = None
217 if terminator:
218 # At start of next part. Read headers first.
219 headers = http.client.parse_headers(fp)
220 clength = headers.get('content-length')
221 if clength:
222 try:
223 bytes = int(clength)
224 except ValueError:
225 pass
226 if bytes > 0:
227 if maxlen and bytes > maxlen:
228 raise ValueError('Maximum content length exceeded')
229 data = fp.read(bytes)
230 else:
231 data = ""
232 # Read lines until end of part.
233 lines = []
234 while 1:
235 line = fp.readline()
236 if not line:
237 terminator = lastpart # End outer loop
238 break
239 if line[:2] == "--":
240 terminator = line.strip()
241 if terminator in (nextpart, lastpart):
242 break
243 lines.append(line)
244 # Done with part.
245 if data is None:
246 continue
247 if bytes < 0:
248 if lines:
249 # Strip final line terminator
250 line = lines[-1]
251 if line[-2:] == "\r\n":
252 line = line[:-2]
253 elif line[-1:] == "\n":
254 line = line[:-1]
255 lines[-1] = line
256 data = "".join(lines)
257 line = headers['content-disposition']
258 if not line:
259 continue
260 key, params = parse_header(line)
261 if key != 'form-data':
262 continue
263 if 'name' in params:
264 name = params['name']
265 else:
266 continue
267 if name in partdict:
268 partdict[name].append(data)
269 else:
270 partdict[name] = [data]
272 return partdict
275 def _parseparam(s):
276 while s[:1] == ';':
277 s = s[1:]
278 end = s.find(';')
279 while end > 0 and s.count('"', 0, end) % 2:
280 end = s.find(';', end + 1)
281 if end < 0:
282 end = len(s)
283 f = s[:end]
284 yield f.strip()
285 s = s[end:]
287 def parse_header(line):
288 """Parse a Content-type like header.
290 Return the main content-type and a dictionary of options.
293 parts = _parseparam(';' + line)
294 key = parts.__next__()
295 pdict = {}
296 for p in parts:
297 i = p.find('=')
298 if i >= 0:
299 name = p[:i].strip().lower()
300 value = p[i+1:].strip()
301 if len(value) >= 2 and value[0] == value[-1] == '"':
302 value = value[1:-1]
303 value = value.replace('\\\\', '\\').replace('\\"', '"')
304 pdict[name] = value
305 return key, pdict
308 # Classes for field storage
309 # =========================
311 class MiniFieldStorage:
313 """Like FieldStorage, for use when no file uploads are possible."""
315 # Dummy attributes
316 filename = None
317 list = None
318 type = None
319 file = None
320 type_options = {}
321 disposition = None
322 disposition_options = {}
323 headers = {}
325 def __init__(self, name, value):
326 """Constructor from field name and value."""
327 self.name = name
328 self.value = value
329 # self.file = StringIO(value)
331 def __repr__(self):
332 """Return printable representation."""
333 return "MiniFieldStorage(%r, %r)" % (self.name, self.value)
336 class FieldStorage:
338 """Store a sequence of fields, reading multipart/form-data.
340 This class provides naming, typing, files stored on disk, and
341 more. At the top level, it is accessible like a dictionary, whose
342 keys are the field names. (Note: None can occur as a field name.)
343 The items are either a Python list (if there's multiple values) or
344 another FieldStorage or MiniFieldStorage object. If it's a single
345 object, it has the following attributes:
347 name: the field name, if specified; otherwise None
349 filename: the filename, if specified; otherwise None; this is the
350 client side filename, *not* the file name on which it is
351 stored (that's a temporary file you don't deal with)
353 value: the value as a *string*; for file uploads, this
354 transparently reads the file every time you request the value
356 file: the file(-like) object from which you can read the data;
357 None if the data is stored a simple string
359 type: the content-type, or None if not specified
361 type_options: dictionary of options specified on the content-type
362 line
364 disposition: content-disposition, or None if not specified
366 disposition_options: dictionary of corresponding options
368 headers: a dictionary(-like) object (sometimes email.message.Message or a
369 subclass thereof) containing *all* headers
371 The class is subclassable, mostly for the purpose of overriding
372 the make_file() method, which is called internally to come up with
373 a file open for reading and writing. This makes it possible to
374 override the default choice of storing all files in a temporary
375 directory and unlinking them as soon as they have been opened.
379 def __init__(self, fp=None, headers=None, outerboundary="",
380 environ=os.environ, keep_blank_values=0, strict_parsing=0):
381 """Constructor. Read multipart/* until last part.
383 Arguments, all optional:
385 fp : file pointer; default: sys.stdin
386 (not used when the request method is GET)
388 headers : header dictionary-like object; default:
389 taken from environ as per CGI spec
391 outerboundary : terminating multipart boundary
392 (for internal use only)
394 environ : environment dictionary; default: os.environ
396 keep_blank_values: flag indicating whether blank values in
397 URL encoded forms should be treated as blank strings.
398 A true value indicates that blanks should be retained as
399 blank strings. The default false value indicates that
400 blank values are to be ignored and treated as if they were
401 not included.
403 strict_parsing: flag indicating what to do with parsing errors.
404 If false (the default), errors are silently ignored.
405 If true, errors raise a ValueError exception.
408 method = 'GET'
409 self.keep_blank_values = keep_blank_values
410 self.strict_parsing = strict_parsing
411 if 'REQUEST_METHOD' in environ:
412 method = environ['REQUEST_METHOD'].upper()
413 self.qs_on_post = None
414 if method == 'GET' or method == 'HEAD':
415 if 'QUERY_STRING' in environ:
416 qs = environ['QUERY_STRING']
417 elif sys.argv[1:]:
418 qs = sys.argv[1]
419 else:
420 qs = ""
421 fp = StringIO(qs)
422 if headers is None:
423 headers = {'content-type':
424 "application/x-www-form-urlencoded"}
425 if headers is None:
426 headers = {}
427 if method == 'POST':
428 # Set default content-type for POST to what's traditional
429 headers['content-type'] = "application/x-www-form-urlencoded"
430 if 'CONTENT_TYPE' in environ:
431 headers['content-type'] = environ['CONTENT_TYPE']
432 if 'QUERY_STRING' in environ:
433 self.qs_on_post = environ['QUERY_STRING']
434 if 'CONTENT_LENGTH' in environ:
435 headers['content-length'] = environ['CONTENT_LENGTH']
436 self.fp = fp or sys.stdin
437 self.headers = headers
438 self.outerboundary = outerboundary
440 # Process content-disposition header
441 cdisp, pdict = "", {}
442 if 'content-disposition' in self.headers:
443 cdisp, pdict = parse_header(self.headers['content-disposition'])
444 self.disposition = cdisp
445 self.disposition_options = pdict
446 self.name = None
447 if 'name' in pdict:
448 self.name = pdict['name']
449 self.filename = None
450 if 'filename' in pdict:
451 self.filename = pdict['filename']
453 # Process content-type header
455 # Honor any existing content-type header. But if there is no
456 # content-type header, use some sensible defaults. Assume
457 # outerboundary is "" at the outer level, but something non-false
458 # inside a multi-part. The default for an inner part is text/plain,
459 # but for an outer part it should be urlencoded. This should catch
460 # bogus clients which erroneously forget to include a content-type
461 # header.
463 # See below for what we do if there does exist a content-type header,
464 # but it happens to be something we don't understand.
465 if 'content-type' in self.headers:
466 ctype, pdict = parse_header(self.headers['content-type'])
467 elif self.outerboundary or method != 'POST':
468 ctype, pdict = "text/plain", {}
469 else:
470 ctype, pdict = 'application/x-www-form-urlencoded', {}
471 self.type = ctype
472 self.type_options = pdict
473 self.innerboundary = ""
474 if 'boundary' in pdict:
475 self.innerboundary = pdict['boundary']
476 clen = -1
477 if 'content-length' in self.headers:
478 try:
479 clen = int(self.headers['content-length'])
480 except ValueError:
481 pass
482 if maxlen and clen > maxlen:
483 raise ValueError('Maximum content length exceeded')
484 self.length = clen
486 self.list = self.file = None
487 self.done = 0
488 if ctype == 'application/x-www-form-urlencoded':
489 self.read_urlencoded()
490 elif ctype[:10] == 'multipart/':
491 self.read_multi(environ, keep_blank_values, strict_parsing)
492 else:
493 self.read_single()
495 def __repr__(self):
496 """Return a printable representation."""
497 return "FieldStorage(%r, %r, %r)" % (
498 self.name, self.filename, self.value)
500 def __iter__(self):
501 return iter(self.keys())
503 def __getattr__(self, name):
504 if name != 'value':
505 raise AttributeError(name)
506 if self.file:
507 self.file.seek(0)
508 value = self.file.read()
509 self.file.seek(0)
510 elif self.list is not None:
511 value = self.list
512 else:
513 value = None
514 return value
516 def __getitem__(self, key):
517 """Dictionary style indexing."""
518 if self.list is None:
519 raise TypeError("not indexable")
520 found = []
521 for item in self.list:
522 if item.name == key: found.append(item)
523 if not found:
524 raise KeyError(key)
525 if len(found) == 1:
526 return found[0]
527 else:
528 return found
530 def getvalue(self, key, default=None):
531 """Dictionary style get() method, including 'value' lookup."""
532 if key in self:
533 value = self[key]
534 if type(value) is type([]):
535 return [x.value for x in value]
536 else:
537 return value.value
538 else:
539 return default
541 def getfirst(self, key, default=None):
542 """ Return the first value received."""
543 if key in self:
544 value = self[key]
545 if type(value) is type([]):
546 return value[0].value
547 else:
548 return value.value
549 else:
550 return default
552 def getlist(self, key):
553 """ Return list of received values."""
554 if key in self:
555 value = self[key]
556 if type(value) is type([]):
557 return [x.value for x in value]
558 else:
559 return [value.value]
560 else:
561 return []
563 def keys(self):
564 """Dictionary style keys() method."""
565 if self.list is None:
566 raise TypeError("not indexable")
567 return list(set(item.name for item in self.list))
569 def __contains__(self, key):
570 """Dictionary style __contains__ method."""
571 if self.list is None:
572 raise TypeError("not indexable")
573 return any(item.name == key for item in self.list)
575 def __len__(self):
576 """Dictionary style len(x) support."""
577 return len(self.keys())
579 def __nonzero__(self):
580 return bool(self.list)
582 def read_urlencoded(self):
583 """Internal: read data in query string format."""
584 qs = self.fp.read(self.length)
585 if self.qs_on_post:
586 qs += '&' + self.qs_on_post
587 self.list = list = []
588 for key, value in urllib.parse.parse_qsl(qs, self.keep_blank_values,
589 self.strict_parsing):
590 list.append(MiniFieldStorage(key, value))
591 self.skip_lines()
593 FieldStorageClass = None
595 def read_multi(self, environ, keep_blank_values, strict_parsing):
596 """Internal: read a part that is itself multipart."""
597 ib = self.innerboundary
598 if not valid_boundary(ib):
599 raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
600 self.list = []
601 if self.qs_on_post:
602 for key, value in urllib.parse.parse_qsl(self.qs_on_post,
603 self.keep_blank_values, self.strict_parsing):
604 self.list.append(MiniFieldStorage(key, value))
605 FieldStorageClass = None
607 klass = self.FieldStorageClass or self.__class__
608 parser = email.parser.FeedParser()
609 # Create bogus content-type header for proper multipart parsing
610 parser.feed('Content-Type: %s; boundary=%s\r\n\r\n' % (self.type, ib))
611 parser.feed(self.fp.read())
612 full_msg = parser.close()
613 # Get subparts
614 msgs = full_msg.get_payload()
615 for msg in msgs:
616 fp = StringIO(msg.get_payload())
617 part = klass(fp, msg, ib, environ, keep_blank_values,
618 strict_parsing)
619 self.list.append(part)
620 self.skip_lines()
622 def read_single(self):
623 """Internal: read an atomic part."""
624 if self.length >= 0:
625 self.read_binary()
626 self.skip_lines()
627 else:
628 self.read_lines()
629 self.file.seek(0)
631 bufsize = 8*1024 # I/O buffering size for copy to file
633 def read_binary(self):
634 """Internal: read binary data."""
635 self.file = self.make_file()
636 todo = self.length
637 if todo >= 0:
638 while todo > 0:
639 data = self.fp.read(min(todo, self.bufsize))
640 if not data:
641 self.done = -1
642 break
643 self.file.write(data)
644 todo = todo - len(data)
646 def read_lines(self):
647 """Internal: read lines until EOF or outerboundary."""
648 self.file = self.__file = StringIO()
649 if self.outerboundary:
650 self.read_lines_to_outerboundary()
651 else:
652 self.read_lines_to_eof()
654 def __write(self, line):
655 if self.__file is not None:
656 if self.__file.tell() + len(line) > 1000:
657 self.file = self.make_file()
658 data = self.__file.getvalue()
659 self.file.write(data)
660 self.__file = None
661 self.file.write(line)
663 def read_lines_to_eof(self):
664 """Internal: read lines until EOF."""
665 while 1:
666 line = self.fp.readline(1<<16)
667 if not line:
668 self.done = -1
669 break
670 self.__write(line)
672 def read_lines_to_outerboundary(self):
673 """Internal: read lines until outerboundary."""
674 next = "--" + self.outerboundary
675 last = next + "--"
676 delim = ""
677 last_line_lfend = True
678 while 1:
679 line = self.fp.readline(1<<16)
680 if not line:
681 self.done = -1
682 break
683 if line[:2] == "--" and last_line_lfend:
684 strippedline = line.strip()
685 if strippedline == next:
686 break
687 if strippedline == last:
688 self.done = 1
689 break
690 odelim = delim
691 if line[-2:] == "\r\n":
692 delim = "\r\n"
693 line = line[:-2]
694 last_line_lfend = True
695 elif line[-1] == "\n":
696 delim = "\n"
697 line = line[:-1]
698 last_line_lfend = True
699 else:
700 delim = ""
701 last_line_lfend = False
702 self.__write(odelim + line)
704 def skip_lines(self):
705 """Internal: skip lines until outer boundary if defined."""
706 if not self.outerboundary or self.done:
707 return
708 next = "--" + self.outerboundary
709 last = next + "--"
710 last_line_lfend = True
711 while 1:
712 line = self.fp.readline(1<<16)
713 if not line:
714 self.done = -1
715 break
716 if line[:2] == "--" and last_line_lfend:
717 strippedline = line.strip()
718 if strippedline == next:
719 break
720 if strippedline == last:
721 self.done = 1
722 break
723 last_line_lfend = line.endswith('\n')
725 def make_file(self):
726 """Overridable: return a readable & writable file.
728 The file will be used as follows:
729 - data is written to it
730 - seek(0)
731 - data is read from it
733 The file is always opened in text mode.
735 This version opens a temporary file for reading and writing,
736 and immediately deletes (unlinks) it. The trick (on Unix!) is
737 that the file can still be used, but it can't be opened by
738 another process, and it will automatically be deleted when it
739 is closed or when the current process terminates.
741 If you want a more permanent file, you derive a class which
742 overrides this method. If you want a visible temporary file
743 that is nevertheless automatically deleted when the script
744 terminates, try defining a __del__ method in a derived class
745 which unlinks the temporary files you have created.
748 import tempfile
749 return tempfile.TemporaryFile("w+", encoding="utf-8", newline="\n")
752 # Test/debug code
753 # ===============
755 def test(environ=os.environ):
756 """Robust test CGI script, usable as main program.
758 Write minimal HTTP headers and dump all information provided to
759 the script in HTML form.
762 print("Content-type: text/html")
763 print()
764 sys.stderr = sys.stdout
765 try:
766 form = FieldStorage() # Replace with other classes to test those
767 print_directory()
768 print_arguments()
769 print_form(form)
770 print_environ(environ)
771 print_environ_usage()
772 def f():
773 exec("testing print_exception() -- <I>italics?</I>")
774 def g(f=f):
776 print("<H3>What follows is a test, not an actual exception:</H3>")
778 except:
779 print_exception()
781 print("<H1>Second try with a small maxlen...</H1>")
783 global maxlen
784 maxlen = 50
785 try:
786 form = FieldStorage() # Replace with other classes to test those
787 print_directory()
788 print_arguments()
789 print_form(form)
790 print_environ(environ)
791 except:
792 print_exception()
794 def print_exception(type=None, value=None, tb=None, limit=None):
795 if type is None:
796 type, value, tb = sys.exc_info()
797 import traceback
798 print()
799 print("<H3>Traceback (most recent call last):</H3>")
800 list = traceback.format_tb(tb, limit) + \
801 traceback.format_exception_only(type, value)
802 print("<PRE>%s<B>%s</B></PRE>" % (
803 escape("".join(list[:-1])),
804 escape(list[-1]),
806 del tb
808 def print_environ(environ=os.environ):
809 """Dump the shell environment as HTML."""
810 keys = sorted(environ.keys())
811 print()
812 print("<H3>Shell Environment:</H3>")
813 print("<DL>")
814 for key in keys:
815 print("<DT>", escape(key), "<DD>", escape(environ[key]))
816 print("</DL>")
817 print()
819 def print_form(form):
820 """Dump the contents of a form as HTML."""
821 keys = sorted(form.keys())
822 print()
823 print("<H3>Form Contents:</H3>")
824 if not keys:
825 print("<P>No form fields.")
826 print("<DL>")
827 for key in keys:
828 print("<DT>" + escape(key) + ":", end=' ')
829 value = form[key]
830 print("<i>" + escape(repr(type(value))) + "</i>")
831 print("<DD>" + escape(repr(value)))
832 print("</DL>")
833 print()
835 def print_directory():
836 """Dump the current directory as HTML."""
837 print()
838 print("<H3>Current Working Directory:</H3>")
839 try:
840 pwd = os.getcwd()
841 except os.error as msg:
842 print("os.error:", escape(str(msg)))
843 else:
844 print(escape(pwd))
845 print()
847 def print_arguments():
848 print()
849 print("<H3>Command Line Arguments:</H3>")
850 print()
851 print(sys.argv)
852 print()
854 def print_environ_usage():
855 """Dump a list of environment variables used by CGI as HTML."""
856 print("""
857 <H3>These environment variables could have been set:</H3>
858 <UL>
859 <LI>AUTH_TYPE
860 <LI>CONTENT_LENGTH
861 <LI>CONTENT_TYPE
862 <LI>DATE_GMT
863 <LI>DATE_LOCAL
864 <LI>DOCUMENT_NAME
865 <LI>DOCUMENT_ROOT
866 <LI>DOCUMENT_URI
867 <LI>GATEWAY_INTERFACE
868 <LI>LAST_MODIFIED
869 <LI>PATH
870 <LI>PATH_INFO
871 <LI>PATH_TRANSLATED
872 <LI>QUERY_STRING
873 <LI>REMOTE_ADDR
874 <LI>REMOTE_HOST
875 <LI>REMOTE_IDENT
876 <LI>REMOTE_USER
877 <LI>REQUEST_METHOD
878 <LI>SCRIPT_NAME
879 <LI>SERVER_NAME
880 <LI>SERVER_PORT
881 <LI>SERVER_PROTOCOL
882 <LI>SERVER_ROOT
883 <LI>SERVER_SOFTWARE
884 </UL>
885 In addition, HTTP headers sent by the server may be passed in the
886 environment as well. Here are some common variable names:
887 <UL>
888 <LI>HTTP_ACCEPT
889 <LI>HTTP_CONNECTION
890 <LI>HTTP_HOST
891 <LI>HTTP_PRAGMA
892 <LI>HTTP_REFERER
893 <LI>HTTP_USER_AGENT
894 </UL>
895 """)
898 # Utilities
899 # =========
901 def escape(s, quote=None):
902 '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
903 If the optional flag quote is true, the quotation mark character (")
904 is also translated.'''
905 s = s.replace("&", "&amp;") # Must be done first!
906 s = s.replace("<", "&lt;")
907 s = s.replace(">", "&gt;")
908 if quote:
909 s = s.replace('"', "&quot;")
910 return s
912 def valid_boundary(s, _vb_pattern="^[ -~]{0,200}[!-~]$"):
913 import re
914 return re.match(_vb_pattern, s)
916 # Invoke mainline
917 # ===============
919 # Call test() when this file is run as a script (not imported as a module)
920 if __name__ == '__main__':
921 test()