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 environ is not None and environ_getter is not None:
0491 raise TypeError(
0492 "You can only provide one of the environ and environ_getter arguments")
0493 if environ is None:
0494 self._environ_getter = environ_getter
0495 else:
0496 if not isinstance(environ, dict):
0497 raise TypeError(
0498 "Bad type for environ: %s" % type(environ))
0499 self._environ = environ
0500 if charset is not NoDefault:
0501 self.__dict__['charset'] = charset
0502 if unicode_errors is not NoDefault:
0503 self.__dict__['unicode_errors'] = unicode_errors
0504 if decode_param_names is not NoDefault:
0505 self.__dict__['decode_param_names'] = decode_param_names
0506
0507 def __setattr__(self, attr, value, DEFAULT=[]):
0508
0509 if getattr(self.__class__, attr, DEFAULT) is not DEFAULT or attr.startswith('_'):
0510 object.__setattr__(self, attr, value)
0511 else:
0512 self.environ.setdefault('webob.adhoc_attrs', {})[attr] = value
0513
0514 def __getattr__(self, attr):
0515
0516 if attr in self.__class__.__dict__:
0517 return object.__getattribute__(self, attr)
0518 try:
0519 return self.environ['webob.adhoc_attrs'][attr]
0520 except KeyError:
0521 raise AttributeError(attr)
0522
0523 def __delattr__(self, attr):
0524
0525 if attr in self.__class__.__dict__:
0526 return object.__delattr__(self, attr)
0527 try:
0528 del self.environ['webob.adhoc_attrs'][attr]
0529 except KeyError:
0530 raise AttributeError(attr)
0531
0532 def environ(self):
0533 """
0534 The WSGI environment dictionary for this request
0535 """
0536 return self._environ_getter()
0537 environ = property(environ, doc=environ.__doc__)
0538
0539 def _environ_getter(self):
0540 return self._environ
0541
0542 def _body_file__get(self):
0543 """
0544 Access the body of the request (wsgi.input) as a file-like
0545 object.
0546
0547 If you set this value, CONTENT_LENGTH will also be updated
0548 (either set to -1, 0 if you delete the attribute, or if you
0549 set the attribute to a string then the length of the string).
0550 """
0551 return self.environ['wsgi.input']
0552 def _body_file__set(self, value):
0553 if isinstance(value, str):
0554 length = len(value)
0555 value = StringIO(value)
0556 else:
0557 length = -1
0558 self.environ['wsgi.input'] = value
0559 self.environ['CONTENT_LENGTH'] = str(length)
0560 def _body_file__del(self):
0561 self.environ['wsgi.input'] = StringIO('')
0562 self.environ['CONTENT_LENGTH'] = '0'
0563 body_file = property(_body_file__get, _body_file__set, _body_file__del, doc=_body_file__get.__doc__)
0564
0565 scheme = environ_getter('wsgi.url_scheme')
0566 method = environ_getter('REQUEST_METHOD')
0567 script_name = environ_getter('SCRIPT_NAME')
0568 path_info = environ_getter('PATH_INFO')
0569
0570 content_type = environ_getter('CONTENT_TYPE', rfc_section='14.17')
0571 content_length = converter(
0572 environ_getter('CONTENT_LENGTH', rfc_section='14.13'),
0573 _parse_int_safe, _serialize_int, 'int')
0574 remote_user = environ_getter('REMOTE_USER', default=None)
0575 remote_addr = environ_getter('REMOTE_ADDR', default=None)
0576 query_string = environ_getter('QUERY_STRING')
0577 server_name = environ_getter('SERVER_NAME')
0578 server_port = converter(
0579 environ_getter('SERVER_PORT'),
0580 _parse_int, _serialize_int, 'int')
0581
0582 _headers = None
0583
0584 def _headers__get(self):
0585 """
0586 All the request headers as a case-insensitive dictionary-like
0587 object.
0588 """
0589 if self._headers is None:
0590 self._headers = EnvironHeaders(self.environ)
0591 return self._headers
0592
0593 def _headers__set(self, value):
0594 self.headers.clear()
0595 self.headers.update(value)
0596
0597 headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
0598
0599 def host_url(self):
0600 """
0601 The URL through the host (no path)
0602 """
0603 e = self.environ
0604 url = e['wsgi.url_scheme'] + '://'
0605 if e.get('HTTP_HOST'):
0606 host = e['HTTP_HOST']
0607 if ':' in host:
0608 host, port = host.split(':', 1)
0609 else:
0610
0611 port = None
0612 else:
0613 host = e['SERVER_NAME']
0614 port = e['SERVER_PORT']
0615 if self.environ['wsgi.url_scheme'] == 'https':
0616 if port == '443':
0617 port = None
0618 elif self.environ['wsgi.url_scheme'] == 'http':
0619 if port == '80':
0620 port = None
0621 url += host
0622 if port:
0623 url += ':%s' % port
0624 return url
0625 host_url = property(host_url, doc=host_url.__doc__)
0626
0627 def application_url(self):
0628 """
0629 The URL including SCRIPT_NAME (no PATH_INFO or query string)
0630 """
0631 return self.host_url + urllib.quote(self.environ.get('SCRIPT_NAME', ''))
0632 application_url = property(application_url, doc=application_url.__doc__)
0633
0634 def path_url(self):
0635 """
0636 The URL including SCRIPT_NAME and PATH_INFO, but not QUERY_STRING
0637 """
0638 return self.application_url + urllib.quote(self.environ.get('PATH_INFO', ''))
0639 path_url = property(path_url, doc=path_url.__doc__)
0640
0641 def path(self):
0642 """
0643 The path of the request, without host or query string
0644 """
0645 return urllib.quote(self.script_name) + urllib.quote(self.path_info)
0646 path = property(path, doc=path.__doc__)
0647
0648 def path_qs(self):
0649 """
0650 The path of the request, without host but with query string
0651 """
0652 path = self.path
0653 qs = self.environ.get('QUERY_STRING')
0654 if qs:
0655 path += '?' + qs
0656 return path
0657 path_qs = property(path_qs, doc=path_qs.__doc__)
0658
0659 def url(self):
0660 """
0661 The full request URL, including QUERY_STRING
0662 """
0663 url = self.path_url
0664 if self.environ.get('QUERY_STRING'):
0665 url += '?' + self.environ['QUERY_STRING']
0666 return url
0667 url = property(url, doc=url.__doc__)
0668
0669 def relative_url(self, other_url, to_application=False):
0670 """
0671 Resolve other_url relative to the request URL.
0672
0673 If ``to_application`` is True, then resolve it relative to the
0674 URL with only SCRIPT_NAME
0675 """
0676 if to_application:
0677 url = self.application_url
0678 if not url.endswith('/'):
0679 url += '/'
0680 else:
0681 url = self.path_url
0682 return urlparse.urljoin(url, other_url)
0683
0684 def path_info_pop(self):
0685 """
0686 'Pops' off the next segment of PATH_INFO, pushing it onto
0687 SCRIPT_NAME, and returning the popped segment. Returns None if
0688 there is nothing left on PATH_INFO.
0689
0690 Does not return ``''`` when there's an empty segment (like
0691 ``/path//path``); these segments are just ignored.
0692 """
0693 path = self.path_info
0694 if not path:
0695 return None
0696 while path.startswith('/'):
0697 self.script_name += '/'
0698 path = path[1:]
0699 if '/' not in path:
0700 self.script_name += path
0701 self.path_info = ''
0702 return path
0703 else:
0704 segment, path = path.split('/', 1)
0705 self.path_info = '/' + path
0706 self.script_name += segment
0707 return segment
0708
0709 def path_info_peek(self):
0710 """
0711 Returns the next segment on PATH_INFO, or None if there is no
0712 next segment. Doesn't modify the environment.
0713 """
0714 path = self.path_info
0715 if not path:
0716 return None
0717 path = path.lstrip('/')
0718 return path.split('/', 1)[0]
0719
0720 def _urlvars__get(self):
0721 """
0722 Return any *named* variables matched in the URL.
0723
0724 Takes values from ``environ['wsgiorg.routing_args']``.
0725 Systems like ``routes`` set this value.
0726 """
0727 if 'paste.urlvars' in self.environ:
0728 return self.environ['paste.urlvars']
0729 elif 'wsgiorg.routing_args' in self.environ:
0730 return self.environ['wsgiorg.routing_args'][1]
0731 else:
0732 result = {}
0733 self.environ['wsgiorg.routing_args'] = ((), result)
0734 return result
0735
0736 def _urlvars__set(self, value):
0737 environ = self.environ
0738 if 'wsgiorg.routing_args' in environ:
0739 environ['wsgiorg.routing_args'] = (environ['wsgiorg.routing_args'][0], value)
0740 if 'paste.urlvars' in environ:
0741 del environ['paste.urlvars']
0742 elif 'paste.urlvars' in environ:
0743 environ['paste.urlvars'] = value
0744 else:
0745 environ['wsgiorg.routing_args'] = ((), value)
0746
0747 def _urlvars__del(self):
0748 if 'paste.urlvars' in self.environ:
0749 del self.environ['paste.urlvars']
0750 if 'wsgiorg.routing_args' in self.environ:
0751 if not self.environ['wsgiorg.routing_args'][0]:
0752 del self.environ['wsgiorg.routing_args']
0753 else:
0754 self.environ['wsgiorg.routing_args'] = (self.environ['wsgiorg.routing_args'][0], {})
0755
0756 urlvars = property(_urlvars__get, _urlvars__set, _urlvars__del, doc=_urlvars__get.__doc__)
0757
0758 def _urlargs__get(self):
0759 """
0760 Return any *positional* variables matched in the URL.
0761
0762 Takes values from ``environ['wsgiorg.routing_args']``.
0763 Systems like ``routes`` set this value.
0764 """
0765 if 'wsgiorg.routing_args' in self.environ:
0766 return self.environ['wsgiorg.routing_args'][0]
0767 else:
0768
0769
0770 return ()
0771
0772 def _urlargs__set(self, value):
0773 environ = self.environ
0774 if 'paste.urlvars' in environ:
0775
0776
0777 routing_args = (value, environ.pop('paste.urlvars'))
0778 elif 'wsgiorg.routing_args' in environ:
0779 routing_args = (value, environ['wsgiorg.routing_args'][1])
0780 else:
0781 routing_args = (value, {})
0782 environ['wsgiorg.routing_args'] = routing_args
0783
0784 def _urlargs__del(self):
0785 if 'wsgiorg.routing_args' in self.environ:
0786 if not self.environ['wsgiorg.routing_args'][1]:
0787 del self.environ['wsgiorg.routing_args']
0788 else:
0789 self.environ['wsgiorg.routing_args'] = ((), self.environ['wsgiorg.routing_args'][1])
0790
0791 urlargs = property(_urlargs__get, _urlargs__set, _urlargs__del, _urlargs__get.__doc__)
0792
0793 def is_xhr(self):
0794 """Returns a boolean if X-Requested-With is present and ``XMLHttpRequest``
0795
0796 Note: this isn't set by every XMLHttpRequest request, it is
0797 only set if you are using a Javascript library that sets it
0798 (or you set the header yourself manually). Currently
0799 Prototype and jQuery are known to set this header."""
0800 return self.environ.get('HTTP_X_REQUESTED_WITH', '') == 'XMLHttpRequest'
0801 is_xhr = property(is_xhr, doc=is_xhr.__doc__)
0802
0803 def _host__get(self):
0804 """Host name provided in HTTP_HOST, with fall-back to SERVER_NAME"""
0805 if 'HTTP_HOST' in self.environ:
0806 return self.environ['HTTP_HOST']
0807 else:
0808 return '%(SERVER_NAME)s:%(SERVER_PORT)s' % self.environ
0809 def _host__set(self, value):
0810 self.environ['HTTP_HOST'] = value
0811 def _host__del(self):
0812 if 'HTTP_HOST' in self.environ:
0813 del self.environ['HTTP_HOST']
0814 host = property(_host__get, _host__set, _host__del, doc=_host__get.__doc__)
0815
0816 def _body__get(self):
0817 """
0818 Return the content of the request body.
0819 """
0820 try:
0821 length = int(self.environ.get('CONTENT_LENGTH', '0'))
0822 except ValueError:
0823 return ''
0824 c = self.body_file.read(length)
0825 tempfile_limit = self.request_body_tempfile_limit
0826 if tempfile_limit and len(c) > tempfile_limit:
0827 fileobj = tempfile.TemporaryFile()
0828 fileobj.write(c)
0829 fileobj.seek(0)
0830 else:
0831 fileobj = StringIO(c)
0832
0833
0834 self.environ['wsgi.input'] = fileobj
0835 return c
0836
0837 def _body__set(self, value):
0838 if value is None:
0839 del self.body
0840 return
0841 if not isinstance(value, str):
0842 raise TypeError(
0843 "You can only set Request.body to a str (not %r)" % type(value))
0844 body_file = StringIO(value)
0845 self.body_file = body_file
0846 self.environ['CONTENT_LENGTH'] = str(len(value))
0847
0848 def _body__del(self, value):
0849 del self.body_file
0850
0851 body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
0852
0853 def str_POST(self):
0854 """
0855 Return a MultiDict containing all the variables from a POST
0856 form request. Does *not* return anything for non-POST
0857 requests or for non-form requests (returns empty dict-like
0858 object in that case).
0859 """
0860 env = self.environ
0861 if self.method != 'POST':
0862 return NoVars('Not a POST request')
0863 if 'webob._parsed_post_vars' in env:
0864 vars, body_file = env['webob._parsed_post_vars']
0865 if body_file is self.body_file:
0866 return vars
0867
0868 if 'paste.parsed_formvars' in env:
0869
0870 vars, body_file = env['paste.parsed_formvars']
0871 if body_file is self.body_file:
0872
0873 return vars
0874 content_type = self.content_type
0875 if ';' in content_type:
0876 content_type = content_type.split(';', 1)[0]
0877 if content_type not in ('', 'application/x-www-form-urlencoded',
0878 'multipart/form-data'):
0879
0880 return NoVars('Not an HTML form submission (Content-Type: %s)'
0881 % content_type)
0882 if 'CONTENT_LENGTH' not in env:
0883
0884
0885 env['CONTENT_TYPE'] = '0'
0886 fs_environ = env.copy()
0887 fs_environ['QUERY_STRING'] = ''
0888 fs = cgi.FieldStorage(fp=self.body_file,
0889 environ=fs_environ,
0890 keep_blank_values=True)
0891 vars = MultiDict.from_fieldstorage(fs)
0892 FakeCGIBody.update_environ(env, vars)
0893 env['webob._parsed_post_vars'] = (vars, self.body_file)
0894 return vars
0895
0896 str_POST = property(str_POST, doc=str_POST.__doc__)
0897
0898 str_postvars = deprecated_property(str_POST, 'str_postvars',
0899 'use str_POST instead')
0900
0901 def POST(self):
0902 """
0903 Like ``.str_POST``, but may decode values and keys
0904 """
0905 vars = self.str_POST
0906 if self.charset:
0907 vars = UnicodeMultiDict(vars, encoding=self.charset,
0908 errors=self.unicode_errors,
0909 decode_keys=self.decode_param_names)
0910 return vars
0911
0912 POST = property(POST, doc=POST.__doc__)
0913
0914 postvars = deprecated_property(POST, 'postvars',
0915 'use POST instead')
0916
0917 def str_GET(self):
0918 """
0919 Return a MultiDict containing all the variables from the
0920 QUERY_STRING.
0921 """
0922 env = self.environ
0923 source = env.get('QUERY_STRING', '')
0924 if 'webob._parsed_query_vars' in env:
0925 vars, qs = env['webob._parsed_query_vars']
0926 if qs == source:
0927 return vars
0928 if not source:
0929 vars = MultiDict()
0930 else:
0931 vars = MultiDict(cgi.parse_qsl(
0932 source, keep_blank_values=True,
0933 strict_parsing=False))
0934 env['webob._parsed_query_vars'] = (vars, source)
0935 return vars
0936
0937 str_GET = property(str_GET, doc=str_GET.__doc__)
0938
0939 str_queryvars = deprecated_property(str_GET, 'str_queryvars',
0940 'use str_GET instead')
0941
0942
0943 def GET(self):
0944 """
0945 Like ``.str_GET``, but may decode values and keys
0946 """
0947 vars = self.str_GET
0948 if self.charset:
0949 vars = UnicodeMultiDict(vars, encoding=self.charset,
0950 errors=self.unicode_errors,
0951 decode_keys=self.decode_param_names)
0952 return vars
0953
0954 GET = property(GET, doc=GET.__doc__)
0955
0956 queryvars = deprecated_property(GET, 'queryvars',
0957 'use GET instead')
0958
0959 def str_params(self):
0960 """
0961 A dictionary-like object containing both the parameters from
0962 the query string and request body.
0963 """
0964 return NestedMultiDict(self.str_GET, self.str_POST)
0965
0966 str_params = property(str_params, doc=str_params.__doc__)
0967
0968 def params(self):
0969 """
0970 Like ``.str_params``, but may decode values and keys
0971 """
0972 params = self.str_params
0973 if self.charset:
0974 params = UnicodeMultiDict(params, encoding=self.charset,
0975 errors=self.unicode_errors,
0976 decode_keys=self.decode_param_names)
0977 return params
0978
0979 params = property(params, doc=params.__doc__)
0980
0981 _rx_quotes = re.compile('"(.*)"')
0982
0983 def str_cookies(self):
0984 """
0985 Return a *plain* dictionary of cookies as found in the request.
0986 """
0987 env = self.environ
0988 source = env.get('HTTP_COOKIE', '')
0989 if 'webob._parsed_cookies' in env:
0990 vars, var_source = env['webob._parsed_cookies']
0991 if var_source == source:
0992 return vars
0993 vars = {}
0994 if source:
0995 cookies = BaseCookie()
0996 cookies.load(source)
0997 for name in cookies:
0998 value = cookies[name].value
0999 unquote_match = self._rx_quotes.match(value)
1000 if unquote_match is not None:
1001 value = unquote_match.group(1)
1002 vars[name] = value
1003 env['webob._parsed_cookies'] = (vars, source)
1004 return vars
1005
1006 str_cookies = property(str_cookies, doc=str_cookies.__doc__)
1007
1008 def cookies(self):
1009 """
1010 Like ``.str_cookies``, but may decode values and keys
1011 """
1012 vars = self.str_cookies
1013 if self.charset:
1014 vars = UnicodeMultiDict(vars, encoding=self.charset,
1015 errors=self.unicode_errors,
1016 decode_keys=self.decode_param_names)
1017 return vars
1018
1019 cookies = property(cookies, doc=cookies.__doc__)
1020
1021 def copy(self):
1022 """
1023 Copy the request and environment object.
1024
1025 This only does a shallow copy, except of wsgi.input
1026 """
1027 env = self.environ.copy()
1028 data = self.body
1029 tempfile_limit = self.request_body_tempfile_limit
1030 if tempfile_limit and len(data) > tempfile_limit:
1031 fileobj = tempfile.TemporaryFile()
1032 fileobj.write(data)
1033 fileobj.seek(0)
1034 else:
1035 fileobj = StringIO(data)
1036 env['wsgi.input'] = fileobj
1037 return self.__class__(env)
1038
1039 def copy_get(self):
1040 """
1041 Copies the request and environment object, but turning this request
1042 into a GET along the way. If this was a POST request (or any other verb)
1043 then it becomes GET, and the request body is thrown away.
1044 """
1045 env = self.environ.copy()
1046 env['wsgi.input'] = StringIO('')
1047 env['CONTENT_LENGTH'] = '0'
1048 if 'CONTENT_TYPE' in env:
1049 del env['CONTENT_TYPE']
1050 env['REQUEST_METHOD'] = 'GET'
1051 return self.__class__(env)
1052
1053 def remove_conditional_headers(self, remove_encoding=True):
1054 """
1055 Remove headers that make the request conditional.
1056
1057 These headers can cause the response to be 304 Not Modified,
1058 which in some cases you may not want to be possible.
1059
1060 This does not remove headers like If-Match, which are used for
1061 conflict detection.
1062 """
1063 for key in ['HTTP_IF_MATCH', 'HTTP_IF_MODIFIED_SINCE',
1064 'HTTP_IF_RANGE', 'HTTP_RANGE']:
1065 if key in self.environ:
1066 del self.environ[key]
1067 if remove_encoding:
1068 if 'HTTP_ACCEPT_ENCODING' in self.environ:
1069 del self.environ['HTTP_ACCEPT_ENCODING']
1070
1071 accept = converter(
1072 environ_getter('HTTP_ACCEPT', rfc_section='14.1'),
1073 _parse_accept, _serialize_accept, 'MIME Accept',
1074 converter_args=('Accept', MIMEAccept, MIMENilAccept))
1075
1076 accept_charset = converter(
1077 environ_getter('HTTP_ACCEPT_CHARSET', rfc_section='14.2'),
1078 _parse_accept, _serialize_accept, 'accept header',
1079 converter_args=('Accept-Charset', Accept, NilAccept))
1080
1081 accept_encoding = converter(
1082 environ_getter('HTTP_ACCEPT_ENCODING', rfc_section='14.3'),
1083 _parse_accept, _serialize_accept, 'accept header',
1084 converter_args=('Accept-Encoding', Accept, NoAccept))
1085
1086 accept_language = converter(
1087 environ_getter('HTTP_ACCEPT_LANGUAGE', rfc_section='14.4'),
1088 _parse_accept, _serialize_accept, 'accept header',
1089 converter_args=('Accept-Language', Accept, NilAccept))
1090
1091
1092
1093
1094 def _cache_control__get(self):
1095 """
1096 Get/set/modify the Cache-Control header (section `14.9
1097 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
1098 """
1099 env = self.environ
1100 value = env.get('HTTP_CACHE_CONTROL', '')
1101 cache_header, cache_obj = env.get('webob._cache_control', (None, None))
1102 if cache_obj is not None and cache_header == value:
1103 return cache_obj
1104 cache_obj = CacheControl.parse(value, type='request')
1105 env['webob._cache_control'] = (value, cache_obj)
1106 return cache_obj
1107
1108 def _cache_control__set(self, value):
1109 env = self.environ
1110 if not value:
1111 value = ""
1112 if isinstance(value, dict):
1113 value = CacheControl(value, type='request')
1114 elif isinstance(value, CacheControl):
1115 str_value = str(value)
1116 env['HTTP_CACHE_CONTROL'] = str_value
1117 env['webob._cache_control'] = (str_value, value)
1118 else:
1119 env['HTTP_CACHE_CONTROL'] = str(value)
1120 if 'webob._cache_control' in env:
1121 del env['webob._cache_control']
1122
1123 def _cache_control__del(self, value):
1124 env = self.environ
1125 if 'HTTP_CACHE_CONTROL' in env:
1126 del env['HTTP_CACHE_CONTROL']
1127 if 'webob._cache_control' in env:
1128 del env['webob._cache_control']
1129
1130 cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
1131
1132 date = converter(
1133 environ_getter('HTTP_DATE', rfc_section='14.8'),
1134 _parse_date, _serialize_date, 'HTTP date')
1135
1136 if_match = converter(
1137 environ_getter('HTTP_IF_MATCH', rfc_section='14.24'),
1138 _parse_etag, _serialize_etag, 'ETag', converter_args=(True,))
1139
1140 if_modified_since = converter(
1141 environ_getter('HTTP_IF_MODIFIED_SINCE', rfc_section='14.25'),
1142 _parse_date, _serialize_date, 'HTTP date')
1143
1144 if_none_match = converter(
1145 environ_getter('HTTP_IF_NONE_MATCH', rfc_section='14.26'),
1146 _parse_etag, _serialize_etag, 'ETag', converter_args=(False,))
1147
1148 if_range = converter(
1149 environ_getter('HTTP_IF_RANGE', rfc_section='14.27'),
1150 _parse_if_range, _serialize_if_range, 'IfRange object')
1151
1152 if_unmodified_since = converter(
1153 environ_getter('HTTP_IF_UNMODIFIED_SINCE', rfc_section='14.28'),
1154 _parse_date, _serialize_date, 'HTTP date')
1155
1156 max_forwards = converter(
1157 environ_getter('HTTP_MAX_FORWARDS', rfc_section='14.31'),
1158 _parse_int, _serialize_int, 'int')
1159
1160 pragma = environ_getter('HTTP_PRAGMA', rfc_section='14.32')
1161
1162 range = converter(
1163 environ_getter('HTTP_RANGE', rfc_section='14.35'),
1164 _parse_range, _serialize_range, 'Range object')
1165
1166 referer = environ_getter('HTTP_REFERER', rfc_section='14.36')
1167 referrer = referer
1168
1169 user_agent = environ_getter('HTTP_USER_AGENT', rfc_section='14.43')
1170
1171 def __repr__(self):
1172 msg = '<%s at %x %s %s>' % (
1173 self.__class__.__name__,
1174 abs(id(self)), self.method, self.url)
1175 return msg
1176
1177 def __str__(self):
1178 url = self.url
1179 host = self.host_url
1180 assert url.startswith(host)
1181 url = url[len(host):]
1182 if 'Host' not in self.headers:
1183 self.headers['Host'] = self.host
1184 parts = ['%s %s' % (self.method, url)]
1185 for name, value in sorted(self.headers.items()):
1186 parts.append('%s: %s' % (name, value))
1187 parts.append('')
1188 parts.append(self.body)
1189 return '\r\n'.join(parts)
1190
1191 def call_application(self, application, catch_exc_info=False):
1192 """
1193 Call the given WSGI application, returning ``(status_string,
1194 headerlist, app_iter)``
1195
1196 Be sure to call ``app_iter.close()`` if it's there.
1197
1198 If catch_exc_info is true, then returns ``(status_string,
1199 headerlist, app_iter, exc_info)``, where the fourth item may
1200 be None, but won't be if there was an exception. If you don't
1201 do this and there was an exception, the exception will be
1202 raised directly.
1203 """
1204 captured = []
1205 output = []
1206 def start_response(status, headers, exc_info=None):
1207 if exc_info is not None and not catch_exc_info:
1208 raise exc_info[0], exc_info[1], exc_info[2]
1209 captured[:] = [status, headers, exc_info]
1210 return output.append
1211 app_iter = application(self.environ, start_response)
1212 if (not captured
1213 or output):
1214 try:
1215 output.extend(app_iter)
1216 finally:
1217 if hasattr(app_iter, 'close'):
1218 app_iter.close()
1219 app_iter = output
1220 if catch_exc_info:
1221 return (captured[0], captured[1], app_iter, captured[2])
1222 else:
1223 return (captured[0], captured[1], app_iter)
1224
1225
1226 ResponseClass = None
1227
1228 def get_response(self, application, catch_exc_info=False):
1229 """
1230 Like ``.call_application(application)``, except returns a
1231 response object with ``.status``, ``.headers``, and ``.body``
1232 attributes.
1233
1234 This will use ``self.ResponseClass`` to figure out the class
1235 of the response object to return.
1236 """
1237 if catch_exc_info:
1238 status, headers, app_iter, exc_info = self.call_application(
1239 application, catch_exc_info=True)
1240 del exc_info
1241 else:
1242 status, headers, app_iter = self.call_application(
1243 application, catch_exc_info=False)
1244 return self.ResponseClass(
1245 status=status, headerlist=headers, app_iter=app_iter,
1246 request=self)
1247
1248
1249 def blank(cls, path, environ=None, base_url=None, headers=None, **kw):
1250 """
1251 Create a blank request environ (and Request wrapper) with the
1252 given path (path should be urlencoded), and any keys from
1253 environ.
1254
1255 The path will become path_info, with any query string split
1256 off and used.
1257
1258 All necessary keys will be added to the environ, but the
1259 values you pass in will take precedence. If you pass in
1260 base_url then wsgi.url_scheme, HTTP_HOST, and SCRIPT_NAME will
1261 be filled in from that value.
1262
1263 Any extra keyword will be passed to ``__init__`` (e.g.,
1264 ``decode_param_names``).
1265 """
1266 if _SCHEME_RE.search(path):
1267 scheme, netloc, path, qs, fragment = urlparse.urlsplit(path)
1268 if fragment:
1269 raise TypeError(
1270 "Path cannot contain a fragment (%r)" % fragment)
1271 if qs:
1272 path += '?' + qs
1273 if ':' not in netloc:
1274 if scheme == 'http':
1275 netloc += ':80'
1276 elif scheme == 'https':
1277 netloc += ':443'
1278 else:
1279 raise TypeError("Unknown scheme: %r" % scheme)
1280 else:
1281 scheme = 'http'
1282 netloc = 'localhost:80'
1283 if path and '?' in path:
1284 path_info, query_string = path.split('?', 1)
1285 path_info = urllib.unquote(path_info)
1286 else:
1287 path_info = urllib.unquote(path)
1288 query_string = ''
1289 env = {
1290 'REQUEST_METHOD': 'GET',
1291 'SCRIPT_NAME': '',
1292 'PATH_INFO': path_info or '',
1293 'QUERY_STRING': query_string,
1294 'SERVER_NAME': netloc.split(':')[0],
1295 'SERVER_PORT': netloc.split(':')[1],
1296 'HTTP_HOST': netloc,
1297 'SERVER_PROTOCOL': 'HTTP/1.0',
1298 'wsgi.version': (1, 0),
1299 'wsgi.url_scheme': scheme,
1300 'wsgi.input': StringIO(''),
1301 'wsgi.errors': sys.stderr,
1302 'wsgi.multithread': False,
1303 'wsgi.multiprocess': False,
1304 'wsgi.run_once': False,
1305 }
1306 if base_url:
1307 scheme, netloc, path, query, fragment = urlparse.urlsplit(base_url)
1308 if query or fragment:
1309 raise ValueError(
1310 "base_url (%r) cannot have a query or fragment"
1311 % base_url)
1312 if scheme:
1313 env['wsgi.url_scheme'] = scheme
1314 if netloc:
1315 if ':' not in netloc:
1316 if scheme == 'http':
1317 netloc += ':80'
1318 elif scheme == 'https':
1319 netloc += ':443'
1320 else:
1321 raise ValueError(
1322 "Unknown scheme: %r" % scheme)
1323 host, port = netloc.split(':', 1)
1324 env['SERVER_PORT'] = port
1325 env['SERVER_NAME'] = host
1326 env['HTTP_HOST'] = netloc
1327 if path:
1328 env['SCRIPT_NAME'] = urllib.unquote(path)
1329 if environ:
1330 env.update(environ)
1331 obj = cls(env, **kw)
1332 if headers is not None:
1333 obj.headers.update(headers)
1334 return obj
1335
1336 blank = classmethod(blank)
1337
1338class Response(object):
1339
1340 """
1341 Represents a WSGI response
1342 """
1343
1344 default_content_type = 'text/html'
1345 default_charset = 'UTF-8'
1346 default_conditional_response = False
1347
1348 def __init__(self, body=None, status='200 OK', headerlist=None, app_iter=None,
1349 request=None, content_type=None, conditional_response=NoDefault,
1350 **kw):
1351 if app_iter is None:
1352 if body is None:
1353 body = ''
1354 elif body is not None:
1355 raise TypeError(
1356 "You may only give one of the body and app_iter arguments")
1357 self.status = status
1358 if headerlist is None:
1359 self._headerlist = []
1360 else:
1361 self._headerlist = headerlist
1362 self._headers = None
1363 if request is not None:
1364 if hasattr(request, 'environ'):
1365 self._environ = request.environ
1366 self._request = request
1367 else:
1368 self._environ = request
1369 self._request = None
1370 else:
1371 self._environ = self._request = None
1372 if content_type is not None:
1373 self.content_type = content_type
1374 elif self.default_content_type is not None and headerlist is None:
1375 self.content_type = self.default_content_type
1376 if conditional_response is NoDefault:
1377 self.conditional_response = self.default_conditional_response
1378 else:
1379 self.conditional_response = conditional_response
1380 if 'charset' in kw:
1381
1382 value = kw.pop('charset')
1383 if value:
1384 self.charset = value
1385 elif self.default_charset and not self.charset and headerlist is None:
1386 ct = self.content_type
1387 if ct and (ct.startswith('text/') or ct.startswith('application/xml')
1388 or (ct.startswith('application/') and ct.endswith('+xml'))):
1389 self.charset = self.default_charset
1390 if app_iter is not None:
1391 self._app_iter = app_iter
1392 self._body = None
1393 else:
1394 if isinstance(body, unicode):
1395 self.unicode_body = body
1396 else:
1397 self.body = body
1398 self._app_iter = None
1399 for name, value in kw.items():
1400 if not hasattr(self.__class__, name):
1401
1402 raise TypeError(
1403 "Unexpected keyword: %s=%r in %r" % (name, value))
1404 setattr(self, name, value)
1405
1406 def __repr__(self):
1407 return '<%s %x %s>' % (
1408 self.__class__.__name__,
1409 abs(id(self)),
1410 self.status)
1411
1412 def __str__(self):
1413 return (self.status + '\n'
1414 + '\n'.join(['%s: %s' % (name, value)
1415 for name, value in self.headerlist])
1416 + '\n\n'
1417 + self.body)
1418
1419 def _status__get(self):
1420 """
1421 The status string
1422 """
1423 return self._status
1424
1425 def _status__set(self, value):
1426 if isinstance(value, int):
1427 value = str(value)
1428 if not isinstance(value, str):
1429 raise TypeError(
1430 "You must set status to a string or integer (not %s)"
1431 % type(value))
1432 if ' ' not in value:
1433
1434 code = int(value)
1435 reason = status_reasons[code]
1436 value += ' ' + reason
1437 self._status = value
1438
1439 status = property(_status__get, _status__set, doc=_status__get.__doc__)
1440
1441 def _status_int__get(self):
1442 """
1443 The status as an integer
1444 """
1445 return int(self.status.split()[0])
1446 def _status_int__set(self, value):
1447 self.status = value
1448 status_int = property(_status_int__get, _status_int__set, doc=_status_int__get.__doc__)
1449
1450 def _headerlist__get(self):
1451 """
1452 The list of response headers
1453 """
1454 return self._headerlist
1455
1456 def _headerlist__set(self, value):
1457 self._headers = None
1458 if not isinstance(value, list):
1459 if hasattr(value, 'items'):
1460 value = value.items()
1461 value = list(value)
1462 self._headerlist = value
1463
1464 def _headerlist__del(self):
1465 self.headerlist = []
1466
1467 headerlist = property(_headerlist__get, _headerlist__set, _headerlist__del, doc=_headerlist__get.__doc__)
1468
1469 def _charset__get(self):
1470 """
1471 Get/set the charset (in the Content-Type)
1472 """
1473 header = self.headers.get('content-type')
1474 if not header:
1475 return None
1476 match = _CHARSET_RE.search(header)
1477 if match:
1478 return match.group(1)
1479 return None
1480
1481 def _charset__set(self, charset):
1482 if charset is None:
1483 del self.charset
1484 return
1485 try:
1486 header = self.headers.pop('content-type')
1487 except KeyError:
1488 raise AttributeError(
1489 "You cannot set the charset when no content-type is defined")
1490 match = _CHARSET_RE.search(header)
1491 if match:
1492 header = header[:match.start()] + header[match.end():]
1493 header += '; charset=%s' % charset
1494 self.headers['content-type'] = header
1495
1496 def _charset__del(self):
1497 try:
1498 header = self.headers.pop('content-type')
1499 except KeyError:
1500
1501 return
1502 match = _CHARSET_RE.search(header)
1503 if match:
1504 header = header[:match.start()] + header[match.end():]
1505 self.headers['content-type'] = header
1506
1507 charset = property(_charset__get, _charset__set, _charset__del, doc=_charset__get.__doc__)
1508
1509 def _content_type__get(self):
1510 """
1511 Get/set the Content-Type header (or None), *without* the
1512 charset or any parameters.
1513
1514 If you include parameters (or ``;`` at all) when setting the
1515 content_type, any existing parameters will be deleted;
1516 otherwise they will be preserved.
1517 """
1518 header = self.headers.get('content-type')
1519 if not header:
1520 return None
1521 return header.split(';', 1)[0]
1522
1523 def _content_type__set(self, value):
1524 if ';' not in value:
1525 header = self.headers.get('content-type', '')
1526 if ';' in header:
1527 params = header.split(';', 1)[1]
1528 value += ';' + params
1529 self.headers['content-type'] = value
1530
1531 def _content_type__del(self):
1532 try:
1533 del self.headers['content-type']
1534 except KeyError:
1535 pass
1536
1537 content_type = property(_content_type__get, _content_type__set,
1538 _content_type__del, doc=_content_type__get.__doc__)
1539
1540 def _content_type_params__get(self):
1541 """
1542 Returns a dictionary of all the parameters in the content type.
1543 """
1544 params = self.headers.get('content-type', '')
1545 if ';' not in params:
1546 return {}
1547 params = params.split(';', 1)[1]
1548 result = {}
1549 for match in _PARAM_RE.finditer(params):
1550 result[match.group(1)] = match.group(2) or match.group(3) or ''
1551 return result
1552
1553 def _content_type_params__set(self, value_dict):
1554 if not value_dict:
1555 del self.content_type_params
1556 return
1557 params = []
1558 for k, v in sorted(value_dict.items()):
1559 if not _OK_PARAM_RE.search(v):
1560
1561
1562 v = '"%s"' % v.replace('"', '\\"')
1563 params.append('; %s=%s' % (k, v))
1564 ct = self.headers.pop('content-type', '').split(';', 1)[0]
1565 ct += ''.join(params)
1566 self.headers['content-type'] = ct
1567
1568 def _content_type_params__del(self, value):
1569 self.headers['content-type'] = self.headers.get('content-type', '').split(';', 1)[0]
1570
1571 content_type_params = property(_content_type_params__get, _content_type_params__set, _content_type_params__del, doc=_content_type_params__get.__doc__)
1572
1573 def _headers__get(self):
1574 """
1575 The headers in a dictionary-like object
1576 """
1577 if self._headers is None:
1578 self._headers = HeaderDict.view_list(self.headerlist)
1579 return self._headers
1580
1581 def _headers__set(self, value):
1582 if hasattr(value, 'items'):
1583 value = value.items()
1584 self.headerlist = value
1585 self._headers = None
1586
1587 headers = property(_headers__get, _headers__set, doc=_headers__get.__doc__)
1588
1589 def _body__get(self):
1590 """
1591 The body of the response, as a ``str``. This will read in the
1592 entire app_iter if necessary.
1593 """
1594 if self._body is None:
1595 if self._app_iter is None:
1596 raise AttributeError(
1597 "No body has been set")
1598 try:
1599 self._body = ''.join(self._app_iter)
1600 finally:
1601 if hasattr(self._app_iter, 'close'):
1602 self._app_iter.close()
1603 self._app_iter = None
1604 self.content_length = len(self._body)
1605 return self._body
1606
1607 def _body__set(self, value):
1608 if isinstance(value, unicode):
1609 raise TypeError(
1610 "You cannot set Response.body to a unicode object (use Response.unicode_body)")
1611 if not isinstance(value, str):
1612 raise TypeError(
1613 "You can only set the body to a str (not %s)"
1614 % type(value))
1615 self._body = value
1616 self.content_length = len(value)
1617 self._app_iter = None
1618
1619 def _body__del(self):
1620 self._body = None
1621 self.content_length = None
1622 self._app_iter = None
1623
1624 body = property(_body__get, _body__set, _body__del, doc=_body__get.__doc__)
1625
1626 def _body_file__get(self):
1627 """
1628 Returns a file-like object that can be used to write to the
1629 body. If you passed in a list app_iter, that app_iter will be
1630 modified by writes.
1631 """
1632 return ResponseBodyFile(self)
1633
1634 def _body_file__del(self):
1635 del self.body
1636
1637 body_file = property(_body_file__get, fdel=_body_file__del, doc=_body_file__get.__doc__)
1638
1639 def write(self, text):
1640 if isinstance(text, unicode):
1641 self.unicode_body += text
1642 else:
1643 self.body += text
1644
1645 def _unicode_body__get(self):
1646 """
1647 Get/set the unicode value of the body (using the charset of the Content-Type)
1648 """
1649 if not self.charset:
1650 raise AttributeError(
1651 "You cannot access Response.unicode_body unless charset is set")
1652 body = self.body
1653 return body.decode(self.charset)
1654
1655 def _unicode_body__set(self, value):
1656 if not self.charset:
1657 raise AttributeError(
1658 "You cannot access Response.unicode_body unless charset is set")
1659 if not isinstance(value, unicode):
1660 raise TypeError(
1661 "You can only set Response.unicode_body to a unicode string (not %s)" % type(value))
1662 self.body = value.encode(self.charset)
1663
1664 def _unicode_body__del(self):
1665 del self.body
1666
1667 unicode_body = property(_unicode_body__get, _unicode_body__set, _unicode_body__del, doc=_unicode_body__get.__doc__)
1668
1669 def _app_iter__get(self):
1670 """
1671 Returns the app_iter of the response.
1672
1673 If body was set, this will create an app_iter from that body
1674 (a single-item list)
1675 """
1676 if self._app_iter is None:
1677 if self._body is None:
1678 raise AttributeError(
1679 "No body or app_iter has been set")
1680 return [self._body]
1681 else:
1682 return self._app_iter
1683
1684 def _app_iter__set(self, value):
1685 if self._body is not None:
1686
1687 self.content_length = None
1688 self._app_iter = value
1689 self._body = None
1690
1691 def _app_iter__del(self):
1692 self.content_length = None
1693 self._app_iter = self._body = None
1694
1695 app_iter = property(_app_iter__get, _app_iter__set, _app_iter__del, doc=_app_iter__get.__doc__)
1696
1697 def set_cookie(self, key, value='', max_age=None,
1698 path='/', domain=None, secure=None, httponly=False,
1699 version=None, comment=None, expires=None):
1700 """
1701 Set (add) a cookie for the response
1702 """
1703 if isinstance(value, unicode) and self.charset is not None:
1704 value = '"%s"' % value.encode(self.charset)
1705 cookies = BaseCookie()
1706 cookies[key] = value
1707 if isinstance(max_age, timedelta):
1708 max_age = timedelta.seconds + timedelta.days*24*60*60
1709 if max_age is not None and expires is None:
1710 expires = datetime.utcnow() + timedelta(seconds=max_age)
1711 if isinstance(expires, timedelta):
1712 expires = datetime.utcnow() + expires
1713 if isinstance(expires, datetime):
1714 expires = '"'+_serialize_cookie_date(expires)+'"'
1715 for var_name, var_value in [
1716 ('max_age', max_age),
1717 ('path', path),
1718 ('domain', domain),
1719 ('secure', secure),
1720 ('HttpOnly', httponly),
1721 ('version', version),
1722 ('comment', comment),
1723 ('expires', expires),
1724 ]:
1725 if var_value is not None and var_value is not False:
1726 cookies[key][var_name.replace('_', '-')] = str(var_value)
1727 header_value = cookies[key].output(header='').lstrip()
1728 self.headerlist.append(('Set-Cookie', header_value))
1729
1730 def delete_cookie(self, key, path='/', domain=None):
1731 """
1732 Delete a cookie from the client. Note that path and domain must match
1733 how the cookie was originally set.
1734
1735 This sets the cookie to the empty string, and max_age=0 so
1736 that it should expire immediately.
1737 """
1738 self.set_cookie(key, '', path=path, domain=domain,
1739 max_age=0, expires=timedelta(days=-5))
1740
1741 def unset_cookie(self, key):
1742 """
1743 Unset a cookie with the given name (remove it from the
1744 response). If there are multiple cookies (e.g., two cookies
1745 with the same name and different paths or domains), all such
1746 cookies will be deleted.
1747 """
1748 existing = self.headers.getall('Set-Cookie')
1749 if not existing:
1750 raise KeyError(
1751 "No cookies at all have been set")
1752 del self.headers['Set-Cookie']
1753 found = False
1754 for header in existing:
1755 cookies = BaseCookie()
1756 cookies.load(header)
1757 if key in cookies:
1758 found = True
1759 del cookies[key]
1760 header = cookies.output(header='').lstrip()
1761 if header:
1762 self.headers.add('Set-Cookie', header)
1763 if not found:
1764 raise KeyError(
1765 "No cookie has been set with the name %r" % key)
1766
1767 def _location__get(self):
1768 """
1769 Retrieve the Location header of the response, or None if there
1770 is no header. If the header is not absolute and this response
1771 is associated with a request, make the header absolute.
1772
1773 For more information see `section 14.30
1774 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.30>`_.
1775 """
1776 if 'location' not in self.headers:
1777 return None
1778 location = self.headers['location']
1779 if _SCHEME_RE.search(location):
1780
1781 return location
1782 if self.request is not None:
1783 base_uri = self.request.url
1784 location = urlparse.urljoin(base_uri, location)
1785 return location
1786
1787 def _location__set(self, value):
1788 if not _SCHEME_RE.search(value):
1789
1790 if self.request is not None:
1791 value = urlparse.urljoin(self.request.url, value)
1792 self.headers['location'] = value
1793
1794 def _location__del(self):
1795 if 'location' in self.headers:
1796 del self.headers['location']
1797
1798 location = property(_location__get, _location__set, _location__del, doc=_location__get.__doc__)
1799
1800 accept_ranges = header_getter('Accept-Ranges', rfc_section='14.5')
1801
1802 age = converter(
1803 header_getter('Age', rfc_section='14.6'),
1804 _parse_int_safe, _serialize_int, 'int')
1805
1806 allow = converter(
1807 header_getter('Allow', rfc_section='14.7'),
1808 _parse_list, _serialize_list, 'list')
1809
1810 _cache_control_obj = None
1811
1812 def _cache_control__get(self):
1813 """
1814 Get/set/modify the Cache-Control header (section `14.9
1815 <http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9>`_)
1816 """
1817 value = self.headers.get('cache-control', '')
1818 if self._cache_control_obj is None:
1819 self._cache_control_obj = CacheControl.parse(value, updates_to=self._update_cache_control, type='response')
1820 self._cache_control_obj.header_value = value
1821 if self._cache_control_obj.header_value != value:
1822 new_obj = CacheControl.parse(value, type='response')
1823 self._cache_control_obj.properties.clear()
1824 self._cache_control_obj.properties.update(new_obj.properties)
1825 self._cache_control_obj.header_value = value
1826 return self._cache_control_obj
1827
1828 def _cache_control__set(self, value):
1829
1830 if not value:
1831 value = ""
1832 if isinstance(value, dict):
1833 value = CacheControl(value, 'response')
1834 if isinstance(value, unicode):
1835 value = str(value)
1836 if isinstance(value, str):
1837 if self._cache_control_obj is None:
1838 self.headers['Cache-Control'] = value
1839 return
1840 value = CacheControl.parse(value, 'response')
1841 cache = self.cache_control
1842 cache.properties.clear()
1843 cache.properties.update(value.properties)
1844
1845 def _cache_control__del(self):
1846 self.cache_control = {}
1847
1848 def _update_cache_control(self, prop_dict):
1849 value = serialize_cache_control(prop_dict)
1850 if not value:
1851 if 'Cache-Control' in self.headers:
1852 del self.headers['Cache-Control']
1853 else:
1854 self.headers['Cache-Control'] = value
1855
1856 cache_control = property(_cache_control__get, _cache_control__set, _cache_control__del, doc=_cache_control__get.__doc__)
1857
1858 def cache_expires(self, seconds=0, **kw):
1859 """
1860 Set expiration on this request. This sets the response to
1861 expire in the given seconds, and any other attributes are used
1862 for cache_control (e.g., private=True, etc).
1863 """
1864 cache_control = self.cache_control
1865 if isinstance(seconds, timedelta):
1866 seconds = timedelta_to_seconds(seconds)
1867 if not seconds:
1868
1869
1870
1871
1872 cache_control.no_store = True
1873 cache_control.no_cache = True
1874 cache_control.must_revalidate = True
1875 cache_control.max_age = 0
1876 cache_control.post_check = 0
1877 cache_control.pre_check = 0