Repository: pallets/werkzeug

Issue:
parse_authorization_header raises UnicodeDecodeError, not returning None
`werkzeug.http.parse_authorization_header()` docstring states:
> The return value is either `None` if the header was invalid or not given

But in case of passing string improperly formatted (i.e. not properly UTF-8 encoded) it raises UnicodeDecodeError exception:

```
>>> import base64
>>> from werkzeug.http import parse_authorization_header
>>> value = 'Basic ' + base64.b64encode(b'\xffser:pass').decode()
>>> parse_authorization_header(value)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/py3.8/lib/python3.8/site-packages/werkzeug/http.py", line 610, in parse_authorization_header
    "username": to_unicode(username, _basic_auth_charset),
  File "/py3.8/lib/python3.8/site-packages/werkzeug/_compat.py", line 219, in to_unicode
    return x.decode(charset, errors)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte
```
Am I right that exception handling should be added in `parse_authorization_header()`?


Discussion:
This is an issue with how you constructed the header, you relied on whatever encoding your local machine was set to rather than using 'utf-8'. I don't think this needs to be handled in Werkzeug as it shouldn't come up in normal use and would indicate an issue with the client if the encoding it sends doesn't decode the bytes.
> This is an issue with how you constructed the header, you relied on whatever encoding your local machine was set to rather than using 'utf-8'. I don't think this needs to be handled in Werkzeug as it shouldn't come up in normal use and would indicate an issue with the client if the encoding it sends doesn't decode the bytes.

Such behaviour differs from docstring. In fact Authorization header is improperly formatted and invalid.
Imagine you have a production server and someone sends auth requests using improperly formatted header. Correct behaviour will be to reply with 401 which in turn generated using `None` response taken from `parse_authorization_header()`.

I.e. `Flask-login` uses Flask `request.authorization` which in turn uses parse_authorization_header().
In result you get UnicodeDecodeError rather than 401.
The server should not crash as a result of the client sending bad data. This is indeed a bug in the function that should be fixed.

Resolving patch:
diff --git a/src/werkzeug/http.py b/src/werkzeug/http.py
--- a/src/werkzeug/http.py
+++ b/src/werkzeug/http.py
@@ -572,13 +572,16 @@ def parse_authorization_header(value):
             username, password = base64.b64decode(auth_info).split(b":", 1)
         except Exception:
             return
-        return Authorization(
-            "basic",
-            {
-                "username": _to_str(username, _basic_auth_charset),
-                "password": _to_str(password, _basic_auth_charset),
-            },
-        )
+        try:
+            return Authorization(
+                "basic",
+                {
+                    "username": _to_str(username, _basic_auth_charset),
+                    "password": _to_str(password, _basic_auth_charset),
+                },
+            )
+        except UnicodeDecodeError:
+            return
     elif auth_type == b"digest":
         auth_map = parse_dict_header(auth_info)
         for key in "username", "realm", "nonce", "uri", "response":


Test patch:
diff --git a/tests/test_http.py b/tests/test_http.py
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -1,3 +1,4 @@
+import base64
 from datetime import datetime
 
 import pytest
@@ -181,6 +182,11 @@ def test_authorization_header(self):
         assert http.parse_authorization_header(None) is None
         assert http.parse_authorization_header("foo") is None
 
+    def test_bad_authorization_header_encoding(self):
+        """If the base64 encoded bytes can't be decoded as UTF-8"""
+        content = base64.b64encode(b"\xffser:pass").decode()
+        assert http.parse_authorization_header(f"Basic {content}") is None
+
     def test_www_authenticate_header(self):
         wa = http.parse_www_authenticate_header('Basic realm="WallyWorld"')
         assert wa.type == "basic"