ranchimallflo-api/py3.7/lib/python3.7/site-packages/quart/wrappers/_base.py

368 lines
12 KiB
Python

from base64 import b64decode
from cgi import parse_header
from datetime import datetime
from email.utils import parsedate_to_datetime
from http.cookies import SimpleCookie
from typing import Any, AnyStr, Dict, List, Optional, TYPE_CHECKING, Union
from urllib.parse import parse_qs, ParseResult, urlunparse
from urllib.request import parse_http_list, parse_keqv_list
from ..datastructures import (
Accept, Authorization, CharsetAccept, CIMultiDict, ETags, Headers, IfRange, LanguageAccept,
MIMEAccept, MultiDict, Range, RequestAccessControl, RequestCacheControl,
)
from ..json import loads
if TYPE_CHECKING:
from ..routing import Rule # noqa
sentinel = object()
class JSONMixin:
"""Mixin to provide get_json methods from objects.
The class must support _load_data_json and have a mimetype
attribute.
"""
_cached_json = sentinel
@property
def mimetype(self) -> str:
"""Return the mimetype of the associated data."""
raise NotImplementedError()
async def _load_json_data(self) -> str:
"""Return the data after decoding."""
raise NotImplementedError()
@property
def is_json(self) -> bool:
"""Returns True if the content_type is json like."""
content_type = self.mimetype
if content_type == 'application/json' or (
content_type.startswith('application/') and content_type.endswith('+json')
):
return True
else:
return False
@property
async def json(self) -> Any:
return await self.get_json()
async def get_json(
self, force: bool=False, silent: bool=False, cache: bool=True,
) -> Any:
"""Parses the body data as JSON and returns it.
Arguments:
force: Force JSON parsing even if the mimetype is not JSON.
silent: Do not trigger error handling if parsing fails, without
this the :meth:`on_json_loading_failed` will be called on
error.
cache: Cache the parsed JSON on this request object.
"""
if cache and self._cached_json is not sentinel:
return self._cached_json
if not (force or self.is_json):
return None
data = await self._load_json_data()
try:
result = loads(data)
except ValueError as error:
if silent:
result = None
else:
self.on_json_loading_failed(error)
if cache:
self._cached_json = result
return result
def on_json_loading_failed(self, error: Exception) -> None:
"""Handle a JSON parsing error.
Arguments:
error: The exception raised during parsing.
"""
from ..exceptions import BadRequest # noqa Avoiding circular import
raise BadRequest()
class _BaseRequestResponse:
"""This is the base class for Request or Response.
It implements a number of properties for header handling.
Attributes:
charset: The default charset for encoding/decoding.
"""
charset = url_charset = 'utf-8'
def __init__(self, headers: Optional[Union[dict, CIMultiDict, Headers]]) -> None:
self.headers: Headers
if headers is None:
self.headers = Headers()
else:
self.headers = Headers(headers)
@property
def mimetype(self) -> str:
"""Returns the mimetype parsed from the Content-Type header."""
return parse_header(self.headers.get('Content-Type', ''))[0]
@mimetype.setter
def mimetype(self, value: str) -> None:
"""Set the mimetype to the value."""
if (
value.startswith('text/') or value == 'application/xml' or
(value.startswith('application/') and value.endswith('+xml'))
):
mimetype = f"{value}; charset={self.charset}"
else:
mimetype = value
self.headers['Content-Type'] = mimetype
@property
def mimetype_params(self) -> Dict[str, str]:
"""Returns the params parsed from the Content-Type header."""
return parse_header(self.headers.get('Content-Type', ''))[1]
async def get_data(self, raw: bool=True) -> AnyStr:
raise NotImplementedError()
class BaseRequestWebsocket(_BaseRequestResponse):
"""This class is the basis for Requests and websockets..
Attributes:
routing_exception: If an exception is raised during the route
matching it will be stored here.
url_rule: The rule that this request has been matched too.
view_args: The keyword arguments for the view from the route
matching.
"""
routing_exception: Optional[Exception] = None
url_rule: Optional['Rule'] = None
view_args: Optional[Dict[str, Any]] = None
def __init__(
self,
method: str,
scheme: str,
path: str,
query_string: bytes,
headers: CIMultiDict,
) -> None:
"""Create a request or websocket base object.
Arguments:
method: The HTTP verb.
scheme: The scheme used for the request.
path: The full unquoted path of the request.
query_string: The raw bytes for the query string part.
headers: The request headers.
Attributes:
args: The query string arguments.
scheme: The URL scheme, http or https.
"""
super().__init__(headers)
self.args = MultiDict()
for key, values in parse_qs(query_string.decode('ascii'), keep_blank_values=True).items():
for value in values:
self.args.add(key, value)
self.path = path
self.query_string = query_string
self.scheme = scheme
self.method = method
@property
def endpoint(self) -> Optional[str]:
"""Returns the corresponding endpoint matched for this request.
This can be None if the request has not been matched with a
rule.
"""
if self.url_rule is not None:
return self.url_rule.endpoint
else:
return None
@property
def blueprint(self) -> Optional[str]:
"""Returns the blueprint the matched endpoint belongs to.
This can be None if the request has not been matched or the
endpoint is not in a blueprint.
"""
if self.endpoint is not None and '.' in self.endpoint:
return self.endpoint.rsplit('.', 1)[0]
else:
return None
@property
def accept_charsets(self) -> CharsetAccept:
return CharsetAccept(self.headers.get('Accept-Charset', ''))
@property
def accept_encodings(self) -> Accept:
return Accept(self.headers.get('Accept-Encoding', ''))
@property
def accept_languages(self) -> LanguageAccept:
return LanguageAccept(self.headers.get('Accept-Language', ''))
@property
def accept_mimetypes(self) -> MIMEAccept:
return MIMEAccept(self.headers.get('Accept', ''))
@property
def access_control(self) -> RequestAccessControl:
return RequestAccessControl.from_headers(
self.headers.get('Origin', ''), self.headers.get('Access-Control-Request-Headers', ''),
self.headers.get('Access-Control-Request-Method', ''),
)
@property
def authorization(self) -> Optional[Authorization]:
header = self.headers.get('Authorization', '')
try:
type_, value = header.split(None, 1)
type_ = type_.lower()
except ValueError:
return None
else:
if type_ == 'basic':
try:
username, password = b64decode(value.encode()).decode().split(':', 1)
except ValueError:
return None
else:
return Authorization(username=username, password=password)
elif type_ == 'digest':
items = parse_http_list(value)
params = parse_keqv_list(items)
for key in 'username', 'realm', 'nonce', 'uri', 'response':
if key not in params:
return None
if ('cnonce' in params or 'nc' in params) and 'qop' not in params:
return None
return Authorization(**params)
return None
@property
def cache_control(self) -> RequestCacheControl:
return RequestCacheControl.from_header(self.headers.get('Cache-Control', '')) # type: ignore # noqa: E501
@property
def remote_addr(self) -> str:
"""Returns the remote address of the request, faked into the headers."""
return self.headers['Remote-Addr']
@property
def base_url(self) -> str:
"""Returns the base url without query string or fragments."""
return urlunparse(ParseResult(self.scheme, self.host, self.path, '', '', ''))
@property
def full_path(self) -> str:
if self.query_string:
return f"{self.path}?{self.query_string.decode('ascii')}"
else:
return self.path
@property
def host(self) -> str:
return self.headers.get('host') or self.headers.get(':authority')
@property
def host_url(self) -> str:
return urlunparse(ParseResult(self.scheme, self.host, '', '', '', ''))
@property
def url(self) -> str:
"""Returns the full url requested."""
return urlunparse(
ParseResult(
self.scheme, self.host, self.path, '', self.query_string.decode('ascii'), '',
),
)
@property
def url_root(self) -> str:
return urlunparse(
ParseResult(
self.scheme, self.host, self.path.rsplit('/', 1)[0] + '/', '', '', '',
),
)
@property
def is_secure(self) -> bool:
return self.scheme in {'https', 'wss'}
@property
def cookies(self) -> Dict[str, str]:
"""The parsed cookies attached to this request."""
cookies = SimpleCookie()
cookies.load(self.headers.get('Cookie', ''))
return {key: cookie.value for key, cookie in cookies.items()}
@property
def access_route(self) -> List[str]:
if 'X-Forwarded-For' in self.headers:
return parse_http_list(self.headers['X-Forwarded-For'])
else:
return [self.remote_addr]
@property
def date(self) -> Optional[datetime]:
if 'date' in self.headers:
return parsedate_to_datetime(self.headers['date'])
else:
return None
@property
def if_match(self) -> ETags:
return ETags.from_header(self.headers.get('If-Match', ''))
@property
def if_modified_since(self) -> Optional[datetime]:
if 'If-Modified-Since' in self.headers:
return parsedate_to_datetime(self.headers['If-Modified-Since'])
else:
return None
@property
def if_none_match(self) -> ETags:
return ETags.from_header(self.headers.get('If-None-Match', ''))
@property
def if_range(self) -> IfRange:
return IfRange.from_header(self.headers.get('If-Range', ''))
@property
def max_forwards(self) -> Optional[str]:
return self.headers.get('Max-Forwards')
@property
def pragma(self) -> List[str]:
return parse_http_list(self.headers.get('Pragma', ''))
@property
def range(self) -> Range:
return Range.from_header(self.headers.get('Range', ''))
@property
def referrer(self) -> Optional[str]:
return self.headers.get('Referer')
@property
def if_unmodified_since(self) -> Optional[datetime]:
if 'If-Unmodified-Since' in self.headers:
return parsedate_to_datetime(self.headers['If-Unmodified-Since'])
else:
return None