Repository: pallets/werkzeug (python)

Issue:
Authorization parameter parsing fails on invalid value
### Steps to reproduce
```
>>> from werkzeug.datastructures import Authorization
>>> Authorization.from_header("Digest =foo")
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[2], line 1
----> 1 Authorization.from_header("Digest =foo")

File ~/Downloads/.venv/lib/python3.12/site-packages/werkzeug/datastructures/auth.py:114, in Authorization.from_header(cls, value)
    110     return cls(scheme, {"username": username, "password": password})
    112 if "=" in rest.rstrip("="):
    113     # = that is not trailing, this is parameters.
--> 114     return cls(scheme, parse_dict_header(rest), None)
    116 # No = or only trailing =, this is a token.
    117 return cls(scheme, None, rest)

File ~/Downloads/.venv/lib/python3.12/site-packages/werkzeug/http.py:371, in parse_dict_header(value)
    368 value = value.strip()
    369 encoding: str | None = None
--> 371 if key[-1] == "*":
    372     # key*=charset''value becomes key=value, where value is percent encoded
    373     # adapted from parse_options_header, without the continuation handling
    374     key = key[:-1]
    375     match = _charset_value_re.match(value)

IndexError: string index out of range
```

### Expected behavior
Gracefully handle invalid inputs by skipping them

### Environment

- Python version: `3.12.7`
- Werkzeug version: `3.0.4`


Pull request:
Ignore invalid authorization parameters
This change will gracefully handle invalid authorization parameters.

fixes #2955 



Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index c92fe2e6..c26a58b5 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -7,6 +7,7 @@ Unreleased
 
 -   The Watchdog reloader ignores file closed no write events. :issue:`2945`
 -   Logging works with client addresses containing an IPv6 scope :issue:`2952`
+-   Ignore invalid authorization parameters. :issue:`2955`
 
 
 Version 3.0.4
diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py
index c0ec9238..cb8bc258 100644
--- a/src/werkzeug/http.py
+++ b/src/werkzeug/http.py
@@ -361,6 +361,10 @@ def parse_dict_header(value: str) -> dict[str, str | None]:
         key, has_value, value = item.partition("=")
         key = key.strip()
 
+        if not key:
+            # =value is not valid
+            continue
+
         if not has_value:
             result[key] = None
             continue


Test patch:
diff --git a/tests/test_http.py b/tests/test_http.py
index 02e5eb51..5002c093 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -107,9 +107,16 @@ class TestHTTPUtility:
     def test_list_header(self, value, expect):
         assert http.parse_list_header(value) == expect
 
-    def test_dict_header(self):
-        d = http.parse_dict_header('foo="bar baz", blah=42')
-        assert d == {"foo": "bar baz", "blah": "42"}
+    @pytest.mark.parametrize(
+        ("value", "expect"),
+        [
+            ('foo="bar baz", blah=42', {"foo": "bar baz", "blah": "42"}),
+            ("foo, bar=", {"foo": None, "bar": ""}),
+            ("=foo, =", {}),
+        ],
+    )
+    def test_dict_header(self, value, expect):
+        assert http.parse_dict_header(value) == expect
 
     def test_cache_control_header(self):
         cc = http.parse_cache_control_header("max-age=0, no-cache")
@@ -204,6 +211,10 @@ class TestHTTPUtility:
         assert Authorization.from_header(None) is None
         assert Authorization.from_header("foo").type == "foo"
 
+    def test_authorization_ignore_invalid_parameters(self):
+        a = Authorization.from_header("Digest foo, bar=, =qux, =")
+        assert a.to_header() == 'Digest foo, bar=""'
+
     def test_authorization_token_padding(self):
         # padded with =
         token = base64.b64encode(b"This has base64 padding").decode()