WIP
This commit is contained in:
6
myvenv/lib/python3.10/site-packages/werkzeug/__init__.py
Normal file
6
myvenv/lib/python3.10/site-packages/werkzeug/__init__.py
Normal file
@ -0,0 +1,6 @@
|
||||
from .serving import run_simple as run_simple
|
||||
from .test import Client as Client
|
||||
from .wrappers import Request as Request
|
||||
from .wrappers import Response as Response
|
||||
|
||||
__version__ = "2.0.3"
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
626
myvenv/lib/python3.10/site-packages/werkzeug/_internal.py
Normal file
626
myvenv/lib/python3.10/site-packages/werkzeug/_internal.py
Normal file
@ -0,0 +1,626 @@
|
||||
import inspect
|
||||
import logging
|
||||
import operator
|
||||
import re
|
||||
import string
|
||||
import sys
|
||||
import typing
|
||||
import typing as t
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from itertools import chain
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
from .wrappers.request import Request # noqa: F401
|
||||
|
||||
_logger: t.Optional[logging.Logger] = None
|
||||
_signature_cache = WeakKeyDictionary() # type: ignore
|
||||
_epoch_ord = date(1970, 1, 1).toordinal()
|
||||
_legal_cookie_chars = frozenset(
|
||||
c.encode("ascii")
|
||||
for c in f"{string.ascii_letters}{string.digits}/=!#$%&'*+-.^_`|~:"
|
||||
)
|
||||
|
||||
_cookie_quoting_map = {b",": b"\\054", b";": b"\\073", b'"': b'\\"', b"\\": b"\\\\"}
|
||||
for _i in chain(range(32), range(127, 256)):
|
||||
_cookie_quoting_map[_i.to_bytes(1, sys.byteorder)] = f"\\{_i:03o}".encode("latin1")
|
||||
|
||||
_octal_re = re.compile(rb"\\[0-3][0-7][0-7]")
|
||||
_quote_re = re.compile(rb"[\\].")
|
||||
_legal_cookie_chars_re = rb"[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]"
|
||||
_cookie_re = re.compile(
|
||||
rb"""
|
||||
(?P<key>[^=;]+)
|
||||
(?:\s*=\s*
|
||||
(?P<val>
|
||||
"(?:[^\\"]|\\.)*" |
|
||||
(?:.*?)
|
||||
)
|
||||
)?
|
||||
\s*;
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
class _Missing:
|
||||
def __repr__(self) -> str:
|
||||
return "no value"
|
||||
|
||||
def __reduce__(self) -> str:
|
||||
return "_missing"
|
||||
|
||||
|
||||
_missing = _Missing()
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _make_encode_wrapper(reference: str) -> t.Callable[[str], str]:
|
||||
...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _make_encode_wrapper(reference: bytes) -> t.Callable[[str], bytes]:
|
||||
...
|
||||
|
||||
|
||||
def _make_encode_wrapper(reference: t.AnyStr) -> t.Callable[[str], t.AnyStr]:
|
||||
"""Create a function that will be called with a string argument. If
|
||||
the reference is bytes, values will be encoded to bytes.
|
||||
"""
|
||||
if isinstance(reference, str):
|
||||
return lambda x: x
|
||||
|
||||
return operator.methodcaller("encode", "latin1")
|
||||
|
||||
|
||||
def _check_str_tuple(value: t.Tuple[t.AnyStr, ...]) -> None:
|
||||
"""Ensure tuple items are all strings or all bytes."""
|
||||
if not value:
|
||||
return
|
||||
|
||||
item_type = str if isinstance(value[0], str) else bytes
|
||||
|
||||
if any(not isinstance(item, item_type) for item in value):
|
||||
raise TypeError(f"Cannot mix str and bytes arguments (got {value!r})")
|
||||
|
||||
|
||||
_default_encoding = sys.getdefaultencoding()
|
||||
|
||||
|
||||
def _to_bytes(
|
||||
x: t.Union[str, bytes], charset: str = _default_encoding, errors: str = "strict"
|
||||
) -> bytes:
|
||||
if x is None or isinstance(x, bytes):
|
||||
return x
|
||||
|
||||
if isinstance(x, (bytearray, memoryview)):
|
||||
return bytes(x)
|
||||
|
||||
if isinstance(x, str):
|
||||
return x.encode(charset, errors)
|
||||
|
||||
raise TypeError("Expected bytes")
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _to_str( # type: ignore
|
||||
x: None,
|
||||
charset: t.Optional[str] = ...,
|
||||
errors: str = ...,
|
||||
allow_none_charset: bool = ...,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _to_str(
|
||||
x: t.Any,
|
||||
charset: t.Optional[str] = ...,
|
||||
errors: str = ...,
|
||||
allow_none_charset: bool = ...,
|
||||
) -> str:
|
||||
...
|
||||
|
||||
|
||||
def _to_str(
|
||||
x: t.Optional[t.Any],
|
||||
charset: t.Optional[str] = _default_encoding,
|
||||
errors: str = "strict",
|
||||
allow_none_charset: bool = False,
|
||||
) -> t.Optional[t.Union[str, bytes]]:
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
|
||||
if not isinstance(x, (bytes, bytearray)):
|
||||
return str(x)
|
||||
|
||||
if charset is None:
|
||||
if allow_none_charset:
|
||||
return x
|
||||
|
||||
return x.decode(charset, errors) # type: ignore
|
||||
|
||||
|
||||
def _wsgi_decoding_dance(
|
||||
s: str, charset: str = "utf-8", errors: str = "replace"
|
||||
) -> str:
|
||||
return s.encode("latin1").decode(charset, errors)
|
||||
|
||||
|
||||
def _wsgi_encoding_dance(
|
||||
s: str, charset: str = "utf-8", errors: str = "replace"
|
||||
) -> str:
|
||||
if isinstance(s, bytes):
|
||||
return s.decode("latin1", errors)
|
||||
|
||||
return s.encode(charset).decode("latin1", errors)
|
||||
|
||||
|
||||
def _get_environ(obj: t.Union["WSGIEnvironment", "Request"]) -> "WSGIEnvironment":
|
||||
env = getattr(obj, "environ", obj)
|
||||
assert isinstance(
|
||||
env, dict
|
||||
), f"{type(obj).__name__!r} is not a WSGI environment (has to be a dict)"
|
||||
return env
|
||||
|
||||
|
||||
def _has_level_handler(logger: logging.Logger) -> bool:
|
||||
"""Check if there is a handler in the logging chain that will handle
|
||||
the given logger's effective level.
|
||||
"""
|
||||
level = logger.getEffectiveLevel()
|
||||
current = logger
|
||||
|
||||
while current:
|
||||
if any(handler.level <= level for handler in current.handlers):
|
||||
return True
|
||||
|
||||
if not current.propagate:
|
||||
break
|
||||
|
||||
current = current.parent # type: ignore
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class _ColorStreamHandler(logging.StreamHandler):
|
||||
"""On Windows, wrap stream with Colorama for ANSI style support."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
try:
|
||||
import colorama
|
||||
except ImportError:
|
||||
stream = None
|
||||
else:
|
||||
stream = colorama.AnsiToWin32(sys.stderr)
|
||||
|
||||
super().__init__(stream)
|
||||
|
||||
|
||||
def _log(type: str, message: str, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
"""Log a message to the 'werkzeug' logger.
|
||||
|
||||
The logger is created the first time it is needed. If there is no
|
||||
level set, it is set to :data:`logging.INFO`. If there is no handler
|
||||
for the logger's effective level, a :class:`logging.StreamHandler`
|
||||
is added.
|
||||
"""
|
||||
global _logger
|
||||
|
||||
if _logger is None:
|
||||
_logger = logging.getLogger("werkzeug")
|
||||
|
||||
if _logger.level == logging.NOTSET:
|
||||
_logger.setLevel(logging.INFO)
|
||||
|
||||
if not _has_level_handler(_logger):
|
||||
_logger.addHandler(_ColorStreamHandler())
|
||||
|
||||
getattr(_logger, type)(message.rstrip(), *args, **kwargs)
|
||||
|
||||
|
||||
def _parse_signature(func): # type: ignore
|
||||
"""Return a signature object for the function.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1 along with ``utils.bind`` and
|
||||
``validate_arguments``.
|
||||
"""
|
||||
# if we have a cached validator for this function, return it
|
||||
parse = _signature_cache.get(func)
|
||||
if parse is not None:
|
||||
return parse
|
||||
|
||||
# inspect the function signature and collect all the information
|
||||
tup = inspect.getfullargspec(func)
|
||||
positional, vararg_var, kwarg_var, defaults = tup[:4]
|
||||
defaults = defaults or ()
|
||||
arg_count = len(positional)
|
||||
arguments = []
|
||||
for idx, name in enumerate(positional):
|
||||
if isinstance(name, list):
|
||||
raise TypeError(
|
||||
"cannot parse functions that unpack tuples in the function signature"
|
||||
)
|
||||
try:
|
||||
default = defaults[idx - arg_count]
|
||||
except IndexError:
|
||||
param = (name, False, None)
|
||||
else:
|
||||
param = (name, True, default)
|
||||
arguments.append(param)
|
||||
arguments = tuple(arguments)
|
||||
|
||||
def parse(args, kwargs): # type: ignore
|
||||
new_args = []
|
||||
missing = []
|
||||
extra = {}
|
||||
|
||||
# consume as many arguments as positional as possible
|
||||
for idx, (name, has_default, default) in enumerate(arguments):
|
||||
try:
|
||||
new_args.append(args[idx])
|
||||
except IndexError:
|
||||
try:
|
||||
new_args.append(kwargs.pop(name))
|
||||
except KeyError:
|
||||
if has_default:
|
||||
new_args.append(default)
|
||||
else:
|
||||
missing.append(name)
|
||||
else:
|
||||
if name in kwargs:
|
||||
extra[name] = kwargs.pop(name)
|
||||
|
||||
# handle extra arguments
|
||||
extra_positional = args[arg_count:]
|
||||
if vararg_var is not None:
|
||||
new_args.extend(extra_positional)
|
||||
extra_positional = ()
|
||||
if kwargs and kwarg_var is None:
|
||||
extra.update(kwargs)
|
||||
kwargs = {}
|
||||
|
||||
return (
|
||||
new_args,
|
||||
kwargs,
|
||||
missing,
|
||||
extra,
|
||||
extra_positional,
|
||||
arguments,
|
||||
vararg_var,
|
||||
kwarg_var,
|
||||
)
|
||||
|
||||
_signature_cache[func] = parse
|
||||
return parse
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _dt_as_utc(dt: None) -> None:
|
||||
...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _dt_as_utc(dt: datetime) -> datetime:
|
||||
...
|
||||
|
||||
|
||||
def _dt_as_utc(dt: t.Optional[datetime]) -> t.Optional[datetime]:
|
||||
if dt is None:
|
||||
return dt
|
||||
|
||||
if dt.tzinfo is None:
|
||||
return dt.replace(tzinfo=timezone.utc)
|
||||
elif dt.tzinfo != timezone.utc:
|
||||
return dt.astimezone(timezone.utc)
|
||||
|
||||
return dt
|
||||
|
||||
|
||||
_TAccessorValue = t.TypeVar("_TAccessorValue")
|
||||
|
||||
|
||||
class _DictAccessorProperty(t.Generic[_TAccessorValue]):
|
||||
"""Baseclass for `environ_property` and `header_property`."""
|
||||
|
||||
read_only = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
default: t.Optional[_TAccessorValue] = None,
|
||||
load_func: t.Optional[t.Callable[[str], _TAccessorValue]] = None,
|
||||
dump_func: t.Optional[t.Callable[[_TAccessorValue], str]] = None,
|
||||
read_only: t.Optional[bool] = None,
|
||||
doc: t.Optional[str] = None,
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.default = default
|
||||
self.load_func = load_func
|
||||
self.dump_func = dump_func
|
||||
if read_only is not None:
|
||||
self.read_only = read_only
|
||||
self.__doc__ = doc
|
||||
|
||||
def lookup(self, instance: t.Any) -> t.MutableMapping[str, t.Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
@typing.overload
|
||||
def __get__(
|
||||
self, instance: None, owner: type
|
||||
) -> "_DictAccessorProperty[_TAccessorValue]":
|
||||
...
|
||||
|
||||
@typing.overload
|
||||
def __get__(self, instance: t.Any, owner: type) -> _TAccessorValue:
|
||||
...
|
||||
|
||||
def __get__(
|
||||
self, instance: t.Optional[t.Any], owner: type
|
||||
) -> t.Union[_TAccessorValue, "_DictAccessorProperty[_TAccessorValue]"]:
|
||||
if instance is None:
|
||||
return self
|
||||
|
||||
storage = self.lookup(instance)
|
||||
|
||||
if self.name not in storage:
|
||||
return self.default # type: ignore
|
||||
|
||||
value = storage[self.name]
|
||||
|
||||
if self.load_func is not None:
|
||||
try:
|
||||
return self.load_func(value)
|
||||
except (ValueError, TypeError):
|
||||
return self.default # type: ignore
|
||||
|
||||
return value # type: ignore
|
||||
|
||||
def __set__(self, instance: t.Any, value: _TAccessorValue) -> None:
|
||||
if self.read_only:
|
||||
raise AttributeError("read only property")
|
||||
|
||||
if self.dump_func is not None:
|
||||
self.lookup(instance)[self.name] = self.dump_func(value)
|
||||
else:
|
||||
self.lookup(instance)[self.name] = value
|
||||
|
||||
def __delete__(self, instance: t.Any) -> None:
|
||||
if self.read_only:
|
||||
raise AttributeError("read only property")
|
||||
|
||||
self.lookup(instance).pop(self.name, None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} {self.name}>"
|
||||
|
||||
|
||||
def _cookie_quote(b: bytes) -> bytes:
|
||||
buf = bytearray()
|
||||
all_legal = True
|
||||
_lookup = _cookie_quoting_map.get
|
||||
_push = buf.extend
|
||||
|
||||
for char_int in b:
|
||||
char = char_int.to_bytes(1, sys.byteorder)
|
||||
if char not in _legal_cookie_chars:
|
||||
all_legal = False
|
||||
char = _lookup(char, char)
|
||||
_push(char)
|
||||
|
||||
if all_legal:
|
||||
return bytes(buf)
|
||||
return bytes(b'"' + buf + b'"')
|
||||
|
||||
|
||||
def _cookie_unquote(b: bytes) -> bytes:
|
||||
if len(b) < 2:
|
||||
return b
|
||||
if b[:1] != b'"' or b[-1:] != b'"':
|
||||
return b
|
||||
|
||||
b = b[1:-1]
|
||||
|
||||
i = 0
|
||||
n = len(b)
|
||||
rv = bytearray()
|
||||
_push = rv.extend
|
||||
|
||||
while 0 <= i < n:
|
||||
o_match = _octal_re.search(b, i)
|
||||
q_match = _quote_re.search(b, i)
|
||||
if not o_match and not q_match:
|
||||
rv.extend(b[i:])
|
||||
break
|
||||
j = k = -1
|
||||
if o_match:
|
||||
j = o_match.start(0)
|
||||
if q_match:
|
||||
k = q_match.start(0)
|
||||
if q_match and (not o_match or k < j):
|
||||
_push(b[i:k])
|
||||
_push(b[k + 1 : k + 2])
|
||||
i = k + 2
|
||||
else:
|
||||
_push(b[i:j])
|
||||
rv.append(int(b[j + 1 : j + 4], 8))
|
||||
i = j + 4
|
||||
|
||||
return bytes(rv)
|
||||
|
||||
|
||||
def _cookie_parse_impl(b: bytes) -> t.Iterator[t.Tuple[bytes, bytes]]:
|
||||
"""Lowlevel cookie parsing facility that operates on bytes."""
|
||||
i = 0
|
||||
n = len(b)
|
||||
|
||||
while i < n:
|
||||
match = _cookie_re.search(b + b";", i)
|
||||
if not match:
|
||||
break
|
||||
|
||||
key = match.group("key").strip()
|
||||
value = match.group("val") or b""
|
||||
i = match.end(0)
|
||||
|
||||
yield key, _cookie_unquote(value)
|
||||
|
||||
|
||||
def _encode_idna(domain: str) -> bytes:
|
||||
# If we're given bytes, make sure they fit into ASCII
|
||||
if isinstance(domain, bytes):
|
||||
domain.decode("ascii")
|
||||
return domain
|
||||
|
||||
# Otherwise check if it's already ascii, then return
|
||||
try:
|
||||
return domain.encode("ascii")
|
||||
except UnicodeError:
|
||||
pass
|
||||
|
||||
# Otherwise encode each part separately
|
||||
return b".".join(p.encode("idna") for p in domain.split("."))
|
||||
|
||||
|
||||
def _decode_idna(domain: t.Union[str, bytes]) -> str:
|
||||
# If the input is a string try to encode it to ascii to do the idna
|
||||
# decoding. If that fails because of a unicode error, then we
|
||||
# already have a decoded idna domain.
|
||||
if isinstance(domain, str):
|
||||
try:
|
||||
domain = domain.encode("ascii")
|
||||
except UnicodeError:
|
||||
return domain # type: ignore
|
||||
|
||||
# Decode each part separately. If a part fails, try to decode it
|
||||
# with ascii and silently ignore errors. This makes sense because
|
||||
# the idna codec does not have error handling.
|
||||
def decode_part(part: bytes) -> str:
|
||||
try:
|
||||
return part.decode("idna")
|
||||
except UnicodeError:
|
||||
return part.decode("ascii", "ignore")
|
||||
|
||||
return ".".join(decode_part(p) for p in domain.split(b"."))
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _make_cookie_domain(domain: None) -> None:
|
||||
...
|
||||
|
||||
|
||||
@typing.overload
|
||||
def _make_cookie_domain(domain: str) -> bytes:
|
||||
...
|
||||
|
||||
|
||||
def _make_cookie_domain(domain: t.Optional[str]) -> t.Optional[bytes]:
|
||||
if domain is None:
|
||||
return None
|
||||
domain = _encode_idna(domain)
|
||||
if b":" in domain:
|
||||
domain = domain.split(b":", 1)[0]
|
||||
if b"." in domain:
|
||||
return domain
|
||||
raise ValueError(
|
||||
"Setting 'domain' for a cookie on a server running locally (ex: "
|
||||
"localhost) is not supported by complying browsers. You should "
|
||||
"have something like: '127.0.0.1 localhost dev.localhost' on "
|
||||
"your hosts file and then point your server to run on "
|
||||
"'dev.localhost' and also set 'domain' for 'dev.localhost'"
|
||||
)
|
||||
|
||||
|
||||
def _easteregg(app: t.Optional["WSGIApplication"] = None) -> "WSGIApplication":
|
||||
"""Like the name says. But who knows how it works?"""
|
||||
|
||||
def bzzzzzzz(gyver: bytes) -> str:
|
||||
import base64
|
||||
import zlib
|
||||
|
||||
return zlib.decompress(base64.b64decode(gyver)).decode("ascii")
|
||||
|
||||
gyver = "\n".join(
|
||||
[
|
||||
x + (77 - len(x)) * " "
|
||||
for x in bzzzzzzz(
|
||||
b"""
|
||||
eJyFlzuOJDkMRP06xRjymKgDJCDQStBYT8BCgK4gTwfQ2fcFs2a2FzvZk+hvlcRvRJD148efHt9m
|
||||
9Xz94dRY5hGt1nrYcXx7us9qlcP9HHNh28rz8dZj+q4rynVFFPdlY4zH873NKCexrDM6zxxRymzz
|
||||
4QIxzK4bth1PV7+uHn6WXZ5C4ka/+prFzx3zWLMHAVZb8RRUxtFXI5DTQ2n3Hi2sNI+HK43AOWSY
|
||||
jmEzE4naFp58PdzhPMdslLVWHTGUVpSxImw+pS/D+JhzLfdS1j7PzUMxij+mc2U0I9zcbZ/HcZxc
|
||||
q1QjvvcThMYFnp93agEx392ZdLJWXbi/Ca4Oivl4h/Y1ErEqP+lrg7Xa4qnUKu5UE9UUA4xeqLJ5
|
||||
jWlPKJvR2yhRI7xFPdzPuc6adXu6ovwXwRPXXnZHxlPtkSkqWHilsOrGrvcVWXgGP3daXomCj317
|
||||
8P2UOw/NnA0OOikZyFf3zZ76eN9QXNwYdD8f8/LdBRFg0BO3bB+Pe/+G8er8tDJv83XTkj7WeMBJ
|
||||
v/rnAfdO51d6sFglfi8U7zbnr0u9tyJHhFZNXYfH8Iafv2Oa+DT6l8u9UYlajV/hcEgk1x8E8L/r
|
||||
XJXl2SK+GJCxtnyhVKv6GFCEB1OO3f9YWAIEbwcRWv/6RPpsEzOkXURMN37J0PoCSYeBnJQd9Giu
|
||||
LxYQJNlYPSo/iTQwgaihbART7Fcyem2tTSCcwNCs85MOOpJtXhXDe0E7zgZJkcxWTar/zEjdIVCk
|
||||
iXy87FW6j5aGZhttDBoAZ3vnmlkx4q4mMmCdLtnHkBXFMCReqthSGkQ+MDXLLCpXwBs0t+sIhsDI
|
||||
tjBB8MwqYQpLygZ56rRHHpw+OAVyGgaGRHWy2QfXez+ZQQTTBkmRXdV/A9LwH6XGZpEAZU8rs4pE
|
||||
1R4FQ3Uwt8RKEtRc0/CrANUoes3EzM6WYcFyskGZ6UTHJWenBDS7h163Eo2bpzqxNE9aVgEM2CqI
|
||||
GAJe9Yra4P5qKmta27VjzYdR04Vc7KHeY4vs61C0nbywFmcSXYjzBHdiEjraS7PGG2jHHTpJUMxN
|
||||
Jlxr3pUuFvlBWLJGE3GcA1/1xxLcHmlO+LAXbhrXah1tD6Ze+uqFGdZa5FM+3eHcKNaEarutAQ0A
|
||||
QMAZHV+ve6LxAwWnXbbSXEG2DmCX5ijeLCKj5lhVFBrMm+ryOttCAeFpUdZyQLAQkA06RLs56rzG
|
||||
8MID55vqr/g64Qr/wqwlE0TVxgoiZhHrbY2h1iuuyUVg1nlkpDrQ7Vm1xIkI5XRKLedN9EjzVchu
|
||||
jQhXcVkjVdgP2O99QShpdvXWoSwkp5uMwyjt3jiWCqWGSiaaPAzohjPanXVLbM3x0dNskJsaCEyz
|
||||
DTKIs+7WKJD4ZcJGfMhLFBf6hlbnNkLEePF8Cx2o2kwmYF4+MzAxa6i+6xIQkswOqGO+3x9NaZX8
|
||||
MrZRaFZpLeVTYI9F/djY6DDVVs340nZGmwrDqTCiiqD5luj3OzwpmQCiQhdRYowUYEA3i1WWGwL4
|
||||
GCtSoO4XbIPFeKGU13XPkDf5IdimLpAvi2kVDVQbzOOa4KAXMFlpi/hV8F6IDe0Y2reg3PuNKT3i
|
||||
RYhZqtkQZqSB2Qm0SGtjAw7RDwaM1roESC8HWiPxkoOy0lLTRFG39kvbLZbU9gFKFRvixDZBJmpi
|
||||
Xyq3RE5lW00EJjaqwp/v3EByMSpVZYsEIJ4APaHmVtpGSieV5CALOtNUAzTBiw81GLgC0quyzf6c
|
||||
NlWknzJeCsJ5fup2R4d8CYGN77mu5vnO1UqbfElZ9E6cR6zbHjgsr9ly18fXjZoPeDjPuzlWbFwS
|
||||
pdvPkhntFvkc13qb9094LL5NrA3NIq3r9eNnop9DizWOqCEbyRBFJTHn6Tt3CG1o8a4HevYh0XiJ
|
||||
sR0AVVHuGuMOIfbuQ/OKBkGRC6NJ4u7sbPX8bG/n5sNIOQ6/Y/BX3IwRlTSabtZpYLB85lYtkkgm
|
||||
p1qXK3Du2mnr5INXmT/78KI12n11EFBkJHHp0wJyLe9MvPNUGYsf+170maayRoy2lURGHAIapSpQ
|
||||
krEDuNoJCHNlZYhKpvw4mspVWxqo415n8cD62N9+EfHrAvqQnINStetek7RY2Urv8nxsnGaZfRr/
|
||||
nhXbJ6m/yl1LzYqscDZA9QHLNbdaSTTr+kFg3bC0iYbX/eQy0Bv3h4B50/SGYzKAXkCeOLI3bcAt
|
||||
mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
|
||||
7f2zLkGNv8b191cD/3vs9Q833z8t"""
|
||||
).splitlines()
|
||||
]
|
||||
)
|
||||
|
||||
def easteregged(
|
||||
environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
def injecting_start_response(
|
||||
status: str, headers: t.List[t.Tuple[str, str]], exc_info: t.Any = None
|
||||
) -> t.Callable[[bytes], t.Any]:
|
||||
headers.append(("X-Powered-By", "Werkzeug"))
|
||||
return start_response(status, headers, exc_info)
|
||||
|
||||
if app is not None and environ.get("QUERY_STRING") != "macgybarchakku":
|
||||
return app(environ, injecting_start_response)
|
||||
injecting_start_response("200 OK", [("Content-Type", "text/html")])
|
||||
return [
|
||||
f"""\
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>About Werkzeug</title>
|
||||
<style type="text/css">
|
||||
body {{ font: 15px Georgia, serif; text-align: center; }}
|
||||
a {{ color: #333; text-decoration: none; }}
|
||||
h1 {{ font-size: 30px; margin: 20px 0 10px 0; }}
|
||||
p {{ margin: 0 0 30px 0; }}
|
||||
pre {{ font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
|
||||
<p>the Swiss Army knife of Python web development.</p>
|
||||
<pre>{gyver}\n\n\n</pre>
|
||||
</body>
|
||||
</html>""".encode(
|
||||
"latin1"
|
||||
)
|
||||
]
|
||||
|
||||
return easteregged
|
||||
430
myvenv/lib/python3.10/site-packages/werkzeug/_reloader.py
Normal file
430
myvenv/lib/python3.10/site-packages/werkzeug/_reloader.py
Normal file
@ -0,0 +1,430 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import typing as t
|
||||
from itertools import chain
|
||||
from pathlib import PurePath
|
||||
|
||||
from ._internal import _log
|
||||
|
||||
# The various system prefixes where imports are found. Base values are
|
||||
# different when running in a virtualenv. The stat reloader won't scan
|
||||
# these directories, it would be too inefficient.
|
||||
prefix = {sys.prefix, sys.base_prefix, sys.exec_prefix, sys.base_exec_prefix}
|
||||
|
||||
if hasattr(sys, "real_prefix"):
|
||||
# virtualenv < 20
|
||||
prefix.add(sys.real_prefix) # type: ignore
|
||||
|
||||
_ignore_prefixes = tuple(prefix)
|
||||
del prefix
|
||||
|
||||
|
||||
def _iter_module_paths() -> t.Iterator[str]:
|
||||
"""Find the filesystem paths associated with imported modules."""
|
||||
# List is in case the value is modified by the app while updating.
|
||||
for module in list(sys.modules.values()):
|
||||
name = getattr(module, "__file__", None)
|
||||
|
||||
if name is None:
|
||||
continue
|
||||
|
||||
while not os.path.isfile(name):
|
||||
# Zip file, find the base file without the module path.
|
||||
old = name
|
||||
name = os.path.dirname(name)
|
||||
|
||||
if name == old: # skip if it was all directories somehow
|
||||
break
|
||||
else:
|
||||
yield name
|
||||
|
||||
|
||||
def _remove_by_pattern(paths: t.Set[str], exclude_patterns: t.Set[str]) -> None:
|
||||
for pattern in exclude_patterns:
|
||||
paths.difference_update(fnmatch.filter(paths, pattern))
|
||||
|
||||
|
||||
def _find_stat_paths(
|
||||
extra_files: t.Set[str], exclude_patterns: t.Set[str]
|
||||
) -> t.Iterable[str]:
|
||||
"""Find paths for the stat reloader to watch. Returns imported
|
||||
module files, Python files under non-system paths. Extra files and
|
||||
Python files under extra directories can also be scanned.
|
||||
|
||||
System paths have to be excluded for efficiency. Non-system paths,
|
||||
such as a project root or ``sys.path.insert``, should be the paths
|
||||
of interest to the user anyway.
|
||||
"""
|
||||
paths = set()
|
||||
|
||||
for path in chain(list(sys.path), extra_files):
|
||||
path = os.path.abspath(path)
|
||||
|
||||
if os.path.isfile(path):
|
||||
# zip file on sys.path, or extra file
|
||||
paths.add(path)
|
||||
|
||||
for root, dirs, files in os.walk(path):
|
||||
# Ignore system prefixes for efficience. Don't scan
|
||||
# __pycache__, it will have a py or pyc module at the import
|
||||
# path. As an optimization, ignore .git and .hg since
|
||||
# nothing interesting will be there.
|
||||
if root.startswith(_ignore_prefixes) or os.path.basename(root) in {
|
||||
"__pycache__",
|
||||
".git",
|
||||
".hg",
|
||||
}:
|
||||
dirs.clear()
|
||||
continue
|
||||
|
||||
for name in files:
|
||||
if name.endswith((".py", ".pyc")):
|
||||
paths.add(os.path.join(root, name))
|
||||
|
||||
paths.update(_iter_module_paths())
|
||||
_remove_by_pattern(paths, exclude_patterns)
|
||||
return paths
|
||||
|
||||
|
||||
def _find_watchdog_paths(
|
||||
extra_files: t.Set[str], exclude_patterns: t.Set[str]
|
||||
) -> t.Iterable[str]:
|
||||
"""Find paths for the stat reloader to watch. Looks at the same
|
||||
sources as the stat reloader, but watches everything under
|
||||
directories instead of individual files.
|
||||
"""
|
||||
dirs = set()
|
||||
|
||||
for name in chain(list(sys.path), extra_files):
|
||||
name = os.path.abspath(name)
|
||||
|
||||
if os.path.isfile(name):
|
||||
name = os.path.dirname(name)
|
||||
|
||||
dirs.add(name)
|
||||
|
||||
for name in _iter_module_paths():
|
||||
dirs.add(os.path.dirname(name))
|
||||
|
||||
_remove_by_pattern(dirs, exclude_patterns)
|
||||
return _find_common_roots(dirs)
|
||||
|
||||
|
||||
def _find_common_roots(paths: t.Iterable[str]) -> t.Iterable[str]:
|
||||
root: t.Dict[str, dict] = {}
|
||||
|
||||
for chunks in sorted((PurePath(x).parts for x in paths), key=len, reverse=True):
|
||||
node = root
|
||||
|
||||
for chunk in chunks:
|
||||
node = node.setdefault(chunk, {})
|
||||
|
||||
node.clear()
|
||||
|
||||
rv = set()
|
||||
|
||||
def _walk(node: t.Mapping[str, dict], path: t.Tuple[str, ...]) -> None:
|
||||
for prefix, child in node.items():
|
||||
_walk(child, path + (prefix,))
|
||||
|
||||
if not node:
|
||||
rv.add(os.path.join(*path))
|
||||
|
||||
_walk(root, ())
|
||||
return rv
|
||||
|
||||
|
||||
def _get_args_for_reloading() -> t.List[str]:
|
||||
"""Determine how the script was executed, and return the args needed
|
||||
to execute it again in a new process.
|
||||
"""
|
||||
rv = [sys.executable]
|
||||
py_script = sys.argv[0]
|
||||
args = sys.argv[1:]
|
||||
# Need to look at main module to determine how it was executed.
|
||||
__main__ = sys.modules["__main__"]
|
||||
|
||||
# The value of __package__ indicates how Python was called. It may
|
||||
# not exist if a setuptools script is installed as an egg. It may be
|
||||
# set incorrectly for entry points created with pip on Windows.
|
||||
if getattr(__main__, "__package__", None) is None or (
|
||||
os.name == "nt"
|
||||
and __main__.__package__ == ""
|
||||
and not os.path.exists(py_script)
|
||||
and os.path.exists(f"{py_script}.exe")
|
||||
):
|
||||
# Executed a file, like "python app.py".
|
||||
py_script = os.path.abspath(py_script)
|
||||
|
||||
if os.name == "nt":
|
||||
# Windows entry points have ".exe" extension and should be
|
||||
# called directly.
|
||||
if not os.path.exists(py_script) and os.path.exists(f"{py_script}.exe"):
|
||||
py_script += ".exe"
|
||||
|
||||
if (
|
||||
os.path.splitext(sys.executable)[1] == ".exe"
|
||||
and os.path.splitext(py_script)[1] == ".exe"
|
||||
):
|
||||
rv.pop(0)
|
||||
|
||||
rv.append(py_script)
|
||||
else:
|
||||
# Executed a module, like "python -m werkzeug.serving".
|
||||
if sys.argv[0] == "-m":
|
||||
# Flask works around previous behavior by putting
|
||||
# "-m flask" in sys.argv.
|
||||
# TODO remove this once Flask no longer misbehaves
|
||||
args = sys.argv
|
||||
else:
|
||||
if os.path.isfile(py_script):
|
||||
# Rewritten by Python from "-m script" to "/path/to/script.py".
|
||||
py_module = t.cast(str, __main__.__package__)
|
||||
name = os.path.splitext(os.path.basename(py_script))[0]
|
||||
|
||||
if name != "__main__":
|
||||
py_module += f".{name}"
|
||||
else:
|
||||
# Incorrectly rewritten by pydevd debugger from "-m script" to "script".
|
||||
py_module = py_script
|
||||
|
||||
rv.extend(("-m", py_module.lstrip(".")))
|
||||
|
||||
rv.extend(args)
|
||||
return rv
|
||||
|
||||
|
||||
class ReloaderLoop:
|
||||
name = ""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
extra_files: t.Optional[t.Iterable[str]] = None,
|
||||
exclude_patterns: t.Optional[t.Iterable[str]] = None,
|
||||
interval: t.Union[int, float] = 1,
|
||||
) -> None:
|
||||
self.extra_files: t.Set[str] = {os.path.abspath(x) for x in extra_files or ()}
|
||||
self.exclude_patterns: t.Set[str] = set(exclude_patterns or ())
|
||||
self.interval = interval
|
||||
|
||||
def __enter__(self) -> "ReloaderLoop":
|
||||
"""Do any setup, then run one step of the watch to populate the
|
||||
initial filesystem state.
|
||||
"""
|
||||
self.run_step()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
|
||||
"""Clean up any resources associated with the reloader."""
|
||||
pass
|
||||
|
||||
def run(self) -> None:
|
||||
"""Continually run the watch step, sleeping for the configured
|
||||
interval after each step.
|
||||
"""
|
||||
while True:
|
||||
self.run_step()
|
||||
time.sleep(self.interval)
|
||||
|
||||
def run_step(self) -> None:
|
||||
"""Run one step for watching the filesystem. Called once to set
|
||||
up initial state, then repeatedly to update it.
|
||||
"""
|
||||
pass
|
||||
|
||||
def restart_with_reloader(self) -> int:
|
||||
"""Spawn a new Python interpreter with the same arguments as the
|
||||
current one, but running the reloader thread.
|
||||
"""
|
||||
while True:
|
||||
_log("info", f" * Restarting with {self.name}")
|
||||
args = _get_args_for_reloading()
|
||||
new_environ = os.environ.copy()
|
||||
new_environ["WERKZEUG_RUN_MAIN"] = "true"
|
||||
exit_code = subprocess.call(args, env=new_environ, close_fds=False)
|
||||
|
||||
if exit_code != 3:
|
||||
return exit_code
|
||||
|
||||
def trigger_reload(self, filename: str) -> None:
|
||||
self.log_reload(filename)
|
||||
sys.exit(3)
|
||||
|
||||
def log_reload(self, filename: str) -> None:
|
||||
filename = os.path.abspath(filename)
|
||||
_log("info", f" * Detected change in {filename!r}, reloading")
|
||||
|
||||
|
||||
class StatReloaderLoop(ReloaderLoop):
|
||||
name = "stat"
|
||||
|
||||
def __enter__(self) -> ReloaderLoop:
|
||||
self.mtimes: t.Dict[str, float] = {}
|
||||
return super().__enter__()
|
||||
|
||||
def run_step(self) -> None:
|
||||
for name in chain(_find_stat_paths(self.extra_files, self.exclude_patterns)):
|
||||
try:
|
||||
mtime = os.stat(name).st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
old_time = self.mtimes.get(name)
|
||||
|
||||
if old_time is None:
|
||||
self.mtimes[name] = mtime
|
||||
continue
|
||||
|
||||
if mtime > old_time:
|
||||
self.trigger_reload(name)
|
||||
|
||||
|
||||
class WatchdogReloaderLoop(ReloaderLoop):
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import PatternMatchingEventHandler
|
||||
|
||||
super().__init__(*args, **kwargs)
|
||||
trigger_reload = self.trigger_reload
|
||||
|
||||
class EventHandler(PatternMatchingEventHandler): # type: ignore
|
||||
def on_any_event(self, event): # type: ignore
|
||||
trigger_reload(event.src_path)
|
||||
|
||||
reloader_name = Observer.__name__.lower()
|
||||
|
||||
if reloader_name.endswith("observer"):
|
||||
reloader_name = reloader_name[:-8]
|
||||
|
||||
self.name = f"watchdog ({reloader_name})"
|
||||
self.observer = Observer()
|
||||
# Extra patterns can be non-Python files, match them in addition
|
||||
# to all Python files in default and extra directories. Ignore
|
||||
# __pycache__ since a change there will always have a change to
|
||||
# the source file (or initial pyc file) as well. Ignore Git and
|
||||
# Mercurial internal changes.
|
||||
extra_patterns = [p for p in self.extra_files if not os.path.isdir(p)]
|
||||
self.event_handler = EventHandler(
|
||||
patterns=["*.py", "*.pyc", "*.zip", *extra_patterns],
|
||||
ignore_patterns=[
|
||||
"*/__pycache__/*",
|
||||
"*/.git/*",
|
||||
"*/.hg/*",
|
||||
*self.exclude_patterns,
|
||||
],
|
||||
)
|
||||
self.should_reload = False
|
||||
|
||||
def trigger_reload(self, filename: str) -> None:
|
||||
# This is called inside an event handler, which means throwing
|
||||
# SystemExit has no effect.
|
||||
# https://github.com/gorakhargosh/watchdog/issues/294
|
||||
self.should_reload = True
|
||||
self.log_reload(filename)
|
||||
|
||||
def __enter__(self) -> ReloaderLoop:
|
||||
self.watches: t.Dict[str, t.Any] = {}
|
||||
self.observer.start()
|
||||
return super().__enter__()
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb): # type: ignore
|
||||
self.observer.stop()
|
||||
self.observer.join()
|
||||
|
||||
def run(self) -> None:
|
||||
while not self.should_reload:
|
||||
self.run_step()
|
||||
time.sleep(self.interval)
|
||||
|
||||
sys.exit(3)
|
||||
|
||||
def run_step(self) -> None:
|
||||
to_delete = set(self.watches)
|
||||
|
||||
for path in _find_watchdog_paths(self.extra_files, self.exclude_patterns):
|
||||
if path not in self.watches:
|
||||
try:
|
||||
self.watches[path] = self.observer.schedule(
|
||||
self.event_handler, path, recursive=True
|
||||
)
|
||||
except OSError:
|
||||
# Clear this path from list of watches We don't want
|
||||
# the same error message showing again in the next
|
||||
# iteration.
|
||||
self.watches[path] = None
|
||||
|
||||
to_delete.discard(path)
|
||||
|
||||
for path in to_delete:
|
||||
watch = self.watches.pop(path, None)
|
||||
|
||||
if watch is not None:
|
||||
self.observer.unschedule(watch)
|
||||
|
||||
|
||||
reloader_loops: t.Dict[str, t.Type[ReloaderLoop]] = {
|
||||
"stat": StatReloaderLoop,
|
||||
"watchdog": WatchdogReloaderLoop,
|
||||
}
|
||||
|
||||
try:
|
||||
__import__("watchdog.observers")
|
||||
except ImportError:
|
||||
reloader_loops["auto"] = reloader_loops["stat"]
|
||||
else:
|
||||
reloader_loops["auto"] = reloader_loops["watchdog"]
|
||||
|
||||
|
||||
def ensure_echo_on() -> None:
|
||||
"""Ensure that echo mode is enabled. Some tools such as PDB disable
|
||||
it which causes usability issues after a reload."""
|
||||
# tcgetattr will fail if stdin isn't a tty
|
||||
if sys.stdin is None or not sys.stdin.isatty():
|
||||
return
|
||||
|
||||
try:
|
||||
import termios
|
||||
except ImportError:
|
||||
return
|
||||
|
||||
attributes = termios.tcgetattr(sys.stdin)
|
||||
|
||||
if not attributes[3] & termios.ECHO:
|
||||
attributes[3] |= termios.ECHO
|
||||
termios.tcsetattr(sys.stdin, termios.TCSANOW, attributes)
|
||||
|
||||
|
||||
def run_with_reloader(
|
||||
main_func: t.Callable[[], None],
|
||||
extra_files: t.Optional[t.Iterable[str]] = None,
|
||||
exclude_patterns: t.Optional[t.Iterable[str]] = None,
|
||||
interval: t.Union[int, float] = 1,
|
||||
reloader_type: str = "auto",
|
||||
) -> None:
|
||||
"""Run the given function in an independent Python interpreter."""
|
||||
import signal
|
||||
|
||||
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
|
||||
reloader = reloader_loops[reloader_type](
|
||||
extra_files=extra_files, exclude_patterns=exclude_patterns, interval=interval
|
||||
)
|
||||
|
||||
try:
|
||||
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||
ensure_echo_on()
|
||||
t = threading.Thread(target=main_func, args=())
|
||||
t.daemon = True
|
||||
|
||||
# Enter the reloader to set up initial state, then start
|
||||
# the app thread and reloader update loop.
|
||||
with reloader:
|
||||
t.start()
|
||||
reloader.run()
|
||||
else:
|
||||
sys.exit(reloader.restart_with_reloader())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
3059
myvenv/lib/python3.10/site-packages/werkzeug/datastructures.py
Normal file
3059
myvenv/lib/python3.10/site-packages/werkzeug/datastructures.py
Normal file
File diff suppressed because it is too large
Load Diff
912
myvenv/lib/python3.10/site-packages/werkzeug/datastructures.pyi
Normal file
912
myvenv/lib/python3.10/site-packages/werkzeug/datastructures.pyi
Normal file
@ -0,0 +1,912 @@
|
||||
from datetime import datetime
|
||||
from os import PathLike
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Collection
|
||||
from typing import Dict
|
||||
from typing import FrozenSet
|
||||
from typing import Generic
|
||||
from typing import Hashable
|
||||
from typing import IO
|
||||
from typing import Iterable
|
||||
from typing import Iterator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import NoReturn
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
from typing_extensions import Literal
|
||||
from typing_extensions import SupportsIndex
|
||||
|
||||
K = TypeVar("K")
|
||||
V = TypeVar("V")
|
||||
T = TypeVar("T")
|
||||
D = TypeVar("D")
|
||||
_CD = TypeVar("_CD", bound="CallbackDict")
|
||||
|
||||
def is_immutable(self: object) -> NoReturn: ...
|
||||
def iter_multi_items(
|
||||
mapping: Union[Mapping[K, Union[V, Iterable[V]]], Iterable[Tuple[K, V]]]
|
||||
) -> Iterator[Tuple[K, V]]: ...
|
||||
|
||||
class ImmutableListMixin(List[V]):
|
||||
_hash_cache: Optional[int]
|
||||
def __hash__(self) -> int: ... # type: ignore
|
||||
def __delitem__(self, key: Union[SupportsIndex, slice]) -> NoReturn: ...
|
||||
def __iadd__(self, other: t.Any) -> NoReturn: ... # type: ignore
|
||||
def __imul__(self, other: SupportsIndex) -> NoReturn: ...
|
||||
def __setitem__( # type: ignore
|
||||
self, key: Union[int, slice], value: V
|
||||
) -> NoReturn: ...
|
||||
def append(self, value: V) -> NoReturn: ...
|
||||
def remove(self, value: V) -> NoReturn: ...
|
||||
def extend(self, values: Iterable[V]) -> NoReturn: ...
|
||||
def insert(self, pos: SupportsIndex, value: V) -> NoReturn: ...
|
||||
def pop(self, index: SupportsIndex = -1) -> NoReturn: ...
|
||||
def reverse(self) -> NoReturn: ...
|
||||
def sort(
|
||||
self, key: Optional[Callable[[V], Any]] = None, reverse: bool = False
|
||||
) -> NoReturn: ...
|
||||
|
||||
class ImmutableList(ImmutableListMixin[V]): ...
|
||||
|
||||
class ImmutableDictMixin(Dict[K, V]):
|
||||
_hash_cache: Optional[int]
|
||||
@classmethod
|
||||
def fromkeys( # type: ignore
|
||||
cls, keys: Iterable[K], value: Optional[V] = None
|
||||
) -> ImmutableDictMixin[K, V]: ...
|
||||
def _iter_hashitems(self) -> Iterable[Hashable]: ...
|
||||
def __hash__(self) -> int: ... # type: ignore
|
||||
def setdefault(self, key: K, default: Optional[V] = None) -> NoReturn: ...
|
||||
def update(self, *args: Any, **kwargs: V) -> NoReturn: ...
|
||||
def pop(self, key: K, default: Optional[V] = None) -> NoReturn: ... # type: ignore
|
||||
def popitem(self) -> NoReturn: ...
|
||||
def __setitem__(self, key: K, value: V) -> NoReturn: ...
|
||||
def __delitem__(self, key: K) -> NoReturn: ...
|
||||
def clear(self) -> NoReturn: ...
|
||||
|
||||
class ImmutableMultiDictMixin(ImmutableDictMixin[K, V]):
|
||||
def _iter_hashitems(self) -> Iterable[Hashable]: ...
|
||||
def add(self, key: K, value: V) -> NoReturn: ...
|
||||
def popitemlist(self) -> NoReturn: ...
|
||||
def poplist(self, key: K) -> NoReturn: ...
|
||||
def setlist(self, key: K, new_list: Iterable[V]) -> NoReturn: ...
|
||||
def setlistdefault(
|
||||
self, key: K, default_list: Optional[Iterable[V]] = None
|
||||
) -> NoReturn: ...
|
||||
|
||||
def _calls_update(name: str) -> Callable[[UpdateDictMixin[K, V]], Any]: ...
|
||||
|
||||
class UpdateDictMixin(Dict[K, V]):
|
||||
on_update: Optional[Callable[[UpdateDictMixin[K, V]], None]]
|
||||
def setdefault(self, key: K, default: Optional[V] = None) -> V: ...
|
||||
@overload
|
||||
def pop(self, key: K) -> V: ...
|
||||
@overload
|
||||
def pop(self, key: K, default: Union[V, T] = ...) -> Union[V, T]: ...
|
||||
def __setitem__(self, key: K, value: V) -> None: ...
|
||||
def __delitem__(self, key: K) -> None: ...
|
||||
def clear(self) -> None: ...
|
||||
def popitem(self) -> Tuple[K, V]: ...
|
||||
def update(
|
||||
self, *args: Union[Mapping[K, V], Iterable[Tuple[K, V]]], **kwargs: V
|
||||
) -> None: ...
|
||||
|
||||
class TypeConversionDict(Dict[K, V]):
|
||||
@overload
|
||||
def get(self, key: K, default: None = ..., type: None = ...) -> Optional[V]: ...
|
||||
@overload
|
||||
def get(self, key: K, default: D, type: None = ...) -> Union[D, V]: ...
|
||||
@overload
|
||||
def get(self, key: K, default: D, type: Callable[[V], T]) -> Union[D, T]: ...
|
||||
@overload
|
||||
def get(self, key: K, type: Callable[[V], T]) -> Optional[T]: ...
|
||||
|
||||
class ImmutableTypeConversionDict(ImmutableDictMixin[K, V], TypeConversionDict[K, V]):
|
||||
def copy(self) -> TypeConversionDict[K, V]: ...
|
||||
def __copy__(self) -> ImmutableTypeConversionDict: ...
|
||||
|
||||
class MultiDict(TypeConversionDict[K, V]):
|
||||
def __init__(
|
||||
self,
|
||||
mapping: Optional[
|
||||
Union[Mapping[K, Union[Iterable[V], V]], Iterable[Tuple[K, V]]]
|
||||
] = None,
|
||||
) -> None: ...
|
||||
def __getitem__(self, item: K) -> V: ...
|
||||
def __setitem__(self, key: K, value: V) -> None: ...
|
||||
def add(self, key: K, value: V) -> None: ...
|
||||
@overload
|
||||
def getlist(self, key: K) -> List[V]: ...
|
||||
@overload
|
||||
def getlist(self, key: K, type: Callable[[V], T] = ...) -> List[T]: ...
|
||||
def setlist(self, key: K, new_list: Iterable[V]) -> None: ...
|
||||
def setdefault(self, key: K, default: Optional[V] = None) -> V: ...
|
||||
def setlistdefault(
|
||||
self, key: K, default_list: Optional[Iterable[V]] = None
|
||||
) -> List[V]: ...
|
||||
def items(self, multi: bool = False) -> Iterator[Tuple[K, V]]: ... # type: ignore
|
||||
def lists(self) -> Iterator[Tuple[K, List[V]]]: ...
|
||||
def values(self) -> Iterator[V]: ... # type: ignore
|
||||
def listvalues(self) -> Iterator[List[V]]: ...
|
||||
def copy(self) -> MultiDict[K, V]: ...
|
||||
def deepcopy(self, memo: Any = None) -> MultiDict[K, V]: ...
|
||||
@overload
|
||||
def to_dict(self) -> Dict[K, V]: ...
|
||||
@overload
|
||||
def to_dict(self, flat: Literal[False]) -> Dict[K, List[V]]: ...
|
||||
def update( # type: ignore
|
||||
self, mapping: Union[Mapping[K, Union[Iterable[V], V]], Iterable[Tuple[K, V]]]
|
||||
) -> None: ...
|
||||
@overload
|
||||
def pop(self, key: K) -> V: ...
|
||||
@overload
|
||||
def pop(self, key: K, default: Union[V, T] = ...) -> Union[V, T]: ...
|
||||
def popitem(self) -> Tuple[K, V]: ...
|
||||
def poplist(self, key: K) -> List[V]: ...
|
||||
def popitemlist(self) -> Tuple[K, List[V]]: ...
|
||||
def __copy__(self) -> MultiDict[K, V]: ...
|
||||
def __deepcopy__(self, memo: Any) -> MultiDict[K, V]: ...
|
||||
|
||||
class _omd_bucket(Generic[K, V]):
|
||||
prev: Optional[_omd_bucket]
|
||||
next: Optional[_omd_bucket]
|
||||
key: K
|
||||
value: V
|
||||
def __init__(self, omd: OrderedMultiDict, key: K, value: V) -> None: ...
|
||||
def unlink(self, omd: OrderedMultiDict) -> None: ...
|
||||
|
||||
class OrderedMultiDict(MultiDict[K, V]):
|
||||
_first_bucket: Optional[_omd_bucket]
|
||||
_last_bucket: Optional[_omd_bucket]
|
||||
def __init__(self, mapping: Optional[Mapping[K, V]] = None) -> None: ...
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __getitem__(self, key: K) -> V: ...
|
||||
def __setitem__(self, key: K, value: V) -> None: ...
|
||||
def __delitem__(self, key: K) -> None: ...
|
||||
def keys(self) -> Iterator[K]: ... # type: ignore
|
||||
def __iter__(self) -> Iterator[K]: ...
|
||||
def values(self) -> Iterator[V]: ... # type: ignore
|
||||
def items(self, multi: bool = False) -> Iterator[Tuple[K, V]]: ... # type: ignore
|
||||
def lists(self) -> Iterator[Tuple[K, List[V]]]: ...
|
||||
def listvalues(self) -> Iterator[List[V]]: ...
|
||||
def add(self, key: K, value: V) -> None: ...
|
||||
@overload
|
||||
def getlist(self, key: K) -> List[V]: ...
|
||||
@overload
|
||||
def getlist(self, key: K, type: Callable[[V], T] = ...) -> List[T]: ...
|
||||
def setlist(self, key: K, new_list: Iterable[V]) -> None: ...
|
||||
def setlistdefault(
|
||||
self, key: K, default_list: Optional[Iterable[V]] = None
|
||||
) -> List[V]: ...
|
||||
def update( # type: ignore
|
||||
self, mapping: Union[Mapping[K, V], Iterable[Tuple[K, V]]]
|
||||
) -> None: ...
|
||||
def poplist(self, key: K) -> List[V]: ...
|
||||
@overload
|
||||
def pop(self, key: K) -> V: ...
|
||||
@overload
|
||||
def pop(self, key: K, default: Union[V, T] = ...) -> Union[V, T]: ...
|
||||
def popitem(self) -> Tuple[K, V]: ...
|
||||
def popitemlist(self) -> Tuple[K, List[V]]: ...
|
||||
|
||||
def _options_header_vkw(
|
||||
value: str, kw: Mapping[str, Optional[Union[str, int]]]
|
||||
) -> str: ...
|
||||
def _unicodify_header_value(value: Union[str, int]) -> str: ...
|
||||
|
||||
HV = Union[str, int]
|
||||
|
||||
class Headers(Dict[str, str]):
|
||||
_list: List[Tuple[str, str]]
|
||||
def __init__(
|
||||
self,
|
||||
defaults: Optional[
|
||||
Union[Mapping[str, Union[HV, Iterable[HV]]], Iterable[Tuple[str, HV]]]
|
||||
] = None,
|
||||
) -> None: ...
|
||||
@overload
|
||||
def __getitem__(self, key: str) -> str: ...
|
||||
@overload
|
||||
def __getitem__(self, key: int) -> Tuple[str, str]: ...
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> Headers: ...
|
||||
@overload
|
||||
def __getitem__(self, key: str, _get_mode: Literal[True] = ...) -> str: ...
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
@overload # type: ignore
|
||||
def get(self, key: str, default: str) -> str: ...
|
||||
@overload
|
||||
def get(self, key: str, default: Optional[str] = None) -> Optional[str]: ...
|
||||
@overload
|
||||
def get(
|
||||
self, key: str, default: Optional[T] = None, type: Callable[[str], T] = ...
|
||||
) -> Optional[T]: ...
|
||||
@overload
|
||||
def getlist(self, key: str) -> List[str]: ...
|
||||
@overload
|
||||
def getlist(self, key: str, type: Callable[[str], T]) -> List[T]: ...
|
||||
def get_all(self, name: str) -> List[str]: ...
|
||||
def items( # type: ignore
|
||||
self, lower: bool = False
|
||||
) -> Iterator[Tuple[str, str]]: ...
|
||||
def keys(self, lower: bool = False) -> Iterator[str]: ... # type: ignore
|
||||
def values(self) -> Iterator[str]: ... # type: ignore
|
||||
def extend(
|
||||
self,
|
||||
*args: Union[Mapping[str, Union[HV, Iterable[HV]]], Iterable[Tuple[str, HV]]],
|
||||
**kwargs: Union[HV, Iterable[HV]],
|
||||
) -> None: ...
|
||||
@overload
|
||||
def __delitem__(self, key: Union[str, int, slice]) -> None: ...
|
||||
@overload
|
||||
def __delitem__(self, key: str, _index_operation: Literal[False]) -> None: ...
|
||||
def remove(self, key: str) -> None: ...
|
||||
@overload # type: ignore
|
||||
def pop(self, key: str, default: Optional[str] = None) -> str: ...
|
||||
@overload
|
||||
def pop(
|
||||
self, key: Optional[int] = None, default: Optional[Tuple[str, str]] = None
|
||||
) -> Tuple[str, str]: ...
|
||||
def popitem(self) -> Tuple[str, str]: ...
|
||||
def __contains__(self, key: str) -> bool: ... # type: ignore
|
||||
def has_key(self, key: str) -> bool: ...
|
||||
def __iter__(self) -> Iterator[Tuple[str, str]]: ... # type: ignore
|
||||
def add(self, _key: str, _value: HV, **kw: HV) -> None: ...
|
||||
def _validate_value(self, value: str) -> None: ...
|
||||
def add_header(self, _key: str, _value: HV, **_kw: HV) -> None: ...
|
||||
def clear(self) -> None: ...
|
||||
def set(self, _key: str, _value: HV, **kw: HV) -> None: ...
|
||||
def setlist(self, key: str, values: Iterable[HV]) -> None: ...
|
||||
def setdefault(self, key: str, default: HV) -> str: ... # type: ignore
|
||||
def setlistdefault(self, key: str, default: Iterable[HV]) -> None: ...
|
||||
@overload
|
||||
def __setitem__(self, key: str, value: HV) -> None: ...
|
||||
@overload
|
||||
def __setitem__(self, key: int, value: Tuple[str, HV]) -> None: ...
|
||||
@overload
|
||||
def __setitem__(self, key: slice, value: Iterable[Tuple[str, HV]]) -> None: ...
|
||||
def update(
|
||||
self,
|
||||
*args: Union[Mapping[str, HV], Iterable[Tuple[str, HV]]],
|
||||
**kwargs: Union[HV, Iterable[HV]],
|
||||
) -> None: ...
|
||||
def to_wsgi_list(self) -> List[Tuple[str, str]]: ...
|
||||
def copy(self) -> Headers: ...
|
||||
def __copy__(self) -> Headers: ...
|
||||
|
||||
class ImmutableHeadersMixin(Headers):
|
||||
def __delitem__(self, key: Any, _index_operation: bool = True) -> NoReturn: ...
|
||||
def __setitem__(self, key: Any, value: Any) -> NoReturn: ...
|
||||
def set(self, _key: Any, _value: Any, **kw: Any) -> NoReturn: ...
|
||||
def setlist(self, key: Any, values: Any) -> NoReturn: ...
|
||||
def add(self, _key: Any, _value: Any, **kw: Any) -> NoReturn: ...
|
||||
def add_header(self, _key: Any, _value: Any, **_kw: Any) -> NoReturn: ...
|
||||
def remove(self, key: Any) -> NoReturn: ...
|
||||
def extend(self, *args: Any, **kwargs: Any) -> NoReturn: ...
|
||||
def update(self, *args: Any, **kwargs: Any) -> NoReturn: ...
|
||||
def insert(self, pos: Any, value: Any) -> NoReturn: ...
|
||||
def pop(self, key: Any = None, default: Any = ...) -> NoReturn: ...
|
||||
def popitem(self) -> NoReturn: ...
|
||||
def setdefault(self, key: Any, default: Any) -> NoReturn: ... # type: ignore
|
||||
def setlistdefault(self, key: Any, default: Any) -> NoReturn: ...
|
||||
|
||||
class EnvironHeaders(ImmutableHeadersMixin, Headers):
|
||||
environ: WSGIEnvironment
|
||||
def __init__(self, environ: WSGIEnvironment) -> None: ...
|
||||
def __eq__(self, other: object) -> bool: ...
|
||||
def __getitem__( # type: ignore
|
||||
self, key: str, _get_mode: Literal[False] = False
|
||||
) -> str: ...
|
||||
def __iter__(self) -> Iterator[Tuple[str, str]]: ... # type: ignore
|
||||
def copy(self) -> NoReturn: ...
|
||||
|
||||
class CombinedMultiDict(ImmutableMultiDictMixin[K, V], MultiDict[K, V]): # type: ignore
|
||||
dicts: List[MultiDict[K, V]]
|
||||
def __init__(self, dicts: Optional[Iterable[MultiDict[K, V]]]) -> None: ...
|
||||
@classmethod
|
||||
def fromkeys(cls, keys: Any, value: Any = None) -> NoReturn: ...
|
||||
def __getitem__(self, key: K) -> V: ...
|
||||
@overload # type: ignore
|
||||
def get(self, key: K) -> Optional[V]: ...
|
||||
@overload
|
||||
def get(self, key: K, default: Union[V, T] = ...) -> Union[V, T]: ...
|
||||
@overload
|
||||
def get(
|
||||
self, key: K, default: Optional[T] = None, type: Callable[[V], T] = ...
|
||||
) -> Optional[T]: ...
|
||||
@overload
|
||||
def getlist(self, key: K) -> List[V]: ...
|
||||
@overload
|
||||
def getlist(self, key: K, type: Callable[[V], T] = ...) -> List[T]: ...
|
||||
def _keys_impl(self) -> Set[K]: ...
|
||||
def keys(self) -> Set[K]: ... # type: ignore
|
||||
def __iter__(self) -> Set[K]: ... # type: ignore
|
||||
def items(self, multi: bool = False) -> Iterator[Tuple[K, V]]: ... # type: ignore
|
||||
def values(self) -> Iterator[V]: ... # type: ignore
|
||||
def lists(self) -> Iterator[Tuple[K, List[V]]]: ...
|
||||
def listvalues(self) -> Iterator[List[V]]: ...
|
||||
def copy(self) -> MultiDict[K, V]: ...
|
||||
@overload
|
||||
def to_dict(self) -> Dict[K, V]: ...
|
||||
@overload
|
||||
def to_dict(self, flat: Literal[False]) -> Dict[K, List[V]]: ...
|
||||
def __contains__(self, key: K) -> bool: ... # type: ignore
|
||||
def has_key(self, key: K) -> bool: ...
|
||||
|
||||
class FileMultiDict(MultiDict[str, "FileStorage"]):
|
||||
def add_file(
|
||||
self,
|
||||
name: str,
|
||||
file: Union[FileStorage, str, IO[bytes]],
|
||||
filename: Optional[str] = None,
|
||||
content_type: Optional[str] = None,
|
||||
) -> None: ...
|
||||
|
||||
class ImmutableDict(ImmutableDictMixin[K, V], Dict[K, V]):
|
||||
def copy(self) -> Dict[K, V]: ...
|
||||
def __copy__(self) -> ImmutableDict[K, V]: ...
|
||||
|
||||
class ImmutableMultiDict( # type: ignore
|
||||
ImmutableMultiDictMixin[K, V], MultiDict[K, V]
|
||||
):
|
||||
def copy(self) -> MultiDict[K, V]: ...
|
||||
def __copy__(self) -> ImmutableMultiDict[K, V]: ...
|
||||
|
||||
class ImmutableOrderedMultiDict( # type: ignore
|
||||
ImmutableMultiDictMixin[K, V], OrderedMultiDict[K, V]
|
||||
):
|
||||
def _iter_hashitems(self) -> Iterator[Tuple[int, Tuple[K, V]]]: ...
|
||||
def copy(self) -> OrderedMultiDict[K, V]: ...
|
||||
def __copy__(self) -> ImmutableOrderedMultiDict[K, V]: ...
|
||||
|
||||
class Accept(ImmutableList[Tuple[str, int]]):
|
||||
provided: bool
|
||||
def __init__(
|
||||
self, values: Optional[Union[Accept, Iterable[Tuple[str, float]]]] = None
|
||||
) -> None: ...
|
||||
def _specificity(self, value: str) -> Tuple[bool, ...]: ...
|
||||
def _value_matches(self, value: str, item: str) -> bool: ...
|
||||
@overload # type: ignore
|
||||
def __getitem__(self, key: str) -> int: ...
|
||||
@overload
|
||||
def __getitem__(self, key: int) -> Tuple[str, int]: ...
|
||||
@overload
|
||||
def __getitem__(self, key: slice) -> Iterable[Tuple[str, int]]: ...
|
||||
def quality(self, key: str) -> int: ...
|
||||
def __contains__(self, value: str) -> bool: ... # type: ignore
|
||||
def index(self, key: str) -> int: ... # type: ignore
|
||||
def find(self, key: str) -> int: ...
|
||||
def values(self) -> Iterator[str]: ...
|
||||
def to_header(self) -> str: ...
|
||||
def _best_single_match(self, match: str) -> Optional[Tuple[str, int]]: ...
|
||||
def best_match(
|
||||
self, matches: Iterable[str], default: Optional[str] = None
|
||||
) -> Optional[str]: ...
|
||||
@property
|
||||
def best(self) -> str: ...
|
||||
|
||||
def _normalize_mime(value: str) -> List[str]: ...
|
||||
|
||||
class MIMEAccept(Accept):
|
||||
def _specificity(self, value: str) -> Tuple[bool, ...]: ...
|
||||
def _value_matches(self, value: str, item: str) -> bool: ...
|
||||
@property
|
||||
def accept_html(self) -> bool: ...
|
||||
@property
|
||||
def accept_xhtml(self) -> bool: ...
|
||||
@property
|
||||
def accept_json(self) -> bool: ...
|
||||
|
||||
def _normalize_lang(value: str) -> List[str]: ...
|
||||
|
||||
class LanguageAccept(Accept):
|
||||
def _value_matches(self, value: str, item: str) -> bool: ...
|
||||
def best_match(
|
||||
self, matches: Iterable[str], default: Optional[str] = None
|
||||
) -> Optional[str]: ...
|
||||
|
||||
class CharsetAccept(Accept):
|
||||
def _value_matches(self, value: str, item: str) -> bool: ...
|
||||
|
||||
_CPT = TypeVar("_CPT", str, int, bool)
|
||||
_OptCPT = Optional[_CPT]
|
||||
|
||||
def cache_property(key: str, empty: _OptCPT, type: Type[_CPT]) -> property: ...
|
||||
|
||||
class _CacheControl(UpdateDictMixin[str, _OptCPT], Dict[str, _OptCPT]):
|
||||
provided: bool
|
||||
def __init__(
|
||||
self,
|
||||
values: Union[Mapping[str, _OptCPT], Iterable[Tuple[str, _OptCPT]]] = (),
|
||||
on_update: Optional[Callable[[_CacheControl], None]] = None,
|
||||
) -> None: ...
|
||||
@property
|
||||
def no_cache(self) -> Optional[bool]: ...
|
||||
@no_cache.setter
|
||||
def no_cache(self, value: Optional[bool]) -> None: ...
|
||||
@no_cache.deleter
|
||||
def no_cache(self) -> None: ...
|
||||
@property
|
||||
def no_store(self) -> Optional[bool]: ...
|
||||
@no_store.setter
|
||||
def no_store(self, value: Optional[bool]) -> None: ...
|
||||
@no_store.deleter
|
||||
def no_store(self) -> None: ...
|
||||
@property
|
||||
def max_age(self) -> Optional[int]: ...
|
||||
@max_age.setter
|
||||
def max_age(self, value: Optional[int]) -> None: ...
|
||||
@max_age.deleter
|
||||
def max_age(self) -> None: ...
|
||||
@property
|
||||
def no_transform(self) -> Optional[bool]: ...
|
||||
@no_transform.setter
|
||||
def no_transform(self, value: Optional[bool]) -> None: ...
|
||||
@no_transform.deleter
|
||||
def no_transform(self) -> None: ...
|
||||
def _get_cache_value(self, key: str, empty: Optional[T], type: Type[T]) -> T: ...
|
||||
def _set_cache_value(self, key: str, value: Optional[T], type: Type[T]) -> None: ...
|
||||
def _del_cache_value(self, key: str) -> None: ...
|
||||
def to_header(self) -> str: ...
|
||||
@staticmethod
|
||||
def cache_property(key: str, empty: _OptCPT, type: Type[_CPT]) -> property: ...
|
||||
|
||||
class RequestCacheControl(ImmutableDictMixin[str, _OptCPT], _CacheControl):
|
||||
@property
|
||||
def max_stale(self) -> Optional[int]: ...
|
||||
@max_stale.setter
|
||||
def max_stale(self, value: Optional[int]) -> None: ...
|
||||
@max_stale.deleter
|
||||
def max_stale(self) -> None: ...
|
||||
@property
|
||||
def min_fresh(self) -> Optional[int]: ...
|
||||
@min_fresh.setter
|
||||
def min_fresh(self, value: Optional[int]) -> None: ...
|
||||
@min_fresh.deleter
|
||||
def min_fresh(self) -> None: ...
|
||||
@property
|
||||
def only_if_cached(self) -> Optional[bool]: ...
|
||||
@only_if_cached.setter
|
||||
def only_if_cached(self, value: Optional[bool]) -> None: ...
|
||||
@only_if_cached.deleter
|
||||
def only_if_cached(self) -> None: ...
|
||||
|
||||
class ResponseCacheControl(_CacheControl):
|
||||
@property
|
||||
def public(self) -> Optional[bool]: ...
|
||||
@public.setter
|
||||
def public(self, value: Optional[bool]) -> None: ...
|
||||
@public.deleter
|
||||
def public(self) -> None: ...
|
||||
@property
|
||||
def private(self) -> Optional[bool]: ...
|
||||
@private.setter
|
||||
def private(self, value: Optional[bool]) -> None: ...
|
||||
@private.deleter
|
||||
def private(self) -> None: ...
|
||||
@property
|
||||
def must_revalidate(self) -> Optional[bool]: ...
|
||||
@must_revalidate.setter
|
||||
def must_revalidate(self, value: Optional[bool]) -> None: ...
|
||||
@must_revalidate.deleter
|
||||
def must_revalidate(self) -> None: ...
|
||||
@property
|
||||
def proxy_revalidate(self) -> Optional[bool]: ...
|
||||
@proxy_revalidate.setter
|
||||
def proxy_revalidate(self, value: Optional[bool]) -> None: ...
|
||||
@proxy_revalidate.deleter
|
||||
def proxy_revalidate(self) -> None: ...
|
||||
@property
|
||||
def s_maxage(self) -> Optional[int]: ...
|
||||
@s_maxage.setter
|
||||
def s_maxage(self, value: Optional[int]) -> None: ...
|
||||
@s_maxage.deleter
|
||||
def s_maxage(self) -> None: ...
|
||||
@property
|
||||
def immutable(self) -> Optional[bool]: ...
|
||||
@immutable.setter
|
||||
def immutable(self, value: Optional[bool]) -> None: ...
|
||||
@immutable.deleter
|
||||
def immutable(self) -> None: ...
|
||||
|
||||
def csp_property(key: str) -> property: ...
|
||||
|
||||
class ContentSecurityPolicy(UpdateDictMixin[str, str], Dict[str, str]):
|
||||
@property
|
||||
def base_uri(self) -> Optional[str]: ...
|
||||
@base_uri.setter
|
||||
def base_uri(self, value: Optional[str]) -> None: ...
|
||||
@base_uri.deleter
|
||||
def base_uri(self) -> None: ...
|
||||
@property
|
||||
def child_src(self) -> Optional[str]: ...
|
||||
@child_src.setter
|
||||
def child_src(self, value: Optional[str]) -> None: ...
|
||||
@child_src.deleter
|
||||
def child_src(self) -> None: ...
|
||||
@property
|
||||
def connect_src(self) -> Optional[str]: ...
|
||||
@connect_src.setter
|
||||
def connect_src(self, value: Optional[str]) -> None: ...
|
||||
@connect_src.deleter
|
||||
def connect_src(self) -> None: ...
|
||||
@property
|
||||
def default_src(self) -> Optional[str]: ...
|
||||
@default_src.setter
|
||||
def default_src(self, value: Optional[str]) -> None: ...
|
||||
@default_src.deleter
|
||||
def default_src(self) -> None: ...
|
||||
@property
|
||||
def font_src(self) -> Optional[str]: ...
|
||||
@font_src.setter
|
||||
def font_src(self, value: Optional[str]) -> None: ...
|
||||
@font_src.deleter
|
||||
def font_src(self) -> None: ...
|
||||
@property
|
||||
def form_action(self) -> Optional[str]: ...
|
||||
@form_action.setter
|
||||
def form_action(self, value: Optional[str]) -> None: ...
|
||||
@form_action.deleter
|
||||
def form_action(self) -> None: ...
|
||||
@property
|
||||
def frame_ancestors(self) -> Optional[str]: ...
|
||||
@frame_ancestors.setter
|
||||
def frame_ancestors(self, value: Optional[str]) -> None: ...
|
||||
@frame_ancestors.deleter
|
||||
def frame_ancestors(self) -> None: ...
|
||||
@property
|
||||
def frame_src(self) -> Optional[str]: ...
|
||||
@frame_src.setter
|
||||
def frame_src(self, value: Optional[str]) -> None: ...
|
||||
@frame_src.deleter
|
||||
def frame_src(self) -> None: ...
|
||||
@property
|
||||
def img_src(self) -> Optional[str]: ...
|
||||
@img_src.setter
|
||||
def img_src(self, value: Optional[str]) -> None: ...
|
||||
@img_src.deleter
|
||||
def img_src(self) -> None: ...
|
||||
@property
|
||||
def manifest_src(self) -> Optional[str]: ...
|
||||
@manifest_src.setter
|
||||
def manifest_src(self, value: Optional[str]) -> None: ...
|
||||
@manifest_src.deleter
|
||||
def manifest_src(self) -> None: ...
|
||||
@property
|
||||
def media_src(self) -> Optional[str]: ...
|
||||
@media_src.setter
|
||||
def media_src(self, value: Optional[str]) -> None: ...
|
||||
@media_src.deleter
|
||||
def media_src(self) -> None: ...
|
||||
@property
|
||||
def navigate_to(self) -> Optional[str]: ...
|
||||
@navigate_to.setter
|
||||
def navigate_to(self, value: Optional[str]) -> None: ...
|
||||
@navigate_to.deleter
|
||||
def navigate_to(self) -> None: ...
|
||||
@property
|
||||
def object_src(self) -> Optional[str]: ...
|
||||
@object_src.setter
|
||||
def object_src(self, value: Optional[str]) -> None: ...
|
||||
@object_src.deleter
|
||||
def object_src(self) -> None: ...
|
||||
@property
|
||||
def prefetch_src(self) -> Optional[str]: ...
|
||||
@prefetch_src.setter
|
||||
def prefetch_src(self, value: Optional[str]) -> None: ...
|
||||
@prefetch_src.deleter
|
||||
def prefetch_src(self) -> None: ...
|
||||
@property
|
||||
def plugin_types(self) -> Optional[str]: ...
|
||||
@plugin_types.setter
|
||||
def plugin_types(self, value: Optional[str]) -> None: ...
|
||||
@plugin_types.deleter
|
||||
def plugin_types(self) -> None: ...
|
||||
@property
|
||||
def report_to(self) -> Optional[str]: ...
|
||||
@report_to.setter
|
||||
def report_to(self, value: Optional[str]) -> None: ...
|
||||
@report_to.deleter
|
||||
def report_to(self) -> None: ...
|
||||
@property
|
||||
def report_uri(self) -> Optional[str]: ...
|
||||
@report_uri.setter
|
||||
def report_uri(self, value: Optional[str]) -> None: ...
|
||||
@report_uri.deleter
|
||||
def report_uri(self) -> None: ...
|
||||
@property
|
||||
def sandbox(self) -> Optional[str]: ...
|
||||
@sandbox.setter
|
||||
def sandbox(self, value: Optional[str]) -> None: ...
|
||||
@sandbox.deleter
|
||||
def sandbox(self) -> None: ...
|
||||
@property
|
||||
def script_src(self) -> Optional[str]: ...
|
||||
@script_src.setter
|
||||
def script_src(self, value: Optional[str]) -> None: ...
|
||||
@script_src.deleter
|
||||
def script_src(self) -> None: ...
|
||||
@property
|
||||
def script_src_attr(self) -> Optional[str]: ...
|
||||
@script_src_attr.setter
|
||||
def script_src_attr(self, value: Optional[str]) -> None: ...
|
||||
@script_src_attr.deleter
|
||||
def script_src_attr(self) -> None: ...
|
||||
@property
|
||||
def script_src_elem(self) -> Optional[str]: ...
|
||||
@script_src_elem.setter
|
||||
def script_src_elem(self, value: Optional[str]) -> None: ...
|
||||
@script_src_elem.deleter
|
||||
def script_src_elem(self) -> None: ...
|
||||
@property
|
||||
def style_src(self) -> Optional[str]: ...
|
||||
@style_src.setter
|
||||
def style_src(self, value: Optional[str]) -> None: ...
|
||||
@style_src.deleter
|
||||
def style_src(self) -> None: ...
|
||||
@property
|
||||
def style_src_attr(self) -> Optional[str]: ...
|
||||
@style_src_attr.setter
|
||||
def style_src_attr(self, value: Optional[str]) -> None: ...
|
||||
@style_src_attr.deleter
|
||||
def style_src_attr(self) -> None: ...
|
||||
@property
|
||||
def style_src_elem(self) -> Optional[str]: ...
|
||||
@style_src_elem.setter
|
||||
def style_src_elem(self, value: Optional[str]) -> None: ...
|
||||
@style_src_elem.deleter
|
||||
def style_src_elem(self) -> None: ...
|
||||
@property
|
||||
def worker_src(self) -> Optional[str]: ...
|
||||
@worker_src.setter
|
||||
def worker_src(self, value: Optional[str]) -> None: ...
|
||||
@worker_src.deleter
|
||||
def worker_src(self) -> None: ...
|
||||
provided: bool
|
||||
def __init__(
|
||||
self,
|
||||
values: Union[Mapping[str, str], Iterable[Tuple[str, str]]] = (),
|
||||
on_update: Optional[Callable[[ContentSecurityPolicy], None]] = None,
|
||||
) -> None: ...
|
||||
def _get_value(self, key: str) -> Optional[str]: ...
|
||||
def _set_value(self, key: str, value: str) -> None: ...
|
||||
def _del_value(self, key: str) -> None: ...
|
||||
def to_header(self) -> str: ...
|
||||
|
||||
class CallbackDict(UpdateDictMixin[K, V], Dict[K, V]):
|
||||
def __init__(
|
||||
self,
|
||||
initial: Optional[Union[Mapping[K, V], Iterable[Tuple[K, V]]]] = None,
|
||||
on_update: Optional[Callable[[_CD], None]] = None,
|
||||
) -> None: ...
|
||||
|
||||
class HeaderSet(Set[str]):
|
||||
_headers: List[str]
|
||||
_set: Set[str]
|
||||
on_update: Optional[Callable[[HeaderSet], None]]
|
||||
def __init__(
|
||||
self,
|
||||
headers: Optional[Iterable[str]] = None,
|
||||
on_update: Optional[Callable[[HeaderSet], None]] = None,
|
||||
) -> None: ...
|
||||
def add(self, header: str) -> None: ...
|
||||
def remove(self, header: str) -> None: ...
|
||||
def update(self, iterable: Iterable[str]) -> None: ... # type: ignore
|
||||
def discard(self, header: str) -> None: ...
|
||||
def find(self, header: str) -> int: ...
|
||||
def index(self, header: str) -> int: ...
|
||||
def clear(self) -> None: ...
|
||||
def as_set(self, preserve_casing: bool = False) -> Set[str]: ...
|
||||
def to_header(self) -> str: ...
|
||||
def __getitem__(self, idx: int) -> str: ...
|
||||
def __delitem__(self, idx: int) -> None: ...
|
||||
def __setitem__(self, idx: int, value: str) -> None: ...
|
||||
def __contains__(self, header: str) -> bool: ... # type: ignore
|
||||
def __len__(self) -> int: ...
|
||||
def __iter__(self) -> Iterator[str]: ...
|
||||
|
||||
class ETags(Collection[str]):
|
||||
_strong: FrozenSet[str]
|
||||
_weak: FrozenSet[str]
|
||||
star_tag: bool
|
||||
def __init__(
|
||||
self,
|
||||
strong_etags: Optional[Iterable[str]] = None,
|
||||
weak_etags: Optional[Iterable[str]] = None,
|
||||
star_tag: bool = False,
|
||||
) -> None: ...
|
||||
def as_set(self, include_weak: bool = False) -> Set[str]: ...
|
||||
def is_weak(self, etag: str) -> bool: ...
|
||||
def is_strong(self, etag: str) -> bool: ...
|
||||
def contains_weak(self, etag: str) -> bool: ...
|
||||
def contains(self, etag: str) -> bool: ...
|
||||
def contains_raw(self, etag: str) -> bool: ...
|
||||
def to_header(self) -> str: ...
|
||||
def __call__(
|
||||
self,
|
||||
etag: Optional[str] = None,
|
||||
data: Optional[bytes] = None,
|
||||
include_weak: bool = False,
|
||||
) -> bool: ...
|
||||
def __len__(self) -> int: ...
|
||||
def __iter__(self) -> Iterator[str]: ...
|
||||
def __contains__(self, item: str) -> bool: ... # type: ignore
|
||||
|
||||
class IfRange:
|
||||
etag: Optional[str]
|
||||
date: Optional[datetime]
|
||||
def __init__(
|
||||
self, etag: Optional[str] = None, date: Optional[datetime] = None
|
||||
) -> None: ...
|
||||
def to_header(self) -> str: ...
|
||||
|
||||
class Range:
|
||||
units: str
|
||||
ranges: List[Tuple[int, Optional[int]]]
|
||||
def __init__(self, units: str, ranges: List[Tuple[int, Optional[int]]]) -> None: ...
|
||||
def range_for_length(self, length: Optional[int]) -> Optional[Tuple[int, int]]: ...
|
||||
def make_content_range(self, length: Optional[int]) -> Optional[ContentRange]: ...
|
||||
def to_header(self) -> str: ...
|
||||
def to_content_range_header(self, length: Optional[int]) -> Optional[str]: ...
|
||||
|
||||
def _callback_property(name: str) -> property: ...
|
||||
|
||||
class ContentRange:
|
||||
on_update: Optional[Callable[[ContentRange], None]]
|
||||
def __init__(
|
||||
self,
|
||||
units: Optional[str],
|
||||
start: Optional[int],
|
||||
stop: Optional[int],
|
||||
length: Optional[int] = None,
|
||||
on_update: Optional[Callable[[ContentRange], None]] = None,
|
||||
) -> None: ...
|
||||
@property
|
||||
def units(self) -> Optional[str]: ...
|
||||
@units.setter
|
||||
def units(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def start(self) -> Optional[int]: ...
|
||||
@start.setter
|
||||
def start(self, value: Optional[int]) -> None: ...
|
||||
@property
|
||||
def stop(self) -> Optional[int]: ...
|
||||
@stop.setter
|
||||
def stop(self, value: Optional[int]) -> None: ...
|
||||
@property
|
||||
def length(self) -> Optional[int]: ...
|
||||
@length.setter
|
||||
def length(self, value: Optional[int]) -> None: ...
|
||||
def set(
|
||||
self,
|
||||
start: Optional[int],
|
||||
stop: Optional[int],
|
||||
length: Optional[int] = None,
|
||||
units: Optional[str] = "bytes",
|
||||
) -> None: ...
|
||||
def unset(self) -> None: ...
|
||||
def to_header(self) -> str: ...
|
||||
|
||||
class Authorization(ImmutableDictMixin[str, str], Dict[str, str]):
|
||||
type: str
|
||||
def __init__(
|
||||
self,
|
||||
auth_type: str,
|
||||
data: Optional[Union[Mapping[str, str], Iterable[Tuple[str, str]]]] = None,
|
||||
) -> None: ...
|
||||
@property
|
||||
def username(self) -> Optional[str]: ...
|
||||
@property
|
||||
def password(self) -> Optional[str]: ...
|
||||
@property
|
||||
def realm(self) -> Optional[str]: ...
|
||||
@property
|
||||
def nonce(self) -> Optional[str]: ...
|
||||
@property
|
||||
def uri(self) -> Optional[str]: ...
|
||||
@property
|
||||
def nc(self) -> Optional[str]: ...
|
||||
@property
|
||||
def cnonce(self) -> Optional[str]: ...
|
||||
@property
|
||||
def response(self) -> Optional[str]: ...
|
||||
@property
|
||||
def opaque(self) -> Optional[str]: ...
|
||||
@property
|
||||
def qop(self) -> Optional[str]: ...
|
||||
def to_header(self) -> str: ...
|
||||
|
||||
def auth_property(name: str, doc: Optional[str] = None) -> property: ...
|
||||
def _set_property(name: str, doc: Optional[str] = None) -> property: ...
|
||||
|
||||
class WWWAuthenticate(UpdateDictMixin[str, str], Dict[str, str]):
|
||||
_require_quoting: FrozenSet[str]
|
||||
def __init__(
|
||||
self,
|
||||
auth_type: Optional[str] = None,
|
||||
values: Optional[Union[Mapping[str, str], Iterable[Tuple[str, str]]]] = None,
|
||||
on_update: Optional[Callable[[WWWAuthenticate], None]] = None,
|
||||
) -> None: ...
|
||||
def set_basic(self, realm: str = ...) -> None: ...
|
||||
def set_digest(
|
||||
self,
|
||||
realm: str,
|
||||
nonce: str,
|
||||
qop: Iterable[str] = ("auth",),
|
||||
opaque: Optional[str] = None,
|
||||
algorithm: Optional[str] = None,
|
||||
stale: bool = False,
|
||||
) -> None: ...
|
||||
def to_header(self) -> str: ...
|
||||
@property
|
||||
def type(self) -> Optional[str]: ...
|
||||
@type.setter
|
||||
def type(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def realm(self) -> Optional[str]: ...
|
||||
@realm.setter
|
||||
def realm(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def domain(self) -> HeaderSet: ...
|
||||
@property
|
||||
def nonce(self) -> Optional[str]: ...
|
||||
@nonce.setter
|
||||
def nonce(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def opaque(self) -> Optional[str]: ...
|
||||
@opaque.setter
|
||||
def opaque(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def algorithm(self) -> Optional[str]: ...
|
||||
@algorithm.setter
|
||||
def algorithm(self, value: Optional[str]) -> None: ...
|
||||
@property
|
||||
def qop(self) -> HeaderSet: ...
|
||||
@property
|
||||
def stale(self) -> Optional[bool]: ...
|
||||
@stale.setter
|
||||
def stale(self, value: Optional[bool]) -> None: ...
|
||||
@staticmethod
|
||||
def auth_property(name: str, doc: Optional[str] = None) -> property: ...
|
||||
|
||||
class FileStorage:
|
||||
name: Optional[str]
|
||||
stream: IO[bytes]
|
||||
filename: Optional[str]
|
||||
headers: Headers
|
||||
_parsed_content_type: Tuple[str, Dict[str, str]]
|
||||
def __init__(
|
||||
self,
|
||||
stream: Optional[IO[bytes]] = None,
|
||||
filename: Optional[str] = None,
|
||||
name: Optional[str] = None,
|
||||
content_type: Optional[str] = None,
|
||||
content_length: Optional[int] = None,
|
||||
headers: Optional[Headers] = None,
|
||||
) -> None: ...
|
||||
def _parse_content_type(self) -> None: ...
|
||||
@property
|
||||
def content_type(self) -> str: ...
|
||||
@property
|
||||
def content_length(self) -> int: ...
|
||||
@property
|
||||
def mimetype(self) -> str: ...
|
||||
@property
|
||||
def mimetype_params(self) -> Dict[str, str]: ...
|
||||
def save(
|
||||
self, dst: Union[str, PathLike, IO[bytes]], buffer_size: int = ...
|
||||
) -> None: ...
|
||||
def close(self) -> None: ...
|
||||
def __bool__(self) -> bool: ...
|
||||
def __getattr__(self, name: str) -> Any: ...
|
||||
def __iter__(self) -> Iterator[bytes]: ...
|
||||
def __repr__(self) -> str: ...
|
||||
500
myvenv/lib/python3.10/site-packages/werkzeug/debug/__init__.py
Normal file
500
myvenv/lib/python3.10/site-packages/werkzeug/debug/__init__.py
Normal file
@ -0,0 +1,500 @@
|
||||
import getpass
|
||||
import hashlib
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import typing as t
|
||||
import uuid
|
||||
from itertools import chain
|
||||
from os.path import basename
|
||||
from os.path import join
|
||||
|
||||
from .._internal import _log
|
||||
from ..http import parse_cookie
|
||||
from ..security import gen_salt
|
||||
from ..wrappers.request import Request
|
||||
from ..wrappers.response import Response
|
||||
from .console import Console
|
||||
from .tbtools import Frame
|
||||
from .tbtools import get_current_traceback
|
||||
from .tbtools import render_console_html
|
||||
from .tbtools import Traceback
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
# A week
|
||||
PIN_TIME = 60 * 60 * 24 * 7
|
||||
|
||||
|
||||
def hash_pin(pin: str) -> str:
|
||||
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]
|
||||
|
||||
|
||||
_machine_id: t.Optional[t.Union[str, bytes]] = None
|
||||
|
||||
|
||||
def get_machine_id() -> t.Optional[t.Union[str, bytes]]:
|
||||
global _machine_id
|
||||
|
||||
if _machine_id is not None:
|
||||
return _machine_id
|
||||
|
||||
def _generate() -> t.Optional[t.Union[str, bytes]]:
|
||||
linux = b""
|
||||
|
||||
# machine-id is stable across boots, boot_id is not.
|
||||
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
|
||||
try:
|
||||
with open(filename, "rb") as f:
|
||||
value = f.readline().strip()
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
if value:
|
||||
linux += value
|
||||
break
|
||||
|
||||
# Containers share the same machine id, add some cgroup
|
||||
# information. This is used outside containers too but should be
|
||||
# relatively stable across boots.
|
||||
try:
|
||||
with open("/proc/self/cgroup", "rb") as f:
|
||||
linux += f.readline().strip().rpartition(b"/")[2]
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
if linux:
|
||||
return linux
|
||||
|
||||
# On OS X, use ioreg to get the computer's serial number.
|
||||
try:
|
||||
# subprocess may not be available, e.g. Google App Engine
|
||||
# https://github.com/pallets/werkzeug/issues/925
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
dump = Popen(
|
||||
["ioreg", "-c", "IOPlatformExpertDevice", "-d", "2"], stdout=PIPE
|
||||
).communicate()[0]
|
||||
match = re.search(b'"serial-number" = <([^>]+)', dump)
|
||||
|
||||
if match is not None:
|
||||
return match.group(1)
|
||||
except (OSError, ImportError):
|
||||
pass
|
||||
|
||||
# On Windows, use winreg to get the machine guid.
|
||||
if sys.platform == "win32":
|
||||
import winreg
|
||||
|
||||
try:
|
||||
with winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Cryptography",
|
||||
0,
|
||||
winreg.KEY_READ | winreg.KEY_WOW64_64KEY,
|
||||
) as rk:
|
||||
guid: t.Union[str, bytes]
|
||||
guid_type: int
|
||||
guid, guid_type = winreg.QueryValueEx(rk, "MachineGuid")
|
||||
|
||||
if guid_type == winreg.REG_SZ:
|
||||
return guid.encode("utf-8")
|
||||
|
||||
return guid
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
_machine_id = _generate()
|
||||
return _machine_id
|
||||
|
||||
|
||||
class _ConsoleFrame:
|
||||
"""Helper class so that we can reuse the frame console code for the
|
||||
standalone console.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace: t.Dict[str, t.Any]):
|
||||
self.console = Console(namespace)
|
||||
self.id = 0
|
||||
|
||||
|
||||
def get_pin_and_cookie_name(
|
||||
app: "WSGIApplication",
|
||||
) -> t.Union[t.Tuple[str, str], t.Tuple[None, None]]:
|
||||
"""Given an application object this returns a semi-stable 9 digit pin
|
||||
code and a random key. The hope is that this is stable between
|
||||
restarts to not make debugging particularly frustrating. If the pin
|
||||
was forcefully disabled this returns `None`.
|
||||
|
||||
Second item in the resulting tuple is the cookie name for remembering.
|
||||
"""
|
||||
pin = os.environ.get("WERKZEUG_DEBUG_PIN")
|
||||
rv = None
|
||||
num = None
|
||||
|
||||
# Pin was explicitly disabled
|
||||
if pin == "off":
|
||||
return None, None
|
||||
|
||||
# Pin was provided explicitly
|
||||
if pin is not None and pin.replace("-", "").isdigit():
|
||||
# If there are separators in the pin, return it directly
|
||||
if "-" in pin:
|
||||
rv = pin
|
||||
else:
|
||||
num = pin
|
||||
|
||||
modname = getattr(app, "__module__", t.cast(object, app).__class__.__module__)
|
||||
username: t.Optional[str]
|
||||
|
||||
try:
|
||||
# getuser imports the pwd module, which does not exist in Google
|
||||
# App Engine. It may also raise a KeyError if the UID does not
|
||||
# have a username, such as in Docker.
|
||||
username = getpass.getuser()
|
||||
except (ImportError, KeyError):
|
||||
username = None
|
||||
|
||||
mod = sys.modules.get(modname)
|
||||
|
||||
# This information only exists to make the cookie unique on the
|
||||
# computer, not as a security feature.
|
||||
probably_public_bits = [
|
||||
username,
|
||||
modname,
|
||||
getattr(app, "__name__", type(app).__name__),
|
||||
getattr(mod, "__file__", None),
|
||||
]
|
||||
|
||||
# This information is here to make it harder for an attacker to
|
||||
# guess the cookie name. They are unlikely to be contained anywhere
|
||||
# within the unauthenticated debug page.
|
||||
private_bits = [str(uuid.getnode()), get_machine_id()]
|
||||
|
||||
h = hashlib.sha1()
|
||||
for bit in chain(probably_public_bits, private_bits):
|
||||
if not bit:
|
||||
continue
|
||||
if isinstance(bit, str):
|
||||
bit = bit.encode("utf-8")
|
||||
h.update(bit)
|
||||
h.update(b"cookiesalt")
|
||||
|
||||
cookie_name = f"__wzd{h.hexdigest()[:20]}"
|
||||
|
||||
# If we need to generate a pin we salt it a bit more so that we don't
|
||||
# end up with the same value and generate out 9 digits
|
||||
if num is None:
|
||||
h.update(b"pinsalt")
|
||||
num = f"{int(h.hexdigest(), 16):09d}"[:9]
|
||||
|
||||
# Format the pincode in groups of digits for easier remembering if
|
||||
# we don't have a result yet.
|
||||
if rv is None:
|
||||
for group_size in 5, 4, 3:
|
||||
if len(num) % group_size == 0:
|
||||
rv = "-".join(
|
||||
num[x : x + group_size].rjust(group_size, "0")
|
||||
for x in range(0, len(num), group_size)
|
||||
)
|
||||
break
|
||||
else:
|
||||
rv = num
|
||||
|
||||
return rv, cookie_name
|
||||
|
||||
|
||||
class DebuggedApplication:
|
||||
"""Enables debugging support for a given application::
|
||||
|
||||
from werkzeug.debug import DebuggedApplication
|
||||
from myapp import app
|
||||
app = DebuggedApplication(app, evalex=True)
|
||||
|
||||
The `evalex` keyword argument allows evaluating expressions in a
|
||||
traceback's frame context.
|
||||
|
||||
:param app: the WSGI application to run debugged.
|
||||
:param evalex: enable exception evaluation feature (interactive
|
||||
debugging). This requires a non-forking server.
|
||||
:param request_key: The key that points to the request object in ths
|
||||
environment. This parameter is ignored in current
|
||||
versions.
|
||||
:param console_path: the URL for a general purpose console.
|
||||
:param console_init_func: the function that is executed before starting
|
||||
the general purpose console. The return value
|
||||
is used as initial namespace.
|
||||
:param show_hidden_frames: by default hidden traceback frames are skipped.
|
||||
You can show them by setting this parameter
|
||||
to `True`.
|
||||
:param pin_security: can be used to disable the pin based security system.
|
||||
:param pin_logging: enables the logging of the pin system.
|
||||
"""
|
||||
|
||||
_pin: str
|
||||
_pin_cookie: str
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
evalex: bool = False,
|
||||
request_key: str = "werkzeug.request",
|
||||
console_path: str = "/console",
|
||||
console_init_func: t.Optional[t.Callable[[], t.Dict[str, t.Any]]] = None,
|
||||
show_hidden_frames: bool = False,
|
||||
pin_security: bool = True,
|
||||
pin_logging: bool = True,
|
||||
) -> None:
|
||||
if not console_init_func:
|
||||
console_init_func = None
|
||||
self.app = app
|
||||
self.evalex = evalex
|
||||
self.frames: t.Dict[int, t.Union[Frame, _ConsoleFrame]] = {}
|
||||
self.tracebacks: t.Dict[int, Traceback] = {}
|
||||
self.request_key = request_key
|
||||
self.console_path = console_path
|
||||
self.console_init_func = console_init_func
|
||||
self.show_hidden_frames = show_hidden_frames
|
||||
self.secret = gen_salt(20)
|
||||
self._failed_pin_auth = 0
|
||||
|
||||
self.pin_logging = pin_logging
|
||||
if pin_security:
|
||||
# Print out the pin for the debugger on standard out.
|
||||
if os.environ.get("WERKZEUG_RUN_MAIN") == "true" and pin_logging:
|
||||
_log("warning", " * Debugger is active!")
|
||||
if self.pin is None:
|
||||
_log("warning", " * Debugger PIN disabled. DEBUGGER UNSECURED!")
|
||||
else:
|
||||
_log("info", " * Debugger PIN: %s", self.pin)
|
||||
else:
|
||||
self.pin = None
|
||||
|
||||
@property
|
||||
def pin(self) -> t.Optional[str]:
|
||||
if not hasattr(self, "_pin"):
|
||||
pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
self._pin, self._pin_cookie = pin_cookie # type: ignore
|
||||
return self._pin
|
||||
|
||||
@pin.setter
|
||||
def pin(self, value: str) -> None:
|
||||
self._pin = value
|
||||
|
||||
@property
|
||||
def pin_cookie_name(self) -> str:
|
||||
"""The name of the pin cookie."""
|
||||
if not hasattr(self, "_pin_cookie"):
|
||||
pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
self._pin, self._pin_cookie = pin_cookie # type: ignore
|
||||
return self._pin_cookie
|
||||
|
||||
def debug_application(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterator[bytes]:
|
||||
"""Run the application and conserve the traceback frames."""
|
||||
app_iter = None
|
||||
try:
|
||||
app_iter = self.app(environ, start_response)
|
||||
yield from app_iter
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close() # type: ignore
|
||||
except Exception:
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close() # type: ignore
|
||||
traceback = get_current_traceback(
|
||||
skip=1,
|
||||
show_hidden_frames=self.show_hidden_frames,
|
||||
ignore_system_exceptions=True,
|
||||
)
|
||||
for frame in traceback.frames:
|
||||
self.frames[frame.id] = frame
|
||||
self.tracebacks[traceback.id] = traceback
|
||||
|
||||
try:
|
||||
start_response(
|
||||
"500 INTERNAL SERVER ERROR",
|
||||
[
|
||||
("Content-Type", "text/html; charset=utf-8"),
|
||||
# Disable Chrome's XSS protection, the debug
|
||||
# output can cause false-positives.
|
||||
("X-XSS-Protection", "0"),
|
||||
],
|
||||
)
|
||||
except Exception:
|
||||
# if we end up here there has been output but an error
|
||||
# occurred. in that situation we can do nothing fancy any
|
||||
# more, better log something into the error log and fall
|
||||
# back gracefully.
|
||||
environ["wsgi.errors"].write(
|
||||
"Debugging middleware caught exception in streamed "
|
||||
"response at a point where response headers were already "
|
||||
"sent.\n"
|
||||
)
|
||||
else:
|
||||
is_trusted = bool(self.check_pin_trust(environ))
|
||||
yield traceback.render_full(
|
||||
evalex=self.evalex, evalex_trusted=is_trusted, secret=self.secret
|
||||
).encode("utf-8", "replace")
|
||||
|
||||
traceback.log(environ["wsgi.errors"])
|
||||
|
||||
def execute_command(
|
||||
self, request: Request, command: str, frame: t.Union[Frame, _ConsoleFrame]
|
||||
) -> Response:
|
||||
"""Execute a command in a console."""
|
||||
return Response(frame.console.eval(command), mimetype="text/html")
|
||||
|
||||
def display_console(self, request: Request) -> Response:
|
||||
"""Display a standalone shell."""
|
||||
if 0 not in self.frames:
|
||||
if self.console_init_func is None:
|
||||
ns = {}
|
||||
else:
|
||||
ns = dict(self.console_init_func())
|
||||
ns.setdefault("app", self.app)
|
||||
self.frames[0] = _ConsoleFrame(ns)
|
||||
is_trusted = bool(self.check_pin_trust(request.environ))
|
||||
return Response(
|
||||
render_console_html(secret=self.secret, evalex_trusted=is_trusted),
|
||||
mimetype="text/html",
|
||||
)
|
||||
|
||||
def get_resource(self, request: Request, filename: str) -> Response:
|
||||
"""Return a static resource from the shared folder."""
|
||||
filename = join("shared", basename(filename))
|
||||
try:
|
||||
data = pkgutil.get_data(__package__, filename)
|
||||
except OSError:
|
||||
data = None
|
||||
if data is not None:
|
||||
mimetype = mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
||||
return Response(data, mimetype=mimetype)
|
||||
return Response("Not Found", status=404)
|
||||
|
||||
def check_pin_trust(self, environ: "WSGIEnvironment") -> t.Optional[bool]:
|
||||
"""Checks if the request passed the pin test. This returns `True` if the
|
||||
request is trusted on a pin/cookie basis and returns `False` if not.
|
||||
Additionally if the cookie's stored pin hash is wrong it will return
|
||||
`None` so that appropriate action can be taken.
|
||||
"""
|
||||
if self.pin is None:
|
||||
return True
|
||||
val = parse_cookie(environ).get(self.pin_cookie_name)
|
||||
if not val or "|" not in val:
|
||||
return False
|
||||
ts, pin_hash = val.split("|", 1)
|
||||
if not ts.isdigit():
|
||||
return False
|
||||
if pin_hash != hash_pin(self.pin):
|
||||
return None
|
||||
return (time.time() - PIN_TIME) < int(ts)
|
||||
|
||||
def _fail_pin_auth(self) -> None:
|
||||
time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5)
|
||||
self._failed_pin_auth += 1
|
||||
|
||||
def pin_auth(self, request: Request) -> Response:
|
||||
"""Authenticates with the pin."""
|
||||
exhausted = False
|
||||
auth = False
|
||||
trust = self.check_pin_trust(request.environ)
|
||||
pin = t.cast(str, self.pin)
|
||||
|
||||
# If the trust return value is `None` it means that the cookie is
|
||||
# set but the stored pin hash value is bad. This means that the
|
||||
# pin was changed. In this case we count a bad auth and unset the
|
||||
# cookie. This way it becomes harder to guess the cookie name
|
||||
# instead of the pin as we still count up failures.
|
||||
bad_cookie = False
|
||||
if trust is None:
|
||||
self._fail_pin_auth()
|
||||
bad_cookie = True
|
||||
|
||||
# If we're trusted, we're authenticated.
|
||||
elif trust:
|
||||
auth = True
|
||||
|
||||
# If we failed too many times, then we're locked out.
|
||||
elif self._failed_pin_auth > 10:
|
||||
exhausted = True
|
||||
|
||||
# Otherwise go through pin based authentication
|
||||
else:
|
||||
entered_pin = request.args["pin"]
|
||||
|
||||
if entered_pin.strip().replace("-", "") == pin.replace("-", ""):
|
||||
self._failed_pin_auth = 0
|
||||
auth = True
|
||||
else:
|
||||
self._fail_pin_auth()
|
||||
|
||||
rv = Response(
|
||||
json.dumps({"auth": auth, "exhausted": exhausted}),
|
||||
mimetype="application/json",
|
||||
)
|
||||
if auth:
|
||||
rv.set_cookie(
|
||||
self.pin_cookie_name,
|
||||
f"{int(time.time())}|{hash_pin(pin)}",
|
||||
httponly=True,
|
||||
samesite="Strict",
|
||||
secure=request.is_secure,
|
||||
)
|
||||
elif bad_cookie:
|
||||
rv.delete_cookie(self.pin_cookie_name)
|
||||
return rv
|
||||
|
||||
def log_pin_request(self) -> Response:
|
||||
"""Log the pin if needed."""
|
||||
if self.pin_logging and self.pin is not None:
|
||||
_log(
|
||||
"info", " * To enable the debugger you need to enter the security pin:"
|
||||
)
|
||||
_log("info", " * Debugger pin code: %s", self.pin)
|
||||
return Response("")
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
"""Dispatch the requests."""
|
||||
# important: don't ever access a function here that reads the incoming
|
||||
# form data! Otherwise the application won't have access to that data
|
||||
# any more!
|
||||
request = Request(environ)
|
||||
response = self.debug_application
|
||||
if request.args.get("__debugger__") == "yes":
|
||||
cmd = request.args.get("cmd")
|
||||
arg = request.args.get("f")
|
||||
secret = request.args.get("s")
|
||||
frame = self.frames.get(request.args.get("frm", type=int)) # type: ignore
|
||||
if cmd == "resource" and arg:
|
||||
response = self.get_resource(request, arg) # type: ignore
|
||||
elif cmd == "pinauth" and secret == self.secret:
|
||||
response = self.pin_auth(request) # type: ignore
|
||||
elif cmd == "printpin" and secret == self.secret:
|
||||
response = self.log_pin_request() # type: ignore
|
||||
elif (
|
||||
self.evalex
|
||||
and cmd is not None
|
||||
and frame is not None
|
||||
and self.secret == secret
|
||||
and self.check_pin_trust(environ)
|
||||
):
|
||||
response = self.execute_command(request, cmd, frame) # type: ignore
|
||||
elif (
|
||||
self.evalex
|
||||
and self.console_path is not None
|
||||
and request.path == self.console_path
|
||||
):
|
||||
response = self.display_console(request) # type: ignore
|
||||
return response(environ, start_response)
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
214
myvenv/lib/python3.10/site-packages/werkzeug/debug/console.py
Normal file
214
myvenv/lib/python3.10/site-packages/werkzeug/debug/console.py
Normal file
@ -0,0 +1,214 @@
|
||||
import code
|
||||
import sys
|
||||
import typing as t
|
||||
from html import escape
|
||||
from types import CodeType
|
||||
|
||||
from ..local import Local
|
||||
from .repr import debug_repr
|
||||
from .repr import dump
|
||||
from .repr import helper
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import codeop # noqa: F401
|
||||
|
||||
_local = Local()
|
||||
|
||||
|
||||
class HTMLStringO:
|
||||
"""A StringO version that HTML escapes on write."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._buffer: t.List[str] = []
|
||||
|
||||
def isatty(self) -> bool:
|
||||
return False
|
||||
|
||||
def close(self) -> None:
|
||||
pass
|
||||
|
||||
def flush(self) -> None:
|
||||
pass
|
||||
|
||||
def seek(self, n: int, mode: int = 0) -> None:
|
||||
pass
|
||||
|
||||
def readline(self) -> str:
|
||||
if len(self._buffer) == 0:
|
||||
return ""
|
||||
ret = self._buffer[0]
|
||||
del self._buffer[0]
|
||||
return ret
|
||||
|
||||
def reset(self) -> str:
|
||||
val = "".join(self._buffer)
|
||||
del self._buffer[:]
|
||||
return val
|
||||
|
||||
def _write(self, x: str) -> None:
|
||||
if isinstance(x, bytes):
|
||||
x = x.decode("utf-8", "replace")
|
||||
self._buffer.append(x)
|
||||
|
||||
def write(self, x: str) -> None:
|
||||
self._write(escape(x))
|
||||
|
||||
def writelines(self, x: t.Iterable[str]) -> None:
|
||||
self._write(escape("".join(x)))
|
||||
|
||||
|
||||
class ThreadedStream:
|
||||
"""Thread-local wrapper for sys.stdout for the interactive console."""
|
||||
|
||||
@staticmethod
|
||||
def push() -> None:
|
||||
if not isinstance(sys.stdout, ThreadedStream):
|
||||
sys.stdout = t.cast(t.TextIO, ThreadedStream())
|
||||
_local.stream = HTMLStringO()
|
||||
|
||||
@staticmethod
|
||||
def fetch() -> str:
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return ""
|
||||
return stream.reset() # type: ignore
|
||||
|
||||
@staticmethod
|
||||
def displayhook(obj: object) -> None:
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return _displayhook(obj) # type: ignore
|
||||
# stream._write bypasses escaping as debug_repr is
|
||||
# already generating HTML for us.
|
||||
if obj is not None:
|
||||
_local._current_ipy.locals["_"] = obj
|
||||
stream._write(debug_repr(obj))
|
||||
|
||||
def __setattr__(self, name: str, value: t.Any) -> None:
|
||||
raise AttributeError(f"read only attribute {name}")
|
||||
|
||||
def __dir__(self) -> t.List[str]:
|
||||
return dir(sys.__stdout__)
|
||||
|
||||
def __getattribute__(self, name: str) -> t.Any:
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
stream = sys.__stdout__
|
||||
return getattr(stream, name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return repr(sys.__stdout__)
|
||||
|
||||
|
||||
# add the threaded stream as display hook
|
||||
_displayhook = sys.displayhook
|
||||
sys.displayhook = ThreadedStream.displayhook
|
||||
|
||||
|
||||
class _ConsoleLoader:
|
||||
def __init__(self) -> None:
|
||||
self._storage: t.Dict[int, str] = {}
|
||||
|
||||
def register(self, code: CodeType, source: str) -> None:
|
||||
self._storage[id(code)] = source
|
||||
# register code objects of wrapped functions too.
|
||||
for var in code.co_consts:
|
||||
if isinstance(var, CodeType):
|
||||
self._storage[id(var)] = source
|
||||
|
||||
def get_source_by_code(self, code: CodeType) -> t.Optional[str]:
|
||||
try:
|
||||
return self._storage[id(code)]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
class _InteractiveConsole(code.InteractiveInterpreter):
|
||||
locals: t.Dict[str, t.Any]
|
||||
|
||||
def __init__(self, globals: t.Dict[str, t.Any], locals: t.Dict[str, t.Any]) -> None:
|
||||
self.loader = _ConsoleLoader()
|
||||
locals = {
|
||||
**globals,
|
||||
**locals,
|
||||
"dump": dump,
|
||||
"help": helper,
|
||||
"__loader__": self.loader,
|
||||
}
|
||||
super().__init__(locals)
|
||||
original_compile = self.compile
|
||||
|
||||
def compile(source: str, filename: str, symbol: str) -> t.Optional[CodeType]:
|
||||
code = original_compile(source, filename, symbol)
|
||||
|
||||
if code is not None:
|
||||
self.loader.register(code, source)
|
||||
|
||||
return code
|
||||
|
||||
self.compile = compile # type: ignore[assignment]
|
||||
self.more = False
|
||||
self.buffer: t.List[str] = []
|
||||
|
||||
def runsource(self, source: str, **kwargs: t.Any) -> str: # type: ignore
|
||||
source = f"{source.rstrip()}\n"
|
||||
ThreadedStream.push()
|
||||
prompt = "... " if self.more else ">>> "
|
||||
try:
|
||||
source_to_eval = "".join(self.buffer + [source])
|
||||
if super().runsource(source_to_eval, "<debugger>", "single"):
|
||||
self.more = True
|
||||
self.buffer.append(source)
|
||||
else:
|
||||
self.more = False
|
||||
del self.buffer[:]
|
||||
finally:
|
||||
output = ThreadedStream.fetch()
|
||||
return prompt + escape(source) + output
|
||||
|
||||
def runcode(self, code: CodeType) -> None:
|
||||
try:
|
||||
exec(code, self.locals)
|
||||
except Exception:
|
||||
self.showtraceback()
|
||||
|
||||
def showtraceback(self) -> None:
|
||||
from .tbtools import get_current_traceback
|
||||
|
||||
tb = get_current_traceback(skip=1)
|
||||
sys.stdout._write(tb.render_summary()) # type: ignore
|
||||
|
||||
def showsyntaxerror(self, filename: t.Optional[str] = None) -> None:
|
||||
from .tbtools import get_current_traceback
|
||||
|
||||
tb = get_current_traceback(skip=4)
|
||||
sys.stdout._write(tb.render_summary()) # type: ignore
|
||||
|
||||
def write(self, data: str) -> None:
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
class Console:
|
||||
"""An interactive console."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
globals: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
locals: t.Optional[t.Dict[str, t.Any]] = None,
|
||||
) -> None:
|
||||
if locals is None:
|
||||
locals = {}
|
||||
if globals is None:
|
||||
globals = {}
|
||||
self._ipy = _InteractiveConsole(globals, locals)
|
||||
|
||||
def eval(self, code: str) -> str:
|
||||
_local._current_ipy = self._ipy
|
||||
old_sys_stdout = sys.stdout
|
||||
try:
|
||||
return self._ipy.runsource(code)
|
||||
finally:
|
||||
sys.stdout = old_sys_stdout
|
||||
284
myvenv/lib/python3.10/site-packages/werkzeug/debug/repr.py
Normal file
284
myvenv/lib/python3.10/site-packages/werkzeug/debug/repr.py
Normal file
@ -0,0 +1,284 @@
|
||||
"""Object representations for debugging purposes. Unlike the default
|
||||
repr, these expose more information and produce HTML instead of ASCII.
|
||||
|
||||
Together with the CSS and JavaScript of the debugger this gives a
|
||||
colorful and more compact output.
|
||||
"""
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
from collections import deque
|
||||
from html import escape
|
||||
from traceback import format_exception_only
|
||||
|
||||
missing = object()
|
||||
_paragraph_re = re.compile(r"(?:\r\n|\r|\n){2,}")
|
||||
RegexType = type(_paragraph_re)
|
||||
|
||||
HELP_HTML = """\
|
||||
<div class=box>
|
||||
<h3>%(title)s</h3>
|
||||
<pre class=help>%(text)s</pre>
|
||||
</div>\
|
||||
"""
|
||||
OBJECT_DUMP_HTML = """\
|
||||
<div class=box>
|
||||
<h3>%(title)s</h3>
|
||||
%(repr)s
|
||||
<table>%(items)s</table>
|
||||
</div>\
|
||||
"""
|
||||
|
||||
|
||||
def debug_repr(obj: object) -> str:
|
||||
"""Creates a debug repr of an object as HTML string."""
|
||||
return DebugReprGenerator().repr(obj)
|
||||
|
||||
|
||||
def dump(obj: object = missing) -> None:
|
||||
"""Print the object details to stdout._write (for the interactive
|
||||
console of the web debugger.
|
||||
"""
|
||||
gen = DebugReprGenerator()
|
||||
if obj is missing:
|
||||
rv = gen.dump_locals(sys._getframe(1).f_locals)
|
||||
else:
|
||||
rv = gen.dump_object(obj)
|
||||
sys.stdout._write(rv) # type: ignore
|
||||
|
||||
|
||||
class _Helper:
|
||||
"""Displays an HTML version of the normal help, for the interactive
|
||||
debugger only because it requires a patched sys.stdout.
|
||||
"""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return "Type help(object) for help about object."
|
||||
|
||||
def __call__(self, topic: t.Optional[t.Any] = None) -> None:
|
||||
if topic is None:
|
||||
sys.stdout._write(f"<span class=help>{self!r}</span>") # type: ignore
|
||||
return
|
||||
import pydoc
|
||||
|
||||
pydoc.help(topic)
|
||||
rv = sys.stdout.reset() # type: ignore
|
||||
if isinstance(rv, bytes):
|
||||
rv = rv.decode("utf-8", "ignore")
|
||||
paragraphs = _paragraph_re.split(rv)
|
||||
if len(paragraphs) > 1:
|
||||
title = paragraphs[0]
|
||||
text = "\n\n".join(paragraphs[1:])
|
||||
else:
|
||||
title = "Help"
|
||||
text = paragraphs[0]
|
||||
sys.stdout._write(HELP_HTML % {"title": title, "text": text}) # type: ignore
|
||||
|
||||
|
||||
helper = _Helper()
|
||||
|
||||
|
||||
def _add_subclass_info(
|
||||
inner: str, obj: object, base: t.Union[t.Type, t.Tuple[t.Type, ...]]
|
||||
) -> str:
|
||||
if isinstance(base, tuple):
|
||||
for base in base:
|
||||
if type(obj) is base:
|
||||
return inner
|
||||
elif type(obj) is base:
|
||||
return inner
|
||||
module = ""
|
||||
if obj.__class__.__module__ not in ("__builtin__", "exceptions"):
|
||||
module = f'<span class="module">{obj.__class__.__module__}.</span>'
|
||||
return f"{module}{type(obj).__name__}({inner})"
|
||||
|
||||
|
||||
def _sequence_repr_maker(
|
||||
left: str, right: str, base: t.Type, limit: int = 8
|
||||
) -> t.Callable[["DebugReprGenerator", t.Iterable, bool], str]:
|
||||
def proxy(self: "DebugReprGenerator", obj: t.Iterable, recursive: bool) -> str:
|
||||
if recursive:
|
||||
return _add_subclass_info(f"{left}...{right}", obj, base)
|
||||
buf = [left]
|
||||
have_extended_section = False
|
||||
for idx, item in enumerate(obj):
|
||||
if idx:
|
||||
buf.append(", ")
|
||||
if idx == limit:
|
||||
buf.append('<span class="extended">')
|
||||
have_extended_section = True
|
||||
buf.append(self.repr(item))
|
||||
if have_extended_section:
|
||||
buf.append("</span>")
|
||||
buf.append(right)
|
||||
return _add_subclass_info("".join(buf), obj, base)
|
||||
|
||||
return proxy
|
||||
|
||||
|
||||
class DebugReprGenerator:
|
||||
def __init__(self) -> None:
|
||||
self._stack: t.List[t.Any] = []
|
||||
|
||||
list_repr = _sequence_repr_maker("[", "]", list)
|
||||
tuple_repr = _sequence_repr_maker("(", ")", tuple)
|
||||
set_repr = _sequence_repr_maker("set([", "])", set)
|
||||
frozenset_repr = _sequence_repr_maker("frozenset([", "])", frozenset)
|
||||
deque_repr = _sequence_repr_maker(
|
||||
'<span class="module">collections.</span>deque([', "])", deque
|
||||
)
|
||||
|
||||
def regex_repr(self, obj: t.Pattern) -> str:
|
||||
pattern = repr(obj.pattern)
|
||||
pattern = codecs.decode(pattern, "unicode-escape", "ignore") # type: ignore
|
||||
pattern = f"r{pattern}"
|
||||
return f're.compile(<span class="string regex">{pattern}</span>)'
|
||||
|
||||
def string_repr(self, obj: t.Union[str, bytes], limit: int = 70) -> str:
|
||||
buf = ['<span class="string">']
|
||||
r = repr(obj)
|
||||
|
||||
# shorten the repr when the hidden part would be at least 3 chars
|
||||
if len(r) - limit > 2:
|
||||
buf.extend(
|
||||
(
|
||||
escape(r[:limit]),
|
||||
'<span class="extended">',
|
||||
escape(r[limit:]),
|
||||
"</span>",
|
||||
)
|
||||
)
|
||||
else:
|
||||
buf.append(escape(r))
|
||||
|
||||
buf.append("</span>")
|
||||
out = "".join(buf)
|
||||
|
||||
# if the repr looks like a standard string, add subclass info if needed
|
||||
if r[0] in "'\"" or (r[0] == "b" and r[1] in "'\""):
|
||||
return _add_subclass_info(out, obj, (bytes, str))
|
||||
|
||||
# otherwise, assume the repr distinguishes the subclass already
|
||||
return out
|
||||
|
||||
def dict_repr(
|
||||
self,
|
||||
d: t.Union[t.Dict[int, None], t.Dict[str, int], t.Dict[t.Union[str, int], int]],
|
||||
recursive: bool,
|
||||
limit: int = 5,
|
||||
) -> str:
|
||||
if recursive:
|
||||
return _add_subclass_info("{...}", d, dict)
|
||||
buf = ["{"]
|
||||
have_extended_section = False
|
||||
for idx, (key, value) in enumerate(d.items()):
|
||||
if idx:
|
||||
buf.append(", ")
|
||||
if idx == limit - 1:
|
||||
buf.append('<span class="extended">')
|
||||
have_extended_section = True
|
||||
buf.append(
|
||||
f'<span class="pair"><span class="key">{self.repr(key)}</span>:'
|
||||
f' <span class="value">{self.repr(value)}</span></span>'
|
||||
)
|
||||
if have_extended_section:
|
||||
buf.append("</span>")
|
||||
buf.append("}")
|
||||
return _add_subclass_info("".join(buf), d, dict)
|
||||
|
||||
def object_repr(
|
||||
self, obj: t.Optional[t.Union[t.Type[dict], t.Callable, t.Type[list]]]
|
||||
) -> str:
|
||||
r = repr(obj)
|
||||
return f'<span class="object">{escape(r)}</span>'
|
||||
|
||||
def dispatch_repr(self, obj: t.Any, recursive: bool) -> str:
|
||||
if obj is helper:
|
||||
return f'<span class="help">{helper!r}</span>'
|
||||
if isinstance(obj, (int, float, complex)):
|
||||
return f'<span class="number">{obj!r}</span>'
|
||||
if isinstance(obj, str) or isinstance(obj, bytes):
|
||||
return self.string_repr(obj)
|
||||
if isinstance(obj, RegexType):
|
||||
return self.regex_repr(obj)
|
||||
if isinstance(obj, list):
|
||||
return self.list_repr(obj, recursive)
|
||||
if isinstance(obj, tuple):
|
||||
return self.tuple_repr(obj, recursive)
|
||||
if isinstance(obj, set):
|
||||
return self.set_repr(obj, recursive)
|
||||
if isinstance(obj, frozenset):
|
||||
return self.frozenset_repr(obj, recursive)
|
||||
if isinstance(obj, dict):
|
||||
return self.dict_repr(obj, recursive)
|
||||
if isinstance(obj, deque):
|
||||
return self.deque_repr(obj, recursive)
|
||||
return self.object_repr(obj)
|
||||
|
||||
def fallback_repr(self) -> str:
|
||||
try:
|
||||
info = "".join(format_exception_only(*sys.exc_info()[:2]))
|
||||
except Exception:
|
||||
info = "?"
|
||||
return (
|
||||
'<span class="brokenrepr">'
|
||||
f"<broken repr ({escape(info.strip())})></span>"
|
||||
)
|
||||
|
||||
def repr(self, obj: object) -> str:
|
||||
recursive = False
|
||||
for item in self._stack:
|
||||
if item is obj:
|
||||
recursive = True
|
||||
break
|
||||
self._stack.append(obj)
|
||||
try:
|
||||
try:
|
||||
return self.dispatch_repr(obj, recursive)
|
||||
except Exception:
|
||||
return self.fallback_repr()
|
||||
finally:
|
||||
self._stack.pop()
|
||||
|
||||
def dump_object(self, obj: object) -> str:
|
||||
repr = None
|
||||
items: t.Optional[t.List[t.Tuple[str, str]]] = None
|
||||
|
||||
if isinstance(obj, dict):
|
||||
title = "Contents of"
|
||||
items = []
|
||||
for key, value in obj.items():
|
||||
if not isinstance(key, str):
|
||||
items = None
|
||||
break
|
||||
items.append((key, self.repr(value)))
|
||||
if items is None:
|
||||
items = []
|
||||
repr = self.repr(obj)
|
||||
for key in dir(obj):
|
||||
try:
|
||||
items.append((key, self.repr(getattr(obj, key))))
|
||||
except Exception:
|
||||
pass
|
||||
title = "Details for"
|
||||
title += f" {object.__repr__(obj)[1:-1]}"
|
||||
return self.render_object_dump(items, title, repr)
|
||||
|
||||
def dump_locals(self, d: t.Dict[str, t.Any]) -> str:
|
||||
items = [(key, self.repr(value)) for key, value in d.items()]
|
||||
return self.render_object_dump(items, "Local variables in frame")
|
||||
|
||||
def render_object_dump(
|
||||
self, items: t.List[t.Tuple[str, str]], title: str, repr: t.Optional[str] = None
|
||||
) -> str:
|
||||
html_items = []
|
||||
for key, value in items:
|
||||
html_items.append(f"<tr><th>{escape(key)}<td><pre class=repr>{value}</pre>")
|
||||
if not html_items:
|
||||
html_items.append("<tr><td><em>Nothing</em>")
|
||||
return OBJECT_DUMP_HTML % {
|
||||
"title": escape(title),
|
||||
"repr": f"<pre class=repr>{repr if repr else ''}</pre>",
|
||||
"items": "\n".join(html_items),
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
-------------------------------
|
||||
UBUNTU FONT LICENCE Version 1.0
|
||||
-------------------------------
|
||||
|
||||
PREAMBLE
|
||||
This licence allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely. The fonts, including any derivative works, can be
|
||||
bundled, embedded, and redistributed provided the terms of this licence
|
||||
are met. The fonts and derivatives, however, cannot be released under
|
||||
any other licence. The requirement for fonts to remain under this
|
||||
licence does not require any document created using the fonts or their
|
||||
derivatives to be published under this licence, as long as the primary
|
||||
purpose of the document is not to be a vehicle for the distribution of
|
||||
the fonts.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this licence and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Original Version" refers to the collection of Font Software components
|
||||
as received under this licence.
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to
|
||||
a new environment.
|
||||
|
||||
"Copyright Holder(s)" refers to all individuals and companies who have a
|
||||
copyright ownership of the Font Software.
|
||||
|
||||
"Substantially Changed" refers to Modified Versions which can be easily
|
||||
identified as dissimilar to the Font Software by users of the Font
|
||||
Software comparing the Original Version with the Modified Version.
|
||||
|
||||
To "Propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification and with or without charging
|
||||
a redistribution fee), making available to the public, and in some
|
||||
countries other activities as well.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
This licence does not grant any rights under trademark law and all such
|
||||
rights are reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of the Font Software, to propagate the Font Software, subject to
|
||||
the below conditions:
|
||||
|
||||
1) Each copy of the Font Software must contain the above copyright
|
||||
notice and this licence. These can be included either as stand-alone
|
||||
text files, human-readable headers or in the appropriate machine-
|
||||
readable metadata fields within text or binary files as long as those
|
||||
fields can be easily viewed by the user.
|
||||
|
||||
2) The font name complies with the following:
|
||||
(a) The Original Version must retain its name, unmodified.
|
||||
(b) Modified Versions which are Substantially Changed must be renamed to
|
||||
avoid use of the name of the Original Version or similar names entirely.
|
||||
(c) Modified Versions which are not Substantially Changed must be
|
||||
renamed to both (i) retain the name of the Original Version and (ii) add
|
||||
additional naming elements to distinguish the Modified Version from the
|
||||
Original Version. The name of such Modified Versions must be the name of
|
||||
the Original Version, with "derivative X" where X represents the name of
|
||||
the new work, appended to that name.
|
||||
|
||||
3) The name(s) of the Copyright Holder(s) and any contributor to the
|
||||
Font Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except (i) as required by this licence, (ii) to
|
||||
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
|
||||
their explicit written permission.
|
||||
|
||||
4) The Font Software, modified or unmodified, in part or in whole, must
|
||||
be distributed entirely under this licence, and must not be distributed
|
||||
under any other licence. The requirement for fonts to remain under this
|
||||
licence does not affect any document created using the Font Software,
|
||||
except any version of the Font Software extracted from a document
|
||||
created using the Font Software may only be distributed under this
|
||||
licence.
|
||||
|
||||
TERMINATION
|
||||
This licence becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
||||
DEALINGS IN THE FONT SOFTWARE.
|
||||
@ -0,0 +1,6 @@
|
||||
Silk icon set 1.3 by Mark James <mjames@gmail.com>
|
||||
|
||||
http://www.famfamfam.com/lab/icons/silk/
|
||||
|
||||
License: [CC-BY-2.5](https://creativecommons.org/licenses/by/2.5/)
|
||||
or [CC-BY-3.0](https://creativecommons.org/licenses/by/3.0/)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 507 B |
@ -0,0 +1,359 @@
|
||||
docReady(() => {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
initPinBox();
|
||||
}
|
||||
// if we are in console mode, show the console.
|
||||
if (CONSOLE_MODE && EVALEX) {
|
||||
createInteractiveConsole();
|
||||
}
|
||||
|
||||
const frames = document.querySelectorAll("div.traceback div.frame");
|
||||
if (EVALEX) {
|
||||
addConsoleIconToFrames(frames);
|
||||
}
|
||||
addEventListenersToElements(document.querySelectorAll("div.detail"), "click", () =>
|
||||
document.querySelector("div.traceback").scrollIntoView(false)
|
||||
);
|
||||
addToggleFrameTraceback(frames);
|
||||
addToggleTraceTypesOnClick(document.querySelectorAll("h2.traceback"));
|
||||
addInfoPrompt(document.querySelectorAll("span.nojavascript"));
|
||||
wrapPlainTraceback();
|
||||
});
|
||||
|
||||
function addToggleFrameTraceback(frames) {
|
||||
frames.forEach((frame) => {
|
||||
frame.addEventListener("click", () => {
|
||||
frame.getElementsByTagName("pre")[0].parentElement.classList.toggle("expanded");
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function wrapPlainTraceback() {
|
||||
const plainTraceback = document.querySelector("div.plain textarea");
|
||||
const wrapper = document.createElement("pre");
|
||||
const textNode = document.createTextNode(plainTraceback.textContent);
|
||||
wrapper.appendChild(textNode);
|
||||
plainTraceback.replaceWith(wrapper);
|
||||
}
|
||||
|
||||
function initPinBox() {
|
||||
document.querySelector(".pin-prompt form").addEventListener(
|
||||
"submit",
|
||||
function (event) {
|
||||
event.preventDefault();
|
||||
const pin = encodeURIComponent(this.pin.value);
|
||||
const encodedSecret = encodeURIComponent(SECRET);
|
||||
const btn = this.btn;
|
||||
btn.disabled = true;
|
||||
|
||||
fetch(
|
||||
`${document.location.pathname}?__debugger__=yes&cmd=pinauth&pin=${pin}&s=${encodedSecret}`
|
||||
)
|
||||
.then((res) => res.json())
|
||||
.then(({auth, exhausted}) => {
|
||||
if (auth) {
|
||||
EVALEX_TRUSTED = true;
|
||||
fadeOut(document.getElementsByClassName("pin-prompt")[0]);
|
||||
} else {
|
||||
alert(
|
||||
`Error: ${
|
||||
exhausted
|
||||
? "too many attempts. Restart server to retry."
|
||||
: "incorrect pin"
|
||||
}`
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
alert("Error: Could not verify PIN. Network error?");
|
||||
console.error(err);
|
||||
})
|
||||
.finally(() => (btn.disabled = false));
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
function promptForPin() {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
const encodedSecret = encodeURIComponent(SECRET);
|
||||
fetch(
|
||||
`${document.location.pathname}?__debugger__=yes&cmd=printpin&s=${encodedSecret}`
|
||||
);
|
||||
const pinPrompt = document.getElementsByClassName("pin-prompt")[0];
|
||||
fadeIn(pinPrompt);
|
||||
document.querySelector('.pin-prompt input[name="pin"]').focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for shell initialization
|
||||
*/
|
||||
function openShell(consoleNode, target, frameID) {
|
||||
promptForPin();
|
||||
if (consoleNode) {
|
||||
slideToggle(consoleNode);
|
||||
return consoleNode;
|
||||
}
|
||||
let historyPos = 0;
|
||||
const history = [""];
|
||||
const consoleElement = createConsole();
|
||||
const output = createConsoleOutput();
|
||||
const form = createConsoleInputForm();
|
||||
const command = createConsoleInput();
|
||||
|
||||
target.parentNode.appendChild(consoleElement);
|
||||
consoleElement.append(output);
|
||||
consoleElement.append(form);
|
||||
form.append(command);
|
||||
command.focus();
|
||||
slideToggle(consoleElement);
|
||||
|
||||
form.addEventListener("submit", (e) => {
|
||||
handleConsoleSubmit(e, command, frameID).then((consoleOutput) => {
|
||||
output.append(consoleOutput);
|
||||
command.focus();
|
||||
consoleElement.scrollTo(0, consoleElement.scrollHeight);
|
||||
const old = history.pop();
|
||||
history.push(command.value);
|
||||
if (typeof old !== "undefined") {
|
||||
history.push(old);
|
||||
}
|
||||
historyPos = history.length - 1;
|
||||
command.value = "";
|
||||
});
|
||||
});
|
||||
|
||||
command.addEventListener("keydown", (e) => {
|
||||
if (e.key === "l" && e.ctrlKey) {
|
||||
output.innerText = "--- screen cleared ---";
|
||||
} else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
|
||||
// Handle up arrow and down arrow.
|
||||
if (e.key === "ArrowUp" && historyPos > 0) {
|
||||
e.preventDefault();
|
||||
historyPos--;
|
||||
} else if (e.key === "ArrowDown" && historyPos < history.length - 1) {
|
||||
historyPos++;
|
||||
}
|
||||
command.value = history[historyPos];
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return consoleElement;
|
||||
}
|
||||
|
||||
function addEventListenersToElements(elements, event, listener) {
|
||||
elements.forEach((el) => el.addEventListener(event, listener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add extra info
|
||||
*/
|
||||
function addInfoPrompt(elements) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].innerHTML =
|
||||
"<p>To switch between the interactive traceback and the plaintext " +
|
||||
'one, you can click on the "Traceback" headline. From the text ' +
|
||||
"traceback you can also create a paste of it. " +
|
||||
(!EVALEX
|
||||
? ""
|
||||
: "For code execution mouse-over the frame you want to debug and " +
|
||||
"click on the console icon on the right side." +
|
||||
"<p>You can execute arbitrary Python code in the stack frames and " +
|
||||
"there are some extra helpers available for introspection:" +
|
||||
"<ul><li><code>dump()</code> shows all variables in the frame" +
|
||||
"<li><code>dump(obj)</code> dumps all that's known about the object</ul>");
|
||||
elements[i].classList.remove("nojavascript");
|
||||
}
|
||||
}
|
||||
|
||||
function addConsoleIconToFrames(frames) {
|
||||
for (let i = 0; i < frames.length; i++) {
|
||||
let consoleNode = null;
|
||||
const target = frames[i];
|
||||
const frameID = frames[i].id.substring(6);
|
||||
|
||||
for (let j = 0; j < target.getElementsByTagName("pre").length; j++) {
|
||||
const img = createIconForConsole();
|
||||
img.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
consoleNode = openShell(consoleNode, target, frameID);
|
||||
return false;
|
||||
});
|
||||
target.getElementsByTagName("pre")[j].append(img);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function slideToggle(target) {
|
||||
target.classList.toggle("active");
|
||||
}
|
||||
|
||||
/**
|
||||
* toggle traceback types on click.
|
||||
*/
|
||||
function addToggleTraceTypesOnClick(elements) {
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].addEventListener("click", () => {
|
||||
document.querySelector("div.traceback").classList.toggle("hidden");
|
||||
document.querySelector("div.plain").classList.toggle("hidden");
|
||||
});
|
||||
elements[i].style.cursor = "pointer";
|
||||
document.querySelector("div.plain").classList.toggle("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function createConsole() {
|
||||
const consoleNode = document.createElement("pre");
|
||||
consoleNode.classList.add("console");
|
||||
consoleNode.classList.add("active");
|
||||
return consoleNode;
|
||||
}
|
||||
|
||||
function createConsoleOutput() {
|
||||
const output = document.createElement("div");
|
||||
output.classList.add("output");
|
||||
output.innerHTML = "[console ready]";
|
||||
return output;
|
||||
}
|
||||
|
||||
function createConsoleInputForm() {
|
||||
const form = document.createElement("form");
|
||||
form.innerHTML = ">>> ";
|
||||
return form;
|
||||
}
|
||||
|
||||
function createConsoleInput() {
|
||||
const command = document.createElement("input");
|
||||
command.type = "text";
|
||||
command.setAttribute("autocomplete", "off");
|
||||
command.setAttribute("spellcheck", false);
|
||||
command.setAttribute("autocapitalize", "off");
|
||||
command.setAttribute("autocorrect", "off");
|
||||
return command;
|
||||
}
|
||||
|
||||
function createIconForConsole() {
|
||||
const img = document.createElement("img");
|
||||
img.setAttribute("src", "?__debugger__=yes&cmd=resource&f=console.png");
|
||||
img.setAttribute("title", "Open an interactive python shell in this frame");
|
||||
return img;
|
||||
}
|
||||
|
||||
function createExpansionButtonForConsole() {
|
||||
const expansionButton = document.createElement("a");
|
||||
expansionButton.setAttribute("href", "#");
|
||||
expansionButton.setAttribute("class", "toggle");
|
||||
expansionButton.innerHTML = " ";
|
||||
return expansionButton;
|
||||
}
|
||||
|
||||
function createInteractiveConsole() {
|
||||
const target = document.querySelector("div.console div.inner");
|
||||
while (target.firstChild) {
|
||||
target.removeChild(target.firstChild);
|
||||
}
|
||||
openShell(null, target, 0);
|
||||
}
|
||||
|
||||
function handleConsoleSubmit(e, command, frameID) {
|
||||
// Prevent page from refreshing.
|
||||
e.preventDefault();
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Get input command.
|
||||
const cmd = command.value;
|
||||
|
||||
// Setup GET request.
|
||||
const urlPath = "";
|
||||
const params = {
|
||||
__debugger__: "yes",
|
||||
cmd: cmd,
|
||||
frm: frameID,
|
||||
s: SECRET,
|
||||
};
|
||||
const paramString = Object.keys(params)
|
||||
.map((key) => {
|
||||
return "&" + encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
|
||||
})
|
||||
.join("");
|
||||
|
||||
fetch(urlPath + "?" + paramString)
|
||||
.then((res) => {
|
||||
return res.text();
|
||||
})
|
||||
.then((data) => {
|
||||
const tmp = document.createElement("div");
|
||||
tmp.innerHTML = data;
|
||||
resolve(tmp);
|
||||
|
||||
// Handle expandable span for long list outputs.
|
||||
// Example to test: list(range(13))
|
||||
let wrapperAdded = false;
|
||||
const wrapperSpan = document.createElement("span");
|
||||
const expansionButton = createExpansionButtonForConsole();
|
||||
|
||||
tmp.querySelectorAll("span.extended").forEach((spanToWrap) => {
|
||||
const parentDiv = spanToWrap.parentNode;
|
||||
if (!wrapperAdded) {
|
||||
parentDiv.insertBefore(wrapperSpan, spanToWrap);
|
||||
wrapperAdded = true;
|
||||
}
|
||||
parentDiv.removeChild(spanToWrap);
|
||||
wrapperSpan.append(spanToWrap);
|
||||
spanToWrap.hidden = true;
|
||||
|
||||
expansionButton.addEventListener("click", () => {
|
||||
spanToWrap.hidden = !spanToWrap.hidden;
|
||||
expansionButton.classList.toggle("open");
|
||||
return false;
|
||||
});
|
||||
});
|
||||
|
||||
// Add expansion button at end of wrapper.
|
||||
if (wrapperAdded) {
|
||||
wrapperSpan.append(expansionButton);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
});
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function fadeOut(element) {
|
||||
element.style.opacity = 1;
|
||||
|
||||
(function fade() {
|
||||
element.style.opacity -= 0.1;
|
||||
if (element.style.opacity < 0) {
|
||||
element.style.display = "none";
|
||||
} else {
|
||||
requestAnimationFrame(fade);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function fadeIn(element, display) {
|
||||
element.style.opacity = 0;
|
||||
element.style.display = display || "block";
|
||||
|
||||
(function fade() {
|
||||
let val = parseFloat(element.style.opacity) + 0.1;
|
||||
if (val <= 1) {
|
||||
element.style.opacity = val;
|
||||
requestAnimationFrame(fade);
|
||||
}
|
||||
})();
|
||||
}
|
||||
|
||||
function docReady(fn) {
|
||||
if (document.readyState === "complete" || document.readyState === "interactive") {
|
||||
setTimeout(fn, 1);
|
||||
} else {
|
||||
document.addEventListener("DOMContentLoaded", fn);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 191 B |
Binary file not shown.
|
After Width: | Height: | Size: 200 B |
Binary file not shown.
|
After Width: | Height: | Size: 818 B |
@ -0,0 +1,163 @@
|
||||
@font-face {
|
||||
font-family: 'Ubuntu';
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
src: local('Ubuntu'), local('Ubuntu-Regular'),
|
||||
url('?__debugger__=yes&cmd=resource&f=ubuntu.ttf') format('truetype');
|
||||
}
|
||||
|
||||
body, input { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; color: #000; text-align: center;
|
||||
margin: 1em; padding: 0; font-size: 15px; }
|
||||
h1, h2, h3 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode',
|
||||
'Geneva', 'Verdana', sans-serif; font-weight: normal; }
|
||||
|
||||
input { background-color: #fff; margin: 0; text-align: left;
|
||||
outline: none !important; }
|
||||
input[type="submit"] { padding: 3px 6px; }
|
||||
a { color: #11557C; }
|
||||
a:hover { color: #177199; }
|
||||
pre, code,
|
||||
textarea { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
||||
monospace; font-size: 14px; }
|
||||
|
||||
div.debugger { text-align: left; padding: 12px; margin: auto;
|
||||
background-color: white; }
|
||||
h1 { font-size: 36px; margin: 0 0 0.3em 0; }
|
||||
div.detail { cursor: pointer; }
|
||||
div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap;
|
||||
font-family: monospace; }
|
||||
div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
|
||||
div.footer { font-size: 13px; text-align: right; margin: 30px 0;
|
||||
color: #86989B; }
|
||||
|
||||
h2 { font-size: 16px; margin: 1.3em 0 0.0 0; padding: 9px;
|
||||
background-color: #11557C; color: white; }
|
||||
h2 em, h3 em { font-style: normal; color: #A5D6D9; font-weight: normal; }
|
||||
|
||||
div.traceback, div.plain { border: 1px solid #ddd; margin: 0 0 1em 0; padding: 10px; }
|
||||
div.plain p { margin: 0; }
|
||||
div.plain textarea,
|
||||
div.plain pre { margin: 10px 0 0 0; padding: 4px;
|
||||
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
||||
div.plain textarea { width: 99%; height: 300px; }
|
||||
div.traceback h3 { font-size: 1em; margin: 0 0 0.8em 0; }
|
||||
div.traceback ul { list-style: none; margin: 0; padding: 0 0 0 1em; }
|
||||
div.traceback h4 { font-size: 13px; font-weight: normal; margin: 0.7em 0 0.1em 0; }
|
||||
div.traceback pre { margin: 0; padding: 5px 0 3px 15px;
|
||||
background-color: #E8EFF0; border: 1px solid #D3E7E9; }
|
||||
div.traceback .library .current { background: white; color: #555; }
|
||||
div.traceback .expanded .current { background: #E8EFF0; color: black; }
|
||||
div.traceback pre:hover { background-color: #DDECEE; color: black; cursor: pointer; }
|
||||
div.traceback div.source.expanded pre + pre { border-top: none; }
|
||||
|
||||
div.traceback span.ws { display: none; }
|
||||
div.traceback pre.before, div.traceback pre.after { display: none; background: white; }
|
||||
div.traceback div.source.expanded pre.before,
|
||||
div.traceback div.source.expanded pre.after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.traceback div.source.expanded span.ws {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
div.traceback blockquote { margin: 1em 0 0 0; padding: 0; white-space: pre-line; }
|
||||
div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; }
|
||||
div.traceback img:hover { background-color: #ddd; cursor: pointer;
|
||||
border-color: #BFDDE0; }
|
||||
div.traceback pre:hover img { display: block; }
|
||||
div.traceback cite.filename { font-style: normal; color: #3B666B; }
|
||||
|
||||
pre.console { border: 1px solid #ccc; background: white!important;
|
||||
color: black; padding: 5px!important;
|
||||
margin: 3px 0 0 0!important; cursor: default!important;
|
||||
max-height: 400px; overflow: auto; }
|
||||
pre.console form { color: #555; }
|
||||
pre.console input { background-color: transparent; color: #555;
|
||||
width: 90%; font-family: 'Consolas', 'Deja Vu Sans Mono',
|
||||
'Bitstream Vera Sans Mono', monospace; font-size: 14px;
|
||||
border: none!important; }
|
||||
|
||||
span.string { color: #30799B; }
|
||||
span.number { color: #9C1A1C; }
|
||||
span.help { color: #3A7734; }
|
||||
span.object { color: #485F6E; }
|
||||
span.extended { opacity: 0.5; }
|
||||
span.extended:hover { opacity: 1; }
|
||||
a.toggle { text-decoration: none; background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-image: url(?__debugger__=yes&cmd=resource&f=more.png); }
|
||||
a.toggle:hover { background-color: #444; }
|
||||
a.open { background-image: url(?__debugger__=yes&cmd=resource&f=less.png); }
|
||||
|
||||
pre.console div.traceback,
|
||||
pre.console div.box { margin: 5px 10px; white-space: normal;
|
||||
border: 1px solid #11557C; padding: 10px;
|
||||
font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; }
|
||||
pre.console div.box h3,
|
||||
pre.console div.traceback h3 { margin: -10px -10px 10px -10px; padding: 5px;
|
||||
background: #11557C; color: white; }
|
||||
|
||||
pre.console div.traceback pre:hover { cursor: default; background: #E8EFF0; }
|
||||
pre.console div.traceback pre.syntaxerror { background: inherit; border: none;
|
||||
margin: 20px -10px -10px -10px;
|
||||
padding: 10px; border-top: 1px solid #BFDDE0;
|
||||
background: #E8EFF0; }
|
||||
pre.console div.noframe-traceback pre.syntaxerror { margin-top: -10px; border: none; }
|
||||
|
||||
pre.console div.box pre.repr { padding: 0; margin: 0; background-color: white; border: none; }
|
||||
pre.console div.box table { margin-top: 6px; }
|
||||
pre.console div.box pre { border: none; }
|
||||
pre.console div.box pre.help { background-color: white; }
|
||||
pre.console div.box pre.help:hover { cursor: default; }
|
||||
pre.console table tr { vertical-align: top; }
|
||||
div.console { border: 1px solid #ccc; padding: 4px; background-color: #fafafa; }
|
||||
|
||||
div.traceback pre, div.console pre {
|
||||
white-space: pre-wrap; /* css-3 should we be so lucky... */
|
||||
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
|
||||
white-space: -pre-wrap; /* Opera 4-6 ?? */
|
||||
white-space: -o-pre-wrap; /* Opera 7 ?? */
|
||||
word-wrap: break-word; /* Internet Explorer 5.5+ */
|
||||
_white-space: pre; /* IE only hack to re-specify in
|
||||
addition to word-wrap */
|
||||
}
|
||||
|
||||
|
||||
div.pin-prompt {
|
||||
position: absolute;
|
||||
display: none;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
div.pin-prompt .inner {
|
||||
background: #eee;
|
||||
padding: 10px 50px;
|
||||
width: 350px;
|
||||
margin: 10% auto 0 auto;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
div.exc-divider {
|
||||
margin: 0.7em 0 0 -1em;
|
||||
padding: 0.5em;
|
||||
background: #11557C;
|
||||
color: #ddd;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.console.active {
|
||||
max-height: 0!important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
Binary file not shown.
600
myvenv/lib/python3.10/site-packages/werkzeug/debug/tbtools.py
Normal file
600
myvenv/lib/python3.10/site-packages/werkzeug/debug/tbtools.py
Normal file
@ -0,0 +1,600 @@
|
||||
import codecs
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
import traceback
|
||||
import typing as t
|
||||
from html import escape
|
||||
from tokenize import TokenError
|
||||
from types import CodeType
|
||||
from types import TracebackType
|
||||
|
||||
from .._internal import _to_str
|
||||
from ..filesystem import get_filesystem_encoding
|
||||
from ..utils import cached_property
|
||||
from .console import Console
|
||||
|
||||
_coding_re = re.compile(rb"coding[:=]\s*([-\w.]+)")
|
||||
_line_re = re.compile(rb"^(.*?)$", re.MULTILINE)
|
||||
_funcdef_re = re.compile(r"^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)")
|
||||
|
||||
HEADER = """\
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>%(title)s // Werkzeug Debugger</title>
|
||||
<link rel="stylesheet" href="?__debugger__=yes&cmd=resource&f=style.css"
|
||||
type="text/css">
|
||||
<!-- We need to make sure this has a favicon so that the debugger does
|
||||
not accidentally trigger a request to /favicon.ico which might
|
||||
change the application's state. -->
|
||||
<link rel="shortcut icon"
|
||||
href="?__debugger__=yes&cmd=resource&f=console.png">
|
||||
<script src="?__debugger__=yes&cmd=resource&f=debugger.js"></script>
|
||||
<script type="text/javascript">
|
||||
var TRACEBACK = %(traceback_id)d,
|
||||
CONSOLE_MODE = %(console)s,
|
||||
EVALEX = %(evalex)s,
|
||||
EVALEX_TRUSTED = %(evalex_trusted)s,
|
||||
SECRET = "%(secret)s";
|
||||
</script>
|
||||
</head>
|
||||
<body style="background-color: #fff">
|
||||
<div class="debugger">
|
||||
"""
|
||||
FOOTER = """\
|
||||
<div class="footer">
|
||||
Brought to you by <strong class="arthur">DON'T PANIC</strong>, your
|
||||
friendly Werkzeug powered traceback interpreter.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pin-prompt">
|
||||
<div class="inner">
|
||||
<h3>Console Locked</h3>
|
||||
<p>
|
||||
The console is locked and needs to be unlocked by entering the PIN.
|
||||
You can find the PIN printed out on the standard output of your
|
||||
shell that runs the server.
|
||||
<form>
|
||||
<p>PIN:
|
||||
<input type=text name=pin size=14>
|
||||
<input type=submit name=btn value="Confirm Pin">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
PAGE_HTML = (
|
||||
HEADER
|
||||
+ """\
|
||||
<h1>%(exception_type)s</h1>
|
||||
<div class="detail">
|
||||
<p class="errormsg">%(exception)s</p>
|
||||
</div>
|
||||
<h2 class="traceback">Traceback <em>(most recent call last)</em></h2>
|
||||
%(summary)s
|
||||
<div class="plain">
|
||||
<p>
|
||||
This is the Copy/Paste friendly version of the traceback.
|
||||
</p>
|
||||
<textarea cols="50" rows="10" name="code" readonly>%(plaintext)s</textarea>
|
||||
</div>
|
||||
<div class="explanation">
|
||||
The debugger caught an exception in your WSGI application. You can now
|
||||
look at the traceback which led to the error. <span class="nojavascript">
|
||||
If you enable JavaScript you can also use additional features such as code
|
||||
execution (if the evalex feature is enabled), automatic pasting of the
|
||||
exceptions and much more.</span>
|
||||
</div>
|
||||
"""
|
||||
+ FOOTER
|
||||
+ """
|
||||
<!--
|
||||
|
||||
%(plaintext_cs)s
|
||||
|
||||
-->
|
||||
"""
|
||||
)
|
||||
|
||||
CONSOLE_HTML = (
|
||||
HEADER
|
||||
+ """\
|
||||
<h1>Interactive Console</h1>
|
||||
<div class="explanation">
|
||||
In this console you can execute Python expressions in the context of the
|
||||
application. The initial namespace was created by the debugger automatically.
|
||||
</div>
|
||||
<div class="console"><div class="inner">The Console requires JavaScript.</div></div>
|
||||
"""
|
||||
+ FOOTER
|
||||
)
|
||||
|
||||
SUMMARY_HTML = """\
|
||||
<div class="%(classes)s">
|
||||
%(title)s
|
||||
<ul>%(frames)s</ul>
|
||||
%(description)s
|
||||
</div>
|
||||
"""
|
||||
|
||||
FRAME_HTML = """\
|
||||
<div class="frame" id="frame-%(id)d">
|
||||
<h4>File <cite class="filename">"%(filename)s"</cite>,
|
||||
line <em class="line">%(lineno)s</em>,
|
||||
in <code class="function">%(function_name)s</code></h4>
|
||||
<div class="source %(library)s">%(lines)s</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
SOURCE_LINE_HTML = """\
|
||||
<tr class="%(classes)s">
|
||||
<td class=lineno>%(lineno)s</td>
|
||||
<td>%(code)s</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
|
||||
def render_console_html(secret: str, evalex_trusted: bool = True) -> str:
|
||||
return CONSOLE_HTML % {
|
||||
"evalex": "true",
|
||||
"evalex_trusted": "true" if evalex_trusted else "false",
|
||||
"console": "true",
|
||||
"title": "Console",
|
||||
"secret": secret,
|
||||
"traceback_id": -1,
|
||||
}
|
||||
|
||||
|
||||
def get_current_traceback(
|
||||
ignore_system_exceptions: bool = False,
|
||||
show_hidden_frames: bool = False,
|
||||
skip: int = 0,
|
||||
) -> "Traceback":
|
||||
"""Get the current exception info as `Traceback` object. Per default
|
||||
calling this method will reraise system exceptions such as generator exit,
|
||||
system exit or others. This behavior can be disabled by passing `False`
|
||||
to the function as first parameter.
|
||||
"""
|
||||
info = t.cast(
|
||||
t.Tuple[t.Type[BaseException], BaseException, TracebackType], sys.exc_info()
|
||||
)
|
||||
exc_type, exc_value, tb = info
|
||||
|
||||
if ignore_system_exceptions and exc_type in {
|
||||
SystemExit,
|
||||
KeyboardInterrupt,
|
||||
GeneratorExit,
|
||||
}:
|
||||
raise
|
||||
for _ in range(skip):
|
||||
if tb.tb_next is None:
|
||||
break
|
||||
tb = tb.tb_next
|
||||
tb = Traceback(exc_type, exc_value, tb)
|
||||
if not show_hidden_frames:
|
||||
tb.filter_hidden_frames()
|
||||
return tb
|
||||
|
||||
|
||||
class Line:
|
||||
"""Helper for the source renderer."""
|
||||
|
||||
__slots__ = ("lineno", "code", "in_frame", "current")
|
||||
|
||||
def __init__(self, lineno: int, code: str) -> None:
|
||||
self.lineno = lineno
|
||||
self.code = code
|
||||
self.in_frame = False
|
||||
self.current = False
|
||||
|
||||
@property
|
||||
def classes(self) -> t.List[str]:
|
||||
rv = ["line"]
|
||||
if self.in_frame:
|
||||
rv.append("in-frame")
|
||||
if self.current:
|
||||
rv.append("current")
|
||||
return rv
|
||||
|
||||
def render(self) -> str:
|
||||
return SOURCE_LINE_HTML % {
|
||||
"classes": " ".join(self.classes),
|
||||
"lineno": self.lineno,
|
||||
"code": escape(self.code),
|
||||
}
|
||||
|
||||
|
||||
class Traceback:
|
||||
"""Wraps a traceback."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exc_type: t.Type[BaseException],
|
||||
exc_value: BaseException,
|
||||
tb: TracebackType,
|
||||
) -> None:
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
self.tb = tb
|
||||
|
||||
exception_type = exc_type.__name__
|
||||
if exc_type.__module__ not in {"builtins", "__builtin__", "exceptions"}:
|
||||
exception_type = f"{exc_type.__module__}.{exception_type}"
|
||||
self.exception_type = exception_type
|
||||
|
||||
self.groups = []
|
||||
memo = set()
|
||||
while True:
|
||||
self.groups.append(Group(exc_type, exc_value, tb))
|
||||
memo.add(id(exc_value))
|
||||
exc_value = exc_value.__cause__ or exc_value.__context__ # type: ignore
|
||||
if exc_value is None or id(exc_value) in memo:
|
||||
break
|
||||
exc_type = type(exc_value)
|
||||
tb = exc_value.__traceback__ # type: ignore
|
||||
self.groups.reverse()
|
||||
self.frames = [frame for group in self.groups for frame in group.frames]
|
||||
|
||||
def filter_hidden_frames(self) -> None:
|
||||
"""Remove the frames according to the paste spec."""
|
||||
for group in self.groups:
|
||||
group.filter_hidden_frames()
|
||||
|
||||
self.frames[:] = [frame for group in self.groups for frame in group.frames]
|
||||
|
||||
@property
|
||||
def is_syntax_error(self) -> bool:
|
||||
"""Is it a syntax error?"""
|
||||
return isinstance(self.exc_value, SyntaxError)
|
||||
|
||||
@property
|
||||
def exception(self) -> str:
|
||||
"""String representation of the final exception."""
|
||||
return self.groups[-1].exception
|
||||
|
||||
def log(self, logfile: t.Optional[t.IO[str]] = None) -> None:
|
||||
"""Log the ASCII traceback into a file object."""
|
||||
if logfile is None:
|
||||
logfile = sys.stderr
|
||||
tb = f"{self.plaintext.rstrip()}\n"
|
||||
logfile.write(tb)
|
||||
|
||||
def render_summary(self, include_title: bool = True) -> str:
|
||||
"""Render the traceback for the interactive console."""
|
||||
title = ""
|
||||
classes = ["traceback"]
|
||||
if not self.frames:
|
||||
classes.append("noframe-traceback")
|
||||
frames = []
|
||||
else:
|
||||
library_frames = sum(frame.is_library for frame in self.frames)
|
||||
mark_lib = 0 < library_frames < len(self.frames)
|
||||
frames = [group.render(mark_lib=mark_lib) for group in self.groups]
|
||||
|
||||
if include_title:
|
||||
if self.is_syntax_error:
|
||||
title = "Syntax Error"
|
||||
else:
|
||||
title = "Traceback <em>(most recent call last)</em>:"
|
||||
|
||||
if self.is_syntax_error:
|
||||
description = f"<pre class=syntaxerror>{escape(self.exception)}</pre>"
|
||||
else:
|
||||
description = f"<blockquote>{escape(self.exception)}</blockquote>"
|
||||
|
||||
return SUMMARY_HTML % {
|
||||
"classes": " ".join(classes),
|
||||
"title": f"<h3>{title if title else ''}</h3>",
|
||||
"frames": "\n".join(frames),
|
||||
"description": description,
|
||||
}
|
||||
|
||||
def render_full(
|
||||
self,
|
||||
evalex: bool = False,
|
||||
secret: t.Optional[str] = None,
|
||||
evalex_trusted: bool = True,
|
||||
) -> str:
|
||||
"""Render the Full HTML page with the traceback info."""
|
||||
exc = escape(self.exception)
|
||||
return PAGE_HTML % {
|
||||
"evalex": "true" if evalex else "false",
|
||||
"evalex_trusted": "true" if evalex_trusted else "false",
|
||||
"console": "false",
|
||||
"title": exc,
|
||||
"exception": exc,
|
||||
"exception_type": escape(self.exception_type),
|
||||
"summary": self.render_summary(include_title=False),
|
||||
"plaintext": escape(self.plaintext),
|
||||
"plaintext_cs": re.sub("-{2,}", "-", self.plaintext),
|
||||
"traceback_id": self.id,
|
||||
"secret": secret,
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def plaintext(self) -> str:
|
||||
return "\n".join([group.render_text() for group in self.groups])
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return id(self)
|
||||
|
||||
|
||||
class Group:
|
||||
"""A group of frames for an exception in a traceback. If the
|
||||
exception has a ``__cause__`` or ``__context__``, there are multiple
|
||||
exception groups.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exc_type: t.Type[BaseException],
|
||||
exc_value: BaseException,
|
||||
tb: TracebackType,
|
||||
) -> None:
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
self.info = None
|
||||
if exc_value.__cause__ is not None:
|
||||
self.info = (
|
||||
"The above exception was the direct cause of the following exception"
|
||||
)
|
||||
elif exc_value.__context__ is not None:
|
||||
self.info = (
|
||||
"During handling of the above exception, another exception occurred"
|
||||
)
|
||||
|
||||
self.frames = []
|
||||
while tb is not None:
|
||||
self.frames.append(Frame(exc_type, exc_value, tb))
|
||||
tb = tb.tb_next # type: ignore
|
||||
|
||||
def filter_hidden_frames(self) -> None:
|
||||
# An exception may not have a traceback to filter frames, such
|
||||
# as one re-raised from ProcessPoolExecutor.
|
||||
if not self.frames:
|
||||
return
|
||||
|
||||
new_frames: t.List[Frame] = []
|
||||
hidden = False
|
||||
|
||||
for frame in self.frames:
|
||||
hide = frame.hide
|
||||
if hide in ("before", "before_and_this"):
|
||||
new_frames = []
|
||||
hidden = False
|
||||
if hide == "before_and_this":
|
||||
continue
|
||||
elif hide in ("reset", "reset_and_this"):
|
||||
hidden = False
|
||||
if hide == "reset_and_this":
|
||||
continue
|
||||
elif hide in ("after", "after_and_this"):
|
||||
hidden = True
|
||||
if hide == "after_and_this":
|
||||
continue
|
||||
elif hide or hidden:
|
||||
continue
|
||||
new_frames.append(frame)
|
||||
|
||||
# if we only have one frame and that frame is from the codeop
|
||||
# module, remove it.
|
||||
if len(new_frames) == 1 and self.frames[0].module == "codeop":
|
||||
del self.frames[:]
|
||||
|
||||
# if the last frame is missing something went terrible wrong :(
|
||||
elif self.frames[-1] in new_frames:
|
||||
self.frames[:] = new_frames
|
||||
|
||||
@property
|
||||
def exception(self) -> str:
|
||||
"""String representation of the exception."""
|
||||
buf = traceback.format_exception_only(self.exc_type, self.exc_value)
|
||||
rv = "".join(buf).strip()
|
||||
return _to_str(rv, "utf-8", "replace")
|
||||
|
||||
def render(self, mark_lib: bool = True) -> str:
|
||||
out = []
|
||||
if self.info is not None:
|
||||
out.append(f'<li><div class="exc-divider">{self.info}:</div>')
|
||||
for frame in self.frames:
|
||||
title = f' title="{escape(frame.info)}"' if frame.info else ""
|
||||
out.append(f"<li{title}>{frame.render(mark_lib=mark_lib)}")
|
||||
return "\n".join(out)
|
||||
|
||||
def render_text(self) -> str:
|
||||
out = []
|
||||
if self.info is not None:
|
||||
out.append(f"\n{self.info}:\n")
|
||||
out.append("Traceback (most recent call last):")
|
||||
for frame in self.frames:
|
||||
out.append(frame.render_text())
|
||||
out.append(self.exception)
|
||||
return "\n".join(out)
|
||||
|
||||
|
||||
class Frame:
|
||||
"""A single frame in a traceback."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
exc_type: t.Type[BaseException],
|
||||
exc_value: BaseException,
|
||||
tb: TracebackType,
|
||||
) -> None:
|
||||
self.lineno = tb.tb_lineno
|
||||
self.function_name = tb.tb_frame.f_code.co_name
|
||||
self.locals = tb.tb_frame.f_locals
|
||||
self.globals = tb.tb_frame.f_globals
|
||||
|
||||
fn = inspect.getsourcefile(tb) or inspect.getfile(tb)
|
||||
if fn[-4:] in (".pyo", ".pyc"):
|
||||
fn = fn[:-1]
|
||||
# if it's a file on the file system resolve the real filename.
|
||||
if os.path.isfile(fn):
|
||||
fn = os.path.realpath(fn)
|
||||
self.filename = _to_str(fn, get_filesystem_encoding())
|
||||
self.module = self.globals.get("__name__", self.locals.get("__name__"))
|
||||
self.loader = self.globals.get("__loader__", self.locals.get("__loader__"))
|
||||
self.code = tb.tb_frame.f_code
|
||||
|
||||
# support for paste's traceback extensions
|
||||
self.hide = self.locals.get("__traceback_hide__", False)
|
||||
info = self.locals.get("__traceback_info__")
|
||||
if info is not None:
|
||||
info = _to_str(info, "utf-8", "replace")
|
||||
self.info = info
|
||||
|
||||
def render(self, mark_lib: bool = True) -> str:
|
||||
"""Render a single frame in a traceback."""
|
||||
return FRAME_HTML % {
|
||||
"id": self.id,
|
||||
"filename": escape(self.filename),
|
||||
"lineno": self.lineno,
|
||||
"function_name": escape(self.function_name),
|
||||
"lines": self.render_line_context(),
|
||||
"library": "library" if mark_lib and self.is_library else "",
|
||||
}
|
||||
|
||||
@cached_property
|
||||
def is_library(self) -> bool:
|
||||
return any(
|
||||
self.filename.startswith(os.path.realpath(path))
|
||||
for path in sysconfig.get_paths().values()
|
||||
)
|
||||
|
||||
def render_text(self) -> str:
|
||||
return (
|
||||
f' File "{self.filename}", line {self.lineno}, in {self.function_name}\n'
|
||||
f" {self.current_line.strip()}"
|
||||
)
|
||||
|
||||
def render_line_context(self) -> str:
|
||||
before, current, after = self.get_context_lines()
|
||||
rv = []
|
||||
|
||||
def render_line(line: str, cls: str) -> None:
|
||||
line = line.expandtabs().rstrip()
|
||||
stripped_line = line.strip()
|
||||
prefix = len(line) - len(stripped_line)
|
||||
rv.append(
|
||||
f'<pre class="line {cls}"><span class="ws">{" " * prefix}</span>'
|
||||
f"{escape(stripped_line) if stripped_line else ' '}</pre>"
|
||||
)
|
||||
|
||||
for line in before:
|
||||
render_line(line, "before")
|
||||
render_line(current, "current")
|
||||
for line in after:
|
||||
render_line(line, "after")
|
||||
|
||||
return "\n".join(rv)
|
||||
|
||||
def get_annotated_lines(self) -> t.List[Line]:
|
||||
"""Helper function that returns lines with extra information."""
|
||||
lines = [Line(idx + 1, x) for idx, x in enumerate(self.sourcelines)]
|
||||
|
||||
# find function definition and mark lines
|
||||
if hasattr(self.code, "co_firstlineno"):
|
||||
lineno = self.code.co_firstlineno - 1
|
||||
while lineno > 0:
|
||||
if _funcdef_re.match(lines[lineno].code):
|
||||
break
|
||||
lineno -= 1
|
||||
try:
|
||||
offset = len(inspect.getblock([f"{x.code}\n" for x in lines[lineno:]]))
|
||||
except TokenError:
|
||||
offset = 0
|
||||
for line in lines[lineno : lineno + offset]:
|
||||
line.in_frame = True
|
||||
|
||||
# mark current line
|
||||
try:
|
||||
lines[self.lineno - 1].current = True
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
return lines
|
||||
|
||||
def eval(self, code: t.Union[str, CodeType], mode: str = "single") -> t.Any:
|
||||
"""Evaluate code in the context of the frame."""
|
||||
if isinstance(code, str):
|
||||
code = compile(code, "<interactive>", mode)
|
||||
return eval(code, self.globals, self.locals)
|
||||
|
||||
@cached_property
|
||||
def sourcelines(self) -> t.List[str]:
|
||||
"""The sourcecode of the file as list of strings."""
|
||||
# get sourcecode from loader or file
|
||||
source = None
|
||||
if self.loader is not None:
|
||||
try:
|
||||
if hasattr(self.loader, "get_source"):
|
||||
source = self.loader.get_source(self.module)
|
||||
elif hasattr(self.loader, "get_source_by_code"):
|
||||
source = self.loader.get_source_by_code(self.code)
|
||||
except Exception:
|
||||
# we munch the exception so that we don't cause troubles
|
||||
# if the loader is broken.
|
||||
pass
|
||||
|
||||
if source is None:
|
||||
try:
|
||||
with open(self.filename, mode="rb") as f:
|
||||
source = f.read()
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
# already str? return right away
|
||||
if isinstance(source, str):
|
||||
return source.splitlines()
|
||||
|
||||
charset = "utf-8"
|
||||
if source.startswith(codecs.BOM_UTF8):
|
||||
source = source[3:]
|
||||
else:
|
||||
for idx, match in enumerate(_line_re.finditer(source)):
|
||||
coding_match = _coding_re.search(match.group())
|
||||
if coding_match is not None:
|
||||
charset = coding_match.group(1).decode("utf-8")
|
||||
break
|
||||
if idx > 1:
|
||||
break
|
||||
|
||||
# on broken cookies we fall back to utf-8 too
|
||||
charset = _to_str(charset)
|
||||
try:
|
||||
codecs.lookup(charset)
|
||||
except LookupError:
|
||||
charset = "utf-8"
|
||||
|
||||
return source.decode(charset, "replace").splitlines()
|
||||
|
||||
def get_context_lines(
|
||||
self, context: int = 5
|
||||
) -> t.Tuple[t.List[str], str, t.List[str]]:
|
||||
before = self.sourcelines[self.lineno - context - 1 : self.lineno - 1]
|
||||
past = self.sourcelines[self.lineno : self.lineno + context]
|
||||
return (before, self.current_line, past)
|
||||
|
||||
@property
|
||||
def current_line(self) -> str:
|
||||
try:
|
||||
return self.sourcelines[self.lineno - 1]
|
||||
except IndexError:
|
||||
return ""
|
||||
|
||||
@cached_property
|
||||
def console(self) -> Console:
|
||||
return Console(self.globals, self.locals)
|
||||
|
||||
@property
|
||||
def id(self) -> int:
|
||||
return id(self)
|
||||
944
myvenv/lib/python3.10/site-packages/werkzeug/exceptions.py
Normal file
944
myvenv/lib/python3.10/site-packages/werkzeug/exceptions.py
Normal file
@ -0,0 +1,944 @@
|
||||
"""Implements a number of Python exceptions which can be raised from within
|
||||
a view to trigger a standard HTTP non-200 response.
|
||||
|
||||
Usage Example
|
||||
-------------
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.wrappers.request import Request
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
|
||||
def view(request):
|
||||
raise NotFound()
|
||||
|
||||
@Request.application
|
||||
def application(request):
|
||||
try:
|
||||
return view(request)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
|
||||
As you can see from this example those exceptions are callable WSGI
|
||||
applications. However, they are not Werkzeug response objects. You
|
||||
can get a response object by calling ``get_response()`` on a HTTP
|
||||
exception.
|
||||
|
||||
Keep in mind that you may have to pass an environ (WSGI) or scope
|
||||
(ASGI) to ``get_response()`` because some errors fetch additional
|
||||
information relating to the request.
|
||||
|
||||
If you want to hook in a different exception page to say, a 404 status
|
||||
code, you can add a second except for a specific subclass of an error:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@Request.application
|
||||
def application(request):
|
||||
try:
|
||||
return view(request)
|
||||
except NotFound as e:
|
||||
return not_found(request)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
|
||||
"""
|
||||
import sys
|
||||
import typing as t
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from html import escape
|
||||
|
||||
from ._internal import _get_environ
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
from .datastructures import WWWAuthenticate
|
||||
from .sansio.response import Response
|
||||
from .wrappers.request import Request as WSGIRequest # noqa: F401
|
||||
from .wrappers.response import Response as WSGIResponse # noqa: F401
|
||||
|
||||
|
||||
class HTTPException(Exception):
|
||||
"""The base class for all HTTP exceptions. This exception can be called as a WSGI
|
||||
application to render a default error page or you can catch the subclasses
|
||||
of it independently and render nicer error messages.
|
||||
"""
|
||||
|
||||
code: t.Optional[int] = None
|
||||
description: t.Optional[str] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if description is not None:
|
||||
self.description = description
|
||||
self.response = response
|
||||
|
||||
@classmethod
|
||||
def wrap(
|
||||
cls, exception: t.Type[BaseException], name: t.Optional[str] = None
|
||||
) -> t.Type["HTTPException"]:
|
||||
"""Create an exception that is a subclass of the calling HTTP
|
||||
exception and the ``exception`` argument.
|
||||
|
||||
The first argument to the class will be passed to the
|
||||
wrapped ``exception``, the rest to the HTTP exception. If
|
||||
``e.args`` is not empty and ``e.show_exception`` is ``True``,
|
||||
the wrapped exception message is added to the HTTP error
|
||||
description.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Create a subclass manually
|
||||
instead.
|
||||
|
||||
.. versionchanged:: 0.15.5
|
||||
The ``show_exception`` attribute controls whether the
|
||||
description includes the wrapped exception message.
|
||||
|
||||
.. versionchanged:: 0.15.0
|
||||
The description includes the wrapped exception message.
|
||||
"""
|
||||
warnings.warn(
|
||||
"'HTTPException.wrap' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Create a subclass manually instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
class newcls(cls, exception): # type: ignore
|
||||
_description = cls.description
|
||||
show_exception = False
|
||||
|
||||
def __init__(
|
||||
self, arg: t.Optional[t.Any] = None, *args: t.Any, **kwargs: t.Any
|
||||
) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if arg is None:
|
||||
exception.__init__(self)
|
||||
else:
|
||||
exception.__init__(self, arg)
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
if self.show_exception:
|
||||
return (
|
||||
f"{self._description}\n"
|
||||
f"{exception.__name__}: {exception.__str__(self)}"
|
||||
)
|
||||
|
||||
return self._description # type: ignore
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str) -> None:
|
||||
self._description = value
|
||||
|
||||
newcls.__module__ = sys._getframe(1).f_globals["__name__"]
|
||||
name = name or cls.__name__ + exception.__name__
|
||||
newcls.__name__ = newcls.__qualname__ = name
|
||||
return newcls
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""The status name."""
|
||||
from .http import HTTP_STATUS_CODES
|
||||
|
||||
return HTTP_STATUS_CODES.get(self.code, "Unknown Error") # type: ignore
|
||||
|
||||
def get_description(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> str:
|
||||
"""Get the description."""
|
||||
if self.description is None:
|
||||
description = ""
|
||||
elif not isinstance(self.description, str):
|
||||
description = str(self.description)
|
||||
else:
|
||||
description = self.description
|
||||
|
||||
description = escape(description).replace("\n", "<br>")
|
||||
return f"<p>{description}</p>"
|
||||
|
||||
def get_body(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> str:
|
||||
"""Get the HTML body."""
|
||||
return (
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
|
||||
f"<title>{self.code} {escape(self.name)}</title>\n"
|
||||
f"<h1>{escape(self.name)}</h1>\n"
|
||||
f"{self.get_description(environ)}\n"
|
||||
)
|
||||
|
||||
def get_headers(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> t.List[t.Tuple[str, str]]:
|
||||
"""Get a list of headers."""
|
||||
return [("Content-Type", "text/html; charset=utf-8")]
|
||||
|
||||
def get_response(
|
||||
self,
|
||||
environ: t.Optional[t.Union["WSGIEnvironment", "WSGIRequest"]] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> "Response":
|
||||
"""Get a response object. If one was passed to the exception
|
||||
it's returned directly.
|
||||
|
||||
:param environ: the optional environ for the request. This
|
||||
can be used to modify the response depending
|
||||
on how the request looked like.
|
||||
:return: a :class:`Response` object or a subclass thereof.
|
||||
"""
|
||||
from .wrappers.response import Response as WSGIResponse # noqa: F811
|
||||
|
||||
if self.response is not None:
|
||||
return self.response
|
||||
if environ is not None:
|
||||
environ = _get_environ(environ)
|
||||
headers = self.get_headers(environ, scope)
|
||||
return WSGIResponse(self.get_body(environ, scope), self.code, headers)
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
"""Call the exception as WSGI application.
|
||||
|
||||
:param environ: the WSGI environment.
|
||||
:param start_response: the response callable provided by the WSGI
|
||||
server.
|
||||
"""
|
||||
response = t.cast("WSGIResponse", self.get_response(environ))
|
||||
return response(environ, start_response)
|
||||
|
||||
def __str__(self) -> str:
|
||||
code = self.code if self.code is not None else "???"
|
||||
return f"{code} {self.name}: {self.description}"
|
||||
|
||||
def __repr__(self) -> str:
|
||||
code = self.code if self.code is not None else "???"
|
||||
return f"<{type(self).__name__} '{code}: {self.name}'>"
|
||||
|
||||
|
||||
class BadRequest(HTTPException):
|
||||
"""*400* `Bad Request`
|
||||
|
||||
Raise if the browser sends something to the application the application
|
||||
or server cannot handle.
|
||||
"""
|
||||
|
||||
code = 400
|
||||
description = (
|
||||
"The browser (or proxy) sent a request that this server could "
|
||||
"not understand."
|
||||
)
|
||||
|
||||
|
||||
class BadRequestKeyError(BadRequest, KeyError):
|
||||
"""An exception that is used to signal both a :exc:`KeyError` and a
|
||||
:exc:`BadRequest`. Used by many of the datastructures.
|
||||
"""
|
||||
|
||||
_description = BadRequest.description
|
||||
#: Show the KeyError along with the HTTP error message in the
|
||||
#: response. This should be disabled in production, but can be
|
||||
#: useful in a debug mode.
|
||||
show_exception = False
|
||||
|
||||
def __init__(self, arg: t.Optional[str] = None, *args: t.Any, **kwargs: t.Any):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if arg is None:
|
||||
KeyError.__init__(self)
|
||||
else:
|
||||
KeyError.__init__(self, arg)
|
||||
|
||||
@property # type: ignore
|
||||
def description(self) -> str: # type: ignore
|
||||
if self.show_exception:
|
||||
return (
|
||||
f"{self._description}\n"
|
||||
f"{KeyError.__name__}: {KeyError.__str__(self)}"
|
||||
)
|
||||
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str) -> None:
|
||||
self._description = value
|
||||
|
||||
|
||||
class ClientDisconnected(BadRequest):
|
||||
"""Internal exception that is raised if Werkzeug detects a disconnected
|
||||
client. Since the client is already gone at that point attempting to
|
||||
send the error message to the client might not work and might ultimately
|
||||
result in another exception in the server. Mainly this is here so that
|
||||
it is silenced by default as far as Werkzeug is concerned.
|
||||
|
||||
Since disconnections cannot be reliably detected and are unspecified
|
||||
by WSGI to a large extent this might or might not be raised if a client
|
||||
is gone.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
|
||||
|
||||
class SecurityError(BadRequest):
|
||||
"""Raised if something triggers a security error. This is otherwise
|
||||
exactly like a bad request error.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
|
||||
|
||||
class BadHost(BadRequest):
|
||||
"""Raised if the submitted host is badly formatted.
|
||||
|
||||
.. versionadded:: 0.11.2
|
||||
"""
|
||||
|
||||
|
||||
class Unauthorized(HTTPException):
|
||||
"""*401* ``Unauthorized``
|
||||
|
||||
Raise if the user is not authorized to access a resource.
|
||||
|
||||
The ``www_authenticate`` argument should be used to set the
|
||||
``WWW-Authenticate`` header. This is used for HTTP basic auth and
|
||||
other schemes. Use :class:`~werkzeug.datastructures.WWWAuthenticate`
|
||||
to create correctly formatted values. Strictly speaking a 401
|
||||
response is invalid if it doesn't provide at least one value for
|
||||
this header, although real clients typically don't care.
|
||||
|
||||
:param description: Override the default message used for the body
|
||||
of the response.
|
||||
:param www-authenticate: A single value, or list of values, for the
|
||||
WWW-Authenticate header(s).
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Serialize multiple ``www_authenticate`` items into multiple
|
||||
``WWW-Authenticate`` headers, rather than joining them
|
||||
into a single value, for better interoperability.
|
||||
|
||||
.. versionchanged:: 0.15.3
|
||||
If the ``www_authenticate`` argument is not set, the
|
||||
``WWW-Authenticate`` header is not set.
|
||||
|
||||
.. versionchanged:: 0.15.3
|
||||
The ``response`` argument was restored.
|
||||
|
||||
.. versionchanged:: 0.15.1
|
||||
``description`` was moved back as the first argument, restoring
|
||||
its previous position.
|
||||
|
||||
.. versionchanged:: 0.15.0
|
||||
``www_authenticate`` was added as the first argument, ahead of
|
||||
``description``.
|
||||
"""
|
||||
|
||||
code = 401
|
||||
description = (
|
||||
"The server could not verify that you are authorized to access"
|
||||
" the URL requested. You either supplied the wrong credentials"
|
||||
" (e.g. a bad password), or your browser doesn't understand"
|
||||
" how to supply the credentials required."
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
www_authenticate: t.Optional[
|
||||
t.Union["WWWAuthenticate", t.Iterable["WWWAuthenticate"]]
|
||||
] = None,
|
||||
) -> None:
|
||||
super().__init__(description, response)
|
||||
|
||||
from .datastructures import WWWAuthenticate
|
||||
|
||||
if isinstance(www_authenticate, WWWAuthenticate):
|
||||
www_authenticate = (www_authenticate,)
|
||||
|
||||
self.www_authenticate = www_authenticate
|
||||
|
||||
def get_headers(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> t.List[t.Tuple[str, str]]:
|
||||
headers = super().get_headers(environ, scope)
|
||||
if self.www_authenticate:
|
||||
headers.extend(("WWW-Authenticate", str(x)) for x in self.www_authenticate)
|
||||
return headers
|
||||
|
||||
|
||||
class Forbidden(HTTPException):
|
||||
"""*403* `Forbidden`
|
||||
|
||||
Raise if the user doesn't have the permission for the requested resource
|
||||
but was authenticated.
|
||||
"""
|
||||
|
||||
code = 403
|
||||
description = (
|
||||
"You don't have the permission to access the requested"
|
||||
" resource. It is either read-protected or not readable by the"
|
||||
" server."
|
||||
)
|
||||
|
||||
|
||||
class NotFound(HTTPException):
|
||||
"""*404* `Not Found`
|
||||
|
||||
Raise if a resource does not exist and never existed.
|
||||
"""
|
||||
|
||||
code = 404
|
||||
description = (
|
||||
"The requested URL was not found on the server. If you entered"
|
||||
" the URL manually please check your spelling and try again."
|
||||
)
|
||||
|
||||
|
||||
class MethodNotAllowed(HTTPException):
|
||||
"""*405* `Method Not Allowed`
|
||||
|
||||
Raise if the server used a method the resource does not handle. For
|
||||
example `POST` if the resource is view only. Especially useful for REST.
|
||||
|
||||
The first argument for this exception should be a list of allowed methods.
|
||||
Strictly speaking the response would be invalid if you don't provide valid
|
||||
methods in the header which you can do with that list.
|
||||
"""
|
||||
|
||||
code = 405
|
||||
description = "The method is not allowed for the requested URL."
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
valid_methods: t.Optional[t.Iterable[str]] = None,
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
) -> None:
|
||||
"""Takes an optional list of valid http methods
|
||||
starting with werkzeug 0.3 the list will be mandatory."""
|
||||
super().__init__(description=description, response=response)
|
||||
self.valid_methods = valid_methods
|
||||
|
||||
def get_headers(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> t.List[t.Tuple[str, str]]:
|
||||
headers = super().get_headers(environ, scope)
|
||||
if self.valid_methods:
|
||||
headers.append(("Allow", ", ".join(self.valid_methods)))
|
||||
return headers
|
||||
|
||||
|
||||
class NotAcceptable(HTTPException):
|
||||
"""*406* `Not Acceptable`
|
||||
|
||||
Raise if the server can't return any content conforming to the
|
||||
`Accept` headers of the client.
|
||||
"""
|
||||
|
||||
code = 406
|
||||
description = (
|
||||
"The resource identified by the request is only capable of"
|
||||
" generating response entities which have content"
|
||||
" characteristics not acceptable according to the accept"
|
||||
" headers sent in the request."
|
||||
)
|
||||
|
||||
|
||||
class RequestTimeout(HTTPException):
|
||||
"""*408* `Request Timeout`
|
||||
|
||||
Raise to signalize a timeout.
|
||||
"""
|
||||
|
||||
code = 408
|
||||
description = (
|
||||
"The server closed the network connection because the browser"
|
||||
" didn't finish the request within the specified time."
|
||||
)
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
"""*409* `Conflict`
|
||||
|
||||
Raise to signal that a request cannot be completed because it conflicts
|
||||
with the current state on the server.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
code = 409
|
||||
description = (
|
||||
"A conflict happened while processing the request. The"
|
||||
" resource might have been modified while the request was being"
|
||||
" processed."
|
||||
)
|
||||
|
||||
|
||||
class Gone(HTTPException):
|
||||
"""*410* `Gone`
|
||||
|
||||
Raise if a resource existed previously and went away without new location.
|
||||
"""
|
||||
|
||||
code = 410
|
||||
description = (
|
||||
"The requested URL is no longer available on this server and"
|
||||
" there is no forwarding address. If you followed a link from a"
|
||||
" foreign page, please contact the author of this page."
|
||||
)
|
||||
|
||||
|
||||
class LengthRequired(HTTPException):
|
||||
"""*411* `Length Required`
|
||||
|
||||
Raise if the browser submitted data but no ``Content-Length`` header which
|
||||
is required for the kind of processing the server does.
|
||||
"""
|
||||
|
||||
code = 411
|
||||
description = (
|
||||
"A request with this method requires a valid <code>Content-"
|
||||
"Length</code> header."
|
||||
)
|
||||
|
||||
|
||||
class PreconditionFailed(HTTPException):
|
||||
"""*412* `Precondition Failed`
|
||||
|
||||
Status code used in combination with ``If-Match``, ``If-None-Match``, or
|
||||
``If-Unmodified-Since``.
|
||||
"""
|
||||
|
||||
code = 412
|
||||
description = (
|
||||
"The precondition on the request for the URL failed positive evaluation."
|
||||
)
|
||||
|
||||
|
||||
class RequestEntityTooLarge(HTTPException):
|
||||
"""*413* `Request Entity Too Large`
|
||||
|
||||
The status code one should return if the data submitted exceeded a given
|
||||
limit.
|
||||
"""
|
||||
|
||||
code = 413
|
||||
description = "The data value transmitted exceeds the capacity limit."
|
||||
|
||||
|
||||
class RequestURITooLarge(HTTPException):
|
||||
"""*414* `Request URI Too Large`
|
||||
|
||||
Like *413* but for too long URLs.
|
||||
"""
|
||||
|
||||
code = 414
|
||||
description = (
|
||||
"The length of the requested URL exceeds the capacity limit for"
|
||||
" this server. The request cannot be processed."
|
||||
)
|
||||
|
||||
|
||||
class UnsupportedMediaType(HTTPException):
|
||||
"""*415* `Unsupported Media Type`
|
||||
|
||||
The status code returned if the server is unable to handle the media type
|
||||
the client transmitted.
|
||||
"""
|
||||
|
||||
code = 415
|
||||
description = (
|
||||
"The server does not support the media type transmitted in the request."
|
||||
)
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPException):
|
||||
"""*416* `Requested Range Not Satisfiable`
|
||||
|
||||
The client asked for an invalid part of the file.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
code = 416
|
||||
description = "The server cannot provide the requested range."
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
length: t.Optional[int] = None,
|
||||
units: str = "bytes",
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
) -> None:
|
||||
"""Takes an optional `Content-Range` header value based on ``length``
|
||||
parameter.
|
||||
"""
|
||||
super().__init__(description=description, response=response)
|
||||
self.length = length
|
||||
self.units = units
|
||||
|
||||
def get_headers(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> t.List[t.Tuple[str, str]]:
|
||||
headers = super().get_headers(environ, scope)
|
||||
if self.length is not None:
|
||||
headers.append(("Content-Range", f"{self.units} */{self.length}"))
|
||||
return headers
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPException):
|
||||
"""*417* `Expectation Failed`
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
code = 417
|
||||
description = "The server could not meet the requirements of the Expect header"
|
||||
|
||||
|
||||
class ImATeapot(HTTPException):
|
||||
"""*418* `I'm a teapot`
|
||||
|
||||
The server should return this if it is a teapot and someone attempted
|
||||
to brew coffee with it.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
code = 418
|
||||
description = "This server is a teapot, not a coffee machine"
|
||||
|
||||
|
||||
class UnprocessableEntity(HTTPException):
|
||||
"""*422* `Unprocessable Entity`
|
||||
|
||||
Used if the request is well formed, but the instructions are otherwise
|
||||
incorrect.
|
||||
"""
|
||||
|
||||
code = 422
|
||||
description = (
|
||||
"The request was well-formed but was unable to be followed due"
|
||||
" to semantic errors."
|
||||
)
|
||||
|
||||
|
||||
class Locked(HTTPException):
|
||||
"""*423* `Locked`
|
||||
|
||||
Used if the resource that is being accessed is locked.
|
||||
"""
|
||||
|
||||
code = 423
|
||||
description = "The resource that is being accessed is locked."
|
||||
|
||||
|
||||
class FailedDependency(HTTPException):
|
||||
"""*424* `Failed Dependency`
|
||||
|
||||
Used if the method could not be performed on the resource
|
||||
because the requested action depended on another action and that action failed.
|
||||
"""
|
||||
|
||||
code = 424
|
||||
description = (
|
||||
"The method could not be performed on the resource because the"
|
||||
" requested action depended on another action and that action"
|
||||
" failed."
|
||||
)
|
||||
|
||||
|
||||
class PreconditionRequired(HTTPException):
|
||||
"""*428* `Precondition Required`
|
||||
|
||||
The server requires this request to be conditional, typically to prevent
|
||||
the lost update problem, which is a race condition between two or more
|
||||
clients attempting to update a resource through PUT or DELETE. By requiring
|
||||
each client to include a conditional header ("If-Match" or "If-Unmodified-
|
||||
Since") with the proper value retained from a recent GET request, the
|
||||
server ensures that each client has at least seen the previous revision of
|
||||
the resource.
|
||||
"""
|
||||
|
||||
code = 428
|
||||
description = (
|
||||
"This request is required to be conditional; try using"
|
||||
' "If-Match" or "If-Unmodified-Since".'
|
||||
)
|
||||
|
||||
|
||||
class _RetryAfter(HTTPException):
|
||||
"""Adds an optional ``retry_after`` parameter which will set the
|
||||
``Retry-After`` header. May be an :class:`int` number of seconds or
|
||||
a :class:`~datetime.datetime`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
retry_after: t.Optional[t.Union[datetime, int]] = None,
|
||||
) -> None:
|
||||
super().__init__(description, response)
|
||||
self.retry_after = retry_after
|
||||
|
||||
def get_headers(
|
||||
self,
|
||||
environ: t.Optional["WSGIEnvironment"] = None,
|
||||
scope: t.Optional[dict] = None,
|
||||
) -> t.List[t.Tuple[str, str]]:
|
||||
headers = super().get_headers(environ, scope)
|
||||
|
||||
if self.retry_after:
|
||||
if isinstance(self.retry_after, datetime):
|
||||
from .http import http_date
|
||||
|
||||
value = http_date(self.retry_after)
|
||||
else:
|
||||
value = str(self.retry_after)
|
||||
|
||||
headers.append(("Retry-After", value))
|
||||
|
||||
return headers
|
||||
|
||||
|
||||
class TooManyRequests(_RetryAfter):
|
||||
"""*429* `Too Many Requests`
|
||||
|
||||
The server is limiting the rate at which this user receives
|
||||
responses, and this request exceeds that rate. (The server may use
|
||||
any convenient method to identify users and their request rates).
|
||||
The server may include a "Retry-After" header to indicate how long
|
||||
the user should wait before retrying.
|
||||
|
||||
:param retry_after: If given, set the ``Retry-After`` header to this
|
||||
value. May be an :class:`int` number of seconds or a
|
||||
:class:`~datetime.datetime`.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Added ``retry_after`` parameter.
|
||||
"""
|
||||
|
||||
code = 429
|
||||
description = "This user has exceeded an allotted request count. Try again later."
|
||||
|
||||
|
||||
class RequestHeaderFieldsTooLarge(HTTPException):
|
||||
"""*431* `Request Header Fields Too Large`
|
||||
|
||||
The server refuses to process the request because the header fields are too
|
||||
large. One or more individual fields may be too large, or the set of all
|
||||
headers is too large.
|
||||
"""
|
||||
|
||||
code = 431
|
||||
description = "One or more header fields exceeds the maximum size."
|
||||
|
||||
|
||||
class UnavailableForLegalReasons(HTTPException):
|
||||
"""*451* `Unavailable For Legal Reasons`
|
||||
|
||||
This status code indicates that the server is denying access to the
|
||||
resource as a consequence of a legal demand.
|
||||
"""
|
||||
|
||||
code = 451
|
||||
description = "Unavailable for legal reasons."
|
||||
|
||||
|
||||
class InternalServerError(HTTPException):
|
||||
"""*500* `Internal Server Error`
|
||||
|
||||
Raise if an internal server error occurred. This is a good fallback if an
|
||||
unknown error occurred in the dispatcher.
|
||||
|
||||
.. versionchanged:: 1.0.0
|
||||
Added the :attr:`original_exception` attribute.
|
||||
"""
|
||||
|
||||
code = 500
|
||||
description = (
|
||||
"The server encountered an internal error and was unable to"
|
||||
" complete your request. Either the server is overloaded or"
|
||||
" there is an error in the application."
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
description: t.Optional[str] = None,
|
||||
response: t.Optional["Response"] = None,
|
||||
original_exception: t.Optional[BaseException] = None,
|
||||
) -> None:
|
||||
#: The original exception that caused this 500 error. Can be
|
||||
#: used by frameworks to provide context when handling
|
||||
#: unexpected errors.
|
||||
self.original_exception = original_exception
|
||||
super().__init__(description=description, response=response)
|
||||
|
||||
|
||||
class NotImplemented(HTTPException):
|
||||
"""*501* `Not Implemented`
|
||||
|
||||
Raise if the application does not support the action requested by the
|
||||
browser.
|
||||
"""
|
||||
|
||||
code = 501
|
||||
description = "The server does not support the action requested by the browser."
|
||||
|
||||
|
||||
class BadGateway(HTTPException):
|
||||
"""*502* `Bad Gateway`
|
||||
|
||||
If you do proxying in your application you should return this status code
|
||||
if you received an invalid response from the upstream server it accessed
|
||||
in attempting to fulfill the request.
|
||||
"""
|
||||
|
||||
code = 502
|
||||
description = (
|
||||
"The proxy server received an invalid response from an upstream server."
|
||||
)
|
||||
|
||||
|
||||
class ServiceUnavailable(_RetryAfter):
|
||||
"""*503* `Service Unavailable`
|
||||
|
||||
Status code you should return if a service is temporarily
|
||||
unavailable.
|
||||
|
||||
:param retry_after: If given, set the ``Retry-After`` header to this
|
||||
value. May be an :class:`int` number of seconds or a
|
||||
:class:`~datetime.datetime`.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Added ``retry_after`` parameter.
|
||||
"""
|
||||
|
||||
code = 503
|
||||
description = (
|
||||
"The server is temporarily unable to service your request due"
|
||||
" to maintenance downtime or capacity problems. Please try"
|
||||
" again later."
|
||||
)
|
||||
|
||||
|
||||
class GatewayTimeout(HTTPException):
|
||||
"""*504* `Gateway Timeout`
|
||||
|
||||
Status code you should return if a connection to an upstream server
|
||||
times out.
|
||||
"""
|
||||
|
||||
code = 504
|
||||
description = "The connection to an upstream server timed out."
|
||||
|
||||
|
||||
class HTTPVersionNotSupported(HTTPException):
|
||||
"""*505* `HTTP Version Not Supported`
|
||||
|
||||
The server does not support the HTTP protocol version used in the request.
|
||||
"""
|
||||
|
||||
code = 505
|
||||
description = (
|
||||
"The server does not support the HTTP protocol version used in the request."
|
||||
)
|
||||
|
||||
|
||||
default_exceptions: t.Dict[int, t.Type[HTTPException]] = {}
|
||||
|
||||
|
||||
def _find_exceptions() -> None:
|
||||
for obj in globals().values():
|
||||
try:
|
||||
is_http_exception = issubclass(obj, HTTPException)
|
||||
except TypeError:
|
||||
is_http_exception = False
|
||||
if not is_http_exception or obj.code is None:
|
||||
continue
|
||||
old_obj = default_exceptions.get(obj.code, None)
|
||||
if old_obj is not None and issubclass(obj, old_obj):
|
||||
continue
|
||||
default_exceptions[obj.code] = obj
|
||||
|
||||
|
||||
_find_exceptions()
|
||||
del _find_exceptions
|
||||
|
||||
|
||||
class Aborter:
|
||||
"""When passed a dict of code -> exception items it can be used as
|
||||
callable that raises exceptions. If the first argument to the
|
||||
callable is an integer it will be looked up in the mapping, if it's
|
||||
a WSGI application it will be raised in a proxy exception.
|
||||
|
||||
The rest of the arguments are forwarded to the exception constructor.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mapping: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
|
||||
extra: t.Optional[t.Dict[int, t.Type[HTTPException]]] = None,
|
||||
) -> None:
|
||||
if mapping is None:
|
||||
mapping = default_exceptions
|
||||
self.mapping = dict(mapping)
|
||||
if extra is not None:
|
||||
self.mapping.update(extra)
|
||||
|
||||
def __call__(
|
||||
self, code: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
|
||||
) -> "te.NoReturn":
|
||||
from .sansio.response import Response
|
||||
|
||||
if isinstance(code, Response):
|
||||
raise HTTPException(response=code)
|
||||
|
||||
if code not in self.mapping:
|
||||
raise LookupError(f"no exception for {code!r}")
|
||||
|
||||
raise self.mapping[code](*args, **kwargs)
|
||||
|
||||
|
||||
def abort(
|
||||
status: t.Union[int, "Response"], *args: t.Any, **kwargs: t.Any
|
||||
) -> "te.NoReturn":
|
||||
"""Raises an :py:exc:`HTTPException` for the given status code or WSGI
|
||||
application.
|
||||
|
||||
If a status code is given, it will be looked up in the list of
|
||||
exceptions and will raise that exception. If passed a WSGI application,
|
||||
it will wrap it in a proxy WSGI exception and raise that::
|
||||
|
||||
abort(404) # 404 Not Found
|
||||
abort(Response('Hello World'))
|
||||
|
||||
"""
|
||||
_aborter(status, *args, **kwargs)
|
||||
|
||||
|
||||
_aborter: Aborter = Aborter()
|
||||
55
myvenv/lib/python3.10/site-packages/werkzeug/filesystem.py
Normal file
55
myvenv/lib/python3.10/site-packages/werkzeug/filesystem.py
Normal file
@ -0,0 +1,55 @@
|
||||
import codecs
|
||||
import sys
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
# We do not trust traditional unixes.
|
||||
has_likely_buggy_unicode_filesystem = (
|
||||
sys.platform.startswith("linux") or "bsd" in sys.platform
|
||||
)
|
||||
|
||||
|
||||
def _is_ascii_encoding(encoding: t.Optional[str]) -> bool:
|
||||
"""Given an encoding this figures out if the encoding is actually ASCII (which
|
||||
is something we don't actually want in most cases). This is necessary
|
||||
because ASCII comes under many names such as ANSI_X3.4-1968.
|
||||
"""
|
||||
if encoding is None:
|
||||
return False
|
||||
try:
|
||||
return codecs.lookup(encoding).name == "ascii"
|
||||
except LookupError:
|
||||
return False
|
||||
|
||||
|
||||
class BrokenFilesystemWarning(RuntimeWarning, UnicodeWarning):
|
||||
"""The warning used by Werkzeug to signal a broken filesystem. Will only be
|
||||
used once per runtime."""
|
||||
|
||||
|
||||
_warned_about_filesystem_encoding = False
|
||||
|
||||
|
||||
def get_filesystem_encoding() -> str:
|
||||
"""Returns the filesystem encoding that should be used. Note that this is
|
||||
different from the Python understanding of the filesystem encoding which
|
||||
might be deeply flawed. Do not use this value against Python's string APIs
|
||||
because it might be different. See :ref:`filesystem-encoding` for the exact
|
||||
behavior.
|
||||
|
||||
The concept of a filesystem encoding in generally is not something you
|
||||
should rely on. As such if you ever need to use this function except for
|
||||
writing wrapper code reconsider.
|
||||
"""
|
||||
global _warned_about_filesystem_encoding
|
||||
rv = sys.getfilesystemencoding()
|
||||
if has_likely_buggy_unicode_filesystem and not rv or _is_ascii_encoding(rv):
|
||||
if not _warned_about_filesystem_encoding:
|
||||
warnings.warn(
|
||||
"Detected a misconfigured UNIX filesystem: Will use"
|
||||
f" UTF-8 as filesystem encoding instead of {rv!r}",
|
||||
BrokenFilesystemWarning,
|
||||
)
|
||||
_warned_about_filesystem_encoding = True
|
||||
return "utf-8"
|
||||
return rv
|
||||
495
myvenv/lib/python3.10/site-packages/werkzeug/formparser.py
Normal file
495
myvenv/lib/python3.10/site-packages/werkzeug/formparser.py
Normal file
@ -0,0 +1,495 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
from functools import update_wrapper
|
||||
from io import BytesIO
|
||||
from itertools import chain
|
||||
from typing import Union
|
||||
|
||||
from . import exceptions
|
||||
from ._internal import _to_str
|
||||
from .datastructures import FileStorage
|
||||
from .datastructures import Headers
|
||||
from .datastructures import MultiDict
|
||||
from .http import parse_options_header
|
||||
from .sansio.multipart import Data
|
||||
from .sansio.multipart import Epilogue
|
||||
from .sansio.multipart import Field
|
||||
from .sansio.multipart import File
|
||||
from .sansio.multipart import MultipartDecoder
|
||||
from .sansio.multipart import NeedData
|
||||
from .urls import url_decode_stream
|
||||
from .wsgi import _make_chunk_iter
|
||||
from .wsgi import get_content_length
|
||||
from .wsgi import get_input_stream
|
||||
|
||||
# there are some platforms where SpooledTemporaryFile is not available.
|
||||
# In that case we need to provide a fallback.
|
||||
try:
|
||||
from tempfile import SpooledTemporaryFile
|
||||
except ImportError:
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
SpooledTemporaryFile = None # type: ignore
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing as te
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
t_parse_result = t.Tuple[t.IO[bytes], MultiDict, MultiDict]
|
||||
|
||||
class TStreamFactory(te.Protocol):
|
||||
def __call__(
|
||||
self,
|
||||
total_content_length: t.Optional[int],
|
||||
content_type: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
content_length: t.Optional[int] = None,
|
||||
) -> t.IO[bytes]:
|
||||
...
|
||||
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
|
||||
def _exhaust(stream: t.IO[bytes]) -> None:
|
||||
bts = stream.read(64 * 1024)
|
||||
while bts:
|
||||
bts = stream.read(64 * 1024)
|
||||
|
||||
|
||||
def default_stream_factory(
|
||||
total_content_length: t.Optional[int],
|
||||
content_type: t.Optional[str],
|
||||
filename: t.Optional[str],
|
||||
content_length: t.Optional[int] = None,
|
||||
) -> t.IO[bytes]:
|
||||
max_size = 1024 * 500
|
||||
|
||||
if SpooledTemporaryFile is not None:
|
||||
return t.cast(t.IO[bytes], SpooledTemporaryFile(max_size=max_size, mode="rb+"))
|
||||
elif total_content_length is None or total_content_length > max_size:
|
||||
return t.cast(t.IO[bytes], TemporaryFile("rb+"))
|
||||
|
||||
return BytesIO()
|
||||
|
||||
|
||||
def parse_form_data(
|
||||
environ: "WSGIEnvironment",
|
||||
stream_factory: t.Optional["TStreamFactory"] = None,
|
||||
charset: str = "utf-8",
|
||||
errors: str = "replace",
|
||||
max_form_memory_size: t.Optional[int] = None,
|
||||
max_content_length: t.Optional[int] = None,
|
||||
cls: t.Optional[t.Type[MultiDict]] = None,
|
||||
silent: bool = True,
|
||||
) -> "t_parse_result":
|
||||
"""Parse the form data in the environ and return it as tuple in the form
|
||||
``(stream, form, files)``. You should only call this method if the
|
||||
transport method is `POST`, `PUT`, or `PATCH`.
|
||||
|
||||
If the mimetype of the data transmitted is `multipart/form-data` the
|
||||
files multidict will be filled with `FileStorage` objects. If the
|
||||
mimetype is unknown the input stream is wrapped and returned as first
|
||||
argument, else the stream is empty.
|
||||
|
||||
This is a shortcut for the common usage of :class:`FormDataParser`.
|
||||
|
||||
Have a look at :doc:`/request_data` for more details.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
The `max_form_memory_size`, `max_content_length` and
|
||||
`cls` parameters were added.
|
||||
|
||||
.. versionadded:: 0.5.1
|
||||
The optional `silent` flag was added.
|
||||
|
||||
:param environ: the WSGI environment to be used for parsing.
|
||||
:param stream_factory: An optional callable that returns a new read and
|
||||
writeable file descriptor. This callable works
|
||||
the same as :meth:`Response._get_file_stream`.
|
||||
:param charset: The character set for URL and url encoded form data.
|
||||
:param errors: The encoding error behavior.
|
||||
:param max_form_memory_size: the maximum number of bytes to be accepted for
|
||||
in-memory stored form data. If the data
|
||||
exceeds the value specified an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param max_content_length: If this is provided and the transmitted data
|
||||
is longer than this value an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`MultiDict` is used.
|
||||
:param silent: If set to False parsing errors will not be caught.
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
return FormDataParser(
|
||||
stream_factory,
|
||||
charset,
|
||||
errors,
|
||||
max_form_memory_size,
|
||||
max_content_length,
|
||||
cls,
|
||||
silent,
|
||||
).parse_from_environ(environ)
|
||||
|
||||
|
||||
def exhaust_stream(f: F) -> F:
|
||||
"""Helper decorator for methods that exhausts the stream on return."""
|
||||
|
||||
def wrapper(self, stream, *args, **kwargs): # type: ignore
|
||||
try:
|
||||
return f(self, stream, *args, **kwargs)
|
||||
finally:
|
||||
exhaust = getattr(stream, "exhaust", None)
|
||||
|
||||
if exhaust is not None:
|
||||
exhaust()
|
||||
else:
|
||||
while True:
|
||||
chunk = stream.read(1024 * 64)
|
||||
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
return update_wrapper(t.cast(F, wrapper), f)
|
||||
|
||||
|
||||
class FormDataParser:
|
||||
"""This class implements parsing of form data for Werkzeug. By itself
|
||||
it can parse multipart and url encoded form data. It can be subclassed
|
||||
and extended but for most mimetypes it is a better idea to use the
|
||||
untouched stream and expose it as separate attributes on a request
|
||||
object.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
:param stream_factory: An optional callable that returns a new read and
|
||||
writeable file descriptor. This callable works
|
||||
the same as :meth:`Response._get_file_stream`.
|
||||
:param charset: The character set for URL and url encoded form data.
|
||||
:param errors: The encoding error behavior.
|
||||
:param max_form_memory_size: the maximum number of bytes to be accepted for
|
||||
in-memory stored form data. If the data
|
||||
exceeds the value specified an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param max_content_length: If this is provided and the transmitted data
|
||||
is longer than this value an
|
||||
:exc:`~exceptions.RequestEntityTooLarge`
|
||||
exception is raised.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`MultiDict` is used.
|
||||
:param silent: If set to False parsing errors will not be caught.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
stream_factory: t.Optional["TStreamFactory"] = None,
|
||||
charset: str = "utf-8",
|
||||
errors: str = "replace",
|
||||
max_form_memory_size: t.Optional[int] = None,
|
||||
max_content_length: t.Optional[int] = None,
|
||||
cls: t.Optional[t.Type[MultiDict]] = None,
|
||||
silent: bool = True,
|
||||
) -> None:
|
||||
if stream_factory is None:
|
||||
stream_factory = default_stream_factory
|
||||
|
||||
self.stream_factory = stream_factory
|
||||
self.charset = charset
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.max_content_length = max_content_length
|
||||
|
||||
if cls is None:
|
||||
cls = MultiDict
|
||||
|
||||
self.cls = cls
|
||||
self.silent = silent
|
||||
|
||||
def get_parse_func(
|
||||
self, mimetype: str, options: t.Dict[str, str]
|
||||
) -> t.Optional[
|
||||
t.Callable[
|
||||
["FormDataParser", t.IO[bytes], str, t.Optional[int], t.Dict[str, str]],
|
||||
"t_parse_result",
|
||||
]
|
||||
]:
|
||||
return self.parse_functions.get(mimetype)
|
||||
|
||||
def parse_from_environ(self, environ: "WSGIEnvironment") -> "t_parse_result":
|
||||
"""Parses the information from the environment as form data.
|
||||
|
||||
:param environ: the WSGI environment to be used for parsing.
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
content_type = environ.get("CONTENT_TYPE", "")
|
||||
content_length = get_content_length(environ)
|
||||
mimetype, options = parse_options_header(content_type)
|
||||
return self.parse(get_input_stream(environ), mimetype, content_length, options)
|
||||
|
||||
def parse(
|
||||
self,
|
||||
stream: t.IO[bytes],
|
||||
mimetype: str,
|
||||
content_length: t.Optional[int],
|
||||
options: t.Optional[t.Dict[str, str]] = None,
|
||||
) -> "t_parse_result":
|
||||
"""Parses the information from the given stream, mimetype,
|
||||
content length and mimetype parameters.
|
||||
|
||||
:param stream: an input stream
|
||||
:param mimetype: the mimetype of the data
|
||||
:param content_length: the content length of the incoming data
|
||||
:param options: optional mimetype parameters (used for
|
||||
the multipart boundary for instance)
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
"""
|
||||
if (
|
||||
self.max_content_length is not None
|
||||
and content_length is not None
|
||||
and content_length > self.max_content_length
|
||||
):
|
||||
# if the input stream is not exhausted, firefox reports Connection Reset
|
||||
_exhaust(stream)
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
|
||||
if options is None:
|
||||
options = {}
|
||||
|
||||
parse_func = self.get_parse_func(mimetype, options)
|
||||
|
||||
if parse_func is not None:
|
||||
try:
|
||||
return parse_func(self, stream, mimetype, content_length, options)
|
||||
except ValueError:
|
||||
if not self.silent:
|
||||
raise
|
||||
|
||||
return stream, self.cls(), self.cls()
|
||||
|
||||
@exhaust_stream
|
||||
def _parse_multipart(
|
||||
self,
|
||||
stream: t.IO[bytes],
|
||||
mimetype: str,
|
||||
content_length: t.Optional[int],
|
||||
options: t.Dict[str, str],
|
||||
) -> "t_parse_result":
|
||||
parser = MultiPartParser(
|
||||
self.stream_factory,
|
||||
self.charset,
|
||||
self.errors,
|
||||
max_form_memory_size=self.max_form_memory_size,
|
||||
cls=self.cls,
|
||||
)
|
||||
boundary = options.get("boundary", "").encode("ascii")
|
||||
|
||||
if not boundary:
|
||||
raise ValueError("Missing boundary")
|
||||
|
||||
form, files = parser.parse(stream, boundary, content_length)
|
||||
return stream, form, files
|
||||
|
||||
@exhaust_stream
|
||||
def _parse_urlencoded(
|
||||
self,
|
||||
stream: t.IO[bytes],
|
||||
mimetype: str,
|
||||
content_length: t.Optional[int],
|
||||
options: t.Dict[str, str],
|
||||
) -> "t_parse_result":
|
||||
if (
|
||||
self.max_form_memory_size is not None
|
||||
and content_length is not None
|
||||
and content_length > self.max_form_memory_size
|
||||
):
|
||||
# if the input stream is not exhausted, firefox reports Connection Reset
|
||||
_exhaust(stream)
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
|
||||
form = url_decode_stream(stream, self.charset, errors=self.errors, cls=self.cls)
|
||||
return stream, form, self.cls()
|
||||
|
||||
#: mapping of mimetypes to parsing functions
|
||||
parse_functions: t.Dict[
|
||||
str,
|
||||
t.Callable[
|
||||
["FormDataParser", t.IO[bytes], str, t.Optional[int], t.Dict[str, str]],
|
||||
"t_parse_result",
|
||||
],
|
||||
] = {
|
||||
"multipart/form-data": _parse_multipart,
|
||||
"application/x-www-form-urlencoded": _parse_urlencoded,
|
||||
"application/x-url-encoded": _parse_urlencoded,
|
||||
}
|
||||
|
||||
|
||||
def _line_parse(line: str) -> t.Tuple[str, bool]:
|
||||
"""Removes line ending characters and returns a tuple (`stripped_line`,
|
||||
`is_terminated`).
|
||||
"""
|
||||
if line[-2:] == "\r\n":
|
||||
return line[:-2], True
|
||||
|
||||
elif line[-1:] in {"\r", "\n"}:
|
||||
return line[:-1], True
|
||||
|
||||
return line, False
|
||||
|
||||
|
||||
def parse_multipart_headers(iterable: t.Iterable[bytes]) -> Headers:
|
||||
"""Parses multipart headers from an iterable that yields lines (including
|
||||
the trailing newline symbol). The iterable has to be newline terminated.
|
||||
The iterable will stop at the line where the headers ended so it can be
|
||||
further consumed.
|
||||
:param iterable: iterable of strings that are newline terminated
|
||||
"""
|
||||
warnings.warn(
|
||||
"'parse_multipart_headers' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
result: t.List[t.Tuple[str, str]] = []
|
||||
|
||||
for b_line in iterable:
|
||||
line = _to_str(b_line)
|
||||
line, line_terminated = _line_parse(line)
|
||||
|
||||
if not line_terminated:
|
||||
raise ValueError("unexpected end of line in multipart header")
|
||||
|
||||
if not line:
|
||||
break
|
||||
elif line[0] in " \t" and result:
|
||||
key, value = result[-1]
|
||||
result[-1] = (key, f"{value}\n {line[1:]}")
|
||||
else:
|
||||
parts = line.split(":", 1)
|
||||
|
||||
if len(parts) == 2:
|
||||
result.append((parts[0].strip(), parts[1].strip()))
|
||||
|
||||
# we link the list to the headers, no need to create a copy, the
|
||||
# list was not shared anyways.
|
||||
return Headers(result)
|
||||
|
||||
|
||||
class MultiPartParser:
|
||||
def __init__(
|
||||
self,
|
||||
stream_factory: t.Optional["TStreamFactory"] = None,
|
||||
charset: str = "utf-8",
|
||||
errors: str = "replace",
|
||||
max_form_memory_size: t.Optional[int] = None,
|
||||
cls: t.Optional[t.Type[MultiDict]] = None,
|
||||
buffer_size: int = 64 * 1024,
|
||||
) -> None:
|
||||
self.charset = charset
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
|
||||
if stream_factory is None:
|
||||
stream_factory = default_stream_factory
|
||||
|
||||
self.stream_factory = stream_factory
|
||||
|
||||
if cls is None:
|
||||
cls = MultiDict
|
||||
|
||||
self.cls = cls
|
||||
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def fail(self, message: str) -> "te.NoReturn":
|
||||
raise ValueError(message)
|
||||
|
||||
def get_part_charset(self, headers: Headers) -> str:
|
||||
# Figure out input charset for current part
|
||||
content_type = headers.get("content-type")
|
||||
|
||||
if content_type:
|
||||
mimetype, ct_params = parse_options_header(content_type)
|
||||
return ct_params.get("charset", self.charset)
|
||||
|
||||
return self.charset
|
||||
|
||||
def start_file_streaming(
|
||||
self, event: File, total_content_length: t.Optional[int]
|
||||
) -> t.IO[bytes]:
|
||||
content_type = event.headers.get("content-type")
|
||||
|
||||
try:
|
||||
content_length = int(event.headers["content-length"])
|
||||
except (KeyError, ValueError):
|
||||
content_length = 0
|
||||
|
||||
container = self.stream_factory(
|
||||
total_content_length=total_content_length,
|
||||
filename=event.filename,
|
||||
content_type=content_type,
|
||||
content_length=content_length,
|
||||
)
|
||||
return container
|
||||
|
||||
def parse(
|
||||
self, stream: t.IO[bytes], boundary: bytes, content_length: t.Optional[int]
|
||||
) -> t.Tuple[MultiDict, MultiDict]:
|
||||
container: t.Union[t.IO[bytes], t.List[bytes]]
|
||||
_write: t.Callable[[bytes], t.Any]
|
||||
|
||||
iterator = chain(
|
||||
_make_chunk_iter(
|
||||
stream,
|
||||
limit=content_length,
|
||||
buffer_size=self.buffer_size,
|
||||
),
|
||||
[None],
|
||||
)
|
||||
|
||||
parser = MultipartDecoder(boundary, self.max_form_memory_size)
|
||||
|
||||
fields = []
|
||||
files = []
|
||||
|
||||
current_part: Union[Field, File]
|
||||
for data in iterator:
|
||||
parser.receive_data(data)
|
||||
event = parser.next_event()
|
||||
while not isinstance(event, (Epilogue, NeedData)):
|
||||
if isinstance(event, Field):
|
||||
current_part = event
|
||||
container = []
|
||||
_write = container.append
|
||||
elif isinstance(event, File):
|
||||
current_part = event
|
||||
container = self.start_file_streaming(event, content_length)
|
||||
_write = container.write
|
||||
elif isinstance(event, Data):
|
||||
_write(event.data)
|
||||
if not event.more_data:
|
||||
if isinstance(current_part, Field):
|
||||
value = b"".join(container).decode(
|
||||
self.get_part_charset(current_part.headers), self.errors
|
||||
)
|
||||
fields.append((current_part.name, value))
|
||||
else:
|
||||
container = t.cast(t.IO[bytes], container)
|
||||
container.seek(0)
|
||||
files.append(
|
||||
(
|
||||
current_part.name,
|
||||
FileStorage(
|
||||
container,
|
||||
current_part.filename,
|
||||
current_part.name,
|
||||
headers=current_part.headers,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
event = parser.next_event()
|
||||
|
||||
return self.cls(fields), self.cls(files)
|
||||
1393
myvenv/lib/python3.10/site-packages/werkzeug/http.py
Normal file
1393
myvenv/lib/python3.10/site-packages/werkzeug/http.py
Normal file
File diff suppressed because it is too large
Load Diff
690
myvenv/lib/python3.10/site-packages/werkzeug/local.py
Normal file
690
myvenv/lib/python3.10/site-packages/werkzeug/local.py
Normal file
@ -0,0 +1,690 @@
|
||||
import copy
|
||||
import math
|
||||
import operator
|
||||
import sys
|
||||
import typing as t
|
||||
import warnings
|
||||
from functools import partial
|
||||
from functools import update_wrapper
|
||||
|
||||
from .wsgi import ClosingIterator
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
F = t.TypeVar("F", bound=t.Callable[..., t.Any])
|
||||
|
||||
try:
|
||||
from greenlet import getcurrent as _get_ident
|
||||
except ImportError:
|
||||
from threading import get_ident as _get_ident
|
||||
|
||||
|
||||
def get_ident() -> int:
|
||||
warnings.warn(
|
||||
"'get_ident' is deprecated and will be removed in Werkzeug"
|
||||
" 2.1. Use 'greenlet.getcurrent' or 'threading.get_ident' for"
|
||||
" previous behavior.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _get_ident() # type: ignore
|
||||
|
||||
|
||||
class _CannotUseContextVar(Exception):
|
||||
pass
|
||||
|
||||
|
||||
try:
|
||||
from contextvars import ContextVar
|
||||
|
||||
if "gevent" in sys.modules or "eventlet" in sys.modules:
|
||||
# Both use greenlet, so first check it has patched
|
||||
# ContextVars, Greenlet <0.4.17 does not.
|
||||
import greenlet
|
||||
|
||||
greenlet_patched = getattr(greenlet, "GREENLET_USE_CONTEXT_VARS", False)
|
||||
|
||||
if not greenlet_patched:
|
||||
# If Gevent is used, check it has patched ContextVars,
|
||||
# <20.5 does not.
|
||||
try:
|
||||
from gevent.monkey import is_object_patched
|
||||
except ImportError:
|
||||
# Gevent isn't used, but Greenlet is and hasn't patched
|
||||
raise _CannotUseContextVar() from None
|
||||
else:
|
||||
if is_object_patched("threading", "local") and not is_object_patched(
|
||||
"contextvars", "ContextVar"
|
||||
):
|
||||
raise _CannotUseContextVar()
|
||||
|
||||
def __release_local__(storage: t.Any) -> None:
|
||||
# Can remove when support for non-stdlib ContextVars is
|
||||
# removed, see "Fake" version below.
|
||||
storage.set({})
|
||||
|
||||
except (ImportError, _CannotUseContextVar):
|
||||
|
||||
class ContextVar: # type: ignore
|
||||
"""A fake ContextVar based on the previous greenlet/threading
|
||||
ident function. Used on Python 3.6, eventlet, and old versions
|
||||
of gevent.
|
||||
"""
|
||||
|
||||
def __init__(self, _name: str) -> None:
|
||||
self.storage: t.Dict[int, t.Dict[str, t.Any]] = {}
|
||||
|
||||
def get(self, default: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
|
||||
return self.storage.get(_get_ident(), default)
|
||||
|
||||
def set(self, value: t.Dict[str, t.Any]) -> None:
|
||||
self.storage[_get_ident()] = value
|
||||
|
||||
def __release_local__(storage: t.Any) -> None:
|
||||
# Special version to ensure that the storage is cleaned up on
|
||||
# release.
|
||||
storage.storage.pop(_get_ident(), None)
|
||||
|
||||
|
||||
def release_local(local: t.Union["Local", "LocalStack"]) -> None:
|
||||
"""Releases the contents of the local for the current context.
|
||||
This makes it possible to use locals without a manager.
|
||||
|
||||
Example::
|
||||
|
||||
>>> loc = Local()
|
||||
>>> loc.foo = 42
|
||||
>>> release_local(loc)
|
||||
>>> hasattr(loc, 'foo')
|
||||
False
|
||||
|
||||
With this function one can release :class:`Local` objects as well
|
||||
as :class:`LocalStack` objects. However it is not possible to
|
||||
release data held by proxies that way, one always has to retain
|
||||
a reference to the underlying local object in order to be able
|
||||
to release it.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
"""
|
||||
local.__release_local__()
|
||||
|
||||
|
||||
class Local:
|
||||
__slots__ = ("_storage",)
|
||||
|
||||
def __init__(self) -> None:
|
||||
object.__setattr__(self, "_storage", ContextVar("local_storage"))
|
||||
|
||||
@property
|
||||
def __storage__(self) -> t.Dict[str, t.Any]:
|
||||
warnings.warn(
|
||||
"'__storage__' is deprecated and will be removed in Werkzeug 2.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._storage.get({}) # type: ignore
|
||||
|
||||
@property
|
||||
def __ident_func__(self) -> t.Callable[[], int]:
|
||||
warnings.warn(
|
||||
"'__ident_func__' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. It should not be used in Python 3.7+.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _get_ident # type: ignore
|
||||
|
||||
@__ident_func__.setter
|
||||
def __ident_func__(self, func: t.Callable[[], int]) -> None:
|
||||
warnings.warn(
|
||||
"'__ident_func__' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Setting it no longer has any effect.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def __iter__(self) -> t.Iterator[t.Tuple[int, t.Any]]:
|
||||
return iter(self._storage.get({}).items())
|
||||
|
||||
def __call__(self, proxy: str) -> "LocalProxy":
|
||||
"""Create a proxy for a name."""
|
||||
return LocalProxy(self, proxy)
|
||||
|
||||
def __release_local__(self) -> None:
|
||||
__release_local__(self._storage)
|
||||
|
||||
def __getattr__(self, name: str) -> t.Any:
|
||||
values = self._storage.get({})
|
||||
try:
|
||||
return values[name]
|
||||
except KeyError:
|
||||
raise AttributeError(name) from None
|
||||
|
||||
def __setattr__(self, name: str, value: t.Any) -> None:
|
||||
values = self._storage.get({}).copy()
|
||||
values[name] = value
|
||||
self._storage.set(values)
|
||||
|
||||
def __delattr__(self, name: str) -> None:
|
||||
values = self._storage.get({}).copy()
|
||||
try:
|
||||
del values[name]
|
||||
self._storage.set(values)
|
||||
except KeyError:
|
||||
raise AttributeError(name) from None
|
||||
|
||||
|
||||
class LocalStack:
|
||||
"""This class works similar to a :class:`Local` but keeps a stack
|
||||
of objects instead. This is best explained with an example::
|
||||
|
||||
>>> ls = LocalStack()
|
||||
>>> ls.push(42)
|
||||
>>> ls.top
|
||||
42
|
||||
>>> ls.push(23)
|
||||
>>> ls.top
|
||||
23
|
||||
>>> ls.pop()
|
||||
23
|
||||
>>> ls.top
|
||||
42
|
||||
|
||||
They can be force released by using a :class:`LocalManager` or with
|
||||
the :func:`release_local` function but the correct way is to pop the
|
||||
item from the stack after using. When the stack is empty it will
|
||||
no longer be bound to the current context (and as such released).
|
||||
|
||||
By calling the stack without arguments it returns a proxy that resolves to
|
||||
the topmost item on the stack.
|
||||
|
||||
.. versionadded:: 0.6.1
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._local = Local()
|
||||
|
||||
def __release_local__(self) -> None:
|
||||
self._local.__release_local__()
|
||||
|
||||
@property
|
||||
def __ident_func__(self) -> t.Callable[[], int]:
|
||||
return self._local.__ident_func__
|
||||
|
||||
@__ident_func__.setter
|
||||
def __ident_func__(self, value: t.Callable[[], int]) -> None:
|
||||
object.__setattr__(self._local, "__ident_func__", value)
|
||||
|
||||
def __call__(self) -> "LocalProxy":
|
||||
def _lookup() -> t.Any:
|
||||
rv = self.top
|
||||
if rv is None:
|
||||
raise RuntimeError("object unbound")
|
||||
return rv
|
||||
|
||||
return LocalProxy(_lookup)
|
||||
|
||||
def push(self, obj: t.Any) -> t.List[t.Any]:
|
||||
"""Pushes a new item to the stack"""
|
||||
rv = getattr(self._local, "stack", []).copy()
|
||||
rv.append(obj)
|
||||
self._local.stack = rv
|
||||
return rv
|
||||
|
||||
def pop(self) -> t.Any:
|
||||
"""Removes the topmost item from the stack, will return the
|
||||
old value or `None` if the stack was already empty.
|
||||
"""
|
||||
stack = getattr(self._local, "stack", None)
|
||||
if stack is None:
|
||||
return None
|
||||
elif len(stack) == 1:
|
||||
release_local(self._local)
|
||||
return stack[-1]
|
||||
else:
|
||||
return stack.pop()
|
||||
|
||||
@property
|
||||
def top(self) -> t.Any:
|
||||
"""The topmost item on the stack. If the stack is empty,
|
||||
`None` is returned.
|
||||
"""
|
||||
try:
|
||||
return self._local.stack[-1]
|
||||
except (AttributeError, IndexError):
|
||||
return None
|
||||
|
||||
|
||||
class LocalManager:
|
||||
"""Local objects cannot manage themselves. For that you need a local
|
||||
manager. You can pass a local manager multiple locals or add them
|
||||
later by appending them to `manager.locals`. Every time the manager
|
||||
cleans up, it will clean up all the data left in the locals for this
|
||||
context.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``ident_func`` is deprecated and will be removed in Werkzeug
|
||||
2.1.
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
The :func:`release_local` function can be used instead of a
|
||||
manager.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
The ``ident_func`` parameter was added.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
locals: t.Optional[t.Iterable[t.Union[Local, LocalStack]]] = None,
|
||||
ident_func: None = None,
|
||||
) -> None:
|
||||
if locals is None:
|
||||
self.locals = []
|
||||
elif isinstance(locals, Local):
|
||||
self.locals = [locals]
|
||||
else:
|
||||
self.locals = list(locals)
|
||||
|
||||
if ident_func is not None:
|
||||
warnings.warn(
|
||||
"'ident_func' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Setting it no longer has any effect.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
@property
|
||||
def ident_func(self) -> t.Callable[[], int]:
|
||||
warnings.warn(
|
||||
"'ident_func' is deprecated and will be removed in Werkzeug 2.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return _get_ident # type: ignore
|
||||
|
||||
@ident_func.setter
|
||||
def ident_func(self, func: t.Callable[[], int]) -> None:
|
||||
warnings.warn(
|
||||
"'ident_func' is deprecated and will be removedin Werkzeug"
|
||||
" 2.1. Setting it no longer has any effect.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def get_ident(self) -> int:
|
||||
"""Return the context identifier the local objects use internally for
|
||||
this context. You cannot override this method to change the behavior
|
||||
but use it to link other context local objects (such as SQLAlchemy's
|
||||
scoped sessions) to the Werkzeug locals.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
You can pass a different ident function to the local manager that
|
||||
will then be propagated to all the locals passed to the
|
||||
constructor.
|
||||
"""
|
||||
warnings.warn(
|
||||
"'get_ident' is deprecated and will be removed in Werkzeug 2.1.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.ident_func()
|
||||
|
||||
def cleanup(self) -> None:
|
||||
"""Manually clean up the data in the locals for this context. Call
|
||||
this at the end of the request or use `make_middleware()`.
|
||||
"""
|
||||
for local in self.locals:
|
||||
release_local(local)
|
||||
|
||||
def make_middleware(self, app: "WSGIApplication") -> "WSGIApplication":
|
||||
"""Wrap a WSGI application so that cleaning up happens after
|
||||
request end.
|
||||
"""
|
||||
|
||||
def application(
|
||||
environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
return ClosingIterator(app(environ, start_response), self.cleanup)
|
||||
|
||||
return application
|
||||
|
||||
def middleware(self, func: "WSGIApplication") -> "WSGIApplication":
|
||||
"""Like `make_middleware` but for decorating functions.
|
||||
|
||||
Example usage::
|
||||
|
||||
@manager.middleware
|
||||
def application(environ, start_response):
|
||||
...
|
||||
|
||||
The difference to `make_middleware` is that the function passed
|
||||
will have all the arguments copied from the inner application
|
||||
(name, docstring, module).
|
||||
"""
|
||||
return update_wrapper(self.make_middleware(func), func)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} storages: {len(self.locals)}>"
|
||||
|
||||
|
||||
class _ProxyLookup:
|
||||
"""Descriptor that handles proxied attribute lookup for
|
||||
:class:`LocalProxy`.
|
||||
|
||||
:param f: The built-in function this attribute is accessed through.
|
||||
Instead of looking up the special method, the function call
|
||||
is redone on the object.
|
||||
:param fallback: Return this function if the proxy is unbound
|
||||
instead of raising a :exc:`RuntimeError`.
|
||||
:param is_attr: This proxied name is an attribute, not a function.
|
||||
Call the fallback immediately to get the value.
|
||||
:param class_value: Value to return when accessed from the
|
||||
``LocalProxy`` class directly. Used for ``__doc__`` so building
|
||||
docs still works.
|
||||
"""
|
||||
|
||||
__slots__ = ("bind_f", "fallback", "is_attr", "class_value", "name")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
f: t.Optional[t.Callable] = None,
|
||||
fallback: t.Optional[t.Callable] = None,
|
||||
class_value: t.Optional[t.Any] = None,
|
||||
is_attr: bool = False,
|
||||
) -> None:
|
||||
bind_f: t.Optional[t.Callable[["LocalProxy", t.Any], t.Callable]]
|
||||
|
||||
if hasattr(f, "__get__"):
|
||||
# A Python function, can be turned into a bound method.
|
||||
|
||||
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
|
||||
return f.__get__(obj, type(obj)) # type: ignore
|
||||
|
||||
elif f is not None:
|
||||
# A C function, use partial to bind the first argument.
|
||||
|
||||
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
|
||||
return partial(f, obj) # type: ignore
|
||||
|
||||
else:
|
||||
# Use getattr, which will produce a bound method.
|
||||
bind_f = None
|
||||
|
||||
self.bind_f = bind_f
|
||||
self.fallback = fallback
|
||||
self.class_value = class_value
|
||||
self.is_attr = is_attr
|
||||
|
||||
def __set_name__(self, owner: "LocalProxy", name: str) -> None:
|
||||
self.name = name
|
||||
|
||||
def __get__(self, instance: "LocalProxy", owner: t.Optional[type] = None) -> t.Any:
|
||||
if instance is None:
|
||||
if self.class_value is not None:
|
||||
return self.class_value
|
||||
|
||||
return self
|
||||
|
||||
try:
|
||||
obj = instance._get_current_object()
|
||||
except RuntimeError:
|
||||
if self.fallback is None:
|
||||
raise
|
||||
|
||||
fallback = self.fallback.__get__(instance, owner) # type: ignore
|
||||
|
||||
if self.is_attr:
|
||||
# __class__ and __doc__ are attributes, not methods.
|
||||
# Call the fallback to get the value.
|
||||
return fallback()
|
||||
|
||||
return fallback
|
||||
|
||||
if self.bind_f is not None:
|
||||
return self.bind_f(instance, obj)
|
||||
|
||||
return getattr(obj, self.name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"proxy {self.name}"
|
||||
|
||||
def __call__(self, instance: "LocalProxy", *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
"""Support calling unbound methods from the class. For example,
|
||||
this happens with ``copy.copy``, which does
|
||||
``type(x).__copy__(x)``. ``type(x)`` can't be proxied, so it
|
||||
returns the proxy type and descriptor.
|
||||
"""
|
||||
return self.__get__(instance, type(instance))(*args, **kwargs)
|
||||
|
||||
|
||||
class _ProxyIOp(_ProxyLookup):
|
||||
"""Look up an augmented assignment method on a proxied object. The
|
||||
method is wrapped to return the proxy instead of the object.
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(
|
||||
self, f: t.Optional[t.Callable] = None, fallback: t.Optional[t.Callable] = None
|
||||
) -> None:
|
||||
super().__init__(f, fallback)
|
||||
|
||||
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
|
||||
def i_op(self: t.Any, other: t.Any) -> "LocalProxy":
|
||||
f(self, other) # type: ignore
|
||||
return instance
|
||||
|
||||
return i_op.__get__(obj, type(obj)) # type: ignore
|
||||
|
||||
self.bind_f = bind_f
|
||||
|
||||
|
||||
def _l_to_r_op(op: F) -> F:
|
||||
"""Swap the argument order to turn an l-op into an r-op."""
|
||||
|
||||
def r_op(obj: t.Any, other: t.Any) -> t.Any:
|
||||
return op(other, obj)
|
||||
|
||||
return t.cast(F, r_op)
|
||||
|
||||
|
||||
class LocalProxy:
|
||||
"""A proxy to the object bound to a :class:`Local`. All operations
|
||||
on the proxy are forwarded to the bound object. If no object is
|
||||
bound, a :exc:`RuntimeError` is raised.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.local import Local
|
||||
l = Local()
|
||||
|
||||
# a proxy to whatever l.user is set to
|
||||
user = l("user")
|
||||
|
||||
from werkzeug.local import LocalStack
|
||||
_request_stack = LocalStack()
|
||||
|
||||
# a proxy to _request_stack.top
|
||||
request = _request_stack()
|
||||
|
||||
# a proxy to the session attribute of the request proxy
|
||||
session = LocalProxy(lambda: request.session)
|
||||
|
||||
``__repr__`` and ``__class__`` are forwarded, so ``repr(x)`` and
|
||||
``isinstance(x, cls)`` will look like the proxied object. Use
|
||||
``issubclass(type(x), LocalProxy)`` to check if an object is a
|
||||
proxy.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
repr(user) # <User admin>
|
||||
isinstance(user, User) # True
|
||||
issubclass(type(user), LocalProxy) # True
|
||||
|
||||
:param local: The :class:`Local` or callable that provides the
|
||||
proxied object.
|
||||
:param name: The attribute name to look up on a :class:`Local`. Not
|
||||
used if a callable is given.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Updated proxied attributes and methods to reflect the current
|
||||
data model.
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
The class can be instantiated with a callable.
|
||||
"""
|
||||
|
||||
__slots__ = ("__local", "__name", "__wrapped__")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
local: t.Union["Local", t.Callable[[], t.Any]],
|
||||
name: t.Optional[str] = None,
|
||||
) -> None:
|
||||
object.__setattr__(self, "_LocalProxy__local", local)
|
||||
object.__setattr__(self, "_LocalProxy__name", name)
|
||||
|
||||
if callable(local) and not hasattr(local, "__release_local__"):
|
||||
# "local" is a callable that is not an instance of Local or
|
||||
# LocalManager: mark it as a wrapped function.
|
||||
object.__setattr__(self, "__wrapped__", local)
|
||||
|
||||
def _get_current_object(self) -> t.Any:
|
||||
"""Return the current object. This is useful if you want the real
|
||||
object behind the proxy at a time for performance reasons or because
|
||||
you want to pass the object into a different context.
|
||||
"""
|
||||
if not hasattr(self.__local, "__release_local__"): # type: ignore
|
||||
return self.__local() # type: ignore
|
||||
|
||||
try:
|
||||
return getattr(self.__local, self.__name) # type: ignore
|
||||
except AttributeError:
|
||||
name = self.__name # type: ignore
|
||||
raise RuntimeError(f"no object bound to {name}") from None
|
||||
|
||||
__doc__ = _ProxyLookup( # type: ignore
|
||||
class_value=__doc__, fallback=lambda self: type(self).__doc__, is_attr=True
|
||||
)
|
||||
# __del__ should only delete the proxy
|
||||
__repr__ = _ProxyLookup( # type: ignore
|
||||
repr, fallback=lambda self: f"<{type(self).__name__} unbound>"
|
||||
)
|
||||
__str__ = _ProxyLookup(str) # type: ignore
|
||||
__bytes__ = _ProxyLookup(bytes)
|
||||
__format__ = _ProxyLookup() # type: ignore
|
||||
__lt__ = _ProxyLookup(operator.lt)
|
||||
__le__ = _ProxyLookup(operator.le)
|
||||
__eq__ = _ProxyLookup(operator.eq) # type: ignore
|
||||
__ne__ = _ProxyLookup(operator.ne) # type: ignore
|
||||
__gt__ = _ProxyLookup(operator.gt)
|
||||
__ge__ = _ProxyLookup(operator.ge)
|
||||
__hash__ = _ProxyLookup(hash) # type: ignore
|
||||
__bool__ = _ProxyLookup(bool, fallback=lambda self: False)
|
||||
__getattr__ = _ProxyLookup(getattr)
|
||||
# __getattribute__ triggered through __getattr__
|
||||
__setattr__ = _ProxyLookup(setattr) # type: ignore
|
||||
__delattr__ = _ProxyLookup(delattr) # type: ignore
|
||||
__dir__ = _ProxyLookup(dir, fallback=lambda self: []) # type: ignore
|
||||
# __get__ (proxying descriptor not supported)
|
||||
# __set__ (descriptor)
|
||||
# __delete__ (descriptor)
|
||||
# __set_name__ (descriptor)
|
||||
# __objclass__ (descriptor)
|
||||
# __slots__ used by proxy itself
|
||||
# __dict__ (__getattr__)
|
||||
# __weakref__ (__getattr__)
|
||||
# __init_subclass__ (proxying metaclass not supported)
|
||||
# __prepare__ (metaclass)
|
||||
__class__ = _ProxyLookup(
|
||||
fallback=lambda self: type(self), is_attr=True
|
||||
) # type: ignore
|
||||
__instancecheck__ = _ProxyLookup(lambda self, other: isinstance(other, self))
|
||||
__subclasscheck__ = _ProxyLookup(lambda self, other: issubclass(other, self))
|
||||
# __class_getitem__ triggered through __getitem__
|
||||
__call__ = _ProxyLookup(lambda self, *args, **kwargs: self(*args, **kwargs))
|
||||
__len__ = _ProxyLookup(len)
|
||||
__length_hint__ = _ProxyLookup(operator.length_hint)
|
||||
__getitem__ = _ProxyLookup(operator.getitem)
|
||||
__setitem__ = _ProxyLookup(operator.setitem)
|
||||
__delitem__ = _ProxyLookup(operator.delitem)
|
||||
# __missing__ triggered through __getitem__
|
||||
__iter__ = _ProxyLookup(iter)
|
||||
__next__ = _ProxyLookup(next)
|
||||
__reversed__ = _ProxyLookup(reversed)
|
||||
__contains__ = _ProxyLookup(operator.contains)
|
||||
__add__ = _ProxyLookup(operator.add)
|
||||
__sub__ = _ProxyLookup(operator.sub)
|
||||
__mul__ = _ProxyLookup(operator.mul)
|
||||
__matmul__ = _ProxyLookup(operator.matmul)
|
||||
__truediv__ = _ProxyLookup(operator.truediv)
|
||||
__floordiv__ = _ProxyLookup(operator.floordiv)
|
||||
__mod__ = _ProxyLookup(operator.mod)
|
||||
__divmod__ = _ProxyLookup(divmod)
|
||||
__pow__ = _ProxyLookup(pow)
|
||||
__lshift__ = _ProxyLookup(operator.lshift)
|
||||
__rshift__ = _ProxyLookup(operator.rshift)
|
||||
__and__ = _ProxyLookup(operator.and_)
|
||||
__xor__ = _ProxyLookup(operator.xor)
|
||||
__or__ = _ProxyLookup(operator.or_)
|
||||
__radd__ = _ProxyLookup(_l_to_r_op(operator.add))
|
||||
__rsub__ = _ProxyLookup(_l_to_r_op(operator.sub))
|
||||
__rmul__ = _ProxyLookup(_l_to_r_op(operator.mul))
|
||||
__rmatmul__ = _ProxyLookup(_l_to_r_op(operator.matmul))
|
||||
__rtruediv__ = _ProxyLookup(_l_to_r_op(operator.truediv))
|
||||
__rfloordiv__ = _ProxyLookup(_l_to_r_op(operator.floordiv))
|
||||
__rmod__ = _ProxyLookup(_l_to_r_op(operator.mod))
|
||||
__rdivmod__ = _ProxyLookup(_l_to_r_op(divmod))
|
||||
__rpow__ = _ProxyLookup(_l_to_r_op(pow))
|
||||
__rlshift__ = _ProxyLookup(_l_to_r_op(operator.lshift))
|
||||
__rrshift__ = _ProxyLookup(_l_to_r_op(operator.rshift))
|
||||
__rand__ = _ProxyLookup(_l_to_r_op(operator.and_))
|
||||
__rxor__ = _ProxyLookup(_l_to_r_op(operator.xor))
|
||||
__ror__ = _ProxyLookup(_l_to_r_op(operator.or_))
|
||||
__iadd__ = _ProxyIOp(operator.iadd)
|
||||
__isub__ = _ProxyIOp(operator.isub)
|
||||
__imul__ = _ProxyIOp(operator.imul)
|
||||
__imatmul__ = _ProxyIOp(operator.imatmul)
|
||||
__itruediv__ = _ProxyIOp(operator.itruediv)
|
||||
__ifloordiv__ = _ProxyIOp(operator.ifloordiv)
|
||||
__imod__ = _ProxyIOp(operator.imod)
|
||||
__ipow__ = _ProxyIOp(operator.ipow)
|
||||
__ilshift__ = _ProxyIOp(operator.ilshift)
|
||||
__irshift__ = _ProxyIOp(operator.irshift)
|
||||
__iand__ = _ProxyIOp(operator.iand)
|
||||
__ixor__ = _ProxyIOp(operator.ixor)
|
||||
__ior__ = _ProxyIOp(operator.ior)
|
||||
__neg__ = _ProxyLookup(operator.neg)
|
||||
__pos__ = _ProxyLookup(operator.pos)
|
||||
__abs__ = _ProxyLookup(abs)
|
||||
__invert__ = _ProxyLookup(operator.invert)
|
||||
__complex__ = _ProxyLookup(complex)
|
||||
__int__ = _ProxyLookup(int)
|
||||
__float__ = _ProxyLookup(float)
|
||||
__index__ = _ProxyLookup(operator.index)
|
||||
__round__ = _ProxyLookup(round)
|
||||
__trunc__ = _ProxyLookup(math.trunc)
|
||||
__floor__ = _ProxyLookup(math.floor)
|
||||
__ceil__ = _ProxyLookup(math.ceil)
|
||||
__enter__ = _ProxyLookup()
|
||||
__exit__ = _ProxyLookup()
|
||||
__await__ = _ProxyLookup()
|
||||
__aiter__ = _ProxyLookup()
|
||||
__anext__ = _ProxyLookup()
|
||||
__aenter__ = _ProxyLookup()
|
||||
__aexit__ = _ProxyLookup()
|
||||
__copy__ = _ProxyLookup(copy.copy)
|
||||
__deepcopy__ = _ProxyLookup(copy.deepcopy)
|
||||
# __getnewargs_ex__ (pickle through proxy not supported)
|
||||
# __getnewargs__ (pickle)
|
||||
# __getstate__ (pickle)
|
||||
# __setstate__ (pickle)
|
||||
# __reduce__ (pickle)
|
||||
# __reduce_ex__ (pickle)
|
||||
@ -0,0 +1,22 @@
|
||||
"""
|
||||
Middleware
|
||||
==========
|
||||
|
||||
A WSGI middleware is a WSGI application that wraps another application
|
||||
in order to observe or change its behavior. Werkzeug provides some
|
||||
middleware for common use cases.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
proxy_fix
|
||||
shared_data
|
||||
dispatcher
|
||||
http_proxy
|
||||
lint
|
||||
profiler
|
||||
|
||||
The :doc:`interactive debugger </debug>` is also a middleware that can
|
||||
be applied manually, although it is typically used automatically with
|
||||
the :doc:`development server </serving>`.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,78 @@
|
||||
"""
|
||||
Application Dispatcher
|
||||
======================
|
||||
|
||||
This middleware creates a single WSGI application that dispatches to
|
||||
multiple other WSGI applications mounted at different URL paths.
|
||||
|
||||
A common example is writing a Single Page Application, where you have a
|
||||
backend API and a frontend written in JavaScript that does the routing
|
||||
in the browser rather than requesting different pages from the server.
|
||||
The frontend is a single HTML and JS file that should be served for any
|
||||
path besides "/api".
|
||||
|
||||
This example dispatches to an API app under "/api", an admin app
|
||||
under "/admin", and an app that serves frontend files for all other
|
||||
requests::
|
||||
|
||||
app = DispatcherMiddleware(serve_frontend, {
|
||||
'/api': api_app,
|
||||
'/admin': admin_app,
|
||||
})
|
||||
|
||||
In production, you might instead handle this at the HTTP server level,
|
||||
serving files or proxying to application servers based on location. The
|
||||
API and admin apps would each be deployed with a separate WSGI server,
|
||||
and the static files would be served directly by the HTTP server.
|
||||
|
||||
.. autoclass:: DispatcherMiddleware
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import typing as t
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class DispatcherMiddleware:
|
||||
"""Combine multiple applications as a single WSGI application.
|
||||
Requests are dispatched to an application based on the path it is
|
||||
mounted under.
|
||||
|
||||
:param app: The WSGI application to dispatch to if the request
|
||||
doesn't match a mounted path.
|
||||
:param mounts: Maps path prefixes to applications for dispatching.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
mounts: t.Optional[t.Dict[str, "WSGIApplication"]] = None,
|
||||
) -> None:
|
||||
self.app = app
|
||||
self.mounts = mounts or {}
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
script = environ.get("PATH_INFO", "")
|
||||
path_info = ""
|
||||
|
||||
while "/" in script:
|
||||
if script in self.mounts:
|
||||
app = self.mounts[script]
|
||||
break
|
||||
|
||||
script, last_item = script.rsplit("/", 1)
|
||||
path_info = f"/{last_item}{path_info}"
|
||||
else:
|
||||
app = self.mounts.get(script, self.app)
|
||||
|
||||
original_script_name = environ.get("SCRIPT_NAME", "")
|
||||
environ["SCRIPT_NAME"] = original_script_name + script
|
||||
environ["PATH_INFO"] = path_info
|
||||
return app(environ, start_response)
|
||||
@ -0,0 +1,230 @@
|
||||
"""
|
||||
Basic HTTP Proxy
|
||||
================
|
||||
|
||||
.. autoclass:: ProxyMiddleware
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import typing as t
|
||||
from http import client
|
||||
|
||||
from ..datastructures import EnvironHeaders
|
||||
from ..http import is_hop_by_hop_header
|
||||
from ..urls import url_parse
|
||||
from ..urls import url_quote
|
||||
from ..wsgi import get_input_stream
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class ProxyMiddleware:
|
||||
"""Proxy requests under a path to an external server, routing other
|
||||
requests to the app.
|
||||
|
||||
This middleware can only proxy HTTP requests, as HTTP is the only
|
||||
protocol handled by the WSGI server. Other protocols, such as
|
||||
WebSocket requests, cannot be proxied at this layer. This should
|
||||
only be used for development, in production a real proxy server
|
||||
should be used.
|
||||
|
||||
The middleware takes a dict mapping a path prefix to a dict
|
||||
describing the host to be proxied to::
|
||||
|
||||
app = ProxyMiddleware(app, {
|
||||
"/static/": {
|
||||
"target": "http://127.0.0.1:5001/",
|
||||
}
|
||||
})
|
||||
|
||||
Each host has the following options:
|
||||
|
||||
``target``:
|
||||
The target URL to dispatch to. This is required.
|
||||
``remove_prefix``:
|
||||
Whether to remove the prefix from the URL before dispatching it
|
||||
to the target. The default is ``False``.
|
||||
``host``:
|
||||
``"<auto>"`` (default):
|
||||
The host header is automatically rewritten to the URL of the
|
||||
target.
|
||||
``None``:
|
||||
The host header is unmodified from the client request.
|
||||
Any other value:
|
||||
The host header is overwritten with the value.
|
||||
``headers``:
|
||||
A dictionary of headers to be sent with the request to the
|
||||
target. The default is ``{}``.
|
||||
``ssl_context``:
|
||||
A :class:`ssl.SSLContext` defining how to verify requests if the
|
||||
target is HTTPS. The default is ``None``.
|
||||
|
||||
In the example above, everything under ``"/static/"`` is proxied to
|
||||
the server on port 5001. The host header is rewritten to the target,
|
||||
and the ``"/static/"`` prefix is removed from the URLs.
|
||||
|
||||
:param app: The WSGI application to wrap.
|
||||
:param targets: Proxy target configurations. See description above.
|
||||
:param chunk_size: Size of chunks to read from input stream and
|
||||
write to target.
|
||||
:param timeout: Seconds before an operation to a target fails.
|
||||
|
||||
.. versionadded:: 0.14
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
targets: t.Mapping[str, t.Dict[str, t.Any]],
|
||||
chunk_size: int = 2 << 13,
|
||||
timeout: int = 10,
|
||||
) -> None:
|
||||
def _set_defaults(opts: t.Dict[str, t.Any]) -> t.Dict[str, t.Any]:
|
||||
opts.setdefault("remove_prefix", False)
|
||||
opts.setdefault("host", "<auto>")
|
||||
opts.setdefault("headers", {})
|
||||
opts.setdefault("ssl_context", None)
|
||||
return opts
|
||||
|
||||
self.app = app
|
||||
self.targets = {
|
||||
f"/{k.strip('/')}/": _set_defaults(v) for k, v in targets.items()
|
||||
}
|
||||
self.chunk_size = chunk_size
|
||||
self.timeout = timeout
|
||||
|
||||
def proxy_to(
|
||||
self, opts: t.Dict[str, t.Any], path: str, prefix: str
|
||||
) -> "WSGIApplication":
|
||||
target = url_parse(opts["target"])
|
||||
host = t.cast(str, target.ascii_host)
|
||||
|
||||
def application(
|
||||
environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
headers = list(EnvironHeaders(environ).items())
|
||||
headers[:] = [
|
||||
(k, v)
|
||||
for k, v in headers
|
||||
if not is_hop_by_hop_header(k)
|
||||
and k.lower() not in ("content-length", "host")
|
||||
]
|
||||
headers.append(("Connection", "close"))
|
||||
|
||||
if opts["host"] == "<auto>":
|
||||
headers.append(("Host", host))
|
||||
elif opts["host"] is None:
|
||||
headers.append(("Host", environ["HTTP_HOST"]))
|
||||
else:
|
||||
headers.append(("Host", opts["host"]))
|
||||
|
||||
headers.extend(opts["headers"].items())
|
||||
remote_path = path
|
||||
|
||||
if opts["remove_prefix"]:
|
||||
remote_path = remote_path[len(prefix) :].lstrip("/")
|
||||
remote_path = f"{target.path.rstrip('/')}/{remote_path}"
|
||||
|
||||
content_length = environ.get("CONTENT_LENGTH")
|
||||
chunked = False
|
||||
|
||||
if content_length not in ("", None):
|
||||
headers.append(("Content-Length", content_length)) # type: ignore
|
||||
elif content_length is not None:
|
||||
headers.append(("Transfer-Encoding", "chunked"))
|
||||
chunked = True
|
||||
|
||||
try:
|
||||
if target.scheme == "http":
|
||||
con = client.HTTPConnection(
|
||||
host, target.port or 80, timeout=self.timeout
|
||||
)
|
||||
elif target.scheme == "https":
|
||||
con = client.HTTPSConnection(
|
||||
host,
|
||||
target.port or 443,
|
||||
timeout=self.timeout,
|
||||
context=opts["ssl_context"],
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Target scheme must be 'http' or 'https', got"
|
||||
f" {target.scheme!r}."
|
||||
)
|
||||
|
||||
con.connect()
|
||||
remote_url = url_quote(remote_path)
|
||||
querystring = environ["QUERY_STRING"]
|
||||
|
||||
if querystring:
|
||||
remote_url = f"{remote_url}?{querystring}"
|
||||
|
||||
con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True)
|
||||
|
||||
for k, v in headers:
|
||||
if k.lower() == "connection":
|
||||
v = "close"
|
||||
|
||||
con.putheader(k, v)
|
||||
|
||||
con.endheaders()
|
||||
stream = get_input_stream(environ)
|
||||
|
||||
while True:
|
||||
data = stream.read(self.chunk_size)
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
if chunked:
|
||||
con.send(b"%x\r\n%s\r\n" % (len(data), data))
|
||||
else:
|
||||
con.send(data)
|
||||
|
||||
resp = con.getresponse()
|
||||
except OSError:
|
||||
from ..exceptions import BadGateway
|
||||
|
||||
return BadGateway()(environ, start_response)
|
||||
|
||||
start_response(
|
||||
f"{resp.status} {resp.reason}",
|
||||
[
|
||||
(k.title(), v)
|
||||
for k, v in resp.getheaders()
|
||||
if not is_hop_by_hop_header(k)
|
||||
],
|
||||
)
|
||||
|
||||
def read() -> t.Iterator[bytes]:
|
||||
while True:
|
||||
try:
|
||||
data = resp.read(self.chunk_size)
|
||||
except OSError:
|
||||
break
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
yield data
|
||||
|
||||
return read()
|
||||
|
||||
return application
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
path = environ["PATH_INFO"]
|
||||
app = self.app
|
||||
|
||||
for prefix, opts in self.targets.items():
|
||||
if path.startswith(prefix):
|
||||
app = self.proxy_to(opts, path, prefix)
|
||||
break
|
||||
|
||||
return app(environ, start_response)
|
||||
420
myvenv/lib/python3.10/site-packages/werkzeug/middleware/lint.py
Normal file
420
myvenv/lib/python3.10/site-packages/werkzeug/middleware/lint.py
Normal file
@ -0,0 +1,420 @@
|
||||
"""
|
||||
WSGI Protocol Linter
|
||||
====================
|
||||
|
||||
This module provides a middleware that performs sanity checks on the
|
||||
behavior of the WSGI server and application. It checks that the
|
||||
:pep:`3333` WSGI spec is properly implemented. It also warns on some
|
||||
common HTTP errors such as non-empty responses for 304 status codes.
|
||||
|
||||
.. autoclass:: LintMiddleware
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import typing as t
|
||||
from types import TracebackType
|
||||
from urllib.parse import urlparse
|
||||
from warnings import warn
|
||||
|
||||
from ..datastructures import Headers
|
||||
from ..http import is_entity_header
|
||||
from ..wsgi import FileWrapper
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class WSGIWarning(Warning):
|
||||
"""Warning class for WSGI warnings."""
|
||||
|
||||
|
||||
class HTTPWarning(Warning):
|
||||
"""Warning class for HTTP warnings."""
|
||||
|
||||
|
||||
def check_type(context: str, obj: object, need: t.Type = str) -> None:
|
||||
if type(obj) is not need:
|
||||
warn(
|
||||
f"{context!r} requires {need.__name__!r}, got {type(obj).__name__!r}.",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
|
||||
class InputStream:
|
||||
def __init__(self, stream: t.IO[bytes]) -> None:
|
||||
self._stream = stream
|
||||
|
||||
def read(self, *args: t.Any) -> bytes:
|
||||
if len(args) == 0:
|
||||
warn(
|
||||
"WSGI does not guarantee an EOF marker on the input stream, thus making"
|
||||
" calls to 'wsgi.input.read()' unsafe. Conforming servers may never"
|
||||
" return from this call.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
elif len(args) != 1:
|
||||
warn(
|
||||
"Too many parameters passed to 'wsgi.input.read()'.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._stream.read(*args)
|
||||
|
||||
def readline(self, *args: t.Any) -> bytes:
|
||||
if len(args) == 0:
|
||||
warn(
|
||||
"Calls to 'wsgi.input.readline()' without arguments are unsafe. Use"
|
||||
" 'wsgi.input.read()' instead.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
elif len(args) == 1:
|
||||
warn(
|
||||
"'wsgi.input.readline()' was called with a size hint. WSGI does not"
|
||||
" support this, although it's available on all major servers.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
raise TypeError("Too many arguments passed to 'wsgi.input.readline()'.")
|
||||
return self._stream.readline(*args)
|
||||
|
||||
def __iter__(self) -> t.Iterator[bytes]:
|
||||
try:
|
||||
return iter(self._stream)
|
||||
except TypeError:
|
||||
warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2)
|
||||
return iter(())
|
||||
|
||||
def close(self) -> None:
|
||||
warn("The application closed the input stream!", WSGIWarning, stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class ErrorStream:
|
||||
def __init__(self, stream: t.IO[str]) -> None:
|
||||
self._stream = stream
|
||||
|
||||
def write(self, s: str) -> None:
|
||||
check_type("wsgi.error.write()", s, str)
|
||||
self._stream.write(s)
|
||||
|
||||
def flush(self) -> None:
|
||||
self._stream.flush()
|
||||
|
||||
def writelines(self, seq: t.Iterable[str]) -> None:
|
||||
for line in seq:
|
||||
self.write(line)
|
||||
|
||||
def close(self) -> None:
|
||||
warn("The application closed the error stream!", WSGIWarning, stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class GuardedWrite:
|
||||
def __init__(self, write: t.Callable[[bytes], None], chunks: t.List[int]) -> None:
|
||||
self._write = write
|
||||
self._chunks = chunks
|
||||
|
||||
def __call__(self, s: bytes) -> None:
|
||||
check_type("write()", s, bytes)
|
||||
self._write(s)
|
||||
self._chunks.append(len(s))
|
||||
|
||||
|
||||
class GuardedIterator:
|
||||
def __init__(
|
||||
self,
|
||||
iterator: t.Iterable[bytes],
|
||||
headers_set: t.Tuple[int, Headers],
|
||||
chunks: t.List[int],
|
||||
) -> None:
|
||||
self._iterator = iterator
|
||||
self._next = iter(iterator).__next__
|
||||
self.closed = False
|
||||
self.headers_set = headers_set
|
||||
self.chunks = chunks
|
||||
|
||||
def __iter__(self) -> "GuardedIterator":
|
||||
return self
|
||||
|
||||
def __next__(self) -> bytes:
|
||||
if self.closed:
|
||||
warn("Iterated over closed 'app_iter'.", WSGIWarning, stacklevel=2)
|
||||
|
||||
rv = self._next()
|
||||
|
||||
if not self.headers_set:
|
||||
warn(
|
||||
"The application returned before it started the response.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
check_type("application iterator items", rv, bytes)
|
||||
self.chunks.append(len(rv))
|
||||
return rv
|
||||
|
||||
def close(self) -> None:
|
||||
self.closed = True
|
||||
|
||||
if hasattr(self._iterator, "close"):
|
||||
self._iterator.close() # type: ignore
|
||||
|
||||
if self.headers_set:
|
||||
status_code, headers = self.headers_set
|
||||
bytes_sent = sum(self.chunks)
|
||||
content_length = headers.get("content-length", type=int)
|
||||
|
||||
if status_code == 304:
|
||||
for key, _value in headers:
|
||||
key = key.lower()
|
||||
if key not in ("expires", "content-location") and is_entity_header(
|
||||
key
|
||||
):
|
||||
warn(
|
||||
f"Entity header {key!r} found in 304 response.", HTTPWarning
|
||||
)
|
||||
if bytes_sent:
|
||||
warn("304 responses must not have a body.", HTTPWarning)
|
||||
elif 100 <= status_code < 200 or status_code == 204:
|
||||
if content_length != 0:
|
||||
warn(
|
||||
f"{status_code} responses must have an empty content length.",
|
||||
HTTPWarning,
|
||||
)
|
||||
if bytes_sent:
|
||||
warn(f"{status_code} responses must not have a body.", HTTPWarning)
|
||||
elif content_length is not None and content_length != bytes_sent:
|
||||
warn(
|
||||
"Content-Length and the number of bytes sent to the"
|
||||
" client do not match.",
|
||||
WSGIWarning,
|
||||
)
|
||||
|
||||
def __del__(self) -> None:
|
||||
if not self.closed:
|
||||
try:
|
||||
warn(
|
||||
"Iterator was garbage collected before it was closed.", WSGIWarning
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class LintMiddleware:
|
||||
"""Warns about common errors in the WSGI and HTTP behavior of the
|
||||
server and wrapped application. Some of the issues it checks are:
|
||||
|
||||
- invalid status codes
|
||||
- non-bytes sent to the WSGI server
|
||||
- strings returned from the WSGI application
|
||||
- non-empty conditional responses
|
||||
- unquoted etags
|
||||
- relative URLs in the Location header
|
||||
- unsafe calls to wsgi.input
|
||||
- unclosed iterators
|
||||
|
||||
Error information is emitted using the :mod:`warnings` module.
|
||||
|
||||
:param app: The WSGI application to wrap.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.lint import LintMiddleware
|
||||
app = LintMiddleware(app)
|
||||
"""
|
||||
|
||||
def __init__(self, app: "WSGIApplication") -> None:
|
||||
self.app = app
|
||||
|
||||
def check_environ(self, environ: "WSGIEnvironment") -> None:
|
||||
if type(environ) is not dict:
|
||||
warn(
|
||||
"WSGI environment is not a standard Python dict.",
|
||||
WSGIWarning,
|
||||
stacklevel=4,
|
||||
)
|
||||
for key in (
|
||||
"REQUEST_METHOD",
|
||||
"SERVER_NAME",
|
||||
"SERVER_PORT",
|
||||
"wsgi.version",
|
||||
"wsgi.input",
|
||||
"wsgi.errors",
|
||||
"wsgi.multithread",
|
||||
"wsgi.multiprocess",
|
||||
"wsgi.run_once",
|
||||
):
|
||||
if key not in environ:
|
||||
warn(
|
||||
f"Required environment key {key!r} not found",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
if environ["wsgi.version"] != (1, 0):
|
||||
warn("Environ is not a WSGI 1.0 environ.", WSGIWarning, stacklevel=3)
|
||||
|
||||
script_name = environ.get("SCRIPT_NAME", "")
|
||||
path_info = environ.get("PATH_INFO", "")
|
||||
|
||||
if script_name and script_name[0] != "/":
|
||||
warn(
|
||||
f"'SCRIPT_NAME' does not start with a slash: {script_name!r}",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
if path_info and path_info[0] != "/":
|
||||
warn(
|
||||
f"'PATH_INFO' does not start with a slash: {path_info!r}",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
def check_start_response(
|
||||
self,
|
||||
status: str,
|
||||
headers: t.List[t.Tuple[str, str]],
|
||||
exc_info: t.Optional[
|
||||
t.Tuple[t.Type[BaseException], BaseException, TracebackType]
|
||||
],
|
||||
) -> t.Tuple[int, Headers]:
|
||||
check_type("status", status, str)
|
||||
status_code_str = status.split(None, 1)[0]
|
||||
|
||||
if len(status_code_str) != 3 or not status_code_str.isdigit():
|
||||
warn("Status code must be three digits.", WSGIWarning, stacklevel=3)
|
||||
|
||||
if len(status) < 4 or status[3] != " ":
|
||||
warn(
|
||||
f"Invalid value for status {status!r}. Valid status strings are three"
|
||||
" digits, a space and a status explanation.",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
status_code = int(status_code_str)
|
||||
|
||||
if status_code < 100:
|
||||
warn("Status code < 100 detected.", WSGIWarning, stacklevel=3)
|
||||
|
||||
if type(headers) is not list:
|
||||
warn("Header list is not a list.", WSGIWarning, stacklevel=3)
|
||||
|
||||
for item in headers:
|
||||
if type(item) is not tuple or len(item) != 2:
|
||||
warn("Header items must be 2-item tuples.", WSGIWarning, stacklevel=3)
|
||||
name, value = item
|
||||
if type(name) is not str or type(value) is not str:
|
||||
warn(
|
||||
"Header keys and values must be strings.", WSGIWarning, stacklevel=3
|
||||
)
|
||||
if name.lower() == "status":
|
||||
warn(
|
||||
"The status header is not supported due to"
|
||||
" conflicts with the CGI spec.",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
if exc_info is not None and not isinstance(exc_info, tuple):
|
||||
warn("Invalid value for exc_info.", WSGIWarning, stacklevel=3)
|
||||
|
||||
headers = Headers(headers)
|
||||
self.check_headers(headers)
|
||||
|
||||
return status_code, headers
|
||||
|
||||
def check_headers(self, headers: Headers) -> None:
|
||||
etag = headers.get("etag")
|
||||
|
||||
if etag is not None:
|
||||
if etag.startswith(("W/", "w/")):
|
||||
if etag.startswith("w/"):
|
||||
warn(
|
||||
"Weak etag indicator should be upper case.",
|
||||
HTTPWarning,
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
etag = etag[2:]
|
||||
|
||||
if not (etag[:1] == etag[-1:] == '"'):
|
||||
warn("Unquoted etag emitted.", HTTPWarning, stacklevel=4)
|
||||
|
||||
location = headers.get("location")
|
||||
|
||||
if location is not None:
|
||||
if not urlparse(location).netloc:
|
||||
warn(
|
||||
"Absolute URLs required for location header.",
|
||||
HTTPWarning,
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
def check_iterator(self, app_iter: t.Iterable[bytes]) -> None:
|
||||
if isinstance(app_iter, bytes):
|
||||
warn(
|
||||
"The application returned a bytestring. The response will send one"
|
||||
" character at a time to the client, which will kill performance."
|
||||
" Return a list or iterable instead.",
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Iterable[bytes]:
|
||||
if len(args) != 2:
|
||||
warn("A WSGI app takes two arguments.", WSGIWarning, stacklevel=2)
|
||||
|
||||
if kwargs:
|
||||
warn(
|
||||
"A WSGI app does not take keyword arguments.", WSGIWarning, stacklevel=2
|
||||
)
|
||||
|
||||
environ: "WSGIEnvironment" = args[0]
|
||||
start_response: "StartResponse" = args[1]
|
||||
|
||||
self.check_environ(environ)
|
||||
environ["wsgi.input"] = InputStream(environ["wsgi.input"])
|
||||
environ["wsgi.errors"] = ErrorStream(environ["wsgi.errors"])
|
||||
|
||||
# Hook our own file wrapper in so that applications will always
|
||||
# iterate to the end and we can check the content length.
|
||||
environ["wsgi.file_wrapper"] = FileWrapper
|
||||
|
||||
headers_set: t.List[t.Any] = []
|
||||
chunks: t.List[int] = []
|
||||
|
||||
def checking_start_response(
|
||||
*args: t.Any, **kwargs: t.Any
|
||||
) -> t.Callable[[bytes], None]:
|
||||
if len(args) not in {2, 3}:
|
||||
warn(
|
||||
f"Invalid number of arguments: {len(args)}, expected 2 or 3.",
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if kwargs:
|
||||
warn("'start_response' does not take keyword arguments.", WSGIWarning)
|
||||
|
||||
status: str = args[0]
|
||||
headers: t.List[t.Tuple[str, str]] = args[1]
|
||||
exc_info: t.Optional[
|
||||
t.Tuple[t.Type[BaseException], BaseException, TracebackType]
|
||||
] = (args[2] if len(args) == 3 else None)
|
||||
|
||||
headers_set[:] = self.check_start_response(status, headers, exc_info)
|
||||
return GuardedWrite(start_response(status, headers, exc_info), chunks)
|
||||
|
||||
app_iter = self.app(environ, t.cast("StartResponse", checking_start_response))
|
||||
self.check_iterator(app_iter)
|
||||
return GuardedIterator(
|
||||
app_iter, t.cast(t.Tuple[int, Headers], headers_set), chunks
|
||||
)
|
||||
@ -0,0 +1,139 @@
|
||||
"""
|
||||
Application Profiler
|
||||
====================
|
||||
|
||||
This module provides a middleware that profiles each request with the
|
||||
:mod:`cProfile` module. This can help identify bottlenecks in your code
|
||||
that may be slowing down your application.
|
||||
|
||||
.. autoclass:: ProfilerMiddleware
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
import typing as t
|
||||
from pstats import Stats
|
||||
|
||||
try:
|
||||
from cProfile import Profile
|
||||
except ImportError:
|
||||
from profile import Profile # type: ignore
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class ProfilerMiddleware:
|
||||
"""Wrap a WSGI application and profile the execution of each
|
||||
request. Responses are buffered so that timings are more exact.
|
||||
|
||||
If ``stream`` is given, :class:`pstats.Stats` are written to it
|
||||
after each request. If ``profile_dir`` is given, :mod:`cProfile`
|
||||
data files are saved to that directory, one file per request.
|
||||
|
||||
The filename can be customized by passing ``filename_format``. If
|
||||
it is a string, it will be formatted using :meth:`str.format` with
|
||||
the following fields available:
|
||||
|
||||
- ``{method}`` - The request method; GET, POST, etc.
|
||||
- ``{path}`` - The request path or 'root' should one not exist.
|
||||
- ``{elapsed}`` - The elapsed time of the request.
|
||||
- ``{time}`` - The time of the request.
|
||||
|
||||
If it is a callable, it will be called with the WSGI ``environ``
|
||||
dict and should return a filename.
|
||||
|
||||
:param app: The WSGI application to wrap.
|
||||
:param stream: Write stats to this stream. Disable with ``None``.
|
||||
:param sort_by: A tuple of columns to sort stats by. See
|
||||
:meth:`pstats.Stats.sort_stats`.
|
||||
:param restrictions: A tuple of restrictions to filter stats by. See
|
||||
:meth:`pstats.Stats.print_stats`.
|
||||
:param profile_dir: Save profile data files to this directory.
|
||||
:param filename_format: Format string for profile data file names,
|
||||
or a callable returning a name. See explanation above.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
app = ProfilerMiddleware(app)
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
Stats are written even if ``profile_dir`` is given, and can be
|
||||
disable by passing ``stream=None``.
|
||||
|
||||
.. versionadded:: 0.15
|
||||
Added ``filename_format``.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Added ``restrictions`` and ``profile_dir``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
stream: t.IO[str] = sys.stdout,
|
||||
sort_by: t.Iterable[str] = ("time", "calls"),
|
||||
restrictions: t.Iterable[t.Union[str, int, float]] = (),
|
||||
profile_dir: t.Optional[str] = None,
|
||||
filename_format: str = "{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof",
|
||||
) -> None:
|
||||
self._app = app
|
||||
self._stream = stream
|
||||
self._sort_by = sort_by
|
||||
self._restrictions = restrictions
|
||||
self._profile_dir = profile_dir
|
||||
self._filename_format = filename_format
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
response_body: t.List[bytes] = []
|
||||
|
||||
def catching_start_response(status, headers, exc_info=None): # type: ignore
|
||||
start_response(status, headers, exc_info)
|
||||
return response_body.append
|
||||
|
||||
def runapp() -> None:
|
||||
app_iter = self._app(
|
||||
environ, t.cast("StartResponse", catching_start_response)
|
||||
)
|
||||
response_body.extend(app_iter)
|
||||
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close() # type: ignore
|
||||
|
||||
profile = Profile()
|
||||
start = time.time()
|
||||
profile.runcall(runapp)
|
||||
body = b"".join(response_body)
|
||||
elapsed = time.time() - start
|
||||
|
||||
if self._profile_dir is not None:
|
||||
if callable(self._filename_format):
|
||||
filename = self._filename_format(environ)
|
||||
else:
|
||||
filename = self._filename_format.format(
|
||||
method=environ["REQUEST_METHOD"],
|
||||
path=environ["PATH_INFO"].strip("/").replace("/", ".") or "root",
|
||||
elapsed=elapsed * 1000.0,
|
||||
time=time.time(),
|
||||
)
|
||||
filename = os.path.join(self._profile_dir, filename)
|
||||
profile.dump_stats(filename)
|
||||
|
||||
if self._stream is not None:
|
||||
stats = Stats(profile, stream=self._stream)
|
||||
stats.sort_stats(*self._sort_by)
|
||||
print("-" * 80, file=self._stream)
|
||||
path_info = environ.get("PATH_INFO", "")
|
||||
print(f"PATH: {path_info!r}", file=self._stream)
|
||||
stats.print_stats(*self._restrictions)
|
||||
print(f"{'-' * 80}\n", file=self._stream)
|
||||
|
||||
return [body]
|
||||
@ -0,0 +1,187 @@
|
||||
"""
|
||||
X-Forwarded-For Proxy Fix
|
||||
=========================
|
||||
|
||||
This module provides a middleware that adjusts the WSGI environ based on
|
||||
``X-Forwarded-`` headers that proxies in front of an application may
|
||||
set.
|
||||
|
||||
When an application is running behind a proxy server, WSGI may see the
|
||||
request as coming from that server rather than the real client. Proxies
|
||||
set various headers to track where the request actually came from.
|
||||
|
||||
This middleware should only be used if the application is actually
|
||||
behind such a proxy, and should be configured with the number of proxies
|
||||
that are chained in front of it. Not all proxies set all the headers.
|
||||
Since incoming headers can be faked, you must set how many proxies are
|
||||
setting each header so the middleware knows what to trust.
|
||||
|
||||
.. autoclass:: ProxyFix
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import typing as t
|
||||
|
||||
from ..http import parse_list_header
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class ProxyFix:
|
||||
"""Adjust the WSGI environ based on ``X-Forwarded-`` that proxies in
|
||||
front of the application may set.
|
||||
|
||||
- ``X-Forwarded-For`` sets ``REMOTE_ADDR``.
|
||||
- ``X-Forwarded-Proto`` sets ``wsgi.url_scheme``.
|
||||
- ``X-Forwarded-Host`` sets ``HTTP_HOST``, ``SERVER_NAME``, and
|
||||
``SERVER_PORT``.
|
||||
- ``X-Forwarded-Port`` sets ``HTTP_HOST`` and ``SERVER_PORT``.
|
||||
- ``X-Forwarded-Prefix`` sets ``SCRIPT_NAME``.
|
||||
|
||||
You must tell the middleware how many proxies set each header so it
|
||||
knows what values to trust. It is a security issue to trust values
|
||||
that came from the client rather than a proxy.
|
||||
|
||||
The original values of the headers are stored in the WSGI
|
||||
environ as ``werkzeug.proxy_fix.orig``, a dict.
|
||||
|
||||
:param app: The WSGI application to wrap.
|
||||
:param x_for: Number of values to trust for ``X-Forwarded-For``.
|
||||
:param x_proto: Number of values to trust for ``X-Forwarded-Proto``.
|
||||
:param x_host: Number of values to trust for ``X-Forwarded-Host``.
|
||||
:param x_port: Number of values to trust for ``X-Forwarded-Port``.
|
||||
:param x_prefix: Number of values to trust for
|
||||
``X-Forwarded-Prefix``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||
# App is behind one proxy that sets the -For and -Host headers.
|
||||
app = ProxyFix(app, x_for=1, x_host=1)
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Deprecated code has been removed:
|
||||
|
||||
* The ``num_proxies`` argument and attribute.
|
||||
* The ``get_remote_addr`` method.
|
||||
* The environ keys ``orig_remote_addr``,
|
||||
``orig_wsgi_url_scheme``, and ``orig_http_host``.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
All headers support multiple values. The ``num_proxies``
|
||||
argument is deprecated. Each header is configured with a
|
||||
separate number of trusted proxies.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
Original WSGI environ values are stored in the
|
||||
``werkzeug.proxy_fix.orig`` dict. ``orig_remote_addr``,
|
||||
``orig_wsgi_url_scheme``, and ``orig_http_host`` are deprecated
|
||||
and will be removed in 1.0.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
Support ``X-Forwarded-Port`` and ``X-Forwarded-Prefix``.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
``X-Forwarded-Host`` and ``X-Forwarded-Port`` modify
|
||||
``SERVER_NAME`` and ``SERVER_PORT``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
x_for: int = 1,
|
||||
x_proto: int = 1,
|
||||
x_host: int = 0,
|
||||
x_port: int = 0,
|
||||
x_prefix: int = 0,
|
||||
) -> None:
|
||||
self.app = app
|
||||
self.x_for = x_for
|
||||
self.x_proto = x_proto
|
||||
self.x_host = x_host
|
||||
self.x_port = x_port
|
||||
self.x_prefix = x_prefix
|
||||
|
||||
def _get_real_value(self, trusted: int, value: t.Optional[str]) -> t.Optional[str]:
|
||||
"""Get the real value from a list header based on the configured
|
||||
number of trusted proxies.
|
||||
|
||||
:param trusted: Number of values to trust in the header.
|
||||
:param value: Comma separated list header value to parse.
|
||||
:return: The real value, or ``None`` if there are fewer values
|
||||
than the number of trusted proxies.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Renamed from ``_get_trusted_comma``.
|
||||
|
||||
.. versionadded:: 0.15
|
||||
"""
|
||||
if not (trusted and value):
|
||||
return None
|
||||
values = parse_list_header(value)
|
||||
if len(values) >= trusted:
|
||||
return values[-trusted]
|
||||
return None
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
"""Modify the WSGI environ based on the various ``Forwarded``
|
||||
headers before calling the wrapped application. Store the
|
||||
original environ values in ``werkzeug.proxy_fix.orig_{key}``.
|
||||
"""
|
||||
environ_get = environ.get
|
||||
orig_remote_addr = environ_get("REMOTE_ADDR")
|
||||
orig_wsgi_url_scheme = environ_get("wsgi.url_scheme")
|
||||
orig_http_host = environ_get("HTTP_HOST")
|
||||
environ.update(
|
||||
{
|
||||
"werkzeug.proxy_fix.orig": {
|
||||
"REMOTE_ADDR": orig_remote_addr,
|
||||
"wsgi.url_scheme": orig_wsgi_url_scheme,
|
||||
"HTTP_HOST": orig_http_host,
|
||||
"SERVER_NAME": environ_get("SERVER_NAME"),
|
||||
"SERVER_PORT": environ_get("SERVER_PORT"),
|
||||
"SCRIPT_NAME": environ_get("SCRIPT_NAME"),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
x_for = self._get_real_value(self.x_for, environ_get("HTTP_X_FORWARDED_FOR"))
|
||||
if x_for:
|
||||
environ["REMOTE_ADDR"] = x_for
|
||||
|
||||
x_proto = self._get_real_value(
|
||||
self.x_proto, environ_get("HTTP_X_FORWARDED_PROTO")
|
||||
)
|
||||
if x_proto:
|
||||
environ["wsgi.url_scheme"] = x_proto
|
||||
|
||||
x_host = self._get_real_value(self.x_host, environ_get("HTTP_X_FORWARDED_HOST"))
|
||||
if x_host:
|
||||
environ["HTTP_HOST"] = environ["SERVER_NAME"] = x_host
|
||||
# "]" to check for IPv6 address without port
|
||||
if ":" in x_host and not x_host.endswith("]"):
|
||||
environ["SERVER_NAME"], environ["SERVER_PORT"] = x_host.rsplit(":", 1)
|
||||
|
||||
x_port = self._get_real_value(self.x_port, environ_get("HTTP_X_FORWARDED_PORT"))
|
||||
if x_port:
|
||||
host = environ.get("HTTP_HOST")
|
||||
if host:
|
||||
# "]" to check for IPv6 address without port
|
||||
if ":" in host and not host.endswith("]"):
|
||||
host = host.rsplit(":", 1)[0]
|
||||
environ["HTTP_HOST"] = f"{host}:{x_port}"
|
||||
environ["SERVER_PORT"] = x_port
|
||||
|
||||
x_prefix = self._get_real_value(
|
||||
self.x_prefix, environ_get("HTTP_X_FORWARDED_PREFIX")
|
||||
)
|
||||
if x_prefix:
|
||||
environ["SCRIPT_NAME"] = x_prefix
|
||||
|
||||
return self.app(environ, start_response)
|
||||
@ -0,0 +1,320 @@
|
||||
"""
|
||||
Serve Shared Static Files
|
||||
=========================
|
||||
|
||||
.. autoclass:: SharedDataMiddleware
|
||||
:members: is_allowed
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import mimetypes
|
||||
import os
|
||||
import pkgutil
|
||||
import posixpath
|
||||
import typing as t
|
||||
from datetime import datetime
|
||||
from datetime import timezone
|
||||
from io import BytesIO
|
||||
from time import time
|
||||
from zlib import adler32
|
||||
|
||||
from ..filesystem import get_filesystem_encoding
|
||||
from ..http import http_date
|
||||
from ..http import is_resource_modified
|
||||
from ..security import safe_join
|
||||
from ..utils import get_content_type
|
||||
from ..wsgi import get_path_info
|
||||
from ..wsgi import wrap_file
|
||||
|
||||
_TOpener = t.Callable[[], t.Tuple[t.IO[bytes], datetime, int]]
|
||||
_TLoader = t.Callable[[t.Optional[str]], t.Tuple[t.Optional[str], t.Optional[_TOpener]]]
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIApplication
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class SharedDataMiddleware:
|
||||
|
||||
"""A WSGI middleware which provides static content for development
|
||||
environments or simple server setups. Its usage is quite simple::
|
||||
|
||||
import os
|
||||
from werkzeug.middleware.shared_data import SharedDataMiddleware
|
||||
|
||||
app = SharedDataMiddleware(app, {
|
||||
'/shared': os.path.join(os.path.dirname(__file__), 'shared')
|
||||
})
|
||||
|
||||
The contents of the folder ``./shared`` will now be available on
|
||||
``http://example.com/shared/``. This is pretty useful during development
|
||||
because a standalone media server is not required. Files can also be
|
||||
mounted on the root folder and still continue to use the application because
|
||||
the shared data middleware forwards all unhandled requests to the
|
||||
application, even if the requests are below one of the shared folders.
|
||||
|
||||
If `pkg_resources` is available you can also tell the middleware to serve
|
||||
files from package data::
|
||||
|
||||
app = SharedDataMiddleware(app, {
|
||||
'/static': ('myapplication', 'static')
|
||||
})
|
||||
|
||||
This will then serve the ``static`` folder in the `myapplication`
|
||||
Python package.
|
||||
|
||||
The optional `disallow` parameter can be a list of :func:`~fnmatch.fnmatch`
|
||||
rules for files that are not accessible from the web. If `cache` is set to
|
||||
`False` no caching headers are sent.
|
||||
|
||||
Currently the middleware does not support non-ASCII filenames. If the
|
||||
encoding on the file system happens to match the encoding of the URI it may
|
||||
work but this could also be by accident. We strongly suggest using ASCII
|
||||
only file names for static files.
|
||||
|
||||
The middleware will guess the mimetype using the Python `mimetype`
|
||||
module. If it's unable to figure out the charset it will fall back
|
||||
to `fallback_mimetype`.
|
||||
|
||||
:param app: the application to wrap. If you don't want to wrap an
|
||||
application you can pass it :exc:`NotFound`.
|
||||
:param exports: a list or dict of exported files and folders.
|
||||
:param disallow: a list of :func:`~fnmatch.fnmatch` rules.
|
||||
:param cache: enable or disable caching headers.
|
||||
:param cache_timeout: the cache timeout in seconds for the headers.
|
||||
:param fallback_mimetype: The fallback mimetype for unknown files.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
The default ``fallback_mimetype`` is
|
||||
``application/octet-stream``. If a filename looks like a text
|
||||
mimetype, the ``utf-8`` charset is added to it.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
Added ``fallback_mimetype``.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
Added ``cache_timeout``.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app: "WSGIApplication",
|
||||
exports: t.Union[
|
||||
t.Dict[str, t.Union[str, t.Tuple[str, str]]],
|
||||
t.Iterable[t.Tuple[str, t.Union[str, t.Tuple[str, str]]]],
|
||||
],
|
||||
disallow: None = None,
|
||||
cache: bool = True,
|
||||
cache_timeout: int = 60 * 60 * 12,
|
||||
fallback_mimetype: str = "application/octet-stream",
|
||||
) -> None:
|
||||
self.app = app
|
||||
self.exports: t.List[t.Tuple[str, _TLoader]] = []
|
||||
self.cache = cache
|
||||
self.cache_timeout = cache_timeout
|
||||
|
||||
if isinstance(exports, dict):
|
||||
exports = exports.items()
|
||||
|
||||
for key, value in exports:
|
||||
if isinstance(value, tuple):
|
||||
loader = self.get_package_loader(*value)
|
||||
elif isinstance(value, str):
|
||||
if os.path.isfile(value):
|
||||
loader = self.get_file_loader(value)
|
||||
else:
|
||||
loader = self.get_directory_loader(value)
|
||||
else:
|
||||
raise TypeError(f"unknown def {value!r}")
|
||||
|
||||
self.exports.append((key, loader))
|
||||
|
||||
if disallow is not None:
|
||||
from fnmatch import fnmatch
|
||||
|
||||
self.is_allowed = lambda x: not fnmatch(x, disallow)
|
||||
|
||||
self.fallback_mimetype = fallback_mimetype
|
||||
|
||||
def is_allowed(self, filename: str) -> bool:
|
||||
"""Subclasses can override this method to disallow the access to
|
||||
certain files. However by providing `disallow` in the constructor
|
||||
this method is overwritten.
|
||||
"""
|
||||
return True
|
||||
|
||||
def _opener(self, filename: str) -> _TOpener:
|
||||
return lambda: (
|
||||
open(filename, "rb"),
|
||||
datetime.fromtimestamp(os.path.getmtime(filename), tz=timezone.utc),
|
||||
int(os.path.getsize(filename)),
|
||||
)
|
||||
|
||||
def get_file_loader(self, filename: str) -> _TLoader:
|
||||
return lambda x: (os.path.basename(filename), self._opener(filename))
|
||||
|
||||
def get_package_loader(self, package: str, package_path: str) -> _TLoader:
|
||||
load_time = datetime.now(timezone.utc)
|
||||
provider = pkgutil.get_loader(package)
|
||||
|
||||
if hasattr(provider, "get_resource_reader"):
|
||||
# Python 3
|
||||
reader = provider.get_resource_reader(package) # type: ignore
|
||||
|
||||
def loader(
|
||||
path: t.Optional[str],
|
||||
) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]:
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
path = safe_join(package_path, path)
|
||||
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
basename = posixpath.basename(path)
|
||||
|
||||
try:
|
||||
resource = reader.open_resource(path)
|
||||
except OSError:
|
||||
return None, None
|
||||
|
||||
if isinstance(resource, BytesIO):
|
||||
return (
|
||||
basename,
|
||||
lambda: (resource, load_time, len(resource.getvalue())),
|
||||
)
|
||||
|
||||
return (
|
||||
basename,
|
||||
lambda: (
|
||||
resource,
|
||||
datetime.fromtimestamp(
|
||||
os.path.getmtime(resource.name), tz=timezone.utc
|
||||
),
|
||||
os.path.getsize(resource.name),
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
# Python 3.6
|
||||
package_filename = provider.get_filename(package) # type: ignore
|
||||
is_filesystem = os.path.exists(package_filename)
|
||||
root = os.path.join(os.path.dirname(package_filename), package_path)
|
||||
|
||||
def loader(
|
||||
path: t.Optional[str],
|
||||
) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]:
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
path = safe_join(root, path)
|
||||
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
basename = posixpath.basename(path)
|
||||
|
||||
if is_filesystem:
|
||||
if not os.path.isfile(path):
|
||||
return None, None
|
||||
|
||||
return basename, self._opener(path)
|
||||
|
||||
try:
|
||||
data = provider.get_data(path) # type: ignore
|
||||
except OSError:
|
||||
return None, None
|
||||
|
||||
return basename, lambda: (BytesIO(data), load_time, len(data))
|
||||
|
||||
return loader
|
||||
|
||||
def get_directory_loader(self, directory: str) -> _TLoader:
|
||||
def loader(
|
||||
path: t.Optional[str],
|
||||
) -> t.Tuple[t.Optional[str], t.Optional[_TOpener]]:
|
||||
if path is not None:
|
||||
path = safe_join(directory, path)
|
||||
|
||||
if path is None:
|
||||
return None, None
|
||||
else:
|
||||
path = directory
|
||||
|
||||
if os.path.isfile(path):
|
||||
return os.path.basename(path), self._opener(path)
|
||||
|
||||
return None, None
|
||||
|
||||
return loader
|
||||
|
||||
def generate_etag(self, mtime: datetime, file_size: int, real_filename: str) -> str:
|
||||
if not isinstance(real_filename, bytes):
|
||||
real_filename = real_filename.encode( # type: ignore
|
||||
get_filesystem_encoding()
|
||||
)
|
||||
|
||||
timestamp = mtime.timestamp()
|
||||
checksum = adler32(real_filename) & 0xFFFFFFFF # type: ignore
|
||||
return f"wzsdm-{timestamp}-{file_size}-{checksum}"
|
||||
|
||||
def __call__(
|
||||
self, environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
path = get_path_info(environ)
|
||||
file_loader = None
|
||||
|
||||
for search_path, loader in self.exports:
|
||||
if search_path == path:
|
||||
real_filename, file_loader = loader(None)
|
||||
|
||||
if file_loader is not None:
|
||||
break
|
||||
|
||||
if not search_path.endswith("/"):
|
||||
search_path += "/"
|
||||
|
||||
if path.startswith(search_path):
|
||||
real_filename, file_loader = loader(path[len(search_path) :])
|
||||
|
||||
if file_loader is not None:
|
||||
break
|
||||
|
||||
if file_loader is None or not self.is_allowed(real_filename): # type: ignore
|
||||
return self.app(environ, start_response)
|
||||
|
||||
guessed_type = mimetypes.guess_type(real_filename) # type: ignore
|
||||
mime_type = get_content_type(guessed_type[0] or self.fallback_mimetype, "utf-8")
|
||||
f, mtime, file_size = file_loader()
|
||||
|
||||
headers = [("Date", http_date())]
|
||||
|
||||
if self.cache:
|
||||
timeout = self.cache_timeout
|
||||
etag = self.generate_etag(mtime, file_size, real_filename) # type: ignore
|
||||
headers += [
|
||||
("Etag", f'"{etag}"'),
|
||||
("Cache-Control", f"max-age={timeout}, public"),
|
||||
]
|
||||
|
||||
if not is_resource_modified(environ, etag, last_modified=mtime):
|
||||
f.close()
|
||||
start_response("304 Not Modified", headers)
|
||||
return []
|
||||
|
||||
headers.append(("Expires", http_date(time() + timeout)))
|
||||
else:
|
||||
headers.append(("Cache-Control", "public"))
|
||||
|
||||
headers.extend(
|
||||
(
|
||||
("Content-Type", mime_type),
|
||||
("Content-Length", str(file_size)),
|
||||
("Last-Modified", http_date(mtime)),
|
||||
)
|
||||
)
|
||||
start_response("200 OK", headers)
|
||||
return wrap_file(environ, f)
|
||||
2342
myvenv/lib/python3.10/site-packages/werkzeug/routing.py
Normal file
2342
myvenv/lib/python3.10/site-packages/werkzeug/routing.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
260
myvenv/lib/python3.10/site-packages/werkzeug/sansio/multipart.py
Normal file
260
myvenv/lib/python3.10/site-packages/werkzeug/sansio/multipart.py
Normal file
@ -0,0 +1,260 @@
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import auto
|
||||
from enum import Enum
|
||||
from typing import cast
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
from .._internal import _to_bytes
|
||||
from .._internal import _to_str
|
||||
from ..datastructures import Headers
|
||||
from ..exceptions import RequestEntityTooLarge
|
||||
from ..http import parse_options_header
|
||||
|
||||
|
||||
class Event:
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Preamble(Event):
|
||||
data: bytes
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Field(Event):
|
||||
name: str
|
||||
headers: Headers
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class File(Event):
|
||||
name: str
|
||||
filename: str
|
||||
headers: Headers
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Data(Event):
|
||||
data: bytes
|
||||
more_data: bool
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Epilogue(Event):
|
||||
data: bytes
|
||||
|
||||
|
||||
class NeedData(Event):
|
||||
pass
|
||||
|
||||
|
||||
NEED_DATA = NeedData()
|
||||
|
||||
|
||||
class State(Enum):
|
||||
PREAMBLE = auto()
|
||||
PART = auto()
|
||||
DATA = auto()
|
||||
EPILOGUE = auto()
|
||||
COMPLETE = auto()
|
||||
|
||||
|
||||
# Multipart line breaks MUST be CRLF (\r\n) by RFC-7578, except that
|
||||
# many implementations break this and either use CR or LF alone.
|
||||
LINE_BREAK = b"(?:\r\n|\n|\r)"
|
||||
BLANK_LINE_RE = re.compile(b"(?:\r\n\r\n|\r\r|\n\n)", re.MULTILINE)
|
||||
LINE_BREAK_RE = re.compile(LINE_BREAK, re.MULTILINE)
|
||||
# Header values can be continued via a space or tab after the linebreak, as
|
||||
# per RFC2231
|
||||
HEADER_CONTINUATION_RE = re.compile(b"%s[ \t]" % LINE_BREAK, re.MULTILINE)
|
||||
|
||||
|
||||
class MultipartDecoder:
|
||||
"""Decodes a multipart message as bytes into Python events.
|
||||
|
||||
The part data is returned as available to allow the caller to save
|
||||
the data from memory to disk, if desired.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
boundary: bytes,
|
||||
max_form_memory_size: Optional[int] = None,
|
||||
) -> None:
|
||||
self.buffer = bytearray()
|
||||
self.complete = False
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.state = State.PREAMBLE
|
||||
self.boundary = boundary
|
||||
|
||||
# Note in the below \h i.e. horizontal whitespace is used
|
||||
# as [^\S\n\r] as \h isn't supported in python.
|
||||
|
||||
# The preamble must end with a boundary where the boundary is
|
||||
# prefixed by a line break, RFC2046. Except that many
|
||||
# implementations including Werkzeug's tests omit the line
|
||||
# break prefix. In addition the first boundary could be the
|
||||
# epilogue boundary (for empty form-data) hence the matching
|
||||
# group to understand if it is an epilogue boundary.
|
||||
self.preamble_re = re.compile(
|
||||
rb"%s?--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)"
|
||||
% (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
|
||||
re.MULTILINE,
|
||||
)
|
||||
# A boundary must include a line break prefix and suffix, and
|
||||
# may include trailing whitespace. In addition the boundary
|
||||
# could be the epilogue boundary hence the matching group to
|
||||
# understand if it is an epilogue boundary.
|
||||
self.boundary_re = re.compile(
|
||||
rb"%s--%s(--[^\S\n\r]*%s?|[^\S\n\r]*%s)"
|
||||
% (LINE_BREAK, re.escape(boundary), LINE_BREAK, LINE_BREAK),
|
||||
re.MULTILINE,
|
||||
)
|
||||
|
||||
def last_newline(self) -> int:
|
||||
try:
|
||||
last_nl = self.buffer.rindex(b"\n")
|
||||
except ValueError:
|
||||
last_nl = len(self.buffer)
|
||||
try:
|
||||
last_cr = self.buffer.rindex(b"\r")
|
||||
except ValueError:
|
||||
last_cr = len(self.buffer)
|
||||
|
||||
return min(last_nl, last_cr)
|
||||
|
||||
def receive_data(self, data: Optional[bytes]) -> None:
|
||||
if data is None:
|
||||
self.complete = True
|
||||
elif (
|
||||
self.max_form_memory_size is not None
|
||||
and len(self.buffer) + len(data) > self.max_form_memory_size
|
||||
):
|
||||
raise RequestEntityTooLarge()
|
||||
else:
|
||||
self.buffer.extend(data)
|
||||
|
||||
def next_event(self) -> Event:
|
||||
event: Event = NEED_DATA
|
||||
|
||||
if self.state == State.PREAMBLE:
|
||||
match = self.preamble_re.search(self.buffer)
|
||||
if match is not None:
|
||||
if match.group(1).startswith(b"--"):
|
||||
self.state = State.EPILOGUE
|
||||
else:
|
||||
self.state = State.PART
|
||||
data = bytes(self.buffer[: match.start()])
|
||||
del self.buffer[: match.end()]
|
||||
event = Preamble(data=data)
|
||||
|
||||
elif self.state == State.PART:
|
||||
match = BLANK_LINE_RE.search(self.buffer)
|
||||
if match is not None:
|
||||
headers = self._parse_headers(self.buffer[: match.start()])
|
||||
del self.buffer[: match.end()]
|
||||
|
||||
if "content-disposition" not in headers:
|
||||
raise ValueError("Missing Content-Disposition header")
|
||||
|
||||
disposition, extra = parse_options_header(
|
||||
headers["content-disposition"]
|
||||
)
|
||||
name = cast(str, extra.get("name"))
|
||||
filename = extra.get("filename")
|
||||
if filename is not None:
|
||||
event = File(
|
||||
filename=filename,
|
||||
headers=headers,
|
||||
name=name,
|
||||
)
|
||||
else:
|
||||
event = Field(
|
||||
headers=headers,
|
||||
name=name,
|
||||
)
|
||||
self.state = State.DATA
|
||||
|
||||
elif self.state == State.DATA:
|
||||
if self.buffer.find(b"--" + self.boundary) == -1:
|
||||
# No complete boundary in the buffer, but there may be
|
||||
# a partial boundary at the end. As the boundary
|
||||
# starts with either a nl or cr find the earliest and
|
||||
# return up to that as data.
|
||||
data_length = del_index = self.last_newline()
|
||||
more_data = True
|
||||
else:
|
||||
match = self.boundary_re.search(self.buffer)
|
||||
if match is not None:
|
||||
if match.group(1).startswith(b"--"):
|
||||
self.state = State.EPILOGUE
|
||||
else:
|
||||
self.state = State.PART
|
||||
data_length = match.start()
|
||||
del_index = match.end()
|
||||
else:
|
||||
data_length = del_index = self.last_newline()
|
||||
more_data = match is None
|
||||
|
||||
data = bytes(self.buffer[:data_length])
|
||||
del self.buffer[:del_index]
|
||||
if data or not more_data:
|
||||
event = Data(data=data, more_data=more_data)
|
||||
|
||||
elif self.state == State.EPILOGUE and self.complete:
|
||||
event = Epilogue(data=bytes(self.buffer))
|
||||
del self.buffer[:]
|
||||
self.state = State.COMPLETE
|
||||
|
||||
if self.complete and isinstance(event, NeedData):
|
||||
raise ValueError(f"Invalid form-data cannot parse beyond {self.state}")
|
||||
|
||||
return event
|
||||
|
||||
def _parse_headers(self, data: bytes) -> Headers:
|
||||
headers: List[Tuple[str, str]] = []
|
||||
# Merge the continued headers into one line
|
||||
data = HEADER_CONTINUATION_RE.sub(b" ", data)
|
||||
# Now there is one header per line
|
||||
for line in data.splitlines():
|
||||
if line.strip() != b"":
|
||||
name, value = _to_str(line).strip().split(":", 1)
|
||||
headers.append((name.strip(), value.strip()))
|
||||
return Headers(headers)
|
||||
|
||||
|
||||
class MultipartEncoder:
|
||||
def __init__(self, boundary: bytes) -> None:
|
||||
self.boundary = boundary
|
||||
self.state = State.PREAMBLE
|
||||
|
||||
def send_event(self, event: Event) -> bytes:
|
||||
if isinstance(event, Preamble) and self.state == State.PREAMBLE:
|
||||
self.state = State.PART
|
||||
return event.data
|
||||
elif isinstance(event, (Field, File)) and self.state in {
|
||||
State.PREAMBLE,
|
||||
State.PART,
|
||||
State.DATA,
|
||||
}:
|
||||
self.state = State.DATA
|
||||
data = b"\r\n--" + self.boundary + b"\r\n"
|
||||
data += b'Content-Disposition: form-data; name="%s"' % _to_bytes(event.name)
|
||||
if isinstance(event, File):
|
||||
data += b'; filename="%s"' % _to_bytes(event.filename)
|
||||
data += b"\r\n"
|
||||
for name, value in cast(Field, event).headers:
|
||||
if name.lower() != "content-disposition":
|
||||
data += _to_bytes(f"{name}: {value}\r\n")
|
||||
data += b"\r\n"
|
||||
return data
|
||||
elif isinstance(event, Data) and self.state == State.DATA:
|
||||
return event.data
|
||||
elif isinstance(event, Epilogue):
|
||||
self.state = State.COMPLETE
|
||||
return b"\r\n--" + self.boundary + b"--\r\n" + event.data
|
||||
else:
|
||||
raise ValueError(f"Cannot generate {event} in state: {self.state}")
|
||||
548
myvenv/lib/python3.10/site-packages/werkzeug/sansio/request.py
Normal file
548
myvenv/lib/python3.10/site-packages/werkzeug/sansio/request.py
Normal file
@ -0,0 +1,548 @@
|
||||
import typing as t
|
||||
from datetime import datetime
|
||||
|
||||
from .._internal import _to_str
|
||||
from ..datastructures import Accept
|
||||
from ..datastructures import Authorization
|
||||
from ..datastructures import CharsetAccept
|
||||
from ..datastructures import ETags
|
||||
from ..datastructures import Headers
|
||||
from ..datastructures import HeaderSet
|
||||
from ..datastructures import IfRange
|
||||
from ..datastructures import ImmutableList
|
||||
from ..datastructures import ImmutableMultiDict
|
||||
from ..datastructures import LanguageAccept
|
||||
from ..datastructures import MIMEAccept
|
||||
from ..datastructures import MultiDict
|
||||
from ..datastructures import Range
|
||||
from ..datastructures import RequestCacheControl
|
||||
from ..http import parse_accept_header
|
||||
from ..http import parse_authorization_header
|
||||
from ..http import parse_cache_control_header
|
||||
from ..http import parse_cookie
|
||||
from ..http import parse_date
|
||||
from ..http import parse_etags
|
||||
from ..http import parse_if_range_header
|
||||
from ..http import parse_list_header
|
||||
from ..http import parse_options_header
|
||||
from ..http import parse_range_header
|
||||
from ..http import parse_set_header
|
||||
from ..urls import url_decode
|
||||
from ..user_agent import UserAgent
|
||||
from ..useragents import _UserAgent as _DeprecatedUserAgent
|
||||
from ..utils import cached_property
|
||||
from ..utils import header_property
|
||||
from .utils import get_current_url
|
||||
from .utils import get_host
|
||||
|
||||
|
||||
class Request:
|
||||
"""Represents the non-IO parts of a HTTP request, including the
|
||||
method, URL info, and headers.
|
||||
|
||||
This class is not meant for general use. It should only be used when
|
||||
implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
|
||||
provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`.
|
||||
|
||||
:param method: The method the request was made with, such as
|
||||
``GET``.
|
||||
:param scheme: The URL scheme of the protocol the request used, such
|
||||
as ``https`` or ``wss``.
|
||||
:param server: The address of the server. ``(host, port)``,
|
||||
``(path, None)`` for unix sockets, or ``None`` if not known.
|
||||
:param root_path: The prefix that the application is mounted under.
|
||||
This is prepended to generated URLs, but is not part of route
|
||||
matching.
|
||||
:param path: The path part of the URL after ``root_path``.
|
||||
:param query_string: The part of the URL after the "?".
|
||||
:param headers: The headers received with the request.
|
||||
:param remote_addr: The address of the client sending the request.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
#: The charset used to decode most data in the request.
|
||||
charset = "utf-8"
|
||||
|
||||
#: the error handling procedure for errors, defaults to 'replace'
|
||||
encoding_errors = "replace"
|
||||
|
||||
#: the class to use for `args` and `form`. The default is an
|
||||
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
|
||||
#: multiple values per key. alternatively it makes sense to use an
|
||||
#: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
|
||||
#: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
|
||||
#: which is the fastest but only remembers the last key. It is also
|
||||
#: possible to use mutable structures, but this is not recommended.
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict
|
||||
|
||||
#: The type to be used for dict values from the incoming WSGI
|
||||
#: environment. (For example for :attr:`cookies`.) By default an
|
||||
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used.
|
||||
#:
|
||||
#: .. versionchanged:: 1.0.0
|
||||
#: Changed to ``ImmutableMultiDict`` to support multiple values.
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict
|
||||
|
||||
#: the type to be used for list values from the incoming WSGI environment.
|
||||
#: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
|
||||
#: (for example for :attr:`access_list`).
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
list_storage_class: t.Type[t.List] = ImmutableList
|
||||
|
||||
user_agent_class: t.Type[UserAgent] = _DeprecatedUserAgent
|
||||
"""The class used and returned by the :attr:`user_agent` property to
|
||||
parse the header. Defaults to
|
||||
:class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An
|
||||
extension can provide a subclass that uses a parser to provide other
|
||||
data.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
#: Valid host names when handling requests. By default all hosts are
|
||||
#: trusted, which means that whatever the client says the host is
|
||||
#: will be accepted.
|
||||
#:
|
||||
#: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to
|
||||
#: any value by a malicious client, it is recommended to either set
|
||||
#: this property or implement similar validation in the proxy (if
|
||||
#: the application is being run behind one).
|
||||
#:
|
||||
#: .. versionadded:: 0.9
|
||||
trusted_hosts: t.Optional[t.List[str]] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
method: str,
|
||||
scheme: str,
|
||||
server: t.Optional[t.Tuple[str, t.Optional[int]]],
|
||||
root_path: str,
|
||||
path: str,
|
||||
query_string: bytes,
|
||||
headers: Headers,
|
||||
remote_addr: t.Optional[str],
|
||||
) -> None:
|
||||
#: The method the request was made with, such as ``GET``.
|
||||
self.method = method.upper()
|
||||
#: The URL scheme of the protocol the request used, such as
|
||||
#: ``https`` or ``wss``.
|
||||
self.scheme = scheme
|
||||
#: The address of the server. ``(host, port)``, ``(path, None)``
|
||||
#: for unix sockets, or ``None`` if not known.
|
||||
self.server = server
|
||||
#: The prefix that the application is mounted under, without a
|
||||
#: trailing slash. :attr:`path` comes after this.
|
||||
self.root_path = root_path.rstrip("/")
|
||||
#: The path part of the URL after :attr:`root_path`. This is the
|
||||
#: path used for routing within the application.
|
||||
self.path = "/" + path.lstrip("/")
|
||||
#: The part of the URL after the "?". This is the raw value, use
|
||||
#: :attr:`args` for the parsed values.
|
||||
self.query_string = query_string
|
||||
#: The headers received with the request.
|
||||
self.headers = headers
|
||||
#: The address of the client sending the request.
|
||||
self.remote_addr = remote_addr
|
||||
|
||||
def __repr__(self) -> str:
|
||||
try:
|
||||
url = self.url
|
||||
except Exception as e:
|
||||
url = f"(invalid URL: {e})"
|
||||
|
||||
return f"<{type(self).__name__} {url!r} [{self.method}]>"
|
||||
|
||||
@property
|
||||
def url_charset(self) -> str:
|
||||
"""The charset that is assumed for URLs. Defaults to the value
|
||||
of :attr:`charset`.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
return self.charset
|
||||
|
||||
@cached_property
|
||||
def args(self) -> "MultiDict[str, str]":
|
||||
"""The parsed URL parameters (the part in the URL after the question
|
||||
mark).
|
||||
|
||||
By default an
|
||||
:class:`~werkzeug.datastructures.ImmutableMultiDict`
|
||||
is returned from this function. This can be changed by setting
|
||||
:attr:`parameter_storage_class` to a different type. This might
|
||||
be necessary if the order of the form data is important.
|
||||
"""
|
||||
return url_decode(
|
||||
self.query_string,
|
||||
self.url_charset,
|
||||
errors=self.encoding_errors,
|
||||
cls=self.parameter_storage_class,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def access_route(self) -> t.List[str]:
|
||||
"""If a forwarded header exists this is a list of all ip addresses
|
||||
from the client ip to the last proxy server.
|
||||
"""
|
||||
if "X-Forwarded-For" in self.headers:
|
||||
return self.list_storage_class(
|
||||
parse_list_header(self.headers["X-Forwarded-For"])
|
||||
)
|
||||
elif self.remote_addr is not None:
|
||||
return self.list_storage_class([self.remote_addr])
|
||||
return self.list_storage_class()
|
||||
|
||||
@cached_property
|
||||
def full_path(self) -> str:
|
||||
"""Requested path, including the query string."""
|
||||
return f"{self.path}?{_to_str(self.query_string, self.url_charset)}"
|
||||
|
||||
@property
|
||||
def is_secure(self) -> bool:
|
||||
"""``True`` if the request was made with a secure protocol
|
||||
(HTTPS or WSS).
|
||||
"""
|
||||
return self.scheme in {"https", "wss"}
|
||||
|
||||
@cached_property
|
||||
def url(self) -> str:
|
||||
"""The full request URL with the scheme, host, root path, path,
|
||||
and query string."""
|
||||
return get_current_url(
|
||||
self.scheme, self.host, self.root_path, self.path, self.query_string
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def base_url(self) -> str:
|
||||
"""Like :attr:`url` but without the query string."""
|
||||
return get_current_url(self.scheme, self.host, self.root_path, self.path)
|
||||
|
||||
@cached_property
|
||||
def root_url(self) -> str:
|
||||
"""The request URL scheme, host, and root path. This is the root
|
||||
that the application is accessed from.
|
||||
"""
|
||||
return get_current_url(self.scheme, self.host, self.root_path)
|
||||
|
||||
@cached_property
|
||||
def host_url(self) -> str:
|
||||
"""The request URL scheme and host only."""
|
||||
return get_current_url(self.scheme, self.host)
|
||||
|
||||
@cached_property
|
||||
def host(self) -> str:
|
||||
"""The host name the request was made to, including the port if
|
||||
it's non-standard. Validated with :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_host(
|
||||
self.scheme, self.headers.get("host"), self.server, self.trusted_hosts
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def cookies(self) -> "ImmutableMultiDict[str, str]":
|
||||
"""A :class:`dict` with the contents of all cookies transmitted with
|
||||
the request."""
|
||||
wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
|
||||
return parse_cookie( # type: ignore
|
||||
wsgi_combined_cookie,
|
||||
self.charset,
|
||||
self.encoding_errors,
|
||||
cls=self.dict_storage_class,
|
||||
)
|
||||
|
||||
# Common Descriptors
|
||||
|
||||
content_type = header_property[str](
|
||||
"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.""",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def content_length(self) -> t.Optional[int]:
|
||||
"""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.
|
||||
"""
|
||||
if self.headers.get("Transfer-Encoding", "") == "chunked":
|
||||
return None
|
||||
|
||||
content_length = self.headers.get("Content-Length")
|
||||
if content_length is not None:
|
||||
try:
|
||||
return max(0, int(content_length))
|
||||
except (ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
content_encoding = header_property[str](
|
||||
"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""",
|
||||
read_only=True,
|
||||
)
|
||||
content_md5 = header_property[str](
|
||||
"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""",
|
||||
read_only=True,
|
||||
)
|
||||
referrer = header_property[str](
|
||||
"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).""",
|
||||
read_only=True,
|
||||
)
|
||||
date = header_property(
|
||||
"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.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
""",
|
||||
read_only=True,
|
||||
)
|
||||
max_forwards = header_property(
|
||||
"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.""",
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
def _parse_content_type(self) -> None:
|
||||
if not hasattr(self, "_parsed_content_type"):
|
||||
self._parsed_content_type = parse_options_header(
|
||||
self.headers.get("Content-Type", "")
|
||||
)
|
||||
|
||||
@property
|
||||
def mimetype(self) -> str:
|
||||
"""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) -> t.Dict[str, str]:
|
||||
"""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) -> HeaderSet:
|
||||
"""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.headers.get("Pragma", ""))
|
||||
|
||||
# Accept
|
||||
|
||||
@cached_property
|
||||
def accept_mimetypes(self) -> MIMEAccept:
|
||||
"""List of mimetypes this client supports as
|
||||
:class:`~werkzeug.datastructures.MIMEAccept` object.
|
||||
"""
|
||||
return parse_accept_header(self.headers.get("Accept"), MIMEAccept)
|
||||
|
||||
@cached_property
|
||||
def accept_charsets(self) -> CharsetAccept:
|
||||
"""List of charsets this client supports as
|
||||
:class:`~werkzeug.datastructures.CharsetAccept` object.
|
||||
"""
|
||||
return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept)
|
||||
|
||||
@cached_property
|
||||
def accept_encodings(self) -> Accept:
|
||||
"""List of encodings this client accepts. Encodings in a HTTP term
|
||||
are compression encodings such as gzip. For charsets have a look at
|
||||
:attr:`accept_charset`.
|
||||
"""
|
||||
return parse_accept_header(self.headers.get("Accept-Encoding"))
|
||||
|
||||
@cached_property
|
||||
def accept_languages(self) -> LanguageAccept:
|
||||
"""List of languages this client accepts as
|
||||
:class:`~werkzeug.datastructures.LanguageAccept` object.
|
||||
|
||||
.. versionchanged 0.5
|
||||
In previous versions this was a regular
|
||||
:class:`~werkzeug.datastructures.Accept` object.
|
||||
"""
|
||||
return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept)
|
||||
|
||||
# ETag
|
||||
|
||||
@cached_property
|
||||
def cache_control(self) -> RequestCacheControl:
|
||||
"""A :class:`~werkzeug.datastructures.RequestCacheControl` object
|
||||
for the incoming cache control headers.
|
||||
"""
|
||||
cache_control = self.headers.get("Cache-Control")
|
||||
return parse_cache_control_header(cache_control, None, RequestCacheControl)
|
||||
|
||||
@cached_property
|
||||
def if_match(self) -> ETags:
|
||||
"""An object containing all the etags in the `If-Match` header.
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.headers.get("If-Match"))
|
||||
|
||||
@cached_property
|
||||
def if_none_match(self) -> ETags:
|
||||
"""An object containing all the etags in the `If-None-Match` header.
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.headers.get("If-None-Match"))
|
||||
|
||||
@cached_property
|
||||
def if_modified_since(self) -> t.Optional[datetime]:
|
||||
"""The parsed `If-Modified-Since` header as a datetime object.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
"""
|
||||
return parse_date(self.headers.get("If-Modified-Since"))
|
||||
|
||||
@cached_property
|
||||
def if_unmodified_since(self) -> t.Optional[datetime]:
|
||||
"""The parsed `If-Unmodified-Since` header as a datetime object.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
"""
|
||||
return parse_date(self.headers.get("If-Unmodified-Since"))
|
||||
|
||||
@cached_property
|
||||
def if_range(self) -> IfRange:
|
||||
"""The parsed ``If-Range`` header.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
``IfRange.date`` is timezone-aware.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
return parse_if_range_header(self.headers.get("If-Range"))
|
||||
|
||||
@cached_property
|
||||
def range(self) -> t.Optional[Range]:
|
||||
"""The parsed `Range` header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.Range`
|
||||
"""
|
||||
return parse_range_header(self.headers.get("Range"))
|
||||
|
||||
# User Agent
|
||||
|
||||
@cached_property
|
||||
def user_agent(self) -> UserAgent:
|
||||
"""The user agent. Use ``user_agent.string`` to get the header
|
||||
value. Set :attr:`user_agent_class` to a subclass of
|
||||
:class:`~werkzeug.user_agent.UserAgent` to provide parsing for
|
||||
the other properties or other extended data.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The built in parser is deprecated and will be removed in
|
||||
Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse
|
||||
data from the string.
|
||||
"""
|
||||
return self.user_agent_class(self.headers.get("User-Agent", ""))
|
||||
|
||||
# Authorization
|
||||
|
||||
@cached_property
|
||||
def authorization(self) -> t.Optional[Authorization]:
|
||||
"""The `Authorization` object in parsed form."""
|
||||
return parse_authorization_header(self.headers.get("Authorization"))
|
||||
|
||||
# CORS
|
||||
|
||||
origin = header_property[str](
|
||||
"Origin",
|
||||
doc=(
|
||||
"The host that the request originated from. Set"
|
||||
" :attr:`~CORSResponseMixin.access_control_allow_origin` on"
|
||||
" the response to indicate which origins are allowed."
|
||||
),
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
access_control_request_headers = header_property(
|
||||
"Access-Control-Request-Headers",
|
||||
load_func=parse_set_header,
|
||||
doc=(
|
||||
"Sent with a preflight request to indicate which headers"
|
||||
" will be sent with the cross origin request. Set"
|
||||
" :attr:`~CORSResponseMixin.access_control_allow_headers`"
|
||||
" on the response to indicate which headers are allowed."
|
||||
),
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
access_control_request_method = header_property[str](
|
||||
"Access-Control-Request-Method",
|
||||
doc=(
|
||||
"Sent with a preflight request to indicate which method"
|
||||
" will be used for the cross origin request. Set"
|
||||
" :attr:`~CORSResponseMixin.access_control_allow_methods`"
|
||||
" on the response to indicate which methods are allowed."
|
||||
),
|
||||
read_only=True,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_json(self) -> bool:
|
||||
"""Check if the mimetype indicates JSON data, either
|
||||
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
||||
"""
|
||||
mt = self.mimetype
|
||||
return (
|
||||
mt == "application/json"
|
||||
or mt.startswith("application/")
|
||||
and mt.endswith("+json")
|
||||
)
|
||||
704
myvenv/lib/python3.10/site-packages/werkzeug/sansio/response.py
Normal file
704
myvenv/lib/python3.10/site-packages/werkzeug/sansio/response.py
Normal file
@ -0,0 +1,704 @@
|
||||
import typing as t
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import timezone
|
||||
from http import HTTPStatus
|
||||
|
||||
from .._internal import _to_str
|
||||
from ..datastructures import Headers
|
||||
from ..datastructures import HeaderSet
|
||||
from ..http import dump_cookie
|
||||
from ..http import HTTP_STATUS_CODES
|
||||
from ..utils import get_content_type
|
||||
from werkzeug.datastructures import CallbackDict
|
||||
from werkzeug.datastructures import ContentRange
|
||||
from werkzeug.datastructures import ContentSecurityPolicy
|
||||
from werkzeug.datastructures import ResponseCacheControl
|
||||
from werkzeug.datastructures import WWWAuthenticate
|
||||
from werkzeug.http import COEP
|
||||
from werkzeug.http import COOP
|
||||
from werkzeug.http import dump_age
|
||||
from werkzeug.http import dump_header
|
||||
from werkzeug.http import dump_options_header
|
||||
from werkzeug.http import http_date
|
||||
from werkzeug.http import parse_age
|
||||
from werkzeug.http import parse_cache_control_header
|
||||
from werkzeug.http import parse_content_range_header
|
||||
from werkzeug.http import parse_csp_header
|
||||
from werkzeug.http import parse_date
|
||||
from werkzeug.http import parse_options_header
|
||||
from werkzeug.http import parse_set_header
|
||||
from werkzeug.http import parse_www_authenticate_header
|
||||
from werkzeug.http import quote_etag
|
||||
from werkzeug.http import unquote_etag
|
||||
from werkzeug.utils import header_property
|
||||
|
||||
|
||||
def _set_property(name: str, doc: t.Optional[str] = None) -> property:
|
||||
def fget(self: "Response") -> HeaderSet:
|
||||
def on_update(header_set: HeaderSet) -> None:
|
||||
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: "Response",
|
||||
value: t.Optional[
|
||||
t.Union[str, t.Dict[str, t.Union[str, int]], t.Iterable[str]]
|
||||
],
|
||||
) -> None:
|
||||
if not value:
|
||||
del self.headers[name]
|
||||
elif isinstance(value, str):
|
||||
self.headers[name] = value
|
||||
else:
|
||||
self.headers[name] = dump_header(value)
|
||||
|
||||
return property(fget, fset, doc=doc)
|
||||
|
||||
|
||||
class Response:
|
||||
"""Represents the non-IO parts of an HTTP response, specifically the
|
||||
status and headers but not the body.
|
||||
|
||||
This class is not meant for general use. It should only be used when
|
||||
implementing WSGI, ASGI, or another HTTP application spec. Werkzeug
|
||||
provides a WSGI implementation at :cls:`werkzeug.wrappers.Response`.
|
||||
|
||||
:param status: The status code for the response. Either an int, in
|
||||
which case the default status message is added, or a string in
|
||||
the form ``{code} {message}``, like ``404 Not Found``. Defaults
|
||||
to 200.
|
||||
:param headers: A :class:`~werkzeug.datastructures.Headers` object,
|
||||
or a list of ``(key, value)`` tuples that will be converted to a
|
||||
``Headers`` object.
|
||||
:param mimetype: The mime type (content type without charset or
|
||||
other parameters) of the response. If the value starts with
|
||||
``text/`` (or matches some other special cases), the charset
|
||||
will be added to create the ``content_type``.
|
||||
:param content_type: The full content type of the response.
|
||||
Overrides building the value from ``mimetype``.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
#: the charset of the response.
|
||||
charset = "utf-8"
|
||||
|
||||
#: the default status if none is provided.
|
||||
default_status = 200
|
||||
|
||||
#: the default mimetype if none is provided.
|
||||
default_mimetype = "text/plain"
|
||||
|
||||
#: Warn if a cookie header exceeds this size. The default, 4093, should be
|
||||
#: safely `supported by most browsers <cookie_>`_. A cookie larger than
|
||||
#: this size will still be sent, but it may be ignored or handled
|
||||
#: incorrectly by some browsers. Set to 0 to disable this check.
|
||||
#:
|
||||
#: .. versionadded:: 0.13
|
||||
#:
|
||||
#: .. _`cookie`: http://browsercookielimits.squawky.net/
|
||||
max_cookie_size = 4093
|
||||
|
||||
# A :class:`Headers` object representing the response headers.
|
||||
headers: Headers
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
status: t.Optional[t.Union[int, str, HTTPStatus]] = None,
|
||||
headers: t.Optional[
|
||||
t.Union[
|
||||
t.Mapping[str, t.Union[str, int, t.Iterable[t.Union[str, int]]]],
|
||||
t.Iterable[t.Tuple[str, t.Union[str, int]]],
|
||||
]
|
||||
] = None,
|
||||
mimetype: t.Optional[str] = None,
|
||||
content_type: t.Optional[str] = None,
|
||||
) -> None:
|
||||
if isinstance(headers, Headers):
|
||||
self.headers = headers
|
||||
elif not headers:
|
||||
self.headers = Headers()
|
||||
else:
|
||||
self.headers = Headers(headers)
|
||||
|
||||
if content_type is None:
|
||||
if mimetype is None and "content-type" not in self.headers:
|
||||
mimetype = self.default_mimetype
|
||||
if mimetype is not None:
|
||||
mimetype = get_content_type(mimetype, self.charset)
|
||||
content_type = mimetype
|
||||
if content_type is not None:
|
||||
self.headers["Content-Type"] = content_type
|
||||
if status is None:
|
||||
status = self.default_status
|
||||
self.status = status # type: ignore
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} [{self.status}]>"
|
||||
|
||||
@property
|
||||
def status_code(self) -> int:
|
||||
"""The HTTP status code as a number."""
|
||||
return self._status_code
|
||||
|
||||
@status_code.setter
|
||||
def status_code(self, code: int) -> None:
|
||||
self.status = code # type: ignore
|
||||
|
||||
@property
|
||||
def status(self) -> str:
|
||||
"""The HTTP status code as a string."""
|
||||
return self._status
|
||||
|
||||
@status.setter
|
||||
def status(self, value: t.Union[str, int, HTTPStatus]) -> None:
|
||||
if not isinstance(value, (str, bytes, int, HTTPStatus)):
|
||||
raise TypeError("Invalid status argument")
|
||||
|
||||
self._status, self._status_code = self._clean_status(value)
|
||||
|
||||
def _clean_status(self, value: t.Union[str, int, HTTPStatus]) -> t.Tuple[str, int]:
|
||||
if isinstance(value, HTTPStatus):
|
||||
value = int(value)
|
||||
status = _to_str(value, self.charset)
|
||||
split_status = status.split(None, 1)
|
||||
|
||||
if len(split_status) == 0:
|
||||
raise ValueError("Empty status argument")
|
||||
|
||||
if len(split_status) > 1:
|
||||
if split_status[0].isdigit():
|
||||
# code and message
|
||||
return status, int(split_status[0])
|
||||
|
||||
# multi-word message
|
||||
return f"0 {status}", 0
|
||||
|
||||
if split_status[0].isdigit():
|
||||
# code only
|
||||
status_code = int(split_status[0])
|
||||
|
||||
try:
|
||||
status = f"{status_code} {HTTP_STATUS_CODES[status_code].upper()}"
|
||||
except KeyError:
|
||||
status = f"{status_code} UNKNOWN"
|
||||
|
||||
return status, status_code
|
||||
|
||||
# one-word message
|
||||
return f"0 {status}", 0
|
||||
|
||||
def set_cookie(
|
||||
self,
|
||||
key: str,
|
||||
value: str = "",
|
||||
max_age: t.Optional[t.Union[timedelta, int]] = None,
|
||||
expires: t.Optional[t.Union[str, datetime, int, float]] = None,
|
||||
path: t.Optional[str] = "/",
|
||||
domain: t.Optional[str] = None,
|
||||
secure: bool = False,
|
||||
httponly: bool = False,
|
||||
samesite: t.Optional[str] = None,
|
||||
) -> None:
|
||||
"""Sets a cookie.
|
||||
|
||||
A warning is raised if the size of the cookie header exceeds
|
||||
:attr:`max_cookie_size`, but the header will still be set.
|
||||
|
||||
:param key: the key (name) of the cookie to be set.
|
||||
:param value: the value of the cookie.
|
||||
:param max_age: should be a number of seconds, or `None` (default) if
|
||||
the cookie should last only as long as the client's
|
||||
browser session.
|
||||
:param expires: should be a `datetime` object or UNIX timestamp.
|
||||
:param path: limits the cookie to a given path, per default it will
|
||||
span the whole domain.
|
||||
:param domain: if you want to set a cross-domain cookie. For example,
|
||||
``domain=".example.com"`` will set a cookie that is
|
||||
readable by the domain ``www.example.com``,
|
||||
``foo.example.com`` etc. Otherwise, a cookie will only
|
||||
be readable by the domain that set it.
|
||||
:param secure: If ``True``, the cookie will only be available
|
||||
via HTTPS.
|
||||
:param httponly: Disallow JavaScript access to the cookie.
|
||||
:param samesite: Limit the scope of the cookie to only be
|
||||
attached to requests that are "same-site".
|
||||
"""
|
||||
self.headers.add(
|
||||
"Set-Cookie",
|
||||
dump_cookie(
|
||||
key,
|
||||
value=value,
|
||||
max_age=max_age,
|
||||
expires=expires,
|
||||
path=path,
|
||||
domain=domain,
|
||||
secure=secure,
|
||||
httponly=httponly,
|
||||
charset=self.charset,
|
||||
max_size=self.max_cookie_size,
|
||||
samesite=samesite,
|
||||
),
|
||||
)
|
||||
|
||||
def delete_cookie(
|
||||
self,
|
||||
key: str,
|
||||
path: str = "/",
|
||||
domain: t.Optional[str] = None,
|
||||
secure: bool = False,
|
||||
httponly: bool = False,
|
||||
samesite: t.Optional[str] = None,
|
||||
) -> None:
|
||||
"""Delete a cookie. Fails silently if key doesn't exist.
|
||||
|
||||
:param key: the key (name) of the cookie to be deleted.
|
||||
:param path: if the cookie that should be deleted was limited to a
|
||||
path, the path has to be defined here.
|
||||
:param domain: if the cookie that should be deleted was limited to a
|
||||
domain, that domain has to be defined here.
|
||||
:param secure: If ``True``, the cookie will only be available
|
||||
via HTTPS.
|
||||
:param httponly: Disallow JavaScript access to the cookie.
|
||||
:param samesite: Limit the scope of the cookie to only be
|
||||
attached to requests that are "same-site".
|
||||
"""
|
||||
self.set_cookie(
|
||||
key,
|
||||
expires=0,
|
||||
max_age=0,
|
||||
path=path,
|
||||
domain=domain,
|
||||
secure=secure,
|
||||
httponly=httponly,
|
||||
samesite=samesite,
|
||||
)
|
||||
|
||||
@property
|
||||
def is_json(self) -> bool:
|
||||
"""Check if the mimetype indicates JSON data, either
|
||||
:mimetype:`application/json` or :mimetype:`application/*+json`.
|
||||
"""
|
||||
mt = self.mimetype
|
||||
return mt is not None and (
|
||||
mt == "application/json"
|
||||
or mt.startswith("application/")
|
||||
and mt.endswith("+json")
|
||||
)
|
||||
|
||||
# Common Descriptors
|
||||
|
||||
@property
|
||||
def mimetype(self) -> t.Optional[str]:
|
||||
"""The mimetype (content type without charset etc.)"""
|
||||
ct = self.headers.get("content-type")
|
||||
|
||||
if ct:
|
||||
return ct.split(";")[0].strip()
|
||||
else:
|
||||
return None
|
||||
|
||||
@mimetype.setter
|
||||
def mimetype(self, value: str) -> None:
|
||||
self.headers["Content-Type"] = get_content_type(value, self.charset)
|
||||
|
||||
@property
|
||||
def mimetype_params(self) -> t.Dict[str, str]:
|
||||
"""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: CallbackDict) -> None:
|
||||
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[str](
|
||||
"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, # type: ignore
|
||||
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[str](
|
||||
"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[str](
|
||||
"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[str](
|
||||
"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[str](
|
||||
"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.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
""",
|
||||
)
|
||||
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.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
""",
|
||||
)
|
||||
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.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
""",
|
||||
)
|
||||
|
||||
@property
|
||||
def retry_after(self) -> t.Optional[datetime]:
|
||||
"""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.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
The datetime object is timezone-aware.
|
||||
"""
|
||||
value = self.headers.get("retry-after")
|
||||
if value is None:
|
||||
return None
|
||||
elif value.isdigit():
|
||||
return datetime.now(timezone.utc) + timedelta(seconds=int(value))
|
||||
return parse_date(value)
|
||||
|
||||
@retry_after.setter
|
||||
def retry_after(self, value: t.Optional[t.Union[datetime, int, str]]) -> None:
|
||||
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
|
||||
|
||||
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.""",
|
||||
)
|
||||
|
||||
# ETag
|
||||
|
||||
@property
|
||||
def cache_control(self) -> ResponseCacheControl:
|
||||
"""The Cache-Control general-header field is used to specify
|
||||
directives that MUST be obeyed by all caching mechanisms along the
|
||||
request/response chain.
|
||||
"""
|
||||
|
||||
def on_update(cache_control: ResponseCacheControl) -> None:
|
||||
if not cache_control and "cache-control" in self.headers:
|
||||
del self.headers["cache-control"]
|
||||
elif cache_control:
|
||||
self.headers["Cache-Control"] = cache_control.to_header()
|
||||
|
||||
return parse_cache_control_header(
|
||||
self.headers.get("cache-control"), on_update, ResponseCacheControl
|
||||
)
|
||||
|
||||
def set_etag(self, etag: str, weak: bool = False) -> None:
|
||||
"""Set the etag, and override the old one if there was one."""
|
||||
self.headers["ETag"] = quote_etag(etag, weak)
|
||||
|
||||
def get_etag(self) -> t.Union[t.Tuple[str, bool], t.Tuple[None, None]]:
|
||||
"""Return a tuple in the form ``(etag, is_weak)``. If there is no
|
||||
ETag the return value is ``(None, None)``.
|
||||
"""
|
||||
return unquote_etag(self.headers.get("ETag"))
|
||||
|
||||
accept_ranges = header_property[str](
|
||||
"Accept-Ranges",
|
||||
doc="""The `Accept-Ranges` header. Even though the name would
|
||||
indicate that multiple values are supported, it must be one
|
||||
string token only.
|
||||
|
||||
The values ``'bytes'`` and ``'none'`` are common.
|
||||
|
||||
.. versionadded:: 0.7""",
|
||||
)
|
||||
|
||||
@property
|
||||
def content_range(self) -> ContentRange:
|
||||
"""The ``Content-Range`` header as a
|
||||
:class:`~werkzeug.datastructures.ContentRange` object. Available
|
||||
even if the header is not set.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
def on_update(rng: ContentRange) -> None:
|
||||
if not rng:
|
||||
del self.headers["content-range"]
|
||||
else:
|
||||
self.headers["Content-Range"] = rng.to_header()
|
||||
|
||||
rv = parse_content_range_header(self.headers.get("content-range"), on_update)
|
||||
# always provide a content range object to make the descriptor
|
||||
# more user friendly. It provides an unset() method that can be
|
||||
# used to remove the header quickly.
|
||||
if rv is None:
|
||||
rv = ContentRange(None, None, None, on_update=on_update)
|
||||
return rv
|
||||
|
||||
@content_range.setter
|
||||
def content_range(self, value: t.Optional[t.Union[ContentRange, str]]) -> None:
|
||||
if not value:
|
||||
del self.headers["content-range"]
|
||||
elif isinstance(value, str):
|
||||
self.headers["Content-Range"] = value
|
||||
else:
|
||||
self.headers["Content-Range"] = value.to_header()
|
||||
|
||||
# Authorization
|
||||
|
||||
@property
|
||||
def www_authenticate(self) -> WWWAuthenticate:
|
||||
"""The ``WWW-Authenticate`` header in a parsed form."""
|
||||
|
||||
def on_update(www_auth: WWWAuthenticate) -> None:
|
||||
if not www_auth and "www-authenticate" in self.headers:
|
||||
del self.headers["www-authenticate"]
|
||||
elif www_auth:
|
||||
self.headers["WWW-Authenticate"] = www_auth.to_header()
|
||||
|
||||
header = self.headers.get("www-authenticate")
|
||||
return parse_www_authenticate_header(header, on_update)
|
||||
|
||||
# CSP
|
||||
|
||||
@property
|
||||
def content_security_policy(self) -> ContentSecurityPolicy:
|
||||
"""The ``Content-Security-Policy`` header as a
|
||||
:class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
|
||||
even if the header is not set.
|
||||
|
||||
The Content-Security-Policy header adds an additional layer of
|
||||
security to help detect and mitigate certain types of attacks.
|
||||
"""
|
||||
|
||||
def on_update(csp: ContentSecurityPolicy) -> None:
|
||||
if not csp:
|
||||
del self.headers["content-security-policy"]
|
||||
else:
|
||||
self.headers["Content-Security-Policy"] = csp.to_header()
|
||||
|
||||
rv = parse_csp_header(self.headers.get("content-security-policy"), on_update)
|
||||
if rv is None:
|
||||
rv = ContentSecurityPolicy(None, on_update=on_update)
|
||||
return rv
|
||||
|
||||
@content_security_policy.setter
|
||||
def content_security_policy(
|
||||
self, value: t.Optional[t.Union[ContentSecurityPolicy, str]]
|
||||
) -> None:
|
||||
if not value:
|
||||
del self.headers["content-security-policy"]
|
||||
elif isinstance(value, str):
|
||||
self.headers["Content-Security-Policy"] = value
|
||||
else:
|
||||
self.headers["Content-Security-Policy"] = value.to_header()
|
||||
|
||||
@property
|
||||
def content_security_policy_report_only(self) -> ContentSecurityPolicy:
|
||||
"""The ``Content-Security-policy-report-only`` header as a
|
||||
:class:`~werkzeug.datastructures.ContentSecurityPolicy` object. Available
|
||||
even if the header is not set.
|
||||
|
||||
The Content-Security-Policy-Report-Only header adds a csp policy
|
||||
that is not enforced but is reported thereby helping detect
|
||||
certain types of attacks.
|
||||
"""
|
||||
|
||||
def on_update(csp: ContentSecurityPolicy) -> None:
|
||||
if not csp:
|
||||
del self.headers["content-security-policy-report-only"]
|
||||
else:
|
||||
self.headers["Content-Security-policy-report-only"] = csp.to_header()
|
||||
|
||||
rv = parse_csp_header(
|
||||
self.headers.get("content-security-policy-report-only"), on_update
|
||||
)
|
||||
if rv is None:
|
||||
rv = ContentSecurityPolicy(None, on_update=on_update)
|
||||
return rv
|
||||
|
||||
@content_security_policy_report_only.setter
|
||||
def content_security_policy_report_only(
|
||||
self, value: t.Optional[t.Union[ContentSecurityPolicy, str]]
|
||||
) -> None:
|
||||
if not value:
|
||||
del self.headers["content-security-policy-report-only"]
|
||||
elif isinstance(value, str):
|
||||
self.headers["Content-Security-policy-report-only"] = value
|
||||
else:
|
||||
self.headers["Content-Security-policy-report-only"] = value.to_header()
|
||||
|
||||
# CORS
|
||||
|
||||
@property
|
||||
def access_control_allow_credentials(self) -> bool:
|
||||
"""Whether credentials can be shared by the browser to
|
||||
JavaScript code. As part of the preflight request it indicates
|
||||
whether credentials can be used on the cross origin request.
|
||||
"""
|
||||
return "Access-Control-Allow-Credentials" in self.headers
|
||||
|
||||
@access_control_allow_credentials.setter
|
||||
def access_control_allow_credentials(self, value: t.Optional[bool]) -> None:
|
||||
if value is True:
|
||||
self.headers["Access-Control-Allow-Credentials"] = "true"
|
||||
else:
|
||||
self.headers.pop("Access-Control-Allow-Credentials", None)
|
||||
|
||||
access_control_allow_headers = header_property(
|
||||
"Access-Control-Allow-Headers",
|
||||
load_func=parse_set_header,
|
||||
dump_func=dump_header,
|
||||
doc="Which headers can be sent with the cross origin request.",
|
||||
)
|
||||
|
||||
access_control_allow_methods = header_property(
|
||||
"Access-Control-Allow-Methods",
|
||||
load_func=parse_set_header,
|
||||
dump_func=dump_header,
|
||||
doc="Which methods can be used for the cross origin request.",
|
||||
)
|
||||
|
||||
access_control_allow_origin = header_property[str](
|
||||
"Access-Control-Allow-Origin",
|
||||
doc="The origin or '*' for any origin that may make cross origin requests.",
|
||||
)
|
||||
|
||||
access_control_expose_headers = header_property(
|
||||
"Access-Control-Expose-Headers",
|
||||
load_func=parse_set_header,
|
||||
dump_func=dump_header,
|
||||
doc="Which headers can be shared by the browser to JavaScript code.",
|
||||
)
|
||||
|
||||
access_control_max_age = header_property(
|
||||
"Access-Control-Max-Age",
|
||||
load_func=int,
|
||||
dump_func=str,
|
||||
doc="The maximum age in seconds the access control settings can be cached for.",
|
||||
)
|
||||
|
||||
cross_origin_opener_policy = header_property[COOP](
|
||||
"Cross-Origin-Opener-Policy",
|
||||
load_func=lambda value: COOP(value),
|
||||
dump_func=lambda value: value.value,
|
||||
default=COOP.UNSAFE_NONE,
|
||||
doc="""Allows control over sharing of browsing context group with cross-origin
|
||||
documents. Values must be a member of the :class:`werkzeug.http.COOP` enum.""",
|
||||
)
|
||||
|
||||
cross_origin_embedder_policy = header_property[COEP](
|
||||
"Cross-Origin-Embedder-Policy",
|
||||
load_func=lambda value: COEP(value),
|
||||
dump_func=lambda value: value.value,
|
||||
default=COEP.UNSAFE_NONE,
|
||||
doc="""Prevents a document from loading any cross-origin resources that do not
|
||||
explicitly grant the document permission. Values must be a member of the
|
||||
:class:`werkzeug.http.COEP` enum.""",
|
||||
)
|
||||
142
myvenv/lib/python3.10/site-packages/werkzeug/sansio/utils.py
Normal file
142
myvenv/lib/python3.10/site-packages/werkzeug/sansio/utils.py
Normal file
@ -0,0 +1,142 @@
|
||||
import typing as t
|
||||
|
||||
from .._internal import _encode_idna
|
||||
from ..exceptions import SecurityError
|
||||
from ..urls import uri_to_iri
|
||||
from ..urls import url_quote
|
||||
|
||||
|
||||
def host_is_trusted(hostname: str, trusted_list: t.Iterable[str]) -> bool:
|
||||
"""Check if a host matches a list of trusted names.
|
||||
|
||||
:param hostname: The name to check.
|
||||
:param trusted_list: A list of valid names to match. If a name
|
||||
starts with a dot it will match all subdomains.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if not hostname:
|
||||
return False
|
||||
|
||||
if isinstance(trusted_list, str):
|
||||
trusted_list = [trusted_list]
|
||||
|
||||
def _normalize(hostname: str) -> bytes:
|
||||
if ":" in hostname:
|
||||
hostname = hostname.rsplit(":", 1)[0]
|
||||
|
||||
return _encode_idna(hostname)
|
||||
|
||||
try:
|
||||
hostname_bytes = _normalize(hostname)
|
||||
except UnicodeError:
|
||||
return False
|
||||
|
||||
for ref in trusted_list:
|
||||
if ref.startswith("."):
|
||||
ref = ref[1:]
|
||||
suffix_match = True
|
||||
else:
|
||||
suffix_match = False
|
||||
|
||||
try:
|
||||
ref_bytes = _normalize(ref)
|
||||
except UnicodeError:
|
||||
return False
|
||||
|
||||
if ref_bytes == hostname_bytes:
|
||||
return True
|
||||
|
||||
if suffix_match and hostname_bytes.endswith(b"." + ref_bytes):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_host(
|
||||
scheme: str,
|
||||
host_header: t.Optional[str],
|
||||
server: t.Optional[t.Tuple[str, t.Optional[int]]] = None,
|
||||
trusted_hosts: t.Optional[t.Iterable[str]] = None,
|
||||
) -> str:
|
||||
"""Return the host for the given parameters.
|
||||
|
||||
This first checks the ``host_header``. If it's not present, then
|
||||
``server`` is used. The host will only contain the port if it is
|
||||
different than the standard port for the protocol.
|
||||
|
||||
Optionally, verify that the host is trusted using
|
||||
:func:`host_is_trusted` and raise a
|
||||
:exc:`~werkzeug.exceptions.SecurityError` if it is not.
|
||||
|
||||
:param scheme: The protocol the request used, like ``"https"``.
|
||||
:param host_header: The ``Host`` header value.
|
||||
:param server: Address of the server. ``(host, port)``, or
|
||||
``(path, None)`` for unix sockets.
|
||||
:param trusted_hosts: A list of trusted host names.
|
||||
|
||||
:return: Host, with port if necessary.
|
||||
:raise ~werkzeug.exceptions.SecurityError: If the host is not
|
||||
trusted.
|
||||
"""
|
||||
host = ""
|
||||
|
||||
if host_header is not None:
|
||||
host = host_header
|
||||
elif server is not None:
|
||||
host = server[0]
|
||||
|
||||
if server[1] is not None:
|
||||
host = f"{host}:{server[1]}"
|
||||
|
||||
if scheme in {"http", "ws"} and host.endswith(":80"):
|
||||
host = host[:-3]
|
||||
elif scheme in {"https", "wss"} and host.endswith(":443"):
|
||||
host = host[:-4]
|
||||
|
||||
if trusted_hosts is not None:
|
||||
if not host_is_trusted(host, trusted_hosts):
|
||||
raise SecurityError(f"Host {host!r} is not trusted.")
|
||||
|
||||
return host
|
||||
|
||||
|
||||
def get_current_url(
|
||||
scheme: str,
|
||||
host: str,
|
||||
root_path: t.Optional[str] = None,
|
||||
path: t.Optional[str] = None,
|
||||
query_string: t.Optional[bytes] = None,
|
||||
) -> str:
|
||||
"""Recreate the URL for a request. If an optional part isn't
|
||||
provided, it and subsequent parts are not included in the URL.
|
||||
|
||||
The URL is an IRI, not a URI, so it may contain Unicode characters.
|
||||
Use :func:`~werkzeug.urls.iri_to_uri` to convert it to ASCII.
|
||||
|
||||
:param scheme: The protocol the request used, like ``"https"``.
|
||||
:param host: The host the request was made to. See :func:`get_host`.
|
||||
:param root_path: Prefix that the application is mounted under. This
|
||||
is prepended to ``path``.
|
||||
:param path: The path part of the URL after ``root_path``.
|
||||
:param query_string: The portion of the URL after the "?".
|
||||
"""
|
||||
url = [scheme, "://", host]
|
||||
|
||||
if root_path is None:
|
||||
url.append("/")
|
||||
return uri_to_iri("".join(url))
|
||||
|
||||
url.append(url_quote(root_path.rstrip("/")))
|
||||
url.append("/")
|
||||
|
||||
if path is None:
|
||||
return uri_to_iri("".join(url))
|
||||
|
||||
url.append(url_quote(path.lstrip("/")))
|
||||
|
||||
if query_string:
|
||||
url.append("?")
|
||||
url.append(url_quote(query_string, safe=":&%=+$!*'(),"))
|
||||
|
||||
return uri_to_iri("".join(url))
|
||||
247
myvenv/lib/python3.10/site-packages/werkzeug/security.py
Normal file
247
myvenv/lib/python3.10/site-packages/werkzeug/security.py
Normal file
@ -0,0 +1,247 @@
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import posixpath
|
||||
import secrets
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
pass
|
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
DEFAULT_PBKDF2_ITERATIONS = 260000
|
||||
|
||||
_os_alt_seps: t.List[str] = list(
|
||||
sep for sep in [os.path.sep, os.path.altsep] if sep is not None and sep != "/"
|
||||
)
|
||||
|
||||
|
||||
def pbkdf2_hex(
|
||||
data: t.Union[str, bytes],
|
||||
salt: t.Union[str, bytes],
|
||||
iterations: int = DEFAULT_PBKDF2_ITERATIONS,
|
||||
keylen: t.Optional[int] = None,
|
||||
hashfunc: t.Optional[t.Union[str, t.Callable]] = None,
|
||||
) -> str:
|
||||
"""Like :func:`pbkdf2_bin`, but returns a hex-encoded string.
|
||||
|
||||
:param data: the data to derive.
|
||||
:param salt: the salt for the derivation.
|
||||
:param iterations: the number of iterations.
|
||||
:param keylen: the length of the resulting key. If not provided,
|
||||
the digest size will be used.
|
||||
:param hashfunc: the hash function to use. This can either be the
|
||||
string name of a known hash function, or a function
|
||||
from the hashlib module. Defaults to sha256.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac`
|
||||
instead.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
warnings.warn(
|
||||
"'pbkdf2_hex' is deprecated and will be removed in Werkzeug"
|
||||
" 2.1. Use 'hashlib.pbkdf2_hmac().hex()' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return pbkdf2_bin(data, salt, iterations, keylen, hashfunc).hex()
|
||||
|
||||
|
||||
def pbkdf2_bin(
|
||||
data: t.Union[str, bytes],
|
||||
salt: t.Union[str, bytes],
|
||||
iterations: int = DEFAULT_PBKDF2_ITERATIONS,
|
||||
keylen: t.Optional[int] = None,
|
||||
hashfunc: t.Optional[t.Union[str, t.Callable]] = None,
|
||||
) -> bytes:
|
||||
"""Returns a binary digest for the PBKDF2 hash algorithm of `data`
|
||||
with the given `salt`. It iterates `iterations` times and produces a
|
||||
key of `keylen` bytes. By default, SHA-256 is used as hash function;
|
||||
a different hashlib `hashfunc` can be provided.
|
||||
|
||||
:param data: the data to derive.
|
||||
:param salt: the salt for the derivation.
|
||||
:param iterations: the number of iterations.
|
||||
:param keylen: the length of the resulting key. If not provided
|
||||
the digest size will be used.
|
||||
:param hashfunc: the hash function to use. This can either be the
|
||||
string name of a known hash function or a function
|
||||
from the hashlib module. Defaults to sha256.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Use :func:`hashlib.pbkdf2_hmac`
|
||||
instead.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
warnings.warn(
|
||||
"'pbkdf2_bin' is deprecated and will be removed in Werkzeug"
|
||||
" 2.1. Use 'hashlib.pbkdf2_hmac()' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if isinstance(data, str):
|
||||
data = data.encode("utf8")
|
||||
|
||||
if isinstance(salt, str):
|
||||
salt = salt.encode("utf8")
|
||||
|
||||
if not hashfunc:
|
||||
hash_name = "sha256"
|
||||
elif callable(hashfunc):
|
||||
hash_name = hashfunc().name
|
||||
else:
|
||||
hash_name = hashfunc
|
||||
|
||||
return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen)
|
||||
|
||||
|
||||
def safe_str_cmp(a: str, b: str) -> bool:
|
||||
"""This function compares strings in somewhat constant time. This
|
||||
requires that the length of at least one string is known in advance.
|
||||
|
||||
Returns `True` if the two strings are equal, or `False` if they are not.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Use
|
||||
:func:`hmac.compare_digest` instead.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
warnings.warn(
|
||||
"'safe_str_cmp' is deprecated and will be removed in Werkzeug"
|
||||
" 2.1. Use 'hmac.compare_digest' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if isinstance(a, str):
|
||||
a = a.encode("utf-8") # type: ignore
|
||||
|
||||
if isinstance(b, str):
|
||||
b = b.encode("utf-8") # type: ignore
|
||||
|
||||
return hmac.compare_digest(a, b)
|
||||
|
||||
|
||||
def gen_salt(length: int) -> str:
|
||||
"""Generate a random string of SALT_CHARS with specified ``length``."""
|
||||
if length <= 0:
|
||||
raise ValueError("Salt length must be positive")
|
||||
|
||||
return "".join(secrets.choice(SALT_CHARS) for _ in range(length))
|
||||
|
||||
|
||||
def _hash_internal(method: str, salt: str, password: str) -> t.Tuple[str, str]:
|
||||
"""Internal password hash helper. Supports plaintext without salt,
|
||||
unsalted and salted passwords. In case salted passwords are used
|
||||
hmac is used.
|
||||
"""
|
||||
if method == "plain":
|
||||
return password, method
|
||||
|
||||
salt = salt.encode("utf-8")
|
||||
password = password.encode("utf-8")
|
||||
|
||||
if method.startswith("pbkdf2:"):
|
||||
if not salt:
|
||||
raise ValueError("Salt is required for PBKDF2")
|
||||
|
||||
args = method[7:].split(":")
|
||||
|
||||
if len(args) not in (1, 2):
|
||||
raise ValueError("Invalid number of arguments for PBKDF2")
|
||||
|
||||
method = args.pop(0)
|
||||
iterations = int(args[0] or 0) if args else DEFAULT_PBKDF2_ITERATIONS
|
||||
return (
|
||||
hashlib.pbkdf2_hmac(method, password, salt, iterations).hex(),
|
||||
f"pbkdf2:{method}:{iterations}",
|
||||
)
|
||||
|
||||
if salt:
|
||||
return hmac.new(salt, password, method).hexdigest(), method
|
||||
|
||||
return hashlib.new(method, password).hexdigest(), method
|
||||
|
||||
|
||||
def generate_password_hash(
|
||||
password: str, method: str = "pbkdf2:sha256", salt_length: int = 16
|
||||
) -> str:
|
||||
"""Hash a password with the given method and salt with a string of
|
||||
the given length. The format of the string returned includes the method
|
||||
that was used so that :func:`check_password_hash` can check the hash.
|
||||
|
||||
The format for the hashed string looks like this::
|
||||
|
||||
method$salt$hash
|
||||
|
||||
This method can **not** generate unsalted passwords but it is possible
|
||||
to set param method='plain' in order to enforce plaintext passwords.
|
||||
If a salt is used, hmac is used internally to salt the password.
|
||||
|
||||
If PBKDF2 is wanted it can be enabled by setting the method to
|
||||
``pbkdf2:method:iterations`` where iterations is optional::
|
||||
|
||||
pbkdf2:sha256:80000$salt$hash
|
||||
pbkdf2:sha256$salt$hash
|
||||
|
||||
:param password: the password to hash.
|
||||
:param method: the hash method to use (one that hashlib supports). Can
|
||||
optionally be in the format ``pbkdf2:method:iterations``
|
||||
to enable PBKDF2.
|
||||
:param salt_length: the length of the salt in letters.
|
||||
"""
|
||||
salt = gen_salt(salt_length) if method != "plain" else ""
|
||||
h, actual_method = _hash_internal(method, salt, password)
|
||||
return f"{actual_method}${salt}${h}"
|
||||
|
||||
|
||||
def check_password_hash(pwhash: str, password: str) -> bool:
|
||||
"""Check a password against a given salted and hashed password value.
|
||||
In order to support unsalted legacy passwords this method supports
|
||||
plain text passwords, md5 and sha1 hashes (both salted and unsalted).
|
||||
|
||||
Returns `True` if the password matched, `False` otherwise.
|
||||
|
||||
:param pwhash: a hashed string like returned by
|
||||
:func:`generate_password_hash`.
|
||||
:param password: the plaintext password to compare against the hash.
|
||||
"""
|
||||
if pwhash.count("$") < 2:
|
||||
return False
|
||||
|
||||
method, salt, hashval = pwhash.split("$", 2)
|
||||
return hmac.compare_digest(_hash_internal(method, salt, password)[0], hashval)
|
||||
|
||||
|
||||
def safe_join(directory: str, *pathnames: str) -> t.Optional[str]:
|
||||
"""Safely join zero or more untrusted path components to a base
|
||||
directory to avoid escaping the base directory.
|
||||
|
||||
:param directory: The trusted base directory.
|
||||
:param pathnames: The untrusted path components relative to the
|
||||
base directory.
|
||||
:return: A safe path, otherwise ``None``.
|
||||
"""
|
||||
parts = [directory]
|
||||
|
||||
for filename in pathnames:
|
||||
if filename != "":
|
||||
filename = posixpath.normpath(filename)
|
||||
|
||||
if (
|
||||
any(sep in filename for sep in _os_alt_seps)
|
||||
or os.path.isabs(filename)
|
||||
or filename == ".."
|
||||
or filename.startswith("../")
|
||||
):
|
||||
return None
|
||||
|
||||
parts.append(filename)
|
||||
|
||||
return posixpath.join(*parts)
|
||||
1088
myvenv/lib/python3.10/site-packages/werkzeug/serving.py
Normal file
1088
myvenv/lib/python3.10/site-packages/werkzeug/serving.py
Normal file
File diff suppressed because it is too large
Load Diff
1331
myvenv/lib/python3.10/site-packages/werkzeug/test.py
Normal file
1331
myvenv/lib/python3.10/site-packages/werkzeug/test.py
Normal file
File diff suppressed because it is too large
Load Diff
240
myvenv/lib/python3.10/site-packages/werkzeug/testapp.py
Normal file
240
myvenv/lib/python3.10/site-packages/werkzeug/testapp.py
Normal file
@ -0,0 +1,240 @@
|
||||
"""A small application that can be used to test a WSGI server and check
|
||||
it for WSGI compliance.
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
import typing as t
|
||||
from html import escape
|
||||
from textwrap import wrap
|
||||
|
||||
from . import __version__ as _werkzeug_version
|
||||
from .wrappers.request import Request
|
||||
from .wrappers.response import Response
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import StartResponse
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
logo = Response(
|
||||
base64.b64decode(
|
||||
"""
|
||||
R0lGODlhoACgAOMIAAEDACwpAEpCAGdgAJaKAM28AOnVAP3rAP/////////
|
||||
//////////////////////yH5BAEKAAgALAAAAACgAKAAAAT+EMlJq704680R+F0ojmRpnuj0rWnrv
|
||||
nB8rbRs33gu0bzu/0AObxgsGn3D5HHJbCUFyqZ0ukkSDlAidctNFg7gbI9LZlrBaHGtzAae0eloe25
|
||||
7w9EDOX2fst/xenyCIn5/gFqDiVVDV4aGeYiKkhSFjnCQY5OTlZaXgZp8nJ2ekaB0SQOjqphrpnOiq
|
||||
ncEn65UsLGytLVmQ6m4sQazpbtLqL/HwpnER8bHyLrLOc3Oz8PRONPU1crXN9na263dMt/g4SzjMeX
|
||||
m5yDpLqgG7OzJ4u8lT/P69ej3JPn69kHzN2OIAHkB9RUYSFCFQYQJFTIkCDBiwoXWGnowaLEjRm7+G
|
||||
p9A7Hhx4rUkAUaSLJlxHMqVMD/aSycSZkyTplCqtGnRAM5NQ1Ly5OmzZc6gO4d6DGAUKA+hSocWYAo
|
||||
SlM6oUWX2O/o0KdaVU5vuSQLAa0ADwQgMEMB2AIECZhVSnTno6spgbtXmHcBUrQACcc2FrTrWS8wAf
|
||||
78cMFBgwIBgbN+qvTt3ayikRBk7BoyGAGABAdYyfdzRQGV3l4coxrqQ84GpUBmrdR3xNIDUPAKDBSA
|
||||
ADIGDhhqTZIWaDcrVX8EsbNzbkvCOxG8bN5w8ly9H8jyTJHC6DFndQydbguh2e/ctZJFXRxMAqqPVA
|
||||
tQH5E64SPr1f0zz7sQYjAHg0In+JQ11+N2B0XXBeeYZgBZFx4tqBToiTCPv0YBgQv8JqA6BEf6RhXx
|
||||
w1ENhRBnWV8ctEX4Ul2zc3aVGcQNC2KElyTDYyYUWvShdjDyMOGMuFjqnII45aogPhz/CodUHFwaDx
|
||||
lTgsaOjNyhGWJQd+lFoAGk8ObghI0kawg+EV5blH3dr+digkYuAGSaQZFHFz2P/cTaLmhF52QeSb45
|
||||
Jwxd+uSVGHlqOZpOeJpCFZ5J+rkAkFjQ0N1tah7JJSZUFNsrkeJUJMIBi8jyaEKIhKPomnC91Uo+NB
|
||||
yyaJ5umnnpInIFh4t6ZSpGaAVmizqjpByDegYl8tPE0phCYrhcMWSv+uAqHfgH88ak5UXZmlKLVJhd
|
||||
dj78s1Fxnzo6yUCrV6rrDOkluG+QzCAUTbCwf9SrmMLzK6p+OPHx7DF+bsfMRq7Ec61Av9i6GLw23r
|
||||
idnZ+/OO0a99pbIrJkproCQMA17OPG6suq3cca5ruDfXCCDoS7BEdvmJn5otdqscn+uogRHHXs8cbh
|
||||
EIfYaDY1AkrC0cqwcZpnM6ludx72x0p7Fo/hZAcpJDjax0UdHavMKAbiKltMWCF3xxh9k25N/Viud8
|
||||
ba78iCvUkt+V6BpwMlErmcgc502x+u1nSxJSJP9Mi52awD1V4yB/QHONsnU3L+A/zR4VL/indx/y64
|
||||
gqcj+qgTeweM86f0Qy1QVbvmWH1D9h+alqg254QD8HJXHvjQaGOqEqC22M54PcftZVKVSQG9jhkv7C
|
||||
JyTyDoAJfPdu8v7DRZAxsP/ky9MJ3OL36DJfCFPASC3/aXlfLOOON9vGZZHydGf8LnxYJuuVIbl83y
|
||||
Az5n/RPz07E+9+zw2A2ahz4HxHo9Kt79HTMx1Q7ma7zAzHgHqYH0SoZWyTuOLMiHwSfZDAQTn0ajk9
|
||||
YQqodnUYjByQZhZak9Wu4gYQsMyEpIOAOQKze8CmEF45KuAHTvIDOfHJNipwoHMuGHBnJElUoDmAyX
|
||||
c2Qm/R8Ah/iILCCJOEokGowdhDYc/yoL+vpRGwyVSCWFYZNljkhEirGXsalWcAgOdeAdoXcktF2udb
|
||||
qbUhjWyMQxYO01o6KYKOr6iK3fE4MaS+DsvBsGOBaMb0Y6IxADaJhFICaOLmiWTlDAnY1KzDG4ambL
|
||||
cWBA8mUzjJsN2KjSaSXGqMCVXYpYkj33mcIApyhQf6YqgeNAmNvuC0t4CsDbSshZJkCS1eNisKqlyG
|
||||
cF8G2JeiDX6tO6Mv0SmjCa3MFb0bJaGPMU0X7c8XcpvMaOQmCajwSeY9G0WqbBmKv34DsMIEztU6Y2
|
||||
KiDlFdt6jnCSqx7Dmt6XnqSKaFFHNO5+FmODxMCWBEaco77lNDGXBM0ECYB/+s7nKFdwSF5hgXumQe
|
||||
EZ7amRg39RHy3zIjyRCykQh8Zo2iviRKyTDn/zx6EefptJj2Cw+Ep2FSc01U5ry4KLPYsTyWnVGnvb
|
||||
UpyGlhjBUljyjHhWpf8OFaXwhp9O4T1gU9UeyPPa8A2l0p1kNqPXEVRm1AOs1oAGZU596t6SOR2mcB
|
||||
Oco1srWtkaVrMUzIErrKri85keKqRQYX9VX0/eAUK1hrSu6HMEX3Qh2sCh0q0D2CtnUqS4hj62sE/z
|
||||
aDs2Sg7MBS6xnQeooc2R2tC9YrKpEi9pLXfYXp20tDCpSP8rKlrD4axprb9u1Df5hSbz9QU0cRpfgn
|
||||
kiIzwKucd0wsEHlLpe5yHXuc6FrNelOl7pY2+11kTWx7VpRu97dXA3DO1vbkhcb4zyvERYajQgAADs
|
||||
="""
|
||||
),
|
||||
mimetype="image/png",
|
||||
)
|
||||
|
||||
|
||||
TEMPLATE = """\
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<title>WSGI Information</title>
|
||||
<style type="text/css">
|
||||
@import url(https://fonts.googleapis.com/css?family=Ubuntu);
|
||||
|
||||
body { font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva',
|
||||
'Verdana', sans-serif; background-color: white; color: #000;
|
||||
font-size: 15px; text-align: center; }
|
||||
#logo { float: right; padding: 0 0 10px 10px; }
|
||||
div.box { text-align: left; width: 45em; margin: auto; padding: 50px 0;
|
||||
background-color: white; }
|
||||
h1, h2 { font-family: 'Ubuntu', 'Lucida Grande', 'Lucida Sans Unicode',
|
||||
'Geneva', 'Verdana', sans-serif; font-weight: normal; }
|
||||
h1 { margin: 0 0 30px 0; }
|
||||
h2 { font-size: 1.4em; margin: 1em 0 0.5em 0; }
|
||||
table { width: 100%%; border-collapse: collapse; border: 1px solid #AFC5C9 }
|
||||
table th { background-color: #AFC1C4; color: white; font-size: 0.72em;
|
||||
font-weight: normal; width: 18em; vertical-align: top;
|
||||
padding: 0.5em 0 0.1em 0.5em; }
|
||||
table td { border: 1px solid #AFC5C9; padding: 0.1em 0 0.1em 0.5em; }
|
||||
code { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
||||
monospace; font-size: 0.7em; }
|
||||
ul li { line-height: 1.5em; }
|
||||
ul.path { font-size: 0.7em; margin: 0 -30px; padding: 8px 30px;
|
||||
list-style: none; background: #E8EFF0; }
|
||||
ul.path li { line-height: 1.6em; }
|
||||
li.virtual { color: #999; text-decoration: underline; }
|
||||
li.exp { background: white; }
|
||||
</style>
|
||||
<div class="box">
|
||||
<img src="?resource=logo" id="logo" alt="[The Werkzeug Logo]" />
|
||||
<h1>WSGI Information</h1>
|
||||
<p>
|
||||
This page displays all available information about the WSGI server and
|
||||
the underlying Python interpreter.
|
||||
<h2 id="python-interpreter">Python Interpreter</h2>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Python Version
|
||||
<td>%(python_version)s
|
||||
<tr>
|
||||
<th>Platform
|
||||
<td>%(platform)s [%(os)s]
|
||||
<tr>
|
||||
<th>API Version
|
||||
<td>%(api_version)s
|
||||
<tr>
|
||||
<th>Byteorder
|
||||
<td>%(byteorder)s
|
||||
<tr>
|
||||
<th>Werkzeug Version
|
||||
<td>%(werkzeug_version)s
|
||||
</table>
|
||||
<h2 id="wsgi-environment">WSGI Environment</h2>
|
||||
<table>%(wsgi_env)s</table>
|
||||
<h2 id="installed-eggs">Installed Eggs</h2>
|
||||
<p>
|
||||
The following python packages were installed on the system as
|
||||
Python eggs:
|
||||
<ul>%(python_eggs)s</ul>
|
||||
<h2 id="sys-path">System Path</h2>
|
||||
<p>
|
||||
The following paths are the current contents of the load path. The
|
||||
following entries are looked up for Python packages. Note that not
|
||||
all items in this path are folders. Gray and underlined items are
|
||||
entries pointing to invalid resources or used by custom import hooks
|
||||
such as the zip importer.
|
||||
<p>
|
||||
Items with a bright background were expanded for display from a relative
|
||||
path. If you encounter such paths in the output you might want to check
|
||||
your setup as relative paths are usually problematic in multithreaded
|
||||
environments.
|
||||
<ul class="path">%(sys_path)s</ul>
|
||||
</div>
|
||||
"""
|
||||
|
||||
|
||||
def iter_sys_path() -> t.Iterator[t.Tuple[str, bool, bool]]:
|
||||
if os.name == "posix":
|
||||
|
||||
def strip(x: str) -> str:
|
||||
prefix = os.path.expanduser("~")
|
||||
if x.startswith(prefix):
|
||||
x = f"~{x[len(prefix) :]}"
|
||||
return x
|
||||
|
||||
else:
|
||||
|
||||
def strip(x: str) -> str:
|
||||
return x
|
||||
|
||||
cwd = os.path.abspath(os.getcwd())
|
||||
for item in sys.path:
|
||||
path = os.path.join(cwd, item or os.path.curdir)
|
||||
yield strip(os.path.normpath(path)), not os.path.isdir(path), path != item
|
||||
|
||||
|
||||
def render_testapp(req: Request) -> bytes:
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
eggs: t.Iterable[t.Any] = ()
|
||||
else:
|
||||
eggs = sorted(
|
||||
pkg_resources.working_set,
|
||||
key=lambda x: x.project_name.lower(), # type: ignore
|
||||
)
|
||||
python_eggs = []
|
||||
for egg in eggs:
|
||||
try:
|
||||
version = egg.version
|
||||
except (ValueError, AttributeError):
|
||||
version = "unknown"
|
||||
python_eggs.append(
|
||||
f"<li>{escape(egg.project_name)} <small>[{escape(version)}]</small>"
|
||||
)
|
||||
|
||||
wsgi_env = []
|
||||
sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower())
|
||||
for key, value in sorted_environ:
|
||||
value = "".join(wrap(escape(repr(value))))
|
||||
wsgi_env.append(f"<tr><th>{escape(str(key))}<td><code>{value}</code>")
|
||||
|
||||
sys_path = []
|
||||
for item, virtual, expanded in iter_sys_path():
|
||||
class_ = []
|
||||
if virtual:
|
||||
class_.append("virtual")
|
||||
if expanded:
|
||||
class_.append("exp")
|
||||
class_ = f' class="{" ".join(class_)}"' if class_ else ""
|
||||
sys_path.append(f"<li{class_}>{escape(item)}")
|
||||
|
||||
return (
|
||||
TEMPLATE
|
||||
% {
|
||||
"python_version": "<br>".join(escape(sys.version).splitlines()),
|
||||
"platform": escape(sys.platform),
|
||||
"os": escape(os.name),
|
||||
"api_version": sys.api_version,
|
||||
"byteorder": sys.byteorder,
|
||||
"werkzeug_version": _werkzeug_version,
|
||||
"python_eggs": "\n".join(python_eggs),
|
||||
"wsgi_env": "\n".join(wsgi_env),
|
||||
"sys_path": "\n".join(sys_path),
|
||||
}
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def test_app(
|
||||
environ: "WSGIEnvironment", start_response: "StartResponse"
|
||||
) -> t.Iterable[bytes]:
|
||||
"""Simple test application that dumps the environment. You can use
|
||||
it to check if Werkzeug is working properly:
|
||||
|
||||
.. sourcecode:: pycon
|
||||
|
||||
>>> from werkzeug.serving import run_simple
|
||||
>>> from werkzeug.testapp import test_app
|
||||
>>> run_simple('localhost', 3000, test_app)
|
||||
* Running on http://localhost:3000/
|
||||
|
||||
The application displays important information from the WSGI environment,
|
||||
the Python interpreter and the installed libraries.
|
||||
"""
|
||||
req = Request(environ, populate_request=False)
|
||||
if req.args.get("resource") == "logo":
|
||||
response = logo
|
||||
else:
|
||||
response = Response(render_testapp(req), mimetype="text/html")
|
||||
return response(environ, start_response)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from .serving import run_simple
|
||||
|
||||
run_simple("localhost", 5000, test_app, use_reloader=True)
|
||||
1211
myvenv/lib/python3.10/site-packages/werkzeug/urls.py
Normal file
1211
myvenv/lib/python3.10/site-packages/werkzeug/urls.py
Normal file
File diff suppressed because it is too large
Load Diff
47
myvenv/lib/python3.10/site-packages/werkzeug/user_agent.py
Normal file
47
myvenv/lib/python3.10/site-packages/werkzeug/user_agent.py
Normal file
@ -0,0 +1,47 @@
|
||||
import typing as t
|
||||
|
||||
|
||||
class UserAgent:
|
||||
"""Represents a parsed user agent header value.
|
||||
|
||||
The default implementation does no parsing, only the :attr:`string`
|
||||
attribute is set. A subclass may parse the string to set the
|
||||
common attributes or expose other information. Set
|
||||
:attr:`werkzeug.wrappers.Request.user_agent_class` to use a
|
||||
subclass.
|
||||
|
||||
:param string: The header value to parse.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
This replaces the previous ``useragents`` module, but does not
|
||||
provide a built-in parser.
|
||||
"""
|
||||
|
||||
platform: t.Optional[str] = None
|
||||
"""The OS name, if it could be parsed from the string."""
|
||||
|
||||
browser: t.Optional[str] = None
|
||||
"""The browser name, if it could be parsed from the string."""
|
||||
|
||||
version: t.Optional[str] = None
|
||||
"""The browser version, if it could be parsed from the string."""
|
||||
|
||||
language: t.Optional[str] = None
|
||||
"""The browser language, if it could be parsed from the string."""
|
||||
|
||||
def __init__(self, string: str) -> None:
|
||||
self.string: str = string
|
||||
"""The original header value."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} {self.browser}/{self.version}>"
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.string
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
return bool(self.browser)
|
||||
|
||||
def to_header(self) -> str:
|
||||
"""Convert to a header value."""
|
||||
return self.string
|
||||
215
myvenv/lib/python3.10/site-packages/werkzeug/useragents.py
Normal file
215
myvenv/lib/python3.10/site-packages/werkzeug/useragents.py
Normal file
@ -0,0 +1,215 @@
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
from .user_agent import UserAgent as _BaseUserAgent
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
from _typeshed.wsgi import WSGIEnvironment
|
||||
|
||||
|
||||
class _UserAgentParser:
|
||||
platform_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
|
||||
(" cros ", "chromeos"),
|
||||
("iphone|ios", "iphone"),
|
||||
("ipad", "ipad"),
|
||||
(r"darwin\b|mac\b|os\s*x", "macos"),
|
||||
("win", "windows"),
|
||||
(r"android", "android"),
|
||||
("netbsd", "netbsd"),
|
||||
("openbsd", "openbsd"),
|
||||
("freebsd", "freebsd"),
|
||||
("dragonfly", "dragonflybsd"),
|
||||
("(sun|i86)os", "solaris"),
|
||||
(r"x11\b|lin(\b|ux)?", "linux"),
|
||||
(r"nintendo\s+wii", "wii"),
|
||||
("irix", "irix"),
|
||||
("hp-?ux", "hpux"),
|
||||
("aix", "aix"),
|
||||
("sco|unix_sv", "sco"),
|
||||
("bsd", "bsd"),
|
||||
("amiga", "amiga"),
|
||||
("blackberry|playbook", "blackberry"),
|
||||
("symbian", "symbian"),
|
||||
)
|
||||
browser_rules: t.ClassVar[t.Iterable[t.Tuple[str, str]]] = (
|
||||
("googlebot", "google"),
|
||||
("msnbot", "msn"),
|
||||
("yahoo", "yahoo"),
|
||||
("ask jeeves", "ask"),
|
||||
(r"aol|america\s+online\s+browser", "aol"),
|
||||
(r"opera|opr", "opera"),
|
||||
("edge|edg", "edge"),
|
||||
("chrome|crios", "chrome"),
|
||||
("seamonkey", "seamonkey"),
|
||||
("firefox|firebird|phoenix|iceweasel", "firefox"),
|
||||
("galeon", "galeon"),
|
||||
("safari|version", "safari"),
|
||||
("webkit", "webkit"),
|
||||
("camino", "camino"),
|
||||
("konqueror", "konqueror"),
|
||||
("k-meleon", "kmeleon"),
|
||||
("netscape", "netscape"),
|
||||
(r"msie|microsoft\s+internet\s+explorer|trident/.+? rv:", "msie"),
|
||||
("lynx", "lynx"),
|
||||
("links", "links"),
|
||||
("Baiduspider", "baidu"),
|
||||
("bingbot", "bing"),
|
||||
("mozilla", "mozilla"),
|
||||
)
|
||||
|
||||
_browser_version_re = r"(?:{pattern})[/\sa-z(]*(\d+[.\da-z]+)?"
|
||||
_language_re = re.compile(
|
||||
r"(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|"
|
||||
r"(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)"
|
||||
)
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platform_rules]
|
||||
self.browsers = [
|
||||
(b, re.compile(self._browser_version_re.format(pattern=a), re.I))
|
||||
for a, b in self.browser_rules
|
||||
]
|
||||
|
||||
def __call__(
|
||||
self, user_agent: str
|
||||
) -> t.Tuple[t.Optional[str], t.Optional[str], t.Optional[str], t.Optional[str]]:
|
||||
platform: t.Optional[str]
|
||||
browser: t.Optional[str]
|
||||
version: t.Optional[str]
|
||||
language: t.Optional[str]
|
||||
|
||||
for platform, regex in self.platforms: # noqa: B007
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
break
|
||||
else:
|
||||
platform = None
|
||||
|
||||
# Except for Trident, all browser key words come after the last ')'
|
||||
last_closing_paren = 0
|
||||
if (
|
||||
not re.compile(r"trident/.+? rv:", re.I).search(user_agent)
|
||||
and ")" in user_agent
|
||||
and user_agent[-1] != ")"
|
||||
):
|
||||
last_closing_paren = user_agent.rindex(")")
|
||||
|
||||
for browser, regex in self.browsers: # noqa: B007
|
||||
match = regex.search(user_agent[last_closing_paren:])
|
||||
if match is not None:
|
||||
version = match.group(1)
|
||||
break
|
||||
else:
|
||||
browser = version = None
|
||||
match = self._language_re.search(user_agent)
|
||||
if match is not None:
|
||||
language = match.group(1) or match.group(2)
|
||||
else:
|
||||
language = None
|
||||
return platform, browser, version, language
|
||||
|
||||
|
||||
# It wasn't public, but users might have imported it anyway, show a
|
||||
# warning if a user created an instance.
|
||||
class UserAgentParser(_UserAgentParser):
|
||||
"""A simple user agent parser. Used by the `UserAgent`.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Use a dedicated parser library
|
||||
instead.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
warnings.warn(
|
||||
"'UserAgentParser' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Use a dedicated parser library instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__()
|
||||
|
||||
|
||||
class _deprecated_property(property):
|
||||
def __init__(self, fget: t.Callable[["_UserAgent"], t.Any]) -> None:
|
||||
super().__init__(fget)
|
||||
self.message = (
|
||||
"The built-in user agent parser is deprecated and will be"
|
||||
f" removed in Werkzeug 2.1. The {fget.__name__!r} property"
|
||||
" will be 'None'. Subclass 'werkzeug.user_agent.UserAgent'"
|
||||
" and set 'Request.user_agent_class' to use a different"
|
||||
" parser."
|
||||
)
|
||||
|
||||
def __get__(self, *args: t.Any, **kwargs: t.Any) -> t.Any:
|
||||
warnings.warn(self.message, DeprecationWarning, stacklevel=3)
|
||||
return super().__get__(*args, **kwargs)
|
||||
|
||||
|
||||
# This is what Request.user_agent returns for now, only show warnings on
|
||||
# attribute access, not creation.
|
||||
class _UserAgent(_BaseUserAgent):
|
||||
_parser = _UserAgentParser()
|
||||
|
||||
def __init__(self, string: str) -> None:
|
||||
super().__init__(string)
|
||||
info = self._parser(string)
|
||||
self._platform, self._browser, self._version, self._language = info
|
||||
|
||||
@_deprecated_property
|
||||
def platform(self) -> t.Optional[str]: # type: ignore
|
||||
return self._platform
|
||||
|
||||
@_deprecated_property
|
||||
def browser(self) -> t.Optional[str]: # type: ignore
|
||||
return self._browser
|
||||
|
||||
@_deprecated_property
|
||||
def version(self) -> t.Optional[str]: # type: ignore
|
||||
return self._version
|
||||
|
||||
@_deprecated_property
|
||||
def language(self) -> t.Optional[str]: # type: ignore
|
||||
return self._language
|
||||
|
||||
|
||||
# This is what users might be importing, show warnings on create.
|
||||
class UserAgent(_UserAgent):
|
||||
"""Represents a parsed user agent header value.
|
||||
|
||||
This uses a basic parser to try to extract some information from the
|
||||
header.
|
||||
|
||||
:param environ_or_string: The header value to parse, or a WSGI
|
||||
environ containing the header.
|
||||
|
||||
.. deprecated:: 2.0
|
||||
Will be removed in Werkzeug 2.1. Subclass
|
||||
:class:`werkzeug.user_agent.UserAgent` (note the new module
|
||||
name) to use a dedicated parser instead.
|
||||
|
||||
.. versionchanged:: 2.0
|
||||
Passing a WSGI environ is deprecated and will be removed in 2.1.
|
||||
"""
|
||||
|
||||
def __init__(self, environ_or_string: "t.Union[str, WSGIEnvironment]") -> None:
|
||||
if isinstance(environ_or_string, dict):
|
||||
warnings.warn(
|
||||
"Passing an environ to 'UserAgent' is deprecated and"
|
||||
" will be removed in Werkzeug 2.1. Pass the header"
|
||||
" value string instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
string = environ_or_string.get("HTTP_USER_AGENT", "")
|
||||
else:
|
||||
string = environ_or_string
|
||||
|
||||
warnings.warn(
|
||||
"The 'werkzeug.useragents' module is deprecated and will be"
|
||||
" removed in Werkzeug 2.1. The new base API is"
|
||||
" 'werkzeug.user_agent.UserAgent'.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(string)
|
||||
1099
myvenv/lib/python3.10/site-packages/werkzeug/utils.py
Normal file
1099
myvenv/lib/python3.10/site-packages/werkzeug/utils.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,16 @@
|
||||
from .accept import AcceptMixin
|
||||
from .auth import AuthorizationMixin
|
||||
from .auth import WWWAuthenticateMixin
|
||||
from .base_request import BaseRequest
|
||||
from .base_response import BaseResponse
|
||||
from .common_descriptors import CommonRequestDescriptorsMixin
|
||||
from .common_descriptors import CommonResponseDescriptorsMixin
|
||||
from .etag import ETagRequestMixin
|
||||
from .etag import ETagResponseMixin
|
||||
from .request import PlainRequest
|
||||
from .request import Request as Request
|
||||
from .request import StreamOnlyMixin
|
||||
from .response import Response as Response
|
||||
from .response import ResponseStream
|
||||
from .response import ResponseStreamMixin
|
||||
from .user_agent import UserAgentMixin
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,14 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
|
||||
class AcceptMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'AcceptMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Request' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,26 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
|
||||
class AuthorizationMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'AuthorizationMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Request' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class WWWAuthenticateMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'WWWAuthenticateMixin' is deprecated and will be removed"
|
||||
" in Werkzeug 2.1. 'Response' now includes the"
|
||||
" functionality directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,36 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
from .request import Request
|
||||
|
||||
|
||||
class _FakeSubclassCheck(type):
|
||||
def __subclasscheck__(cls, subclass: t.Type) -> bool:
|
||||
warnings.warn(
|
||||
"'BaseRequest' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Use 'issubclass(cls, Request)' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return issubclass(subclass, Request)
|
||||
|
||||
def __instancecheck__(cls, instance: t.Any) -> bool:
|
||||
warnings.warn(
|
||||
"'BaseRequest' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Use 'isinstance(obj, Request)' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return isinstance(instance, Request)
|
||||
|
||||
|
||||
class BaseRequest(Request, metaclass=_FakeSubclassCheck):
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'BaseRequest' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Request' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,36 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
from .response import Response
|
||||
|
||||
|
||||
class _FakeSubclassCheck(type):
|
||||
def __subclasscheck__(cls, subclass: t.Type) -> bool:
|
||||
warnings.warn(
|
||||
"'BaseResponse' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Use 'issubclass(cls, Response)' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return issubclass(subclass, Response)
|
||||
|
||||
def __instancecheck__(cls, instance: t.Any) -> bool:
|
||||
warnings.warn(
|
||||
"'BaseResponse' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. Use 'isinstance(obj, Response)' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return isinstance(instance, Response)
|
||||
|
||||
|
||||
class BaseResponse(Response, metaclass=_FakeSubclassCheck):
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'BaseResponse' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Response' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,26 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
|
||||
class CommonRequestDescriptorsMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'CommonRequestDescriptorsMixin' is deprecated and will be"
|
||||
" removed in Werkzeug 2.1. 'Request' now includes the"
|
||||
" functionality directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CommonResponseDescriptorsMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'CommonResponseDescriptorsMixin' is deprecated and will be"
|
||||
" removed in Werkzeug 2.1. 'Response' now includes the"
|
||||
" functionality directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,26 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
|
||||
class CORSRequestMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'CORSRequestMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Request' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class CORSResponseMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'CORSResponseMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Response' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
@ -0,0 +1,26 @@
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
|
||||
class ETagRequestMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'ETagRequestMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Request' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class ETagResponseMixin:
|
||||
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
|
||||
warnings.warn(
|
||||
"'ETagResponseMixin' is deprecated and will be removed in"
|
||||
" Werkzeug 2.1. 'Response' now includes the functionality"
|
||||
" directly.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
super().__init__(*args, **kwargs)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user