mirror of
https://github.com/bvanroll/cicdTest.git
synced 2025-08-30 04:22:49 +00:00
build test
This commit is contained in:
20
venv/lib/python3.7/site-packages/werkzeug/__init__.py
Normal file
20
venv/lib/python3.7/site-packages/werkzeug/__init__.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
werkzeug
|
||||
~~~~~~~~
|
||||
|
||||
Werkzeug is the Swiss Army knife of Python web development.
|
||||
|
||||
It provides useful classes and functions for any WSGI application to
|
||||
make the life of a Python web developer much easier. All of the provided
|
||||
classes are independent from each other so you can mix it with any other
|
||||
library.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
from .serving import run_simple
|
||||
from .test import Client
|
||||
from .wrappers import Request
|
||||
from .wrappers import Response
|
||||
|
||||
__version__ = "1.0.0"
|
228
venv/lib/python3.7/site-packages/werkzeug/_compat.py
Normal file
228
venv/lib/python3.7/site-packages/werkzeug/_compat.py
Normal file
@@ -0,0 +1,228 @@
|
||||
# flake8: noqa
|
||||
# This whole file is full of lint errors
|
||||
import functools
|
||||
import operator
|
||||
import sys
|
||||
|
||||
try:
|
||||
import builtins
|
||||
except ImportError:
|
||||
import __builtin__ as builtins
|
||||
|
||||
|
||||
PY2 = sys.version_info[0] == 2
|
||||
WIN = sys.platform.startswith("win")
|
||||
|
||||
_identity = lambda x: x
|
||||
|
||||
if PY2:
|
||||
unichr = unichr
|
||||
text_type = unicode
|
||||
string_types = (str, unicode)
|
||||
integer_types = (int, long)
|
||||
|
||||
iterkeys = lambda d, *args, **kwargs: d.iterkeys(*args, **kwargs)
|
||||
itervalues = lambda d, *args, **kwargs: d.itervalues(*args, **kwargs)
|
||||
iteritems = lambda d, *args, **kwargs: d.iteritems(*args, **kwargs)
|
||||
|
||||
iterlists = lambda d, *args, **kwargs: d.iterlists(*args, **kwargs)
|
||||
iterlistvalues = lambda d, *args, **kwargs: d.iterlistvalues(*args, **kwargs)
|
||||
|
||||
int_to_byte = chr
|
||||
iter_bytes = iter
|
||||
|
||||
import collections as collections_abc
|
||||
|
||||
exec("def reraise(tp, value, tb=None):\n raise tp, value, tb")
|
||||
|
||||
def fix_tuple_repr(obj):
|
||||
def __repr__(self):
|
||||
cls = self.__class__
|
||||
return "%s(%s)" % (
|
||||
cls.__name__,
|
||||
", ".join(
|
||||
"%s=%r" % (field, self[index])
|
||||
for index, field in enumerate(cls._fields)
|
||||
),
|
||||
)
|
||||
|
||||
obj.__repr__ = __repr__
|
||||
return obj
|
||||
|
||||
def implements_iterator(cls):
|
||||
cls.next = cls.__next__
|
||||
del cls.__next__
|
||||
return cls
|
||||
|
||||
def implements_to_string(cls):
|
||||
cls.__unicode__ = cls.__str__
|
||||
cls.__str__ = lambda x: x.__unicode__().encode("utf-8")
|
||||
return cls
|
||||
|
||||
def native_string_result(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
return func(*args, **kwargs).encode("utf-8")
|
||||
|
||||
return functools.update_wrapper(wrapper, func)
|
||||
|
||||
def implements_bool(cls):
|
||||
cls.__nonzero__ = cls.__bool__
|
||||
del cls.__bool__
|
||||
return cls
|
||||
|
||||
from itertools import imap, izip, ifilter
|
||||
|
||||
range_type = xrange
|
||||
|
||||
from StringIO import StringIO
|
||||
from cStringIO import StringIO as BytesIO
|
||||
|
||||
NativeStringIO = BytesIO
|
||||
|
||||
def make_literal_wrapper(reference):
|
||||
return _identity
|
||||
|
||||
def normalize_string_tuple(tup):
|
||||
"""Normalizes a string tuple to a common type. Following Python 2
|
||||
rules, upgrades to unicode are implicit.
|
||||
"""
|
||||
if any(isinstance(x, text_type) for x in tup):
|
||||
return tuple(to_unicode(x) for x in tup)
|
||||
return tup
|
||||
|
||||
def try_coerce_native(s):
|
||||
"""Try to coerce a unicode string to native if possible. Otherwise,
|
||||
leave it as unicode.
|
||||
"""
|
||||
try:
|
||||
return to_native(s)
|
||||
except UnicodeError:
|
||||
return s
|
||||
|
||||
wsgi_get_bytes = _identity
|
||||
|
||||
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
|
||||
return s.decode(charset, errors)
|
||||
|
||||
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
|
||||
if isinstance(s, bytes):
|
||||
return s
|
||||
return s.encode(charset, errors)
|
||||
|
||||
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, (bytes, bytearray, buffer)):
|
||||
return bytes(x)
|
||||
if isinstance(x, unicode):
|
||||
return x.encode(charset, errors)
|
||||
raise TypeError("Expected bytes")
|
||||
|
||||
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
return x.encode(charset, errors)
|
||||
|
||||
|
||||
else:
|
||||
unichr = chr
|
||||
text_type = str
|
||||
string_types = (str,)
|
||||
integer_types = (int,)
|
||||
|
||||
iterkeys = lambda d, *args, **kwargs: iter(d.keys(*args, **kwargs))
|
||||
itervalues = lambda d, *args, **kwargs: iter(d.values(*args, **kwargs))
|
||||
iteritems = lambda d, *args, **kwargs: iter(d.items(*args, **kwargs))
|
||||
|
||||
iterlists = lambda d, *args, **kwargs: iter(d.lists(*args, **kwargs))
|
||||
iterlistvalues = lambda d, *args, **kwargs: iter(d.listvalues(*args, **kwargs))
|
||||
|
||||
int_to_byte = operator.methodcaller("to_bytes", 1, "big")
|
||||
iter_bytes = functools.partial(map, int_to_byte)
|
||||
|
||||
import collections.abc as collections_abc
|
||||
|
||||
def reraise(tp, value, tb=None):
|
||||
if value.__traceback__ is not tb:
|
||||
raise value.with_traceback(tb)
|
||||
raise value
|
||||
|
||||
fix_tuple_repr = _identity
|
||||
implements_iterator = _identity
|
||||
implements_to_string = _identity
|
||||
implements_bool = _identity
|
||||
native_string_result = _identity
|
||||
imap = map
|
||||
izip = zip
|
||||
ifilter = filter
|
||||
range_type = range
|
||||
|
||||
from io import StringIO, BytesIO
|
||||
|
||||
NativeStringIO = StringIO
|
||||
|
||||
_latin1_encode = operator.methodcaller("encode", "latin1")
|
||||
|
||||
def make_literal_wrapper(reference):
|
||||
if isinstance(reference, text_type):
|
||||
return _identity
|
||||
return _latin1_encode
|
||||
|
||||
def normalize_string_tuple(tup):
|
||||
"""Ensures that all types in the tuple are either strings
|
||||
or bytes.
|
||||
"""
|
||||
tupiter = iter(tup)
|
||||
is_text = isinstance(next(tupiter, None), text_type)
|
||||
for arg in tupiter:
|
||||
if isinstance(arg, text_type) != is_text:
|
||||
raise TypeError(
|
||||
"Cannot mix str and bytes arguments (got %s)" % repr(tup)
|
||||
)
|
||||
return tup
|
||||
|
||||
try_coerce_native = _identity
|
||||
wsgi_get_bytes = _latin1_encode
|
||||
|
||||
def wsgi_decoding_dance(s, charset="utf-8", errors="replace"):
|
||||
return s.encode("latin1").decode(charset, errors)
|
||||
|
||||
def wsgi_encoding_dance(s, charset="utf-8", errors="replace"):
|
||||
if isinstance(s, text_type):
|
||||
s = s.encode(charset)
|
||||
return s.decode("latin1", errors)
|
||||
|
||||
def to_bytes(x, charset=sys.getdefaultencoding(), errors="strict"):
|
||||
if x is None:
|
||||
return None
|
||||
if isinstance(x, (bytes, bytearray, memoryview)): # noqa
|
||||
return bytes(x)
|
||||
if isinstance(x, str):
|
||||
return x.encode(charset, errors)
|
||||
raise TypeError("Expected bytes")
|
||||
|
||||
def to_native(x, charset=sys.getdefaultencoding(), errors="strict"):
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
return x.decode(charset, errors)
|
||||
|
||||
|
||||
def to_unicode(
|
||||
x, charset=sys.getdefaultencoding(), errors="strict", allow_none_charset=False
|
||||
):
|
||||
if x is None:
|
||||
return None
|
||||
if not isinstance(x, bytes):
|
||||
return text_type(x)
|
||||
if charset is None and allow_none_charset:
|
||||
return x
|
||||
return x.decode(charset, errors)
|
||||
|
||||
|
||||
try:
|
||||
from os import fspath
|
||||
except ImportError:
|
||||
# Python < 3.6
|
||||
# https://www.python.org/dev/peps/pep-0519/#backwards-compatibility
|
||||
def fspath(path):
|
||||
return path.__fspath__() if hasattr(path, "__fspath__") else path
|
473
venv/lib/python3.7/site-packages/werkzeug/_internal.py
Normal file
473
venv/lib/python3.7/site-packages/werkzeug/_internal.py
Normal file
@@ -0,0 +1,473 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug._internal
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides internally used helpers and constants.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import inspect
|
||||
import logging
|
||||
import re
|
||||
import string
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
from itertools import chain
|
||||
from weakref import WeakKeyDictionary
|
||||
|
||||
from ._compat import int_to_byte
|
||||
from ._compat import integer_types
|
||||
from ._compat import iter_bytes
|
||||
from ._compat import range_type
|
||||
from ._compat import text_type
|
||||
|
||||
|
||||
_logger = None
|
||||
_signature_cache = WeakKeyDictionary()
|
||||
_epoch_ord = date(1970, 1, 1).toordinal()
|
||||
_legal_cookie_chars = (
|
||||
string.ascii_letters + string.digits + u"/=!#$%&'*+-.^_`|~:"
|
||||
).encode("ascii")
|
||||
|
||||
_cookie_quoting_map = {b",": b"\\054", b";": b"\\073", b'"': b'\\"', b"\\": b"\\\\"}
|
||||
for _i in chain(range_type(32), range_type(127, 256)):
|
||||
_cookie_quoting_map[int_to_byte(_i)] = ("\\%03o" % _i).encode("latin1")
|
||||
|
||||
_octal_re = re.compile(br"\\[0-3][0-7][0-7]")
|
||||
_quote_re = re.compile(br"[\\].")
|
||||
_legal_cookie_chars_re = br"[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]"
|
||||
_cookie_re = re.compile(
|
||||
br"""
|
||||
(?P<key>[^=;]+)
|
||||
(?:\s*=\s*
|
||||
(?P<val>
|
||||
"(?:[^\\"]|\\.)*" |
|
||||
(?:.*?)
|
||||
)
|
||||
)?
|
||||
\s*;
|
||||
""",
|
||||
flags=re.VERBOSE,
|
||||
)
|
||||
|
||||
|
||||
class _Missing(object):
|
||||
def __repr__(self):
|
||||
return "no value"
|
||||
|
||||
def __reduce__(self):
|
||||
return "_missing"
|
||||
|
||||
|
||||
_missing = _Missing()
|
||||
|
||||
|
||||
def _get_environ(obj):
|
||||
env = getattr(obj, "environ", obj)
|
||||
assert isinstance(env, dict), (
|
||||
"%r is not a WSGI environment (has to be a dict)" % type(obj).__name__
|
||||
)
|
||||
return env
|
||||
|
||||
|
||||
def _has_level_handler(logger):
|
||||
"""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
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _log(type, message, *args, **kwargs):
|
||||
"""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(logging.StreamHandler())
|
||||
|
||||
getattr(_logger, type)(message.rstrip(), *args, **kwargs)
|
||||
|
||||
|
||||
def _parse_signature(func):
|
||||
"""Return a signature object for the function."""
|
||||
if hasattr(func, "im_func"):
|
||||
func = func.im_func
|
||||
|
||||
# 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
|
||||
if hasattr(inspect, "getfullargspec"):
|
||||
tup = inspect.getfullargspec(func)
|
||||
else:
|
||||
tup = inspect.getargspec(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):
|
||||
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
|
||||
|
||||
|
||||
def _date_to_unix(arg):
|
||||
"""Converts a timetuple, integer or datetime object into the seconds from
|
||||
epoch in utc.
|
||||
"""
|
||||
if isinstance(arg, datetime):
|
||||
arg = arg.utctimetuple()
|
||||
elif isinstance(arg, integer_types + (float,)):
|
||||
return int(arg)
|
||||
year, month, day, hour, minute, second = arg[:6]
|
||||
days = date(year, month, 1).toordinal() - _epoch_ord + day - 1
|
||||
hours = days * 24 + hour
|
||||
minutes = hours * 60 + minute
|
||||
seconds = minutes * 60 + second
|
||||
return seconds
|
||||
|
||||
|
||||
class _DictAccessorProperty(object):
|
||||
"""Baseclass for `environ_property` and `header_property`."""
|
||||
|
||||
read_only = False
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
default=None,
|
||||
load_func=None,
|
||||
dump_func=None,
|
||||
read_only=None,
|
||||
doc=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 __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
storage = self.lookup(obj)
|
||||
if self.name not in storage:
|
||||
return self.default
|
||||
rv = storage[self.name]
|
||||
if self.load_func is not None:
|
||||
try:
|
||||
rv = self.load_func(rv)
|
||||
except (ValueError, TypeError):
|
||||
rv = self.default
|
||||
return rv
|
||||
|
||||
def __set__(self, obj, value):
|
||||
if self.read_only:
|
||||
raise AttributeError("read only property")
|
||||
if self.dump_func is not None:
|
||||
value = self.dump_func(value)
|
||||
self.lookup(obj)[self.name] = value
|
||||
|
||||
def __delete__(self, obj):
|
||||
if self.read_only:
|
||||
raise AttributeError("read only property")
|
||||
self.lookup(obj).pop(self.name, None)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
def _cookie_quote(b):
|
||||
buf = bytearray()
|
||||
all_legal = True
|
||||
_lookup = _cookie_quoting_map.get
|
||||
_push = buf.extend
|
||||
|
||||
for char in iter_bytes(b):
|
||||
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):
|
||||
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):
|
||||
"""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 _cookie_unquote(key), _cookie_unquote(value)
|
||||
|
||||
|
||||
def _encode_idna(domain):
|
||||
# If we're given bytes, make sure they fit into ASCII
|
||||
if not isinstance(domain, text_type):
|
||||
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
|
||||
parts = domain.split(".")
|
||||
for idx, part in enumerate(parts):
|
||||
parts[idx] = part.encode("idna")
|
||||
return b".".join(parts)
|
||||
|
||||
|
||||
def _decode_idna(domain):
|
||||
# If the input is a string try to encode it to ascii to
|
||||
# do the idna decoding. if that fails because of an
|
||||
# unicode error, then we already have a decoded idna domain
|
||||
if isinstance(domain, text_type):
|
||||
try:
|
||||
domain = domain.encode("ascii")
|
||||
except UnicodeError:
|
||||
return domain
|
||||
|
||||
# Decode each part separately. If a part fails, try to
|
||||
# decode it with ascii and silently ignore errors. This makes
|
||||
# most sense because the idna codec does not have error handling
|
||||
parts = domain.split(b".")
|
||||
for idx, part in enumerate(parts):
|
||||
try:
|
||||
parts[idx] = part.decode("idna")
|
||||
except UnicodeError:
|
||||
parts[idx] = part.decode("ascii", "ignore")
|
||||
|
||||
return ".".join(parts)
|
||||
|
||||
|
||||
def _make_cookie_domain(domain):
|
||||
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=None):
|
||||
"""Like the name says. But who knows how it works?"""
|
||||
|
||||
def bzzzzzzz(gyver):
|
||||
import base64
|
||||
import zlib
|
||||
|
||||
return zlib.decompress(base64.b64decode(gyver)).decode("ascii")
|
||||
|
||||
gyver = u"\n".join(
|
||||
[
|
||||
x + (77 - len(x)) * u" "
|
||||
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, start_response):
|
||||
def injecting_start_response(status, headers, exc_info=None):
|
||||
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 [
|
||||
(
|
||||
u"""
|
||||
<!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>%s\n\n\n</pre>
|
||||
</body>
|
||||
</html>"""
|
||||
% gyver
|
||||
).encode("latin1")
|
||||
]
|
||||
|
||||
return easteregged
|
341
venv/lib/python3.7/site-packages/werkzeug/_reloader.py
Normal file
341
venv/lib/python3.7/site-packages/werkzeug/_reloader.py
Normal file
@@ -0,0 +1,341 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from itertools import chain
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._compat import PY2
|
||||
from ._compat import text_type
|
||||
from ._internal import _log
|
||||
|
||||
|
||||
def _iter_module_files():
|
||||
"""This iterates over all relevant Python files. It goes through all
|
||||
loaded files from modules, all files in folders of already loaded modules
|
||||
as well as all files reachable through a package.
|
||||
"""
|
||||
# The list call is necessary on Python 3 in case the module
|
||||
# dictionary modifies during iteration.
|
||||
for module in list(sys.modules.values()):
|
||||
if module is None:
|
||||
continue
|
||||
filename = getattr(module, "__file__", None)
|
||||
if filename:
|
||||
if os.path.isdir(filename) and os.path.exists(
|
||||
os.path.join(filename, "__init__.py")
|
||||
):
|
||||
filename = os.path.join(filename, "__init__.py")
|
||||
|
||||
old = None
|
||||
while not os.path.isfile(filename):
|
||||
old = filename
|
||||
filename = os.path.dirname(filename)
|
||||
if filename == old:
|
||||
break
|
||||
else:
|
||||
if filename[-4:] in (".pyc", ".pyo"):
|
||||
filename = filename[:-1]
|
||||
yield filename
|
||||
|
||||
|
||||
def _find_observable_paths(extra_files=None):
|
||||
"""Finds all paths that should be observed."""
|
||||
rv = set(
|
||||
os.path.dirname(os.path.abspath(x)) if os.path.isfile(x) else os.path.abspath(x)
|
||||
for x in sys.path
|
||||
)
|
||||
|
||||
for filename in extra_files or ():
|
||||
rv.add(os.path.dirname(os.path.abspath(filename)))
|
||||
|
||||
for module in list(sys.modules.values()):
|
||||
fn = getattr(module, "__file__", None)
|
||||
if fn is None:
|
||||
continue
|
||||
fn = os.path.abspath(fn)
|
||||
rv.add(os.path.dirname(fn))
|
||||
|
||||
return _find_common_roots(rv)
|
||||
|
||||
|
||||
def _get_args_for_reloading():
|
||||
"""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(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(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 = __main__.__package__
|
||||
name = os.path.splitext(os.path.basename(py_script))[0]
|
||||
|
||||
if name != "__main__":
|
||||
py_module += "." + 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
|
||||
|
||||
|
||||
def _find_common_roots(paths):
|
||||
"""Out of some paths it finds the common roots that need monitoring."""
|
||||
paths = [x.split(os.path.sep) for x in paths]
|
||||
root = {}
|
||||
for chunks in sorted(paths, key=len, reverse=True):
|
||||
node = root
|
||||
for chunk in chunks:
|
||||
node = node.setdefault(chunk, {})
|
||||
node.clear()
|
||||
|
||||
rv = set()
|
||||
|
||||
def _walk(node, path):
|
||||
for prefix, child in iteritems(node):
|
||||
_walk(child, path + (prefix,))
|
||||
if not node:
|
||||
rv.add("/".join(path))
|
||||
|
||||
_walk(root, ())
|
||||
return rv
|
||||
|
||||
|
||||
class ReloaderLoop(object):
|
||||
name = None
|
||||
|
||||
# monkeypatched by testsuite. wrapping with `staticmethod` is required in
|
||||
# case time.sleep has been replaced by a non-c function (e.g. by
|
||||
# `eventlet.monkey_patch`) before we get here
|
||||
_sleep = staticmethod(time.sleep)
|
||||
|
||||
def __init__(self, extra_files=None, interval=1):
|
||||
self.extra_files = set(os.path.abspath(x) for x in extra_files or ())
|
||||
self.interval = interval
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
|
||||
def restart_with_reloader(self):
|
||||
"""Spawn a new Python interpreter with the same arguments as this one,
|
||||
but running the reloader thread.
|
||||
"""
|
||||
while 1:
|
||||
_log("info", " * Restarting with %s" % self.name)
|
||||
args = _get_args_for_reloading()
|
||||
|
||||
# a weird bug on windows. sometimes unicode strings end up in the
|
||||
# environment and subprocess.call does not like this, encode them
|
||||
# to latin1 and continue.
|
||||
if os.name == "nt" and PY2:
|
||||
new_environ = {}
|
||||
for key, value in iteritems(os.environ):
|
||||
if isinstance(key, text_type):
|
||||
key = key.encode("iso-8859-1")
|
||||
if isinstance(value, text_type):
|
||||
value = value.encode("iso-8859-1")
|
||||
new_environ[key] = value
|
||||
else:
|
||||
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):
|
||||
self.log_reload(filename)
|
||||
sys.exit(3)
|
||||
|
||||
def log_reload(self, filename):
|
||||
filename = os.path.abspath(filename)
|
||||
_log("info", " * Detected change in %r, reloading" % filename)
|
||||
|
||||
|
||||
class StatReloaderLoop(ReloaderLoop):
|
||||
name = "stat"
|
||||
|
||||
def run(self):
|
||||
mtimes = {}
|
||||
while 1:
|
||||
for filename in chain(_iter_module_files(), self.extra_files):
|
||||
try:
|
||||
mtime = os.stat(filename).st_mtime
|
||||
except OSError:
|
||||
continue
|
||||
|
||||
old_time = mtimes.get(filename)
|
||||
if old_time is None:
|
||||
mtimes[filename] = mtime
|
||||
continue
|
||||
elif mtime > old_time:
|
||||
self.trigger_reload(filename)
|
||||
self._sleep(self.interval)
|
||||
|
||||
|
||||
class WatchdogReloaderLoop(ReloaderLoop):
|
||||
def __init__(self, *args, **kwargs):
|
||||
ReloaderLoop.__init__(self, *args, **kwargs)
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
self.observable_paths = set()
|
||||
|
||||
def _check_modification(filename):
|
||||
if filename in self.extra_files:
|
||||
self.trigger_reload(filename)
|
||||
dirname = os.path.dirname(filename)
|
||||
if dirname.startswith(tuple(self.observable_paths)):
|
||||
if filename.endswith((".pyc", ".pyo", ".py")):
|
||||
self.trigger_reload(filename)
|
||||
|
||||
class _CustomHandler(FileSystemEventHandler):
|
||||
def on_created(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
def on_modified(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
def on_moved(self, event):
|
||||
_check_modification(event.src_path)
|
||||
_check_modification(event.dest_path)
|
||||
|
||||
def on_deleted(self, event):
|
||||
_check_modification(event.src_path)
|
||||
|
||||
reloader_name = Observer.__name__.lower()
|
||||
if reloader_name.endswith("observer"):
|
||||
reloader_name = reloader_name[:-8]
|
||||
reloader_name += " reloader"
|
||||
|
||||
self.name = reloader_name
|
||||
|
||||
self.observer_class = Observer
|
||||
self.event_handler = _CustomHandler()
|
||||
self.should_reload = False
|
||||
|
||||
def trigger_reload(self, filename):
|
||||
# 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 run(self):
|
||||
watches = {}
|
||||
observer = self.observer_class()
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while not self.should_reload:
|
||||
to_delete = set(watches)
|
||||
paths = _find_observable_paths(self.extra_files)
|
||||
for path in paths:
|
||||
if path not in watches:
|
||||
try:
|
||||
watches[path] = 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.
|
||||
watches[path] = None
|
||||
to_delete.discard(path)
|
||||
for path in to_delete:
|
||||
watch = watches.pop(path, None)
|
||||
if watch is not None:
|
||||
observer.unschedule(watch)
|
||||
self.observable_paths = paths
|
||||
self._sleep(self.interval)
|
||||
finally:
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
reloader_loops = {"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():
|
||||
"""Ensure that echo mode is enabled. Some tools such as PDB disable
|
||||
it which causes usability issues after reload."""
|
||||
# tcgetattr will fail if stdin isn't a tty
|
||||
if 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, extra_files=None, interval=1, reloader_type="auto"):
|
||||
"""Run the given function in an independent python interpreter."""
|
||||
import signal
|
||||
|
||||
reloader = reloader_loops[reloader_type](extra_files, interval)
|
||||
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
|
||||
try:
|
||||
if os.environ.get("WERKZEUG_RUN_MAIN") == "true":
|
||||
ensure_echo_on()
|
||||
t = threading.Thread(target=main_func, args=())
|
||||
t.setDaemon(True)
|
||||
t.start()
|
||||
reloader.run()
|
||||
else:
|
||||
sys.exit(reloader.restart_with_reloader())
|
||||
except KeyboardInterrupt:
|
||||
pass
|
3118
venv/lib/python3.7/site-packages/werkzeug/datastructures.py
Normal file
3118
venv/lib/python3.7/site-packages/werkzeug/datastructures.py
Normal file
File diff suppressed because it is too large
Load Diff
498
venv/lib/python3.7/site-packages/werkzeug/debug/__init__.py
Normal file
498
venv/lib/python3.7/site-packages/werkzeug/debug/__init__.py
Normal file
@@ -0,0 +1,498 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
WSGI application traceback debugger.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import getpass
|
||||
import hashlib
|
||||
import json
|
||||
import mimetypes
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import uuid
|
||||
from itertools import chain
|
||||
from os.path import basename
|
||||
from os.path import join
|
||||
|
||||
from .._compat import text_type
|
||||
from .._internal import _log
|
||||
from ..http import parse_cookie
|
||||
from ..security import gen_salt
|
||||
from ..wrappers import BaseRequest as Request
|
||||
from ..wrappers import BaseResponse as Response
|
||||
from .console import Console
|
||||
from .tbtools import get_current_traceback
|
||||
from .tbtools import render_console_html
|
||||
|
||||
# A week
|
||||
PIN_TIME = 60 * 60 * 24 * 7
|
||||
|
||||
|
||||
def hash_pin(pin):
|
||||
if isinstance(pin, text_type):
|
||||
pin = pin.encode("utf-8", "replace")
|
||||
return hashlib.md5(pin + b"shittysalt").hexdigest()[:12]
|
||||
|
||||
|
||||
_machine_id = None
|
||||
|
||||
|
||||
def get_machine_id():
|
||||
global _machine_id
|
||||
|
||||
if _machine_id is not None:
|
||||
return _machine_id
|
||||
|
||||
def _generate():
|
||||
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 IOError:
|
||||
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 IOError:
|
||||
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.
|
||||
try:
|
||||
import winreg as wr
|
||||
except ImportError:
|
||||
try:
|
||||
import _winreg as wr
|
||||
except ImportError:
|
||||
wr = None
|
||||
|
||||
if wr is not None:
|
||||
try:
|
||||
with wr.OpenKey(
|
||||
wr.HKEY_LOCAL_MACHINE,
|
||||
"SOFTWARE\\Microsoft\\Cryptography",
|
||||
0,
|
||||
wr.KEY_READ | wr.KEY_WOW64_64KEY,
|
||||
) as rk:
|
||||
guid, guid_type = wr.QueryValueEx(rk, "MachineGuid")
|
||||
|
||||
if guid_type == wr.REG_SZ:
|
||||
return guid.encode("utf-8")
|
||||
|
||||
return guid
|
||||
except WindowsError:
|
||||
pass
|
||||
|
||||
_machine_id = _generate()
|
||||
return _machine_id
|
||||
|
||||
|
||||
class _ConsoleFrame(object):
|
||||
"""Helper class so that we can reuse the frame console code for the
|
||||
standalone console.
|
||||
"""
|
||||
|
||||
def __init__(self, namespace):
|
||||
self.console = Console(namespace)
|
||||
self.id = 0
|
||||
|
||||
|
||||
def get_pin_and_cookie_name(app):
|
||||
"""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__", app.__class__.__module__)
|
||||
|
||||
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__", app.__class__.__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.md5()
|
||||
for bit in chain(probably_public_bits, private_bits):
|
||||
if not bit:
|
||||
continue
|
||||
if isinstance(bit, text_type):
|
||||
bit = bit.encode("utf-8")
|
||||
h.update(bit)
|
||||
h.update(b"cookiesalt")
|
||||
|
||||
cookie_name = "__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 = ("%09d" % int(h.hexdigest(), 16))[: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(object):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
app,
|
||||
evalex=False,
|
||||
request_key="werkzeug.request",
|
||||
console_path="/console",
|
||||
console_init_func=None,
|
||||
show_hidden_frames=False,
|
||||
pin_security=True,
|
||||
pin_logging=True,
|
||||
):
|
||||
if not console_init_func:
|
||||
console_init_func = None
|
||||
self.app = app
|
||||
self.evalex = evalex
|
||||
self.frames = {}
|
||||
self.tracebacks = {}
|
||||
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):
|
||||
if not hasattr(self, "_pin"):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin
|
||||
|
||||
@pin.setter
|
||||
def pin(self, value):
|
||||
self._pin = value
|
||||
|
||||
@property
|
||||
def pin_cookie_name(self):
|
||||
"""The name of the pin cookie."""
|
||||
if not hasattr(self, "_pin_cookie"):
|
||||
self._pin, self._pin_cookie = get_pin_and_cookie_name(self.app)
|
||||
return self._pin_cookie
|
||||
|
||||
def debug_application(self, environ, start_response):
|
||||
"""Run the application and conserve the traceback frames."""
|
||||
app_iter = None
|
||||
try:
|
||||
app_iter = self.app(environ, start_response)
|
||||
for item in app_iter:
|
||||
yield item
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close()
|
||||
except Exception:
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close()
|
||||
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, command, frame):
|
||||
"""Execute a command in a console."""
|
||||
return Response(frame.console.eval(command), mimetype="text/html")
|
||||
|
||||
def display_console(self, request):
|
||||
"""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 paste_traceback(self, request, traceback):
|
||||
"""Paste the traceback and return a JSON response."""
|
||||
rv = traceback.paste()
|
||||
return Response(json.dumps(rv), mimetype="application/json")
|
||||
|
||||
def get_resource(self, request, filename):
|
||||
"""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):
|
||||
"""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):
|
||||
time.sleep(5.0 if self._failed_pin_auth > 5 else 0.5)
|
||||
self._failed_pin_auth += 1
|
||||
|
||||
def pin_auth(self, request):
|
||||
"""Authenticates with the pin."""
|
||||
exhausted = False
|
||||
auth = False
|
||||
trust = self.check_pin_trust(request.environ)
|
||||
|
||||
# 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.get("pin")
|
||||
if entered_pin.strip().replace("-", "") == self.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,
|
||||
"%s|%s" % (int(time.time()), hash_pin(self.pin)),
|
||||
httponly=True,
|
||||
)
|
||||
elif bad_cookie:
|
||||
rv.delete_cookie(self.pin_cookie_name)
|
||||
return rv
|
||||
|
||||
def log_pin_request(self):
|
||||
"""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, start_response):
|
||||
"""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")
|
||||
traceback = self.tracebacks.get(request.args.get("tb", type=int))
|
||||
frame = self.frames.get(request.args.get("frm", type=int))
|
||||
if cmd == "resource" and arg:
|
||||
response = self.get_resource(request, arg)
|
||||
elif cmd == "paste" and traceback is not None and secret == self.secret:
|
||||
response = self.paste_traceback(request, traceback)
|
||||
elif cmd == "pinauth" and secret == self.secret:
|
||||
response = self.pin_auth(request)
|
||||
elif cmd == "printpin" and secret == self.secret:
|
||||
response = self.log_pin_request()
|
||||
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)
|
||||
elif (
|
||||
self.evalex
|
||||
and self.console_path is not None
|
||||
and request.path == self.console_path
|
||||
):
|
||||
response = self.display_console(request)
|
||||
return response(environ, start_response)
|
217
venv/lib/python3.7/site-packages/werkzeug/debug/console.py
Normal file
217
venv/lib/python3.7/site-packages/werkzeug/debug/console.py
Normal file
@@ -0,0 +1,217 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.console
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Interactive console support.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import code
|
||||
import sys
|
||||
from types import CodeType
|
||||
|
||||
from ..local import Local
|
||||
from ..utils import escape
|
||||
from .repr import debug_repr
|
||||
from .repr import dump
|
||||
from .repr import helper
|
||||
|
||||
|
||||
_local = Local()
|
||||
|
||||
|
||||
class HTMLStringO(object):
|
||||
"""A StringO version that HTML escapes on write."""
|
||||
|
||||
def __init__(self):
|
||||
self._buffer = []
|
||||
|
||||
def isatty(self):
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def seek(self, n, mode=0):
|
||||
pass
|
||||
|
||||
def readline(self):
|
||||
if len(self._buffer) == 0:
|
||||
return ""
|
||||
ret = self._buffer[0]
|
||||
del self._buffer[0]
|
||||
return ret
|
||||
|
||||
def reset(self):
|
||||
val = "".join(self._buffer)
|
||||
del self._buffer[:]
|
||||
return val
|
||||
|
||||
def _write(self, x):
|
||||
if isinstance(x, bytes):
|
||||
x = x.decode("utf-8", "replace")
|
||||
self._buffer.append(x)
|
||||
|
||||
def write(self, x):
|
||||
self._write(escape(x))
|
||||
|
||||
def writelines(self, x):
|
||||
self._write(escape("".join(x)))
|
||||
|
||||
|
||||
class ThreadedStream(object):
|
||||
"""Thread-local wrapper for sys.stdout for the interactive console."""
|
||||
|
||||
@staticmethod
|
||||
def push():
|
||||
if not isinstance(sys.stdout, ThreadedStream):
|
||||
sys.stdout = ThreadedStream()
|
||||
_local.stream = HTMLStringO()
|
||||
|
||||
@staticmethod
|
||||
def fetch():
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return ""
|
||||
return stream.reset()
|
||||
|
||||
@staticmethod
|
||||
def displayhook(obj):
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
return _displayhook(obj)
|
||||
# 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, value):
|
||||
raise AttributeError("read only attribute %s" % name)
|
||||
|
||||
def __dir__(self):
|
||||
return dir(sys.__stdout__)
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name == "__members__":
|
||||
return dir(sys.__stdout__)
|
||||
try:
|
||||
stream = _local.stream
|
||||
except AttributeError:
|
||||
stream = sys.__stdout__
|
||||
return getattr(stream, name)
|
||||
|
||||
def __repr__(self):
|
||||
return repr(sys.__stdout__)
|
||||
|
||||
|
||||
# add the threaded stream as display hook
|
||||
_displayhook = sys.displayhook
|
||||
sys.displayhook = ThreadedStream.displayhook
|
||||
|
||||
|
||||
class _ConsoleLoader(object):
|
||||
def __init__(self):
|
||||
self._storage = {}
|
||||
|
||||
def register(self, code, source):
|
||||
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):
|
||||
try:
|
||||
return self._storage[id(code)]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def _wrap_compiler(console):
|
||||
compile = console.compile
|
||||
|
||||
def func(source, filename, symbol):
|
||||
code = compile(source, filename, symbol)
|
||||
console.loader.register(code, source)
|
||||
return code
|
||||
|
||||
console.compile = func
|
||||
|
||||
|
||||
class _InteractiveConsole(code.InteractiveInterpreter):
|
||||
def __init__(self, globals, locals):
|
||||
locals = dict(globals)
|
||||
locals.update(locals)
|
||||
locals["dump"] = dump
|
||||
locals["help"] = helper
|
||||
locals["__loader__"] = self.loader = _ConsoleLoader()
|
||||
code.InteractiveInterpreter.__init__(self, locals)
|
||||
self.more = False
|
||||
self.buffer = []
|
||||
_wrap_compiler(self)
|
||||
|
||||
def runsource(self, source):
|
||||
source = source.rstrip() + "\n"
|
||||
ThreadedStream.push()
|
||||
prompt = "... " if self.more else ">>> "
|
||||
try:
|
||||
source_to_eval = "".join(self.buffer + [source])
|
||||
if code.InteractiveInterpreter.runsource(
|
||||
self, 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):
|
||||
try:
|
||||
exec(code, self.locals)
|
||||
except Exception:
|
||||
self.showtraceback()
|
||||
|
||||
def showtraceback(self):
|
||||
from .tbtools import get_current_traceback
|
||||
|
||||
tb = get_current_traceback(skip=1)
|
||||
sys.stdout._write(tb.render_summary())
|
||||
|
||||
def showsyntaxerror(self, filename=None):
|
||||
from .tbtools import get_current_traceback
|
||||
|
||||
tb = get_current_traceback(skip=4)
|
||||
sys.stdout._write(tb.render_summary())
|
||||
|
||||
def write(self, data):
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
class Console(object):
|
||||
"""An interactive console."""
|
||||
|
||||
def __init__(self, globals=None, locals=None):
|
||||
if locals is None:
|
||||
locals = {}
|
||||
if globals is None:
|
||||
globals = {}
|
||||
self._ipy = _InteractiveConsole(globals, locals)
|
||||
|
||||
def eval(self, code):
|
||||
_local._current_ipy = self._ipy
|
||||
old_sys_stdout = sys.stdout
|
||||
try:
|
||||
return self._ipy.runsource(code)
|
||||
finally:
|
||||
sys.stdout = old_sys_stdout
|
297
venv/lib/python3.7/site-packages/werkzeug/debug/repr.py
Normal file
297
venv/lib/python3.7/site-packages/werkzeug/debug/repr.py
Normal file
@@ -0,0 +1,297 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.repr
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements object representations for debugging purposes.
|
||||
Unlike the default repr these reprs expose a lot more information and
|
||||
produce HTML instead of ASCII.
|
||||
|
||||
Together with the CSS and JavaScript files of the debugger this gives
|
||||
a colorful and more compact output.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import re
|
||||
import sys
|
||||
from collections import deque
|
||||
from traceback import format_exception_only
|
||||
|
||||
from .._compat import integer_types
|
||||
from .._compat import iteritems
|
||||
from .._compat import PY2
|
||||
from .._compat import string_types
|
||||
from .._compat import text_type
|
||||
from ..utils import escape
|
||||
|
||||
|
||||
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):
|
||||
"""Creates a debug repr of an object as HTML unicode string."""
|
||||
return DebugReprGenerator().repr(obj)
|
||||
|
||||
|
||||
def dump(obj=missing):
|
||||
"""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)
|
||||
|
||||
|
||||
class _Helper(object):
|
||||
"""Displays an HTML version of the normal help, for the interactive
|
||||
debugger only because it requires a patched sys.stdout.
|
||||
"""
|
||||
|
||||
def __repr__(self):
|
||||
return "Type help(object) for help about object."
|
||||
|
||||
def __call__(self, topic=None):
|
||||
if topic is None:
|
||||
sys.stdout._write("<span class=help>%s</span>" % repr(self))
|
||||
return
|
||||
import pydoc
|
||||
|
||||
pydoc.help(topic)
|
||||
rv = sys.stdout.reset()
|
||||
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: # pragma: no cover
|
||||
title = "Help"
|
||||
text = paragraphs[0]
|
||||
sys.stdout._write(HELP_HTML % {"title": title, "text": text})
|
||||
|
||||
|
||||
helper = _Helper()
|
||||
|
||||
|
||||
def _add_subclass_info(inner, obj, base):
|
||||
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 = '<span class="module">%s.</span>' % obj.__class__.__module__
|
||||
return "%s%s(%s)" % (module, obj.__class__.__name__, inner)
|
||||
|
||||
|
||||
class DebugReprGenerator(object):
|
||||
def __init__(self):
|
||||
self._stack = []
|
||||
|
||||
def _sequence_repr_maker(left, right, base=object(), limit=8): # noqa: B008, B902
|
||||
def proxy(self, obj, recursive):
|
||||
if recursive:
|
||||
return _add_subclass_info(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(u"".join(buf), obj, base)
|
||||
|
||||
return proxy
|
||||
|
||||
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
|
||||
)
|
||||
del _sequence_repr_maker
|
||||
|
||||
def regex_repr(self, obj):
|
||||
pattern = repr(obj.pattern)
|
||||
if PY2:
|
||||
pattern = pattern.decode("string-escape", "ignore")
|
||||
else:
|
||||
pattern = codecs.decode(pattern, "unicode-escape", "ignore")
|
||||
if pattern[:1] == "u":
|
||||
pattern = "ur" + pattern[1:]
|
||||
else:
|
||||
pattern = "r" + pattern
|
||||
return u're.compile(<span class="string regex">%s</span>)' % pattern
|
||||
|
||||
def string_repr(self, obj, limit=70):
|
||||
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 = u"".join(buf)
|
||||
|
||||
# if the repr looks like a standard string, add subclass info if needed
|
||||
if r[0] in "'\"" or (r[0] in "ub" and r[1] in "'\""):
|
||||
return _add_subclass_info(out, obj, (bytes, text_type))
|
||||
|
||||
# otherwise, assume the repr distinguishes the subclass already
|
||||
return out
|
||||
|
||||
def dict_repr(self, d, recursive, limit=5):
|
||||
if recursive:
|
||||
return _add_subclass_info(u"{...}", d, dict)
|
||||
buf = ["{"]
|
||||
have_extended_section = False
|
||||
for idx, (key, value) in enumerate(iteritems(d)):
|
||||
if idx:
|
||||
buf.append(", ")
|
||||
if idx == limit - 1:
|
||||
buf.append('<span class="extended">')
|
||||
have_extended_section = True
|
||||
buf.append(
|
||||
'<span class="pair"><span class="key">%s</span>: '
|
||||
'<span class="value">%s</span></span>'
|
||||
% (self.repr(key), self.repr(value))
|
||||
)
|
||||
if have_extended_section:
|
||||
buf.append("</span>")
|
||||
buf.append("}")
|
||||
return _add_subclass_info(u"".join(buf), d, dict)
|
||||
|
||||
def object_repr(self, obj):
|
||||
r = repr(obj)
|
||||
if PY2:
|
||||
r = r.decode("utf-8", "replace")
|
||||
return u'<span class="object">%s</span>' % escape(r)
|
||||
|
||||
def dispatch_repr(self, obj, recursive):
|
||||
if obj is helper:
|
||||
return u'<span class="help">%r</span>' % helper
|
||||
if isinstance(obj, (integer_types, float, complex)):
|
||||
return u'<span class="number">%r</span>' % obj
|
||||
if isinstance(obj, string_types) 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 deque is not None and isinstance(obj, deque):
|
||||
return self.deque_repr(obj, recursive)
|
||||
return self.object_repr(obj)
|
||||
|
||||
def fallback_repr(self):
|
||||
try:
|
||||
info = "".join(format_exception_only(*sys.exc_info()[:2]))
|
||||
except Exception: # pragma: no cover
|
||||
info = "?"
|
||||
if PY2:
|
||||
info = info.decode("utf-8", "ignore")
|
||||
return u'<span class="brokenrepr"><broken repr (%s)>' u"</span>" % escape(
|
||||
info.strip()
|
||||
)
|
||||
|
||||
def repr(self, obj):
|
||||
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):
|
||||
repr = items = None
|
||||
if isinstance(obj, dict):
|
||||
title = "Contents of"
|
||||
items = []
|
||||
for key, value in iteritems(obj):
|
||||
if not isinstance(key, string_types):
|
||||
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 += " " + object.__repr__(obj)[1:-1]
|
||||
return self.render_object_dump(items, title, repr)
|
||||
|
||||
def dump_locals(self, d):
|
||||
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, title, repr=None):
|
||||
html_items = []
|
||||
for key, value in items:
|
||||
html_items.append(
|
||||
"<tr><th>%s<td><pre class=repr>%s</pre>" % (escape(key), value)
|
||||
)
|
||||
if not html_items:
|
||||
html_items.append("<tr><td><em>Nothing</em>")
|
||||
return OBJECT_DUMP_HTML % {
|
||||
"title": escape(title),
|
||||
"repr": "<pre class=repr>%s</pre>" % repr if repr else "",
|
||||
"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.
|
Binary file not shown.
After Width: | Height: | Size: 507 B |
@@ -0,0 +1,210 @@
|
||||
$(function() {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
initPinBox();
|
||||
}
|
||||
|
||||
/**
|
||||
* if we are in console mode, show the console.
|
||||
*/
|
||||
if (CONSOLE_MODE && EVALEX) {
|
||||
openShell(null, $('div.console div.inner').empty(), 0);
|
||||
}
|
||||
|
||||
$("div.detail").click(function() {
|
||||
$("div.traceback").get(0).scrollIntoView(false);
|
||||
});
|
||||
|
||||
$('div.traceback div.frame').each(function() {
|
||||
var
|
||||
target = $('pre', this),
|
||||
consoleNode = null,
|
||||
frameID = this.id.substring(6);
|
||||
|
||||
target.click(function() {
|
||||
$(this).parent().toggleClass('expanded');
|
||||
});
|
||||
|
||||
/**
|
||||
* Add an interactive console to the frames
|
||||
*/
|
||||
if (EVALEX && target.is('.current')) {
|
||||
$('<img src="?__debugger__=yes&cmd=resource&f=console.png">')
|
||||
.attr('title', 'Open an interactive python shell in this frame')
|
||||
.click(function() {
|
||||
consoleNode = openShell(consoleNode, target, frameID);
|
||||
return false;
|
||||
})
|
||||
.prependTo(target);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* toggle traceback types on click.
|
||||
*/
|
||||
$('h2.traceback').click(function() {
|
||||
$(this).next().slideToggle('fast');
|
||||
$('div.plain').slideToggle('fast');
|
||||
}).css('cursor', 'pointer');
|
||||
$('div.plain').hide();
|
||||
|
||||
/**
|
||||
* Add extra info (this is here so that only users with JavaScript
|
||||
* enabled see it.)
|
||||
*/
|
||||
$('span.nojavascript')
|
||||
.removeClass('nojavascript')
|
||||
.html('<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>'));
|
||||
|
||||
/**
|
||||
* Add the pastebin feature
|
||||
*/
|
||||
$('div.plain form')
|
||||
.submit(function() {
|
||||
var label = $('input[type="submit"]', this);
|
||||
var old_val = label.val();
|
||||
label.val('submitting...');
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', tb: TRACEBACK, cmd: 'paste',
|
||||
s: SECRET},
|
||||
success: function(data) {
|
||||
$('div.plain span.pastemessage')
|
||||
.removeClass('pastemessage')
|
||||
.text('Paste created: ')
|
||||
.append($('<a>#' + data.id + '</a>').attr('href', data.url));
|
||||
},
|
||||
error: function() {
|
||||
alert('Error: Could not submit paste. No network connection?');
|
||||
label.val(old_val);
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// if we have javascript we submit by ajax anyways, so no need for the
|
||||
// not scaling textarea.
|
||||
var plainTraceback = $('div.plain textarea');
|
||||
plainTraceback.replaceWith($('<pre>').text(plainTraceback.text()));
|
||||
});
|
||||
|
||||
function initPinBox() {
|
||||
$('.pin-prompt form').submit(function(evt) {
|
||||
evt.preventDefault();
|
||||
var pin = this.pin.value;
|
||||
var btn = this.btn;
|
||||
btn.disabled = true;
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', cmd: 'pinauth', pin: pin,
|
||||
s: SECRET},
|
||||
success: function(data) {
|
||||
btn.disabled = false;
|
||||
if (data.auth) {
|
||||
EVALEX_TRUSTED = true;
|
||||
$('.pin-prompt').fadeOut();
|
||||
} else {
|
||||
if (data.exhausted) {
|
||||
alert('Error: too many attempts. Restart server to retry.');
|
||||
} else {
|
||||
alert('Error: incorrect pin');
|
||||
}
|
||||
}
|
||||
console.log(data);
|
||||
},
|
||||
error: function() {
|
||||
btn.disabled = false;
|
||||
alert('Error: Could not verify PIN. Network error?');
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function promptForPin() {
|
||||
if (!EVALEX_TRUSTED) {
|
||||
$.ajax({
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', cmd: 'printpin', s: SECRET}
|
||||
});
|
||||
$('.pin-prompt').fadeIn(function() {
|
||||
$('.pin-prompt input[name="pin"]').focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper function for shell initialization
|
||||
*/
|
||||
function openShell(consoleNode, target, frameID) {
|
||||
promptForPin();
|
||||
if (consoleNode)
|
||||
return consoleNode.slideToggle('fast');
|
||||
consoleNode = $('<pre class="console">')
|
||||
.appendTo(target.parent())
|
||||
.hide()
|
||||
var historyPos = 0, history = [''];
|
||||
var output = $('<div class="output">[console ready]</div>')
|
||||
.appendTo(consoleNode);
|
||||
var form = $('<form>>>> </form>')
|
||||
.submit(function() {
|
||||
var cmd = command.val();
|
||||
$.get('', {
|
||||
__debugger__: 'yes', cmd: cmd, frm: frameID, s: SECRET}, function(data) {
|
||||
var tmp = $('<div>').html(data);
|
||||
$('span.extended', tmp).each(function() {
|
||||
var hidden = $(this).wrap('<span>').hide();
|
||||
hidden
|
||||
.parent()
|
||||
.append($('<a href="#" class="toggle"> </a>')
|
||||
.click(function() {
|
||||
hidden.toggle();
|
||||
$(this).toggleClass('open')
|
||||
return false;
|
||||
}));
|
||||
});
|
||||
output.append(tmp);
|
||||
command.focus();
|
||||
consoleNode.scrollTop(consoleNode.get(0).scrollHeight);
|
||||
var old = history.pop();
|
||||
history.push(cmd);
|
||||
if (typeof old != 'undefined')
|
||||
history.push(old);
|
||||
historyPos = history.length - 1;
|
||||
});
|
||||
command.val('');
|
||||
return false;
|
||||
}).
|
||||
appendTo(consoleNode);
|
||||
|
||||
var command = $('<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">')
|
||||
.appendTo(form)
|
||||
.keydown(function(e) {
|
||||
if (e.key == 'l' && e.ctrlKey) {
|
||||
output.text('--- screen cleared ---');
|
||||
return false;
|
||||
}
|
||||
else if (e.charCode == 0 && (e.keyCode == 38 || e.keyCode == 40)) {
|
||||
// handle up arrow and down arrow
|
||||
if (e.keyCode == 38 && historyPos > 0)
|
||||
historyPos--;
|
||||
else if (e.keyCode == 40 && historyPos < history.length)
|
||||
historyPos++;
|
||||
command.val(history[historyPos]);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
return consoleNode.slideDown('fast', function() {
|
||||
command.focus();
|
||||
});
|
||||
}
|
2
venv/lib/python3.7/site-packages/werkzeug/debug/shared/jquery.js
vendored
Normal file
2
venv/lib/python3.7/site-packages/werkzeug/debug/shared/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
venv/lib/python3.7/site-packages/werkzeug/debug/shared/less.png
Normal file
BIN
venv/lib/python3.7/site-packages/werkzeug/debug/shared/less.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 191 B |
BIN
venv/lib/python3.7/site-packages/werkzeug/debug/shared/more.png
Normal file
BIN
venv/lib/python3.7/site-packages/werkzeug/debug/shared/more.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 200 B |
Binary file not shown.
After Width: | Height: | Size: 818 B |
154
venv/lib/python3.7/site-packages/werkzeug/debug/shared/style.css
Normal file
154
venv/lib/python3.7/site-packages/werkzeug/debug/shared/style.css
Normal file
@@ -0,0 +1,154 @@
|
||||
@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;
|
||||
}
|
Binary file not shown.
628
venv/lib/python3.7/site-packages/werkzeug/debug/tbtools.py
Normal file
628
venv/lib/python3.7/site-packages/werkzeug/debug/tbtools.py
Normal file
@@ -0,0 +1,628 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.tbtools
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides various traceback related utility functions.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import sysconfig
|
||||
import traceback
|
||||
from tokenize import TokenError
|
||||
|
||||
from .._compat import PY2
|
||||
from .._compat import range_type
|
||||
from .._compat import reraise
|
||||
from .._compat import string_types
|
||||
from .._compat import text_type
|
||||
from .._compat import to_native
|
||||
from .._compat import to_unicode
|
||||
from ..filesystem import get_filesystem_encoding
|
||||
from ..utils import cached_property
|
||||
from ..utils import escape
|
||||
from .console import Console
|
||||
|
||||
|
||||
_coding_re = re.compile(br"coding[:=]\s*([-\w.]+)")
|
||||
_line_re = re.compile(br"^(.*?)$", re.MULTILINE)
|
||||
_funcdef_re = re.compile(r"^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)")
|
||||
UTF8_COOKIE = b"\xef\xbb\xbf"
|
||||
|
||||
system_exceptions = (SystemExit, KeyboardInterrupt)
|
||||
try:
|
||||
system_exceptions += (GeneratorExit,)
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
HEADER = u"""\
|
||||
<!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 by accident trigger a request to /favicon.ico which might
|
||||
change the application state. -->
|
||||
<link rel="shortcut icon"
|
||||
href="?__debugger__=yes&cmd=resource&f=console.png">
|
||||
<script src="?__debugger__=yes&cmd=resource&f=jquery.js"></script>
|
||||
<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 = u"""\
|
||||
<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
|
||||
+ u"""\
|
||||
<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">
|
||||
<form action="/?__debugger__=yes&cmd=paste" method="post">
|
||||
<p>
|
||||
<input type="hidden" name="language" value="pytb">
|
||||
This is the Copy/Paste friendly version of the traceback. <span
|
||||
class="pastemessage">You can also paste this traceback into
|
||||
a <a href="https://gist.github.com/">gist</a>:
|
||||
<input type="submit" value="create paste"></span>
|
||||
</p>
|
||||
<textarea cols="50" rows="10" name="code" readonly>%(plaintext)s</textarea>
|
||||
</form>
|
||||
</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
|
||||
+ u"""\
|
||||
<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 = u"""\
|
||||
<div class="%(classes)s">
|
||||
%(title)s
|
||||
<ul>%(frames)s</ul>
|
||||
%(description)s
|
||||
</div>
|
||||
"""
|
||||
|
||||
FRAME_HTML = u"""\
|
||||
<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 = u"""\
|
||||
<tr class="%(classes)s">
|
||||
<td class=lineno>%(lineno)s</td>
|
||||
<td>%(code)s</td>
|
||||
</tr>
|
||||
"""
|
||||
|
||||
|
||||
def render_console_html(secret, evalex_trusted=True):
|
||||
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=False, show_hidden_frames=False, skip=0
|
||||
):
|
||||
"""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.
|
||||
"""
|
||||
exc_type, exc_value, tb = sys.exc_info()
|
||||
if ignore_system_exceptions and exc_type in system_exceptions:
|
||||
reraise(exc_type, exc_value, tb)
|
||||
for _ in range_type(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(object):
|
||||
"""Helper for the source renderer."""
|
||||
|
||||
__slots__ = ("lineno", "code", "in_frame", "current")
|
||||
|
||||
def __init__(self, lineno, code):
|
||||
self.lineno = lineno
|
||||
self.code = code
|
||||
self.in_frame = False
|
||||
self.current = False
|
||||
|
||||
@property
|
||||
def classes(self):
|
||||
rv = ["line"]
|
||||
if self.in_frame:
|
||||
rv.append("in-frame")
|
||||
if self.current:
|
||||
rv.append("current")
|
||||
return rv
|
||||
|
||||
def render(self):
|
||||
return SOURCE_LINE_HTML % {
|
||||
"classes": u" ".join(self.classes),
|
||||
"lineno": self.lineno,
|
||||
"code": escape(self.code),
|
||||
}
|
||||
|
||||
|
||||
class Traceback(object):
|
||||
"""Wraps a traceback."""
|
||||
|
||||
def __init__(self, exc_type, exc_value, tb):
|
||||
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 = 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))
|
||||
if PY2:
|
||||
break
|
||||
exc_value = exc_value.__cause__ or exc_value.__context__
|
||||
if exc_value is None or id(exc_value) in memo:
|
||||
break
|
||||
exc_type = type(exc_value)
|
||||
tb = exc_value.__traceback__
|
||||
self.groups.reverse()
|
||||
self.frames = [frame for group in self.groups for frame in group.frames]
|
||||
|
||||
def filter_hidden_frames(self):
|
||||
"""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):
|
||||
"""Is it a syntax error?"""
|
||||
return isinstance(self.exc_value, SyntaxError)
|
||||
|
||||
@property
|
||||
def exception(self):
|
||||
"""String representation of the final exception."""
|
||||
return self.groups[-1].exception
|
||||
|
||||
def log(self, logfile=None):
|
||||
"""Log the ASCII traceback into a file object."""
|
||||
if logfile is None:
|
||||
logfile = sys.stderr
|
||||
tb = self.plaintext.rstrip() + u"\n"
|
||||
logfile.write(to_native(tb, "utf-8", "replace"))
|
||||
|
||||
def paste(self):
|
||||
"""Create a paste and return the paste id."""
|
||||
data = json.dumps(
|
||||
{
|
||||
"description": "Werkzeug Internal Server Error",
|
||||
"public": False,
|
||||
"files": {"traceback.txt": {"content": self.plaintext}},
|
||||
}
|
||||
).encode("utf-8")
|
||||
try:
|
||||
from urllib2 import urlopen
|
||||
except ImportError:
|
||||
from urllib.request import urlopen
|
||||
rv = urlopen("https://api.github.com/gists", data=data)
|
||||
resp = json.loads(rv.read().decode("utf-8"))
|
||||
rv.close()
|
||||
return {"url": resp["html_url"], "id": resp["id"]}
|
||||
|
||||
def render_summary(self, include_title=True):
|
||||
"""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 = u"Syntax Error"
|
||||
else:
|
||||
title = u"Traceback <em>(most recent call last)</em>:"
|
||||
|
||||
if self.is_syntax_error:
|
||||
description_wrapper = u"<pre class=syntaxerror>%s</pre>"
|
||||
else:
|
||||
description_wrapper = u"<blockquote>%s</blockquote>"
|
||||
|
||||
return SUMMARY_HTML % {
|
||||
"classes": u" ".join(classes),
|
||||
"title": u"<h3>%s</h3>" % title if title else u"",
|
||||
"frames": u"\n".join(frames),
|
||||
"description": description_wrapper % escape(self.exception),
|
||||
}
|
||||
|
||||
def render_full(self, evalex=False, secret=None, evalex_trusted=True):
|
||||
"""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):
|
||||
return u"\n".join([group.render_text() for group in self.groups])
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return id(self)
|
||||
|
||||
|
||||
class Group(object):
|
||||
"""A group of frames for an exception in a traceback. On Python 3,
|
||||
if the exception has a ``__cause__`` or ``__context__``, there are
|
||||
multiple exception groups.
|
||||
"""
|
||||
|
||||
def __init__(self, exc_type, exc_value, tb):
|
||||
self.exc_type = exc_type
|
||||
self.exc_value = exc_value
|
||||
self.info = None
|
||||
if not PY2:
|
||||
if exc_value.__cause__ is not None:
|
||||
self.info = (
|
||||
u"The above exception was the direct cause of the"
|
||||
u" following exception"
|
||||
)
|
||||
elif exc_value.__context__ is not None:
|
||||
self.info = (
|
||||
u"During handling of the above exception, another"
|
||||
u" exception occurred"
|
||||
)
|
||||
|
||||
self.frames = []
|
||||
while tb is not None:
|
||||
self.frames.append(Frame(exc_type, exc_value, tb))
|
||||
tb = tb.tb_next
|
||||
|
||||
def filter_hidden_frames(self):
|
||||
new_frames = []
|
||||
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):
|
||||
"""String representation of the exception."""
|
||||
buf = traceback.format_exception_only(self.exc_type, self.exc_value)
|
||||
rv = "".join(buf).strip()
|
||||
return to_unicode(rv, "utf-8", "replace")
|
||||
|
||||
def render(self, mark_lib=True):
|
||||
out = []
|
||||
if self.info is not None:
|
||||
out.append(u'<li><div class="exc-divider">%s:</div>' % self.info)
|
||||
for frame in self.frames:
|
||||
out.append(
|
||||
u"<li%s>%s"
|
||||
% (
|
||||
u' title="%s"' % escape(frame.info) if frame.info else u"",
|
||||
frame.render(mark_lib=mark_lib),
|
||||
)
|
||||
)
|
||||
return u"\n".join(out)
|
||||
|
||||
def render_text(self):
|
||||
out = []
|
||||
if self.info is not None:
|
||||
out.append(u"\n%s:\n" % self.info)
|
||||
out.append(u"Traceback (most recent call last):")
|
||||
for frame in self.frames:
|
||||
out.append(frame.render_text())
|
||||
out.append(self.exception)
|
||||
return u"\n".join(out)
|
||||
|
||||
|
||||
class Frame(object):
|
||||
"""A single frame in a traceback."""
|
||||
|
||||
def __init__(self, exc_type, exc_value, tb):
|
||||
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_unicode(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_unicode(info, "utf-8", "replace")
|
||||
self.info = info
|
||||
|
||||
def render(self, mark_lib=True):
|
||||
"""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):
|
||||
return any(
|
||||
self.filename.startswith(path) for path in sysconfig.get_paths().values()
|
||||
)
|
||||
|
||||
def render_text(self):
|
||||
return u' File "%s", line %s, in %s\n %s' % (
|
||||
self.filename,
|
||||
self.lineno,
|
||||
self.function_name,
|
||||
self.current_line.strip(),
|
||||
)
|
||||
|
||||
def render_line_context(self):
|
||||
before, current, after = self.get_context_lines()
|
||||
rv = []
|
||||
|
||||
def render_line(line, cls):
|
||||
line = line.expandtabs().rstrip()
|
||||
stripped_line = line.strip()
|
||||
prefix = len(line) - len(stripped_line)
|
||||
rv.append(
|
||||
'<pre class="line %s"><span class="ws">%s</span>%s</pre>'
|
||||
% (cls, " " * prefix, escape(stripped_line) or " ")
|
||||
)
|
||||
|
||||
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):
|
||||
"""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([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, mode="single"):
|
||||
"""Evaluate code in the context of the frame."""
|
||||
if isinstance(code, string_types):
|
||||
if PY2 and isinstance(code, text_type): # noqa
|
||||
code = UTF8_COOKIE + code.encode("utf-8")
|
||||
code = compile(code, "<interactive>", mode)
|
||||
return eval(code, self.globals, self.locals)
|
||||
|
||||
@cached_property
|
||||
def sourcelines(self):
|
||||
"""The sourcecode of the file as list of unicode 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(
|
||||
to_native(self.filename, get_filesystem_encoding()), mode="rb"
|
||||
) as f:
|
||||
source = f.read()
|
||||
except IOError:
|
||||
return []
|
||||
|
||||
# already unicode? return right away
|
||||
if isinstance(source, text_type):
|
||||
return source.splitlines()
|
||||
|
||||
# yes. it should be ascii, but we don't want to reject too many
|
||||
# characters in the debugger if something breaks
|
||||
charset = "utf-8"
|
||||
if source.startswith(UTF8_COOKIE):
|
||||
source = source[3:]
|
||||
else:
|
||||
for idx, match in enumerate(_line_re.finditer(source)):
|
||||
match = _coding_re.search(match.group())
|
||||
if match is not None:
|
||||
charset = match.group(1)
|
||||
break
|
||||
if idx > 1:
|
||||
break
|
||||
|
||||
# on broken cookies we fall back to utf-8 too
|
||||
charset = to_native(charset)
|
||||
try:
|
||||
codecs.lookup(charset)
|
||||
except LookupError:
|
||||
charset = "utf-8"
|
||||
|
||||
return source.decode(charset, "replace").splitlines()
|
||||
|
||||
def get_context_lines(self, context=5):
|
||||
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):
|
||||
try:
|
||||
return self.sourcelines[self.lineno - 1]
|
||||
except IndexError:
|
||||
return u""
|
||||
|
||||
@cached_property
|
||||
def console(self):
|
||||
return Console(self.globals, self.locals)
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return id(self)
|
829
venv/lib/python3.7/site-packages/werkzeug/exceptions.py
Normal file
829
venv/lib/python3.7/site-packages/werkzeug/exceptions.py
Normal file
@@ -0,0 +1,829 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.exceptions
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements a number of Python exceptions you can raise from
|
||||
within your views to trigger a standard non-200 response.
|
||||
|
||||
|
||||
Usage Example
|
||||
-------------
|
||||
|
||||
::
|
||||
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.wsgi import responder
|
||||
from werkzeug.exceptions import HTTPException, NotFound
|
||||
|
||||
def view(request):
|
||||
raise NotFound()
|
||||
|
||||
@responder
|
||||
def application(environ, start_response):
|
||||
request = BaseRequest(environ)
|
||||
try:
|
||||
return view(request)
|
||||
except HTTPException as e:
|
||||
return e
|
||||
|
||||
|
||||
As you can see from this example those exceptions are callable WSGI
|
||||
applications. Because of Python 2.4 compatibility those do not extend
|
||||
from the response objects but only from the python exception class.
|
||||
|
||||
As a matter of fact they are not Werkzeug response objects. However you
|
||||
can get a response object by calling ``get_response()`` on a HTTP
|
||||
exception.
|
||||
|
||||
Keep in mind that you have to pass an environment to ``get_response()``
|
||||
because some errors fetch additional information from the WSGI
|
||||
environment.
|
||||
|
||||
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::
|
||||
|
||||
@responder
|
||||
def application(environ, start_response):
|
||||
request = BaseRequest(environ)
|
||||
try:
|
||||
return view(request)
|
||||
except NotFound, e:
|
||||
return not_found(request)
|
||||
except HTTPException, e:
|
||||
return e
|
||||
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from ._compat import implements_to_string
|
||||
from ._compat import integer_types
|
||||
from ._compat import iteritems
|
||||
from ._compat import text_type
|
||||
from ._internal import _get_environ
|
||||
from .utils import escape
|
||||
|
||||
|
||||
@implements_to_string
|
||||
class HTTPException(Exception):
|
||||
"""Baseclass for all HTTP exceptions. This exception can be called as WSGI
|
||||
application to render a default error page or you can catch the subclasses
|
||||
of it independently and render nicer error messages.
|
||||
"""
|
||||
|
||||
code = None
|
||||
description = None
|
||||
|
||||
def __init__(self, description=None, response=None):
|
||||
super(HTTPException, self).__init__()
|
||||
if description is not None:
|
||||
self.description = description
|
||||
self.response = response
|
||||
|
||||
@classmethod
|
||||
def wrap(cls, exception, name=None):
|
||||
"""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.
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
|
||||
class newcls(cls, exception):
|
||||
_description = cls.description
|
||||
show_exception = False
|
||||
|
||||
def __init__(self, arg=None, *args, **kwargs):
|
||||
super(cls, self).__init__(*args, **kwargs)
|
||||
|
||||
if arg is None:
|
||||
exception.__init__(self)
|
||||
else:
|
||||
exception.__init__(self, arg)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
if self.show_exception:
|
||||
return "{}\n{}: {}".format(
|
||||
self._description, exception.__name__, exception.__str__(self)
|
||||
)
|
||||
|
||||
return self._description
|
||||
|
||||
@description.setter
|
||||
def description(self, value):
|
||||
self._description = value
|
||||
|
||||
newcls.__module__ = sys._getframe(1).f_globals.get("__name__")
|
||||
name = name or cls.__name__ + exception.__name__
|
||||
newcls.__name__ = newcls.__qualname__ = name
|
||||
return newcls
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""The status name."""
|
||||
from .http import HTTP_STATUS_CODES
|
||||
|
||||
return HTTP_STATUS_CODES.get(self.code, "Unknown Error")
|
||||
|
||||
def get_description(self, environ=None):
|
||||
"""Get the description."""
|
||||
return u"<p>%s</p>" % escape(self.description).replace("\n", "<br>")
|
||||
|
||||
def get_body(self, environ=None):
|
||||
"""Get the HTML body."""
|
||||
return text_type(
|
||||
(
|
||||
u'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
|
||||
u"<title>%(code)s %(name)s</title>\n"
|
||||
u"<h1>%(name)s</h1>\n"
|
||||
u"%(description)s\n"
|
||||
)
|
||||
% {
|
||||
"code": self.code,
|
||||
"name": escape(self.name),
|
||||
"description": self.get_description(environ),
|
||||
}
|
||||
)
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
"""Get a list of headers."""
|
||||
return [("Content-Type", "text/html; charset=utf-8")]
|
||||
|
||||
def get_response(self, environ=None):
|
||||
"""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
|
||||
|
||||
if self.response is not None:
|
||||
return self.response
|
||||
if environ is not None:
|
||||
environ = _get_environ(environ)
|
||||
headers = self.get_headers(environ)
|
||||
return Response(self.get_body(environ), self.code, headers)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Call the exception as WSGI application.
|
||||
|
||||
:param environ: the WSGI environment.
|
||||
:param start_response: the response callable provided by the WSGI
|
||||
server.
|
||||
"""
|
||||
response = self.get_response(environ)
|
||||
return response(environ, start_response)
|
||||
|
||||
def __str__(self):
|
||||
code = self.code if self.code is not None else "???"
|
||||
return "%s %s: %s" % (code, self.name, self.description)
|
||||
|
||||
def __repr__(self):
|
||||
code = self.code if self.code is not None else "???"
|
||||
return "<%s '%s: %s'>" % (self.__class__.__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 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.
|
||||
|
||||
.. 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=None, response=None, www_authenticate=None):
|
||||
HTTPException.__init__(self, description, response)
|
||||
|
||||
if www_authenticate is not None:
|
||||
if not isinstance(www_authenticate, (tuple, list)):
|
||||
www_authenticate = (www_authenticate,)
|
||||
|
||||
self.www_authenticate = www_authenticate
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
headers = HTTPException.get_headers(self, environ)
|
||||
if self.www_authenticate:
|
||||
headers.append(
|
||||
("WWW-Authenticate", ", ".join([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=None, description=None):
|
||||
"""Takes an optional list of valid http methods
|
||||
starting with werkzeug 0.3 the list will be mandatory."""
|
||||
HTTPException.__init__(self, description)
|
||||
self.valid_methods = valid_methods
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
headers = HTTPException.get_headers(self, environ)
|
||||
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=None, units="bytes", description=None):
|
||||
"""Takes an optional `Content-Range` header value based on ``length``
|
||||
parameter.
|
||||
"""
|
||||
HTTPException.__init__(self, description)
|
||||
self.length = length
|
||||
self.units = units
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
headers = HTTPException.get_headers(self, environ)
|
||||
if self.length is not None:
|
||||
headers.append(("Content-Range", "%s */%d" % (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=None, response=None, retry_after=None):
|
||||
super(_RetryAfter, self).__init__(description, response)
|
||||
self.retry_after = retry_after
|
||||
|
||||
def get_headers(self, environ=None):
|
||||
headers = super(_RetryAfter, self).get_headers(environ)
|
||||
|
||||
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=None, response=None, original_exception=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(InternalServerError, self).__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 = {}
|
||||
__all__ = ["HTTPException"]
|
||||
|
||||
|
||||
def _find_exceptions():
|
||||
for _name, obj in iteritems(globals()):
|
||||
try:
|
||||
is_http_exception = issubclass(obj, HTTPException)
|
||||
except TypeError:
|
||||
is_http_exception = False
|
||||
if not is_http_exception or obj.code is None:
|
||||
continue
|
||||
__all__.append(obj.__name__)
|
||||
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(object):
|
||||
"""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=None, extra=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, *args, **kwargs):
|
||||
if not args and not kwargs and not isinstance(code, integer_types):
|
||||
raise HTTPException(response=code)
|
||||
if code not in self.mapping:
|
||||
raise LookupError("no exception for %r" % code)
|
||||
raise self.mapping[code](*args, **kwargs)
|
||||
|
||||
|
||||
def abort(status, *args, **kwargs):
|
||||
"""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'))
|
||||
|
||||
"""
|
||||
return _aborter(status, *args, **kwargs)
|
||||
|
||||
|
||||
_aborter = Aborter()
|
||||
|
||||
#: An exception that is used to signal both a :exc:`KeyError` and a
|
||||
#: :exc:`BadRequest`. Used by many of the datastructures.
|
||||
BadRequestKeyError = BadRequest.wrap(KeyError)
|
64
venv/lib/python3.7/site-packages/werkzeug/filesystem.py
Normal file
64
venv/lib/python3.7/site-packages/werkzeug/filesystem.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.filesystem
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Various utilities for the local filesystem.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import sys
|
||||
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):
|
||||
"""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():
|
||||
"""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 unicode 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"
|
||||
" UTF-8 as filesystem encoding instead of {0!r}".format(rv),
|
||||
BrokenFilesystemWarning,
|
||||
)
|
||||
_warned_about_filesystem_encoding = True
|
||||
return "utf-8"
|
||||
return rv
|
584
venv/lib/python3.7/site-packages/werkzeug/formparser.py
Normal file
584
venv/lib/python3.7/site-packages/werkzeug/formparser.py
Normal file
@@ -0,0 +1,584 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.formparser
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module implements the form parsing. It supports url-encoded forms
|
||||
as well as non-nested multipart uploads.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import re
|
||||
from functools import update_wrapper
|
||||
from itertools import chain
|
||||
from itertools import repeat
|
||||
from itertools import tee
|
||||
|
||||
from . import exceptions
|
||||
from ._compat import BytesIO
|
||||
from ._compat import text_type
|
||||
from ._compat import to_native
|
||||
from .datastructures import FileStorage
|
||||
from .datastructures import Headers
|
||||
from .datastructures import MultiDict
|
||||
from .http import parse_options_header
|
||||
from .urls import url_decode_stream
|
||||
from .wsgi import get_content_length
|
||||
from .wsgi import get_input_stream
|
||||
from .wsgi import make_line_iter
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
#: an iterator that yields empty strings
|
||||
_empty_string_iter = repeat("")
|
||||
|
||||
#: a regular expression for multipart boundaries
|
||||
_multipart_boundary_re = re.compile("^[ -~]{0,200}[!-~]$")
|
||||
|
||||
#: supported http encodings that are also available in python we support
|
||||
#: for multipart messages.
|
||||
_supported_multipart_encodings = frozenset(["base64", "quoted-printable"])
|
||||
|
||||
|
||||
def default_stream_factory(
|
||||
total_content_length, filename, content_type, content_length=None
|
||||
):
|
||||
"""The stream factory that is used per default."""
|
||||
max_size = 1024 * 500
|
||||
if SpooledTemporaryFile is not None:
|
||||
return SpooledTemporaryFile(max_size=max_size, mode="wb+")
|
||||
if total_content_length is None or total_content_length > max_size:
|
||||
return TemporaryFile("wb+")
|
||||
return BytesIO()
|
||||
|
||||
|
||||
def parse_form_data(
|
||||
environ,
|
||||
stream_factory=None,
|
||||
charset="utf-8",
|
||||
errors="replace",
|
||||
max_form_memory_size=None,
|
||||
max_content_length=None,
|
||||
cls=None,
|
||||
silent=True,
|
||||
):
|
||||
"""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 :ref:`dealing-with-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:`~BaseResponse._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):
|
||||
"""Helper decorator for methods that exhausts the stream on return."""
|
||||
|
||||
def wrapper(self, stream, *args, **kwargs):
|
||||
try:
|
||||
return f(self, stream, *args, **kwargs)
|
||||
finally:
|
||||
exhaust = getattr(stream, "exhaust", None)
|
||||
if exhaust is not None:
|
||||
exhaust()
|
||||
else:
|
||||
while 1:
|
||||
chunk = stream.read(1024 * 64)
|
||||
if not chunk:
|
||||
break
|
||||
|
||||
return update_wrapper(wrapper, f)
|
||||
|
||||
|
||||
class FormDataParser(object):
|
||||
"""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:`~BaseResponse._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=None,
|
||||
charset="utf-8",
|
||||
errors="replace",
|
||||
max_form_memory_size=None,
|
||||
max_content_length=None,
|
||||
cls=None,
|
||||
silent=True,
|
||||
):
|
||||
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, options):
|
||||
return self.parse_functions.get(mimetype)
|
||||
|
||||
def parse_from_environ(self, environ):
|
||||
"""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, mimetype, content_length, options=None):
|
||||
"""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
|
||||
):
|
||||
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, mimetype, content_length, options):
|
||||
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")
|
||||
if boundary is None:
|
||||
raise ValueError("Missing boundary")
|
||||
if isinstance(boundary, text_type):
|
||||
boundary = boundary.encode("ascii")
|
||||
form, files = parser.parse(stream, boundary, content_length)
|
||||
return stream, form, files
|
||||
|
||||
@exhaust_stream
|
||||
def _parse_urlencoded(self, stream, mimetype, content_length, options):
|
||||
if (
|
||||
self.max_form_memory_size is not None
|
||||
and content_length is not None
|
||||
and content_length > self.max_form_memory_size
|
||||
):
|
||||
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 = {
|
||||
"multipart/form-data": _parse_multipart,
|
||||
"application/x-www-form-urlencoded": _parse_urlencoded,
|
||||
"application/x-url-encoded": _parse_urlencoded,
|
||||
}
|
||||
|
||||
|
||||
def is_valid_multipart_boundary(boundary):
|
||||
"""Checks if the string given is a valid multipart boundary."""
|
||||
return _multipart_boundary_re.match(boundary) is not None
|
||||
|
||||
|
||||
def _line_parse(line):
|
||||
"""Removes line ending characters and returns a tuple (`stripped_line`,
|
||||
`is_terminated`).
|
||||
"""
|
||||
if line[-2:] in ["\r\n", b"\r\n"]:
|
||||
return line[:-2], True
|
||||
elif line[-1:] in ["\r", "\n", b"\r", b"\n"]:
|
||||
return line[:-1], True
|
||||
return line, False
|
||||
|
||||
|
||||
def parse_multipart_headers(iterable):
|
||||
"""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
|
||||
"""
|
||||
result = []
|
||||
for line in iterable:
|
||||
line = to_native(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, 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)
|
||||
|
||||
|
||||
_begin_form = "begin_form"
|
||||
_begin_file = "begin_file"
|
||||
_cont = "cont"
|
||||
_end = "end"
|
||||
|
||||
|
||||
class MultiPartParser(object):
|
||||
def __init__(
|
||||
self,
|
||||
stream_factory=None,
|
||||
charset="utf-8",
|
||||
errors="replace",
|
||||
max_form_memory_size=None,
|
||||
cls=None,
|
||||
buffer_size=64 * 1024,
|
||||
):
|
||||
self.charset = charset
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.stream_factory = (
|
||||
default_stream_factory if stream_factory is None else stream_factory
|
||||
)
|
||||
self.cls = MultiDict if cls is None else cls
|
||||
|
||||
# make sure the buffer size is divisible by four so that we can base64
|
||||
# decode chunk by chunk
|
||||
assert buffer_size % 4 == 0, "buffer size has to be divisible by 4"
|
||||
# also the buffer size has to be at least 1024 bytes long or long headers
|
||||
# will freak out the system
|
||||
assert buffer_size >= 1024, "buffer size has to be at least 1KB"
|
||||
|
||||
self.buffer_size = buffer_size
|
||||
|
||||
def _fix_ie_filename(self, filename):
|
||||
"""Internet Explorer 6 transmits the full file name if a file is
|
||||
uploaded. This function strips the full path if it thinks the
|
||||
filename is Windows-like absolute.
|
||||
"""
|
||||
if filename[1:3] == ":\\" or filename[:2] == "\\\\":
|
||||
return filename.split("\\")[-1]
|
||||
return filename
|
||||
|
||||
def _find_terminator(self, iterator):
|
||||
"""The terminator might have some additional newlines before it.
|
||||
There is at least one application that sends additional newlines
|
||||
before headers (the python setuptools package).
|
||||
"""
|
||||
for line in iterator:
|
||||
if not line:
|
||||
break
|
||||
line = line.strip()
|
||||
if line:
|
||||
return line
|
||||
return b""
|
||||
|
||||
def fail(self, message):
|
||||
raise ValueError(message)
|
||||
|
||||
def get_part_encoding(self, headers):
|
||||
transfer_encoding = headers.get("content-transfer-encoding")
|
||||
if (
|
||||
transfer_encoding is not None
|
||||
and transfer_encoding in _supported_multipart_encodings
|
||||
):
|
||||
return transfer_encoding
|
||||
|
||||
def get_part_charset(self, headers):
|
||||
# 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, filename, headers, total_content_length):
|
||||
if isinstance(filename, bytes):
|
||||
filename = filename.decode(self.charset, self.errors)
|
||||
filename = self._fix_ie_filename(filename)
|
||||
content_type = headers.get("content-type")
|
||||
try:
|
||||
content_length = int(headers["content-length"])
|
||||
except (KeyError, ValueError):
|
||||
content_length = 0
|
||||
container = self.stream_factory(
|
||||
total_content_length=total_content_length,
|
||||
filename=filename,
|
||||
content_type=content_type,
|
||||
content_length=content_length,
|
||||
)
|
||||
return filename, container
|
||||
|
||||
def in_memory_threshold_reached(self, bytes):
|
||||
raise exceptions.RequestEntityTooLarge()
|
||||
|
||||
def validate_boundary(self, boundary):
|
||||
if not boundary:
|
||||
self.fail("Missing boundary")
|
||||
if not is_valid_multipart_boundary(boundary):
|
||||
self.fail("Invalid boundary: %s" % boundary)
|
||||
if len(boundary) > self.buffer_size: # pragma: no cover
|
||||
# this should never happen because we check for a minimum size
|
||||
# of 1024 and boundaries may not be longer than 200. The only
|
||||
# situation when this happens is for non debug builds where
|
||||
# the assert is skipped.
|
||||
self.fail("Boundary longer than buffer size")
|
||||
|
||||
def parse_lines(self, file, boundary, content_length, cap_at_buffer=True):
|
||||
"""Generate parts of
|
||||
``('begin_form', (headers, name))``
|
||||
``('begin_file', (headers, name, filename))``
|
||||
``('cont', bytestring)``
|
||||
``('end', None)``
|
||||
|
||||
Always obeys the grammar
|
||||
parts = ( begin_form cont* end |
|
||||
begin_file cont* end )*
|
||||
"""
|
||||
next_part = b"--" + boundary
|
||||
last_part = next_part + b"--"
|
||||
|
||||
iterator = chain(
|
||||
make_line_iter(
|
||||
file,
|
||||
limit=content_length,
|
||||
buffer_size=self.buffer_size,
|
||||
cap_at_buffer=cap_at_buffer,
|
||||
),
|
||||
_empty_string_iter,
|
||||
)
|
||||
|
||||
terminator = self._find_terminator(iterator)
|
||||
|
||||
if terminator == last_part:
|
||||
return
|
||||
elif terminator != next_part:
|
||||
self.fail("Expected boundary at start of multipart data")
|
||||
|
||||
while terminator != last_part:
|
||||
headers = parse_multipart_headers(iterator)
|
||||
|
||||
disposition = headers.get("content-disposition")
|
||||
if disposition is None:
|
||||
self.fail("Missing Content-Disposition header")
|
||||
disposition, extra = parse_options_header(disposition)
|
||||
transfer_encoding = self.get_part_encoding(headers)
|
||||
name = extra.get("name")
|
||||
filename = extra.get("filename")
|
||||
|
||||
# if no content type is given we stream into memory. A list is
|
||||
# used as a temporary container.
|
||||
if filename is None:
|
||||
yield _begin_form, (headers, name)
|
||||
|
||||
# otherwise we parse the rest of the headers and ask the stream
|
||||
# factory for something we can write in.
|
||||
else:
|
||||
yield _begin_file, (headers, name, filename)
|
||||
|
||||
buf = b""
|
||||
for line in iterator:
|
||||
if not line:
|
||||
self.fail("unexpected end of stream")
|
||||
|
||||
if line[:2] == b"--":
|
||||
terminator = line.rstrip()
|
||||
if terminator in (next_part, last_part):
|
||||
break
|
||||
|
||||
if transfer_encoding is not None:
|
||||
if transfer_encoding == "base64":
|
||||
transfer_encoding = "base64_codec"
|
||||
try:
|
||||
line = codecs.decode(line, transfer_encoding)
|
||||
except Exception:
|
||||
self.fail("could not decode transfer encoded chunk")
|
||||
|
||||
# we have something in the buffer from the last iteration.
|
||||
# this is usually a newline delimiter.
|
||||
if buf:
|
||||
yield _cont, buf
|
||||
buf = b""
|
||||
|
||||
# If the line ends with windows CRLF we write everything except
|
||||
# the last two bytes. In all other cases however we write
|
||||
# everything except the last byte. If it was a newline, that's
|
||||
# fine, otherwise it does not matter because we will write it
|
||||
# the next iteration. this ensures we do not write the
|
||||
# final newline into the stream. That way we do not have to
|
||||
# truncate the stream. However we do have to make sure that
|
||||
# if something else than a newline is in there we write it
|
||||
# out.
|
||||
if line[-2:] == b"\r\n":
|
||||
buf = b"\r\n"
|
||||
cutoff = -2
|
||||
else:
|
||||
buf = line[-1:]
|
||||
cutoff = -1
|
||||
yield _cont, line[:cutoff]
|
||||
|
||||
else: # pragma: no cover
|
||||
raise ValueError("unexpected end of part")
|
||||
|
||||
# if we have a leftover in the buffer that is not a newline
|
||||
# character we have to flush it, otherwise we will chop of
|
||||
# certain values.
|
||||
if buf not in (b"", b"\r", b"\n", b"\r\n"):
|
||||
yield _cont, buf
|
||||
|
||||
yield _end, None
|
||||
|
||||
def parse_parts(self, file, boundary, content_length):
|
||||
"""Generate ``('file', (name, val))`` and
|
||||
``('form', (name, val))`` parts.
|
||||
"""
|
||||
in_memory = 0
|
||||
|
||||
for ellt, ell in self.parse_lines(file, boundary, content_length):
|
||||
if ellt == _begin_file:
|
||||
headers, name, filename = ell
|
||||
is_file = True
|
||||
guard_memory = False
|
||||
filename, container = self.start_file_streaming(
|
||||
filename, headers, content_length
|
||||
)
|
||||
_write = container.write
|
||||
|
||||
elif ellt == _begin_form:
|
||||
headers, name = ell
|
||||
is_file = False
|
||||
container = []
|
||||
_write = container.append
|
||||
guard_memory = self.max_form_memory_size is not None
|
||||
|
||||
elif ellt == _cont:
|
||||
_write(ell)
|
||||
# if we write into memory and there is a memory size limit we
|
||||
# count the number of bytes in memory and raise an exception if
|
||||
# there is too much data in memory.
|
||||
if guard_memory:
|
||||
in_memory += len(ell)
|
||||
if in_memory > self.max_form_memory_size:
|
||||
self.in_memory_threshold_reached(in_memory)
|
||||
|
||||
elif ellt == _end:
|
||||
if is_file:
|
||||
container.seek(0)
|
||||
yield (
|
||||
"file",
|
||||
(name, FileStorage(container, filename, name, headers=headers)),
|
||||
)
|
||||
else:
|
||||
part_charset = self.get_part_charset(headers)
|
||||
yield (
|
||||
"form",
|
||||
(name, b"".join(container).decode(part_charset, self.errors)),
|
||||
)
|
||||
|
||||
def parse(self, file, boundary, content_length):
|
||||
formstream, filestream = tee(
|
||||
self.parse_parts(file, boundary, content_length), 2
|
||||
)
|
||||
form = (p[1] for p in formstream if p[0] == "form")
|
||||
files = (p[1] for p in filestream if p[0] == "file")
|
||||
return self.cls(form), self.cls(files)
|
1305
venv/lib/python3.7/site-packages/werkzeug/http.py
Normal file
1305
venv/lib/python3.7/site-packages/werkzeug/http.py
Normal file
File diff suppressed because it is too large
Load Diff
420
venv/lib/python3.7/site-packages/werkzeug/local.py
Normal file
420
venv/lib/python3.7/site-packages/werkzeug/local.py
Normal file
@@ -0,0 +1,420 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.local
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements context-local objects.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import copy
|
||||
from functools import update_wrapper
|
||||
|
||||
from ._compat import implements_bool
|
||||
from ._compat import PY2
|
||||
from .wsgi import ClosingIterator
|
||||
|
||||
# since each thread has its own greenlet we can just use those as identifiers
|
||||
# for the context. If greenlets are not available we fall back to the
|
||||
# current thread ident depending on where it is.
|
||||
try:
|
||||
from greenlet import getcurrent as get_ident
|
||||
except ImportError:
|
||||
try:
|
||||
from thread import get_ident
|
||||
except ImportError:
|
||||
from _thread import get_ident
|
||||
|
||||
|
||||
def release_local(local):
|
||||
"""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(object):
|
||||
__slots__ = ("__storage__", "__ident_func__")
|
||||
|
||||
def __init__(self):
|
||||
object.__setattr__(self, "__storage__", {})
|
||||
object.__setattr__(self, "__ident_func__", get_ident)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__storage__.items())
|
||||
|
||||
def __call__(self, proxy):
|
||||
"""Create a proxy for a name."""
|
||||
return LocalProxy(self, proxy)
|
||||
|
||||
def __release_local__(self):
|
||||
self.__storage__.pop(self.__ident_func__(), None)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
return self.__storage__[self.__ident_func__()][name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
ident = self.__ident_func__()
|
||||
storage = self.__storage__
|
||||
try:
|
||||
storage[ident][name] = value
|
||||
except KeyError:
|
||||
storage[ident] = {name: value}
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__storage__[self.__ident_func__()][name]
|
||||
except KeyError:
|
||||
raise AttributeError(name)
|
||||
|
||||
|
||||
class LocalStack(object):
|
||||
"""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):
|
||||
self._local = Local()
|
||||
|
||||
def __release_local__(self):
|
||||
self._local.__release_local__()
|
||||
|
||||
@property
|
||||
def __ident_func__(self):
|
||||
return self._local.__ident_func__
|
||||
|
||||
@__ident_func__.setter
|
||||
def __ident_func__(self, value):
|
||||
object.__setattr__(self._local, "__ident_func__", value)
|
||||
|
||||
def __call__(self):
|
||||
def _lookup():
|
||||
rv = self.top
|
||||
if rv is None:
|
||||
raise RuntimeError("object unbound")
|
||||
return rv
|
||||
|
||||
return LocalProxy(_lookup)
|
||||
|
||||
def push(self, obj):
|
||||
"""Pushes a new item to the stack"""
|
||||
rv = getattr(self._local, "stack", None)
|
||||
if rv is None:
|
||||
self._local.stack = rv = []
|
||||
rv.append(obj)
|
||||
return rv
|
||||
|
||||
def pop(self):
|
||||
"""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):
|
||||
"""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(object):
|
||||
"""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.
|
||||
|
||||
The `ident_func` parameter can be added to override the default ident
|
||||
function for the wrapped locals.
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
Instead of a manager the :func:`release_local` function can be used
|
||||
as well.
|
||||
|
||||
.. versionchanged:: 0.7
|
||||
`ident_func` was added.
|
||||
"""
|
||||
|
||||
def __init__(self, locals=None, ident_func=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:
|
||||
self.ident_func = ident_func
|
||||
for local in self.locals:
|
||||
object.__setattr__(local, "__ident_func__", ident_func)
|
||||
else:
|
||||
self.ident_func = get_ident
|
||||
|
||||
def get_ident(self):
|
||||
"""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.
|
||||
|
||||
.. 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.
|
||||
"""
|
||||
return self.ident_func()
|
||||
|
||||
def cleanup(self):
|
||||
"""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):
|
||||
"""Wrap a WSGI application so that cleaning up happens after
|
||||
request end.
|
||||
"""
|
||||
|
||||
def application(environ, start_response):
|
||||
return ClosingIterator(app(environ, start_response), self.cleanup)
|
||||
|
||||
return application
|
||||
|
||||
def middleware(self, func):
|
||||
"""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):
|
||||
return "<%s storages: %d>" % (self.__class__.__name__, len(self.locals))
|
||||
|
||||
|
||||
@implements_bool
|
||||
class LocalProxy(object):
|
||||
"""Acts as a proxy for a werkzeug local. Forwards all operations to
|
||||
a proxied object. The only operations not supported for forwarding
|
||||
are right handed operands and any kind of assignment.
|
||||
|
||||
Example usage::
|
||||
|
||||
from werkzeug.local import Local
|
||||
l = Local()
|
||||
|
||||
# these are proxies
|
||||
request = l('request')
|
||||
user = l('user')
|
||||
|
||||
|
||||
from werkzeug.local import LocalStack
|
||||
_response_local = LocalStack()
|
||||
|
||||
# this is a proxy
|
||||
response = _response_local()
|
||||
|
||||
Whenever something is bound to l.user / l.request the proxy objects
|
||||
will forward all operations. If no object is bound a :exc:`RuntimeError`
|
||||
will be raised.
|
||||
|
||||
To create proxies to :class:`Local` or :class:`LocalStack` objects,
|
||||
call the object as shown above. If you want to have a proxy to an
|
||||
object looked up by a function, you can (as of Werkzeug 0.6.1) pass
|
||||
a function to the :class:`LocalProxy` constructor::
|
||||
|
||||
session = LocalProxy(lambda: get_current_request().session)
|
||||
|
||||
.. versionchanged:: 0.6.1
|
||||
The class can be instantiated with a callable as well now.
|
||||
"""
|
||||
|
||||
__slots__ = ("__local", "__dict__", "__name__", "__wrapped__")
|
||||
|
||||
def __init__(self, local, name=None):
|
||||
object.__setattr__(self, "_LocalProxy__local", local)
|
||||
object.__setattr__(self, "__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):
|
||||
"""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__"):
|
||||
return self.__local()
|
||||
try:
|
||||
return getattr(self.__local, self.__name__)
|
||||
except AttributeError:
|
||||
raise RuntimeError("no object bound to %s" % self.__name__)
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
try:
|
||||
return self._get_current_object().__dict__
|
||||
except RuntimeError:
|
||||
raise AttributeError("__dict__")
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
obj = self._get_current_object()
|
||||
except RuntimeError:
|
||||
return "<%s unbound>" % self.__class__.__name__
|
||||
return repr(obj)
|
||||
|
||||
def __bool__(self):
|
||||
try:
|
||||
return bool(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return False
|
||||
|
||||
def __unicode__(self):
|
||||
try:
|
||||
return unicode(self._get_current_object()) # noqa
|
||||
except RuntimeError:
|
||||
return repr(self)
|
||||
|
||||
def __dir__(self):
|
||||
try:
|
||||
return dir(self._get_current_object())
|
||||
except RuntimeError:
|
||||
return []
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "__members__":
|
||||
return dir(self._get_current_object())
|
||||
return getattr(self._get_current_object(), name)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self._get_current_object()[key] = value
|
||||
|
||||
def __delitem__(self, key):
|
||||
del self._get_current_object()[key]
|
||||
|
||||
if PY2:
|
||||
__getslice__ = lambda x, i, j: x._get_current_object()[i:j]
|
||||
|
||||
def __setslice__(self, i, j, seq):
|
||||
self._get_current_object()[i:j] = seq
|
||||
|
||||
def __delslice__(self, i, j):
|
||||
del self._get_current_object()[i:j]
|
||||
|
||||
__setattr__ = lambda x, n, v: setattr(x._get_current_object(), n, v)
|
||||
__delattr__ = lambda x, n: delattr(x._get_current_object(), n)
|
||||
__str__ = lambda x: str(x._get_current_object())
|
||||
__lt__ = lambda x, o: x._get_current_object() < o
|
||||
__le__ = lambda x, o: x._get_current_object() <= o
|
||||
__eq__ = lambda x, o: x._get_current_object() == o
|
||||
__ne__ = lambda x, o: x._get_current_object() != o
|
||||
__gt__ = lambda x, o: x._get_current_object() > o
|
||||
__ge__ = lambda x, o: x._get_current_object() >= o
|
||||
__cmp__ = lambda x, o: cmp(x._get_current_object(), o) # noqa
|
||||
__hash__ = lambda x: hash(x._get_current_object())
|
||||
__call__ = lambda x, *a, **kw: x._get_current_object()(*a, **kw)
|
||||
__len__ = lambda x: len(x._get_current_object())
|
||||
__getitem__ = lambda x, i: x._get_current_object()[i]
|
||||
__iter__ = lambda x: iter(x._get_current_object())
|
||||
__contains__ = lambda x, i: i in x._get_current_object()
|
||||
__add__ = lambda x, o: x._get_current_object() + o
|
||||
__sub__ = lambda x, o: x._get_current_object() - o
|
||||
__mul__ = lambda x, o: x._get_current_object() * o
|
||||
__floordiv__ = lambda x, o: x._get_current_object() // o
|
||||
__mod__ = lambda x, o: x._get_current_object() % o
|
||||
__divmod__ = lambda x, o: x._get_current_object().__divmod__(o)
|
||||
__pow__ = lambda x, o: x._get_current_object() ** o
|
||||
__lshift__ = lambda x, o: x._get_current_object() << o
|
||||
__rshift__ = lambda x, o: x._get_current_object() >> o
|
||||
__and__ = lambda x, o: x._get_current_object() & o
|
||||
__xor__ = lambda x, o: x._get_current_object() ^ o
|
||||
__or__ = lambda x, o: x._get_current_object() | o
|
||||
__div__ = lambda x, o: x._get_current_object().__div__(o)
|
||||
__truediv__ = lambda x, o: x._get_current_object().__truediv__(o)
|
||||
__neg__ = lambda x: -(x._get_current_object())
|
||||
__pos__ = lambda x: +(x._get_current_object())
|
||||
__abs__ = lambda x: abs(x._get_current_object())
|
||||
__invert__ = lambda x: ~(x._get_current_object())
|
||||
__complex__ = lambda x: complex(x._get_current_object())
|
||||
__int__ = lambda x: int(x._get_current_object())
|
||||
__long__ = lambda x: long(x._get_current_object()) # noqa
|
||||
__float__ = lambda x: float(x._get_current_object())
|
||||
__oct__ = lambda x: oct(x._get_current_object())
|
||||
__hex__ = lambda x: hex(x._get_current_object())
|
||||
__index__ = lambda x: x._get_current_object().__index__()
|
||||
__coerce__ = lambda x, o: x._get_current_object().__coerce__(x, o)
|
||||
__enter__ = lambda x: x._get_current_object().__enter__()
|
||||
__exit__ = lambda x, *a, **kw: x._get_current_object().__exit__(*a, **kw)
|
||||
__radd__ = lambda x, o: o + x._get_current_object()
|
||||
__rsub__ = lambda x, o: o - x._get_current_object()
|
||||
__rmul__ = lambda x, o: o * x._get_current_object()
|
||||
__rdiv__ = lambda x, o: o / x._get_current_object()
|
||||
if PY2:
|
||||
__rtruediv__ = lambda x, o: x._get_current_object().__rtruediv__(o)
|
||||
else:
|
||||
__rtruediv__ = __rdiv__
|
||||
__rfloordiv__ = lambda x, o: o // x._get_current_object()
|
||||
__rmod__ = lambda x, o: o % x._get_current_object()
|
||||
__rdivmod__ = lambda x, o: x._get_current_object().__rdivmod__(o)
|
||||
__copy__ = lambda x: copy.copy(x._get_current_object())
|
||||
__deepcopy__ = lambda x, memo: copy.deepcopy(x._get_current_object(), memo)
|
@@ -0,0 +1,25 @@
|
||||
"""
|
||||
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>`.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
@@ -0,0 +1,66 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
|
||||
class DispatcherMiddleware(object):
|
||||
"""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, mounts=None):
|
||||
self.app = app
|
||||
self.mounts = mounts or {}
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
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 = "/%s%s" % (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,219 @@
|
||||
"""
|
||||
Basic HTTP Proxy
|
||||
================
|
||||
|
||||
.. autoclass:: ProxyMiddleware
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import socket
|
||||
|
||||
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
|
||||
|
||||
try:
|
||||
from http import client
|
||||
except ImportError:
|
||||
import httplib as client
|
||||
|
||||
|
||||
class ProxyMiddleware(object):
|
||||
"""Proxy requests under a path to an external server, routing other
|
||||
requests to the app.
|
||||
|
||||
This middleware can only proxy HTTP requests, as that 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 proxying server
|
||||
should be used.
|
||||
|
||||
The middleware takes a dict that maps 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, targets, chunk_size=2 << 13, timeout=10):
|
||||
def _set_defaults(opts):
|
||||
opts.setdefault("remove_prefix", False)
|
||||
opts.setdefault("host", "<auto>")
|
||||
opts.setdefault("headers", {})
|
||||
opts.setdefault("ssl_context", None)
|
||||
return opts
|
||||
|
||||
self.app = app
|
||||
self.targets = dict(
|
||||
("/%s/" % k.strip("/"), _set_defaults(v)) for k, v in targets.items()
|
||||
)
|
||||
self.chunk_size = chunk_size
|
||||
self.timeout = timeout
|
||||
|
||||
def proxy_to(self, opts, path, prefix):
|
||||
target = url_parse(opts["target"])
|
||||
|
||||
def application(environ, start_response):
|
||||
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", target.ascii_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 = "%s/%s" % (
|
||||
target.path.rstrip("/"),
|
||||
remote_path[len(prefix) :].lstrip("/"),
|
||||
)
|
||||
|
||||
content_length = environ.get("CONTENT_LENGTH")
|
||||
chunked = False
|
||||
|
||||
if content_length not in ("", None):
|
||||
headers.append(("Content-Length", content_length))
|
||||
elif content_length is not None:
|
||||
headers.append(("Transfer-Encoding", "chunked"))
|
||||
chunked = True
|
||||
|
||||
try:
|
||||
if target.scheme == "http":
|
||||
con = client.HTTPConnection(
|
||||
target.ascii_host, target.port or 80, timeout=self.timeout
|
||||
)
|
||||
elif target.scheme == "https":
|
||||
con = client.HTTPSConnection(
|
||||
target.ascii_host,
|
||||
target.port or 443,
|
||||
timeout=self.timeout,
|
||||
context=opts["ssl_context"],
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Target scheme must be 'http' or 'https', got '{}'.".format(
|
||||
target.scheme
|
||||
)
|
||||
)
|
||||
|
||||
con.connect()
|
||||
remote_url = url_quote(remote_path)
|
||||
querystring = environ["QUERY_STRING"]
|
||||
|
||||
if querystring:
|
||||
remote_url = 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 1:
|
||||
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 socket.error:
|
||||
from ..exceptions import BadGateway
|
||||
|
||||
return BadGateway()(environ, start_response)
|
||||
|
||||
start_response(
|
||||
"%d %s" % (resp.status, resp.reason),
|
||||
[
|
||||
(k.title(), v)
|
||||
for k, v in resp.getheaders()
|
||||
if not is_hop_by_hop_header(k)
|
||||
],
|
||||
)
|
||||
|
||||
def read():
|
||||
while 1:
|
||||
try:
|
||||
data = resp.read(self.chunk_size)
|
||||
except socket.error:
|
||||
break
|
||||
|
||||
if not data:
|
||||
break
|
||||
|
||||
yield data
|
||||
|
||||
return read()
|
||||
|
||||
return application
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
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)
|
408
venv/lib/python3.7/site-packages/werkzeug/middleware/lint.py
Normal file
408
venv/lib/python3.7/site-packages/werkzeug/middleware/lint.py
Normal file
@@ -0,0 +1,408 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
from warnings import warn
|
||||
|
||||
from .._compat import implements_iterator
|
||||
from .._compat import PY2
|
||||
from .._compat import string_types
|
||||
from ..datastructures import Headers
|
||||
from ..http import is_entity_header
|
||||
from ..wsgi import FileWrapper
|
||||
|
||||
try:
|
||||
from urllib.parse import urlparse
|
||||
except ImportError:
|
||||
from urlparse import urlparse
|
||||
|
||||
|
||||
class WSGIWarning(Warning):
|
||||
"""Warning class for WSGI warnings."""
|
||||
|
||||
|
||||
class HTTPWarning(Warning):
|
||||
"""Warning class for HTTP warnings."""
|
||||
|
||||
|
||||
def check_string(context, obj, stacklevel=3):
|
||||
if type(obj) is not str:
|
||||
warn(
|
||||
"'%s' requires strings, got '%s'" % (context, type(obj).__name__),
|
||||
WSGIWarning,
|
||||
)
|
||||
|
||||
|
||||
class InputStream(object):
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def read(self, *args):
|
||||
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):
|
||||
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):
|
||||
try:
|
||||
return iter(self._stream)
|
||||
except TypeError:
|
||||
warn("'wsgi.input' is not iterable.", WSGIWarning, stacklevel=2)
|
||||
return iter(())
|
||||
|
||||
def close(self):
|
||||
warn("The application closed the input stream!", WSGIWarning, stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class ErrorStream(object):
|
||||
def __init__(self, stream):
|
||||
self._stream = stream
|
||||
|
||||
def write(self, s):
|
||||
check_string("wsgi.error.write()", s)
|
||||
self._stream.write(s)
|
||||
|
||||
def flush(self):
|
||||
self._stream.flush()
|
||||
|
||||
def writelines(self, seq):
|
||||
for line in seq:
|
||||
self.write(line)
|
||||
|
||||
def close(self):
|
||||
warn("The application closed the error stream!", WSGIWarning, stacklevel=2)
|
||||
self._stream.close()
|
||||
|
||||
|
||||
class GuardedWrite(object):
|
||||
def __init__(self, write, chunks):
|
||||
self._write = write
|
||||
self._chunks = chunks
|
||||
|
||||
def __call__(self, s):
|
||||
check_string("write()", s)
|
||||
self._write.write(s)
|
||||
self._chunks.append(len(s))
|
||||
|
||||
|
||||
@implements_iterator
|
||||
class GuardedIterator(object):
|
||||
def __init__(self, iterator, headers_set, chunks):
|
||||
self._iterator = iterator
|
||||
if PY2:
|
||||
self._next = iter(iterator).next
|
||||
else:
|
||||
self._next = iter(iterator).__next__
|
||||
self.closed = False
|
||||
self.headers_set = headers_set
|
||||
self.chunks = chunks
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
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_string("application iterator items", rv)
|
||||
self.chunks.append(len(rv))
|
||||
return rv
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
if hasattr(self._iterator, "close"):
|
||||
self._iterator.close()
|
||||
|
||||
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(
|
||||
"Entity header %r found in 304 response." % key, 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(
|
||||
"%r responses must have an empty content length." % status_code,
|
||||
HTTPWarning,
|
||||
)
|
||||
if bytes_sent:
|
||||
warn(
|
||||
"%r responses must not have a body." % status_code, 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):
|
||||
if not self.closed:
|
||||
try:
|
||||
warn(
|
||||
"Iterator was garbage collected before it was closed.", WSGIWarning
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
class LintMiddleware(object):
|
||||
"""Warns about common errors in the WSGI and HTTP behavior of the
|
||||
server and wrapped application. Some of the issues it check are:
|
||||
|
||||
- invalid status codes
|
||||
- non-bytestrings 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):
|
||||
self.app = app
|
||||
|
||||
def check_environ(self, environ):
|
||||
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(
|
||||
"Required environment key %r not found" % key,
|
||||
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(
|
||||
"'SCRIPT_NAME' does not start with a slash: %r" % script_name,
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
if path_info and path_info[0] != "/":
|
||||
warn(
|
||||
"'PATH_INFO' does not start with a slash: %r" % path_info,
|
||||
WSGIWarning,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
def check_start_response(self, status, headers, exc_info):
|
||||
check_string("status", status)
|
||||
status_code = status.split(None, 1)[0]
|
||||
|
||||
if len(status_code) != 3 or not status_code.isdigit():
|
||||
warn(WSGIWarning("Status code must be three digits"), stacklevel=3)
|
||||
|
||||
if len(status) < 4 or status[3] != " ":
|
||||
warn(
|
||||
WSGIWarning(
|
||||
"Invalid value for status %r. Valid "
|
||||
"status strings are three digits, a space "
|
||||
"and a status explanation"
|
||||
),
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
status_code = int(status_code)
|
||||
|
||||
if status_code < 100:
|
||||
warn(WSGIWarning("status code < 100 detected"), stacklevel=3)
|
||||
|
||||
if type(headers) is not list:
|
||||
warn(WSGIWarning("header list is not a list"), stacklevel=3)
|
||||
|
||||
for item in headers:
|
||||
if type(item) is not tuple or len(item) != 2:
|
||||
warn(WSGIWarning("Headers must tuple 2-item tuples"), stacklevel=3)
|
||||
name, value = item
|
||||
if type(name) is not str or type(value) is not str:
|
||||
warn(WSGIWarning("header items must be strings"), stacklevel=3)
|
||||
if name.lower() == "status":
|
||||
warn(
|
||||
WSGIWarning(
|
||||
"The status header is not supported due to "
|
||||
"conflicts with the CGI spec."
|
||||
),
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
if exc_info is not None and not isinstance(exc_info, tuple):
|
||||
warn(WSGIWarning("invalid value for exc_info"), stacklevel=3)
|
||||
|
||||
headers = Headers(headers)
|
||||
self.check_headers(headers)
|
||||
|
||||
return status_code, headers
|
||||
|
||||
def check_headers(self, headers):
|
||||
etag = headers.get("etag")
|
||||
|
||||
if etag is not None:
|
||||
if etag.startswith(("W/", "w/")):
|
||||
if etag.startswith("w/"):
|
||||
warn(
|
||||
HTTPWarning("weak etag indicator should be upcase."),
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
etag = etag[2:]
|
||||
|
||||
if not (etag[:1] == etag[-1:] == '"'):
|
||||
warn(HTTPWarning("unquoted etag emitted."), stacklevel=4)
|
||||
|
||||
location = headers.get("location")
|
||||
|
||||
if location is not None:
|
||||
if not urlparse(location).netloc:
|
||||
warn(
|
||||
HTTPWarning("absolute URLs required for location header"),
|
||||
stacklevel=4,
|
||||
)
|
||||
|
||||
def check_iterator(self, app_iter):
|
||||
if isinstance(app_iter, string_types):
|
||||
warn(
|
||||
"The application returned astring. 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, **kwargs):
|
||||
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, start_response = args
|
||||
|
||||
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 = []
|
||||
chunks = []
|
||||
|
||||
def checking_start_response(*args, **kwargs):
|
||||
if len(args) not in (2, 3):
|
||||
warn(
|
||||
"Invalid number of arguments: %s, expected 2 or 3." % len(args),
|
||||
WSGIWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if kwargs:
|
||||
warn("'start_response' does not take keyword arguments.", WSGIWarning)
|
||||
|
||||
status, headers = args[:2]
|
||||
|
||||
if len(args) == 3:
|
||||
exc_info = args[2]
|
||||
else:
|
||||
exc_info = 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, checking_start_response)
|
||||
self.check_iterator(app_iter)
|
||||
return GuardedIterator(app_iter, headers_set, chunks)
|
132
venv/lib/python3.7/site-packages/werkzeug/middleware/profiler.py
Normal file
132
venv/lib/python3.7/site-packages/werkzeug/middleware/profiler.py
Normal file
@@ -0,0 +1,132 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
from __future__ import print_function
|
||||
|
||||
import os.path
|
||||
import sys
|
||||
import time
|
||||
from pstats import Stats
|
||||
|
||||
try:
|
||||
from cProfile import Profile
|
||||
except ImportError:
|
||||
from profile import Profile
|
||||
|
||||
|
||||
class ProfilerMiddleware(object):
|
||||
"""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,
|
||||
stream=sys.stdout,
|
||||
sort_by=("time", "calls"),
|
||||
restrictions=(),
|
||||
profile_dir=None,
|
||||
filename_format="{method}.{path}.{elapsed:.0f}ms.{time:.0f}.prof",
|
||||
):
|
||||
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, start_response):
|
||||
response_body = []
|
||||
|
||||
def catching_start_response(status, headers, exc_info=None):
|
||||
start_response(status, headers, exc_info)
|
||||
return response_body.append
|
||||
|
||||
def runapp():
|
||||
app_iter = self._app(environ, catching_start_response)
|
||||
response_body.extend(app_iter)
|
||||
|
||||
if hasattr(app_iter, "close"):
|
||||
app_iter.close()
|
||||
|
||||
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.get("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)
|
||||
print("PATH: {!r}".format(environ.get("PATH_INFO", "")), file=self._stream)
|
||||
stats.print_stats(*self._restrictions)
|
||||
print("-" * 80 + "\n", file=self._stream)
|
||||
|
||||
return [body]
|
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
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 applied 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
|
||||
"""
|
||||
from werkzeug.http import parse_list_header
|
||||
|
||||
|
||||
class ProxyFix(object):
|
||||
"""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, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=0):
|
||||
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, value):
|
||||
"""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
|
||||
values = parse_list_header(value)
|
||||
if len(values) >= trusted:
|
||||
return values[-trusted]
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""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"] = x_host
|
||||
parts = x_host.split(":", 1)
|
||||
environ["SERVER_NAME"] = parts[0]
|
||||
if len(parts) == 2:
|
||||
environ["SERVER_PORT"] = parts[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:
|
||||
parts = host.split(":", 1)
|
||||
host = parts[0] if len(parts) == 2 else host
|
||||
environ["HTTP_HOST"] = "%s:%s" % (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,293 @@
|
||||
"""
|
||||
Serve Shared Static Files
|
||||
=========================
|
||||
|
||||
.. autoclass:: SharedDataMiddleware
|
||||
:members: is_allowed
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import mimetypes
|
||||
import os
|
||||
import pkgutil
|
||||
import posixpath
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
from time import mktime
|
||||
from time import time
|
||||
from zlib import adler32
|
||||
|
||||
from .._compat import PY2
|
||||
from .._compat import string_types
|
||||
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
|
||||
|
||||
|
||||
class SharedDataMiddleware(object):
|
||||
|
||||
"""A WSGI middleware that provides static content for development
|
||||
environments or simple server setups. Usage is quite simple::
|
||||
|
||||
import os
|
||||
from werkzeug.wsgi import SharedDataMiddleware
|
||||
|
||||
app = SharedDataMiddleware(app, {
|
||||
'/static': os.path.join(os.path.dirname(__file__), 'static')
|
||||
})
|
||||
|
||||
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. One can also mount
|
||||
files 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 be 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,
|
||||
exports,
|
||||
disallow=None,
|
||||
cache=True,
|
||||
cache_timeout=60 * 60 * 12,
|
||||
fallback_mimetype="application/octet-stream",
|
||||
):
|
||||
self.app = app
|
||||
self.exports = []
|
||||
self.cache = cache
|
||||
self.cache_timeout = cache_timeout
|
||||
|
||||
if hasattr(exports, "items"):
|
||||
exports = exports.items()
|
||||
|
||||
for key, value in exports:
|
||||
if isinstance(value, tuple):
|
||||
loader = self.get_package_loader(*value)
|
||||
elif isinstance(value, string_types):
|
||||
if os.path.isfile(value):
|
||||
loader = self.get_file_loader(value)
|
||||
else:
|
||||
loader = self.get_directory_loader(value)
|
||||
else:
|
||||
raise TypeError("unknown def %r" % value)
|
||||
|
||||
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):
|
||||
"""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):
|
||||
return lambda: (
|
||||
open(filename, "rb"),
|
||||
datetime.utcfromtimestamp(os.path.getmtime(filename)),
|
||||
int(os.path.getsize(filename)),
|
||||
)
|
||||
|
||||
def get_file_loader(self, filename):
|
||||
return lambda x: (os.path.basename(filename), self._opener(filename))
|
||||
|
||||
def get_package_loader(self, package, package_path):
|
||||
loadtime = datetime.utcnow()
|
||||
provider = pkgutil.get_loader(package)
|
||||
|
||||
if hasattr(provider, "get_resource_reader"):
|
||||
# Python 3
|
||||
reader = provider.get_resource_reader(package)
|
||||
|
||||
def loader(path):
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
path = safe_join(package_path, path)
|
||||
basename = posixpath.basename(path)
|
||||
|
||||
try:
|
||||
resource = reader.open_resource(path)
|
||||
except IOError:
|
||||
return None, None
|
||||
|
||||
if isinstance(resource, BytesIO):
|
||||
return (
|
||||
basename,
|
||||
lambda: (resource, loadtime, len(resource.getvalue())),
|
||||
)
|
||||
|
||||
return (
|
||||
basename,
|
||||
lambda: (
|
||||
resource,
|
||||
datetime.utcfromtimestamp(os.path.getmtime(resource.name)),
|
||||
os.path.getsize(resource.name),
|
||||
),
|
||||
)
|
||||
|
||||
else:
|
||||
# Python 2
|
||||
package_filename = provider.get_filename(package)
|
||||
is_filesystem = os.path.exists(package_filename)
|
||||
root = os.path.join(os.path.dirname(package_filename), package_path)
|
||||
|
||||
def loader(path):
|
||||
if path is None:
|
||||
return None, None
|
||||
|
||||
path = safe_join(root, path)
|
||||
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)
|
||||
except IOError:
|
||||
return None, None
|
||||
|
||||
return basename, lambda: (BytesIO(data), loadtime, len(data))
|
||||
|
||||
return loader
|
||||
|
||||
def get_directory_loader(self, directory):
|
||||
def loader(path):
|
||||
if path is not None:
|
||||
path = safe_join(directory, path)
|
||||
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, file_size, real_filename):
|
||||
if not isinstance(real_filename, bytes):
|
||||
real_filename = real_filename.encode(get_filesystem_encoding())
|
||||
|
||||
return "wzsdm-%d-%s-%s" % (
|
||||
mktime(mtime.timetuple()),
|
||||
file_size,
|
||||
adler32(real_filename) & 0xFFFFFFFF,
|
||||
)
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
path = get_path_info(environ)
|
||||
|
||||
if PY2:
|
||||
path = path.encode(get_filesystem_encoding())
|
||||
|
||||
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):
|
||||
return self.app(environ, start_response)
|
||||
|
||||
guessed_type = mimetypes.guess_type(real_filename)
|
||||
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)
|
||||
headers += [
|
||||
("Etag", '"%s"' % etag),
|
||||
("Cache-Control", "max-age=%d, public" % timeout),
|
||||
]
|
||||
|
||||
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)
|
117
venv/lib/python3.7/site-packages/werkzeug/posixemulation.py
Normal file
117
venv/lib/python3.7/site-packages/werkzeug/posixemulation.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""
|
||||
werkzeug.posixemulation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Provides a POSIX emulation for some features that are relevant to
|
||||
web applications. The main purpose is to simplify support for
|
||||
systems such as Windows NT that are not 100% POSIX compatible.
|
||||
|
||||
Currently this only implements a :func:`rename` function that
|
||||
follows POSIX semantics. Eg: if the target file already exists it
|
||||
will be replaced without asking.
|
||||
|
||||
This module was introduced in 0.6.1 and is not a public interface.
|
||||
It might become one in later versions of Werkzeug.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import errno
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
|
||||
from ._compat import to_unicode
|
||||
from .filesystem import get_filesystem_encoding
|
||||
|
||||
can_rename_open_file = False
|
||||
|
||||
if os.name == "nt":
|
||||
try:
|
||||
import ctypes
|
||||
|
||||
_MOVEFILE_REPLACE_EXISTING = 0x1
|
||||
_MOVEFILE_WRITE_THROUGH = 0x8
|
||||
_MoveFileEx = ctypes.windll.kernel32.MoveFileExW
|
||||
|
||||
def _rename(src, dst):
|
||||
src = to_unicode(src, get_filesystem_encoding())
|
||||
dst = to_unicode(dst, get_filesystem_encoding())
|
||||
if _rename_atomic(src, dst):
|
||||
return True
|
||||
retry = 0
|
||||
rv = False
|
||||
while not rv and retry < 100:
|
||||
rv = _MoveFileEx(
|
||||
src, dst, _MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH
|
||||
)
|
||||
if not rv:
|
||||
time.sleep(0.001)
|
||||
retry += 1
|
||||
return rv
|
||||
|
||||
# new in Vista and Windows Server 2008
|
||||
_CreateTransaction = ctypes.windll.ktmw32.CreateTransaction
|
||||
_CommitTransaction = ctypes.windll.ktmw32.CommitTransaction
|
||||
_MoveFileTransacted = ctypes.windll.kernel32.MoveFileTransactedW
|
||||
_CloseHandle = ctypes.windll.kernel32.CloseHandle
|
||||
can_rename_open_file = True
|
||||
|
||||
def _rename_atomic(src, dst):
|
||||
ta = _CreateTransaction(None, 0, 0, 0, 0, 1000, "Werkzeug rename")
|
||||
if ta == -1:
|
||||
return False
|
||||
try:
|
||||
retry = 0
|
||||
rv = False
|
||||
while not rv and retry < 100:
|
||||
rv = _MoveFileTransacted(
|
||||
src,
|
||||
dst,
|
||||
None,
|
||||
None,
|
||||
_MOVEFILE_REPLACE_EXISTING | _MOVEFILE_WRITE_THROUGH,
|
||||
ta,
|
||||
)
|
||||
if rv:
|
||||
rv = _CommitTransaction(ta)
|
||||
break
|
||||
else:
|
||||
time.sleep(0.001)
|
||||
retry += 1
|
||||
return rv
|
||||
finally:
|
||||
_CloseHandle(ta)
|
||||
|
||||
except Exception:
|
||||
|
||||
def _rename(src, dst):
|
||||
return False
|
||||
|
||||
def _rename_atomic(src, dst):
|
||||
return False
|
||||
|
||||
def rename(src, dst):
|
||||
# Try atomic or pseudo-atomic rename
|
||||
if _rename(src, dst):
|
||||
return
|
||||
# Fall back to "move away and replace"
|
||||
try:
|
||||
os.rename(src, dst)
|
||||
except OSError as e:
|
||||
if e.errno != errno.EEXIST:
|
||||
raise
|
||||
old = "%s-%08x" % (dst, random.randint(0, sys.maxsize))
|
||||
os.rename(dst, old)
|
||||
os.rename(src, dst)
|
||||
try:
|
||||
os.unlink(old)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
else:
|
||||
rename = os.rename
|
||||
can_rename_open_file = True
|
2210
venv/lib/python3.7/site-packages/werkzeug/routing.py
Normal file
2210
venv/lib/python3.7/site-packages/werkzeug/routing.py
Normal file
File diff suppressed because it is too large
Load Diff
249
venv/lib/python3.7/site-packages/werkzeug/security.py
Normal file
249
venv/lib/python3.7/site-packages/werkzeug/security.py
Normal file
@@ -0,0 +1,249 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.security
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Security related helpers such as secure password hashing tools.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
import posixpath
|
||||
from random import SystemRandom
|
||||
from struct import Struct
|
||||
|
||||
from ._compat import izip
|
||||
from ._compat import PY2
|
||||
from ._compat import range_type
|
||||
from ._compat import text_type
|
||||
from ._compat import to_bytes
|
||||
from ._compat import to_native
|
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
DEFAULT_PBKDF2_ITERATIONS = 150000
|
||||
|
||||
_pack_int = Struct(">I").pack
|
||||
_builtin_safe_str_cmp = getattr(hmac, "compare_digest", None)
|
||||
_sys_rng = SystemRandom()
|
||||
_os_alt_seps = list(
|
||||
sep for sep in [os.path.sep, os.path.altsep] if sep not in (None, "/")
|
||||
)
|
||||
|
||||
|
||||
def pbkdf2_hex(
|
||||
data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS, keylen=None, hashfunc=None
|
||||
):
|
||||
"""Like :func:`pbkdf2_bin`, but returns a hex-encoded string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
: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.
|
||||
"""
|
||||
rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc)
|
||||
return to_native(codecs.encode(rv, "hex_codec"))
|
||||
|
||||
|
||||
def pbkdf2_bin(
|
||||
data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS, keylen=None, hashfunc=None
|
||||
):
|
||||
"""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.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
|
||||
: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.
|
||||
"""
|
||||
if not hashfunc:
|
||||
hashfunc = "sha256"
|
||||
|
||||
data = to_bytes(data)
|
||||
salt = to_bytes(salt)
|
||||
|
||||
if callable(hashfunc):
|
||||
_test_hash = hashfunc()
|
||||
hash_name = getattr(_test_hash, "name", None)
|
||||
else:
|
||||
hash_name = hashfunc
|
||||
return hashlib.pbkdf2_hmac(hash_name, data, salt, iterations, keylen)
|
||||
|
||||
|
||||
def safe_str_cmp(a, b):
|
||||
"""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.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if isinstance(a, text_type):
|
||||
a = a.encode("utf-8")
|
||||
if isinstance(b, text_type):
|
||||
b = b.encode("utf-8")
|
||||
|
||||
if _builtin_safe_str_cmp is not None:
|
||||
return _builtin_safe_str_cmp(a, b)
|
||||
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
|
||||
rv = 0
|
||||
if PY2:
|
||||
for x, y in izip(a, b):
|
||||
rv |= ord(x) ^ ord(y)
|
||||
else:
|
||||
for x, y in izip(a, b):
|
||||
rv |= x ^ y
|
||||
|
||||
return rv == 0
|
||||
|
||||
|
||||
def gen_salt(length):
|
||||
"""Generate a random string of SALT_CHARS with specified ``length``."""
|
||||
if length <= 0:
|
||||
raise ValueError("Salt length must be positive")
|
||||
return "".join(_sys_rng.choice(SALT_CHARS) for _ in range_type(length))
|
||||
|
||||
|
||||
def _hash_internal(method, salt, password):
|
||||
"""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
|
||||
|
||||
if isinstance(password, text_type):
|
||||
password = password.encode("utf-8")
|
||||
|
||||
if method.startswith("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 = args and int(args[0] or 0) or DEFAULT_PBKDF2_ITERATIONS
|
||||
is_pbkdf2 = True
|
||||
actual_method = "pbkdf2:%s:%d" % (method, iterations)
|
||||
else:
|
||||
is_pbkdf2 = False
|
||||
actual_method = method
|
||||
|
||||
if is_pbkdf2:
|
||||
if not salt:
|
||||
raise ValueError("Salt is required for PBKDF2")
|
||||
rv = pbkdf2_hex(password, salt, iterations, hashfunc=method)
|
||||
elif salt:
|
||||
if isinstance(salt, text_type):
|
||||
salt = salt.encode("utf-8")
|
||||
mac = _create_mac(salt, password, method)
|
||||
rv = mac.hexdigest()
|
||||
else:
|
||||
rv = hashlib.new(method, password).hexdigest()
|
||||
return rv, actual_method
|
||||
|
||||
|
||||
def _create_mac(key, msg, method):
|
||||
if callable(method):
|
||||
return hmac.HMAC(key, msg, method)
|
||||
|
||||
def hashfunc(d=b""):
|
||||
return hashlib.new(method, d)
|
||||
|
||||
# Python 2.7 used ``hasattr(digestmod, '__call__')``
|
||||
# to detect if hashfunc is callable
|
||||
hashfunc.__call__ = hashfunc
|
||||
return hmac.HMAC(key, msg, hashfunc)
|
||||
|
||||
|
||||
def generate_password_hash(password, method="pbkdf2:sha256", salt_length=8):
|
||||
"""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 "%s$%s$%s" % (actual_method, salt, h)
|
||||
|
||||
|
||||
def check_password_hash(pwhash, password):
|
||||
"""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 safe_str_cmp(_hash_internal(method, salt, password)[0], hashval)
|
||||
|
||||
|
||||
def safe_join(directory, *pathnames):
|
||||
"""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)
|
1117
venv/lib/python3.7/site-packages/werkzeug/serving.py
Normal file
1117
venv/lib/python3.7/site-packages/werkzeug/serving.py
Normal file
File diff suppressed because it is too large
Load Diff
1123
venv/lib/python3.7/site-packages/werkzeug/test.py
Normal file
1123
venv/lib/python3.7/site-packages/werkzeug/test.py
Normal file
File diff suppressed because it is too large
Load Diff
241
venv/lib/python3.7/site-packages/werkzeug/testapp.py
Normal file
241
venv/lib/python3.7/site-packages/werkzeug/testapp.py
Normal file
@@ -0,0 +1,241 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.testapp
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Provide a small test application that can be used to test a WSGI server
|
||||
and check it for WSGI compliance.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import base64
|
||||
import os
|
||||
import sys
|
||||
from textwrap import wrap
|
||||
|
||||
from . import __version__ as _werkzeug_version
|
||||
from .utils import escape
|
||||
from .wrappers import BaseRequest as Request
|
||||
from .wrappers import BaseResponse as Response
|
||||
|
||||
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 = u"""\
|
||||
<!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():
|
||||
if os.name == "posix":
|
||||
|
||||
def strip(x):
|
||||
prefix = os.path.expanduser("~")
|
||||
if x.startswith(prefix):
|
||||
x = "~" + x[len(prefix) :]
|
||||
return x
|
||||
|
||||
else:
|
||||
|
||||
def strip(x):
|
||||
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):
|
||||
try:
|
||||
import pkg_resources
|
||||
except ImportError:
|
||||
eggs = ()
|
||||
else:
|
||||
eggs = sorted(pkg_resources.working_set, key=lambda x: x.project_name.lower())
|
||||
python_eggs = []
|
||||
for egg in eggs:
|
||||
try:
|
||||
version = egg.version
|
||||
except (ValueError, AttributeError):
|
||||
version = "unknown"
|
||||
python_eggs.append(
|
||||
"<li>%s <small>[%s]</small>" % (escape(egg.project_name), escape(version))
|
||||
)
|
||||
|
||||
wsgi_env = []
|
||||
sorted_environ = sorted(req.environ.items(), key=lambda x: repr(x[0]).lower())
|
||||
for key, value in sorted_environ:
|
||||
wsgi_env.append(
|
||||
"<tr><th>%s<td><code>%s</code>"
|
||||
% (escape(str(key)), " ".join(wrap(escape(repr(value)))))
|
||||
)
|
||||
|
||||
sys_path = []
|
||||
for item, virtual, expanded in iter_sys_path():
|
||||
class_ = []
|
||||
if virtual:
|
||||
class_.append("virtual")
|
||||
if expanded:
|
||||
class_.append("exp")
|
||||
sys_path.append(
|
||||
"<li%s>%s"
|
||||
% (' class="%s"' % " ".join(class_) if class_ else "", 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, start_response):
|
||||
"""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)
|
1138
venv/lib/python3.7/site-packages/werkzeug/urls.py
Normal file
1138
venv/lib/python3.7/site-packages/werkzeug/urls.py
Normal file
File diff suppressed because it is too large
Load Diff
202
venv/lib/python3.7/site-packages/werkzeug/useragents.py
Normal file
202
venv/lib/python3.7/site-packages/werkzeug/useragents.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.useragents
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This module provides a helper to inspect user agent strings. This module
|
||||
is far from complete but should work for most of the currently available
|
||||
browsers.
|
||||
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import re
|
||||
|
||||
|
||||
class UserAgentParser(object):
|
||||
"""A simple user agent parser. Used by the `UserAgent`."""
|
||||
|
||||
platforms = (
|
||||
(" cros ", "chromeos"),
|
||||
("iphone|ios", "iphone"),
|
||||
("ipad", "ipad"),
|
||||
(r"darwin|mac|os\s*x", "macos"),
|
||||
("win", "windows"),
|
||||
(r"android", "android"),
|
||||
("netbsd", "netbsd"),
|
||||
("openbsd", "openbsd"),
|
||||
("freebsd", "freebsd"),
|
||||
("dragonfly", "dragonflybsd"),
|
||||
("(sun|i86)os", "solaris"),
|
||||
(r"x11|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"),
|
||||
)
|
||||
browsers = (
|
||||
("googlebot", "google"),
|
||||
("msnbot", "msn"),
|
||||
("yahoo", "yahoo"),
|
||||
("ask jeeves", "ask"),
|
||||
(r"aol|america\s+online\s+browser", "aol"),
|
||||
(r"opera|opr", "opera"),
|
||||
("edge", "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"(?:%s)[/\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):
|
||||
self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms]
|
||||
self.browsers = [
|
||||
(b, re.compile(self._browser_version_re % a, re.I))
|
||||
for a, b in self.browsers
|
||||
]
|
||||
|
||||
def __call__(self, user_agent):
|
||||
for platform, regex in self.platforms: # noqa: B007
|
||||
match = regex.search(user_agent)
|
||||
if match is not None:
|
||||
break
|
||||
else:
|
||||
platform = None
|
||||
for browser, regex in self.browsers: # noqa: B007
|
||||
match = regex.search(user_agent)
|
||||
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
|
||||
|
||||
|
||||
class UserAgent(object):
|
||||
"""Represents a user agent. Pass it a WSGI environment or a user agent
|
||||
string and you can inspect some of the details from the user agent
|
||||
string via the attributes. The following attributes exist:
|
||||
|
||||
.. attribute:: string
|
||||
|
||||
the raw user agent string
|
||||
|
||||
.. attribute:: platform
|
||||
|
||||
the browser platform. The following platforms are currently
|
||||
recognized:
|
||||
|
||||
- `aix`
|
||||
- `amiga`
|
||||
- `android`
|
||||
- `blackberry`
|
||||
- `bsd`
|
||||
- `chromeos`
|
||||
- `dragonflybsd`
|
||||
- `freebsd`
|
||||
- `hpux`
|
||||
- `ipad`
|
||||
- `iphone`
|
||||
- `irix`
|
||||
- `linux`
|
||||
- `macos`
|
||||
- `netbsd`
|
||||
- `openbsd`
|
||||
- `sco`
|
||||
- `solaris`
|
||||
- `symbian`
|
||||
- `wii`
|
||||
- `windows`
|
||||
|
||||
.. attribute:: browser
|
||||
|
||||
the name of the browser. The following browsers are currently
|
||||
recognized:
|
||||
|
||||
- `aol` *
|
||||
- `ask` *
|
||||
- `baidu` *
|
||||
- `bing` *
|
||||
- `camino`
|
||||
- `chrome`
|
||||
- `edge`
|
||||
- `firefox`
|
||||
- `galeon`
|
||||
- `google` *
|
||||
- `kmeleon`
|
||||
- `konqueror`
|
||||
- `links`
|
||||
- `lynx`
|
||||
- `mozilla`
|
||||
- `msie`
|
||||
- `msn`
|
||||
- `netscape`
|
||||
- `opera`
|
||||
- `safari`
|
||||
- `seamonkey`
|
||||
- `webkit`
|
||||
- `yahoo` *
|
||||
|
||||
(Browsers marked with a star (``*``) are crawlers.)
|
||||
|
||||
.. attribute:: version
|
||||
|
||||
the version of the browser
|
||||
|
||||
.. attribute:: language
|
||||
|
||||
the language of the browser
|
||||
"""
|
||||
|
||||
_parser = UserAgentParser()
|
||||
|
||||
def __init__(self, environ_or_string):
|
||||
if isinstance(environ_or_string, dict):
|
||||
environ_or_string = environ_or_string.get("HTTP_USER_AGENT", "")
|
||||
self.string = environ_or_string
|
||||
self.platform, self.browser, self.version, self.language = self._parser(
|
||||
environ_or_string
|
||||
)
|
||||
|
||||
def to_header(self):
|
||||
return self.string
|
||||
|
||||
def __str__(self):
|
||||
return self.string
|
||||
|
||||
def __nonzero__(self):
|
||||
return bool(self.browser)
|
||||
|
||||
__bool__ = __nonzero__
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %r/%s>" % (self.__class__.__name__, self.browser, self.version)
|
778
venv/lib/python3.7/site-packages/werkzeug/utils.py
Normal file
778
venv/lib/python3.7/site-packages/werkzeug/utils.py
Normal file
@@ -0,0 +1,778 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.utils
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This module implements various utilities for WSGI applications. Most of
|
||||
them are used by the request and response wrappers but especially for
|
||||
middleware development it makes sense to use them without the wrappers.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
import codecs
|
||||
import os
|
||||
import pkgutil
|
||||
import re
|
||||
import sys
|
||||
|
||||
from ._compat import iteritems
|
||||
from ._compat import PY2
|
||||
from ._compat import reraise
|
||||
from ._compat import string_types
|
||||
from ._compat import text_type
|
||||
from ._compat import unichr
|
||||
from ._internal import _DictAccessorProperty
|
||||
from ._internal import _missing
|
||||
from ._internal import _parse_signature
|
||||
|
||||
try:
|
||||
from html.entities import name2codepoint
|
||||
except ImportError:
|
||||
from htmlentitydefs import name2codepoint
|
||||
|
||||
|
||||
_format_re = re.compile(r"\$(?:(%s)|\{(%s)\})" % (("[a-zA-Z_][a-zA-Z0-9_]*",) * 2))
|
||||
_entity_re = re.compile(r"&([^;]+);")
|
||||
_filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]")
|
||||
_windows_device_files = (
|
||||
"CON",
|
||||
"AUX",
|
||||
"COM1",
|
||||
"COM2",
|
||||
"COM3",
|
||||
"COM4",
|
||||
"LPT1",
|
||||
"LPT2",
|
||||
"LPT3",
|
||||
"PRN",
|
||||
"NUL",
|
||||
)
|
||||
|
||||
|
||||
class cached_property(property):
|
||||
"""A decorator that converts a function into a lazy property. The
|
||||
function wrapped is called the first time to retrieve the result
|
||||
and then that calculated result is used the next time you access
|
||||
the value::
|
||||
|
||||
class Foo(object):
|
||||
|
||||
@cached_property
|
||||
def foo(self):
|
||||
# calculate something important here
|
||||
return 42
|
||||
|
||||
The class has to have a `__dict__` in order for this property to
|
||||
work.
|
||||
"""
|
||||
|
||||
# implementation detail: A subclass of python's builtin property
|
||||
# decorator, we override __get__ to check for a cached value. If one
|
||||
# chooses to invoke __get__ by hand the property will still work as
|
||||
# expected because the lookup logic is replicated in __get__ for
|
||||
# manual invocation.
|
||||
|
||||
def __init__(self, func, name=None, doc=None):
|
||||
self.__name__ = name or func.__name__
|
||||
self.__module__ = func.__module__
|
||||
self.__doc__ = doc or func.__doc__
|
||||
self.func = func
|
||||
|
||||
def __set__(self, obj, value):
|
||||
obj.__dict__[self.__name__] = value
|
||||
|
||||
def __get__(self, obj, type=None):
|
||||
if obj is None:
|
||||
return self
|
||||
value = obj.__dict__.get(self.__name__, _missing)
|
||||
if value is _missing:
|
||||
value = self.func(obj)
|
||||
obj.__dict__[self.__name__] = value
|
||||
return value
|
||||
|
||||
|
||||
def invalidate_cached_property(obj, name):
|
||||
"""Invalidates the cache for a :class:`cached_property`:
|
||||
|
||||
>>> class Test(object):
|
||||
... @cached_property
|
||||
... def magic_number(self):
|
||||
... print("recalculating...")
|
||||
... return 42
|
||||
...
|
||||
>>> var = Test()
|
||||
>>> var.magic_number
|
||||
recalculating...
|
||||
42
|
||||
>>> var.magic_number
|
||||
42
|
||||
>>> invalidate_cached_property(var, "magic_number")
|
||||
>>> var.magic_number
|
||||
recalculating...
|
||||
42
|
||||
|
||||
You must pass the name of the cached property as the second argument.
|
||||
"""
|
||||
if not isinstance(getattr(obj.__class__, name, None), cached_property):
|
||||
raise TypeError(
|
||||
"Attribute {} of object {} is not a cached_property, "
|
||||
"cannot be invalidated".format(name, obj)
|
||||
)
|
||||
obj.__dict__[name] = _missing
|
||||
|
||||
|
||||
class environ_property(_DictAccessorProperty):
|
||||
"""Maps request attributes to environment variables. This works not only
|
||||
for the Werzeug request object, but also any other class with an
|
||||
environ attribute:
|
||||
|
||||
>>> class Test(object):
|
||||
... environ = {'key': 'value'}
|
||||
... test = environ_property('key')
|
||||
>>> var = Test()
|
||||
>>> var.test
|
||||
'value'
|
||||
|
||||
If you pass it a second value it's used as default if the key does not
|
||||
exist, the third one can be a converter that takes a value and converts
|
||||
it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value
|
||||
is used. If no default value is provided `None` is used.
|
||||
|
||||
Per default the property is read only. You have to explicitly enable it
|
||||
by passing ``read_only=False`` to the constructor.
|
||||
"""
|
||||
|
||||
read_only = True
|
||||
|
||||
def lookup(self, obj):
|
||||
return obj.environ
|
||||
|
||||
|
||||
class header_property(_DictAccessorProperty):
|
||||
"""Like `environ_property` but for headers."""
|
||||
|
||||
def lookup(self, obj):
|
||||
return obj.headers
|
||||
|
||||
|
||||
class HTMLBuilder(object):
|
||||
"""Helper object for HTML generation.
|
||||
|
||||
Per default there are two instances of that class. The `html` one, and
|
||||
the `xhtml` one for those two dialects. The class uses keyword parameters
|
||||
and positional parameters to generate small snippets of HTML.
|
||||
|
||||
Keyword parameters are converted to XML/SGML attributes, positional
|
||||
arguments are used as children. Because Python accepts positional
|
||||
arguments before keyword arguments it's a good idea to use a list with the
|
||||
star-syntax for some children:
|
||||
|
||||
>>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ',
|
||||
... html.a('bar', href='bar.html')])
|
||||
u'<p class="foo"><a href="foo.html">foo</a> <a href="bar.html">bar</a></p>'
|
||||
|
||||
This class works around some browser limitations and can not be used for
|
||||
arbitrary SGML/XML generation. For that purpose lxml and similar
|
||||
libraries exist.
|
||||
|
||||
Calling the builder escapes the string passed:
|
||||
|
||||
>>> html.p(html("<foo>"))
|
||||
u'<p><foo></p>'
|
||||
"""
|
||||
|
||||
_entity_re = re.compile(r"&([^;]+);")
|
||||
_entities = name2codepoint.copy()
|
||||
_entities["apos"] = 39
|
||||
_empty_elements = {
|
||||
"area",
|
||||
"base",
|
||||
"basefont",
|
||||
"br",
|
||||
"col",
|
||||
"command",
|
||||
"embed",
|
||||
"frame",
|
||||
"hr",
|
||||
"img",
|
||||
"input",
|
||||
"keygen",
|
||||
"isindex",
|
||||
"link",
|
||||
"meta",
|
||||
"param",
|
||||
"source",
|
||||
"wbr",
|
||||
}
|
||||
_boolean_attributes = {
|
||||
"selected",
|
||||
"checked",
|
||||
"compact",
|
||||
"declare",
|
||||
"defer",
|
||||
"disabled",
|
||||
"ismap",
|
||||
"multiple",
|
||||
"nohref",
|
||||
"noresize",
|
||||
"noshade",
|
||||
"nowrap",
|
||||
}
|
||||
_plaintext_elements = {"textarea"}
|
||||
_c_like_cdata = {"script", "style"}
|
||||
|
||||
def __init__(self, dialect):
|
||||
self._dialect = dialect
|
||||
|
||||
def __call__(self, s):
|
||||
return escape(s)
|
||||
|
||||
def __getattr__(self, tag):
|
||||
if tag[:2] == "__":
|
||||
raise AttributeError(tag)
|
||||
|
||||
def proxy(*children, **arguments):
|
||||
buffer = "<" + tag
|
||||
for key, value in iteritems(arguments):
|
||||
if value is None:
|
||||
continue
|
||||
if key[-1] == "_":
|
||||
key = key[:-1]
|
||||
if key in self._boolean_attributes:
|
||||
if not value:
|
||||
continue
|
||||
if self._dialect == "xhtml":
|
||||
value = '="' + key + '"'
|
||||
else:
|
||||
value = ""
|
||||
else:
|
||||
value = '="' + escape(value) + '"'
|
||||
buffer += " " + key + value
|
||||
if not children and tag in self._empty_elements:
|
||||
if self._dialect == "xhtml":
|
||||
buffer += " />"
|
||||
else:
|
||||
buffer += ">"
|
||||
return buffer
|
||||
buffer += ">"
|
||||
|
||||
children_as_string = "".join(
|
||||
[text_type(x) for x in children if x is not None]
|
||||
)
|
||||
|
||||
if children_as_string:
|
||||
if tag in self._plaintext_elements:
|
||||
children_as_string = escape(children_as_string)
|
||||
elif tag in self._c_like_cdata and self._dialect == "xhtml":
|
||||
children_as_string = (
|
||||
"/*<![CDATA[*/" + children_as_string + "/*]]>*/"
|
||||
)
|
||||
buffer += children_as_string + "</" + tag + ">"
|
||||
return buffer
|
||||
|
||||
return proxy
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s for %r>" % (self.__class__.__name__, self._dialect)
|
||||
|
||||
|
||||
html = HTMLBuilder("html")
|
||||
xhtml = HTMLBuilder("xhtml")
|
||||
|
||||
# https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in
|
||||
# https://www.iana.org/assignments/media-types/media-types.xhtml
|
||||
# Types listed in the XDG mime info that have a charset in the IANA registration.
|
||||
_charset_mimetypes = {
|
||||
"application/ecmascript",
|
||||
"application/javascript",
|
||||
"application/sql",
|
||||
"application/xml",
|
||||
"application/xml-dtd",
|
||||
"application/xml-external-parsed-entity",
|
||||
}
|
||||
|
||||
|
||||
def get_content_type(mimetype, charset):
|
||||
"""Returns the full content type string with charset for a mimetype.
|
||||
|
||||
If the mimetype represents text, the charset parameter will be
|
||||
appended, otherwise the mimetype is returned unchanged.
|
||||
|
||||
:param mimetype: The mimetype to be used as content type.
|
||||
:param charset: The charset to be appended for text mimetypes.
|
||||
:return: The content type.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
Any type that ends with ``+xml`` gets a charset, not just those
|
||||
that start with ``application/``. Known text types such as
|
||||
``application/javascript`` are also given charsets.
|
||||
"""
|
||||
if (
|
||||
mimetype.startswith("text/")
|
||||
or mimetype in _charset_mimetypes
|
||||
or mimetype.endswith("+xml")
|
||||
):
|
||||
mimetype += "; charset=" + charset
|
||||
|
||||
return mimetype
|
||||
|
||||
|
||||
def detect_utf_encoding(data):
|
||||
"""Detect which UTF encoding was used to encode the given bytes.
|
||||
|
||||
The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
|
||||
accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big
|
||||
or little endian. Some editors or libraries may prepend a BOM.
|
||||
|
||||
:internal:
|
||||
|
||||
:param data: Bytes in unknown UTF encoding.
|
||||
:return: UTF encoding name
|
||||
|
||||
.. versionadded:: 0.15
|
||||
"""
|
||||
head = data[:4]
|
||||
|
||||
if head[:3] == codecs.BOM_UTF8:
|
||||
return "utf-8-sig"
|
||||
|
||||
if b"\x00" not in head:
|
||||
return "utf-8"
|
||||
|
||||
if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE):
|
||||
return "utf-32"
|
||||
|
||||
if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE):
|
||||
return "utf-16"
|
||||
|
||||
if len(head) == 4:
|
||||
if head[:3] == b"\x00\x00\x00":
|
||||
return "utf-32-be"
|
||||
|
||||
if head[::2] == b"\x00\x00":
|
||||
return "utf-16-be"
|
||||
|
||||
if head[1:] == b"\x00\x00\x00":
|
||||
return "utf-32-le"
|
||||
|
||||
if head[1::2] == b"\x00\x00":
|
||||
return "utf-16-le"
|
||||
|
||||
if len(head) == 2:
|
||||
return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le"
|
||||
|
||||
return "utf-8"
|
||||
|
||||
|
||||
def format_string(string, context):
|
||||
"""String-template format a string:
|
||||
|
||||
>>> format_string('$foo and ${foo}s', dict(foo=42))
|
||||
'42 and 42s'
|
||||
|
||||
This does not do any attribute lookup etc. For more advanced string
|
||||
formattings have a look at the `werkzeug.template` module.
|
||||
|
||||
:param string: the format string.
|
||||
:param context: a dict with the variables to insert.
|
||||
"""
|
||||
|
||||
def lookup_arg(match):
|
||||
x = context[match.group(1) or match.group(2)]
|
||||
if not isinstance(x, string_types):
|
||||
x = type(string)(x)
|
||||
return x
|
||||
|
||||
return _format_re.sub(lookup_arg, string)
|
||||
|
||||
|
||||
def secure_filename(filename):
|
||||
r"""Pass it a filename and it will return a secure version of it. This
|
||||
filename can then safely be stored on a regular file system and passed
|
||||
to :func:`os.path.join`. The filename returned is an ASCII only string
|
||||
for maximum portability.
|
||||
|
||||
On windows systems the function also makes sure that the file is not
|
||||
named after one of the special device files.
|
||||
|
||||
>>> secure_filename("My cool movie.mov")
|
||||
'My_cool_movie.mov'
|
||||
>>> secure_filename("../../../etc/passwd")
|
||||
'etc_passwd'
|
||||
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
|
||||
'i_contain_cool_umlauts.txt'
|
||||
|
||||
The function might return an empty filename. It's your responsibility
|
||||
to ensure that the filename is unique and that you abort or
|
||||
generate a random filename if the function returned an empty one.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
|
||||
:param filename: the filename to secure
|
||||
"""
|
||||
if isinstance(filename, text_type):
|
||||
from unicodedata import normalize
|
||||
|
||||
filename = normalize("NFKD", filename).encode("ascii", "ignore")
|
||||
if not PY2:
|
||||
filename = filename.decode("ascii")
|
||||
for sep in os.path.sep, os.path.altsep:
|
||||
if sep:
|
||||
filename = filename.replace(sep, " ")
|
||||
filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip(
|
||||
"._"
|
||||
)
|
||||
|
||||
# on nt a couple of special files are present in each folder. We
|
||||
# have to ensure that the target file is not such a filename. In
|
||||
# this case we prepend an underline
|
||||
if (
|
||||
os.name == "nt"
|
||||
and filename
|
||||
and filename.split(".")[0].upper() in _windows_device_files
|
||||
):
|
||||
filename = "_" + filename
|
||||
|
||||
return filename
|
||||
|
||||
|
||||
def escape(s):
|
||||
"""Replace special characters "&", "<", ">" and (") to HTML-safe sequences.
|
||||
|
||||
There is a special handling for `None` which escapes to an empty string.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
`quote` is now implicitly on.
|
||||
|
||||
:param s: the string to escape.
|
||||
:param quote: ignored.
|
||||
"""
|
||||
if s is None:
|
||||
return ""
|
||||
elif hasattr(s, "__html__"):
|
||||
return text_type(s.__html__())
|
||||
|
||||
if not isinstance(s, string_types):
|
||||
s = text_type(s)
|
||||
|
||||
return (
|
||||
s.replace("&", "&")
|
||||
.replace("<", "<")
|
||||
.replace(">", ">")
|
||||
.replace('"', """)
|
||||
)
|
||||
|
||||
|
||||
def unescape(s):
|
||||
"""The reverse function of `escape`. This unescapes all the HTML
|
||||
entities, not only the XML entities inserted by `escape`.
|
||||
|
||||
:param s: the string to unescape.
|
||||
"""
|
||||
|
||||
def handle_match(m):
|
||||
name = m.group(1)
|
||||
if name in HTMLBuilder._entities:
|
||||
return unichr(HTMLBuilder._entities[name])
|
||||
try:
|
||||
if name[:2] in ("#x", "#X"):
|
||||
return unichr(int(name[2:], 16))
|
||||
elif name.startswith("#"):
|
||||
return unichr(int(name[1:]))
|
||||
except ValueError:
|
||||
pass
|
||||
return u""
|
||||
|
||||
return _entity_re.sub(handle_match, s)
|
||||
|
||||
|
||||
def redirect(location, code=302, Response=None):
|
||||
"""Returns a response object (a WSGI application) that, if called,
|
||||
redirects the client to the target location. Supported codes are
|
||||
301, 302, 303, 305, 307, and 308. 300 is not supported because
|
||||
it's not a real redirect and 304 because it's the answer for a
|
||||
request with a request with defined If-Modified-Since headers.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
The location can now be a unicode string that is encoded using
|
||||
the :func:`iri_to_uri` function.
|
||||
|
||||
.. versionadded:: 0.10
|
||||
The class used for the Response object can now be passed in.
|
||||
|
||||
:param location: the location the response should redirect to.
|
||||
:param code: the redirect status code. defaults to 302.
|
||||
:param class Response: a Response class to use when instantiating a
|
||||
response. The default is :class:`werkzeug.wrappers.Response` if
|
||||
unspecified.
|
||||
"""
|
||||
if Response is None:
|
||||
from .wrappers import Response
|
||||
|
||||
display_location = escape(location)
|
||||
if isinstance(location, text_type):
|
||||
# Safe conversion is necessary here as we might redirect
|
||||
# to a broken URI scheme (for instance itms-services).
|
||||
from .urls import iri_to_uri
|
||||
|
||||
location = iri_to_uri(location, safe_conversion=True)
|
||||
response = Response(
|
||||
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
|
||||
"<title>Redirecting...</title>\n"
|
||||
"<h1>Redirecting...</h1>\n"
|
||||
"<p>You should be redirected automatically to target URL: "
|
||||
'<a href="%s">%s</a>. If not click the link.'
|
||||
% (escape(location), display_location),
|
||||
code,
|
||||
mimetype="text/html",
|
||||
)
|
||||
response.headers["Location"] = location
|
||||
return response
|
||||
|
||||
|
||||
def append_slash_redirect(environ, code=301):
|
||||
"""Redirects to the same URL but with a slash appended. The behavior
|
||||
of this function is undefined if the path ends with a slash already.
|
||||
|
||||
:param environ: the WSGI environment for the request that triggers
|
||||
the redirect.
|
||||
:param code: the status code for the redirect.
|
||||
"""
|
||||
new_path = environ["PATH_INFO"].strip("/") + "/"
|
||||
query_string = environ.get("QUERY_STRING")
|
||||
if query_string:
|
||||
new_path += "?" + query_string
|
||||
return redirect(new_path, code)
|
||||
|
||||
|
||||
def import_string(import_name, silent=False):
|
||||
"""Imports an object based on a string. This is useful if you want to
|
||||
use import paths as endpoints or something similar. An import path can
|
||||
be specified either in dotted notation (``xml.sax.saxutils.escape``)
|
||||
or with a colon as object delimiter (``xml.sax.saxutils:escape``).
|
||||
|
||||
If `silent` is True the return value will be `None` if the import fails.
|
||||
|
||||
:param import_name: the dotted name for the object to import.
|
||||
:param silent: if set to `True` import errors are ignored and
|
||||
`None` is returned instead.
|
||||
:return: imported object
|
||||
"""
|
||||
# force the import name to automatically convert to strings
|
||||
# __import__ is not able to handle unicode strings in the fromlist
|
||||
# if the module is a package
|
||||
import_name = str(import_name).replace(":", ".")
|
||||
try:
|
||||
try:
|
||||
__import__(import_name)
|
||||
except ImportError:
|
||||
if "." not in import_name:
|
||||
raise
|
||||
else:
|
||||
return sys.modules[import_name]
|
||||
|
||||
module_name, obj_name = import_name.rsplit(".", 1)
|
||||
module = __import__(module_name, globals(), locals(), [obj_name])
|
||||
try:
|
||||
return getattr(module, obj_name)
|
||||
except AttributeError as e:
|
||||
raise ImportError(e)
|
||||
|
||||
except ImportError as e:
|
||||
if not silent:
|
||||
reraise(
|
||||
ImportStringError, ImportStringError(import_name, e), sys.exc_info()[2]
|
||||
)
|
||||
|
||||
|
||||
def find_modules(import_path, include_packages=False, recursive=False):
|
||||
"""Finds all the modules below a package. This can be useful to
|
||||
automatically import all views / controllers so that their metaclasses /
|
||||
function decorators have a chance to register themselves on the
|
||||
application.
|
||||
|
||||
Packages are not returned unless `include_packages` is `True`. This can
|
||||
also recursively list modules but in that case it will import all the
|
||||
packages to get the correct load path of that module.
|
||||
|
||||
:param import_path: the dotted name for the package to find child modules.
|
||||
:param include_packages: set to `True` if packages should be returned, too.
|
||||
:param recursive: set to `True` if recursion should happen.
|
||||
:return: generator
|
||||
"""
|
||||
module = import_string(import_path)
|
||||
path = getattr(module, "__path__", None)
|
||||
if path is None:
|
||||
raise ValueError("%r is not a package" % import_path)
|
||||
basename = module.__name__ + "."
|
||||
for _importer, modname, ispkg in pkgutil.iter_modules(path):
|
||||
modname = basename + modname
|
||||
if ispkg:
|
||||
if include_packages:
|
||||
yield modname
|
||||
if recursive:
|
||||
for item in find_modules(modname, include_packages, True):
|
||||
yield item
|
||||
else:
|
||||
yield modname
|
||||
|
||||
|
||||
def validate_arguments(func, args, kwargs, drop_extra=True):
|
||||
"""Checks if the function accepts the arguments and keyword arguments.
|
||||
Returns a new ``(args, kwargs)`` tuple that can safely be passed to
|
||||
the function without causing a `TypeError` because the function signature
|
||||
is incompatible. If `drop_extra` is set to `True` (which is the default)
|
||||
any extra positional or keyword arguments are dropped automatically.
|
||||
|
||||
The exception raised provides three attributes:
|
||||
|
||||
`missing`
|
||||
A set of argument names that the function expected but where
|
||||
missing.
|
||||
|
||||
`extra`
|
||||
A dict of keyword arguments that the function can not handle but
|
||||
where provided.
|
||||
|
||||
`extra_positional`
|
||||
A list of values that where given by positional argument but the
|
||||
function cannot accept.
|
||||
|
||||
This can be useful for decorators that forward user submitted data to
|
||||
a view function::
|
||||
|
||||
from werkzeug.utils import ArgumentValidationError, validate_arguments
|
||||
|
||||
def sanitize(f):
|
||||
def proxy(request):
|
||||
data = request.values.to_dict()
|
||||
try:
|
||||
args, kwargs = validate_arguments(f, (request,), data)
|
||||
except ArgumentValidationError:
|
||||
raise BadRequest('The browser failed to transmit all '
|
||||
'the data expected.')
|
||||
return f(*args, **kwargs)
|
||||
return proxy
|
||||
|
||||
:param func: the function the validation is performed against.
|
||||
:param args: a tuple of positional arguments.
|
||||
:param kwargs: a dict of keyword arguments.
|
||||
:param drop_extra: set to `False` if you don't want extra arguments
|
||||
to be silently dropped.
|
||||
:return: tuple in the form ``(args, kwargs)``.
|
||||
"""
|
||||
parser = _parse_signature(func)
|
||||
args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5]
|
||||
if missing:
|
||||
raise ArgumentValidationError(tuple(missing))
|
||||
elif (extra or extra_positional) and not drop_extra:
|
||||
raise ArgumentValidationError(None, extra, extra_positional)
|
||||
return tuple(args), kwargs
|
||||
|
||||
|
||||
def bind_arguments(func, args, kwargs):
|
||||
"""Bind the arguments provided into a dict. When passed a function,
|
||||
a tuple of arguments and a dict of keyword arguments `bind_arguments`
|
||||
returns a dict of names as the function would see it. This can be useful
|
||||
to implement a cache decorator that uses the function arguments to build
|
||||
the cache key based on the values of the arguments.
|
||||
|
||||
:param func: the function the arguments should be bound for.
|
||||
:param args: tuple of positional arguments.
|
||||
:param kwargs: a dict of keyword arguments.
|
||||
:return: a :class:`dict` of bound keyword arguments.
|
||||
"""
|
||||
(
|
||||
args,
|
||||
kwargs,
|
||||
missing,
|
||||
extra,
|
||||
extra_positional,
|
||||
arg_spec,
|
||||
vararg_var,
|
||||
kwarg_var,
|
||||
) = _parse_signature(func)(args, kwargs)
|
||||
values = {}
|
||||
for (name, _has_default, _default), value in zip(arg_spec, args):
|
||||
values[name] = value
|
||||
if vararg_var is not None:
|
||||
values[vararg_var] = tuple(extra_positional)
|
||||
elif extra_positional:
|
||||
raise TypeError("too many positional arguments")
|
||||
if kwarg_var is not None:
|
||||
multikw = set(extra) & set([x[0] for x in arg_spec])
|
||||
if multikw:
|
||||
raise TypeError(
|
||||
"got multiple values for keyword argument " + repr(next(iter(multikw)))
|
||||
)
|
||||
values[kwarg_var] = extra
|
||||
elif extra:
|
||||
raise TypeError("got unexpected keyword argument " + repr(next(iter(extra))))
|
||||
return values
|
||||
|
||||
|
||||
class ArgumentValidationError(ValueError):
|
||||
|
||||
"""Raised if :func:`validate_arguments` fails to validate"""
|
||||
|
||||
def __init__(self, missing=None, extra=None, extra_positional=None):
|
||||
self.missing = set(missing or ())
|
||||
self.extra = extra or {}
|
||||
self.extra_positional = extra_positional or []
|
||||
ValueError.__init__(
|
||||
self,
|
||||
"function arguments invalid. (%d missing, %d additional)"
|
||||
% (len(self.missing), len(self.extra) + len(self.extra_positional)),
|
||||
)
|
||||
|
||||
|
||||
class ImportStringError(ImportError):
|
||||
"""Provides information about a failed :func:`import_string` attempt."""
|
||||
|
||||
#: String in dotted notation that failed to be imported.
|
||||
import_name = None
|
||||
#: Wrapped exception.
|
||||
exception = None
|
||||
|
||||
def __init__(self, import_name, exception):
|
||||
self.import_name = import_name
|
||||
self.exception = exception
|
||||
|
||||
msg = (
|
||||
"import_string() failed for %r. Possible reasons are:\n\n"
|
||||
"- missing __init__.py in a package;\n"
|
||||
"- package or module path not included in sys.path;\n"
|
||||
"- duplicated package or module name taking precedence in "
|
||||
"sys.path;\n"
|
||||
"- missing module, class, function or variable;\n\n"
|
||||
"Debugged import:\n\n%s\n\n"
|
||||
"Original exception:\n\n%s: %s"
|
||||
)
|
||||
|
||||
name = ""
|
||||
tracked = []
|
||||
for part in import_name.replace(":", ".").split("."):
|
||||
name += (name and ".") + part
|
||||
imported = import_string(name, silent=True)
|
||||
if imported:
|
||||
tracked.append((name, getattr(imported, "__file__", None)))
|
||||
else:
|
||||
track = ["- %r found in %r." % (n, i) for n, i in tracked]
|
||||
track.append("- %r not found." % name)
|
||||
msg = msg % (
|
||||
import_name,
|
||||
"\n".join(track),
|
||||
exception.__class__.__name__,
|
||||
str(exception),
|
||||
)
|
||||
break
|
||||
|
||||
ImportError.__init__(self, msg)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s(%r, %r)>" % (
|
||||
self.__class__.__name__,
|
||||
self.import_name,
|
||||
self.exception,
|
||||
)
|
@@ -0,0 +1,36 @@
|
||||
"""
|
||||
werkzeug.wrappers
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
The wrappers are simple request and response objects which you can
|
||||
subclass to do whatever you want them to do. The request object contains
|
||||
the information transmitted by the client (webbrowser) and the response
|
||||
object contains all the information sent back to the browser.
|
||||
|
||||
An important detail is that the request object is created with the WSGI
|
||||
environ and will act as high-level proxy whereas the response object is an
|
||||
actual WSGI application.
|
||||
|
||||
Like everything else in Werkzeug these objects will work correctly with
|
||||
unicode data. Incoming form data parsed by the response object will be
|
||||
decoded into an unicode object if possible and if it makes sense.
|
||||
|
||||
:copyright: 2007 Pallets
|
||||
:license: BSD-3-Clause
|
||||
"""
|
||||
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
|
||||
from .request import StreamOnlyMixin
|
||||
from .response import Response
|
||||
from .response import ResponseStream
|
||||
from .response import ResponseStreamMixin
|
||||
from .user_agent import UserAgentMixin
|
50
venv/lib/python3.7/site-packages/werkzeug/wrappers/accept.py
Normal file
50
venv/lib/python3.7/site-packages/werkzeug/wrappers/accept.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from ..datastructures import CharsetAccept
|
||||
from ..datastructures import LanguageAccept
|
||||
from ..datastructures import MIMEAccept
|
||||
from ..http import parse_accept_header
|
||||
from ..utils import cached_property
|
||||
|
||||
|
||||
class AcceptMixin(object):
|
||||
"""A mixin for classes with an :attr:`~BaseResponse.environ` attribute
|
||||
to get all the HTTP accept headers as
|
||||
:class:`~werkzeug.datastructures.Accept` objects (or subclasses
|
||||
thereof).
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def accept_mimetypes(self):
|
||||
"""List of mimetypes this client supports as
|
||||
:class:`~werkzeug.datastructures.MIMEAccept` object.
|
||||
"""
|
||||
return parse_accept_header(self.environ.get("HTTP_ACCEPT"), MIMEAccept)
|
||||
|
||||
@cached_property
|
||||
def accept_charsets(self):
|
||||
"""List of charsets this client supports as
|
||||
:class:`~werkzeug.datastructures.CharsetAccept` object.
|
||||
"""
|
||||
return parse_accept_header(
|
||||
self.environ.get("HTTP_ACCEPT_CHARSET"), CharsetAccept
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def accept_encodings(self):
|
||||
"""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.environ.get("HTTP_ACCEPT_ENCODING"))
|
||||
|
||||
@cached_property
|
||||
def accept_languages(self):
|
||||
"""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.environ.get("HTTP_ACCEPT_LANGUAGE"), LanguageAccept
|
||||
)
|
33
venv/lib/python3.7/site-packages/werkzeug/wrappers/auth.py
Normal file
33
venv/lib/python3.7/site-packages/werkzeug/wrappers/auth.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from ..http import parse_authorization_header
|
||||
from ..http import parse_www_authenticate_header
|
||||
from ..utils import cached_property
|
||||
|
||||
|
||||
class AuthorizationMixin(object):
|
||||
"""Adds an :attr:`authorization` property that represents the parsed
|
||||
value of the `Authorization` header as
|
||||
:class:`~werkzeug.datastructures.Authorization` object.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def authorization(self):
|
||||
"""The `Authorization` object in parsed form."""
|
||||
header = self.environ.get("HTTP_AUTHORIZATION")
|
||||
return parse_authorization_header(header)
|
||||
|
||||
|
||||
class WWWAuthenticateMixin(object):
|
||||
"""Adds a :attr:`www_authenticate` property to a response object."""
|
||||
|
||||
@property
|
||||
def www_authenticate(self):
|
||||
"""The `WWW-Authenticate` header in a parsed form."""
|
||||
|
||||
def on_update(www_auth):
|
||||
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)
|
@@ -0,0 +1,673 @@
|
||||
from functools import update_wrapper
|
||||
from io import BytesIO
|
||||
|
||||
from .._compat import to_native
|
||||
from .._compat import to_unicode
|
||||
from .._compat import wsgi_decoding_dance
|
||||
from .._compat import wsgi_get_bytes
|
||||
from ..datastructures import CombinedMultiDict
|
||||
from ..datastructures import EnvironHeaders
|
||||
from ..datastructures import ImmutableList
|
||||
from ..datastructures import ImmutableMultiDict
|
||||
from ..datastructures import iter_multi_items
|
||||
from ..datastructures import MultiDict
|
||||
from ..formparser import default_stream_factory
|
||||
from ..formparser import FormDataParser
|
||||
from ..http import parse_cookie
|
||||
from ..http import parse_list_header
|
||||
from ..http import parse_options_header
|
||||
from ..urls import url_decode
|
||||
from ..utils import cached_property
|
||||
from ..utils import environ_property
|
||||
from ..wsgi import get_content_length
|
||||
from ..wsgi import get_current_url
|
||||
from ..wsgi import get_host
|
||||
from ..wsgi import get_input_stream
|
||||
|
||||
|
||||
class BaseRequest(object):
|
||||
"""Very basic request object. This does not implement advanced stuff like
|
||||
entity tag parsing or cache controls. The request object is created with
|
||||
the WSGI environment as first argument and will add itself to the WSGI
|
||||
environment as ``'werkzeug.request'`` unless it's created with
|
||||
`populate_request` set to False.
|
||||
|
||||
There are a couple of mixins available that add additional functionality
|
||||
to the request object, there is also a class called `Request` which
|
||||
subclasses `BaseRequest` and all the important mixins.
|
||||
|
||||
It's a good idea to create a custom subclass of the :class:`BaseRequest`
|
||||
and add missing functionality either via mixins or direct implementation.
|
||||
Here an example for such subclasses::
|
||||
|
||||
from werkzeug.wrappers import BaseRequest, ETagRequestMixin
|
||||
|
||||
class Request(BaseRequest, ETagRequestMixin):
|
||||
pass
|
||||
|
||||
Request objects are **read only**. As of 0.5 modifications are not
|
||||
allowed in any place. Unlike the lower level parsing functions the
|
||||
request object will use immutable objects everywhere possible.
|
||||
|
||||
Per default the request object will assume all the text data is `utf-8`
|
||||
encoded. Please refer to :doc:`the unicode chapter </unicode>` for more
|
||||
details about customizing the behavior.
|
||||
|
||||
Per default the request object will be added to the WSGI
|
||||
environment as `werkzeug.request` to support the debugging system.
|
||||
If you don't want that, set `populate_request` to `False`.
|
||||
|
||||
If `shallow` is `True` the environment is initialized as shallow
|
||||
object around the environ. Every operation that would modify the
|
||||
environ in any way (such as consuming form data) raises an exception
|
||||
unless the `shallow` attribute is explicitly set to `False`. This
|
||||
is useful for middlewares where you don't want to consume the form
|
||||
data by accident. A shallow request is not populated to the WSGI
|
||||
environment.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
read-only mode was enforced by using immutables classes for all
|
||||
data.
|
||||
"""
|
||||
|
||||
#: the charset for the request, defaults to utf-8
|
||||
charset = "utf-8"
|
||||
|
||||
#: the error handling procedure for errors, defaults to 'replace'
|
||||
encoding_errors = "replace"
|
||||
|
||||
#: the maximum content length. This is forwarded to the form data
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: parsing fails because more than the specified value is transmitted
|
||||
#: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: Have a look at :ref:`dealing-with-request-data` for more details.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
max_content_length = None
|
||||
|
||||
#: the maximum form field size. This is forwarded to the form data
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: data in memory for post data is longer than the specified value a
|
||||
#: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: Have a look at :ref:`dealing-with-request-data` for more details.
|
||||
#:
|
||||
#: .. versionadded:: 0.5
|
||||
max_form_memory_size = None
|
||||
|
||||
#: 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 = 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 = ImmutableList
|
||||
|
||||
#: 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 = ImmutableMultiDict
|
||||
|
||||
#: The form data parser that shoud be used. Can be replaced to customize
|
||||
#: the form date parsing.
|
||||
form_data_parser_class = FormDataParser
|
||||
|
||||
#: Optionally a list of hosts that is trusted by this request. By default
|
||||
#: all hosts are trusted which means that whatever the client sends 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 application is being run
|
||||
#: behind one).
|
||||
#:
|
||||
#: .. versionadded:: 0.9
|
||||
trusted_hosts = None
|
||||
|
||||
#: Indicates whether the data descriptor should be allowed to read and
|
||||
#: buffer up the input stream. By default it's enabled.
|
||||
#:
|
||||
#: .. versionadded:: 0.9
|
||||
disable_data_descriptor = False
|
||||
|
||||
def __init__(self, environ, populate_request=True, shallow=False):
|
||||
self.environ = environ
|
||||
if populate_request and not shallow:
|
||||
self.environ["werkzeug.request"] = self
|
||||
self.shallow = shallow
|
||||
|
||||
def __repr__(self):
|
||||
# make sure the __repr__ even works if the request was created
|
||||
# from an invalid WSGI environment. If we display the request
|
||||
# in a debug session we don't want the repr to blow up.
|
||||
args = []
|
||||
try:
|
||||
args.append("'%s'" % to_native(self.url, self.url_charset))
|
||||
args.append("[%s]" % self.method)
|
||||
except Exception:
|
||||
args.append("(invalid WSGI environ)")
|
||||
|
||||
return "<%s %s>" % (self.__class__.__name__, " ".join(args))
|
||||
|
||||
@property
|
||||
def url_charset(self):
|
||||
"""The charset that is assumed for URLs. Defaults to the value
|
||||
of :attr:`charset`.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
return self.charset
|
||||
|
||||
@classmethod
|
||||
def from_values(cls, *args, **kwargs):
|
||||
"""Create a new request object based on the values provided. If
|
||||
environ is given missing values are filled from there. This method is
|
||||
useful for small scripts when you need to simulate a request from an URL.
|
||||
Do not use this method for unittesting, there is a full featured client
|
||||
object (:class:`Client`) that allows to create multipart requests,
|
||||
support for cookies etc.
|
||||
|
||||
This accepts the same options as the
|
||||
:class:`~werkzeug.test.EnvironBuilder`.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This method now accepts the same arguments as
|
||||
:class:`~werkzeug.test.EnvironBuilder`. Because of this the
|
||||
`environ` parameter is now called `environ_overrides`.
|
||||
|
||||
:return: request object
|
||||
"""
|
||||
from ..test import EnvironBuilder
|
||||
|
||||
charset = kwargs.pop("charset", cls.charset)
|
||||
kwargs["charset"] = charset
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
try:
|
||||
return builder.get_request(cls)
|
||||
finally:
|
||||
builder.close()
|
||||
|
||||
@classmethod
|
||||
def application(cls, f):
|
||||
"""Decorate a function as responder that accepts the request as
|
||||
the last argument. This works like the :func:`responder`
|
||||
decorator but the function is passed the request object as the
|
||||
last argument and the request object will be closed
|
||||
automatically::
|
||||
|
||||
@Request.application
|
||||
def my_wsgi_app(request):
|
||||
return Response('Hello World!')
|
||||
|
||||
As of Werkzeug 0.14 HTTP exceptions are automatically caught and
|
||||
converted to responses instead of failing.
|
||||
|
||||
:param f: the WSGI callable to decorate
|
||||
:return: a new WSGI callable
|
||||
"""
|
||||
#: return a callable that wraps the -2nd argument with the request
|
||||
#: and calls the function with all the arguments up to that one and
|
||||
#: the request. The return value is then called with the latest
|
||||
#: two arguments. This makes it possible to use this decorator for
|
||||
#: both standalone WSGI functions as well as bound methods and
|
||||
#: partially applied functions.
|
||||
from ..exceptions import HTTPException
|
||||
|
||||
def application(*args):
|
||||
request = cls(args[-2])
|
||||
with request:
|
||||
try:
|
||||
resp = f(*args[:-2] + (request,))
|
||||
except HTTPException as e:
|
||||
resp = e.get_response(args[-2])
|
||||
return resp(*args[-2:])
|
||||
|
||||
return update_wrapper(application, f)
|
||||
|
||||
def _get_file_stream(
|
||||
self, total_content_length, content_type, filename=None, content_length=None
|
||||
):
|
||||
"""Called to get a stream for the file upload.
|
||||
|
||||
This must provide a file-like class with `read()`, `readline()`
|
||||
and `seek()` methods that is both writeable and readable.
|
||||
|
||||
The default implementation returns a temporary file if the total
|
||||
content length is higher than 500KB. Because many browsers do not
|
||||
provide a content length for the files only the total content
|
||||
length matters.
|
||||
|
||||
:param total_content_length: the total content length of all the
|
||||
data in the request combined. This value
|
||||
is guaranteed to be there.
|
||||
:param content_type: the mimetype of the uploaded file.
|
||||
:param filename: the filename of the uploaded file. May be `None`.
|
||||
:param content_length: the length of this file. This value is usually
|
||||
not provided because webbrowsers do not provide
|
||||
this value.
|
||||
"""
|
||||
return default_stream_factory(
|
||||
total_content_length=total_content_length,
|
||||
filename=filename,
|
||||
content_type=content_type,
|
||||
content_length=content_length,
|
||||
)
|
||||
|
||||
@property
|
||||
def want_form_data_parsed(self):
|
||||
"""Returns True if the request method carries content. As of
|
||||
Werkzeug 0.9 this will be the case if a content type is transmitted.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
return bool(self.environ.get("CONTENT_TYPE"))
|
||||
|
||||
def make_form_data_parser(self):
|
||||
"""Creates the form data parser. Instantiates the
|
||||
:attr:`form_data_parser_class` with some parameters.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
return self.form_data_parser_class(
|
||||
self._get_file_stream,
|
||||
self.charset,
|
||||
self.encoding_errors,
|
||||
self.max_form_memory_size,
|
||||
self.max_content_length,
|
||||
self.parameter_storage_class,
|
||||
)
|
||||
|
||||
def _load_form_data(self):
|
||||
"""Method used internally to retrieve submitted data. After calling
|
||||
this sets `form` and `files` on the request object to multi dicts
|
||||
filled with the incoming form data. As a matter of fact the input
|
||||
stream will be empty afterwards. You can also call this method to
|
||||
force the parsing of the form data.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
# abort early if we have already consumed the stream
|
||||
if "form" in self.__dict__:
|
||||
return
|
||||
|
||||
_assert_not_shallow(self)
|
||||
|
||||
if self.want_form_data_parsed:
|
||||
content_type = self.environ.get("CONTENT_TYPE", "")
|
||||
content_length = get_content_length(self.environ)
|
||||
mimetype, options = parse_options_header(content_type)
|
||||
parser = self.make_form_data_parser()
|
||||
data = parser.parse(
|
||||
self._get_stream_for_parsing(), mimetype, content_length, options
|
||||
)
|
||||
else:
|
||||
data = (
|
||||
self.stream,
|
||||
self.parameter_storage_class(),
|
||||
self.parameter_storage_class(),
|
||||
)
|
||||
|
||||
# inject the values into the instance dict so that we bypass
|
||||
# our cached_property non-data descriptor.
|
||||
d = self.__dict__
|
||||
d["stream"], d["form"], d["files"] = data
|
||||
|
||||
def _get_stream_for_parsing(self):
|
||||
"""This is the same as accessing :attr:`stream` with the difference
|
||||
that if it finds cached data from calling :meth:`get_data` first it
|
||||
will create a new stream out of the cached data.
|
||||
|
||||
.. versionadded:: 0.9.3
|
||||
"""
|
||||
cached_data = getattr(self, "_cached_data", None)
|
||||
if cached_data is not None:
|
||||
return BytesIO(cached_data)
|
||||
return self.stream
|
||||
|
||||
def close(self):
|
||||
"""Closes associated resources of this request object. This
|
||||
closes all file handles explicitly. You can also use the request
|
||||
object in a with statement which will automatically close it.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
files = self.__dict__.get("files")
|
||||
for _key, value in iter_multi_items(files or ()):
|
||||
value.close()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close()
|
||||
|
||||
@cached_property
|
||||
def stream(self):
|
||||
"""
|
||||
If the incoming form data was not encoded with a known mimetype
|
||||
the data is stored unmodified in this stream for consumption. Most
|
||||
of the time it is a better idea to use :attr:`data` which will give
|
||||
you that data as a string. The stream only returns the data once.
|
||||
|
||||
Unlike :attr:`input_stream` this stream is properly guarded that you
|
||||
can't accidentally read past the length of the input. Werkzeug will
|
||||
internally always refer to this stream to read data which makes it
|
||||
possible to wrap this object with a stream that does filtering.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
This stream is now always available but might be consumed by the
|
||||
form parser later on. Previously the stream was only set if no
|
||||
parsing happened.
|
||||
"""
|
||||
_assert_not_shallow(self)
|
||||
return get_input_stream(self.environ)
|
||||
|
||||
input_stream = environ_property(
|
||||
"wsgi.input",
|
||||
"""The WSGI input stream.
|
||||
|
||||
In general it's a bad idea to use this one because you can
|
||||
easily read past the boundary. Use the :attr:`stream`
|
||||
instead.""",
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def args(self):
|
||||
"""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(
|
||||
wsgi_get_bytes(self.environ.get("QUERY_STRING", "")),
|
||||
self.url_charset,
|
||||
errors=self.encoding_errors,
|
||||
cls=self.parameter_storage_class,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def data(self):
|
||||
"""
|
||||
Contains the incoming request data as string in case it came with
|
||||
a mimetype Werkzeug does not handle.
|
||||
"""
|
||||
|
||||
if self.disable_data_descriptor:
|
||||
raise AttributeError("data descriptor is disabled")
|
||||
# XXX: this should eventually be deprecated.
|
||||
|
||||
# We trigger form data parsing first which means that the descriptor
|
||||
# will not cache the data that would otherwise be .form or .files
|
||||
# data. This restores the behavior that was there in Werkzeug
|
||||
# before 0.9. New code should use :meth:`get_data` explicitly as
|
||||
# this will make behavior explicit.
|
||||
return self.get_data(parse_form_data=True)
|
||||
|
||||
def get_data(self, cache=True, as_text=False, parse_form_data=False):
|
||||
"""This reads the buffered incoming data from the client into one
|
||||
bytestring. By default this is cached but that behavior can be
|
||||
changed by setting `cache` to `False`.
|
||||
|
||||
Usually it's a bad idea to call this method without checking the
|
||||
content length first as a client could send dozens of megabytes or more
|
||||
to cause memory problems on the server.
|
||||
|
||||
Note that if the form data was already parsed this method will not
|
||||
return anything as form data parsing does not cache the data like
|
||||
this method does. To implicitly invoke form data parsing function
|
||||
set `parse_form_data` to `True`. When this is done the return value
|
||||
of this method will be an empty string if the form parser handles
|
||||
the data. This generally is not necessary as if the whole data is
|
||||
cached (which is the default) the form parser will used the cached
|
||||
data to parse the form data. Please be generally aware of checking
|
||||
the content length first in any case before calling this method
|
||||
to avoid exhausting server memory.
|
||||
|
||||
If `as_text` is set to `True` the return value will be a decoded
|
||||
unicode string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
rv = getattr(self, "_cached_data", None)
|
||||
if rv is None:
|
||||
if parse_form_data:
|
||||
self._load_form_data()
|
||||
rv = self.stream.read()
|
||||
if cache:
|
||||
self._cached_data = rv
|
||||
if as_text:
|
||||
rv = rv.decode(self.charset, self.encoding_errors)
|
||||
return rv
|
||||
|
||||
@cached_property
|
||||
def form(self):
|
||||
"""The form parameters. 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.
|
||||
|
||||
Please keep in mind that file uploads will not end up here, but instead
|
||||
in the :attr:`files` attribute.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
|
||||
Previous to Werkzeug 0.9 this would only contain form data for POST
|
||||
and PUT requests.
|
||||
"""
|
||||
self._load_form_data()
|
||||
return self.form
|
||||
|
||||
@cached_property
|
||||
def values(self):
|
||||
"""A :class:`werkzeug.datastructures.CombinedMultiDict` that combines
|
||||
:attr:`args` and :attr:`form`."""
|
||||
args = []
|
||||
for d in self.args, self.form:
|
||||
if not isinstance(d, MultiDict):
|
||||
d = MultiDict(d)
|
||||
args.append(d)
|
||||
return CombinedMultiDict(args)
|
||||
|
||||
@cached_property
|
||||
def files(self):
|
||||
""":class:`~werkzeug.datastructures.MultiDict` object containing
|
||||
all uploaded files. Each key in :attr:`files` is the name from the
|
||||
``<input type="file" name="">``. Each value in :attr:`files` is a
|
||||
Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
|
||||
|
||||
It basically behaves like a standard file object you know from Python,
|
||||
with the difference that it also has a
|
||||
:meth:`~werkzeug.datastructures.FileStorage.save` function that can
|
||||
store the file on the filesystem.
|
||||
|
||||
Note that :attr:`files` will only contain data if the request method was
|
||||
POST, PUT or PATCH and the ``<form>`` that posted to the request had
|
||||
``enctype="multipart/form-data"``. It will be empty otherwise.
|
||||
|
||||
See the :class:`~werkzeug.datastructures.MultiDict` /
|
||||
:class:`~werkzeug.datastructures.FileStorage` documentation for
|
||||
more details about the used data structure.
|
||||
"""
|
||||
self._load_form_data()
|
||||
return self.files
|
||||
|
||||
@cached_property
|
||||
def cookies(self):
|
||||
"""A :class:`dict` with the contents of all cookies transmitted with
|
||||
the request."""
|
||||
return parse_cookie(
|
||||
self.environ,
|
||||
self.charset,
|
||||
self.encoding_errors,
|
||||
cls=self.dict_storage_class,
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def headers(self):
|
||||
"""The headers from the WSGI environ as immutable
|
||||
:class:`~werkzeug.datastructures.EnvironHeaders`.
|
||||
"""
|
||||
return EnvironHeaders(self.environ)
|
||||
|
||||
@cached_property
|
||||
def path(self):
|
||||
"""Requested path as unicode. This works a bit like the regular path
|
||||
info in the WSGI environment but will always include a leading slash,
|
||||
even if the URL root is accessed.
|
||||
"""
|
||||
raw_path = wsgi_decoding_dance(
|
||||
self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors
|
||||
)
|
||||
return "/" + raw_path.lstrip("/")
|
||||
|
||||
@cached_property
|
||||
def full_path(self):
|
||||
"""Requested path as unicode, including the query string."""
|
||||
return self.path + u"?" + to_unicode(self.query_string, self.url_charset)
|
||||
|
||||
@cached_property
|
||||
def script_root(self):
|
||||
"""The root path of the script without the trailing slash."""
|
||||
raw_path = wsgi_decoding_dance(
|
||||
self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors
|
||||
)
|
||||
return raw_path.rstrip("/")
|
||||
|
||||
@cached_property
|
||||
def url(self):
|
||||
"""The reconstructed current URL as IRI.
|
||||
See also: :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_current_url(self.environ, trusted_hosts=self.trusted_hosts)
|
||||
|
||||
@cached_property
|
||||
def base_url(self):
|
||||
"""Like :attr:`url` but without the querystring
|
||||
See also: :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_current_url(
|
||||
self.environ, strip_querystring=True, trusted_hosts=self.trusted_hosts
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def url_root(self):
|
||||
"""The full URL root (with hostname), this is the application
|
||||
root as IRI.
|
||||
See also: :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_current_url(self.environ, True, trusted_hosts=self.trusted_hosts)
|
||||
|
||||
@cached_property
|
||||
def host_url(self):
|
||||
"""Just the host with scheme as IRI.
|
||||
See also: :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_current_url(
|
||||
self.environ, host_only=True, trusted_hosts=self.trusted_hosts
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def host(self):
|
||||
"""Just the host including the port if available.
|
||||
See also: :attr:`trusted_hosts`.
|
||||
"""
|
||||
return get_host(self.environ, trusted_hosts=self.trusted_hosts)
|
||||
|
||||
query_string = environ_property(
|
||||
"QUERY_STRING",
|
||||
"",
|
||||
read_only=True,
|
||||
load_func=wsgi_get_bytes,
|
||||
doc="The URL parameters as raw bytestring.",
|
||||
)
|
||||
method = environ_property(
|
||||
"REQUEST_METHOD",
|
||||
"GET",
|
||||
read_only=True,
|
||||
load_func=lambda x: x.upper(),
|
||||
doc="The request method. (For example ``'GET'`` or ``'POST'``).",
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def access_route(self):
|
||||
"""If a forwarded header exists this is a list of all ip addresses
|
||||
from the client ip to the last proxy server.
|
||||
"""
|
||||
if "HTTP_X_FORWARDED_FOR" in self.environ:
|
||||
return self.list_storage_class(
|
||||
parse_list_header(self.environ["HTTP_X_FORWARDED_FOR"])
|
||||
)
|
||||
elif "REMOTE_ADDR" in self.environ:
|
||||
return self.list_storage_class([self.environ["REMOTE_ADDR"]])
|
||||
return self.list_storage_class()
|
||||
|
||||
@property
|
||||
def remote_addr(self):
|
||||
"""The remote address of the client."""
|
||||
return self.environ.get("REMOTE_ADDR")
|
||||
|
||||
remote_user = environ_property(
|
||||
"REMOTE_USER",
|
||||
doc="""If the server supports user authentication, and the
|
||||
script is protected, this attribute contains the username the
|
||||
user has authenticated as.""",
|
||||
)
|
||||
scheme = environ_property(
|
||||
"wsgi.url_scheme",
|
||||
doc="""
|
||||
URL scheme (http or https).
|
||||
|
||||
.. versionadded:: 0.7""",
|
||||
)
|
||||
is_secure = property(
|
||||
lambda self: self.environ["wsgi.url_scheme"] == "https",
|
||||
doc="`True` if the request is secure.",
|
||||
)
|
||||
is_multithread = environ_property(
|
||||
"wsgi.multithread",
|
||||
doc="""boolean that is `True` if the application is served by a
|
||||
multithreaded WSGI server.""",
|
||||
)
|
||||
is_multiprocess = environ_property(
|
||||
"wsgi.multiprocess",
|
||||
doc="""boolean that is `True` if the application is served by a
|
||||
WSGI server that spawns multiple processes.""",
|
||||
)
|
||||
is_run_once = environ_property(
|
||||
"wsgi.run_once",
|
||||
doc="""boolean that is `True` if the application will be
|
||||
executed only once in a process lifetime. This is the case for
|
||||
CGI for example, but it's not guaranteed that the execution only
|
||||
happens one time.""",
|
||||
)
|
||||
|
||||
|
||||
def _assert_not_shallow(request):
|
||||
if request.shallow:
|
||||
raise RuntimeError(
|
||||
"A shallow request tried to consume form data. If you really"
|
||||
" want to do that, set `shallow` to False."
|
||||
)
|
@@ -0,0 +1,700 @@
|
||||
import warnings
|
||||
|
||||
from .._compat import integer_types
|
||||
from .._compat import string_types
|
||||
from .._compat import text_type
|
||||
from .._compat import to_bytes
|
||||
from .._compat import to_native
|
||||
from ..datastructures import Headers
|
||||
from ..http import dump_cookie
|
||||
from ..http import HTTP_STATUS_CODES
|
||||
from ..http import remove_entity_headers
|
||||
from ..urls import iri_to_uri
|
||||
from ..urls import url_join
|
||||
from ..utils import get_content_type
|
||||
from ..wsgi import ClosingIterator
|
||||
from ..wsgi import get_current_url
|
||||
|
||||
|
||||
def _run_wsgi_app(*args):
|
||||
"""This function replaces itself to ensure that the test module is not
|
||||
imported unless required. DO NOT USE!
|
||||
"""
|
||||
global _run_wsgi_app
|
||||
from ..test import run_wsgi_app as _run_wsgi_app
|
||||
|
||||
return _run_wsgi_app(*args)
|
||||
|
||||
|
||||
def _warn_if_string(iterable):
|
||||
"""Helper for the response objects to check if the iterable returned
|
||||
to the WSGI server is not a string.
|
||||
"""
|
||||
if isinstance(iterable, string_types):
|
||||
warnings.warn(
|
||||
"Response iterable was set to a string. This will appear to"
|
||||
" work but means that the server will send the data to the"
|
||||
" client one character at a time. This is almost never"
|
||||
" intended behavior, use 'response.data' to assign strings"
|
||||
" to the response object.",
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
def _iter_encoded(iterable, charset):
|
||||
for item in iterable:
|
||||
if isinstance(item, text_type):
|
||||
yield item.encode(charset)
|
||||
else:
|
||||
yield item
|
||||
|
||||
|
||||
def _clean_accept_ranges(accept_ranges):
|
||||
if accept_ranges is True:
|
||||
return "bytes"
|
||||
elif accept_ranges is False:
|
||||
return "none"
|
||||
elif isinstance(accept_ranges, text_type):
|
||||
return to_native(accept_ranges)
|
||||
raise ValueError("Invalid accept_ranges value")
|
||||
|
||||
|
||||
class BaseResponse(object):
|
||||
"""Base response class. The most important fact about a response object
|
||||
is that it's a regular WSGI application. It's initialized with a couple
|
||||
of response parameters (headers, body, status code etc.) and will start a
|
||||
valid WSGI response when called with the environ and start response
|
||||
callable.
|
||||
|
||||
Because it's a WSGI application itself processing usually ends before the
|
||||
actual response is sent to the server. This helps debugging systems
|
||||
because they can catch all the exceptions before responses are started.
|
||||
|
||||
Here a small example WSGI application that takes advantage of the
|
||||
response objects::
|
||||
|
||||
from werkzeug.wrappers import BaseResponse as Response
|
||||
|
||||
def index():
|
||||
return Response('Index page')
|
||||
|
||||
def application(environ, start_response):
|
||||
path = environ.get('PATH_INFO') or '/'
|
||||
if path == '/':
|
||||
response = index()
|
||||
else:
|
||||
response = Response('Not Found', status=404)
|
||||
return response(environ, start_response)
|
||||
|
||||
Like :class:`BaseRequest` which object is lacking a lot of functionality
|
||||
implemented in mixins. This gives you a better control about the actual
|
||||
API of your response objects, so you can create subclasses and add custom
|
||||
functionality. A full featured response object is available as
|
||||
:class:`Response` which implements a couple of useful mixins.
|
||||
|
||||
To enforce a new type of already existing responses you can use the
|
||||
:meth:`force_type` method. This is useful if you're working with different
|
||||
subclasses of response objects and you want to post process them with a
|
||||
known interface.
|
||||
|
||||
Per default the response object will assume all the text data is `utf-8`
|
||||
encoded. Please refer to :doc:`the unicode chapter </unicode>` for more
|
||||
details about customizing the behavior.
|
||||
|
||||
Response can be any kind of iterable or string. If it's a string it's
|
||||
considered being an iterable with one item which is the string passed.
|
||||
Headers can be a list of tuples or a
|
||||
:class:`~werkzeug.datastructures.Headers` object.
|
||||
|
||||
Special note for `mimetype` and `content_type`: For most mime types
|
||||
`mimetype` and `content_type` work the same, the difference affects
|
||||
only 'text' mimetypes. If the mimetype passed with `mimetype` is a
|
||||
mimetype starting with `text/`, the charset parameter of the response
|
||||
object is appended to it. In contrast the `content_type` parameter is
|
||||
always added as header unmodified.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
the `direct_passthrough` parameter was added.
|
||||
|
||||
:param response: a string or response iterable.
|
||||
:param status: a string with a status or an integer with the status code.
|
||||
:param headers: a list of headers or a
|
||||
:class:`~werkzeug.datastructures.Headers` object.
|
||||
:param mimetype: the mimetype for the response. See notice above.
|
||||
:param content_type: the content type for the response. See notice above.
|
||||
:param direct_passthrough: if set to `True` :meth:`iter_encoded` is not
|
||||
called before iteration which makes it
|
||||
possible to pass special iterators through
|
||||
unchanged (see :func:`wrap_file` for more
|
||||
details.)
|
||||
"""
|
||||
|
||||
#: 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"
|
||||
|
||||
#: if set to `False` accessing properties on the response object will
|
||||
#: not try to consume the response iterator and convert it into a list.
|
||||
#:
|
||||
#: .. versionadded:: 0.6.2
|
||||
#:
|
||||
#: That attribute was previously called `implicit_seqence_conversion`.
|
||||
#: (Notice the typo). If you did use this feature, you have to adapt
|
||||
#: your code to the name change.
|
||||
implicit_sequence_conversion = True
|
||||
|
||||
#: Should this response object correct the location header to be RFC
|
||||
#: conformant? This is true by default.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
autocorrect_location_header = True
|
||||
|
||||
#: Should this response object automatically set the content-length
|
||||
#: header if possible? This is true by default.
|
||||
#:
|
||||
#: .. versionadded:: 0.8
|
||||
automatically_set_content_length = True
|
||||
|
||||
#: 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
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
response=None,
|
||||
status=None,
|
||||
headers=None,
|
||||
mimetype=None,
|
||||
content_type=None,
|
||||
direct_passthrough=False,
|
||||
):
|
||||
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
|
||||
if isinstance(status, integer_types):
|
||||
self.status_code = status
|
||||
else:
|
||||
self.status = status
|
||||
|
||||
self.direct_passthrough = direct_passthrough
|
||||
self._on_close = []
|
||||
|
||||
# we set the response after the headers so that if a class changes
|
||||
# the charset attribute, the data is set in the correct charset.
|
||||
if response is None:
|
||||
self.response = []
|
||||
elif isinstance(response, (text_type, bytes, bytearray)):
|
||||
self.set_data(response)
|
||||
else:
|
||||
self.response = response
|
||||
|
||||
def call_on_close(self, func):
|
||||
"""Adds a function to the internal list of functions that should
|
||||
be called as part of closing down the response. Since 0.7 this
|
||||
function also returns the function that was passed so that this
|
||||
can be used as a decorator.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
self._on_close.append(func)
|
||||
return func
|
||||
|
||||
def __repr__(self):
|
||||
if self.is_sequence:
|
||||
body_info = "%d bytes" % sum(map(len, self.iter_encoded()))
|
||||
else:
|
||||
body_info = "streamed" if self.is_streamed else "likely-streamed"
|
||||
return "<%s %s [%s]>" % (self.__class__.__name__, body_info, self.status)
|
||||
|
||||
@classmethod
|
||||
def force_type(cls, response, environ=None):
|
||||
"""Enforce that the WSGI response is a response object of the current
|
||||
type. Werkzeug will use the :class:`BaseResponse` internally in many
|
||||
situations like the exceptions. If you call :meth:`get_response` on an
|
||||
exception you will get back a regular :class:`BaseResponse` object, even
|
||||
if you are using a custom subclass.
|
||||
|
||||
This method can enforce a given response type, and it will also
|
||||
convert arbitrary WSGI callables into response objects if an environ
|
||||
is provided::
|
||||
|
||||
# convert a Werkzeug response object into an instance of the
|
||||
# MyResponseClass subclass.
|
||||
response = MyResponseClass.force_type(response)
|
||||
|
||||
# convert any WSGI application into a response object
|
||||
response = MyResponseClass.force_type(response, environ)
|
||||
|
||||
This is especially useful if you want to post-process responses in
|
||||
the main dispatcher and use functionality provided by your subclass.
|
||||
|
||||
Keep in mind that this will modify response objects in place if
|
||||
possible!
|
||||
|
||||
:param response: a response object or wsgi application.
|
||||
:param environ: a WSGI environment object.
|
||||
:return: a response object.
|
||||
"""
|
||||
if not isinstance(response, BaseResponse):
|
||||
if environ is None:
|
||||
raise TypeError(
|
||||
"cannot convert WSGI application into response"
|
||||
" objects without an environ"
|
||||
)
|
||||
response = BaseResponse(*_run_wsgi_app(response, environ))
|
||||
response.__class__ = cls
|
||||
return response
|
||||
|
||||
@classmethod
|
||||
def from_app(cls, app, environ, buffered=False):
|
||||
"""Create a new response object from an application output. This
|
||||
works best if you pass it an application that returns a generator all
|
||||
the time. Sometimes applications may use the `write()` callable
|
||||
returned by the `start_response` function. This tries to resolve such
|
||||
edge cases automatically. But if you don't get the expected output
|
||||
you should set `buffered` to `True` which enforces buffering.
|
||||
|
||||
:param app: the WSGI application to execute.
|
||||
:param environ: the WSGI environment to execute against.
|
||||
:param buffered: set to `True` to enforce buffering.
|
||||
:return: a response object.
|
||||
"""
|
||||
return cls(*_run_wsgi_app(app, environ, buffered))
|
||||
|
||||
@property
|
||||
def status_code(self):
|
||||
"""The HTTP status code as a number."""
|
||||
return self._status_code
|
||||
|
||||
@status_code.setter
|
||||
def status_code(self, code):
|
||||
self._status_code = code
|
||||
try:
|
||||
self._status = "%d %s" % (code, HTTP_STATUS_CODES[code].upper())
|
||||
except KeyError:
|
||||
self._status = "%d UNKNOWN" % code
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
"""The HTTP status code as a string."""
|
||||
return self._status
|
||||
|
||||
@status.setter
|
||||
def status(self, value):
|
||||
try:
|
||||
self._status = to_native(value)
|
||||
except AttributeError:
|
||||
raise TypeError("Invalid status argument")
|
||||
|
||||
try:
|
||||
self._status_code = int(self._status.split(None, 1)[0])
|
||||
except ValueError:
|
||||
self._status_code = 0
|
||||
self._status = "0 %s" % self._status
|
||||
except IndexError:
|
||||
raise ValueError("Empty status argument")
|
||||
|
||||
def get_data(self, as_text=False):
|
||||
"""The string representation of the request body. Whenever you call
|
||||
this property the request iterable is encoded and flattened. This
|
||||
can lead to unwanted behavior if you stream big data.
|
||||
|
||||
This behavior can be disabled by setting
|
||||
:attr:`implicit_sequence_conversion` to `False`.
|
||||
|
||||
If `as_text` is set to `True` the return value will be a decoded
|
||||
unicode string.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
self._ensure_sequence()
|
||||
rv = b"".join(self.iter_encoded())
|
||||
if as_text:
|
||||
rv = rv.decode(self.charset)
|
||||
return rv
|
||||
|
||||
def set_data(self, value):
|
||||
"""Sets a new string as response. The value set must be either a
|
||||
unicode or bytestring. If a unicode string is set it's encoded
|
||||
automatically to the charset of the response (utf-8 by default).
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
# if an unicode string is set, it's encoded directly so that we
|
||||
# can set the content length
|
||||
if isinstance(value, text_type):
|
||||
value = value.encode(self.charset)
|
||||
else:
|
||||
value = bytes(value)
|
||||
self.response = [value]
|
||||
if self.automatically_set_content_length:
|
||||
self.headers["Content-Length"] = str(len(value))
|
||||
|
||||
data = property(
|
||||
get_data,
|
||||
set_data,
|
||||
doc="A descriptor that calls :meth:`get_data` and :meth:`set_data`.",
|
||||
)
|
||||
|
||||
def calculate_content_length(self):
|
||||
"""Returns the content length if available or `None` otherwise."""
|
||||
try:
|
||||
self._ensure_sequence()
|
||||
except RuntimeError:
|
||||
return None
|
||||
return sum(len(x) for x in self.iter_encoded())
|
||||
|
||||
def _ensure_sequence(self, mutable=False):
|
||||
"""This method can be called by methods that need a sequence. If
|
||||
`mutable` is true, it will also ensure that the response sequence
|
||||
is a standard Python list.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
if self.is_sequence:
|
||||
# if we need a mutable object, we ensure it's a list.
|
||||
if mutable and not isinstance(self.response, list):
|
||||
self.response = list(self.response)
|
||||
return
|
||||
if self.direct_passthrough:
|
||||
raise RuntimeError(
|
||||
"Attempted implicit sequence conversion but the"
|
||||
" response object is in direct passthrough mode."
|
||||
)
|
||||
if not self.implicit_sequence_conversion:
|
||||
raise RuntimeError(
|
||||
"The response object required the iterable to be a"
|
||||
" sequence, but the implicit conversion was disabled."
|
||||
" Call make_sequence() yourself."
|
||||
)
|
||||
self.make_sequence()
|
||||
|
||||
def make_sequence(self):
|
||||
"""Converts the response iterator in a list. By default this happens
|
||||
automatically if required. If `implicit_sequence_conversion` is
|
||||
disabled, this method is not automatically called and some properties
|
||||
might raise exceptions. This also encodes all the items.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
if not self.is_sequence:
|
||||
# if we consume an iterable we have to ensure that the close
|
||||
# method of the iterable is called if available when we tear
|
||||
# down the response
|
||||
close = getattr(self.response, "close", None)
|
||||
self.response = list(self.iter_encoded())
|
||||
if close is not None:
|
||||
self.call_on_close(close)
|
||||
|
||||
def iter_encoded(self):
|
||||
"""Iter the response encoded with the encoding of the response.
|
||||
If the response object is invoked as WSGI application the return
|
||||
value of this method is used as application iterator unless
|
||||
:attr:`direct_passthrough` was activated.
|
||||
"""
|
||||
if __debug__:
|
||||
_warn_if_string(self.response)
|
||||
# Encode in a separate function so that self.response is fetched
|
||||
# early. This allows us to wrap the response with the return
|
||||
# value from get_app_iter or iter_encoded.
|
||||
return _iter_encoded(self.response, self.charset)
|
||||
|
||||
def set_cookie(
|
||||
self,
|
||||
key,
|
||||
value="",
|
||||
max_age=None,
|
||||
expires=None,
|
||||
path="/",
|
||||
domain=None,
|
||||
secure=False,
|
||||
httponly=False,
|
||||
samesite=None,
|
||||
):
|
||||
"""Sets a cookie. The parameters are the same as in the cookie `Morsel`
|
||||
object in the Python standard library but it accepts unicode data, too.
|
||||
|
||||
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 to access the cookie. This is an
|
||||
extension to the cookie standard and probably not
|
||||
supported by all browsers.
|
||||
:param samesite: Limits the scope of the cookie such that it will only
|
||||
be attached to requests if those requests 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, path="/", domain=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.
|
||||
"""
|
||||
self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
|
||||
|
||||
@property
|
||||
def is_streamed(self):
|
||||
"""If the response is streamed (the response is not an iterable with
|
||||
a length information) this property is `True`. In this case streamed
|
||||
means that there is no information about the number of iterations.
|
||||
This is usually `True` if a generator is passed to the response object.
|
||||
|
||||
This is useful for checking before applying some sort of post
|
||||
filtering that should not take place for streamed responses.
|
||||
"""
|
||||
try:
|
||||
len(self.response)
|
||||
except (TypeError, AttributeError):
|
||||
return True
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_sequence(self):
|
||||
"""If the iterator is buffered, this property will be `True`. A
|
||||
response object will consider an iterator to be buffered if the
|
||||
response attribute is a list or tuple.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
return isinstance(self.response, (tuple, list))
|
||||
|
||||
def close(self):
|
||||
"""Close the wrapped response if possible. You can also use the object
|
||||
in a with statement which will automatically close it.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
Can now be used in a with statement.
|
||||
"""
|
||||
if hasattr(self.response, "close"):
|
||||
self.response.close()
|
||||
for func in self._on_close:
|
||||
func()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.close()
|
||||
|
||||
def freeze(self):
|
||||
"""Call this method if you want to make your response object ready for
|
||||
being pickled. This buffers the generator if there is one. It will
|
||||
also set the `Content-Length` header to the length of the body.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
The `Content-Length` header is now set.
|
||||
"""
|
||||
# we explicitly set the length to a list of the *encoded* response
|
||||
# iterator. Even if the implicit sequence conversion is disabled.
|
||||
self.response = list(self.iter_encoded())
|
||||
self.headers["Content-Length"] = str(sum(map(len, self.response)))
|
||||
|
||||
def get_wsgi_headers(self, environ):
|
||||
"""This is automatically called right before the response is started
|
||||
and returns headers modified for the given environment. It returns a
|
||||
copy of the headers from the response with some modifications applied
|
||||
if necessary.
|
||||
|
||||
For example the location header (if present) is joined with the root
|
||||
URL of the environment. Also the content length is automatically set
|
||||
to zero here for certain status codes.
|
||||
|
||||
.. versionchanged:: 0.6
|
||||
Previously that function was called `fix_headers` and modified
|
||||
the response object in place. Also since 0.6, IRIs in location
|
||||
and content-location headers are handled properly.
|
||||
|
||||
Also starting with 0.6, Werkzeug will attempt to set the content
|
||||
length if it is able to figure it out on its own. This is the
|
||||
case if all the strings in the response iterable are already
|
||||
encoded and the iterable is buffered.
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: returns a new :class:`~werkzeug.datastructures.Headers`
|
||||
object.
|
||||
"""
|
||||
headers = Headers(self.headers)
|
||||
location = None
|
||||
content_location = None
|
||||
content_length = None
|
||||
status = self.status_code
|
||||
|
||||
# iterate over the headers to find all values in one go. Because
|
||||
# get_wsgi_headers is used each response that gives us a tiny
|
||||
# speedup.
|
||||
for key, value in headers:
|
||||
ikey = key.lower()
|
||||
if ikey == u"location":
|
||||
location = value
|
||||
elif ikey == u"content-location":
|
||||
content_location = value
|
||||
elif ikey == u"content-length":
|
||||
content_length = value
|
||||
|
||||
# make sure the location header is an absolute URL
|
||||
if location is not None:
|
||||
old_location = location
|
||||
if isinstance(location, text_type):
|
||||
# Safe conversion is necessary here as we might redirect
|
||||
# to a broken URI scheme (for instance itms-services).
|
||||
location = iri_to_uri(location, safe_conversion=True)
|
||||
|
||||
if self.autocorrect_location_header:
|
||||
current_url = get_current_url(environ, strip_querystring=True)
|
||||
if isinstance(current_url, text_type):
|
||||
current_url = iri_to_uri(current_url)
|
||||
location = url_join(current_url, location)
|
||||
if location != old_location:
|
||||
headers["Location"] = location
|
||||
|
||||
# make sure the content location is a URL
|
||||
if content_location is not None and isinstance(content_location, text_type):
|
||||
headers["Content-Location"] = iri_to_uri(content_location)
|
||||
|
||||
if 100 <= status < 200 or status == 204:
|
||||
# Per section 3.3.2 of RFC 7230, "a server MUST NOT send a
|
||||
# Content-Length header field in any response with a status
|
||||
# code of 1xx (Informational) or 204 (No Content)."
|
||||
headers.remove("Content-Length")
|
||||
elif status == 304:
|
||||
remove_entity_headers(headers)
|
||||
|
||||
# if we can determine the content length automatically, we
|
||||
# should try to do that. But only if this does not involve
|
||||
# flattening the iterator or encoding of unicode strings in
|
||||
# the response. We however should not do that if we have a 304
|
||||
# response.
|
||||
if (
|
||||
self.automatically_set_content_length
|
||||
and self.is_sequence
|
||||
and content_length is None
|
||||
and status not in (204, 304)
|
||||
and not (100 <= status < 200)
|
||||
):
|
||||
try:
|
||||
content_length = sum(len(to_bytes(x, "ascii")) for x in self.response)
|
||||
except UnicodeError:
|
||||
# aha, something non-bytestringy in there, too bad, we
|
||||
# can't safely figure out the length of the response.
|
||||
pass
|
||||
else:
|
||||
headers["Content-Length"] = str(content_length)
|
||||
|
||||
return headers
|
||||
|
||||
def get_app_iter(self, environ):
|
||||
"""Returns the application iterator for the given environ. Depending
|
||||
on the request method and the current status code the return value
|
||||
might be an empty response rather than the one from the response.
|
||||
|
||||
If the request method is `HEAD` or the status code is in a range
|
||||
where the HTTP specification requires an empty response, an empty
|
||||
iterable is returned.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: a response iterable.
|
||||
"""
|
||||
status = self.status_code
|
||||
if (
|
||||
environ["REQUEST_METHOD"] == "HEAD"
|
||||
or 100 <= status < 200
|
||||
or status in (204, 304)
|
||||
):
|
||||
iterable = ()
|
||||
elif self.direct_passthrough:
|
||||
if __debug__:
|
||||
_warn_if_string(self.response)
|
||||
return self.response
|
||||
else:
|
||||
iterable = self.iter_encoded()
|
||||
return ClosingIterator(iterable, self.close)
|
||||
|
||||
def get_wsgi_response(self, environ):
|
||||
"""Returns the final WSGI response as tuple. The first item in
|
||||
the tuple is the application iterator, the second the status and
|
||||
the third the list of headers. The response returned is created
|
||||
specially for the given environment. For example if the request
|
||||
method in the WSGI environment is ``'HEAD'`` the response will
|
||||
be empty and only the headers and status code will be present.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: an ``(app_iter, status, headers)`` tuple.
|
||||
"""
|
||||
headers = self.get_wsgi_headers(environ)
|
||||
app_iter = self.get_app_iter(environ)
|
||||
return app_iter, self.status, headers.to_wsgi_list()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Process this response as WSGI application.
|
||||
|
||||
:param environ: the WSGI environment.
|
||||
:param start_response: the response callable provided by the WSGI
|
||||
server.
|
||||
:return: an application iterator
|
||||
"""
|
||||
app_iter, status, headers = self.get_wsgi_response(environ)
|
||||
start_response(status, headers)
|
||||
return app_iter
|
@@ -0,0 +1,341 @@
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from .._compat import string_types
|
||||
from ..datastructures import CallbackDict
|
||||
from ..http import dump_age
|
||||
from ..http import dump_csp_header
|
||||
from ..http import dump_header
|
||||
from ..http import dump_options_header
|
||||
from ..http import http_date
|
||||
from ..http import parse_age
|
||||
from ..http import parse_csp_header
|
||||
from ..http import parse_date
|
||||
from ..http import parse_options_header
|
||||
from ..http import parse_set_header
|
||||
from ..utils import cached_property
|
||||
from ..utils import environ_property
|
||||
from ..utils import get_content_type
|
||||
from ..utils import header_property
|
||||
from ..wsgi import get_content_length
|
||||
|
||||
|
||||
class CommonRequestDescriptorsMixin(object):
|
||||
"""A mixin for :class:`BaseRequest` subclasses. Request objects that
|
||||
mix this class in will automatically get descriptors for a couple of
|
||||
HTTP headers with automatic type conversion.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
|
||||
content_type = environ_property(
|
||||
"CONTENT_TYPE",
|
||||
doc="""The Content-Type entity-header field indicates the media
|
||||
type of the entity-body sent to the recipient or, in the case of
|
||||
the HEAD method, the media type that would have been sent had
|
||||
the request been a GET.""",
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def content_length(self):
|
||||
"""The Content-Length entity-header field indicates the size of the
|
||||
entity-body in bytes or, in the case of the HEAD method, the size of
|
||||
the entity-body that would have been sent had the request been a
|
||||
GET.
|
||||
"""
|
||||
return get_content_length(self.environ)
|
||||
|
||||
content_encoding = environ_property(
|
||||
"HTTP_CONTENT_ENCODING",
|
||||
doc="""The Content-Encoding entity-header field is used as a
|
||||
modifier to the media-type. When present, its value indicates
|
||||
what additional content codings have been applied to the
|
||||
entity-body, and thus what decoding mechanisms must be applied
|
||||
in order to obtain the media-type referenced by the Content-Type
|
||||
header field.
|
||||
|
||||
.. versionadded:: 0.9""",
|
||||
)
|
||||
content_md5 = environ_property(
|
||||
"HTTP_CONTENT_MD5",
|
||||
doc="""The Content-MD5 entity-header field, as defined in
|
||||
RFC 1864, is an MD5 digest of the entity-body for the purpose of
|
||||
providing an end-to-end message integrity check (MIC) of the
|
||||
entity-body. (Note: a MIC is good for detecting accidental
|
||||
modification of the entity-body in transit, but is not proof
|
||||
against malicious attacks.)
|
||||
|
||||
.. versionadded:: 0.9""",
|
||||
)
|
||||
referrer = environ_property(
|
||||
"HTTP_REFERER",
|
||||
doc="""The Referer[sic] request-header field allows the client
|
||||
to specify, for the server's benefit, the address (URI) of the
|
||||
resource from which the Request-URI was obtained (the
|
||||
"referrer", although the header field is misspelled).""",
|
||||
)
|
||||
date = environ_property(
|
||||
"HTTP_DATE",
|
||||
None,
|
||||
parse_date,
|
||||
doc="""The Date general-header field represents the date and
|
||||
time at which the message was originated, having the same
|
||||
semantics as orig-date in RFC 822.""",
|
||||
)
|
||||
max_forwards = environ_property(
|
||||
"HTTP_MAX_FORWARDS",
|
||||
None,
|
||||
int,
|
||||
doc="""The Max-Forwards request-header field provides a
|
||||
mechanism with the TRACE and OPTIONS methods to limit the number
|
||||
of proxies or gateways that can forward the request to the next
|
||||
inbound server.""",
|
||||
)
|
||||
|
||||
def _parse_content_type(self):
|
||||
if not hasattr(self, "_parsed_content_type"):
|
||||
self._parsed_content_type = parse_options_header(
|
||||
self.environ.get("CONTENT_TYPE", "")
|
||||
)
|
||||
|
||||
@property
|
||||
def mimetype(self):
|
||||
"""Like :attr:`content_type`, but without parameters (eg, without
|
||||
charset, type etc.) and always lowercase. For example if the content
|
||||
type is ``text/HTML; charset=utf-8`` the mimetype would be
|
||||
``'text/html'``.
|
||||
"""
|
||||
self._parse_content_type()
|
||||
return self._parsed_content_type[0].lower()
|
||||
|
||||
@property
|
||||
def mimetype_params(self):
|
||||
"""The mimetype parameters as dict. For example if the content
|
||||
type is ``text/html; charset=utf-8`` the params would be
|
||||
``{'charset': 'utf-8'}``.
|
||||
"""
|
||||
self._parse_content_type()
|
||||
return self._parsed_content_type[1]
|
||||
|
||||
@cached_property
|
||||
def pragma(self):
|
||||
"""The Pragma general-header field is used to include
|
||||
implementation-specific directives that might apply to any recipient
|
||||
along the request/response chain. All pragma directives specify
|
||||
optional behavior from the viewpoint of the protocol; however, some
|
||||
systems MAY require that behavior be consistent with the directives.
|
||||
"""
|
||||
return parse_set_header(self.environ.get("HTTP_PRAGMA", ""))
|
||||
|
||||
|
||||
class CommonResponseDescriptorsMixin(object):
|
||||
"""A mixin for :class:`BaseResponse` subclasses. Response objects that
|
||||
mix this class in will automatically get descriptors for a couple of
|
||||
HTTP headers with automatic type conversion.
|
||||
"""
|
||||
|
||||
@property
|
||||
def mimetype(self):
|
||||
"""The mimetype (content type without charset etc.)"""
|
||||
ct = self.headers.get("content-type")
|
||||
if ct:
|
||||
return ct.split(";")[0].strip()
|
||||
|
||||
@mimetype.setter
|
||||
def mimetype(self, value):
|
||||
self.headers["Content-Type"] = get_content_type(value, self.charset)
|
||||
|
||||
@property
|
||||
def mimetype_params(self):
|
||||
"""The mimetype parameters as dict. For example if the
|
||||
content type is ``text/html; charset=utf-8`` the params would be
|
||||
``{'charset': 'utf-8'}``.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
|
||||
def on_update(d):
|
||||
self.headers["Content-Type"] = dump_options_header(self.mimetype, d)
|
||||
|
||||
d = parse_options_header(self.headers.get("content-type", ""))[1]
|
||||
return CallbackDict(d, on_update)
|
||||
|
||||
location = header_property(
|
||||
"Location",
|
||||
doc="""The Location response-header field is used to redirect
|
||||
the recipient to a location other than the Request-URI for
|
||||
completion of the request or identification of a new
|
||||
resource.""",
|
||||
)
|
||||
age = header_property(
|
||||
"Age",
|
||||
None,
|
||||
parse_age,
|
||||
dump_age,
|
||||
doc="""The Age response-header field conveys the sender's
|
||||
estimate of the amount of time since the response (or its
|
||||
revalidation) was generated at the origin server.
|
||||
|
||||
Age values are non-negative decimal integers, representing time
|
||||
in seconds.""",
|
||||
)
|
||||
content_type = header_property(
|
||||
"Content-Type",
|
||||
doc="""The Content-Type entity-header field indicates the media
|
||||
type of the entity-body sent to the recipient or, in the case of
|
||||
the HEAD method, the media type that would have been sent had
|
||||
the request been a GET.""",
|
||||
)
|
||||
content_length = header_property(
|
||||
"Content-Length",
|
||||
None,
|
||||
int,
|
||||
str,
|
||||
doc="""The Content-Length entity-header field indicates the size
|
||||
of the entity-body, in decimal number of OCTETs, sent to the
|
||||
recipient or, in the case of the HEAD method, the size of the
|
||||
entity-body that would have been sent had the request been a
|
||||
GET.""",
|
||||
)
|
||||
content_location = header_property(
|
||||
"Content-Location",
|
||||
doc="""The Content-Location entity-header field MAY be used to
|
||||
supply the resource location for the entity enclosed in the
|
||||
message when that entity is accessible from a location separate
|
||||
from the requested resource's URI.""",
|
||||
)
|
||||
content_encoding = header_property(
|
||||
"Content-Encoding",
|
||||
doc="""The Content-Encoding entity-header field is used as a
|
||||
modifier to the media-type. When present, its value indicates
|
||||
what additional content codings have been applied to the
|
||||
entity-body, and thus what decoding mechanisms must be applied
|
||||
in order to obtain the media-type referenced by the Content-Type
|
||||
header field.""",
|
||||
)
|
||||
content_md5 = header_property(
|
||||
"Content-MD5",
|
||||
doc="""The Content-MD5 entity-header field, as defined in
|
||||
RFC 1864, is an MD5 digest of the entity-body for the purpose of
|
||||
providing an end-to-end message integrity check (MIC) of the
|
||||
entity-body. (Note: a MIC is good for detecting accidental
|
||||
modification of the entity-body in transit, but is not proof
|
||||
against malicious attacks.)""",
|
||||
)
|
||||
content_security_policy = header_property(
|
||||
"Content-Security-Policy",
|
||||
None,
|
||||
parse_csp_header,
|
||||
dump_csp_header,
|
||||
doc="""The Content-Security-Policy header adds an additional layer of
|
||||
security to help detect and mitigate certain types of attacks.""",
|
||||
)
|
||||
content_security_policy_report_only = header_property(
|
||||
"Content-Security-Policy-Report-Only",
|
||||
None,
|
||||
parse_csp_header,
|
||||
dump_csp_header,
|
||||
doc="""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.""",
|
||||
)
|
||||
date = header_property(
|
||||
"Date",
|
||||
None,
|
||||
parse_date,
|
||||
http_date,
|
||||
doc="""The Date general-header field represents the date and
|
||||
time at which the message was originated, having the same
|
||||
semantics as orig-date in RFC 822.""",
|
||||
)
|
||||
expires = header_property(
|
||||
"Expires",
|
||||
None,
|
||||
parse_date,
|
||||
http_date,
|
||||
doc="""The Expires entity-header field gives the date/time after
|
||||
which the response is considered stale. A stale cache entry may
|
||||
not normally be returned by a cache.""",
|
||||
)
|
||||
last_modified = header_property(
|
||||
"Last-Modified",
|
||||
None,
|
||||
parse_date,
|
||||
http_date,
|
||||
doc="""The Last-Modified entity-header field indicates the date
|
||||
and time at which the origin server believes the variant was
|
||||
last modified.""",
|
||||
)
|
||||
|
||||
@property
|
||||
def retry_after(self):
|
||||
"""The Retry-After response-header field can be used with a
|
||||
503 (Service Unavailable) response to indicate how long the
|
||||
service is expected to be unavailable to the requesting client.
|
||||
|
||||
Time in seconds until expiration or date.
|
||||
"""
|
||||
value = self.headers.get("retry-after")
|
||||
if value is None:
|
||||
return
|
||||
elif value.isdigit():
|
||||
return datetime.utcnow() + timedelta(seconds=int(value))
|
||||
return parse_date(value)
|
||||
|
||||
@retry_after.setter
|
||||
def retry_after(self, value):
|
||||
if value is None:
|
||||
if "retry-after" in self.headers:
|
||||
del self.headers["retry-after"]
|
||||
return
|
||||
elif isinstance(value, datetime):
|
||||
value = http_date(value)
|
||||
else:
|
||||
value = str(value)
|
||||
self.headers["Retry-After"] = value
|
||||
|
||||
def _set_property(name, doc=None): # noqa: B902
|
||||
def fget(self):
|
||||
def on_update(header_set):
|
||||
if not header_set and name in self.headers:
|
||||
del self.headers[name]
|
||||
elif header_set:
|
||||
self.headers[name] = header_set.to_header()
|
||||
|
||||
return parse_set_header(self.headers.get(name), on_update)
|
||||
|
||||
def fset(self, value):
|
||||
if not value:
|
||||
del self.headers[name]
|
||||
elif isinstance(value, string_types):
|
||||
self.headers[name] = value
|
||||
else:
|
||||
self.headers[name] = dump_header(value)
|
||||
|
||||
return property(fget, fset, doc=doc)
|
||||
|
||||
vary = _set_property(
|
||||
"Vary",
|
||||
doc="""The Vary field value indicates the set of request-header
|
||||
fields that fully determines, while the response is fresh,
|
||||
whether a cache is permitted to use the response to reply to a
|
||||
subsequent request without revalidation.""",
|
||||
)
|
||||
content_language = _set_property(
|
||||
"Content-Language",
|
||||
doc="""The Content-Language entity-header field describes the
|
||||
natural language(s) of the intended audience for the enclosed
|
||||
entity. Note that this might not be equivalent to all the
|
||||
languages used within the entity-body.""",
|
||||
)
|
||||
allow = _set_property(
|
||||
"Allow",
|
||||
doc="""The Allow entity-header field lists the set of methods
|
||||
supported by the resource identified by the Request-URI. The
|
||||
purpose of this field is strictly to inform the recipient of
|
||||
valid methods associated with the resource. An Allow header
|
||||
field MUST be present in a 405 (Method Not Allowed)
|
||||
response.""",
|
||||
)
|
||||
|
||||
del _set_property
|
102
venv/lib/python3.7/site-packages/werkzeug/wrappers/cors.py
Normal file
102
venv/lib/python3.7/site-packages/werkzeug/wrappers/cors.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from ..http import dump_header
|
||||
from ..http import parse_set_header
|
||||
from ..utils import environ_property
|
||||
from ..utils import header_property
|
||||
|
||||
|
||||
class CORSRequestMixin(object):
|
||||
"""A mixin for :class:`~werkzeug.wrappers.BaseRequest` subclasses
|
||||
that adds descriptors for Cross Origin Resource Sharing (CORS)
|
||||
headers.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
origin = environ_property(
|
||||
"HTTP_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."
|
||||
),
|
||||
)
|
||||
|
||||
access_control_request_headers = environ_property(
|
||||
"HTTP_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."
|
||||
),
|
||||
)
|
||||
|
||||
access_control_request_method = environ_property(
|
||||
"HTTP_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."
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CORSResponseMixin(object):
|
||||
"""A mixin for :class:`~werkzeug.wrappers.BaseResponse` subclasses
|
||||
that adds descriptors for Cross Origin Resource Sharing (CORS)
|
||||
headers.
|
||||
|
||||
.. versionadded:: 1.0
|
||||
"""
|
||||
|
||||
@property
|
||||
def access_control_allow_credentials(self):
|
||||
"""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):
|
||||
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(
|
||||
"Access-Control-Allow-Origin",
|
||||
load_func=parse_set_header,
|
||||
dump_func=dump_header,
|
||||
doc="The origins 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.",
|
||||
)
|
304
venv/lib/python3.7/site-packages/werkzeug/wrappers/etag.py
Normal file
304
venv/lib/python3.7/site-packages/werkzeug/wrappers/etag.py
Normal file
@@ -0,0 +1,304 @@
|
||||
from .._compat import string_types
|
||||
from .._internal import _get_environ
|
||||
from ..datastructures import ContentRange
|
||||
from ..datastructures import RequestCacheControl
|
||||
from ..datastructures import ResponseCacheControl
|
||||
from ..http import generate_etag
|
||||
from ..http import http_date
|
||||
from ..http import is_resource_modified
|
||||
from ..http import parse_cache_control_header
|
||||
from ..http import parse_content_range_header
|
||||
from ..http import parse_date
|
||||
from ..http import parse_etags
|
||||
from ..http import parse_if_range_header
|
||||
from ..http import parse_range_header
|
||||
from ..http import quote_etag
|
||||
from ..http import unquote_etag
|
||||
from ..utils import cached_property
|
||||
from ..utils import header_property
|
||||
from ..wrappers.base_response import _clean_accept_ranges
|
||||
from ..wsgi import _RangeWrapper
|
||||
|
||||
|
||||
class ETagRequestMixin(object):
|
||||
"""Add entity tag and cache descriptors to a request object or object with
|
||||
a WSGI environment available as :attr:`~BaseRequest.environ`. This not
|
||||
only provides access to etags but also to the cache control header.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def cache_control(self):
|
||||
"""A :class:`~werkzeug.datastructures.RequestCacheControl` object
|
||||
for the incoming cache control headers.
|
||||
"""
|
||||
cache_control = self.environ.get("HTTP_CACHE_CONTROL")
|
||||
return parse_cache_control_header(cache_control, None, RequestCacheControl)
|
||||
|
||||
@cached_property
|
||||
def if_match(self):
|
||||
"""An object containing all the etags in the `If-Match` header.
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.environ.get("HTTP_IF_MATCH"))
|
||||
|
||||
@cached_property
|
||||
def if_none_match(self):
|
||||
"""An object containing all the etags in the `If-None-Match` header.
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.environ.get("HTTP_IF_NONE_MATCH"))
|
||||
|
||||
@cached_property
|
||||
def if_modified_since(self):
|
||||
"""The parsed `If-Modified-Since` header as datetime object."""
|
||||
return parse_date(self.environ.get("HTTP_IF_MODIFIED_SINCE"))
|
||||
|
||||
@cached_property
|
||||
def if_unmodified_since(self):
|
||||
"""The parsed `If-Unmodified-Since` header as datetime object."""
|
||||
return parse_date(self.environ.get("HTTP_IF_UNMODIFIED_SINCE"))
|
||||
|
||||
@cached_property
|
||||
def if_range(self):
|
||||
"""The parsed `If-Range` header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.IfRange`
|
||||
"""
|
||||
return parse_if_range_header(self.environ.get("HTTP_IF_RANGE"))
|
||||
|
||||
@cached_property
|
||||
def range(self):
|
||||
"""The parsed `Range` header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.Range`
|
||||
"""
|
||||
return parse_range_header(self.environ.get("HTTP_RANGE"))
|
||||
|
||||
|
||||
class ETagResponseMixin(object):
|
||||
"""Adds extra functionality to a response object for etag and cache
|
||||
handling. This mixin requires an object with at least a `headers`
|
||||
object that implements a dict like interface similar to
|
||||
:class:`~werkzeug.datastructures.Headers`.
|
||||
|
||||
If you want the :meth:`freeze` method to automatically add an etag, you
|
||||
have to mixin this method before the response base class. The default
|
||||
response class does not do that.
|
||||
"""
|
||||
|
||||
@property
|
||||
def cache_control(self):
|
||||
"""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):
|
||||
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 _wrap_response(self, start, length):
|
||||
"""Wrap existing Response in case of Range Request context."""
|
||||
if self.status_code == 206:
|
||||
self.response = _RangeWrapper(self.response, start, length)
|
||||
|
||||
def _is_range_request_processable(self, environ):
|
||||
"""Return ``True`` if `Range` header is present and if underlying
|
||||
resource is considered unchanged when compared with `If-Range` header.
|
||||
"""
|
||||
return (
|
||||
"HTTP_IF_RANGE" not in environ
|
||||
or not is_resource_modified(
|
||||
environ,
|
||||
self.headers.get("etag"),
|
||||
None,
|
||||
self.headers.get("last-modified"),
|
||||
ignore_if_range=False,
|
||||
)
|
||||
) and "HTTP_RANGE" in environ
|
||||
|
||||
def _process_range_request(self, environ, complete_length=None, accept_ranges=None):
|
||||
"""Handle Range Request related headers (RFC7233). If `Accept-Ranges`
|
||||
header is valid, and Range Request is processable, we set the headers
|
||||
as described by the RFC, and wrap the underlying response in a
|
||||
RangeWrapper.
|
||||
|
||||
Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.
|
||||
|
||||
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
||||
if `Range` header could not be parsed or satisfied.
|
||||
"""
|
||||
from ..exceptions import RequestedRangeNotSatisfiable
|
||||
|
||||
if (
|
||||
accept_ranges is None
|
||||
or complete_length is None
|
||||
or not self._is_range_request_processable(environ)
|
||||
):
|
||||
return False
|
||||
|
||||
parsed_range = parse_range_header(environ.get("HTTP_RANGE"))
|
||||
|
||||
if parsed_range is None:
|
||||
raise RequestedRangeNotSatisfiable(complete_length)
|
||||
|
||||
range_tuple = parsed_range.range_for_length(complete_length)
|
||||
content_range_header = parsed_range.to_content_range_header(complete_length)
|
||||
|
||||
if range_tuple is None or content_range_header is None:
|
||||
raise RequestedRangeNotSatisfiable(complete_length)
|
||||
|
||||
content_length = range_tuple[1] - range_tuple[0]
|
||||
self.headers["Content-Length"] = content_length
|
||||
self.headers["Accept-Ranges"] = accept_ranges
|
||||
self.content_range = content_range_header
|
||||
self.status_code = 206
|
||||
self._wrap_response(range_tuple[0], content_length)
|
||||
return True
|
||||
|
||||
def make_conditional(
|
||||
self, request_or_environ, accept_ranges=False, complete_length=None
|
||||
):
|
||||
"""Make the response conditional to the request. This method works
|
||||
best if an etag was defined for the response already. The `add_etag`
|
||||
method can be used to do that. If called without etag just the date
|
||||
header is set.
|
||||
|
||||
This does nothing if the request method in the request or environ is
|
||||
anything but GET or HEAD.
|
||||
|
||||
For optimal performance when handling range requests, it's recommended
|
||||
that your response data object implements `seekable`, `seek` and `tell`
|
||||
methods as described by :py:class:`io.IOBase`. Objects returned by
|
||||
:meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.
|
||||
|
||||
It does not remove the body of the response because that's something
|
||||
the :meth:`__call__` function does for us automatically.
|
||||
|
||||
Returns self so that you can do ``return resp.make_conditional(req)``
|
||||
but modifies the object in-place.
|
||||
|
||||
:param request_or_environ: a request object or WSGI environment to be
|
||||
used to make the response conditional
|
||||
against.
|
||||
:param accept_ranges: This parameter dictates the value of
|
||||
`Accept-Ranges` header. If ``False`` (default),
|
||||
the header is not set. If ``True``, it will be set
|
||||
to ``"bytes"``. If ``None``, it will be set to
|
||||
``"none"``. If it's a string, it will use this
|
||||
value.
|
||||
:param complete_length: Will be used only in valid Range Requests.
|
||||
It will set `Content-Range` complete length
|
||||
value and compute `Content-Length` real value.
|
||||
This parameter is mandatory for successful
|
||||
Range Requests completion.
|
||||
:raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
|
||||
if `Range` header could not be parsed or satisfied.
|
||||
"""
|
||||
environ = _get_environ(request_or_environ)
|
||||
if environ["REQUEST_METHOD"] in ("GET", "HEAD"):
|
||||
# if the date is not in the headers, add it now. We however
|
||||
# will not override an already existing header. Unfortunately
|
||||
# this header will be overriden by many WSGI servers including
|
||||
# wsgiref.
|
||||
if "date" not in self.headers:
|
||||
self.headers["Date"] = http_date()
|
||||
accept_ranges = _clean_accept_ranges(accept_ranges)
|
||||
is206 = self._process_range_request(environ, complete_length, accept_ranges)
|
||||
if not is206 and not is_resource_modified(
|
||||
environ,
|
||||
self.headers.get("etag"),
|
||||
None,
|
||||
self.headers.get("last-modified"),
|
||||
):
|
||||
if parse_etags(environ.get("HTTP_IF_MATCH")):
|
||||
self.status_code = 412
|
||||
else:
|
||||
self.status_code = 304
|
||||
if (
|
||||
self.automatically_set_content_length
|
||||
and "content-length" not in self.headers
|
||||
):
|
||||
length = self.calculate_content_length()
|
||||
if length is not None:
|
||||
self.headers["Content-Length"] = length
|
||||
return self
|
||||
|
||||
def add_etag(self, overwrite=False, weak=False):
|
||||
"""Add an etag for the current response if there is none yet."""
|
||||
if overwrite or "etag" not in self.headers:
|
||||
self.set_etag(generate_etag(self.get_data()), weak)
|
||||
|
||||
def set_etag(self, etag, weak=False):
|
||||
"""Set the etag, and override the old one if there was one."""
|
||||
self.headers["ETag"] = quote_etag(etag, weak)
|
||||
|
||||
def get_etag(self):
|
||||
"""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"))
|
||||
|
||||
def freeze(self, no_etag=False):
|
||||
"""Call this method if you want to make your response object ready for
|
||||
pickeling. This buffers the generator if there is one. This also
|
||||
sets the etag unless `no_etag` is set to `True`.
|
||||
"""
|
||||
if not no_etag:
|
||||
self.add_etag()
|
||||
super(ETagResponseMixin, self).freeze()
|
||||
|
||||
accept_ranges = header_property(
|
||||
"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):
|
||||
"""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):
|
||||
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):
|
||||
if not value:
|
||||
del self.headers["content-range"]
|
||||
elif isinstance(value, string_types):
|
||||
self.headers["Content-Range"] = value
|
||||
else:
|
||||
self.headers["Content-Range"] = value.to_header()
|
145
venv/lib/python3.7/site-packages/werkzeug/wrappers/json.py
Normal file
145
venv/lib/python3.7/site-packages/werkzeug/wrappers/json.py
Normal file
@@ -0,0 +1,145 @@
|
||||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
from .._compat import text_type
|
||||
from ..exceptions import BadRequest
|
||||
from ..utils import detect_utf_encoding
|
||||
|
||||
try:
|
||||
import simplejson as _json
|
||||
except ImportError:
|
||||
import json as _json
|
||||
|
||||
|
||||
class _JSONModule(object):
|
||||
@staticmethod
|
||||
def _default(o):
|
||||
if isinstance(o, datetime.date):
|
||||
return o.isoformat()
|
||||
|
||||
if isinstance(o, uuid.UUID):
|
||||
return str(o)
|
||||
|
||||
if hasattr(o, "__html__"):
|
||||
return text_type(o.__html__())
|
||||
|
||||
raise TypeError()
|
||||
|
||||
@classmethod
|
||||
def dumps(cls, obj, **kw):
|
||||
kw.setdefault("separators", (",", ":"))
|
||||
kw.setdefault("default", cls._default)
|
||||
kw.setdefault("sort_keys", True)
|
||||
return _json.dumps(obj, **kw)
|
||||
|
||||
@staticmethod
|
||||
def loads(s, **kw):
|
||||
if isinstance(s, bytes):
|
||||
# Needed for Python < 3.6
|
||||
encoding = detect_utf_encoding(s)
|
||||
s = s.decode(encoding)
|
||||
|
||||
return _json.loads(s, **kw)
|
||||
|
||||
|
||||
class JSONMixin(object):
|
||||
"""Mixin to parse :attr:`data` as JSON. Can be mixed in for both
|
||||
:class:`~werkzeug.wrappers.Request` and
|
||||
:class:`~werkzeug.wrappers.Response` classes.
|
||||
|
||||
If `simplejson`_ is installed it is preferred over Python's built-in
|
||||
:mod:`json` module.
|
||||
|
||||
.. _simplejson: https://simplejson.readthedocs.io/en/latest/
|
||||
"""
|
||||
|
||||
#: A module or other object that has ``dumps`` and ``loads``
|
||||
#: functions that match the API of the built-in :mod:`json` module.
|
||||
json_module = _JSONModule
|
||||
|
||||
@property
|
||||
def json(self):
|
||||
"""The parsed JSON data if :attr:`mimetype` indicates JSON
|
||||
(:mimetype:`application/json`, see :meth:`is_json`).
|
||||
|
||||
Calls :meth:`get_json` with default arguments.
|
||||
"""
|
||||
return self.get_json()
|
||||
|
||||
@property
|
||||
def is_json(self):
|
||||
"""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")
|
||||
)
|
||||
|
||||
def _get_data_for_json(self, cache):
|
||||
try:
|
||||
return self.get_data(cache=cache)
|
||||
except TypeError:
|
||||
# Response doesn't have cache param.
|
||||
return self.get_data()
|
||||
|
||||
# Cached values for ``(silent=False, silent=True)``. Initialized
|
||||
# with sentinel values.
|
||||
_cached_json = (Ellipsis, Ellipsis)
|
||||
|
||||
def get_json(self, force=False, silent=False, cache=True):
|
||||
"""Parse :attr:`data` as JSON.
|
||||
|
||||
If the mimetype does not indicate JSON
|
||||
(:mimetype:`application/json`, see :meth:`is_json`), this
|
||||
returns ``None``.
|
||||
|
||||
If parsing fails, :meth:`on_json_loading_failed` is called and
|
||||
its return value is used as the return value.
|
||||
|
||||
:param force: Ignore the mimetype and always try to parse JSON.
|
||||
:param silent: Silence parsing errors and return ``None``
|
||||
instead.
|
||||
:param cache: Store the parsed JSON to return for subsequent
|
||||
calls.
|
||||
"""
|
||||
if cache and self._cached_json[silent] is not Ellipsis:
|
||||
return self._cached_json[silent]
|
||||
|
||||
if not (force or self.is_json):
|
||||
return None
|
||||
|
||||
data = self._get_data_for_json(cache=cache)
|
||||
|
||||
try:
|
||||
rv = self.json_module.loads(data)
|
||||
except ValueError as e:
|
||||
if silent:
|
||||
rv = None
|
||||
|
||||
if cache:
|
||||
normal_rv, _ = self._cached_json
|
||||
self._cached_json = (normal_rv, rv)
|
||||
else:
|
||||
rv = self.on_json_loading_failed(e)
|
||||
|
||||
if cache:
|
||||
_, silent_rv = self._cached_json
|
||||
self._cached_json = (rv, silent_rv)
|
||||
else:
|
||||
if cache:
|
||||
self._cached_json = (rv, rv)
|
||||
|
||||
return rv
|
||||
|
||||
def on_json_loading_failed(self, e):
|
||||
"""Called if :meth:`get_json` parsing fails and isn't silenced.
|
||||
If this method returns a value, it is used as the return value
|
||||
for :meth:`get_json`. The default implementation raises
|
||||
:exc:`~werkzeug.exceptions.BadRequest`.
|
||||
"""
|
||||
raise BadRequest("Failed to decode JSON object: {0}".format(e))
|
@@ -0,0 +1,49 @@
|
||||
from .accept import AcceptMixin
|
||||
from .auth import AuthorizationMixin
|
||||
from .base_request import BaseRequest
|
||||
from .common_descriptors import CommonRequestDescriptorsMixin
|
||||
from .cors import CORSRequestMixin
|
||||
from .etag import ETagRequestMixin
|
||||
from .user_agent import UserAgentMixin
|
||||
|
||||
|
||||
class Request(
|
||||
BaseRequest,
|
||||
AcceptMixin,
|
||||
ETagRequestMixin,
|
||||
UserAgentMixin,
|
||||
AuthorizationMixin,
|
||||
CORSRequestMixin,
|
||||
CommonRequestDescriptorsMixin,
|
||||
):
|
||||
"""Full featured request object implementing the following mixins:
|
||||
|
||||
- :class:`AcceptMixin` for accept header parsing
|
||||
- :class:`ETagRequestMixin` for etag and cache control handling
|
||||
- :class:`UserAgentMixin` for user agent introspection
|
||||
- :class:`AuthorizationMixin` for http auth handling
|
||||
- :class:`~werkzeug.wrappers.cors.CORSRequestMixin` for Cross
|
||||
Origin Resource Sharing headers
|
||||
- :class:`CommonRequestDescriptorsMixin` for common headers
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class StreamOnlyMixin(object):
|
||||
"""If mixed in before the request object this will change the behavior
|
||||
of it to disable handling of form parsing. This disables the
|
||||
:attr:`files`, :attr:`form` attributes and will just provide a
|
||||
:attr:`stream` attribute that however is always available.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
|
||||
disable_data_descriptor = True
|
||||
want_form_data_parsed = False
|
||||
|
||||
|
||||
class PlainRequest(StreamOnlyMixin, Request):
|
||||
"""A request object without special form parsing capabilities.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
@@ -0,0 +1,84 @@
|
||||
from ..utils import cached_property
|
||||
from .auth import WWWAuthenticateMixin
|
||||
from .base_response import BaseResponse
|
||||
from .common_descriptors import CommonResponseDescriptorsMixin
|
||||
from .cors import CORSResponseMixin
|
||||
from .etag import ETagResponseMixin
|
||||
|
||||
|
||||
class ResponseStream(object):
|
||||
"""A file descriptor like object used by the :class:`ResponseStreamMixin` to
|
||||
represent the body of the stream. It directly pushes into the response
|
||||
iterable of the response object.
|
||||
"""
|
||||
|
||||
mode = "wb+"
|
||||
|
||||
def __init__(self, response):
|
||||
self.response = response
|
||||
self.closed = False
|
||||
|
||||
def write(self, value):
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
self.response._ensure_sequence(mutable=True)
|
||||
self.response.response.append(value)
|
||||
self.response.headers.pop("Content-Length", None)
|
||||
return len(value)
|
||||
|
||||
def writelines(self, seq):
|
||||
for item in seq:
|
||||
self.write(item)
|
||||
|
||||
def close(self):
|
||||
self.closed = True
|
||||
|
||||
def flush(self):
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
|
||||
def isatty(self):
|
||||
if self.closed:
|
||||
raise ValueError("I/O operation on closed file")
|
||||
return False
|
||||
|
||||
def tell(self):
|
||||
self.response._ensure_sequence()
|
||||
return sum(map(len, self.response.response))
|
||||
|
||||
@property
|
||||
def encoding(self):
|
||||
return self.response.charset
|
||||
|
||||
|
||||
class ResponseStreamMixin(object):
|
||||
"""Mixin for :class:`BaseResponse` subclasses. Classes that inherit from
|
||||
this mixin will automatically get a :attr:`stream` property that provides
|
||||
a write-only interface to the response iterable.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def stream(self):
|
||||
"""The response iterable as write-only stream."""
|
||||
return ResponseStream(self)
|
||||
|
||||
|
||||
class Response(
|
||||
BaseResponse,
|
||||
ETagResponseMixin,
|
||||
WWWAuthenticateMixin,
|
||||
CORSResponseMixin,
|
||||
ResponseStreamMixin,
|
||||
CommonResponseDescriptorsMixin,
|
||||
):
|
||||
"""Full featured response object implementing the following mixins:
|
||||
|
||||
- :class:`ETagResponseMixin` for etag and cache control handling
|
||||
- :class:`WWWAuthenticateMixin` for HTTP authentication support
|
||||
- :class:`~werkzeug.wrappers.cors.CORSResponseMixin` for Cross
|
||||
Origin Resource Sharing headers
|
||||
- :class:`ResponseStreamMixin` to add support for the ``stream``
|
||||
property
|
||||
- :class:`CommonResponseDescriptorsMixin` for various HTTP
|
||||
descriptors
|
||||
"""
|
@@ -0,0 +1,14 @@
|
||||
from ..useragents import UserAgent
|
||||
from ..utils import cached_property
|
||||
|
||||
|
||||
class UserAgentMixin(object):
|
||||
"""Adds a `user_agent` attribute to the request object which
|
||||
contains the parsed user agent of the browser that triggered the
|
||||
request as a :class:`~werkzeug.useragents.UserAgent` object.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def user_agent(self):
|
||||
"""The current user agent."""
|
||||
return UserAgent(self.environ)
|
1000
venv/lib/python3.7/site-packages/werkzeug/wsgi.py
Normal file
1000
venv/lib/python3.7/site-packages/werkzeug/wsgi.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user