Repository: pallets/werkzeug (python)

Issue:
Built-in converters do not check syntax of the parameters
Hey, everybody.

I noticed that default Converters do not check correct syntax of parameters such as:
- minlength
- maxlength
- e.t.c

when using the ";" delimiter instead of "," (there is no clear and exhaustive description of this in the documentation, for example here: https://werkzeug.palletsprojects.com/en/3.0.x/routing/#built-in-converters).
does not cause an exception to be thrown, moreover, the syntax of this kind:
```
<int(minL=0;max=500):my_value>
<int(min=0;max=500):my_value>
```
does not apply any parameters other than max at all.

In another case, for example:
`<string(minLength=8;maxlength=32):my_value>`
minLength=8 - also not applied

but in case you write it like this:
`<string(minlength=8;maxLength=32):my_value>`

an exception occurs:
```
UnicodeConverter:
..\site-packages\werkzeug\routing\converters.py:254 and :77
..\site-packages\werkzeug\routing\rules.py:578
TypeError: init() got an unexpected keyword argument 'maxLength'
```

what do I see as the solutions to this situation?

1. describe the correct syntax in the documentation, so that it would be a complete and exhaustive description (currently the syntax is only specified for python function arguments, but not for rule_route).
2. fix checking of parameter syntax in the stringtype fragment of rule_route
3. execute both items 1 and 2 at once.

below is the full traceback of the error:

1. if syntax:
`<string(minlength=8,maxLength=32):my_value>`

  ```
File "...\lib\site-packages\flask\sansio\scaffold.py", line 44, in wrapper_func
    return f(self, *args, **kwargs)
  File "...\lib\site-packages\flask\sansio\app.py", line 655, in add_url_rule
    self.url_map.add(rule)
  File "...\lib\site-packages\werkzeug\routing\map.py", line 169, in add
    rule.bind(self)
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 563, in bind
    self.compile()
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 708, in compile
    self._parts.extend(self._parse_rule(rule))
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 614, in _parse_rule
    convobj = self.get_converter(
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 578, in get_converter
    return self.map.converters[converter_name](self.map, *args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'maxLength'
```

2. if syntax:
`<string(minlength=8;maxLength=32):my_value>`

  ```
File "...\lib\site-packages\flask\sansio\scaffold.py", line 44, in wrapper_func
    return f(self, *args, **kwargs)
  File "...\lib\site-packages\flask\sansio\app.py", line 655, in add_url_rule
    self.url_map.add(rule)
  File "...\lib\site-packages\werkzeug\routing\map.py", line 169, in add
    rule.bind(self)
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 563, in bind
    self.compile()
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 708, in compile
    self._parts.extend(self._parse_rule(rule))
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 614, in _parse_rule
    convobj = self.get_converter(
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 578, in get_converter
    return self.map.converters[converter_name](self.map, *args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'maxLength'
```

3. if syntax:
`<string(min_my_any_incorrect_input_length=8;maxlength=32):my_value>`
the error is not present, but is expected to be present

4. if syntax:
<string(min_my_any_incorrect_input_length=8,maxlength=32):my_value>

```
File "...\lib\site-packages\flask\sansio\scaffold.py", line 44, in wrapper_func
    return f(self, *args, **kwargs)
  File "...\lib\site-packages\flask\sansio\app.py", line 655, in add_url_rule
    self.url_map.add(rule)
  File "...\lib\site-packages\werkzeug\routing\map.py", line 169, in add
    rule.bind(self)
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 563, in bind
    self.compile()
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 708, in compile
    self._parts.extend(self._parse_rule(rule))
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 614, in _parse_rule
    convobj = self.get_converter(
  File "...\lib\site-packages\werkzeug\routing\rules.py", line 578, in get_converter
    return self.map.converters[converter_name](self.map, *args, **kwargs)
TypeError: __init__() got an unexpected keyword argument 'min_my_any_incorrect_input_length'
```


Once again, let's rephrase what was said:
if a delimiter is specified, e.g. ";", when "," is expected, then the parameters and their correct syntax are not checked, and the invalid syntax exception is not raised, both for the delimiter character ";" and for an invalid parameter name, e.g. (from the code snippets above) "min_my_any_incorrect_input_length".

In this case, the compilation of the application (in my case flask) completes without error, and min & max length are expected to work correctly.
This could create potential security threats, but I don't think it's worth listing the possible variations of potential vulnerabilities.


Environment:
- Python version: 3.9.10
- Werkzeug version: 3.0.0


Pull request description:
Raise an error if the converter arguments cannot be parsed
This could happen for example with `min=0;max=500` as the `;` is not a word character everything before it is ignored in the regex during the finditer call. This then lefts the user confused as the `min=0;` was silently ignored.

fixes #2822 

Checklist:

- [x] Add tests that demonstrate the correct behavior of the change. Tests should fail without the change.
- [x] Add or update relevant docs, in the docs folder and in code.
- [x] Add an entry in `CHANGES.rst` summarizing the change and linking to the issue.
- [x] 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.


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index c7f5aeba..b2f2a08f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,8 @@ Unreleased
 -   Fix response_wrapper type check in test client. :issue:`2831`
 -   Make the return type of ``MultiPartParser.parse`` more
     precise. :issue:`2840`
+-   Raise an error if converter arguments cannot be
+    parsed. :issue:`2822`
 
 Version 3.0.1
 -------------
diff --git a/src/werkzeug/routing/rules.py b/src/werkzeug/routing/rules.py
index ea32d1dc..7029d8bc 100644
--- a/src/werkzeug/routing/rules.py
+++ b/src/werkzeug/routing/rules.py
@@ -67,6 +67,7 @@ _part_re = re.compile(
 _simple_rule_re = re.compile(r"<([^>]+)>")
 _converter_args_re = re.compile(
     r"""
+    \s*
     ((?P<name>\w+)\s*=\s*)?
     (?P<value>
         True|False|
@@ -112,8 +113,14 @@ def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.An
     argstr += ","
     args = []
     kwargs = {}
+    position = 0
 
     for item in _converter_args_re.finditer(argstr):
+        if item.start() != position:
+            raise ValueError(
+                f"Cannot parse converter argument '{argstr[position:item.start()]}'"
+            )
+
         value = item.group("stringval")
         if value is None:
             value = item.group("value")
@@ -123,6 +130,7 @@ def parse_converter_args(argstr: str) -> tuple[tuple[t.Any, ...], dict[str, t.An
         else:
             name = item.group("name")
             kwargs[name] = value
+        position = item.end()
 
     return tuple(args), kwargs
 


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
index 5291348c..02db898d 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -1076,6 +1076,9 @@ def test_converter_parser():
     args, kwargs = r.parse_converter_args('"foo", "bar"')
     assert args == ("foo", "bar")
 
+    with pytest.raises(ValueError):
+        r.parse_converter_args("min=0;max=500")
+
 
 def test_alias_redirects():
     m = r.Map(