Ticket #1201: 1201.2.diff

File 1201.2.diff, 37.7 KB (added by Andy Durdin, 15 years ago)

Updated patch against r10680

  • django/http/multipartparser.py

     
    55file upload handlers for processing.
    66"""
    77
    8 import cgi
     8# Compatibility for python < 2.3.5
     9import sys
     10if sys.version_info < (2, 3, 5):
     11    import django.utils._cgi as cgi
     12else:
     13    import cgi
    914from django.conf import settings
    1015from django.core.exceptions import SuspiciousOperation
    1116from django.utils.datastructures import MultiValueDict
  • django/http/__init__.py

     
    88    # The mod_python version is more efficient, so try importing it first.
    99    from mod_python.util import parse_qsl
    1010except ImportError:
    11     from cgi import parse_qsl
     11    import sys
     12    # Compatibility for python < 2.3.5
     13    if sys.version_info < (2, 3, 5):
     14        from django.utils._cgi import parse_qsl
     15    else:
     16        from cgi import parse_qsl
    1217
    1318from django.utils.datastructures import MultiValueDict, ImmutableList
    1419from django.utils.encoding import smart_str, iri_to_uri, force_unicode
  • django/core/cache/__init__.py

     
    1515See docs/cache.txt for information on the public API.
    1616"""
    1717
    18 from cgi import parse_qsl
     18import sys
     19# Compatibility for python < 2.3.5
     20if sys.version_info < (2, 3, 5):
     21    from django.utils._cgi import parse_qsl
     22else:
     23    from cgi import parse_qsl
    1924from django.conf import settings
    2025from django.core import signals
    2126from django.core.cache.backends.base import InvalidCacheBackendError
     
    4752    host = rest[2:]
    4853    qpos = rest.find('?')
    4954    if qpos != -1:
    50         params = dict(parse_qsl(rest[qpos+1:]))
     55        params = dict(parse_qsl(rest[qpos+1:], True)) # keep_blank_values=True
    5156        host = rest[2:qpos]
    5257    else:
    5358        params = {}
  • django/utils/_cgi.py

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

     
    99import timesince
    1010import datastructures
    1111import itercompat
     12import cgicompat
    1213from decorators import DecoratorFromMiddlewareTests
    1314
    1415# We need this because "datastructures" uses sorted() and the tests are run in
     
    2324    'timesince': timesince,
    2425    'datastructures': datastructures,
    2526    'itercompat': itercompat,
     27    'cgicompat': cgicompat,
    2628}
    2729
    2830class TestUtilsHtml(TestCase):
  • tests/regressiontests/utils/cgicompat.py

     
     1"""
     2# Test of the utils _cgi library for python < 2.3.5 compatibility. See #1201
     3# Check that parse_qsl correctly handles names without values
     4
     5>>> import sys
     6>>> if sys.version_info < (2, 3, 5):
     7...     import _cgi as cgi
     8... else:
     9...     import cgi
     10...
     11>>> cgi.parse_qsl("name", True)
     12[('name', '')]
     13
     14"""
Back to Top