~ubuntu-branches/ubuntu/maverick/python3.1/maverick

« back to all changes in this revision

Viewing changes to Lib/cgi.py

  • Committer: Bazaar Package Importer
  • Author(s): Matthias Klose
  • Date: 2009-03-23 00:01:27 UTC
  • Revision ID: james.westby@ubuntu.com-20090323000127-5fstfxju4ufrhthq
Tags: upstream-3.1~a1+20090322
ImportĀ upstreamĀ versionĀ 3.1~a1+20090322

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/local/bin/python
 
2
 
 
3
# NOTE: the above "/usr/local/bin/python" is NOT a mistake.  It is
 
4
# intentionally NOT "/usr/bin/env python".  On many systems
 
5
# (e.g. Solaris), /usr/local/bin is not in $PATH as passed to CGI
 
6
# scripts, and /usr/local/bin is the default directory where Python is
 
7
# installed, so /usr/bin/env would be unable to find python.  Granted,
 
8
# binary installations by Linux vendors often install Python in
 
9
# /usr/bin.  So let those vendors patch cgi.py to match their choice
 
10
# of installation.
 
11
 
 
12
"""Support module for CGI (Common Gateway Interface) scripts.
 
13
 
 
14
This module defines a number of utilities for use by CGI scripts
 
15
written in Python.
 
16
"""
 
17
 
 
18
# History
 
19
# -------
 
20
#
 
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.
 
26
#
 
27
 
 
28
__version__ = "2.6"
 
29
 
 
30
 
 
31
# Imports
 
32
# =======
 
33
 
 
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
 
41
 
 
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"]
 
47
 
 
48
# Logging support
 
49
# ===============
 
50
 
 
51
logfile = ""            # Filename to log to, if not empty
 
52
logfp = None            # File object to log to, if not None
 
53
 
 
54
def initlog(*allargs):
 
55
    """Write a log message, if there is a log file.
 
56
 
 
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).
 
61
 
 
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.
 
66
 
 
67
    If the global logfp is not None, it should be a file object to
 
68
    which log data is written.
 
69
 
 
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).
 
75
 
 
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)
 
88
 
 
89
def dolog(fmt, *args):
 
90
    """Write a log message to the log file.  See initlog() for docs."""
 
91
    logfp.write(fmt%args + "\n")
 
92
 
 
93
def nolog(*allargs):
 
94
    """Dummy function, assigned to log when logging is disabled."""
 
95
    pass
 
96
 
 
97
log = initlog           # The current logging function
 
98
 
 
99
 
 
100
# Parsing functions
 
101
# =================
 
102
 
 
103
# Maximum input we will accept when REQUEST_METHOD is POST
 
104
# 0 ==> unlimited input
 
105
maxlen = 0
 
106
 
 
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)
 
109
 
 
110
        Arguments, all optional:
 
111
 
 
112
        fp              : file pointer; default: sys.stdin
 
113
 
 
114
        environ         : environment dictionary; default: os.environ
 
115
 
 
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.
 
122
 
 
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.
 
126
    """
 
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)
 
158
 
 
159
 
 
160
# parse query string function called from urlparse,
 
161
# this is done in order to maintain backward compatiblity.
 
162
 
 
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)
 
167
    return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
 
168
 
 
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)
 
173
    return urllib.parse.parse_qsl(qs, keep_blank_values, strict_parsing)
 
174
 
 
175
def parse_multipart(fp, pdict):
 
176
    """Parse multipart input.
 
177
 
 
178
    Arguments:
 
179
    fp   : input file
 
180
    pdict: dictionary containing other parameters of content-type header
 
181
 
 
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.
 
188
 
 
189
    XXX This does not parse nested multipart parts -- use FieldStorage for
 
190
    that.
 
191
 
 
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().
 
198
 
 
199
    """
 
200
    import http.client
 
201
 
 
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,))
 
208
 
 
209
    nextpart = "--" + boundary
 
210
    lastpart = "--" + boundary + "--"
 
211
    partdict = {}
 
212
    terminator = ""
 
213
 
 
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]
 
271
 
 
272
    return partdict
 
273
 
 
274
 
 
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:]
 
286
 
 
287
def parse_header(line):
 
288
    """Parse a Content-type like header.
 
289
 
 
290
    Return the main content-type and a dictionary of options.
 
291
 
 
292
    """
 
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
 
306
 
 
307
 
 
308
# Classes for field storage
 
309
# =========================
 
310
 
 
311
class MiniFieldStorage:
 
312
 
 
313
    """Like FieldStorage, for use when no file uploads are possible."""
 
314
 
 
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 = {}
 
324
 
 
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)
 
330
 
 
331
    def __repr__(self):
 
332
        """Return printable representation."""
 
333
        return "MiniFieldStorage(%r, %r)" % (self.name, self.value)
 
