323 lines
12 KiB
Python
323 lines
12 KiB
Python
from datetime import datetime
|
|
from datetime import timedelta
|
|
|
|
from .._compat import string_types
|
|
from ..datastructures import CallbackDict
|
|
from ..http import dump_age
|
|
from ..http import dump_header
|
|
from ..http import dump_options_header
|
|
from ..http import http_date
|
|
from ..http import parse_age
|
|
from ..http import parse_date
|
|
from ..http import parse_options_header
|
|
from ..http import parse_set_header
|
|
from ..utils import cached_property
|
|
from ..utils import environ_property
|
|
from ..utils import get_content_type
|
|
from ..utils import header_property
|
|
from ..wsgi import get_content_length
|
|
|
|
|
|
class CommonRequestDescriptorsMixin(object):
|
|
"""A mixin for :class:`BaseRequest` subclasses. Request objects that
|
|
mix this class in will automatically get descriptors for a couple of
|
|
HTTP headers with automatic type conversion.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
content_type = environ_property(
|
|
"CONTENT_TYPE",
|
|
doc="""The Content-Type entity-header field indicates the media
|
|
type of the entity-body sent to the recipient or, in the case of
|
|
the HEAD method, the media type that would have been sent had
|
|
the request been a GET.""",
|
|
)
|
|
|
|
@cached_property
|
|
def content_length(self):
|
|
"""The Content-Length entity-header field indicates the size of the
|
|
entity-body in bytes or, in the case of the HEAD method, the size of
|
|
the entity-body that would have been sent had the request been a
|
|
GET.
|
|
"""
|
|
return get_content_length(self.environ)
|
|
|
|
content_encoding = environ_property(
|
|
"HTTP_CONTENT_ENCODING",
|
|
doc="""The Content-Encoding entity-header field is used as a
|
|
modifier to the media-type. When present, its value indicates
|
|
what additional content codings have been applied to the
|
|
entity-body, and thus what decoding mechanisms must be applied
|
|
in order to obtain the media-type referenced by the Content-Type
|
|
header field.
|
|
|
|
.. versionadded:: 0.9""",
|
|
)
|
|
content_md5 = environ_property(
|
|
"HTTP_CONTENT_MD5",
|
|
doc="""The Content-MD5 entity-header field, as defined in
|
|
RFC 1864, is an MD5 digest of the entity-body for the purpose of
|
|
providing an end-to-end message integrity check (MIC) of the
|
|
entity-body. (Note: a MIC is good for detecting accidental
|
|
modification of the entity-body in transit, but is not proof
|
|
against malicious attacks.)
|
|
|
|
.. versionadded:: 0.9""",
|
|
)
|
|
referrer = environ_property(
|
|
"HTTP_REFERER",
|
|
doc="""The Referer[sic] request-header field allows the client
|
|
to specify, for the server's benefit, the address (URI) of the
|
|
resource from which the Request-URI was obtained (the
|
|
"referrer", although the header field is misspelled).""",
|
|
)
|
|
date = environ_property(
|
|
"HTTP_DATE",
|
|
None,
|
|
parse_date,
|
|
doc="""The Date general-header field represents the date and
|
|
time at which the message was originated, having the same
|
|
semantics as orig-date in RFC 822.""",
|
|
)
|
|
max_forwards = environ_property(
|
|
"HTTP_MAX_FORWARDS",
|
|
None,
|
|
int,
|
|
doc="""The Max-Forwards request-header field provides a
|
|
mechanism with the TRACE and OPTIONS methods to limit the number
|
|
of proxies or gateways that can forward the request to the next
|
|
inbound server.""",
|
|
)
|
|
|
|
def _parse_content_type(self):
|
|
if not hasattr(self, "_parsed_content_type"):
|
|
self._parsed_content_type = parse_options_header(
|
|
self.environ.get("CONTENT_TYPE", "")
|
|
)
|
|
|
|
@property
|
|
def mimetype(self):
|
|
"""Like :attr:`content_type`, but without parameters (eg, without
|
|
charset, type etc.) and always lowercase. For example if the content
|
|
type is ``text/HTML; charset=utf-8`` the mimetype would be
|
|
``'text/html'``.
|
|
"""
|
|
self._parse_content_type()
|
|
return self._parsed_content_type[0].lower()
|
|
|
|
@property
|
|
def mimetype_params(self):
|
|
"""The mimetype parameters as dict. For example if the content
|
|
type is ``text/html; charset=utf-8`` the params would be
|
|
``{'charset': 'utf-8'}``.
|
|
"""
|
|
self._parse_content_type()
|
|
return self._parsed_content_type[1]
|
|
|
|
@cached_property
|
|
def pragma(self):
|
|
"""The Pragma general-header field is used to include
|
|
implementation-specific directives that might apply to any recipient
|
|
along the request/response chain. All pragma directives specify
|
|
optional behavior from the viewpoint of the protocol; however, some
|
|
systems MAY require that behavior be consistent with the directives.
|
|
"""
|
|
return parse_set_header(self.environ.get("HTTP_PRAGMA", ""))
|
|
|
|
|
|
class CommonResponseDescriptorsMixin(object):
|
|
"""A mixin for :class:`BaseResponse` subclasses. Response objects that
|
|
mix this class in will automatically get descriptors for a couple of
|
|
HTTP headers with automatic type conversion.
|
|
"""
|
|
|
|
@property
|
|
def mimetype(self):
|
|
"""The mimetype (content type without charset etc.)"""
|
|
ct = self.headers.get("content-type")
|
|
if ct:
|
|
return ct.split(";")[0].strip()
|
|
|
|
@mimetype.setter
|
|
def mimetype(self, value):
|
|
self.headers["Content-Type"] = get_content_type(value, self.charset)
|
|
|
|
@property
|
|
def mimetype_params(self):
|
|
"""The mimetype parameters as dict. For example if the
|
|
content type is ``text/html; charset=utf-8`` the params would be
|
|
``{'charset': 'utf-8'}``.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
def on_update(d):
|
|
self.headers["Content-Type"] = dump_options_header(self.mimetype, d)
|
|
|
|
d = parse_options_header(self.headers.get("content-type", ""))[1]
|
|
return CallbackDict(d, on_update)
|
|
|
|
location = header_property(
|
|
"Location",
|
|
doc="""The Location response-header field is used to redirect
|
|
the recipient to a location other than the Request-URI for
|
|
completion of the request or identification of a new
|
|
resource.""",
|
|
)
|
|
age = header_property(
|
|
"Age",
|
|
None,
|
|
parse_age,
|
|
dump_age,
|
|
doc="""The Age response-header field conveys the sender's
|
|
estimate of the amount of time since the response (or its
|
|
revalidation) was generated at the origin server.
|
|
|
|
Age values are non-negative decimal integers, representing time
|
|
in seconds.""",
|
|
)
|
|
content_type = header_property(
|
|
"Content-Type",
|
|
doc="""The Content-Type entity-header field indicates the media
|
|
type of the entity-body sent to the recipient or, in the case of
|
|
the HEAD method, the media type that would have been sent had
|
|
the request been a GET.""",
|
|
)
|
|
content_length = header_property(
|
|
"Content-Length",
|
|
None,
|
|
int,
|
|
str,
|
|
doc="""The Content-Length entity-header field indicates the size
|
|
of the entity-body, in decimal number of OCTETs, sent to the
|
|
recipient or, in the case of the HEAD method, the size of the
|
|
entity-body that would have been sent had the request been a
|
|
GET.""",
|
|
)
|
|
content_location = header_property(
|
|
"Content-Location",
|
|
doc="""The Content-Location entity-header field MAY be used to
|
|
supply the resource location for the entity enclosed in the
|
|
message when that entity is accessible from a location separate
|
|
from the requested resource's URI.""",
|
|
)
|
|
content_encoding = header_property(
|
|
"Content-Encoding",
|
|
doc="""The Content-Encoding entity-header field is used as a
|
|
modifier to the media-type. When present, its value indicates
|
|
what additional content codings have been applied to the
|
|
entity-body, and thus what decoding mechanisms must be applied
|
|
in order to obtain the media-type referenced by the Content-Type
|
|
header field.""",
|
|
)
|
|
content_md5 = header_property(
|
|
"Content-MD5",
|
|
doc="""The Content-MD5 entity-header field, as defined in
|
|
RFC 1864, is an MD5 digest of the entity-body for the purpose of
|
|
providing an end-to-end message integrity check (MIC) of the
|
|
entity-body. (Note: a MIC is good for detecting accidental
|
|
modification of the entity-body in transit, but is not proof
|
|
against malicious attacks.)""",
|
|
)
|
|
date = header_property(
|
|
"Date",
|
|
None,
|
|
parse_date,
|
|
http_date,
|
|
doc="""The Date general-header field represents the date and
|
|
time at which the message was originated, having the same
|
|
semantics as orig-date in RFC 822.""",
|
|
)
|
|
expires = header_property(
|
|
"Expires",
|
|
None,
|
|
parse_date,
|
|
http_date,
|
|
doc="""The Expires entity-header field gives the date/time after
|
|
which the response is considered stale. A stale cache entry may
|
|
not normally be returned by a cache.""",
|
|
)
|
|
last_modified = header_property(
|
|
"Last-Modified",
|
|
None,
|
|
parse_date,
|
|
http_date,
|
|
doc="""The Last-Modified entity-header field indicates the date
|
|
and time at which the origin server believes the variant was
|
|
last modified.""",
|
|
)
|
|
|
|
@property
|
|
def retry_after(self):
|
|
"""The Retry-After response-header field can be used with a
|
|
503 (Service Unavailable) response to indicate how long the
|
|
service is expected to be unavailable to the requesting client.
|
|
|
|
Time in seconds until expiration or date.
|
|
"""
|
|
value = self.headers.get("retry-after")
|
|
if value is None:
|
|
return
|
|
elif value.isdigit():
|
|
return datetime.utcnow() + timedelta(seconds=int(value))
|
|
return parse_date(value)
|
|
|
|
@retry_after.setter
|
|
def retry_after(self, value):
|
|
if value is None:
|
|
if "retry-after" in self.headers:
|
|
del self.headers["retry-after"]
|
|
return
|
|
elif isinstance(value, datetime):
|
|
value = http_date(value)
|
|
else:
|
|
value = str(value)
|
|
self.headers["Retry-After"] = value
|
|
|
|
def _set_property(name, doc=None): # noqa: B902
|
|
def fget(self):
|
|
def on_update(header_set):
|
|
if not header_set and name in self.headers:
|
|
del self.headers[name]
|
|
elif header_set:
|
|
self.headers[name] = header_set.to_header()
|
|
|
|
return parse_set_header(self.headers.get(name), on_update)
|
|
|
|
def fset(self, value):
|
|
if not value:
|
|
del self.headers[name]
|
|
elif isinstance(value, string_types):
|
|
self.headers[name] = value
|
|
else:
|
|
self.headers[name] = dump_header(value)
|
|
|
|
return property(fget, fset, doc=doc)
|
|
|
|
vary = _set_property(
|
|
"Vary",
|
|
doc="""The Vary field value indicates the set of request-header
|
|
fields that fully determines, while the response is fresh,
|
|
whether a cache is permitted to use the response to reply to a
|
|
subsequent request without revalidation.""",
|
|
)
|
|
content_language = _set_property(
|
|
"Content-Language",
|
|
doc="""The Content-Language entity-header field describes the
|
|
natural language(s) of the intended audience for the enclosed
|
|
entity. Note that this might not be equivalent to all the
|
|
languages used within the entity-body.""",
|
|
)
|
|
allow = _set_property(
|
|
"Allow",
|
|
doc="""The Allow entity-header field lists the set of methods
|
|
supported by the resource identified by the Request-URI. The
|
|
purpose of this field is strictly to inform the recipient of
|
|
valid methods associated with the resource. An Allow header
|
|
field MUST be present in a 405 (Method Not Allowed)
|
|
response.""",
|
|
)
|
|
|
|
del _set_property
|