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
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
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
0283 return None
0284 if t[-1] is None:
0285
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
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
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
0477 charset = None
0478 unicode_errors = 'strict'
0479 decode_param_names = False
0480
0481
0482
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