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