diff --git a/CHANGES.rst b/CHANGES.rst index 120a832a3..a9f41360e 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/tests/test_http.py b/tests/test_http.py index 32d0a270d..a9abf3ae2 100644 --- a/tests/test_http.py +++ b/tests/test_http.py @@ -111,8 +111,18 @@ def test_cache_control_header(self): 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 ca3cbc648..273d30687 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(): diff --git a/werkzeug/http.py b/werkzeug/http.py index 994702624..2e3adff6d 100644 --- a/werkzeug/http.py +++ b/werkzeug/http.py @@ -41,6 +41,7 @@ _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':