334
 
 
335
 
 
336
class FieldStorage:
 
337
 
 
338
    """Store a sequence of fields, reading multipart/form-data.
 
339
 
 
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:
 
346
 
 
347
    name: the field name, if specified; otherwise None
 
348
 
 
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)
 
352
 
 
353
    value: the value as a *string*; for file uploads, this
 
354
        transparently reads the file every time you request the value
 
355
 
 
356
    file: the file(-like) object from which you can read the data;
 
357
        None if the data is stored a simple string
 
358
 
 
359
    type: the content-type, or None if not specified
 
360
 
 
361
    type_options: dictionary of options specified on the content-type
 
362
        line
 
363
 
 
364
    disposition: content-disposition, or None if not specified
 
365
 
 
366
    disposition_options: dictionary of corresponding options
 
367
 
 
368
    headers: a dictionary(-like) object (sometimes email.message.Message or a
 
369
        subclass thereof) containing *all* headers
 
370
 
 
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.
 
376
 
 
377
    """
 
378
 
 
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.
 
382
 
 
383
        Arguments, all optional:
 
384
 
 
385
        fp              : file pointer; default: sys.stdin
 
386
            (not used when the request method is GET)
 
387
 
 
388
        headers         : header dictionary-like object; default:
 
389
            taken from environ as per CGI spec
 
390
 
 
391
        outerboundary   : terminating multipart boundary
 
392
            (for internal use only)
 
393
 
 
394
        environ         : environment dictionary; default: os.environ
 
395
 
 
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.
 
402
 
 
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.
 
406
 
 
407
        """
 
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
 
439
 
 
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']
 
452
 
 
453
        # Process content-type header
 
454
        #
 
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.
 
462
        #
 
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
 
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 __repr__(self):
 
496
        """Return a printable representation."""
 
497
        return "FieldStorage(%r, %r, %r)" % (
 
498
                self.name, self.filename, self.value)
 
499
 
 
500
    def __iter__(self):
 
501
        return iter(self.keys())
 
502
 
 
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
 
515
 
 
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
 
529
 
 
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
 
540
 
 
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
 
551
 
 
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 []
 
562
 
 
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))
 
568
 
 
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)
 
574
 
 
575
    def __len__(self):
 
576
        """Dictionary style len(x) support."""
 
577
        return len(self.keys())
 
578
 
 
579
    def __nonzero__(self):
 
580
        return bool(self.list)
 
581
 
 
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()
 
592
 
 
593
    FieldStorageClass = None
 
594
 
 
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
 
606
 
 
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()
 
621
 
 
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)
 
630
 
 
631
    bufsize = 8*1024            # I/O buffering size for copy to file
 
632
 
 
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)
 
645
 
 
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()
 
653
 
 
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)
 
662
 
 
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)
 
671
 
 
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)
 
703
 
 
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')
 
724
 
 
725
    def make_file(self):
 
726
        """Overridable: return a readable & writable file.
 
727
 
 
728
        The file will be used as follows:
 
729
        - data is written to it
 
730
        - seek(0)
 
731
        - data is read from it
 
732
 
 
733
        The file is always opened in text mode.
 
734
 
 
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.
 
740
 
 
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.
 
746
 
 
747
        """
 
748
        import tempfile
 
749
        return tempfile.TemporaryFile("w+", encoding="utf-8", newline="\n")
 
750
 
 
751
 
 
752
# Test/debug code
 
753
# ===============
 
754
 
 
755
def test(environ=os.environ):
 
756
    """Robust test CGI script, usable as main program.
 
757
 
 
758
    Write minimal HTTP headers and dump all information provided to
 
759
    the script in HTML form.
 
760
 
 
761
    """
 
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):
 
775
            f()
 
776
        print("<H3>What follows is a test, not an actual exception:</H3>")
 
777
        g()
 
778
    except:
 
779
        print_exception()
 
780
 
 
781
    print("<H1>Second try with a small maxlen...</H1>")
 
782
 
 
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()
 
793
 
 
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]),
 
805
        ))
 
806
    del tb
 
807
 
 
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()
 
818
 
 
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()
 
834
 
 
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()
 
846
 
 
847
def print_arguments():
 
848
    print()
 
849
    print("<H3>Command Line Arguments:</H3>")
 
850
    print()
 
851
    print(sys.argv)
 
852
    print()
 
853
 
 
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
""")
 
896
 
 
897
 
 
898
# Utilities
 
899
# =========
 
900
 
 
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
 
911
 
 
912
def valid_boundary(s, _vb_pattern="^[ -~]{0,200}[!-~]$"):
 
913
    import re
 
914
    return re.match(_vb_pattern, s)
 
915
 
 
916
# Invoke mainline
 
917
# ===============
 
918
 
 
919
# Call test() when this file is run as a script (not imported as a module)
 
920
if __name__ == '__main__':
 
921
    test()