lol
This commit is contained in:
@@ -1,6 +1,25 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
|
||||
from .serving import run_simple as run_simple
|
||||
from .test import Client as Client
|
||||
from .wrappers import Request as Request
|
||||
from .wrappers import Response as Response
|
||||
|
||||
__version__ = "2.3.7"
|
||||
|
||||
def __getattr__(name: str) -> t.Any:
|
||||
if name == "__version__":
|
||||
import importlib.metadata
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The '__version__' attribute is deprecated and will be removed in"
|
||||
" Werkzeug 3.1. Use feature detection or"
|
||||
" 'importlib.metadata.version(\"werkzeug\")' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return importlib.metadata.version("werkzeug")
|
||||
|
||||
raise AttributeError(name)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import operator
|
||||
import re
|
||||
import sys
|
||||
import typing as t
|
||||
@@ -26,102 +25,12 @@ class _Missing:
|
||||
_missing = _Missing()
|
||||
|
||||
|
||||
@t.overload
|
||||
def _make_encode_wrapper(reference: str) -> t.Callable[[str], str]:
|
||||
...
|
||||
def _wsgi_decoding_dance(s: str) -> str:
|
||||
return s.encode("latin1").decode(errors="replace")
|
||||
|
||||
|
||||
@t.overload
|
||||
def _make_encode_wrapper(reference: bytes) -> t.Callable[[str], bytes]:
|
||||
...
|
||||
|
||||
|
||||
def _make_encode_wrapper(reference: t.AnyStr) -> t.Callable[[str], t.AnyStr]:
|
||||
"""Create a function that will be called with a string argument. If
|
||||
the reference is bytes, values will be encoded to bytes.
|
||||
"""
|
||||
if isinstance(reference, str):
|
||||
return lambda x: x
|
||||
|
||||
return operator.methodcaller("encode", "latin1")
|
||||
|
||||
|
||||
def _check_str_tuple(value: tuple[t.AnyStr, ...]) -> None:
|
||||
"""Ensure tuple items are all strings or all bytes."""
|
||||
if not value:
|
||||
return
|
||||
|
||||
item_type = str if isinstance(value[0], str) else bytes
|
||||
|
||||
if any(not isinstance(item, item_type) for item in value):
|
||||
raise TypeError(f"Cannot mix str and bytes arguments (got {value!r})")
|
||||
|
||||
|
||||
_default_encoding = sys.getdefaultencoding()
|
||||
|
||||
|
||||
def _to_bytes(
|
||||
x: str | bytes, charset: str = _default_encoding, errors: str = "strict"
|
||||
) -> bytes:
|
||||
if x is None or isinstance(x, bytes):
|
||||
return x
|
||||
|
||||
if isinstance(x, (bytearray, memoryview)):
|
||||
return bytes(x)
|
||||
|
||||
if isinstance(x, str):
|
||||
return x.encode(charset, errors)
|
||||
|
||||
raise TypeError("Expected bytes")
|
||||
|
||||
|
||||
@t.overload
|
||||
def _to_str( # type: ignore
|
||||
x: None,
|
||||
charset: str | None = ...,
|
||||
errors: str = ...,
|
||||
allow_none_charset: bool = ...,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
@t.overload
|
||||
def _to_str(
|
||||
x: t.Any,
|
||||
charset: str | None = ...,
|
||||
errors: str = ...,
|
||||
allow_none_charset: bool = ...,
|
||||
) -> str:
|
||||
...
|
||||
|
||||
|
||||
def _to_str(
|
||||
x: t.Any | None,
|
||||
charset: str | None = _default_encoding,
|
||||
errors: str = "strict",
|
||||
allow_none_charset: bool = False,
|
||||
) -> str | bytes | None:
|
||||
if x is None or isinstance(x, str):
|
||||
return x
|
||||
|
||||
if not isinstance(x, (bytes, bytearray)):
|
||||
return str(x)
|
||||
|
||||
if charset is None:
|
||||
if allow_none_charset:
|
||||
return x
|
||||
|
||||
return x.decode(charset, errors) # type: ignore
|
||||
|
||||
|
||||
def _wsgi_decoding_dance(
|
||||
s: str, charset: str = "utf-8", errors: str = "replace"
|
||||
) -> str:
|
||||
return s.encode("latin1").decode(charset, errors)
|
||||
|
||||
|
||||
def _wsgi_encoding_dance(s: str, charset: str = "utf-8", errors: str = "strict") -> str:
|
||||
return s.encode(charset).decode("latin1", errors)
|
||||
def _wsgi_encoding_dance(s: str) -> str:
|
||||
return s.encode().decode("latin1")
|
||||
|
||||
|
||||
def _get_environ(obj: WSGIEnvironment | Request) -> WSGIEnvironment:
|
||||
@@ -287,31 +196,6 @@ class _DictAccessorProperty(t.Generic[_TAccessorValue]):
|
||||
return f"<{type(self).__name__} {self.name}>"
|
||||
|
||||
|
||||
def _decode_idna(domain: str) -> str:
|
||||
try:
|
||||
data = domain.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
# If the domain is not ASCII, it's decoded already.
|
||||
return domain
|
||||
|
||||
try:
|
||||
# Try decoding in one shot.
|
||||
return data.decode("idna")
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
|
||||
# Decode each part separately, leaving invalid parts as punycode.
|
||||
parts = []
|
||||
|
||||
for part in data.split(b"."):
|
||||
try:
|
||||
parts.append(part.decode("idna"))
|
||||
except UnicodeDecodeError:
|
||||
parts.append(part.decode("ascii"))
|
||||
|
||||
return ".".join(parts)
|
||||
|
||||
|
||||
_plain_int_re = re.compile(r"-?\d+", re.ASCII)
|
||||
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,15 +3,11 @@ from __future__ import annotations
|
||||
import base64
|
||||
import binascii
|
||||
import typing as t
|
||||
import warnings
|
||||
from functools import wraps
|
||||
|
||||
from ..http import dump_header
|
||||
from ..http import parse_dict_header
|
||||
from ..http import parse_set_header
|
||||
from ..http import quote_header_value
|
||||
from .structures import CallbackDict
|
||||
from .structures import HeaderSet
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import typing_extensions as te
|
||||
@@ -46,7 +42,7 @@ class Authorization:
|
||||
def __init__(
|
||||
self,
|
||||
auth_type: str,
|
||||
data: dict[str, str] | None = None,
|
||||
data: dict[str, str | None] | None = None,
|
||||
token: str | None = None,
|
||||
) -> None:
|
||||
self.type = auth_type
|
||||
@@ -143,31 +139,6 @@ class Authorization:
|
||||
return f"<{type(self).__name__} {self.to_header()}>"
|
||||
|
||||
|
||||
def auth_property(name: str, doc: str | None = None) -> property:
|
||||
"""A static helper function for Authentication subclasses to add
|
||||
extra authentication system properties onto a class::
|
||||
|
||||
class FooAuthenticate(WWWAuthenticate):
|
||||
special_realm = auth_property('special_realm')
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0.
|
||||
"""
|
||||
warnings.warn(
|
||||
"'auth_property' is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def _set_value(self, value): # type: ignore[no-untyped-def]
|
||||
if value is None:
|
||||
self.pop(name, None)
|
||||
else:
|
||||
self[name] = str(value)
|
||||
|
||||
return property(lambda x: x.get(name), _set_value, doc=doc)
|
||||
|
||||
|
||||
class WWWAuthenticate:
|
||||
"""Represents the parts of a ``WWW-Authenticate`` response header.
|
||||
|
||||
@@ -196,21 +167,12 @@ class WWWAuthenticate:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
auth_type: str | None = None,
|
||||
values: dict[str, str] | None = None,
|
||||
auth_type: str,
|
||||
values: dict[str, str | None] | None = None,
|
||||
token: str | None = None,
|
||||
):
|
||||
if auth_type is None:
|
||||
warnings.warn(
|
||||
"An auth type must be given as the first parameter. Assuming 'basic' is"
|
||||
" deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
auth_type = "basic"
|
||||
|
||||
self._type = auth_type.lower()
|
||||
self._parameters: dict[str, str] = CallbackDict( # type: ignore[misc]
|
||||
self._parameters: dict[str, str | None] = CallbackDict( # type: ignore[misc]
|
||||
values, lambda _: self._trigger_on_update()
|
||||
)
|
||||
self._token = token
|
||||
@@ -231,7 +193,7 @@ class WWWAuthenticate:
|
||||
self._trigger_on_update()
|
||||
|
||||
@property
|
||||
def parameters(self) -> dict[str, str]:
|
||||
def parameters(self) -> dict[str, str | None]:
|
||||
"""A dict of parameters for the header. Only one of this or :attr:`token` should
|
||||
have a value for a given scheme.
|
||||
"""
|
||||
@@ -261,62 +223,6 @@ class WWWAuthenticate:
|
||||
self._token = value
|
||||
self._trigger_on_update()
|
||||
|
||||
def set_basic(self, realm: str = "authentication required") -> None:
|
||||
"""Clear any existing data and set a ``Basic`` challenge.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Create and assign an instance instead.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'set_basic' method is deprecated and will be removed in Werkzeug 3.0."
|
||||
" Create and assign an instance instead."
|
||||
)
|
||||
self._type = "basic"
|
||||
dict.clear(self.parameters) # type: ignore[arg-type]
|
||||
dict.update(
|
||||
self.parameters, # type: ignore[arg-type]
|
||||
{"realm": realm}, # type: ignore[dict-item]
|
||||
)
|
||||
self._token = None
|
||||
self._trigger_on_update()
|
||||
|
||||
def set_digest(
|
||||
self,
|
||||
realm: str,
|
||||
nonce: str,
|
||||
qop: t.Sequence[str] = ("auth",),
|
||||
opaque: str | None = None,
|
||||
algorithm: str | None = None,
|
||||
stale: bool = False,
|
||||
) -> None:
|
||||
"""Clear any existing data and set a ``Digest`` challenge.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Create and assign an instance instead.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'set_digest' method is deprecated and will be removed in Werkzeug 3.0."
|
||||
" Create and assign an instance instead."
|
||||
)
|
||||
self._type = "digest"
|
||||
dict.clear(self.parameters) # type: ignore[arg-type]
|
||||
parameters = {
|
||||
"realm": realm,
|
||||
"nonce": nonce,
|
||||
"qop": ", ".join(qop),
|
||||
"stale": "TRUE" if stale else "FALSE",
|
||||
}
|
||||
|
||||
if opaque is not None:
|
||||
parameters["opaque"] = opaque
|
||||
|
||||
if algorithm is not None:
|
||||
parameters["algorithm"] = algorithm
|
||||
|
||||
dict.update(self.parameters, parameters) # type: ignore[arg-type]
|
||||
self._token = None
|
||||
self._trigger_on_update()
|
||||
|
||||
def __getitem__(self, key: str) -> str | None:
|
||||
return self.parameters.get(key)
|
||||
|
||||
@@ -410,101 +316,3 @@ class WWWAuthenticate:
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<{type(self).__name__} {self.to_header()}>"
|
||||
|
||||
@property
|
||||
def qop(self) -> set[str]:
|
||||
"""The ``qop`` parameter as a set.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. It will become the same as other
|
||||
parameters, returning a string.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'qop' property is deprecated and will be removed in Werkzeug 3.0."
|
||||
" It will become the same as other parameters, returning a string.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
def on_update(value: HeaderSet) -> None:
|
||||
if not value:
|
||||
if "qop" in self:
|
||||
del self["qop"]
|
||||
|
||||
return
|
||||
|
||||
self.parameters["qop"] = value.to_header()
|
||||
|
||||
return parse_set_header(self.parameters.get("qop"), on_update)
|
||||
|
||||
@property
|
||||
def stale(self) -> bool | None:
|
||||
"""The ``stale`` parameter as a boolean.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. It will become the same as other
|
||||
parameters, returning a string.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'stale' property is deprecated and will be removed in Werkzeug 3.0."
|
||||
" It will become the same as other parameters, returning a string.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if "stale" in self.parameters:
|
||||
return self.parameters["stale"].lower() == "true"
|
||||
|
||||
return None
|
||||
|
||||
@stale.setter
|
||||
def stale(self, value: bool | str | None) -> None:
|
||||
if value is None:
|
||||
if "stale" in self.parameters:
|
||||
del self.parameters["stale"]
|
||||
|
||||
return
|
||||
|
||||
if isinstance(value, bool):
|
||||
warnings.warn(
|
||||
"Setting the 'stale' property to a boolean is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self.parameters["stale"] = "TRUE" if value else "FALSE"
|
||||
else:
|
||||
self.parameters["stale"] = value
|
||||
|
||||
auth_property = staticmethod(auth_property)
|
||||
|
||||
|
||||
def _deprecated_dict_method(f): # type: ignore[no-untyped-def]
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs): # type: ignore[no-untyped-def]
|
||||
warnings.warn(
|
||||
"Treating 'Authorization' and 'WWWAuthenticate' as a dict is deprecated and"
|
||||
" will be removed in Werkzeug 3.0. Use the 'parameters' attribute instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
for name in (
|
||||
"__iter__",
|
||||
"clear",
|
||||
"copy",
|
||||
"items",
|
||||
"keys",
|
||||
"pop",
|
||||
"popitem",
|
||||
"setdefault",
|
||||
"update",
|
||||
"values",
|
||||
):
|
||||
f = _deprecated_dict_method(getattr(dict, name))
|
||||
setattr(Authorization, name, f)
|
||||
setattr(WWWAuthenticate, name, f)
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
|
||||
from .._internal import _missing
|
||||
from ..exceptions import BadRequestKeyError
|
||||
@@ -82,7 +81,7 @@ class Headers:
|
||||
|
||||
__hash__ = None
|
||||
|
||||
def get(self, key, default=None, type=None, as_bytes=None):
|
||||
def get(self, key, default=None, type=None):
|
||||
"""Return the default value if the requested data doesn't exist.
|
||||
If `type` is provided and is a callable it should convert the value,
|
||||
return it or raise a :exc:`ValueError` if that is not possible. In
|
||||
@@ -101,27 +100,16 @@ class Headers:
|
||||
:class:`Headers`. If a :exc:`ValueError` is raised
|
||||
by this callable the default value is returned.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``as_bytes`` parameter is deprecated and will be removed
|
||||
in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``as_bytes`` parameter was removed.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
The ``as_bytes`` parameter was added.
|
||||
"""
|
||||
if as_bytes is not None:
|
||||
warnings.warn(
|
||||
"The 'as_bytes' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
try:
|
||||
rv = self.__getitem__(key, _get_mode=True)
|
||||
except KeyError:
|
||||
return default
|
||||
if as_bytes:
|
||||
rv = rv.encode("latin1")
|
||||
if type is None:
|
||||
return rv
|
||||
try:
|
||||
@@ -129,7 +117,7 @@ class Headers:
|
||||
except ValueError:
|
||||
return default
|
||||
|
||||
def getlist(self, key, type=None, as_bytes=None):
|
||||
def getlist(self, key, type=None):
|
||||
"""Return the list of items for a given key. If that key is not in the
|
||||
:class:`Headers`, the return value will be an empty list. Just like
|
||||
:meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will
|
||||
@@ -141,27 +129,16 @@ class Headers:
|
||||
by this callable the value will be removed from the list.
|
||||
:return: a :class:`list` of all the values for the key.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``as_bytes`` parameter is deprecated and will be removed
|
||||
in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``as_bytes`` parameter was removed.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
The ``as_bytes`` parameter was added.
|
||||
"""
|
||||
if as_bytes is not None:
|
||||
warnings.warn(
|
||||
"The 'as_bytes' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
ikey = key.lower()
|
||||
result = []
|
||||
for k, v in self:
|
||||
if k.lower() == ikey:
|
||||
if as_bytes:
|
||||
v = v.encode("latin1")
|
||||
if type is not None:
|
||||
try:
|
||||
v = type(v)
|
||||
@@ -293,7 +270,6 @@ class Headers:
|
||||
"""
|
||||
if kw:
|
||||
_value = _options_header_vkw(_value, kw)
|
||||
_key = _str_header_key(_key)
|
||||
_value = _str_header_value(_value)
|
||||
self._list.append((_key, _value))
|
||||
|
||||
@@ -326,7 +302,6 @@ class Headers:
|
||||
"""
|
||||
if kw:
|
||||
_value = _options_header_vkw(_value, kw)
|
||||
_key = _str_header_key(_key)
|
||||
_value = _str_header_value(_value)
|
||||
if not self._list:
|
||||
self._list.append((_key, _value))
|
||||
@@ -399,7 +374,7 @@ class Headers:
|
||||
if isinstance(key, (slice, int)):
|
||||
if isinstance(key, int):
|
||||
value = [value]
|
||||
value = [(_str_header_key(k), _str_header_value(v)) for (k, v) in value]
|
||||
value = [(k, _str_header_value(v)) for (k, v) in value]
|
||||
if isinstance(key, int):
|
||||
self._list[key] = value[0]
|
||||
else:
|
||||
@@ -476,36 +451,10 @@ def _options_header_vkw(value: str, kw: dict[str, t.Any]):
|
||||
)
|
||||
|
||||
|
||||
def _str_header_key(key: t.Any) -> str:
|
||||
if not isinstance(key, str):
|
||||
warnings.warn(
|
||||
"Header keys must be strings. Passing other types is deprecated and will"
|
||||
" not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if isinstance(key, bytes):
|
||||
key = key.decode("latin-1")
|
||||
else:
|
||||
key = str(key)
|
||||
|
||||
return key
|
||||
|
||||
|
||||
_newline_re = re.compile(r"[\r\n]")
|
||||
|
||||
|
||||
def _str_header_value(value: t.Any) -> str:
|
||||
if isinstance(value, bytes):
|
||||
warnings.warn(
|
||||
"Passing bytes as a header value is deprecated and will not be supported in"
|
||||
" Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
value = value.decode("latin-1")
|
||||
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
import warnings
|
||||
from io import BytesIO
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
@@ -68,8 +67,6 @@ def default_stream_factory(
|
||||
def parse_form_data(
|
||||
environ: WSGIEnvironment,
|
||||
stream_factory: TStreamFactory | None = None,
|
||||
charset: str | None = None,
|
||||
errors: str | None = None,
|
||||
max_form_memory_size: int | None = None,
|
||||
max_content_length: int | None = None,
|
||||
cls: type[MultiDict] | None = None,
|
||||
@@ -108,12 +105,11 @@ def parse_form_data(
|
||||
is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Added the ``max_form_parts`` parameter.
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` and ``errors`` parameters were removed.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` and ``errors`` parameters are deprecated and will be removed in
|
||||
Werkzeug 3.0.
|
||||
Added the ``max_form_parts`` parameter.
|
||||
|
||||
.. versionadded:: 0.5.1
|
||||
Added the ``silent`` parameter.
|
||||
@@ -124,8 +120,6 @@ def parse_form_data(
|
||||
"""
|
||||
return FormDataParser(
|
||||
stream_factory=stream_factory,
|
||||
charset=charset,
|
||||
errors=errors,
|
||||
max_form_memory_size=max_form_memory_size,
|
||||
max_content_length=max_content_length,
|
||||
max_form_parts=max_form_parts,
|
||||
@@ -159,13 +153,11 @@ class FormDataParser:
|
||||
:param max_form_parts: The maximum number of multipart parts to be parsed. If this
|
||||
is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` and ``errors`` parameters are deprecated and will be removed in
|
||||
Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` and ``errors`` parameters were removed.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``parse_functions`` attribute and ``get_parse_func`` methods are deprecated
|
||||
and will be removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``parse_functions`` attribute and ``get_parse_func`` methods were removed.
|
||||
|
||||
.. versionchanged:: 2.2.3
|
||||
Added the ``max_form_parts`` parameter.
|
||||
@@ -176,8 +168,6 @@ class FormDataParser:
|
||||
def __init__(
|
||||
self,
|
||||
stream_factory: TStreamFactory | None = None,
|
||||
charset: str | None = None,
|
||||
errors: str | None = None,
|
||||
max_form_memory_size: int | None = None,
|
||||
max_content_length: int | None = None,
|
||||
cls: type[MultiDict] | None = None,
|
||||
@@ -189,30 +179,6 @@ class FormDataParser:
|
||||
stream_factory = default_stream_factory
|
||||
|
||||
self.stream_factory = stream_factory
|
||||
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
self.charset = charset
|
||||
|
||||
if errors is not None:
|
||||
warnings.warn(
|
||||
"The 'errors' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
errors = "replace"
|
||||
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.max_content_length = max_content_length
|
||||
self.max_form_parts = max_form_parts
|
||||
@@ -223,44 +189,6 @@ class FormDataParser:
|
||||
self.cls = cls
|
||||
self.silent = silent
|
||||
|
||||
def get_parse_func(
|
||||
self, mimetype: str, options: dict[str, str]
|
||||
) -> None | (
|
||||
t.Callable[
|
||||
[FormDataParser, t.IO[bytes], str, int | None, dict[str, str]],
|
||||
t_parse_result,
|
||||
]
|
||||
):
|
||||
warnings.warn(
|
||||
"The 'get_parse_func' method is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if mimetype == "multipart/form-data":
|
||||
return type(self)._parse_multipart
|
||||
elif mimetype == "application/x-www-form-urlencoded":
|
||||
return type(self)._parse_urlencoded
|
||||
elif mimetype == "application/x-url-encoded":
|
||||
warnings.warn(
|
||||
"The 'application/x-url-encoded' mimetype is invalid, and will not be"
|
||||
" treated as 'application/x-www-form-urlencoded' in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return type(self)._parse_urlencoded
|
||||
elif mimetype in self.parse_functions:
|
||||
warnings.warn(
|
||||
"The 'parse_functions' attribute is deprecated and will be removed in"
|
||||
" Werkzeug 3.0. Override 'parse' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self.parse_functions[mimetype]
|
||||
|
||||
return None
|
||||
|
||||
def parse_from_environ(self, environ: WSGIEnvironment) -> t_parse_result:
|
||||
"""Parses the information from the environment as form data.
|
||||
|
||||
@@ -294,30 +222,14 @@ class FormDataParser:
|
||||
the multipart boundary for instance)
|
||||
:return: A tuple in the form ``(stream, form, files)``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``application/x-url-encoded`` content type is deprecated and will not be
|
||||
treated as ``application/x-www-form-urlencoded`` in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The invalid ``application/x-url-encoded`` content type is not
|
||||
treated as ``application/x-www-form-urlencoded``.
|
||||
"""
|
||||
if mimetype == "multipart/form-data":
|
||||
parse_func = self._parse_multipart
|
||||
elif mimetype == "application/x-www-form-urlencoded":
|
||||
parse_func = self._parse_urlencoded
|
||||
elif mimetype == "application/x-url-encoded":
|
||||
warnings.warn(
|
||||
"The 'application/x-url-encoded' mimetype is invalid, and will not be"
|
||||
" treated as 'application/x-www-form-urlencoded' in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
parse_func = self._parse_urlencoded
|
||||
elif mimetype in self.parse_functions:
|
||||
warnings.warn(
|
||||
"The 'parse_functions' attribute is deprecated and will be removed in"
|
||||
" Werkzeug 3.0. Override 'parse' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
parse_func = self.parse_functions[mimetype].__get__(self, type(self))
|
||||
else:
|
||||
return stream, self.cls(), self.cls()
|
||||
|
||||
@@ -339,12 +251,8 @@ class FormDataParser:
|
||||
content_length: int | None,
|
||||
options: dict[str, str],
|
||||
) -> t_parse_result:
|
||||
charset = self.charset if self.charset != "utf-8" else None
|
||||
errors = self.errors if self.errors != "replace" else None
|
||||
parser = MultiPartParser(
|
||||
stream_factory=self.stream_factory,
|
||||
charset=charset,
|
||||
errors=errors,
|
||||
max_form_memory_size=self.max_form_memory_size,
|
||||
max_form_parts=self.max_form_parts,
|
||||
cls=self.cls,
|
||||
@@ -375,7 +283,6 @@ class FormDataParser:
|
||||
items = parse_qsl(
|
||||
stream.read().decode(),
|
||||
keep_blank_values=True,
|
||||
encoding=self.charset,
|
||||
errors="werkzeug.url_quote",
|
||||
)
|
||||
except ValueError as e:
|
||||
@@ -383,49 +290,16 @@ class FormDataParser:
|
||||
|
||||
return stream, self.cls(items), self.cls()
|
||||
|
||||
parse_functions: dict[
|
||||
str,
|
||||
t.Callable[
|
||||
[FormDataParser, t.IO[bytes], str, int | None, dict[str, str]],
|
||||
t_parse_result,
|
||||
],
|
||||
] = {}
|
||||
|
||||
|
||||
class MultiPartParser:
|
||||
def __init__(
|
||||
self,
|
||||
stream_factory: TStreamFactory | None = None,
|
||||
charset: str | None = None,
|
||||
errors: str | None = None,
|
||||
max_form_memory_size: int | None = None,
|
||||
cls: type[MultiDict] | None = None,
|
||||
buffer_size: int = 64 * 1024,
|
||||
max_form_parts: int | None = None,
|
||||
) -> None:
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
self.charset = charset
|
||||
|
||||
if errors is not None:
|
||||
warnings.warn(
|
||||
"The 'errors' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
errors = "replace"
|
||||
|
||||
self.errors = errors
|
||||
self.max_form_memory_size = max_form_memory_size
|
||||
self.max_form_parts = max_form_parts
|
||||
|
||||
@@ -456,7 +330,7 @@ class MultiPartParser:
|
||||
if ct_charset in {"ascii", "us-ascii", "utf-8", "iso-8859-1"}:
|
||||
return ct_charset
|
||||
|
||||
return self.charset
|
||||
return "utf-8"
|
||||
|
||||
def start_file_streaming(
|
||||
self, event: File, total_content_length: int | None
|
||||
@@ -509,7 +383,7 @@ class MultiPartParser:
|
||||
if not event.more_data:
|
||||
if isinstance(current_part, Field):
|
||||
value = b"".join(container).decode(
|
||||
self.get_part_charset(current_part.headers), self.errors
|
||||
self.get_part_charset(current_part.headers), "replace"
|
||||
)
|
||||
fields.append((current_part.name, value))
|
||||
else:
|
||||
|
||||
@@ -136,11 +136,7 @@ class COOP(Enum):
|
||||
SAME_ORIGIN = "same-origin"
|
||||
|
||||
|
||||
def quote_header_value(
|
||||
value: t.Any,
|
||||
extra_chars: str | None = None,
|
||||
allow_token: bool = True,
|
||||
) -> str:
|
||||
def quote_header_value(value: t.Any, allow_token: bool = True) -> str:
|
||||
"""Add double quotes around a header value. If the header contains only ASCII token
|
||||
characters, it will be returned unchanged. If the header contains ``"`` or ``\\``
|
||||
characters, they will be escaped with an additional ``\\`` character.
|
||||
@@ -150,33 +146,17 @@ def quote_header_value(
|
||||
:param value: The value to quote. Will be converted to a string.
|
||||
:param allow_token: Disable to quote the value even if it only has token characters.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Passing bytes is not supported.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``extra_chars`` parameter is removed.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The value is quoted if it is the empty string.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Passing bytes is deprecated and will not be supported in Werkzeug 3.0.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``extra_chars`` parameter is deprecated and will be removed in Werkzeug 3.0.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
"""
|
||||
if isinstance(value, bytes):
|
||||
warnings.warn(
|
||||
"Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
value = value.decode("latin1")
|
||||
|
||||
if extra_chars is not None:
|
||||
warnings.warn(
|
||||
"The 'extra_chars' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
value = str(value)
|
||||
|
||||
if not value:
|
||||
@@ -185,9 +165,6 @@ def quote_header_value(
|
||||
if allow_token:
|
||||
token_chars = _token_chars
|
||||
|
||||
if extra_chars:
|
||||
token_chars |= set(extra_chars)
|
||||
|
||||
if token_chars.issuperset(value):
|
||||
return value
|
||||
|
||||
@@ -195,7 +172,7 @@ def quote_header_value(
|
||||
return f'"{value}"'
|
||||
|
||||
|
||||
def unquote_header_value(value: str, is_filename: bool | None = None) -> str:
|
||||
def unquote_header_value(value: str) -> str:
|
||||
"""Remove double quotes and decode slash-escaped ``"`` and ``\\`` characters in a
|
||||
header value.
|
||||
|
||||
@@ -203,22 +180,12 @@ def unquote_header_value(value: str, is_filename: bool | None = None) -> str:
|
||||
|
||||
:param value: The header value to unquote.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``is_filename`` parameter is deprecated and will be removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``is_filename`` parameter is removed.
|
||||
"""
|
||||
if is_filename is not None:
|
||||
warnings.warn(
|
||||
"The 'is_filename' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if len(value) >= 2 and value[0] == value[-1] == '"':
|
||||
value = value[1:-1]
|
||||
|
||||
if not is_filename:
|
||||
return value.replace("\\\\", "\\").replace('\\"', '"')
|
||||
return value.replace("\\\\", "\\").replace('\\"', '"')
|
||||
|
||||
return value
|
||||
|
||||
@@ -269,10 +236,7 @@ def dump_options_header(header: str | None, options: t.Mapping[str, t.Any]) -> s
|
||||
return "; ".join(segments)
|
||||
|
||||
|
||||
def dump_header(
|
||||
iterable: dict[str, t.Any] | t.Iterable[t.Any],
|
||||
allow_token: bool | None = None,
|
||||
) -> str:
|
||||
def dump_header(iterable: dict[str, t.Any] | t.Iterable[t.Any]) -> str:
|
||||
"""Produce a header value from a list of items or ``key=value`` pairs, separated by
|
||||
commas ``,``.
|
||||
|
||||
@@ -298,22 +262,12 @@ def dump_header(
|
||||
|
||||
:param iterable: The items to create a header from.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``allow_token`` parameter is deprecated and will be removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``allow_token`` parameter is removed.
|
||||
|
||||
.. versionchanged:: 2.2.3
|
||||
If a key ends with ``*``, its value will not be quoted.
|
||||
"""
|
||||
if allow_token is not None:
|
||||
warnings.warn(
|
||||
"'The 'allow_token' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
allow_token = True
|
||||
|
||||
if isinstance(iterable, dict):
|
||||
items = []
|
||||
|
||||
@@ -323,11 +277,9 @@ def dump_header(
|
||||
elif key[-1] == "*":
|
||||
items.append(f"{key}={value}")
|
||||
else:
|
||||
items.append(
|
||||
f"{key}={quote_header_value(value, allow_token=allow_token)}"
|
||||
)
|
||||
items.append(f"{key}={quote_header_value(value)}")
|
||||
else:
|
||||
items = [quote_header_value(x, allow_token=allow_token) for x in iterable]
|
||||
items = [quote_header_value(x) for x in iterable]
|
||||
|
||||
return ", ".join(items)
|
||||
|
||||
@@ -372,7 +324,7 @@ def parse_list_header(value: str) -> list[str]:
|
||||
return result
|
||||
|
||||
|
||||
def parse_dict_header(value: str, cls: type[dict] | None = None) -> dict[str, str]:
|
||||
def parse_dict_header(value: str) -> dict[str, str | None]:
|
||||
"""Parse a list header using :func:`parse_list_header`, then parse each item as a
|
||||
``key=value`` pair.
|
||||
|
||||
@@ -391,36 +343,19 @@ def parse_dict_header(value: str, cls: type[dict] | None = None) -> dict[str, st
|
||||
|
||||
:param value: The header value to parse.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Passing bytes is not supported.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``cls`` argument is removed.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Added support for ``key*=charset''value`` encoded items.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Passing bytes is deprecated, support will be removed in Werkzeug 3.0.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``cls`` argument is deprecated and will be removed in Werkzeug 3.0.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
The ``cls`` argument was added.
|
||||
"""
|
||||
if cls is None:
|
||||
cls = dict
|
||||
else:
|
||||
warnings.warn(
|
||||
"The 'cls' parameter is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
result = cls()
|
||||
|
||||
if isinstance(value, bytes):
|
||||
warnings.warn(
|
||||
"Passing bytes is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
value = value.decode("latin1")
|
||||
result: dict[str, str | None] = {}
|
||||
|
||||
for item in parse_list_header(value):
|
||||
key, has_value, value = item.partition("=")
|
||||
@@ -815,65 +750,6 @@ def parse_set_header(
|
||||
return ds.HeaderSet(parse_list_header(value), on_update)
|
||||
|
||||
|
||||
def parse_authorization_header(
|
||||
value: str | None,
|
||||
) -> ds.Authorization | None:
|
||||
"""Parse an HTTP basic/digest authorization header transmitted by the web
|
||||
browser. The return value is either `None` if the header was invalid or
|
||||
not given, otherwise an :class:`~werkzeug.datastructures.Authorization`
|
||||
object.
|
||||
|
||||
:param value: the authorization header to parse.
|
||||
:return: a :class:`~werkzeug.datastructures.Authorization` object or `None`.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Use :meth:`.Authorization.from_header` instead.
|
||||
"""
|
||||
from .datastructures import Authorization
|
||||
|
||||
warnings.warn(
|
||||
"'parse_authorization_header' is deprecated and will be removed in Werkzeug"
|
||||
" 2.4. Use 'Authorization.from_header' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return Authorization.from_header(value)
|
||||
|
||||
|
||||
def parse_www_authenticate_header(
|
||||
value: str | None,
|
||||
on_update: t.Callable[[ds.WWWAuthenticate], None] | None = None,
|
||||
) -> ds.WWWAuthenticate:
|
||||
"""Parse an HTTP WWW-Authenticate header into a
|
||||
:class:`~werkzeug.datastructures.WWWAuthenticate` object.
|
||||
|
||||
:param value: a WWW-Authenticate header to parse.
|
||||
:param on_update: an optional callable that is called every time a value
|
||||
on the :class:`~werkzeug.datastructures.WWWAuthenticate`
|
||||
object is changed.
|
||||
:return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Use :meth:`.WWWAuthenticate.from_header`
|
||||
instead.
|
||||
"""
|
||||
from .datastructures.auth import WWWAuthenticate
|
||||
|
||||
warnings.warn(
|
||||
"'parse_www_authenticate_header' is deprecated and will be removed in Werkzeug"
|
||||
" 2.4. Use 'WWWAuthenticate.from_header' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
rv = WWWAuthenticate.from_header(value)
|
||||
|
||||
if rv is None:
|
||||
rv = WWWAuthenticate("basic")
|
||||
|
||||
rv._on_update = on_update
|
||||
return rv
|
||||
|
||||
|
||||
def parse_if_range_header(value: str | None) -> ds.IfRange:
|
||||
"""Parses an if-range header which can be an etag or a date. Returns
|
||||
a :class:`~werkzeug.datastructures.IfRange` object.
|
||||
@@ -1284,8 +1160,6 @@ def is_hop_by_hop_header(header: str) -> bool:
|
||||
|
||||
def parse_cookie(
|
||||
header: WSGIEnvironment | str | None,
|
||||
charset: str | None = None,
|
||||
errors: str | None = None,
|
||||
cls: type[ds.MultiDict] | None = None,
|
||||
) -> ds.MultiDict[str, str]:
|
||||
"""Parse a cookie from a string or WSGI environ.
|
||||
@@ -1300,9 +1174,8 @@ def parse_cookie(
|
||||
:param cls: A dict-like class to store the parsed cookies in.
|
||||
Defaults to :class:`MultiDict`.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and
|
||||
will be removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
Returns a :class:`MultiDict` instead of a ``TypeConversionDict``.
|
||||
@@ -1313,22 +1186,13 @@ def parse_cookie(
|
||||
"""
|
||||
if isinstance(header, dict):
|
||||
cookie = header.get("HTTP_COOKIE")
|
||||
elif isinstance(header, bytes):
|
||||
warnings.warn(
|
||||
"Passing bytes is deprecated and will not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
cookie = header.decode()
|
||||
else:
|
||||
cookie = header
|
||||
|
||||
if cookie:
|
||||
cookie = cookie.encode("latin1").decode()
|
||||
|
||||
return _sansio_http.parse_cookie(
|
||||
cookie=cookie, charset=charset, errors=errors, cls=cls
|
||||
)
|
||||
return _sansio_http.parse_cookie(cookie=cookie, cls=cls)
|
||||
|
||||
|
||||
_cookie_no_quote_re = re.compile(r"[\w!#$%&'()*+\-./:<=>?@\[\]^`{|}~]*", re.A)
|
||||
@@ -1349,7 +1213,6 @@ def dump_cookie(
|
||||
domain: str | None = None,
|
||||
secure: bool = False,
|
||||
httponly: bool = False,
|
||||
charset: str | None = None,
|
||||
sync_expires: bool = True,
|
||||
max_size: int = 4093,
|
||||
samesite: str | None = None,
|
||||
@@ -1392,6 +1255,9 @@ def dump_cookie(
|
||||
|
||||
.. _`cookie`: http://browsercookielimits.squawky.net/
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Passing bytes, and the ``charset`` parameter, were removed.
|
||||
|
||||
.. versionchanged:: 2.3.3
|
||||
The ``path`` parameter is ``/`` by default.
|
||||
|
||||
@@ -1405,46 +1271,14 @@ def dump_cookie(
|
||||
.. versionchanged:: 2.3
|
||||
The ``path`` parameter is ``None`` by default.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Passing bytes, and the ``charset`` parameter, are deprecated and will be removed
|
||||
in Werkzeug 3.0.
|
||||
|
||||
.. versionchanged:: 1.0.0
|
||||
The string ``'None'`` is accepted for ``samesite``.
|
||||
"""
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be removed"
|
||||
" in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
if isinstance(key, bytes):
|
||||
warnings.warn(
|
||||
"The 'key' parameter must be a string. Bytes are deprecated"
|
||||
" and will not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
key = key.decode()
|
||||
|
||||
if isinstance(value, bytes):
|
||||
warnings.warn(
|
||||
"The 'value' parameter must be a string. Bytes are"
|
||||
" deprecated and will not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
value = value.decode()
|
||||
|
||||
if path is not None:
|
||||
# safe = https://url.spec.whatwg.org/#url-path-segment-string
|
||||
# as well as percent for things that are already quoted
|
||||
# excluding semicolon since it's part of the header syntax
|
||||
path = quote(path, safe="%!$&'()*+,/:=@", encoding=charset)
|
||||
path = quote(path, safe="%!$&'()*+,/:=@")
|
||||
|
||||
if domain:
|
||||
domain = domain.partition(":")[0].lstrip(".").encode("idna").decode("ascii")
|
||||
@@ -1469,7 +1303,7 @@ def dump_cookie(
|
||||
if not _cookie_no_quote_re.fullmatch(value):
|
||||
# Work with bytes here, since a UTF-8 character could be multiple bytes.
|
||||
value = _cookie_slash_re.sub(
|
||||
lambda m: _cookie_slash_map[m.group()], value.encode(charset)
|
||||
lambda m: _cookie_slash_map[m.group()], value.encode()
|
||||
).decode("ascii")
|
||||
value = f'"{value}"'
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -44,11 +44,16 @@ class ProfilerMiddleware:
|
||||
|
||||
- ``{method}`` - The request method; GET, POST, etc.
|
||||
- ``{path}`` - The request path or 'root' should one not exist.
|
||||
- ``{elapsed}`` - The elapsed time of the request.
|
||||
- ``{elapsed}`` - The elapsed time of the request in milliseconds.
|
||||
- ``{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.
|
||||
If it is a callable, it will be called with the WSGI ``environ`` and
|
||||
be expected to return a filename string. The ``environ`` dictionary
|
||||
will also have the ``"werkzeug.profiler"`` key populated with a
|
||||
dictionary containing the following fields (more may be added in the
|
||||
future):
|
||||
- ``{elapsed}`` - The elapsed time of the request in milliseconds.
|
||||
- ``{time}`` - The time of the request.
|
||||
|
||||
:param app: The WSGI application to wrap.
|
||||
:param stream: Write stats to this stream. Disable with ``None``.
|
||||
@@ -65,6 +70,10 @@ class ProfilerMiddleware:
|
||||
from werkzeug.middleware.profiler import ProfilerMiddleware
|
||||
app = ProfilerMiddleware(app)
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
Added the ``"werkzeug.profiler"`` key to the ``filename_format(environ)``
|
||||
parameter with the ``elapsed`` and ``time`` fields.
|
||||
|
||||
.. versionchanged:: 0.15
|
||||
Stats are written even if ``profile_dir`` is given, and can be
|
||||
disable by passing ``stream=None``.
|
||||
@@ -118,6 +127,10 @@ class ProfilerMiddleware:
|
||||
|
||||
if self._profile_dir is not None:
|
||||
if callable(self._filename_format):
|
||||
environ["werkzeug.profiler"] = {
|
||||
"elapsed": elapsed * 1000.0,
|
||||
"time": time.time(),
|
||||
}
|
||||
filename = self._filename_format(environ)
|
||||
else:
|
||||
filename = self._filename_format.format(
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -3,7 +3,6 @@ from __future__ import annotations
|
||||
import re
|
||||
import typing as t
|
||||
import uuid
|
||||
import warnings
|
||||
from urllib.parse import quote
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
@@ -42,17 +41,8 @@ class BaseConverter:
|
||||
return value
|
||||
|
||||
def to_url(self, value: t.Any) -> str:
|
||||
if isinstance(value, (bytes, bytearray)):
|
||||
warnings.warn(
|
||||
"Passing bytes as a URL value is deprecated and will not be supported"
|
||||
" in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=7,
|
||||
)
|
||||
return quote(value, safe="!$&'()*+,/:;=@")
|
||||
|
||||
# safe = https://url.spec.whatwg.org/#url-path-segment-string
|
||||
return quote(str(value), encoding=self.map.charset, safe="!$&'()*+,/:;=@")
|
||||
return quote(str(value), safe="!$&'()*+,/:;=@")
|
||||
|
||||
|
||||
class UnicodeConverter(BaseConverter):
|
||||
@@ -129,6 +119,7 @@ class PathConverter(BaseConverter):
|
||||
:param map: the :class:`Map`.
|
||||
"""
|
||||
|
||||
part_isolating = False
|
||||
regex = "[^/].*?"
|
||||
weight = 200
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ class Map:
|
||||
:param rules: sequence of url rules for this map.
|
||||
:param default_subdomain: The default subdomain for rules without a
|
||||
subdomain defined.
|
||||
:param charset: charset of the url. defaults to ``"utf-8"``
|
||||
:param strict_slashes: If a rule ends with a slash but the matched
|
||||
URL does not, redirect to the URL with a trailing slash.
|
||||
:param merge_slashes: Merge consecutive slashes when matching or
|
||||
@@ -62,15 +61,13 @@ class Map:
|
||||
:param sort_parameters: If set to `True` the url parameters are sorted.
|
||||
See `url_encode` for more details.
|
||||
:param sort_key: The sort key function for `url_encode`.
|
||||
:param encoding_errors: the error method to use for decoding
|
||||
:param host_matching: if set to `True` it enables the host matching
|
||||
feature and disables the subdomain one. If
|
||||
enabled the `host` parameter to rules is used
|
||||
instead of the `subdomain` one.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` and ``encoding_errors`` parameters are deprecated and will be
|
||||
removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` and ``encoding_errors`` parameters were removed.
|
||||
|
||||
.. versionchanged:: 1.0
|
||||
If ``url_scheme`` is ``ws`` or ``wss``, only WebSocket rules will match.
|
||||
@@ -97,14 +94,12 @@ class Map:
|
||||
self,
|
||||
rules: t.Iterable[RuleFactory] | None = None,
|
||||
default_subdomain: str = "",
|
||||
charset: str | None = None,
|
||||
strict_slashes: bool = True,
|
||||
merge_slashes: bool = True,
|
||||
redirect_defaults: bool = True,
|
||||
converters: t.Mapping[str, type[BaseConverter]] | None = None,
|
||||
sort_parameters: bool = False,
|
||||
sort_key: t.Callable[[t.Any], t.Any] | None = None,
|
||||
encoding_errors: str | None = None,
|
||||
host_matching: bool = False,
|
||||
) -> None:
|
||||
self._matcher = StateMachineMatcher(merge_slashes)
|
||||
@@ -113,30 +108,6 @@ class Map:
|
||||
self._remap_lock = self.lock_class()
|
||||
|
||||
self.default_subdomain = default_subdomain
|
||||
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
self.charset = charset
|
||||
|
||||
if encoding_errors is not None:
|
||||
warnings.warn(
|
||||
"The 'encoding_errors' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
encoding_errors = "replace"
|
||||
|
||||
self.encoding_errors = encoding_errors
|
||||
self.strict_slashes = strict_slashes
|
||||
self.merge_slashes = merge_slashes
|
||||
self.redirect_defaults = redirect_defaults
|
||||
@@ -362,7 +333,7 @@ class Map:
|
||||
def _get_wsgi_string(name: str) -> str | None:
|
||||
val = env.get(name)
|
||||
if val is not None:
|
||||
return _wsgi_decoding_dance(val, self.charset)
|
||||
return _wsgi_decoding_dance(val)
|
||||
return None
|
||||
|
||||
script_name = _get_wsgi_string("SCRIPT_NAME")
|
||||
@@ -629,9 +600,7 @@ class MapAdapter:
|
||||
result = self.map._matcher.match(domain_part, path_part, method, websocket)
|
||||
except RequestPath as e:
|
||||
# safe = https://url.spec.whatwg.org/#url-path-segment-string
|
||||
new_path = quote(
|
||||
e.path_info, safe="!$&'()*+,/:;=@", encoding=self.map.charset
|
||||
)
|
||||
new_path = quote(e.path_info, safe="!$&'()*+,/:;=@")
|
||||
raise RequestRedirect(
|
||||
self.make_redirect_url(new_path, query_args)
|
||||
) from None
|
||||
@@ -767,7 +736,7 @@ class MapAdapter:
|
||||
|
||||
def encode_query_args(self, query_args: t.Mapping[str, t.Any] | str) -> str:
|
||||
if not isinstance(query_args, str):
|
||||
return _urlencode(query_args, encoding=self.map.charset)
|
||||
return _urlencode(query_args)
|
||||
return query_args
|
||||
|
||||
def make_redirect_url(
|
||||
|
||||
@@ -583,7 +583,7 @@ class Rule(RuleFactory):
|
||||
if self.map.sort_parameters:
|
||||
items = sorted(items, key=self.map.sort_key)
|
||||
|
||||
return _urlencode(items, encoding=self.map.charset)
|
||||
return _urlencode(items)
|
||||
|
||||
def _parse_rule(self, rule: str) -> t.Iterable[RulePart]:
|
||||
content = ""
|
||||
@@ -739,12 +739,7 @@ class Rule(RuleFactory):
|
||||
opl.append((False, data))
|
||||
elif not is_dynamic:
|
||||
# safe = https://url.spec.whatwg.org/#url-path-segment-string
|
||||
opl.append(
|
||||
(
|
||||
False,
|
||||
quote(data, safe="!$&'()*+,/:;=@", encoding=self.map.charset),
|
||||
)
|
||||
)
|
||||
opl.append((False, quote(data, safe="!$&'()*+,/:;=@")))
|
||||
else:
|
||||
opl.append((True, data))
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
|
||||
from .._internal import _dt_as_utc
|
||||
@@ -123,8 +122,6 @@ def _cookie_unslash_replace(m: t.Match[bytes]) -> bytes:
|
||||
|
||||
def parse_cookie(
|
||||
cookie: str | None = None,
|
||||
charset: str | None = None,
|
||||
errors: str | None = None,
|
||||
cls: type[ds.MultiDict] | None = None,
|
||||
) -> ds.MultiDict[str, str]:
|
||||
"""Parse a cookie from a string.
|
||||
@@ -138,42 +135,14 @@ def parse_cookie(
|
||||
:param cls: A dict-like class to store the parsed cookies in.
|
||||
Defaults to :class:`MultiDict`.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
Passing bytes, and the ``charset`` and ``errors`` parameters, are deprecated and
|
||||
will be removed in Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
Passing bytes, and the ``charset`` and ``errors`` parameters, were removed.
|
||||
|
||||
.. versionadded:: 2.2
|
||||
"""
|
||||
if cls is None:
|
||||
cls = ds.MultiDict
|
||||
|
||||
if isinstance(cookie, bytes):
|
||||
warnings.warn(
|
||||
"The 'cookie' parameter must be a string. Passing bytes is deprecated and"
|
||||
" will not be supported in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
cookie = cookie.decode()
|
||||
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
if errors is not None:
|
||||
warnings.warn(
|
||||
"The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
errors = "replace"
|
||||
|
||||
if not cookie:
|
||||
return cls()
|
||||
|
||||
@@ -191,7 +160,7 @@ def parse_cookie(
|
||||
# Work with bytes here, since a UTF-8 character could be multiple bytes.
|
||||
cv = _cookie_unslash_re.sub(
|
||||
_cookie_unslash_replace, cv[1:-1].encode()
|
||||
).decode(charset, errors)
|
||||
).decode(errors="replace")
|
||||
|
||||
out.append((ck, cv))
|
||||
|
||||
|
||||
@@ -251,12 +251,20 @@ class MultipartDecoder:
|
||||
else:
|
||||
data_start = 0
|
||||
|
||||
if self.buffer.find(b"--" + self.boundary) == -1:
|
||||
boundary = b"--" + self.boundary
|
||||
|
||||
if self.buffer.find(boundary) == -1:
|
||||
# No complete boundary in the buffer, but there may be
|
||||
# a partial boundary at the end. As the boundary
|
||||
# starts with either a nl or cr find the earliest and
|
||||
# return up to that as data.
|
||||
data_end = del_index = self.last_newline(data[data_start:]) + data_start
|
||||
# If amount of data after last newline is far from
|
||||
# possible length of partial boundary, we should
|
||||
# assume that there is no partial boundary in the buffer
|
||||
# and return all pending data.
|
||||
if (len(data) - data_end) > len(b"\n" + boundary):
|
||||
data_end = del_index = len(data)
|
||||
more_data = True
|
||||
else:
|
||||
match = self.boundary_re.search(data)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from urllib.parse import parse_qsl
|
||||
|
||||
@@ -59,95 +58,13 @@ class Request:
|
||||
:param headers: The headers received with the request.
|
||||
:param remote_addr: The address of the client sending the request.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset``, ``url_charset``, and ``encoding_errors`` attributes
|
||||
were removed.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
_charset: str
|
||||
|
||||
@property
|
||||
def charset(self) -> str:
|
||||
"""The charset used to decode body, form, and cookie data. Defaults to UTF-8.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Request data must always be UTF-8.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Interpreting bytes as text in body, form, and cookie data will"
|
||||
" always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._charset
|
||||
|
||||
@charset.setter
|
||||
def charset(self, value: str) -> None:
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Interpreting bytes as text in body, form, and cookie data will"
|
||||
" always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._charset = value
|
||||
|
||||
_encoding_errors: str
|
||||
|
||||
@property
|
||||
def encoding_errors(self) -> str:
|
||||
"""How errors when decoding bytes are handled. Defaults to "replace".
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'encoding_errors' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._encoding_errors
|
||||
|
||||
@encoding_errors.setter
|
||||
def encoding_errors(self, value: str) -> None:
|
||||
warnings.warn(
|
||||
"The 'encoding_errors' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._encoding_errors = value
|
||||
|
||||
_url_charset: str
|
||||
|
||||
@property
|
||||
def url_charset(self) -> str:
|
||||
"""The charset to use when decoding percent-encoded bytes in :attr:`args`.
|
||||
Defaults to the value of :attr:`charset`, which defaults to UTF-8.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'url_charset' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._url_charset
|
||||
|
||||
@url_charset.setter
|
||||
def url_charset(self, value: str) -> None:
|
||||
warnings.warn(
|
||||
"The 'url_charset' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._url_charset = value
|
||||
|
||||
#: 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
|
||||
@@ -209,40 +126,6 @@ class Request:
|
||||
headers: Headers,
|
||||
remote_addr: str | None,
|
||||
) -> None:
|
||||
if not isinstance(type(self).charset, property):
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Interpreting bytes as text in body, form, and cookie data will"
|
||||
" always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._charset = self.charset
|
||||
else:
|
||||
self._charset = "utf-8"
|
||||
|
||||
if not isinstance(type(self).encoding_errors, property):
|
||||
warnings.warn(
|
||||
"The 'encoding_errors' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._encoding_errors = self.encoding_errors
|
||||
else:
|
||||
self._encoding_errors = "replace"
|
||||
|
||||
if not isinstance(type(self).url_charset, property):
|
||||
warnings.warn(
|
||||
"The 'url_charset' attribute is deprecated and will not be used in"
|
||||
" Werkzeug 3.0. Percent-encoded bytes must always be UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._url_charset = self.url_charset
|
||||
else:
|
||||
self._url_charset = self._charset
|
||||
|
||||
#: The method the request was made with, such as ``GET``.
|
||||
self.method = method.upper()
|
||||
#: The URL scheme of the protocol the request used, such as
|
||||
@@ -291,7 +174,6 @@ class Request:
|
||||
parse_qsl(
|
||||
self.query_string.decode(),
|
||||
keep_blank_values=True,
|
||||
encoding=self._url_charset,
|
||||
errors="werkzeug.url_quote",
|
||||
)
|
||||
)
|
||||
@@ -360,13 +242,8 @@ class Request:
|
||||
"""A :class:`dict` with the contents of all cookies transmitted with
|
||||
the request."""
|
||||
wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie"))
|
||||
charset = self._charset if self._charset != "utf-8" else None
|
||||
errors = self._encoding_errors if self._encoding_errors != "replace" else None
|
||||
return parse_cookie( # type: ignore
|
||||
wsgi_combined_cookie,
|
||||
charset=charset,
|
||||
errors=errors,
|
||||
cls=self.dict_storage_class,
|
||||
wsgi_combined_cookie, cls=self.dict_storage_class
|
||||
)
|
||||
|
||||
# Common Descriptors
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import typing as t
|
||||
import warnings
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from datetime import timezone
|
||||
@@ -81,36 +80,12 @@ class Response:
|
||||
:param content_type: The full content type of the response.
|
||||
Overrides building the value from ``mimetype``.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` attribute was removed.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
_charset: str
|
||||
|
||||
@property
|
||||
def charset(self) -> str:
|
||||
"""The charset used to encode body and cookie data. Defaults to UTF-8.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0. Response data must always be UTF-8.
|
||||
"""
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Text in body and cookie data will always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return self._charset
|
||||
|
||||
@charset.setter
|
||||
def charset(self, value: str) -> None:
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Text in body and cookie data will always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._charset = value
|
||||
|
||||
#: the default status if none is provided.
|
||||
default_status = 200
|
||||
|
||||
@@ -139,17 +114,6 @@ class Response:
|
||||
mimetype: str | None = None,
|
||||
content_type: str | None = None,
|
||||
) -> None:
|
||||
if not isinstance(type(self).charset, property):
|
||||
warnings.warn(
|
||||
"The 'charset' attribute is deprecated and will not be used in Werkzeug"
|
||||
" 2.4. Text in body and cookie data will always use UTF-8.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
self._charset = self.charset
|
||||
else:
|
||||
self._charset = "utf-8"
|
||||
|
||||
if isinstance(headers, Headers):
|
||||
self.headers = headers
|
||||
elif not headers:
|
||||
@@ -161,7 +125,7 @@ class Response:
|
||||
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)
|
||||
mimetype = get_content_type(mimetype, "utf-8")
|
||||
content_type = mimetype
|
||||
if content_type is not None:
|
||||
self.headers["Content-Type"] = content_type
|
||||
@@ -255,7 +219,6 @@ class Response:
|
||||
:param samesite: Limit the scope of the cookie to only be
|
||||
attached to requests that are "same-site".
|
||||
"""
|
||||
charset = self._charset if self._charset != "utf-8" else None
|
||||
self.headers.add(
|
||||
"Set-Cookie",
|
||||
dump_cookie(
|
||||
@@ -267,7 +230,6 @@ class Response:
|
||||
domain=domain,
|
||||
secure=secure,
|
||||
httponly=httponly,
|
||||
charset=charset,
|
||||
max_size=self.max_cookie_size,
|
||||
samesite=samesite,
|
||||
),
|
||||
@@ -332,7 +294,7 @@ class Response:
|
||||
|
||||
@mimetype.setter
|
||||
def mimetype(self, value: str) -> None:
|
||||
self.headers["Content-Type"] = get_content_type(value, self._charset)
|
||||
self.headers["Content-Type"] = get_content_type(value, "utf-8")
|
||||
|
||||
@property
|
||||
def mimetype_params(self) -> dict[str, str]:
|
||||
|
||||
@@ -5,7 +5,6 @@ import hmac
|
||||
import os
|
||||
import posixpath
|
||||
import secrets
|
||||
import warnings
|
||||
|
||||
SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
DEFAULT_PBKDF2_ITERATIONS = 600000
|
||||
@@ -24,14 +23,6 @@ def gen_salt(length: int) -> str:
|
||||
|
||||
|
||||
def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
|
||||
if method == "plain":
|
||||
warnings.warn(
|
||||
"The 'plain' password method is deprecated and will be removed in"
|
||||
" Werkzeug 3.0. Migrate to the 'scrypt' method.",
|
||||
stacklevel=3,
|
||||
)
|
||||
return password, method
|
||||
|
||||
method, *args = method.split(":")
|
||||
salt = salt.encode("utf-8")
|
||||
password = password.encode("utf-8")
|
||||
@@ -72,26 +63,20 @@ def _hash_internal(method: str, salt: str, password: str) -> tuple[str, str]:
|
||||
f"pbkdf2:{hash_name}:{iterations}",
|
||||
)
|
||||
else:
|
||||
warnings.warn(
|
||||
f"The '{method}' password method is deprecated and will be removed in"
|
||||
" Werkzeug 3.0. Migrate to the 'scrypt' method.",
|
||||
stacklevel=3,
|
||||
)
|
||||
return hmac.new(salt, password, method).hexdigest(), method
|
||||
raise ValueError(f"Invalid hash method '{method}'.")
|
||||
|
||||
|
||||
def generate_password_hash(
|
||||
password: str, method: str = "pbkdf2", salt_length: int = 16
|
||||
password: str, method: str = "scrypt", salt_length: int = 16
|
||||
) -> str:
|
||||
"""Securely hash a password for storage. A password can be compared to a stored hash
|
||||
using :func:`check_password_hash`.
|
||||
|
||||
The following methods are supported:
|
||||
|
||||
- ``scrypt``, more secure but not available on PyPy. The parameters are ``n``,
|
||||
``r``, and ``p``, the default is ``scrypt:32768:8:1``. See
|
||||
:func:`hashlib.scrypt`.
|
||||
- ``pbkdf2``, the default. The parameters are ``hash_method`` and ``iterations``,
|
||||
- ``scrypt``, the default. The parameters are ``n``, ``r``, and ``p``, the default
|
||||
is ``scrypt:32768:8:1``. See :func:`hashlib.scrypt`.
|
||||
- ``pbkdf2``, less secure. The parameters are ``hash_method`` and ``iterations``,
|
||||
the default is ``pbkdf2:sha256:600000``. See :func:`hashlib.pbkdf2_hmac`.
|
||||
|
||||
Default parameters may be updated to reflect current guidelines, and methods may be
|
||||
|
||||
@@ -154,9 +154,7 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
|
||||
|
||||
@property
|
||||
def server_version(self) -> str: # type: ignore
|
||||
from . import __version__
|
||||
|
||||
return f"Werkzeug/{__version__}"
|
||||
return self.server._server_version
|
||||
|
||||
def make_environ(self) -> WSGIEnvironment:
|
||||
request_url = urlsplit(self.path)
|
||||
@@ -764,7 +762,7 @@ class BaseWSGIServer(HTTPServer):
|
||||
if sys.platform == "darwin" and port == 5000:
|
||||
print(
|
||||
"On macOS, try disabling the 'AirPlay Receiver' service"
|
||||
" from System Preferences -> Sharing.",
|
||||
" from System Preferences -> General -> AirDrop & Handoff.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
|
||||
@@ -796,6 +794,10 @@ class BaseWSGIServer(HTTPServer):
|
||||
else:
|
||||
self.ssl_context = None
|
||||
|
||||
import importlib.metadata
|
||||
|
||||
self._server_version = f"Werkzeug/{importlib.metadata.version('werkzeug')}"
|
||||
|
||||
def log(self, type: str, message: str, *args: t.Any) -> None:
|
||||
_log(type, message, *args)
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import dataclasses
|
||||
import mimetypes
|
||||
import sys
|
||||
import typing as t
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from datetime import datetime
|
||||
from io import BytesIO
|
||||
@@ -17,7 +16,6 @@ from urllib.parse import urlsplit
|
||||
from urllib.parse import urlunsplit
|
||||
|
||||
from ._internal import _get_environ
|
||||
from ._internal import _make_encode_wrapper
|
||||
from ._internal import _wsgi_decoding_dance
|
||||
from ._internal import _wsgi_encoding_dance
|
||||
from .datastructures import Authorization
|
||||
@@ -58,24 +56,14 @@ def stream_encode_multipart(
|
||||
use_tempfile: bool = True,
|
||||
threshold: int = 1024 * 500,
|
||||
boundary: str | None = None,
|
||||
charset: str | None = None,
|
||||
) -> tuple[t.IO[bytes], int, str]:
|
||||
"""Encode a dict of values (either strings or file descriptors or
|
||||
:class:`FileStorage` objects.) into a multipart encoded string stored
|
||||
in a file descriptor.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` parameter was removed.
|
||||
"""
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be removed in Werkzeug 3.0",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
if boundary is None:
|
||||
boundary = f"---------------WerkzeugFormPart_{time()}{random()}"
|
||||
|
||||
@@ -144,9 +132,7 @@ def stream_encode_multipart(
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
write_binary(encoder.send_event(Field(name=key, headers=Headers())))
|
||||
write_binary(
|
||||
encoder.send_event(Data(data=value.encode(charset), more_data=False))
|
||||
)
|
||||
write_binary(encoder.send_event(Data(data=value.encode(), more_data=False)))
|
||||
|
||||
write_binary(encoder.send_event(Epilogue(data=b"")))
|
||||
|
||||
@@ -156,18 +142,16 @@ def stream_encode_multipart(
|
||||
|
||||
|
||||
def encode_multipart(
|
||||
values: t.Mapping[str, t.Any],
|
||||
boundary: str | None = None,
|
||||
charset: str | None = None,
|
||||
values: t.Mapping[str, t.Any], boundary: str | None = None
|
||||
) -> tuple[str, bytes]:
|
||||
"""Like `stream_encode_multipart` but returns a tuple in the form
|
||||
(``boundary``, ``data``) where data is bytes.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` parameter was removed.
|
||||
"""
|
||||
stream, length, boundary = stream_encode_multipart(
|
||||
values, use_tempfile=False, boundary=boundary, charset=charset
|
||||
values, use_tempfile=False, boundary=boundary
|
||||
)
|
||||
return boundary, stream.read()
|
||||
|
||||
@@ -259,8 +243,8 @@ class EnvironBuilder:
|
||||
``Authorization`` header value. A ``(username, password)`` tuple
|
||||
is a shortcut for ``Basic`` authorization.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` parameter is deprecated and will be removed in Werkzeug 3.0
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` parameter was removed.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
``CONTENT_TYPE`` and ``CONTENT_LENGTH`` are not duplicated as
|
||||
@@ -328,35 +312,20 @@ class EnvironBuilder:
|
||||
data: None | (t.IO[bytes] | str | bytes | t.Mapping[str, t.Any]) = None,
|
||||
environ_base: t.Mapping[str, t.Any] | None = None,
|
||||
environ_overrides: t.Mapping[str, t.Any] | None = None,
|
||||
charset: str | None = None,
|
||||
mimetype: str | None = None,
|
||||
json: t.Mapping[str, t.Any] | None = None,
|
||||
auth: Authorization | tuple[str, str] | None = None,
|
||||
) -> None:
|
||||
path_s = _make_encode_wrapper(path)
|
||||
if query_string is not None and path_s("?") in path:
|
||||
if query_string is not None and "?" in path:
|
||||
raise ValueError("Query string is defined in the path and as an argument")
|
||||
request_uri = urlsplit(path)
|
||||
if query_string is None and path_s("?") in path:
|
||||
if query_string is None and "?" in path:
|
||||
query_string = request_uri.query
|
||||
|
||||
if charset is not None:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be"
|
||||
" removed in Werkzeug 3.0",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
self.charset = charset
|
||||
self.path = iri_to_uri(request_uri.path)
|
||||
self.request_uri = path
|
||||
if base_url is not None:
|
||||
base_url = iri_to_uri(
|
||||
base_url, charset=charset if charset != "utf-8" else None
|
||||
)
|
||||
base_url = iri_to_uri(base_url)
|
||||
self.base_url = base_url # type: ignore
|
||||
if isinstance(query_string, str):
|
||||
self.query_string = query_string
|
||||
@@ -409,7 +378,7 @@ class EnvironBuilder:
|
||||
if hasattr(data, "read"):
|
||||
data = data.read()
|
||||
if isinstance(data, str):
|
||||
data = data.encode(self.charset)
|
||||
data = data.encode()
|
||||
if isinstance(data, bytes):
|
||||
self.input_stream = BytesIO(data)
|
||||
if self.content_length is None:
|
||||
@@ -526,7 +495,7 @@ class EnvironBuilder:
|
||||
|
||||
@mimetype.setter
|
||||
def mimetype(self, value: str) -> None:
|
||||
self.content_type = get_content_type(value, self.charset)
|
||||
self.content_type = get_content_type(value, "utf-8")
|
||||
|
||||
@property
|
||||
def mimetype_params(self) -> t.Mapping[str, str]:
|
||||
@@ -628,7 +597,7 @@ class EnvironBuilder:
|
||||
"""
|
||||
if self._query_string is None:
|
||||
if self._args is not None:
|
||||
return _urlencode(self._args, encoding=self.charset)
|
||||
return _urlencode(self._args)
|
||||
return ""
|
||||
return self._query_string
|
||||
|
||||
@@ -716,13 +685,12 @@ class EnvironBuilder:
|
||||
input_stream.seek(start_pos)
|
||||
content_length = end_pos - start_pos
|
||||
elif mimetype == "multipart/form-data":
|
||||
charset = self.charset if self.charset != "utf-8" else None
|
||||
input_stream, content_length, boundary = stream_encode_multipart(
|
||||
CombinedMultiDict([self.form, self.files]), charset=charset
|
||||
CombinedMultiDict([self.form, self.files])
|
||||
)
|
||||
content_type = f'{mimetype}; boundary="{boundary}"'
|
||||
elif mimetype == "application/x-www-form-urlencoded":
|
||||
form_encoded = _urlencode(self.form, encoding=self.charset).encode("ascii")
|
||||
form_encoded = _urlencode(self.form).encode("ascii")
|
||||
content_length = len(form_encoded)
|
||||
input_stream = BytesIO(form_encoded)
|
||||
else:
|
||||
@@ -733,15 +701,15 @@ class EnvironBuilder:
|
||||
result.update(self.environ_base)
|
||||
|
||||
def _path_encode(x: str) -> str:
|
||||
return _wsgi_encoding_dance(unquote(x, encoding=self.charset), self.charset)
|
||||
return _wsgi_encoding_dance(unquote(x))
|
||||
|
||||
raw_uri = _wsgi_encoding_dance(self.request_uri, self.charset)
|
||||
raw_uri = _wsgi_encoding_dance(self.request_uri)
|
||||
result.update(
|
||||
{
|
||||
"REQUEST_METHOD": self.method,
|
||||
"SCRIPT_NAME": _path_encode(self.script_root),
|
||||
"PATH_INFO": _path_encode(self.path),
|
||||
"QUERY_STRING": _wsgi_encoding_dance(self.query_string, self.charset),
|
||||
"QUERY_STRING": _wsgi_encoding_dance(self.query_string),
|
||||
# Non-standard, added by mod_wsgi, uWSGI
|
||||
"REQUEST_URI": raw_uri,
|
||||
# Non-standard, added by gunicorn
|
||||
@@ -857,20 +825,6 @@ class Client:
|
||||
|
||||
self.allow_subdomain_redirects = allow_subdomain_redirects
|
||||
|
||||
@property
|
||||
def cookie_jar(self) -> t.Iterable[Cookie] | None:
|
||||
warnings.warn(
|
||||
"The 'cookie_jar' attribute is a private API and will be removed in"
|
||||
" Werkzeug 3.0. Use the 'get_cookie' method instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if self._cookies is None:
|
||||
return None
|
||||
|
||||
return self._cookies.values()
|
||||
|
||||
def get_cookie(
|
||||
self, key: str, domain: str = "localhost", path: str = "/"
|
||||
) -> Cookie | None:
|
||||
@@ -894,7 +848,7 @@ class Client:
|
||||
self,
|
||||
key: str,
|
||||
value: str = "",
|
||||
*args: t.Any,
|
||||
*,
|
||||
domain: str = "localhost",
|
||||
origin_only: bool = True,
|
||||
path: str = "/",
|
||||
@@ -920,34 +874,21 @@ class Client:
|
||||
or as a prefix.
|
||||
:param kwargs: Passed to :func:`.dump_cookie`.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The parameter ``server_name`` is removed. The first parameter is
|
||||
``key``. Use the ``domain`` and ``origin_only`` parameters instead.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``origin_only`` parameter was added.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``domain`` parameter defaults to ``localhost``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The first parameter ``server_name`` is deprecated and will be removed in
|
||||
Werkzeug 3.0. The first parameter is ``key``. Use the ``domain`` and
|
||||
``origin_only`` parameters instead.
|
||||
"""
|
||||
if self._cookies is None:
|
||||
raise TypeError(
|
||||
"Cookies are disabled. Create a client with 'use_cookies=True'."
|
||||
)
|
||||
|
||||
if args:
|
||||
warnings.warn(
|
||||
"The first parameter 'server_name' is no longer used, and will be"
|
||||
" removed in Werkzeug 3.0. The positional parameters are 'key' and"
|
||||
" 'value'. Use the 'domain' and 'origin_only' parameters instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
domain = key
|
||||
key = value
|
||||
value = args[0]
|
||||
|
||||
cookie = Cookie._from_response_header(
|
||||
domain, "/", dump_cookie(key, value, domain=domain, path=path, **kwargs)
|
||||
)
|
||||
@@ -961,10 +902,9 @@ class Client:
|
||||
def delete_cookie(
|
||||
self,
|
||||
key: str,
|
||||
*args: t.Any,
|
||||
*,
|
||||
domain: str = "localhost",
|
||||
path: str = "/",
|
||||
**kwargs: t.Any,
|
||||
) -> None:
|
||||
"""Delete a cookie if it exists. Cookies are uniquely identified by
|
||||
``(domain, path, key)``.
|
||||
@@ -973,44 +913,21 @@ class Client:
|
||||
:param domain: The domain the cookie was set for.
|
||||
:param path: The path the cookie was set for.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``server_name`` parameter is removed. The first parameter is
|
||||
``key``. Use the ``domain`` parameter instead.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``secure``, ``httponly`` and ``samesite`` parameters are removed.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``domain`` parameter defaults to ``localhost``.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The first parameter ``server_name`` is deprecated and will be removed in
|
||||
Werkzeug 3.0. The first parameter is ``key``. Use the ``domain`` parameter
|
||||
instead.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``secure``, ``httponly`` and ``samesite`` parameters are deprecated and
|
||||
will be removed in Werkzeug 2.4.
|
||||
"""
|
||||
if self._cookies is None:
|
||||
raise TypeError(
|
||||
"Cookies are disabled. Create a client with 'use_cookies=True'."
|
||||
)
|
||||
|
||||
if args:
|
||||
warnings.warn(
|
||||
"The first parameter 'server_name' is no longer used, and will be"
|
||||
" removed in Werkzeug 2.4. The first parameter is 'key'. Use the"
|
||||
" 'domain' parameter instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
domain = key
|
||||
key = args[0]
|
||||
|
||||
if kwargs:
|
||||
kwargs_keys = ", ".join(f"'{k}'" for k in kwargs)
|
||||
plural = "parameters are" if len(kwargs) > 1 else "parameter is"
|
||||
warnings.warn(
|
||||
f"The {kwargs_keys} {plural} deprecated and will be"
|
||||
f" removed in Werkzeug 2.4.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
self._cookies.pop((domain, path, key), None)
|
||||
|
||||
def _add_cookies_to_wsgi(self, environ: WSGIEnvironment) -> None:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -50,6 +50,10 @@ class Request(_SansIORequest):
|
||||
prevent consuming the form data in middleware, which would make
|
||||
it unavailable to the final application.
|
||||
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset``, ``url_charset``, and ``encoding_errors`` parameters
|
||||
were removed.
|
||||
|
||||
.. versionchanged:: 2.1
|
||||
Old ``BaseRequest`` and mixin classes were removed.
|
||||
|
||||
@@ -145,9 +149,6 @@ class Request(_SansIORequest):
|
||||
"""
|
||||
from ..test import EnvironBuilder
|
||||
|
||||
kwargs.setdefault(
|
||||
"charset", cls.charset if not isinstance(cls.charset, property) else None
|
||||
)
|
||||
builder = EnvironBuilder(*args, **kwargs)
|
||||
try:
|
||||
return builder.get_request(cls)
|
||||
@@ -240,12 +241,8 @@ class Request(_SansIORequest):
|
||||
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
charset = self._charset if self._charset != "utf-8" else None
|
||||
errors = self._encoding_errors if self._encoding_errors != "replace" else None
|
||||
return self.form_data_parser_class(
|
||||
stream_factory=self._get_file_stream,
|
||||
charset=charset,
|
||||
errors=errors,
|
||||
max_form_memory_size=self.max_form_memory_size,
|
||||
max_content_length=self.max_content_length,
|
||||
max_form_parts=self.max_form_parts,
|
||||
@@ -424,7 +421,7 @@ class Request(_SansIORequest):
|
||||
if cache:
|
||||
self._cached_data = rv
|
||||
if as_text:
|
||||
rv = rv.decode(self._charset, self._encoding_errors)
|
||||
rv = rv.decode(errors="replace")
|
||||
return rv
|
||||
|
||||
@cached_property
|
||||
|
||||
@@ -28,10 +28,10 @@ if t.TYPE_CHECKING:
|
||||
from .request import Request
|
||||
|
||||
|
||||
def _iter_encoded(iterable: t.Iterable[str | bytes], charset: str) -> t.Iterator[bytes]:
|
||||
def _iter_encoded(iterable: t.Iterable[str | bytes]) -> t.Iterator[bytes]:
|
||||
for item in iterable:
|
||||
if isinstance(item, str):
|
||||
yield item.encode(charset)
|
||||
yield item.encode()
|
||||
else:
|
||||
yield item
|
||||
|
||||
@@ -284,7 +284,7 @@ class Response(_SansIOResponse):
|
||||
rv = b"".join(self.iter_encoded())
|
||||
|
||||
if as_text:
|
||||
return rv.decode(self._charset)
|
||||
return rv.decode()
|
||||
|
||||
return rv
|
||||
|
||||
@@ -296,7 +296,7 @@ class Response(_SansIOResponse):
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if isinstance(value, str):
|
||||
value = value.encode(self._charset)
|
||||
value = value.encode()
|
||||
self.response = [value]
|
||||
if self.automatically_set_content_length:
|
||||
self.headers["Content-Length"] = str(len(value))
|
||||
@@ -366,7 +366,7 @@ class Response(_SansIOResponse):
|
||||
# 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)
|
||||
return _iter_encoded(self.response)
|
||||
|
||||
@property
|
||||
def is_streamed(self) -> bool:
|
||||
@@ -832,4 +832,4 @@ class ResponseStream:
|
||||
|
||||
@property
|
||||
def encoding(self) -> str:
|
||||
return self.response._charset
|
||||
return "utf-8"
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import re
|
||||
import typing as t
|
||||
import warnings
|
||||
from functools import partial
|
||||
from functools import update_wrapper
|
||||
from itertools import chain
|
||||
|
||||
from ._internal import _make_encode_wrapper
|
||||
from ._internal import _to_bytes
|
||||
from ._internal import _to_str
|
||||
from .exceptions import ClientDisconnected
|
||||
from .exceptions import RequestEntityTooLarge
|
||||
from .sansio import utils as _sansio_utils
|
||||
@@ -200,45 +194,18 @@ def get_input_stream(
|
||||
return t.cast(t.IO[bytes], LimitedStream(stream, content_length))
|
||||
|
||||
|
||||
def get_path_info(
|
||||
environ: WSGIEnvironment,
|
||||
charset: t.Any = ...,
|
||||
errors: str | None = None,
|
||||
) -> str:
|
||||
def get_path_info(environ: WSGIEnvironment) -> str:
|
||||
"""Return ``PATH_INFO`` from the WSGI environment.
|
||||
|
||||
:param environ: WSGI environment to get the path from.
|
||||
|
||||
.. versionchanged:: 2.3
|
||||
The ``charset`` and ``errors`` parameters are deprecated and will be removed in
|
||||
Werkzeug 3.0.
|
||||
.. versionchanged:: 3.0
|
||||
The ``charset`` and ``errors`` parameters were removed.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
"""
|
||||
if charset is not ...:
|
||||
warnings.warn(
|
||||
"The 'charset' parameter is deprecated and will be removed"
|
||||
" in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if charset is None:
|
||||
charset = "utf-8"
|
||||
else:
|
||||
charset = "utf-8"
|
||||
|
||||
if errors is not None:
|
||||
warnings.warn(
|
||||
"The 'errors' parameter is deprecated and will be removed in Werkzeug 3.0",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
else:
|
||||
errors = "replace"
|
||||
|
||||
path = environ.get("PATH_INFO", "").encode("latin1")
|
||||
return path.decode(charset, errors) # type: ignore[no-any-return]
|
||||
path: bytes = environ.get("PATH_INFO", "").encode("latin1")
|
||||
return path.decode(errors="replace")
|
||||
|
||||
|
||||
class ClosingIterator:
|
||||
@@ -455,225 +422,6 @@ class _RangeWrapper:
|
||||
self.iterable.close()
|
||||
|
||||
|
||||
def _make_chunk_iter(
|
||||
stream: t.Iterable[bytes] | t.IO[bytes],
|
||||
limit: int | None,
|
||||
buffer_size: int,
|
||||
) -> t.Iterator[bytes]:
|
||||
"""Helper for the line and chunk iter functions."""
|
||||
warnings.warn(
|
||||
"'_make_chunk_iter' is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
if isinstance(stream, (bytes, bytearray, str)):
|
||||
raise TypeError(
|
||||
"Passed a string or byte object instead of true iterator or stream."
|
||||
)
|
||||
if not hasattr(stream, "read"):
|
||||
for item in stream:
|
||||
if item:
|
||||
yield item
|
||||
return
|
||||
stream = t.cast(t.IO[bytes], stream)
|
||||
if not isinstance(stream, LimitedStream) and limit is not None:
|
||||
stream = t.cast(t.IO[bytes], LimitedStream(stream, limit))
|
||||
_read = stream.read
|
||||
while True:
|
||||
item = _read(buffer_size)
|
||||
if not item:
|
||||
break
|
||||
yield item
|
||||
|
||||
|
||||
def make_line_iter(
|
||||
stream: t.Iterable[bytes] | t.IO[bytes],
|
||||
limit: int | None = None,
|
||||
buffer_size: int = 10 * 1024,
|
||||
cap_at_buffer: bool = False,
|
||||
) -> t.Iterator[bytes]:
|
||||
"""Safely iterates line-based over an input stream. If the input stream
|
||||
is not a :class:`LimitedStream` the `limit` parameter is mandatory.
|
||||
|
||||
This uses the stream's :meth:`~file.read` method internally as opposite
|
||||
to the :meth:`~file.readline` method that is unsafe and can only be used
|
||||
in violation of the WSGI specification. The same problem applies to the
|
||||
`__iter__` function of the input stream which calls :meth:`~file.readline`
|
||||
without arguments.
|
||||
|
||||
If you need line-by-line processing it's strongly recommended to iterate
|
||||
over the input stream using this helper function.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0.
|
||||
|
||||
.. versionadded:: 0.11
|
||||
added support for the `cap_at_buffer` parameter.
|
||||
|
||||
.. versionadded:: 0.9
|
||||
added support for iterators as input stream.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
This function now ensures that the limit was reached.
|
||||
|
||||
:param stream: the stream or iterate to iterate over.
|
||||
:param limit: the limit in bytes for the stream. (Usually
|
||||
content length. Not necessary if the `stream`
|
||||
is a :class:`LimitedStream`.
|
||||
:param buffer_size: The optional buffer size.
|
||||
:param cap_at_buffer: if this is set chunks are split if they are longer
|
||||
than the buffer size. Internally this is implemented
|
||||
that the buffer size might be exhausted by a factor
|
||||
of two however.
|
||||
"""
|
||||
warnings.warn(
|
||||
"'make_line_iter' is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
_iter = _make_chunk_iter(stream, limit, buffer_size)
|
||||
|
||||
first_item = next(_iter, "")
|
||||
|
||||
if not first_item:
|
||||
return
|
||||
|
||||
s = _make_encode_wrapper(first_item)
|
||||
empty = t.cast(bytes, s(""))
|
||||
cr = t.cast(bytes, s("\r"))
|
||||
lf = t.cast(bytes, s("\n"))
|
||||
crlf = t.cast(bytes, s("\r\n"))
|
||||
|
||||
_iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter))
|
||||
|
||||
def _iter_basic_lines() -> t.Iterator[bytes]:
|
||||
_join = empty.join
|
||||
buffer: list[bytes] = []
|
||||
while True:
|
||||
new_data = next(_iter, "")
|
||||
if not new_data:
|
||||
break
|
||||
new_buf: list[bytes] = []
|
||||
buf_size = 0
|
||||
for item in t.cast(
|
||||
t.Iterator[bytes], chain(buffer, new_data.splitlines(True))
|
||||
):
|
||||
new_buf.append(item)
|
||||
buf_size += len(item)
|
||||
if item and item[-1:] in crlf:
|
||||
yield _join(new_buf)
|
||||
new_buf = []
|
||||
elif cap_at_buffer and buf_size >= buffer_size:
|
||||
rv = _join(new_buf)
|
||||
while len(rv) >= buffer_size:
|
||||
yield rv[:buffer_size]
|
||||
rv = rv[buffer_size:]
|
||||
new_buf = [rv]
|
||||
buffer = new_buf
|
||||
if buffer:
|
||||
yield _join(buffer)
|
||||
|
||||
# This hackery is necessary to merge 'foo\r' and '\n' into one item
|
||||
# of 'foo\r\n' if we were unlucky and we hit a chunk boundary.
|
||||
previous = empty
|
||||
for item in _iter_basic_lines():
|
||||
if item == lf and previous[-1:] == cr:
|
||||
previous += item
|
||||
item = empty
|
||||
if previous:
|
||||
yield previous
|
||||
previous = item
|
||||
if previous:
|
||||
yield previous
|
||||
|
||||
|
||||
def make_chunk_iter(
|
||||
stream: t.Iterable[bytes] | t.IO[bytes],
|
||||
separator: bytes,
|
||||
limit: int | None = None,
|
||||
buffer_size: int = 10 * 1024,
|
||||
cap_at_buffer: bool = False,
|
||||
) -> t.Iterator[bytes]:
|
||||
"""Works like :func:`make_line_iter` but accepts a separator
|
||||
which divides chunks. If you want newline based processing
|
||||
you should use :func:`make_line_iter` instead as it
|
||||
supports arbitrary newline markers.
|
||||
|
||||
.. deprecated:: 2.3
|
||||
Will be removed in Werkzeug 3.0.
|
||||
|
||||
.. versionchanged:: 0.11
|
||||
added support for the `cap_at_buffer` parameter.
|
||||
|
||||
.. versionchanged:: 0.9
|
||||
added support for iterators as input stream.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
:param stream: the stream or iterate to iterate over.
|
||||
:param separator: the separator that divides chunks.
|
||||
:param limit: the limit in bytes for the stream. (Usually
|
||||
content length. Not necessary if the `stream`
|
||||
is otherwise already limited).
|
||||
:param buffer_size: The optional buffer size.
|
||||
:param cap_at_buffer: if this is set chunks are split if they are longer
|
||||
than the buffer size. Internally this is implemented
|
||||
that the buffer size might be exhausted by a factor
|
||||
of two however.
|
||||
"""
|
||||
warnings.warn(
|
||||
"'make_chunk_iter' is deprecated and will be removed in Werkzeug 3.0.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
_iter = _make_chunk_iter(stream, limit, buffer_size)
|
||||
|
||||
first_item = next(_iter, b"")
|
||||
|
||||
if not first_item:
|
||||
return
|
||||
|
||||
_iter = t.cast(t.Iterator[bytes], chain((first_item,), _iter))
|
||||
if isinstance(first_item, str):
|
||||
separator = _to_str(separator)
|
||||
_split = re.compile(f"({re.escape(separator)})").split
|
||||
_join = "".join
|
||||
else:
|
||||
separator = _to_bytes(separator)
|
||||
_split = re.compile(b"(" + re.escape(separator) + b")").split
|
||||
_join = b"".join
|
||||
|
||||
buffer: list[bytes] = []
|
||||
while True:
|
||||
new_data = next(_iter, b"")
|
||||
if not new_data:
|
||||
break
|
||||
chunks = _split(new_data)
|
||||
new_buf: list[bytes] = []
|
||||
buf_size = 0
|
||||
for item in chain(buffer, chunks):
|
||||
if item == separator:
|
||||
yield _join(new_buf)
|
||||
new_buf = []
|
||||
buf_size = 0
|
||||
else:
|
||||
buf_size += len(item)
|
||||
new_buf.append(item)
|
||||
|
||||
if cap_at_buffer and buf_size >= buffer_size:
|
||||
rv = _join(new_buf)
|
||||
while len(rv) >= buffer_size:
|
||||
yield rv[:buffer_size]
|
||||
rv = rv[buffer_size:]
|
||||
new_buf = [rv]
|
||||
buf_size = len(rv)
|
||||
|
||||
buffer = new_buf
|
||||
if buffer:
|
||||
yield _join(buffer)
|
||||
|
||||
|
||||
class LimitedStream(io.RawIOBase):
|
||||
"""Wrap a stream so that it doesn't read more than a given limit. This is used to
|
||||
limit ``wsgi.input`` to the ``Content-Length`` header value or
|
||||
|
||||
Reference in New Issue
Block a user