diff --git a/CHANGES.rst b/CHANGES.rst index 0d89c6d01..2e408af14 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -21,10 +21,10 @@ Unreleased the requested size in one ``read`` call. :issue:`2558` - A cookie header that starts with ``=`` is treated as an empty key and discarded, rather than stripping the leading ``==``. -- Specify a maximum number of multipart parts, default 100, after - which a RequestEntityTooLarge exception is raised on parsing. The - mitigates a DOS attack whereby a larger number file/form parts are - sent resulting in a heavy parsing cost. +- Specify a maximum number of multipart parts, default 1000, after which a + ``RequestEntityTooLarge`` exception is raised on parsing. This mitigates a DoS + attack where a larger number of form/file parts would result in disproportionate + resource use. Version 2.2.2 diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py index 820190be2..bebb2fc8f 100644 --- a/src/werkzeug/formparser.py +++ b/src/werkzeug/formparser.py @@ -179,10 +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 accepted for the - multipart data sent. If this is exceeded an - :exc:`~exceptions.RequestEntityTooLarge` exception - is raised. + :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__( @@ -194,6 +192,7 @@ def __init__( 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: @@ -204,13 +203,13 @@ def __init__( 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 self.cls = cls self.silent = silent - self.max_form_parts = max_form_parts def get_parse_func( self, mimetype: str, options: t.Dict[str, str] @@ -419,7 +418,7 @@ def parse( ) parser = MultipartDecoder( - boundary, self.max_form_memory_size, self.max_form_parts + boundary, self.max_form_memory_size, max_parts=self.max_form_parts ) fields = [] diff --git a/src/werkzeug/sansio/multipart.py b/src/werkzeug/sansio/multipart.py index d90b445b9..2684e5dd6 100644 --- a/src/werkzeug/sansio/multipart.py +++ b/src/werkzeug/sansio/multipart.py @@ -87,6 +87,7 @@ def __init__( self, boundary: bytes, max_form_memory_size: Optional[int] = None, + *, max_parts: Optional[int] = None, ) -> None: self.buffer = bytearray() diff --git a/src/werkzeug/wrappers/request.py b/src/werkzeug/wrappers/request.py index fa5b18586..2de77df42 100644 --- a/src/werkzeug/wrappers/request.py +++ b/src/werkzeug/wrappers/request.py @@ -83,12 +83,9 @@ class Request(_SansIORequest): #: .. versionadded:: 0.5 max_form_memory_size: t.Optional[int] = None - #: the maximum number of multipart parts. This is forwarded to teh - #: form data parsing function (:func:`parse_form_data`). When the - #: :attr:`form` or :attr:`files` attribute is accessed and the - #: parsing fails because more parts than the specified value is - #: transmitted a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` - #: exception is raised. + #: 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