aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorrobot-piglet <robot-piglet@yandex-team.com>2025-02-09 00:00:59 +0300
committerrobot-piglet <robot-piglet@yandex-team.com>2025-02-09 00:13:15 +0300
commit41e6f263ca838bea7b80defd749f859c860c5338 (patch)
tree547d5946e4292f99dad508a94c4cfdaaa8835444 /contrib/python
parentd2703b22bd88ed7b99b83327594ef7cc3dc27f88 (diff)
downloadydb-41e6f263ca838bea7b80defd749f859c860c5338.tar.gz
Intermediate changes
commit_hash:9f477c101838068c76e0d37a313a5e79a2888982
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/Werkzeug/py3/.dist-info/METADATA2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/__init__.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/_internal.py13
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/_reloader.py4
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/datastructures.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/debug/__init__.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/debug/repr.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/debug/tbtools.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/exceptions.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/formparser.py12
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/http.py16
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/local.py4
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/middleware/lint.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/middleware/profiler.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/routing/matcher.py9
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/routing/rules.py31
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/sansio/http.py4
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/sansio/multipart.py8
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/security.py2
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/serving.py113
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/test.py7
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/utils.py11
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/wrappers/request.py20
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/wrappers/response.py10
-rw-r--r--contrib/python/Werkzeug/py3/werkzeug/wsgi.py76
-rw-r--r--contrib/python/Werkzeug/py3/ya.make2
26 files changed, 233 insertions, 127 deletions
diff --git a/contrib/python/Werkzeug/py3/.dist-info/METADATA b/contrib/python/Werkzeug/py3/.dist-info/METADATA
index a40cd1ba9f..647bfc8001 100644
--- a/contrib/python/Werkzeug/py3/.dist-info/METADATA
+++ b/contrib/python/Werkzeug/py3/.dist-info/METADATA
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: Werkzeug
-Version: 2.2.2
+Version: 2.2.3
Summary: The comprehensive WSGI web application library.
Home-page: https://palletsprojects.com/p/werkzeug/
Author: Armin Ronacher
diff --git a/contrib/python/Werkzeug/py3/werkzeug/__init__.py b/contrib/python/Werkzeug/py3/werkzeug/__init__.py
index fd7f8d229a..c20ac29bcb 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/__init__.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/__init__.py
@@ -3,4 +3,4 @@ from .test import Client as Client
from .wrappers import Request as Request
from .wrappers import Response as Response
-__version__ = "2.2.2"
+__version__ = "2.2.3"
diff --git a/contrib/python/Werkzeug/py3/werkzeug/_internal.py b/contrib/python/Werkzeug/py3/werkzeug/_internal.py
index 4636647db3..f95207ab21 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/_internal.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/_internal.py
@@ -34,7 +34,7 @@ _quote_re = re.compile(rb"[\\].")
_legal_cookie_chars_re = rb"[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]"
_cookie_re = re.compile(
rb"""
- (?P<key>[^=;]+)
+ (?P<key>[^=;]*)
(?:\s*=\s*
(?P<val>
"(?:[^\\"]|\\.)*" |
@@ -382,16 +382,21 @@ def _cookie_parse_impl(b: bytes) -> t.Iterator[t.Tuple[bytes, bytes]]:
"""Lowlevel cookie parsing facility that operates on bytes."""
i = 0
n = len(b)
+ b += b";"
while i < n:
- match = _cookie_re.search(b + b";", i)
+ match = _cookie_re.match(b, i)
+
if not match:
break
- key = match.group("key").strip()
- value = match.group("val") or b""
i = match.end(0)
+ key = match.group("key").strip()
+
+ if not key:
+ continue
+ value = match.group("val") or b""
yield key, _cookie_unquote(value)
diff --git a/contrib/python/Werkzeug/py3/werkzeug/_reloader.py b/contrib/python/Werkzeug/py3/werkzeug/_reloader.py
index 21ceb88d38..67614a78a7 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/_reloader.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/_reloader.py
@@ -20,7 +20,7 @@ prefix = {*_ignore_always, sys.prefix, sys.exec_prefix}
if hasattr(sys, "real_prefix"):
# virtualenv < 20
- prefix.add(sys.real_prefix) # type: ignore[attr-defined]
+ prefix.add(sys.real_prefix)
_stat_ignore_scan = tuple(prefix)
del prefix
@@ -309,7 +309,7 @@ class WatchdogReloaderLoop(ReloaderLoop):
super().__init__(*args, **kwargs)
trigger_reload = self.trigger_reload
- class EventHandler(PatternMatchingEventHandler): # type: ignore
+ class EventHandler(PatternMatchingEventHandler):
def on_any_event(self, event): # type: ignore
trigger_reload(event.src_path)
diff --git a/contrib/python/Werkzeug/py3/werkzeug/datastructures.py b/contrib/python/Werkzeug/py3/werkzeug/datastructures.py
index 43ee8c7545..a293dfddb5 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/datastructures.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/datastructures.py
@@ -1226,7 +1226,7 @@ class Headers:
(_unicodify_header_value(k), _unicodify_header_value(v))
for (k, v) in value
]
- for (_, v) in value:
+ for _, v in value:
self._validate_value(v)
if isinstance(key, int):
self._list[key] = value[0]
diff --git a/contrib/python/Werkzeug/py3/werkzeug/debug/__init__.py b/contrib/python/Werkzeug/py3/werkzeug/debug/__init__.py
index e0dcc65fb4..24d19bbdad 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/debug/__init__.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/debug/__init__.py
@@ -329,7 +329,7 @@ class DebuggedApplication:
app_iter = self.app(environ, start_response)
yield from app_iter
if hasattr(app_iter, "close"):
- app_iter.close() # type: ignore
+ app_iter.close()
except Exception as e:
if hasattr(app_iter, "close"):
app_iter.close() # type: ignore
diff --git a/contrib/python/Werkzeug/py3/werkzeug/debug/repr.py b/contrib/python/Werkzeug/py3/werkzeug/debug/repr.py
index c0872f1808..d9c28da41b 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/debug/repr.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/debug/repr.py
@@ -132,7 +132,7 @@ class DebugReprGenerator:
def regex_repr(self, obj: t.Pattern) -> str:
pattern = repr(obj.pattern)
- pattern = codecs.decode(pattern, "unicode-escape", "ignore") # type: ignore
+ pattern = codecs.decode(pattern, "unicode-escape", "ignore")
pattern = f"r{pattern}"
return f're.compile(<span class="string regex">{pattern}</span>)'
diff --git a/contrib/python/Werkzeug/py3/werkzeug/debug/tbtools.py b/contrib/python/Werkzeug/py3/werkzeug/debug/tbtools.py
index ea90de9254..d56f7390a5 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/debug/tbtools.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/debug/tbtools.py
@@ -184,7 +184,7 @@ def _process_traceback(
}
if hasattr(fs, "colno"):
- frame_args["colno"] = fs.colno # type: ignore[attr-defined]
+ frame_args["colno"] = fs.colno
frame_args["end_colno"] = fs.end_colno # type: ignore[attr-defined]
new_stack.append(DebugFrameSummary(**frame_args))
diff --git a/contrib/python/Werkzeug/py3/werkzeug/exceptions.py b/contrib/python/Werkzeug/py3/werkzeug/exceptions.py
index 013df72bd3..739bd905ba 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/exceptions.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/exceptions.py
@@ -205,7 +205,7 @@ class BadRequestKeyError(BadRequest, KeyError):
KeyError.__init__(self, arg)
@property # type: ignore
- def description(self) -> str: # type: ignore
+ def description(self) -> str:
if self.show_exception:
return (
f"{self._description}\n"
diff --git a/contrib/python/Werkzeug/py3/werkzeug/formparser.py b/contrib/python/Werkzeug/py3/werkzeug/formparser.py
index 10d58ca3fa..bebb2fc8fe 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/formparser.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/formparser.py
@@ -179,6 +179,8 @@ class FormDataParser:
: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.
+ :param max_form_parts: The maximum number of parts to be parsed. If this is
+ exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
"""
def __init__(
@@ -190,6 +192,8 @@ class FormDataParser:
max_content_length: t.Optional[int] = None,
cls: t.Optional[t.Type[MultiDict]] = None,
silent: bool = True,
+ *,
+ max_form_parts: t.Optional[int] = None,
) -> None:
if stream_factory is None:
stream_factory = default_stream_factory
@@ -199,6 +203,7 @@ class FormDataParser:
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
if cls is None:
cls = MultiDict
@@ -281,6 +286,7 @@ class FormDataParser:
self.errors,
max_form_memory_size=self.max_form_memory_size,
cls=self.cls,
+ max_form_parts=self.max_form_parts,
)
boundary = options.get("boundary", "").encode("ascii")
@@ -346,10 +352,12 @@ class MultiPartParser:
max_form_memory_size: t.Optional[int] = None,
cls: t.Optional[t.Type[MultiDict]] = None,
buffer_size: int = 64 * 1024,
+ max_form_parts: t.Optional[int] = None,
) -> None:
self.charset = charset
self.errors = errors
self.max_form_memory_size = max_form_memory_size
+ self.max_form_parts = max_form_parts
if stream_factory is None:
stream_factory = default_stream_factory
@@ -409,7 +417,9 @@ class MultiPartParser:
[None],
)
- parser = MultipartDecoder(boundary, self.max_form_memory_size)
+ parser = MultipartDecoder(
+ boundary, self.max_form_memory_size, max_parts=self.max_form_parts
+ )
fields = []
files = []
diff --git a/contrib/python/Werkzeug/py3/werkzeug/http.py b/contrib/python/Werkzeug/py3/werkzeug/http.py
index 97776855d7..0a7bc739c5 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/http.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/http.py
@@ -190,6 +190,15 @@ class COOP(Enum):
SAME_ORIGIN = "same-origin"
+def _is_extended_parameter(key: str) -> bool:
+ """Per RFC 5987/8187, "extended" values may *not* be quoted.
+ This is in keeping with browser implementations. So we test
+ using this function to see if the key indicates this parameter
+ follows the `ext-parameter` syntax (using a trailing '*').
+ """
+ return key.strip().endswith("*")
+
+
def quote_header_value(
value: t.Union[str, int], extra_chars: str = "", allow_token: bool = True
) -> str:
@@ -254,6 +263,8 @@ def dump_options_header(
for key, value in options.items():
if value is None:
segments.append(key)
+ elif _is_extended_parameter(key):
+ segments.append(f"{key}={value}")
else:
segments.append(f"{key}={quote_header_value(value)}")
return "; ".join(segments)
@@ -282,6 +293,8 @@ def dump_header(
for key, value in iterable.items():
if value is None:
items.append(key)
+ elif _is_extended_parameter(key):
+ items.append(f"{key}={value}")
else:
items.append(
f"{key}={quote_header_value(value, allow_token=allow_token)}"
@@ -818,6 +831,9 @@ def parse_content_range_header(
return None
if rng == "*":
+ if not is_byte_range_valid(None, None, length):
+ return None
+
return ds.ContentRange(units, None, None, length, on_update=on_update)
elif "-" not in rng:
return None
diff --git a/contrib/python/Werkzeug/py3/werkzeug/local.py b/contrib/python/Werkzeug/py3/werkzeug/local.py
index 70e9bf72f2..9927a0a1f3 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/local.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/local.py
@@ -291,7 +291,7 @@ class _ProxyLookup:
# A C function, use partial to bind the first argument.
def bind_f(instance: "LocalProxy", obj: t.Any) -> t.Callable:
- return partial(f, obj) # type: ignore
+ return partial(f, obj)
else:
# Use getattr, which will produce a bound method.
@@ -313,7 +313,7 @@ class _ProxyLookup:
return self
try:
- obj = instance._get_current_object() # type: ignore[misc]
+ obj = instance._get_current_object()
except RuntimeError:
if self.fallback is None:
raise
diff --git a/contrib/python/Werkzeug/py3/werkzeug/middleware/lint.py b/contrib/python/Werkzeug/py3/werkzeug/middleware/lint.py
index 6b54630e6e..fcf3b41314 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/middleware/lint.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/middleware/lint.py
@@ -164,7 +164,7 @@ class GuardedIterator:
self.closed = True
if hasattr(self._iterator, "close"):
- self._iterator.close() # type: ignore
+ self._iterator.close()
if self.headers_set:
status_code, headers = self.headers_set
diff --git a/contrib/python/Werkzeug/py3/werkzeug/middleware/profiler.py b/contrib/python/Werkzeug/py3/werkzeug/middleware/profiler.py
index 200dae0341..f91e33b825 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/middleware/profiler.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/middleware/profiler.py
@@ -106,7 +106,7 @@ class ProfilerMiddleware:
response_body.extend(app_iter)
if hasattr(app_iter, "close"):
- app_iter.close() # type: ignore
+ app_iter.close()
profile = Profile()
start = time.time()
diff --git a/contrib/python/Werkzeug/py3/werkzeug/routing/matcher.py b/contrib/python/Werkzeug/py3/werkzeug/routing/matcher.py
index d22b05a5c9..05370c3e07 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/routing/matcher.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/routing/matcher.py
@@ -127,7 +127,14 @@ class StateMachineMatcher:
remaining = []
match = re.compile(test_part.content).match(target)
if match is not None:
- rv = _match(new_state, remaining, values + list(match.groups()))
+ groups = list(match.groups())
+ if test_part.suffixed:
+ # If a part_isolating=False part has a slash suffix, remove the
+ # suffix from the match and check for the slash redirect next.
+ suffix = groups.pop()
+ if suffix == "/":
+ remaining = [""]
+ rv = _match(new_state, remaining, values + groups)
if rv is not None:
return rv
diff --git a/contrib/python/Werkzeug/py3/werkzeug/routing/rules.py b/contrib/python/Werkzeug/py3/werkzeug/routing/rules.py
index a61717ade8..7b37890abd 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/routing/rules.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/routing/rules.py
@@ -36,6 +36,7 @@ class RulePart:
content: str
final: bool
static: bool
+ suffixed: bool
weight: Weighting
@@ -631,7 +632,11 @@ class Rule(RuleFactory):
argument_weights,
)
yield RulePart(
- content=content, final=final, static=static, weight=weight
+ content=content,
+ final=final,
+ static=static,
+ suffixed=False,
+ weight=weight,
)
content = ""
static = True
@@ -641,6 +646,12 @@ class Rule(RuleFactory):
pos = match.end()
+ suffixed = False
+ if final and content[-1] == "/":
+ # If a converter is part_isolating=False (matches slashes) and ends with a
+ # slash, augment the regex to support slash redirects.
+ suffixed = True
+ content = content[:-1] + "(?<!/)(/?)"
if not static:
content += r"\Z"
weight = Weighting(
@@ -649,7 +660,17 @@ class Rule(RuleFactory):
-len(argument_weights),
argument_weights,
)
- yield RulePart(content=content, final=final, static=static, weight=weight)
+ yield RulePart(
+ content=content,
+ final=final,
+ static=static,
+ suffixed=suffixed,
+ weight=weight,
+ )
+ if suffixed:
+ yield RulePart(
+ content="", final=False, static=True, suffixed=False, weight=weight
+ )
def compile(self) -> None:
"""Compiles the regular expression and stores it."""
@@ -665,7 +686,11 @@ class Rule(RuleFactory):
if domain_rule == "":
self._parts = [
RulePart(
- content="", final=False, static=True, weight=Weighting(0, [], 0, [])
+ content="",
+ final=False,
+ static=True,
+ suffixed=False,
+ weight=Weighting(0, [], 0, []),
)
]
else:
diff --git a/contrib/python/Werkzeug/py3/werkzeug/sansio/http.py b/contrib/python/Werkzeug/py3/werkzeug/sansio/http.py
index 8288882b72..6b22738328 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/sansio/http.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/sansio/http.py
@@ -126,10 +126,6 @@ def parse_cookie(
def _parse_pairs() -> t.Iterator[t.Tuple[str, str]]:
for key, val in _cookie_parse_impl(cookie): # type: ignore
key_str = _to_str(key, charset, errors, allow_none_charset=True)
-
- if not key_str:
- continue
-
val_str = _to_str(val, charset, errors, allow_none_charset=True)
yield key_str, val_str
diff --git a/contrib/python/Werkzeug/py3/werkzeug/sansio/multipart.py b/contrib/python/Werkzeug/py3/werkzeug/sansio/multipart.py
index d8abeb3543..2684e5dd64 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/sansio/multipart.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/sansio/multipart.py
@@ -87,10 +87,13 @@ class MultipartDecoder:
self,
boundary: bytes,
max_form_memory_size: Optional[int] = None,
+ *,
+ max_parts: Optional[int] = None,
) -> None:
self.buffer = bytearray()
self.complete = False
self.max_form_memory_size = max_form_memory_size
+ self.max_parts = max_parts
self.state = State.PREAMBLE
self.boundary = boundary
@@ -118,6 +121,7 @@ class MultipartDecoder:
re.MULTILINE,
)
self._search_position = 0
+ self._parts_decoded = 0
def last_newline(self) -> int:
try:
@@ -191,6 +195,10 @@ class MultipartDecoder:
)
self.state = State.DATA
self._search_position = 0
+ self._parts_decoded += 1
+
+ if self.max_parts is not None and self._parts_decoded > self.max_parts:
+ raise RequestEntityTooLarge()
else:
# Update the search start position to be equal to the
# current buffer length (already searched) minus a
diff --git a/contrib/python/Werkzeug/py3/werkzeug/security.py b/contrib/python/Werkzeug/py3/werkzeug/security.py
index 18d0919f83..4599fb3e1d 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/security.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/security.py
@@ -12,7 +12,7 @@ SALT_CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
DEFAULT_PBKDF2_ITERATIONS = 260000
_os_alt_seps: t.List[str] = list(
- sep for sep in [os.path.sep, os.path.altsep] if sep is not None and sep != "/"
+ sep for sep in [os.sep, os.path.altsep] if sep is not None and sep != "/"
)
diff --git a/contrib/python/Werkzeug/py3/werkzeug/serving.py b/contrib/python/Werkzeug/py3/werkzeug/serving.py
index c482469863..2a2e74de2c 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/serving.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/serving.py
@@ -221,9 +221,7 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
try:
# binary_form=False gives nicer information, but wouldn't be compatible with
# what Nginx or Apache could return.
- peer_cert = self.connection.getpeercert( # type: ignore[attr-defined]
- binary_form=True
- )
+ peer_cert = self.connection.getpeercert(binary_form=True)
if peer_cert is not None:
# Nginx and Apache use PEM format.
environ["SSL_CLIENT_CERT"] = ssl.DER_cert_to_PEM_cert(peer_cert)
@@ -329,7 +327,7 @@ class WSGIRequestHandler(BaseHTTPRequestHandler):
self.wfile.write(b"0\r\n\r\n")
finally:
if hasattr(application_iter, "close"):
- application_iter.close() # type: ignore
+ application_iter.close()
try:
execute(self.server.app)
@@ -659,6 +657,7 @@ class BaseWSGIServer(HTTPServer):
multithread = False
multiprocess = False
request_queue_size = LISTEN_QUEUE
+ allow_reuse_address = True
def __init__(
self,
@@ -710,10 +709,36 @@ class BaseWSGIServer(HTTPServer):
try:
self.server_bind()
self.server_activate()
+ except OSError as e:
+ # Catch connection issues and show them without the traceback. Show
+ # extra instructions for address not found, and for macOS.
+ self.server_close()
+ print(e.strerror, file=sys.stderr)
+
+ if e.errno == errno.EADDRINUSE:
+ print(
+ f"Port {port} is in use by another program. Either identify and"
+ " stop that program, or start the server with a different"
+ " port.",
+ file=sys.stderr,
+ )
+
+ if sys.platform == "darwin" and port == 5000:
+ print(
+ "On macOS, try disabling the 'AirPlay Receiver' service"
+ " from System Preferences -> Sharing.",
+ file=sys.stderr,
+ )
+
+ sys.exit(1)
except BaseException:
self.server_close()
raise
else:
+ # TCPServer automatically opens a socket even if bind_and_activate is False.
+ # Close it to silence a ResourceWarning.
+ self.server_close()
+
# Use the passed in socket directly.
self.socket = socket.fromfd(fd, address_family, socket.SOCK_STREAM)
self.server_address = self.socket.getsockname()
@@ -879,60 +904,6 @@ def is_running_from_reloader() -> bool:
return os.environ.get("WERKZEUG_RUN_MAIN") == "true"
-def prepare_socket(hostname: str, port: int) -> socket.socket:
- """Prepare a socket for use by the WSGI server and reloader.
-
- The socket is marked inheritable so that it can be kept across
- reloads instead of breaking connections.
-
- Catch errors during bind and show simpler error messages. For
- "address already in use", show instructions for resolving the issue,
- with special instructions for macOS.
-
- This is called from :func:`run_simple`, but can be used separately
- to control server creation with :func:`make_server`.
- """
- address_family = select_address_family(hostname, port)
- server_address = get_sockaddr(hostname, port, address_family)
- s = socket.socket(address_family, socket.SOCK_STREAM)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.set_inheritable(True)
-
- # Remove the socket file if it already exists.
- if address_family == af_unix:
- server_address = t.cast(str, server_address)
-
- if os.path.exists(server_address):
- os.unlink(server_address)
-
- # Catch connection issues and show them without the traceback. Show
- # extra instructions for address not found, and for macOS.
- try:
- s.bind(server_address)
- except OSError as e:
- print(e.strerror, file=sys.stderr)
-
- if e.errno == errno.EADDRINUSE:
- print(
- f"Port {port} is in use by another program. Either"
- " identify and stop that program, or start the"
- " server with a different port.",
- file=sys.stderr,
- )
-
- if sys.platform == "darwin" and port == 5000:
- print(
- "On macOS, try disabling the 'AirPlay Receiver'"
- " service from System Preferences -> Sharing.",
- file=sys.stderr,
- )
-
- sys.exit(1)
-
- s.listen(LISTEN_QUEUE)
- return s
-
-
def run_simple(
hostname: str,
port: int,
@@ -1059,12 +1030,7 @@ def run_simple(
application = DebuggedApplication(application, evalex=use_evalex)
if not is_running_from_reloader():
- s = prepare_socket(hostname, port)
- fd = s.fileno()
- # Silence a ResourceWarning about an unclosed socket. This object is no longer
- # used, the server will create another with fromfd.
- s.detach()
- os.environ["WERKZEUG_SERVER_FD"] = str(fd)
+ fd = None
else:
fd = int(os.environ["WERKZEUG_SERVER_FD"])
@@ -1079,6 +1045,8 @@ def run_simple(
ssl_context,
fd=fd,
)
+ srv.socket.set_inheritable(True)
+ os.environ["WERKZEUG_SERVER_FD"] = str(srv.fileno())
if not is_running_from_reloader():
srv.log_startup()
@@ -1087,12 +1055,15 @@ def run_simple(
if use_reloader:
from ._reloader import run_with_reloader
- run_with_reloader(
- srv.serve_forever,
- extra_files=extra_files,
- exclude_patterns=exclude_patterns,
- interval=reloader_interval,
- reloader_type=reloader_type,
- )
+ try:
+ run_with_reloader(
+ srv.serve_forever,
+ extra_files=extra_files,
+ exclude_patterns=exclude_patterns,
+ interval=reloader_interval,
+ reloader_type=reloader_type,
+ )
+ finally:
+ srv.server_close()
else:
srv.serve_forever()
diff --git a/contrib/python/Werkzeug/py3/werkzeug/test.py b/contrib/python/Werkzeug/py3/werkzeug/test.py
index 866aa97e53..be67979b58 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/test.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/test.py
@@ -107,7 +107,8 @@ def stream_encode_multipart(
and mimetypes.guess_type(filename)[0]
or "application/octet-stream"
)
- headers = Headers([("Content-Type", content_type)])
+ headers = value.headers
+ headers.update([("Content-Type", content_type)])
if filename is None:
write_binary(encoder.send_event(Field(name=key, headers=headers)))
else:
@@ -441,7 +442,7 @@ class EnvironBuilder:
if input_stream is not None:
raise TypeError("can't provide input stream and data")
if hasattr(data, "read"):
- data = data.read() # type: ignore
+ data = data.read()
if isinstance(data, str):
data = data.encode(self.charset)
if isinstance(data, bytes):
@@ -449,7 +450,7 @@ class EnvironBuilder:
if self.content_length is None:
self.content_length = len(data)
else:
- for key, value in _iter_data(data): # type: ignore
+ for key, value in _iter_data(data):
if isinstance(value, (tuple, dict)) or hasattr(value, "read"):
self._add_file_from_data(key, value)
else:
diff --git a/contrib/python/Werkzeug/py3/werkzeug/utils.py b/contrib/python/Werkzeug/py3/werkzeug/utils.py
index 672e6e5ade..4ef5837137 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/utils.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/utils.py
@@ -221,7 +221,7 @@ def secure_filename(filename: str) -> str:
filename = unicodedata.normalize("NFKD", filename)
filename = filename.encode("ascii", "ignore").decode("ascii")
- for sep in os.path.sep, os.path.altsep:
+ for sep in os.sep, os.path.altsep:
if sep:
filename = filename.replace(sep, " ")
filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip(
@@ -352,7 +352,7 @@ def send_file(
Never pass file paths provided by a user. The path is assumed to be
trusted, so a user could craft a path to access a file you didn't
- intend.
+ intend. Use :func:`send_from_directory` to safely serve user-provided paths.
If the WSGI server sets a ``file_wrapper`` in ``environ``, it is
used, otherwise Werkzeug's built-in wrapper is used. Alternatively,
@@ -562,9 +562,10 @@ def send_from_directory(
If the final path does not point to an existing regular file,
returns a 404 :exc:`~werkzeug.exceptions.NotFound` error.
- :param directory: The directory that ``path`` must be located under.
- :param path: The path to the file to send, relative to
- ``directory``.
+ :param directory: The directory that ``path`` must be located under. This *must not*
+ be a value provided by the client, otherwise it becomes insecure.
+ :param path: The path to the file to send, relative to ``directory``. This is the
+ part of the path provided by the client, which is checked for security.
:param environ: The WSGI environ for the current request.
:param kwargs: Arguments to pass to :func:`send_file`.
diff --git a/contrib/python/Werkzeug/py3/werkzeug/wrappers/request.py b/contrib/python/Werkzeug/py3/werkzeug/wrappers/request.py
index 57b739cc5f..2de77df42a 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/wrappers/request.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/wrappers/request.py
@@ -83,6 +83,13 @@ class Request(_SansIORequest):
#: .. versionadded:: 0.5
max_form_memory_size: t.Optional[int] = None
+ #: The maximum number of multipart parts to parse, passed to
+ #: :attr:`form_data_parser_class`. Parsing form data with more than this
+ #: many parts will raise :exc:`~.RequestEntityTooLarge`.
+ #:
+ #: .. versionadded:: 2.2.3
+ max_form_parts = 1000
+
#: The form data parser that should be used. Can be replaced to customize
#: the form date parsing.
form_data_parser_class: t.Type[FormDataParser] = FormDataParser
@@ -246,6 +253,7 @@ class Request(_SansIORequest):
self.max_form_memory_size,
self.max_content_length,
self.parameter_storage_class,
+ max_form_parts=self.max_form_parts,
)
def _load_form_data(self) -> None:
@@ -543,6 +551,18 @@ class Request(_SansIORequest):
# with sentinel values.
_cached_json: t.Tuple[t.Any, t.Any] = (Ellipsis, Ellipsis)
+ @t.overload
+ def get_json(
+ self, force: bool = ..., silent: "te.Literal[False]" = ..., cache: bool = ...
+ ) -> t.Any:
+ ...
+
+ @t.overload
+ def get_json(
+ self, force: bool = ..., silent: bool = ..., cache: bool = ...
+ ) -> t.Optional[t.Any]:
+ ...
+
def get_json(
self, force: bool = False, silent: bool = False, cache: bool = True
) -> t.Optional[t.Any]:
diff --git a/contrib/python/Werkzeug/py3/werkzeug/wrappers/response.py b/contrib/python/Werkzeug/py3/werkzeug/wrappers/response.py
index 7e888cba5e..454208c773 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/wrappers/response.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/wrappers/response.py
@@ -439,7 +439,7 @@ class Response(_SansIOResponse):
Can now be used in a with statement.
"""
if hasattr(self.response, "close"):
- self.response.close() # type: ignore
+ self.response.close()
for func in self._on_close:
func()
@@ -645,6 +645,14 @@ class Response(_SansIOResponse):
"""
return self.get_json()
+ @t.overload
+ def get_json(self, force: bool = ..., silent: "te.Literal[False]" = ...) -> t.Any:
+ ...
+
+ @t.overload
+ def get_json(self, force: bool = ..., silent: bool = ...) -> t.Optional[t.Any]:
+ ...
+
def get_json(self, force: bool = False, silent: bool = False) -> t.Optional[t.Any]:
"""Parse :attr:`data` as JSON. Useful during testing.
diff --git a/contrib/python/Werkzeug/py3/werkzeug/wsgi.py b/contrib/python/Werkzeug/py3/werkzeug/wsgi.py
index 24ece0b19e..d74430d8bb 100644
--- a/contrib/python/Werkzeug/py3/werkzeug/wsgi.py
+++ b/contrib/python/Werkzeug/py3/werkzeug/wsgi.py
@@ -611,9 +611,7 @@ class _RangeWrapper:
self.end_byte = start_byte + byte_range
self.read_length = 0
- self.seekable = (
- hasattr(iterable, "seekable") and iterable.seekable() # type: ignore
- )
+ self.seekable = hasattr(iterable, "seekable") and iterable.seekable()
self.end_reached = False
def __iter__(self) -> "_RangeWrapper":
@@ -665,7 +663,7 @@ class _RangeWrapper:
def close(self) -> None:
if hasattr(self.iterable, "close"):
- self.iterable.close() # type: ignore
+ self.iterable.close()
def _make_chunk_iter(
@@ -930,37 +928,77 @@ class LimitedStream(io.IOBase):
raise ClientDisconnected()
- def exhaust(self, chunk_size: int = 1024 * 64) -> None:
- """Exhaust the stream. This consumes all the data left until the
- limit is reached.
+ def _exhaust_chunks(self, chunk_size: int = 1024 * 64) -> t.Iterator[bytes]:
+ """Exhaust the stream by reading until the limit is reached or the client
+ disconnects, yielding each chunk.
+
+ :param chunk_size: How many bytes to read at a time.
- :param chunk_size: the size for a chunk. It will read the chunk
- until the stream is exhausted and throw away
- the results.
+ :meta private:
+
+ .. versionadded:: 2.2.3
"""
to_read = self.limit - self._pos
- chunk = chunk_size
+
while to_read > 0:
- chunk = min(to_read, chunk)
- self.read(chunk)
- to_read -= chunk
+ chunk = self.read(min(to_read, chunk_size))
+ yield chunk
+ to_read -= len(chunk)
+
+ def exhaust(self, chunk_size: int = 1024 * 64) -> None:
+ """Exhaust the stream by reading until the limit is reached or the client
+ disconnects, discarding the data.
+
+ :param chunk_size: How many bytes to read at a time.
+
+ .. versionchanged:: 2.2.3
+ Handle case where wrapped stream returns fewer bytes than requested.
+ """
+ for _ in self._exhaust_chunks(chunk_size):
+ pass
def read(self, size: t.Optional[int] = None) -> bytes:
- """Read `size` bytes or if size is not provided everything is read.
+ """Read up to ``size`` bytes from the underlying stream. If size is not
+ provided, read until the limit.
- :param size: the number of bytes read.
+ If the limit is reached, :meth:`on_exhausted` is called, which returns empty
+ bytes.
+
+ If no bytes are read and the limit is not reached, or if an error occurs during
+ the read, :meth:`on_disconnect` is called, which raises
+ :exc:`.ClientDisconnected`.
+
+ :param size: The number of bytes to read. ``None``, default, reads until the
+ limit is reached.
+
+ .. versionchanged:: 2.2.3
+ Handle case where wrapped stream returns fewer bytes than requested.
"""
if self._pos >= self.limit:
return self.on_exhausted()
- if size is None or size == -1: # -1 is for consistence with file
- size = self.limit
+
+ if size is None or size == -1: # -1 is for consistency with file
+ # Keep reading from the wrapped stream until the limit is reached. Can't
+ # rely on stream.read(size) because it's not guaranteed to return size.
+ buf = bytearray()
+
+ for chunk in self._exhaust_chunks():
+ buf.extend(chunk)
+
+ return bytes(buf)
+
to_read = min(self.limit - self._pos, size)
+
try:
read = self._read(to_read)
except (OSError, ValueError):
return self.on_disconnect()
- if to_read and len(read) != to_read:
+
+ if to_read and not len(read):
+ # If no data was read, treat it as a disconnect. As long as some data was
+ # read, a subsequent call can still return more before reaching the limit.
return self.on_disconnect()
+
self._pos += len(read)
return read
diff --git a/contrib/python/Werkzeug/py3/ya.make b/contrib/python/Werkzeug/py3/ya.make
index 823e9d5e5e..0d9e451aa9 100644
--- a/contrib/python/Werkzeug/py3/ya.make
+++ b/contrib/python/Werkzeug/py3/ya.make
@@ -2,7 +2,7 @@
PY3_LIBRARY()
-VERSION(2.2.2)
+VERSION(2.2.3)
LICENSE(BSD-3-Clause)