Repository: pallets/werkzeug
Pull request #2694 (2023-05-05 10:38:30)

Do not apply max_form_parts to non-multipart data
> Another side effect of inlining some helper functions is that parsing application/x-www-form-urlencoded form data now uses max_form_parts like multipart/form-data. Not as important in this case, but may as well be consistent.

I feel like change from #2608 is actually more of a bug/regression that should possibly be reverted, which is exactly what this PR does.

- Unlike parsing multipart data, the overhead of parsing url-encoded form data is tiny (less than 2s for a million fields vs 25s for the same amount of multipart fields)
- It's not really that uncommon to have POST data with many values, especially compared to equally huge multipart requests
- Parsing urlencoded form data is more similar to parsing JSON data than to parsing multipart data - and AFAIK the default JSON parser has no such limit either.
- "parts" is a term that's usually just used when it comes to MIME/multipart data, not random form fields
- the docstring of `Request.max_form_parts` also only mentions "multipart parts"


Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [ ] Add `.. versionchanged::` entries in any relevant code docs.
- [x] Run `pre-commit` hooks and fix any issues.
- [x] Run `pytest` and `tox`, no tests failed.


I believe no `versionchanged` is needed since the change this fixes was silent in this regard as well.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index fa8d36a8..86e9d113 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -8,6 +8,8 @@ Unreleased
 -   ``Authorization.from_header`` and ``WWWAuthenticate.from_header`` detects tokens
     that end with base64 padding (``=``). :issue:`2685`
 -   Remove usage of ``warnings.catch_warnings``. :issue:`2690`
+-   Remove ``max_form_parts`` restriction from standard form data parsing and only use
+    if for multipart content. :pr:`2694`
 
 
 Version 2.3.3
diff --git a/src/werkzeug/formparser.py b/src/werkzeug/formparser.py
index 074ac542..99937e43 100644
--- a/src/werkzeug/formparser.py
+++ b/src/werkzeug/formparser.py
@@ -105,8 +105,8 @@ def parse_form_data(
     :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.
+    :param max_form_parts: The maximum number of multipart parts to be parsed. If this
+        is exceeded, a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
     :return: A tuple in the form ``(stream, form, files)``.
 
     .. versionchanged:: 2.3
@@ -157,8 +157,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.
+    :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
@@ -378,7 +378,6 @@ class FormDataParser:
                 keep_blank_values=True,
                 encoding=self.charset,
                 errors="werkzeug.url_quote",
-                max_num_fields=self.max_form_parts,
             )
         except ValueError as e:
             raise RequestEntityTooLarge() from e


Test patch:
diff --git a/tests/test_formparser.py b/tests/test_formparser.py
index f9b44d7c..1dcb167e 100644
--- a/tests/test_formparser.py
+++ b/tests/test_formparser.py
@@ -126,8 +126,8 @@ class TestFormParser:
         r = Request.from_values(method="POST", data={"a": 1, "b": 2})
         r.max_form_parts = 1
 
-        with pytest.raises(RequestEntityTooLarge):
-            r.form
+        assert r.form["a"] == "1"
+        assert r.form["b"] == "2"
 
     def test_missing_multipart_boundary(self):
         data = (