NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

wrappers.py (29561B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\werkzeug\wrappers.py
      2 import tempfile
      3 import urlparse
      4 from datetime import datetime, timedelta
      5 from werkzeug.http import HTTP_STATUS_CODES, parse_accept_header, parse_cache_control_header, parse_etags, parse_date, generate_etag, is_resource_modified, unquote_etag, quote_etag, parse_set_header, parse_authorization_header, parse_www_authenticate_header, remove_entity_headers, parse_options_header, dump_options_header
      6 from werkzeug.urls import url_decode, iri_to_uri
      7 from werkzeug.formparser import parse_form_data, default_stream_factory
      8 from werkzeug.utils import cached_property, environ_property, cookie_date, parse_cookie, dump_cookie, http_date, escape, header_property, get_content_type
      9 from werkzeug.wsgi import get_current_url, get_host, LimitedStream, ClosingIterator
     10 from werkzeug.datastructures import MultiDict, CombinedMultiDict, Headers, EnvironHeaders, ImmutableMultiDict, ImmutableTypeConversionDict, ImmutableList, MIMEAccept, CharsetAccept, LanguageAccept, ResponseCacheControl, RequestCacheControl, CallbackDict
     11 from werkzeug._internal import _empty_stream, _decode_unicode, _patch_wrapper, _get_environ
     12 
     13 def _run_wsgi_app(*args):
     14     global _run_wsgi_app
     15     from werkzeug.test import run_wsgi_app as _run_wsgi_app
     16     return _run_wsgi_app(*args)
     17 
     18 
     19 def _warn_if_string(iterable):
     20     if isinstance(iterable, basestring):
     21         from warnings import warn
     22         warn(Warning('response iterable was set to a string.  This appears to work but means that the server will send the data to the client char, by char.  This is almost never intended behavior, use response.data to assign strings to the response object.'), stacklevel=2)
     23 
     24 
     25 class BaseRequest(object):
     26     charset = 'utf-8'
     27     encoding_errors = 'ignore'
     28     is_behind_proxy = False
     29     max_content_length = None
     30     max_form_memory_size = None
     31     parameter_storage_class = ImmutableMultiDict
     32     list_storage_class = ImmutableList
     33     dict_storage_class = ImmutableTypeConversionDict
     34 
     35     def __init__(self, environ, populate_request = True, shallow = False):
     36         self.environ = environ
     37         if populate_request and not shallow:
     38             self.environ['werkzeug.request'] = self
     39         self.shallow = shallow
     40 
     41     def __repr__(self):
     42         args = []
     43         try:
     44             args.append("'%s'" % self.url)
     45             args.append('[%s]' % self.method)
     46         except:
     47             args.append('(invalid WSGI environ)')
     48 
     49         return '<%s %s>' % (self.__class__.__name__, ' '.join(args))
     50 
     51     @property
     52     def url_charset(self):
     53         return self.charset
     54 
     55     @classmethod
     56     def from_values(cls, *args, **kwargs):
     57         from werkzeug.test import EnvironBuilder
     58         charset = kwargs.pop('charset', cls.charset)
     59         builder = EnvironBuilder(*args, **kwargs)
     60         try:
     61             return builder.get_request(cls)
     62         finally:
     63             builder.close()
     64 
     65     @classmethod
     66     def application(cls, f):
     67         return _patch_wrapper(f, lambda *a: f(*(a[:-2] + (cls(a[-2]),)))(*a[-2:]))
     68 
     69     def _get_file_stream(self, total_content_length, content_type, filename = None, content_length = None):
     70         return default_stream_factory(total_content_length, content_type, filename, content_length)
     71 
     72     def _load_form_data(self):
     73         if 'stream' in self.__dict__:
     74             return
     75         if self.shallow:
     76             raise RuntimeError('A shallow request tried to consume form data.  If you really want to do that, set `shallow` to False.')
     77         data = None
     78         stream = _empty_stream
     79         if self.environ['REQUEST_METHOD'] in ('POST', 'PUT'):
     80             try:
     81                 data = parse_form_data(self.environ, self._get_file_stream, self.charset, self.encoding_errors, self.max_form_memory_size, self.max_content_length, cls=self.parameter_storage_class, silent=False)
     82             except ValueError as e:
     83                 self._form_parsing_failed(e)
     84 
     85         else:
     86             content_length = self.headers.get('content-length', type=int)
     87             if content_length is not None:
     88                 stream = LimitedStream(self.environ['wsgi.input'], content_length)
     89         if data is None:
     90             data = (stream, self.parameter_storage_class(), self.parameter_storage_class())
     91         d = self.__dict__
     92         d['stream'], d['form'], d['files'] = data
     93 
     94     def _form_parsing_failed(self, error):
     95         pass
     96 
     97     @cached_property
     98     def stream(self):
     99         self._load_form_data()
    100         return self.stream
    101 
    102     input_stream = environ_property('wsgi.input', "The WSGI input stream.\nIn general it's a bad idea to use this one because you can easily read past the boundary.  Use the :attr:`stream` instead.")
    103 
    104     @cached_property
    105     def args(self):
    106         return url_decode(self.environ.get('QUERY_STRING', ''), self.url_charset, errors=self.encoding_errors, cls=self.parameter_storage_class)
    107 
    108     @cached_property
    109     def data(self):
    110         return self.stream.read()
    111 
    112     @cached_property
    113     def form(self):
    114         self._load_form_data()
    115         return self.form
    116 
    117     @cached_property
    118     def values(self):
    119         args = []
    120         for d in (self.args, self.form):
    121             if not isinstance(d, MultiDict):
    122                 d = MultiDict(d)
    123             args.append(d)
    124 
    125         return CombinedMultiDict(args)
    126 
    127     @cached_property
    128     def files(self):
    129         self._load_form_data()
    130         return self.files
    131 
    132     @cached_property
    133     def cookies(self):
    134         return parse_cookie(self.environ, self.charset, cls=self.dict_storage_class)
    135 
    136     @cached_property
    137     def headers(self):
    138         return EnvironHeaders(self.environ)
    139 
    140     @cached_property
    141     def path(self):
    142         path = '/' + (self.environ.get('PATH_INFO') or '').lstrip('/')
    143         return _decode_unicode(path, self.url_charset, self.encoding_errors)
    144 
    145     @cached_property
    146     def script_root(self):
    147         path = (self.environ.get('SCRIPT_NAME') or '').rstrip('/')
    148         return _decode_unicode(path, self.url_charset, self.encoding_errors)
    149 
    150     @cached_property
    151     def url(self):
    152         return get_current_url(self.environ)
    153 
    154     @cached_property
    155     def base_url(self):
    156         return get_current_url(self.environ, strip_querystring=True)
    157 
    158     @cached_property
    159     def url_root(self):
    160         return get_current_url(self.environ, True)
    161 
    162     @cached_property
    163     def host_url(self):
    164         return get_current_url(self.environ, host_only=True)
    165 
    166     @cached_property
    167     def host(self):
    168         return get_host(self.environ)
    169 
    170     query_string = environ_property('QUERY_STRING', '', read_only=True, doc='The URL parameters as raw bytestring.')
    171     method = environ_property('REQUEST_METHOD', 'GET', read_only=True, doc="The transmission method. (For example ``'GET'`` or ``'POST'``).")
    172 
    173     @cached_property
    174     def access_route(self):
    175         if 'HTTP_X_FORWARDED_FOR' in self.environ:
    176             addr = self.environ['HTTP_X_FORWARDED_FOR'].split(',')
    177             return self.list_storage_class([ x.strip() for x in addr ])
    178         if 'REMOTE_ADDR' in self.environ:
    179             return self.list_storage_class([self.environ['REMOTE_ADDR']])
    180         return self.list_storage_class()
    181 
    182     @property
    183     def remote_addr(self):
    184         if self.is_behind_proxy and self.access_route:
    185             return self.access_route[0]
    186         return self.environ.get('REMOTE_ADDR')
    187 
    188     remote_user = environ_property('REMOTE_USER', doc='\n        If the server supports user authentication, and the script is\n        protected, this attribute contains the username the user has\n        authenticated as.')
    189     is_xhr = property(lambda x: x.environ.get('HTTP_X_REQUESTED_WITH', '').lower() == 'xmlhttprequest', doc='\n        True if the request was triggered via a JavaScript XMLHttpRequest.\n        This only works with libraries that support the `X-Requested-With`\n        header and set it to "XMLHttpRequest".  Libraries that do that are\n        prototype, jQuery and Mochikit and probably some more.')
    190     is_secure = property(lambda x: x.environ['wsgi.url_scheme'] == 'https', doc='`True` if the request is secure.')
    191     is_multithread = environ_property('wsgi.multithread', doc='\n        boolean that is `True` if the application is served by\n        a multithreaded WSGI server.')
    192     is_multiprocess = environ_property('wsgi.multiprocess', doc='\n        boolean that is `True` if the application is served by\n        a WSGI server that spawns multiple processes.')
    193     is_run_once = environ_property('wsgi.run_once', doc="\n        boolean that is `True` if the application will be executed only\n        once in a process lifetime.  This is the case for CGI for example,\n        but it's not guaranteed that the exeuction only happens one time.")
    194 
    195 
    196 class BaseResponse(object):
    197     charset = 'utf-8'
    198     default_status = 200
    199     default_mimetype = 'text/plain'
    200     implicit_sequence_conversion = True
    201 
    202     def __init__(self, response = None, status = None, headers = None, mimetype = None, content_type = None, direct_passthrough = False):
    203         if isinstance(headers, Headers):
    204             self.headers = headers
    205         elif not headers:
    206             self.headers = Headers()
    207         else:
    208             self.headers = Headers(headers)
    209         if content_type is None:
    210             if mimetype is None and 'content-type' not in self.headers:
    211                 mimetype = self.default_mimetype
    212             if mimetype is not None:
    213                 mimetype = get_content_type(mimetype, self.charset)
    214             content_type = mimetype
    215         if content_type is not None:
    216             self.headers['Content-Type'] = content_type
    217         if status is None:
    218             status = self.default_status
    219         if isinstance(status, (int, long)):
    220             self.status_code = status
    221         else:
    222             self.status = status
    223         self.direct_passthrough = direct_passthrough
    224         self._on_close = []
    225         if response is None:
    226             self.response = []
    227         elif isinstance(response, basestring):
    228             self.data = response
    229         else:
    230             self.response = response
    231 
    232     def call_on_close(self, func):
    233         self._on_close.append(func)
    234 
    235     def __repr__(self):
    236         if self.is_sequence:
    237             body_info = '%d bytes' % sum(map(len, self.iter_encoded()))
    238         else:
    239             body_info = self.is_streamed and 'streamed' or 'likely-streamed'
    240         return '<%s %s [%s]>' % (self.__class__.__name__, body_info, self.status)
    241 
    242     @classmethod
    243     def force_type(cls, response, environ = None):
    244         if not isinstance(response, BaseResponse):
    245             if environ is None:
    246                 raise TypeError('cannot convert WSGI application into response objects without an environ')
    247             response = BaseResponse(*_run_wsgi_app(response, environ))
    248         response.__class__ = cls
    249         return response
    250 
    251     @classmethod
    252     def from_app(cls, app, environ, buffered = False):
    253         return cls(*_run_wsgi_app(app, environ, buffered))
    254 
    255     def _get_status_code(self):
    256         try:
    257             return int(self.status.split(None, 1)[0])
    258         except ValueError:
    259             return 0
    260 
    261     def _set_status_code(self, code):
    262         try:
    263             self.status = '%d %s' % (code, HTTP_STATUS_CODES[code].upper())
    264         except KeyError:
    265             self.status = '%d UNKNOWN' % code
    266 
    267     status_code = property(_get_status_code, _set_status_code, 'The HTTP Status code as number')
    268     del _get_status_code
    269     del _set_status_code
    270 
    271     def _get_data(self):
    272         self._ensure_sequence()
    273         return ''.join(self.iter_encoded())
    274 
    275     def _set_data(self, value):
    276         if isinstance(value, unicode):
    277             value = value.encode(self.charset)
    278         self.response = [value]
    279 
    280     data = property(_get_data, _set_data, doc=_get_data.__doc__)
    281     del _get_data
    282     del _set_data
    283 
    284     def _ensure_sequence(self, mutable = False):
    285         if self.is_sequence:
    286             if mutable and not isinstance(self.response, list):
    287                 self.response = list(self.response)
    288             return
    289         if not self.implicit_sequence_conversion:
    290             raise RuntimeError('The response object required the iterable to be a sequence, but the implicit conversion was disabled.  Call make_sequence() yourself.')
    291         self.make_sequence()
    292 
    293     def make_sequence(self):
    294         if not self.is_sequence:
    295             close = getattr(self.response, 'close', None)
    296             self.response = list(self.iter_encoded())
    297             if close is not None:
    298                 self.call_on_close(close)
    299 
    300     def iter_encoded(self, charset = None):
    301         if __debug__ and charset is not None:
    302             from warnings import warn
    303             warn(DeprecationWarning('charset was deprecated and is ignored.'), stacklevel=2)
    304         charset = self.charset
    305         for item in self.response:
    306             if isinstance(item, unicode):
    307                 yield item.encode(charset)
    308             else:
    309                 yield str(item)
    310 
    311     def set_cookie(self, key, value = '', max_age = None, expires = None, path = '/', domain = None, secure = None, httponly = False):
    312         self.headers.add('Set-Cookie', dump_cookie(key, value, max_age, expires, path, domain, secure, httponly, self.charset))
    313 
    314     def delete_cookie(self, key, path = '/', domain = None):
    315         self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
    316 
    317     @property
    318     def header_list(self):
    319         return self.headers.to_list(self.charset)
    320 
    321     @property
    322     def is_streamed(self):
    323         try:
    324             len(self.response)
    325         except TypeError:
    326             return True
    327 
    328         return False
    329 
    330     @property
    331     def is_sequence(self):
    332         return isinstance(self.response, (tuple, list))
    333 
    334     def close(self):
    335         if hasattr(self.response, 'close'):
    336             self.response.close()
    337         for func in self._on_close:
    338             func()
    339 
    340     def freeze(self):
    341         self.response = list(self.iter_encoded())
    342         self.headers['Content-Length'] = str(sum(map(len, self.response)))
    343 
    344     def fix_headers(self, environ):
    345         self.headers[:] = self.get_wsgi_headers(environ)
    346 
    347     def get_wsgi_headers(self, environ):
    348         headers = Headers(self.headers)
    349         location = headers.get('location')
    350         if location is not None:
    351             if isinstance(location, unicode):
    352                 location = iri_to_uri(location)
    353             headers['Location'] = urlparse.urljoin(get_current_url(environ, root_only=True), location)
    354         content_location = headers.get('content-location')
    355         if content_location is not None and isinstance(content_location, unicode):
    356             headers['Content-Location'] = iri_to_uri(content_location)
    357         if 100 <= self.status_code < 200 or self.status_code == 204:
    358             headers['Content-Length'] = '0'
    359         elif self.status_code == 304:
    360             remove_entity_headers(headers)
    361         if self.is_sequence and 'content-length' not in self.headers:
    362             try:
    363                 content_length = sum((len(str(x)) for x in self.response))
    364             except UnicodeError:
    365                 pass
    366             else:
    367                 headers['Content-Length'] = str(content_length)
    368 
    369         return headers
    370 
    371     def get_app_iter(self, environ):
    372         if environ['REQUEST_METHOD'] == 'HEAD' or 100 <= self.status_code < 200 or self.status_code in (204, 304):
    373             return ()
    374         if self.direct_passthrough:
    375             return self.response
    376         return ClosingIterator(self.iter_encoded(), self.close)
    377 
    378     def get_wsgi_response(self, environ):
    379         if self.fix_headers.func_code is not BaseResponse.fix_headers.func_code:
    380             self.fix_headers(environ)
    381             headers = self.headers
    382         else:
    383             headers = self.get_wsgi_headers(environ)
    384         app_iter = self.get_app_iter(environ)
    385         return (app_iter, self.status, headers.to_list(self.charset))
    386 
    387     def __call__(self, environ, start_response):
    388         app_iter, status, headers = self.get_wsgi_response(environ)
    389         start_response(status, headers)
    390         return app_iter
    391 
    392 
    393 class AcceptMixin(object):
    394 
    395     @cached_property
    396     def accept_mimetypes(self):
    397         return parse_accept_header(self.environ.get('HTTP_ACCEPT'), MIMEAccept)
    398 
    399     @cached_property
    400     def accept_charsets(self):
    401         return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'), CharsetAccept)
    402 
    403     @cached_property
    404     def accept_encodings(self):
    405         return parse_accept_header(self.environ.get('HTTP_ACCEPT_ENCODING'))
    406 
    407     @cached_property
    408     def accept_languages(self):
    409         return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'), LanguageAccept)
    410 
    411 
    412 class ETagRequestMixin(object):
    413 
    414     @cached_property
    415     def cache_control(self):
    416         cache_control = self.environ.get('HTTP_CACHE_CONTROL')
    417         return parse_cache_control_header(cache_control, None, RequestCacheControl)
    418 
    419     @cached_property
    420     def if_match(self):
    421         return parse_etags(self.environ.get('HTTP_IF_MATCH'))
    422 
    423     @cached_property
    424     def if_none_match(self):
    425         return parse_etags(self.environ.get('HTTP_IF_NONE_MATCH'))
    426 
    427     @cached_property
    428     def if_modified_since(self):
    429         return parse_date(self.environ.get('HTTP_IF_MODIFIED_SINCE'))
    430 
    431     @cached_property
    432     def if_unmodified_since(self):
    433         return parse_date(self.environ.get('HTTP_IF_UNMODIFIED_SINCE'))
    434 
    435 
    436 class UserAgentMixin(object):
    437 
    438     @cached_property
    439     def user_agent(self):
    440         from werkzeug.useragents import UserAgent
    441         return UserAgent(self.environ)
    442 
    443 
    444 class AuthorizationMixin(object):
    445 
    446     @cached_property
    447     def authorization(self):
    448         header = self.environ.get('HTTP_AUTHORIZATION')
    449         return parse_authorization_header(header)
    450 
    451 
    452 class ETagResponseMixin(object):
    453 
    454     @property
    455     def cache_control(self):
    456 
    457         def on_update(cache_control):
    458             if not cache_control and 'cache-control' in self.headers:
    459                 del self.headers['cache-control']
    460             elif cache_control:
    461                 self.headers['Cache-Control'] = cache_control.to_header()
    462 
    463         return parse_cache_control_header(self.headers.get('cache-control'), on_update, ResponseCacheControl)
    464 
    465     def make_conditional(self, request_or_environ):
    466         environ = _get_environ(request_or_environ)
    467         if environ['REQUEST_METHOD'] in ('GET', 'HEAD'):
    468             self.headers['Date'] = http_date()
    469             if 'content-length' in self.headers:
    470                 self.headers['Content-Length'] = len(self.data)
    471             if not is_resource_modified(environ, self.headers.get('etag'), None, self.headers.get('last-modified')):
    472                 self.status_code = 304
    473         return self
    474 
    475     def add_etag(self, overwrite = False, weak = False):
    476         if overwrite or 'etag' not in self.headers:
    477             self.set_etag(generate_etag(self.data), weak)
    478 
    479     def set_etag(self, etag, weak = False):
    480         self.headers['ETag'] = quote_etag(etag, weak)
    481 
    482     def get_etag(self):
    483         return unquote_etag(self.headers.get('ETag'))
    484 
    485     def freeze(self, no_etag = False):
    486         if not no_etag:
    487             self.add_etag()
    488         super(ETagResponseMixin, self).freeze()
    489 
    490 
    491 class ResponseStream(object):
    492     mode = 'wb+'
    493 
    494     def __init__(self, response):
    495         self.response = response
    496         self.closed = False
    497 
    498     def write(self, value):
    499         if self.closed:
    500             raise ValueError('I/O operation on closed file')
    501         self.response._ensure_sequence(mutable=True)
    502         self.response.response.append(value)
    503 
    504     def writelines(self, seq):
    505         for item in seq:
    506             self.write(item)
    507 
    508     def close(self):
    509         self.closed = True
    510 
    511     def flush(self):
    512         if self.closed:
    513             raise ValueError('I/O operation on closed file')
    514 
    515     def isatty(self):
    516         if self.closed:
    517             raise ValueError('I/O operation on closed file')
    518         return False
    519 
    520     @property
    521     def encoding(self):
    522         return self.response.charset
    523 
    524 
    525 class ResponseStreamMixin(object):
    526 
    527     @cached_property
    528     def stream(self):
    529         return ResponseStream(self)
    530 
    531 
    532 class CommonRequestDescriptorsMixin(object):
    533     content_type = environ_property('CONTENT_TYPE', doc='\n         The Content-Type entity-header field indicates the media type of\n         the entity-body sent to the recipient or, in the case of the HEAD\n         method, the media type that would have been sent had the request\n         been a GET.')
    534     content_length = environ_property('CONTENT_LENGTH', None, int, str, doc='\n         The Content-Length entity-header field indicates the size of the\n         entity-body in bytes or, in the case of the HEAD method, the size of\n         the entity-body that would have been sent had the request been a\n         GET.')
    535     referrer = environ_property('HTTP_REFERER', doc='\n        The Referer[sic] request-header field allows the client to specify,\n        for the server\'s benefit, the address (URI) of the resource from which\n        the Request-URI was obtained (the "referrer", although the header\n        field is misspelled).')
    536     date = environ_property('HTTP_DATE', None, parse_date, doc='\n        The Date general-header field represents the date and time at which\n        the message was originated, having the same semantics as orig-date\n        in RFC 822.')
    537     max_forwards = environ_property('HTTP_MAX_FORWARDS', None, int, doc='\n         The Max-Forwards request-header field provides a mechanism with the\n         TRACE and OPTIONS methods to limit the number of proxies or gateways\n         that can forward the request to the next inbound server.')
    538 
    539     def _parse_content_type(self):
    540         if not hasattr(self, '_parsed_content_type'):
    541             self._parsed_content_type = parse_options_header(self.environ.get('CONTENT_TYPE', ''))
    542 
    543     @property
    544     def mimetype(self):
    545         self._parse_content_type()
    546         return self._parsed_content_type[0]
    547 
    548     @property
    549     def mimetype_params(self):
    550         self._parse_content_type()
    551         return self._parsed_content_type[1]
    552 
    553     @cached_property
    554     def pragma(self):
    555         return parse_set_header(self.environ.get('HTTP_PRAGMA', ''))
    556 
    557 
    558 class CommonResponseDescriptorsMixin(object):
    559 
    560     def _get_mimetype(self):
    561         ct = self.headers.get('content-type')
    562         if ct:
    563             return ct.split(';')[0].strip()
    564 
    565     def _set_mimetype(self, value):
    566         self.headers['Content-Type'] = get_content_type(value, self.charset)
    567 
    568     def _get_mimetype_params(self):
    569 
    570         def on_update(d):
    571             self.headers['Content-Type'] = dump_options_header(self.mimetype, d)
    572 
    573         d = parse_options_header(self.headers.get('content-type', ''))[1]
    574         return CallbackDict(d, on_update)
    575 
    576     mimetype = property(_get_mimetype, _set_mimetype, doc='\n        The mimetype (content type without charset etc.)')
    577     mimetype_params = property(_get_mimetype_params, doc="\n        The mimetype parameters as dict.  For example if the content\n        type is ``text/html; charset=utf-8`` the params would be\n        ``{'charset': 'utf-8'}``.\n\n        .. versionadded:: 0.5\n        ")
    578     location = header_property('Location', doc='\n        The Location response-header field is used to redirect the recipient\n        to a location other than the Request-URI for completion of the request\n        or identification of a new resource.')
    579     age = header_property('Age', None, parse_date, http_date, doc="\n        The Age response-header field conveys the sender's estimate of the\n        amount of time since the response (or its revalidation) was\n        generated at the origin server.\n\n        Age values are non-negative decimal integers, representing time in\n        seconds.")
    580     content_type = header_property('Content-Type', doc='\n        The Content-Type entity-header field indicates the media type of the\n        entity-body sent to the recipient or, in the case of the HEAD method,\n        the media type that would have been sent had the request been a GET.\n    ')
    581     content_length = header_property('Content-Length', None, int, str, doc='\n        The Content-Length entity-header field indicates the size of the\n        entity-body, in decimal number of OCTETs, sent to the recipient or,\n        in the case of the HEAD method, the size of the entity-body that would\n        have been sent had the request been a GET.')
    582     content_location = header_property('Content-Location', doc="\n        The Content-Location entity-header field MAY be used to supply the\n        resource location for the entity enclosed in the message when that\n        entity is accessible from a location separate from the requested\n        resource's URI.")
    583     content_encoding = header_property('Content-Encoding', doc='\n        The Content-Encoding entity-header field is used as a modifier to the\n        media-type.  When present, its value indicates what additional content\n        codings have been applied to the entity-body, and thus what decoding\n        mechanisms must be applied in order to obtain the media-type\n        referenced by the Content-Type header field.')
    584     content_md5 = header_property('Content-MD5', doc='\n         The Content-MD5 entity-header field, as defined in RFC 1864, is an\n         MD5 digest of the entity-body for the purpose of providing an\n         end-to-end message integrity check (MIC) of the entity-body.  (Note:\n         a MIC is good for detecting accidental modification of the\n         entity-body in transit, but is not proof against malicious attacks.)\n        ')
    585     date = header_property('Date', None, parse_date, http_date, doc='\n        The Date general-header field represents the date and time at which\n        the message was originated, having the same semantics as orig-date\n        in RFC 822.')
    586     expires = header_property('Expires', None, parse_date, http_date, doc='\n        The Expires entity-header field gives the date/time after which the\n        response is considered stale. A stale cache entry may not normally be\n        returned by a cache.')
    587     last_modified = header_property('Last-Modified', None, parse_date, http_date, doc='\n        The Last-Modified entity-header field indicates the date and time at\n        which the origin server believes the variant was last modified.')
    588 
    589     def _get_retry_after(self):
    590         value = self.headers.get('retry-after')
    591         if value is None:
    592             return
    593         if value.isdigit():
    594             return datetime.utcnow() + timedelta(seconds=int(value))
    595         return parse_date(value)
    596 
    597     def _set_retry_after(self, value):
    598         if value is None:
    599             if 'retry-after' in self.headers:
    600                 del self.headers['retry-after']
    601             return
    602         if isinstance(value, datetime):
    603             value = http_date(value)
    604         else:
    605             value = str(value)
    606         self.headers['Retry-After'] = value
    607 
    608     retry_after = property(_get_retry_after, _set_retry_after, doc='\n        The Retry-After response-header field can be used with a 503 (Service\n        Unavailable) response to indicate how long the service is expected\n        to be unavailable to the requesting client.\n\n        Time in seconds until expiration or date.')
    609 
    610     def _set_property(name, doc = None):
    611 
    612         def fget(self):
    613 
    614             def on_update(header_set):
    615                 if not header_set and name in self.headers:
    616                     del self.headers[name]
    617                 elif header_set:
    618                     self.headers[name] = header_set.to_header()
    619 
    620             return parse_set_header(self.headers.get(name), on_update)
    621 
    622         return property(fget, doc=doc)
    623 
    624     vary = _set_property('Vary', doc='\n         The Vary field value indicates the set of request-header fields that\n         fully determines, while the response is fresh, whether a cache is\n         permitted to use the response to reply to a subsequent request\n         without revalidation.')
    625     content_language = _set_property('Content-Language', doc='\n         The Content-Language entity-header field describes the natural\n         language(s) of the intended audience for the enclosed entity.  Note\n         that this might not be equivalent to all the languages used within\n         the entity-body.')
    626     allow = _set_property('Allow', doc='\n        The Allow entity-header field lists the set of methods supported\n        by the resource identified by the Request-URI. The purpose of this\n        field is strictly to inform the recipient of valid methods\n        associated with the resource. An Allow header field MUST be\n        present in a 405 (Method Not Allowed) response.')
    627     del _set_property
    628     del _get_mimetype
    629     del _set_mimetype
    630     del _get_retry_after
    631     del _set_retry_after
    632 
    633 
    634 class WWWAuthenticateMixin(object):
    635 
    636     @property
    637     def www_authenticate(self):
    638 
    639         def on_update(www_auth):
    640             if not www_auth and 'www-authenticate' in self.headers:
    641                 del self.headers['www-authenticate']
    642             elif www_auth:
    643                 self.headers['WWW-Authenticate'] = www_auth.to_header()
    644 
    645         header = self.headers.get('www-authenticate')
    646         return parse_www_authenticate_header(header, on_update)
    647 
    648 
    649 class Request(BaseRequest, AcceptMixin, ETagRequestMixin, UserAgentMixin, AuthorizationMixin, CommonRequestDescriptorsMixin):
    650     pass
    651 
    652 
    653 class Response(BaseResponse, ETagResponseMixin, ResponseStreamMixin, CommonResponseDescriptorsMixin, WWWAuthenticateMixin):
    654     pass