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