From dbd3af18b9e7e6e081181594fd6a9fb8daa01a91 Mon Sep 17 00:00:00 2001 From: starlet-dx <15929766099@163.com> Date: Tue, 15 Aug 2023 10:04:11 +0800 Subject: [PATCH 1/1] limit the maximum number of multipart form parts Origin: https://github.com/pallets/werkzeug/commit/fe899d0cdf767a7289a8bf746b7f72c2907a1b4b --- src/werkzeug/formparser.py | 16 ++++++++++++++-- src/werkzeug/wrappers/base_request.py | 8 ++++++++ tests/test_formparser.py | 9 +++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py index ffdb9b0..40959fb 100644 --- a/src/werkzeug/formparser.py +++ b/src/werkzeug/formparser.py @@ -168,6 +168,8 @@ 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__( @@ -179,6 +181,7 @@ class FormDataParser(object): max_content_length=None, cls=None, silent=True, + max_form_parts=None, ): if stream_factory is None: stream_factory = default_stream_factory @@ -191,6 +194,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) @@ -244,6 +248,7 @@ class FormDataParser(object): 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") if boundary is None: @@ -333,10 +338,12 @@ class MultiPartParser(object): 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 ) @@ -528,11 +535,12 @@ class MultiPartParser(object): yield _end, None - def parse_parts(self, file, boundary, content_length): + def parse_parts(self, file, boundary, content_length, max_parts=None): """Generate ``('file', (name, val))`` and ``('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: @@ -562,6 +570,9 @@ class MultiPartParser(object): self.in_memory_threshold_reached(in_memory) elif ellt == _end: + _parts_decoded += 1 + if max_parts is not None and _parts_decoded > max_parts: + raise exceptions.RequestEntityTooLarge() if is_file: container.seek(0) yield ( @@ -577,7 +588,8 @@ class MultiPartParser(object): def parse(self, file, boundary, content_length): formstream, filestream = tee( - self.parse_parts(file, boundary, content_length), 2 + self.parse_parts(file, boundary, content_length, + max_parts=self.max_form_parts), 2 ) form = (p[1] for p in formstream if p[0] == "form") files = (p[1] for p in filestream if p[0] == "file") diff --git a/src/werkzeug/wrappers/base_request.py b/src/werkzeug/wrappers/base_request.py index 1f21db2..0c1e221 100644 --- a/src/werkzeug/wrappers/base_request.py +++ b/src/werkzeug/wrappers/base_request.py @@ -98,6 +98,13 @@ 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`. + #: + #: .. versionadded:: 2.2.3 + 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 @@ -293,6 +300,7 @@ class BaseRequest(object): 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): diff --git a/tests/test_formparser.py b/tests/test_formparser.py index 6d85838..2a8d635 100644 --- a/tests/test_formparser.py +++ b/tests/test_formparser.py @@ -124,6 +124,15 @@ class TestFormParser(object): req.max_form_memory_size = 400 strict_eq(req.form["foo"], u"Hello World") + req = Request.from_values( + input_stream=io.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" -- 2.30.0