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