0001from cStringIO import StringIO
0002import sys
0003import cgi
0004import urllib
0005import urlparse
0006import re
0007import textwrap
0008from Cookie import BaseCookie
0009from rfc822 import parsedate_tz, mktime_tz, formatdate
0010from datetime import datetime, date, timedelta, tzinfo
0011import time
0012import calendar
0013import tempfile
0014import warnings
0015from webob.datastruct import EnvironHeaders
0016from webob.multidict import MultiDict, UnicodeMultiDict, NestedMultiDict, NoVars
0017from webob.etag import AnyETag, NoETag, ETagMatcher, IfRange, NoIfRange
0018from webob.headerdict import HeaderDict
0019from webob.statusreasons import status_reasons
0020from webob.cachecontrol import CacheControl, serialize_cache_control
0021from webob.acceptparse import Accept, MIMEAccept, NilAccept, MIMENilAccept, NoAccept
0022from webob.byterange import Range, ContentRange
0023try:
0024    sorted
0025except NameError:
0026    from webob.compat import sorted
0027
0028_CHARSET_RE = re.compile(r';\s*charset=([^;]*)', re.I)
0029_SCHEME_RE = re.compile(r'^[a-z]+:', re.I)
0030_PARAM_RE = re.compile(r'([a-z0-9]+)=(?:"([^"]*)"|([a-z0-9_.-]*))', re.I)
0031_OK_PARAM_RE = re.compile(r'^[a-z0-9_.-]+$', re.I)
0032
0033__all__ = ['Request', 'Response', 'UTC', 'day', 'week', 'hour', 'minute', 'second', 'month', 'year', 'html_escape']
0034
0035class _UTC(tzinfo):
0036    def dst(self, dt):
0037        return timedelta(0)
0038    def utcoffset(self, dt):
0039        return timedelta(0)
0040    def tzname(self, dt):
0041        return 'UTC'
0042    def __repr__(self):
0043        return 'UTC'
0044
0045UTC = _UTC()
0046
0047def html_escape(s):
0048    """HTML-escape a string or object
0049
0050    This converts any non-string objects passed into it to strings
0051    (actually, using ``unicode()``).  All values returned are
0052    non-unicode strings (using ``&#num;`` entities for all non-ASCII
0053    characters).
0054
0055    None is treated specially, and returns the empty string.
0056    """
0057    if s is None:
0058        return ''
0059    if not isinstance(s, basestring):
0060        if hasattr(s, '__unicode__'):
0061            s = unicode(s)
0062        else:
0063            s = str(s)
0064    s = cgi.escape(s, True)
0065    if isinstance(s, unicode):
0066        s = s.encode('ascii', 'xmlcharrefreplace')
0067    return s
0068
0069def timedelta_to_seconds(td):
0070    """
0071    Converts a timedelta instance to seconds.
0072    """
0073    return td.seconds + (td.days*24*60*60)
0074
0075day = timedelta(days=1)
0076week = timedelta(weeks=1)
0077hour = timedelta(hours=1)
0078minute = timedelta(minutes=1)
0079second = timedelta(seconds=1)
0080# Estimate, I know; good enough for expirations
0081month = timedelta(days=30)
0082year = timedelta(days=365)
0083
0084class _NoDefault:
0085    def __repr__(self):
0086        return '(No Default)'
0087NoDefault = _NoDefault()
0088
0089class environ_getter(object):
0090    """For delegating an attribute to a key in self.environ."""
0091
0092    def __init__(self, key, default='', default_factory=None,
0093                 settable=True, deletable=True, doc=None,
0094                 rfc_section=None):
0095        self.key = key
0096        self.default = default
0097        self.default_factory = default_factory
0098        self.settable = settable
0099        self.deletable = deletable
0100        docstring = "Gets"
0101        if self.settable:
0102            docstring += " and sets"
0103        if self.deletable:
0104            docstring += " and deletes"
0105        docstring += " the %r key from the environment." % self.key
0106        docstring += _rfc_reference(self.key, rfc_section)
0107        if doc:
0108            docstring += '\n\n' + textwrap.dedent(doc)
0109        self.__doc__ = docstring
0110
0111    def __get__(self, obj, type=None):
0112        if obj is None:
0113            return self
0114        if self.key not in obj.environ:
0115            if self.default_factory:
0116                val = obj.environ[self.key] = self.default_factory()
0117                return val
0118            else:
0119                return self.default
0120        return obj.environ[self.key]
0121
0122    def __set__(self, obj, value):
0123        if not self.settable:
0124            raise AttributeError("Read-only attribute (key %r)" % self.key)
0125        if value is None:
0126            if self.key in obj.environ:
0127                del obj.environ[self.key]
0128        else:
0129            obj.environ[self.key] = value
0130
0131    def __delete__(self, obj):
0132        if not self.deletable:
0133            raise AttributeError("You cannot delete the key %r" % self.key)
0134        del obj.environ[self.key]
0135
0136    def __repr__(self):
0137        return '<Proxy for WSGI environ %r key>' % self.key
0138
0139class header_getter(object):
0140    """For delegating an attribute to a header in self.headers"""
0141
0142    def __init__(self, header, default=None,
0143                 settable=True, deletable=True, doc=None, rfc_section=None):
0144        self.header = header
0145        self.default = default
0146        self.settable = settable
0147        self.deletable = deletable
0148        docstring = "Gets"
0149        if self.settable:
0150            docstring += " and sets"
0151        if self.deletable:
0152            docstring += " and deletes"
0153        docstring += " they header %s from the headers" % self.header
0154        docstring += _rfc_reference(self.header, rfc_section)
0155        if doc:
0156            docstring += '\n\n' + textwrap.dedent(doc)
0157        self.__doc__ = docstring
0158
0159    def __get__(self, obj, type=None):
0160        if obj is None:
0161            return self
0162        if self.header not in obj.headers:
0163            return self.default
0164        else:
0165            return obj.headers[self.header]
0166
0167    def __set__(self, obj, value):
0168        if not self.settable:
0169            raise AttributeError("Read-only attribute (header %s)" % self.header)
0170        if value is None:
0171            if self.header in obj.headers:
0172                del obj.headers[self.header]
0173        else:
0174            if isinstance(value, unicode):
0175                # This is the standard encoding for headers:
0176                value = value.encode('ISO-8859-1')
0177            obj.headers[self.header] = value
0178
0179    def __delete__(self, obj):
0180        if not self.deletable:
0181            raise AttributeError("You cannot delete the header %s" % self.header)
0182        del obj.headers[self.header]
0183
0184    def __repr__(self):
0185        return '<Proxy for header %s>' % self.header
0186
0187class converter(object):
0188    """
0189    Wraps a decorator, and applies conversion for that decorator
0190    """
0191    def __init__(self, decorator, getter_converter, setter_converter, convert_name=None, doc=None, converter_args=()):
0192        self.decorator = decorator
0193        self.getter_converter = getter_converter
0194        self.setter_converter = setter_converter
0195        self.convert_name = convert_name
0196        self.converter_args = converter_args
0197        docstring = decorator.__doc__ or ''
0198        docstring += "  Converts it as a "
0199        if convert_name:
0200            docstring += convert_name + '.'
0201        else:
0202            docstring += "%r and %r." % (getter_converter, setter_converter)
0203        if doc:
0204            docstring += '\n\n' + textwrap.dedent(doc)
0205        self.__doc__ = docstring
0206
0207    def __get__(self, obj, type=None):
0208        if obj is None:
0209            return self
0210        value = self.decorator.__get__(obj, type)
0211        return self.getter_converter(value, *self.converter_args)
0212
0213    def __set__(self, obj, value):
0214        value = self.setter_converter(value, *self.converter_args)
0215        self.decorator.__set__(obj, value)
0216
0217    def __delete__(self, obj):
0218        self.decorator.__delete__(obj)
0219
0220    def __repr__(self):
0221        if self.convert_name:
0222            name = ' %s' % self.convert_name
0223        else:
0224            name = ''
0225        return '<Converted %r%s>' % (self.decorator, name)
0226
0227def _rfc_reference(header, section):
0228    if not section:
0229        return ''
0230    major_section = section.split('.')[0]
0231    link = 'http://www.w3.org/Protocols/rfc2616/rfc2616-sec%s.html#sec%s' % (
0232        major_section, section)
0233    if header.startswith('HTTP_'):
0234        header = header[5:].title().replace('_', '-')
0235    return "  For more information on %s see `section %s <%s>`_." % (
0236        header, section, link)
0237
0238class deprecated_property(object):
0239    """
0240    Wraps a decorator, with a deprecation warning or error
0241    """
0242    def __init__(self, decorator, attr, message, warning=True):
0243        self.decorator = decorator
0244        self.attr = attr
0245        self.message = message
0246        self.warning = warning
0247
0248    def __get__(self, obj, type=None):
0249        if obj is None:
0250            return self
0251        self.warn()
0252        return self.decorator.__get__(obj, type)
0253
0254    def __set__(self, obj, value):
0255        self.warn()
0256        self.decorator.__set__(obj, value)
0257
0258    def __delete__(self, obj):
0259        self.warn()
0260        self.decorator.__delete__(obj)
0261
0262    def __repr__(self):
0263        return '<Deprecated attribute %s: %r>' % (
0264            self.attr,
0265            self.decorator)
0266
0267    def warn(self):
0268        if not self.warning:
0269            raise DeprecationWarning(
0270                'The attribute %s is deprecated: %s' % (self.attr, self.message))
0271        else:
0272            warnings.warn(
0273                'The attribute %s is deprecated: %s' % (self.attr, self.message),
0274                DeprecationWarning,
0275                stacklevel=3)
0276
0277def _parse_date(value):
0278    if not value:
0279        return None
0280    t = parsedate_tz(value)
0281    if t is None:
0282        # Could not parse
0283        return None
0284    if t[-1] is None:
0285        # No timezone given.  None would mean local time, but we'll force UTC
0286        t = t[:9] + (0,)
0287    t = mktime_tz(t)
0288    return datetime.fromtimestamp(t, UTC)
0289
0290def _serialize_date(dt):
0291    if dt is None:
0292        return None
0293    if isinstance(dt, unicode):
0294        dt = dt.encode('ascii')
0295    if isinstance(dt, str):
0296        return dt
0297    if isinstance(dt, timedelta):
0298        dt = datetime.now() + dt
0299    if isinstance(dt, (datetime, date)):
0300        dt = dt.timetuple()
0301    if isinstance(dt, (tuple, time.struct_time)):
0302        dt = calendar.timegm(dt)
0303    if not isinstance(dt, (float, int)):
0304        raise ValueError(
0305            "You must pass in a datetime, date, time tuple, or integer object, not %r" % dt)
0306    return formatdate(dt)
0307
0308def _serialize_cookie_date(dt):
0309    if dt is None:
0310        return None
0311    if isinstance(dt, unicode):
0312        dt = dt.encode('ascii')
0313    if isinstance(dt, timedelta):
0314        dt = datetime.now() + dt
0315    if isinstance(dt, (datetime, date)):
0316        dt = dt.timetuple()
0317    return time.strftime('%a, %d-%b-%Y %H:%M:%S GMT', dt)
0318
0319def _parse_date_delta(value):
0320    """
0321    like _parse_date, but also handle delta seconds
0322    """
0323    if not value:
0324        return None
0325    try:
0326        value = int(value)
0327    except ValueError:
0328        pass
0329    else:
0330        delta = timedelta(seconds=value)
0331        return datetime.now() + delta
0332    return _parse_date(value)
0333
0334def _serialize_date_delta(value):
0335    if not value and value != 0:
0336        return None
0337    if isinstance(value, (float, int)):
0338        return str(int(value))
0339    return _serialize_date(value)
0340
0341def _parse_etag(value, default=True):
0342    if value is None:
0343        value = ''
0344    value = value.strip()
0345    if not value:
0346        if default:
0347            return AnyETag
0348        else:
0349            return NoETag
0350    if value == '*':
0351        return AnyETag
0352    else:
0353        return ETagMatcher.parse(value)
0354
0355def _serialize_etag(value, default=True):
0356    if value is None:
0357        return None
0358    if value is AnyETag:
0359        if default:
0360            return None
0361        else:
0362            return '*'
0363    return str(value)
0364
0365def _parse_if_range(value):
0366    if not value:
0367        return NoIfRange
0368    else:
0369        return IfRange.parse(value)
0370
0371def _serialize_if_range(value):
0372    if value is None:
0373        return value
0374    if isinstance(value, (datetime, date)):
0375        return _serialize_date(value)
0376    if not isinstance(value, str):
0377        value = str(value)
0378    return value or None
0379
0380def _parse_range(value):
0381    if not value:
0382        return None
0383    # Might return None too:
0384    return Range.parse(value)
0385
0386def _serialize_range(value):
0387    if isinstance(value, (list, tuple)):
0388        if len(value) != 2:
0389            raise ValueError(
0390                "If setting .range to a list or tuple, it must be of length 2 (not %r)"
0391                % value)
0392        value = Range([value])
0393    if value is None:
0394        return None
0395    value = str(value)
0396    return value or None
0397
0398def _parse_int(value):
0399    if value is None or value == '':
0400        return None
0401    return int(value)
0402
0403def _parse_int_safe(value):
0404    if value is None or value == '':
0405        return None
0406    try:
0407        return int(value)
0408    except ValueError:
0409        return None
0410
0411def _serialize_int(value):
0412    if value is None:
0413        return None
0414    return str(value)
0415
0416def _parse_content_range(value):
0417    if not value or not value.strip():
0418        return None
0419    # May still return None
0420    return ContentRange.parse(value)
0421
0422def _serialize_content_range(value):
0423    if value is None:
0424        return None
0425    if isinstance(value, (tuple, list)):
0426        if len(value) not in (2, 3):
0427            raise ValueError(
0428                "When setting content_range to a list/tuple, it must "
0429                "be length 2 or 3 (not %r)" % value)
0430        if len(value) == 2:
0431            begin, end = value
0432            length = None
0433        else:
0434            begin, end, length = value
0435        value = ContentRange(begin, end, length)
0436    value = str(value).strip()
0437    if not value:
0438        return None
0439    return value
0440
0441def _parse_list(value):
0442    if value is None:
0443        return None
0444    value = value.strip()
0445    if not value:
0446        return None
0447    return [v.strip() for v in value.split(',')
0448            if v.strip()]
0449
0450def _serialize_list(value):
0451    if not value:
0452        return None
0453    if isinstance(value, unicode):
0454        value = str(value)
0455    if isinstance(value, str):
0456        return value
0457    return ', '.join(map(str, value))
0458
0459def _parse_accept(value, header_name, AcceptClass, NilClass):
0460    if not value:
0461        return NilClass(header_name)
0462    return AcceptClass(header_name, value)
0463
0464def _serialize_accept(value, header_name, AcceptClass, NilClass):
0465    if not value or isinstance(value, NilClass):
0466        return None
0467    if isinstance(value, (list, tuple, dict)):
0468        value = NilClass(header_name) + value
0469    value = str(value).strip()
0470    if not value:
0471        return None
0472    return value
0473
0474class Request(object):
0475
0476    ## Options:
0477    charset = None
0478    unicode_errors = 'strict'
0479    decode_param_names = False
0480    ## The limit after which request bodies should be stored on disk
0481    ## if they are read in (under this, and the request body is stored
0482    ## in memory):
0483    request_body_tempfile_limit = 10*1024
0484
0485    def __init__(self, environ=None, environ_getter=None, charset=NoDefault, unicode_errors=NoDefault,
0486                 decode_param_names=NoDefault):
0487        if environ is None and environ_getter is None:
0488            raise TypeError(
0489                "You must provide one of environ or environ_getter")
0490        if