File 0001-limit-the-maximum-number-of-multipart-form-parts.patch of Package python-Werkzeug.33733
From 1f1e2cff0f9b841485e47711b8fabb3abbc14c7f Mon Sep 17 00:00:00 2001
From: Darragh O'Reilly <doreilly@suse.com>
Date: Fri, 10 Mar 2023 15:16:45 +0000
Subject: [PATCH] limit the maximum number of multipart form parts
Backport of
https://github.com/pallets/werkzeug/commit/fe899d0cdf767a7289a8bf746b7f72c2907a1b4b
---
tests/test_formparser.py | 7 +++++++
werkzeug/formparser.py | 16 +++++++++++++---
werkzeug/wrappers.py | 9 ++++++++-
3 files changed, 28 insertions(+), 4 deletions(-)
diff --git a/tests/test_formparser.py b/tests/test_formparser.py
index c140476b..be42d30f 100644
--- a/tests/test_formparser.py
+++ b/tests/test_formparser.py
@@ -101,6 +101,13 @@ class TestFormParser(object):
req.max_form_memory_size = 400
strict_eq(req.form['foo'], u'Hello World')
+ req = Request.from_values(input_stream=BytesIO(data),
+ content_length=len(data),
+ content_type="multipart/form-data; boundary=foo",
+ method="POST")
+ req.max_form_parts = 1
+ pytest.raises(RequestEntityTooLarge, lambda: req.form["foo"])
+
def test_missing_multipart_boundary(self):
data = (b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
b'Hello World\r\n'
diff --git a/werkzeug/formparser.py b/werkzeug/formparser.py
index c56859e9..5248e14e 100644
--- a/werkzeug/formparser.py
+++ b/werkzeug/formparser.py
@@ -147,12 +147,14 @@ class FormDataParser(object):
: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__(self, stream_factory=None, charset='utf-8',
errors='replace', max_form_memory_size=None,
max_content_length=None, cls=None,
- silent=True):
+ silent=True, max_form_parts=None):
if stream_factory is None:
stream_factory = default_stream_factory
self.stream_factory = stream_factory
@@ -164,6 +166,7 @@ class FormDataParser(object):
cls = MultiDict
self.cls = cls
self.silent = silent
+ self.max_form_parts = max_form_parts
def get_parse_func(self, mimetype, options):
return self.parse_functions.get(mimetype)
@@ -213,7 +216,7 @@ class FormDataParser(object):
def _parse_multipart(self, stream, mimetype, content_length, options):
parser = MultiPartParser(self.stream_factory, self.charset, self.errors,
max_form_memory_size=self.max_form_memory_size,
- cls=self.cls)
+ cls=self.cls, max_form_parts=self.max_form_parts)
boundary = options.get('boundary')
if boundary is None:
raise ValueError('Missing boundary')
@@ -295,10 +298,12 @@ _end = 'end'
class MultiPartParser(object):
def __init__(self, stream_factory=None, charset='utf-8', errors='replace',
- max_form_memory_size=None, cls=None, buffer_size=64 * 1024):
+ max_form_memory_size=None, cls=None, buffer_size=64 * 1024,
+ max_form_parts=None):
self.charset = charset
self.errors = errors
self.max_form_memory_size = max_form_memory_size
+ self.max_form_parts = max_form_parts
self.stream_factory = default_stream_factory if stream_factory is None else stream_factory
self.cls = MultiDict if cls is None else cls
@@ -484,6 +489,7 @@ class MultiPartParser(object):
``('form', (name, val))`` parts.
"""
in_memory = 0
+ parts_decoded = 0
for ellt, ell in self.parse_lines(file, boundary, content_length):
if ellt == _begin_file:
@@ -512,6 +518,10 @@ class MultiPartParser(object):
self.in_memory_threshold_reached(in_memory)
elif ellt == _end:
+ parts_decoded += 1
+ if self.max_form_parts is not None and \
+ parts_decoded > self.max_form_parts:
+ raise exceptions.RequestEntityTooLarge()
if is_file:
container.seek(0)
yield ('file',
diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py
index 92dfa5da..0671efeb 100644
--- a/werkzeug/wrappers.py
+++ b/werkzeug/wrappers.py
@@ -170,6 +170,12 @@ class BaseRequest(object):
#: .. versionadded:: 0.5
max_form_memory_size = 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`.
+ #:
+ max_form_parts = 1000
+
#: 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
@@ -359,7 +365,8 @@ class BaseRequest(object):
self.encoding_errors,
self.max_form_memory_size,
self.max_content_length,
- self.parameter_storage_class)
+ self.parameter_storage_class,
+ max_form_parts=self.max_form_parts)
def _load_form_data(self):
"""Method used internally to retrieve submitted data. After calling
--
2.34.1