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
14This module defines a number of utilities for use by CGI scripts
15written in Python.
16
17The global variable maxlen can be set to an integer indicating the maximum size
18of a POST request. POST requests larger than this size will result in a
19ValueError being raised during parsing. The default value of this variable is 0,
20meaning 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
39from io import StringIO, BytesIO, TextIOWrapper
40from collections.abc import Mapping
41import sys
42import os
43import urllib.parse
44from email.parser import FeedParser
45from email.message import Message
46import html
47import locale
48import tempfile
49import 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
57warnings._deprecated(__name__, remove=(3,13))
58
59# Logging support
60# ===============
61
62logfile = ""            # Filename to log to, if not empty
63logfp = None            # File object to log to, if not None
64
65def 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
102def dolog(fmt, *args):
103    """Write a log message to the log file.  See initlog() for docs."""
104    logfp.write(fmt%args + "\n")
105
106def nolog(*allargs):
107    """Dummy function, assigned to log when logging is disabled."""
108    pass
109
110def 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
119log = 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
127maxlen = 0
128
129def 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
199def 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
226def _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
238def 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
262class 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
287class 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
853def 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
892def 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
906def 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
917def 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
933def 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
945def print_arguments():
946    print()
947    print("<H3>Command Line Arguments:</H3>")
948    print()
949    print(sys.argv)
950    print()
951
952def 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>
983In addition, HTTP headers sent by the server may be passed in the
984environment 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
999def 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)
1011if __name__ == '__main__':
1012    test()
1013