Repository: pallets/werkzeug
Pull request #1325 (2018-06-11 00:56:18)

The default charset for the basic http authentication is utf-8.
#1323 


Patch:
diff --git a/CHANGES.rst b/CHANGES.rst
index 120a832a..a9f41360 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -84,6 +84,11 @@ Unreleased
     using Flask you can use Flask-Caching. If you are using Werkzeug you
     are welcome to extract the code (see LICENSE) into a separate
     project. (`#1249`_)
+-   :func:`~http.parse_authorization_header` (and
+    :class:`~datastructures.Authorization`,
+    :attr:`~wrappers.Request.authorization`) treats the authorization
+    header as UTF-8. On Python 2, basic auth username and password are
+    ``unicode``. (`#1325`_)
 
 .. _`#209`: https://github.com/pallets/werkzeug/pull/209
 .. _`#609`: https://github.com/pallets/werkzeug/pull/609
@@ -117,6 +122,7 @@ Unreleased
 .. _`#1315`: https://github.com/pallets/werkzeug/pull/1315
 .. _`#1316`: https://github.com/pallets/werkzeug/pull/1316
 .. _`#1318`: https://github.com/pallets/werkzeug/pull/1318
+.. _`#1325`: https://github.com/pallets/werkzeug/pull/1325
 .. _`#1338`: https://github.com/pallets/werkzeug/pull/1338
 .. _`#1340`: https://github.com/pallets/werkzeug/pull/1340
 .. _`#1377`: https://github.com/pallets/werkzeug/pull/1377
diff --git a/werkzeug/http.py b/werkzeug/http.py
index 99470262..2e3adff6 100644
--- a/werkzeug/http.py
+++ b/werkzeug/http.py
@@ -41,6 +41,7 @@ from werkzeug._compat import to_unicode, iteritems, text_type, \
 
 
 _cookie_charset = 'latin1'
+_basic_auth_charset = 'utf-8'
 # for explanation of "media-range", etc. see Sections 5.3.{1,2} of RFC 7231
 _accept_re = re.compile(
     r'''(                       # media-range capturing-parenthesis
@@ -505,8 +506,12 @@ def parse_authorization_header(value):
             username, password = base64.b64decode(auth_info).split(b':', 1)
         except Exception:
             return
-        return Authorization('basic', {'username':  bytes_to_wsgi(username),
-                                       'password': bytes_to_wsgi(password)})
+        return Authorization(
+            'basic', {
+                'username': to_unicode(username, _basic_auth_charset),
+                'password': to_unicode(password, _basic_auth_charset)
+            }
+        )
     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
index 32d0a270..a9abf3ae 100644
--- a/tests/test_http.py
+++ b/tests/test_http.py
@@ -111,8 +111,18 @@ class TestHTTPUtility(object):
     def test_authorization_header(self):
         a = http.parse_authorization_header('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
         assert a.type == 'basic'
-        assert a.username == 'Aladdin'
-        assert a.password == 'open sesame'
+        assert a.username == u'Aladdin'
+        assert a.password == u'open sesame'
+
+        a = http.parse_authorization_header('Basic 0YDRg9GB0YHQutC40IE60JHRg9C60LLRiw==')
+        assert a.type == 'basic'
+        assert a.username == u'русскиЁ'
+        assert a.password == u'Буквы'
+
+        a = http.parse_authorization_header('Basic 5pmu6YCa6K+dOuS4reaWhw==')
+        assert a.type == 'basic'
+        assert a.username == u'普通话'
+        assert a.password == u'中文'
 
         a = http.parse_authorization_header('''Digest username="Mufasa",
                      realm="testrealm@host.invalid",
diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py
index ca3cbc64..273d3068 100644
--- a/tests/test_wrappers.py
+++ b/tests/test_wrappers.py
@@ -206,8 +206,18 @@ def test_authorization_mixin():
     })
     a = request.authorization
     strict_eq(a.type, 'basic')
-    strict_eq(a.username, 'Aladdin')
-    strict_eq(a.password, 'open sesame')
+    strict_eq(a.username, u'Aladdin')
+    strict_eq(a.password, u'open sesame')
+
+
+def test_authorization_with_unicode():
+    request = wrappers.Request.from_values(headers={
+        'Authorization': 'Basic 0YDRg9GB0YHQutC40IE60JHRg9C60LLRiw=='
+    })
+    a = request.authorization
+    strict_eq(a.type, 'basic')
+    strict_eq(a.username, u'русскиЁ')
+    strict_eq(a.password, u'Буквы')
 
 
 def test_stream_only_mixing():