|
32 | 32 | telling the client what kind of data is following. Python code to |
33 | 33 | generate a minimal header section looks like this: |
34 | 34 |
|
35 | | - print "Content-type: text/html" # HTML is following |
36 | | - print # blank line, end of headers |
| 35 | + print "Content-type: text/html" # HTML is following |
| 36 | + print # blank line, end of headers |
37 | 37 |
|
38 | 38 | The second section is usually HTML, which allows the client software |
39 | 39 | to display nicely formatted text with header, in-line images, etc. |
@@ -503,6 +503,272 @@ def parse_header(line): |
503 | 503 | return key, pdict |
504 | 504 |
|
505 | 505 |
|
| 506 | +# Classes for field storage |
| 507 | +# ========================= |
| 508 | + |
| 509 | +class MiniFieldStorage: |
| 510 | + |
| 511 | + """Internal: dummy FieldStorage, used with query string format.""" |
| 512 | + |
| 513 | + def __init__(self, name, value): |
| 514 | + """Constructor from field name and value.""" |
| 515 | + self.name = name |
| 516 | + self.value = value |
| 517 | + from StringIO import StringIO |
| 518 | + self.filename = None |
| 519 | + self.list = None |
| 520 | + self.file = StringIO(value) |
| 521 | + |
| 522 | + def __repr__(self): |
| 523 | + """Return printable representation.""" |
| 524 | + return "MiniFieldStorage(%s, %s)" % (`self.name`, |
| 525 | + `self.value`) |
| 526 | + |
| 527 | + |
| 528 | +class FieldStorage: |
| 529 | + |
| 530 | + """Store a sequence of fields, reading multipart/form-data.""" |
| 531 | + |
| 532 | + def __init__(self, fp=None, headers=None, outerboundary=""): |
| 533 | + """Constructor. Read multipart/* until last part.""" |
| 534 | + method = None |
| 535 | + if environ.has_key('REQUEST_METHOD'): |
| 536 | + method = string.upper(environ['REQUEST_METHOD']) |
| 537 | + if not fp and method == 'GET': |
| 538 | + qs = None |
| 539 | + if environ.has_key('QUERY_STRING'): |
| 540 | + qs = environ['QUERY_STRING'] |
| 541 | + from StringIO import StringIO |
| 542 | + fp = StringIO(qs or "") |
| 543 | + if headers is None: |
| 544 | + headers = {'content-type': |
| 545 | + "application/x-www-form-urlencoded"} |
| 546 | + if headers is None: |
| 547 | + headers = {} |
| 548 | + if environ.has_key('CONTENT_TYPE'): |
| 549 | + headers['content-type'] = environ['CONTENT_TYPE'] |
| 550 | + if environ.has_key('CONTENT_LENGTH'): |
| 551 | + headers['content-length'] = environ['CONTENT_LENGTH'] |
| 552 | + self.fp = fp or sys.stdin |
| 553 | + self.headers = headers |
| 554 | + self.outerboundary = outerboundary |
| 555 | + |
| 556 | + # Process content-disposition header |
| 557 | + cdisp, pdict = "", {} |
| 558 | + if self.headers.has_key('content-disposition'): |
| 559 | + cdisp, pdict = parse_header(self.headers['content-disposition']) |
| 560 | + self.disposition = cdisp |
| 561 | + self.disposition_options = pdict |
| 562 | + self.name = None |
| 563 | + if pdict.has_key('name'): |
| 564 | + self.name = pdict['name'] |
| 565 | + self.filename = None |
| 566 | + if pdict.has_key('filename'): |
| 567 | + self.filename = pdict['filename'] |
| 568 | + |
| 569 | + # Process content-type header |
| 570 | + ctype, pdict = "text/plain", {} |
| 571 | + if self.headers.has_key('content-type'): |
| 572 | + ctype, pdict = parse_header(self.headers['content-type']) |
| 573 | + self.type = ctype |
| 574 | + self.type_options = pdict |
| 575 | + self.innerboundary = "" |
| 576 | + if pdict.has_key('boundary'): |
| 577 | + self.innerboundary = pdict['boundary'] |
| 578 | + clen = -1 |
| 579 | + if self.headers.has_key('content-length'): |
| 580 | + try: |
| 581 | + clen = string.atoi(self.headers['content-length']) |
| 582 | + except: |
| 583 | + pass |
| 584 | + self.length = clen |
| 585 | + |
| 586 | + self.list = self.file = None |
| 587 | + self.done = 0 |
| 588 | + self.lines = [] |
| 589 | + if ctype == 'application/x-www-form-urlencoded': |
| 590 | + self.read_urlencoded() |
| 591 | + elif ctype[:10] == 'multipart/': |
| 592 | + self.read_multi() |
| 593 | + else: |
| 594 | + self.read_single() |
| 595 | + |
| 596 | + def __repr__(self): |
| 597 | + """Return a printable representation.""" |
| 598 | + return "FieldStorage(%s, %s, %s)" % ( |
| 599 | + `self.name`, `self.filename`, `self.value`) |
| 600 | + |
| 601 | + def __getattr__(self, name): |
| 602 | + if name != 'value': |
| 603 | + raise AttributeError, name |
| 604 | + if self.file: |
| 605 | + self.file.seek(0) |
| 606 | + value = self.file.read() |
| 607 | + self.file.seek(0) |
| 608 | + elif self.list is not None: |
| 609 | + value = self.list |
| 610 | + else: |
| 611 | + value = None |
| 612 | + return value |
| 613 | + |
| 614 | + def __getitem__(self, key): |
| 615 | + """Dictionary style indexing.""" |
| 616 | + if self.list is None: |
| 617 | + raise TypeError, "not indexable" |
| 618 | + found = [] |
| 619 | + for item in self.list: |
| 620 | + if item.name == key: found.append(item) |
| 621 | + if not found: |
| 622 | + raise KeyError, key |
| 623 | + return found |
| 624 | + |
| 625 | + def keys(self): |
| 626 | + """Dictionary style keys() method.""" |
| 627 | + if self.list is None: |
| 628 | + raise TypeError, "not indexable" |
| 629 | + keys = [] |
| 630 | + for item in self.list: |
| 631 | + if item.name not in keys: keys.append(item.name) |
| 632 | + return keys |
| 633 | + |
| 634 | + def read_urlencoded(self): |
| 635 | + """Internal: read data in query string format.""" |
| 636 | + qs = self.fp.read(self.length) |
| 637 | + dict = parse_qs(qs) |
| 638 | + self.list = [] |
| 639 | + for key, valuelist in dict.items(): |
| 640 | + for value in valuelist: |
| 641 | + self.list.append(MiniFieldStorage(key, value)) |
| 642 | + self.skip_lines() |
| 643 | + |
| 644 | + def read_multi(self): |
| 645 | + """Internal: read a part that is itself multipart.""" |
| 646 | + import rfc822 |
| 647 | + self.list = [] |
| 648 | + part = self.__class__(self.fp, {}, self.innerboundary) |
| 649 | + # Throw first part away |
| 650 | + while not part.done: |
| 651 | + headers = rfc822.Message(self.fp) |
| 652 | + part = self.__class__(self.fp, headers, self.innerboundary) |
| 653 | + self.list.append(part) |
| 654 | + self.skip_lines() |
| 655 | + |
| 656 | + def read_single(self): |
| 657 | + """Internal: read an atomic part.""" |
| 658 | + if self.length >= 0: |
| 659 | + self.read_binary() |
| 660 | + self.skip_lines() |
| 661 | + else: |
| 662 | + self.read_lines() |
| 663 | + self.file.seek(0) |
| 664 | + |
| 665 | + bufsize = 8*1024 # I/O buffering size for copy to file |
| 666 | + |
| 667 | + def read_binary(self): |
| 668 | + """Internal: read binary data.""" |
| 669 | + self.file = self.make_file('b') |
| 670 | + todo = self.length |
| 671 | + if todo >= 0: |
| 672 | + while todo > 0: |
| 673 | + data = self.fp.read(min(todo, self.bufsize)) |
| 674 | + if not data: |
| 675 | + self.done = -1 |
| 676 | + break |
| 677 | + self.file.write(data) |
| 678 | + todo = todo - len(data) |
| 679 | + |
| 680 | + def read_lines(self): |
| 681 | + """Internal: read lines until EOF or outerboundary.""" |
| 682 | + self.file = self.make_file('') |
| 683 | + if self.outerboundary: |
| 684 | + self.read_lines_to_outerboundary() |
| 685 | + else: |
| 686 | + self.read_lines_to_eof() |
| 687 | + |
| 688 | + def read_lines_to_eof(self): |
| 689 | + """Internal: read lines until EOF.""" |
| 690 | + while 1: |
| 691 | + line = self.fp.readline() |
| 692 | + if not line: |
| 693 | + self.done = -1 |
| 694 | + break |
| 695 | + self.lines.append(line) |
| 696 | + if line[-2:] == '\r\n': |
| 697 | + line = line[:-2] + '\n' |
| 698 | + self.file.write(line) |
| 699 | + |
| 700 | + def read_lines_to_outerboundary(self): |
| 701 | + """Internal: read lines until outerboundary.""" |
| 702 | + next = "--" + self.outerboundary |
| 703 | + last = next + "--" |
| 704 | + delim = "" |
| 705 | + while 1: |
| 706 | + line = self.fp.readline() |
| 707 | + if not line: |
| 708 | + self.done = -1 |
| 709 | + break |
| 710 | + self.lines.append(line) |
| 711 | + if line[:2] == "--": |
| 712 | + strippedline = string.strip(line) |
| 713 | + if strippedline == next: |
| 714 | + break |
| 715 | + if strippedline == last: |
| 716 | + self.done = 1 |
| 717 | + break |
| 718 | + if line[-2:] == "\r\n": |
| 719 | + line = line[:-2] |
| 720 | + elif line[-1] == "\n": |
| 721 | + line = line[:-1] |
| 722 | + self.file.write(delim + line) |
| 723 | + delim = "\n" |
| 724 | + |
| 725 | + def skip_lines(self): |
| 726 | + """Internal: skip lines until outer boundary if defined.""" |
| 727 | + if not self.outerboundary or self.done: |
| 728 | + return |
| 729 | + next = "--" + self.outerboundary |
| 730 | + last = next + "--" |
| 731 | + while 1: |
| 732 | + line = self.fp.readline() |
| 733 | + if not line: |
| 734 | + self.done = -1 |
| 735 | + break |
| 736 | + self.lines.append(line) |
| 737 | + if line[:2] == "--": |
| 738 | + strippedline = string.strip(line) |
| 739 | + if strippedline == next: |
| 740 | + break |
| 741 | + if strippedline == last: |
| 742 | + self.done = 1 |
| 743 | + break |
| 744 | + |
| 745 | + def make_file(self, binary): |
| 746 | + """Overridable: return a readable & writable file. |
| 747 | + |
| 748 | + The file will be used as follows: |
| 749 | + - data is written to it |
| 750 | + - seek(0) |
| 751 | + - data is read from it |
| 752 | + |
| 753 | + The 'binary' argument is 'b' if the file should be created in |
| 754 | + binary mode (on non-Unix systems), '' otherwise. |
| 755 | + |
| 756 | + The intention is that you can override this method to selectively |
| 757 | + create a real (temporary) file or use a memory file dependent on |
| 758 | + the perceived size of the file or the presence of a filename, etc. |
| 759 | + |
| 760 | + """ |
| 761 | + |
| 762 | + # Prefer ArrayIO over StringIO, if it's available |
| 763 | + try: |
| 764 | + from ArrayIO import ArrayIO |
| 765 | + ioclass = ArrayIO |
| 766 | + except ImportError: |
| 767 | + from StringIO import StringIO |
| 768 | + ioclass = StringIO |
| 769 | + return ioclass() |
| 770 | + |
| 771 | + |
506 | 772 | # Main classes |
507 | 773 | # ============ |
508 | 774 |
|
@@ -636,7 +902,7 @@ def test(): |
636 | 902 | sys.stderr = sys.stdout |
637 | 903 | try: |
638 | 904 | print_environ() |
639 | | - print_form(FormContentDict()) |
| 905 | + print_form(FieldStorage()) |
640 | 906 | print |
641 | 907 | print "<H3>Current Working Directory:</H3>" |
642 | 908 | try: |
@@ -671,8 +937,9 @@ def print_form(form): |
671 | 937 | print "<DL>" |
672 | 938 | for key in keys: |
673 | 939 | print "<DT>" + escape(key) + ":", |
674 | | - print "<i>" + escape(`type(form[key])`) + "</i>" |
675 | | - print "<DD>" + escape(`form[key]`) |
| 940 | + value = form[key] |
| 941 | + print "<i>" + escape(`type(value)`) + "</i>" |
| 942 | + print "<DD>" + escape(`value`) |
676 | 943 | print "</DL>" |
677 | 944 | print |
678 | 945 |
|
|
0 commit comments