updownserver 1.0.2__py3-none-any.whl

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