Skip to content

Commit 4db2262

Browse files
committed
fix(authentication): parse base64 in strict mode
1 parent 07decd5 commit 4db2262

3 files changed

Lines changed: 30 additions & 6 deletions

File tree

httoop/authentication/basic.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
import base64
3+
import binascii
44
from binascii import Error as Base64Error
55

66
from httoop.exceptions import InvalidHeader
@@ -18,7 +18,9 @@ def parse(authinfo: bytes) -> dict[str, bytes]:
1818
# raise InvalidHeader(_(u'Invalid base64 in basic authentication'))
1919

2020
try:
21-
username, password = base64.b64decode(authinfo.strip()).split(b':')
21+
username, password = binascii.a2b_base64(authinfo.strip(), strict_mode=True).split(b':', 1)
22+
if not username:
23+
raise ValueError()
2224
except Base64Error:
2325
raise InvalidHeader(_('Basic authentication contains invalid base64'))
2426
except ValueError:
@@ -37,7 +39,7 @@ def compose(authinfo: ByteUnicodeDict) -> bytes:
3739
password = authinfo['password']
3840
# username = username.encode('ISO8859-1')
3941
# password = password.encode('ISO8859-1')
40-
return base64.b64encode(b'%s:%s' % (username, password))
42+
return binascii.b2a_base64(b'%s:%s' % (username, password), newline=False)
4143

4244

4345
class BasicAuthResponseScheme:

httoop/header/element.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ def encode_rfc2047(cls, value: str) -> bytes:
242242
try:
243243
value.encode('ISO8859-1')
244244
except UnicodeEncodeError:
245-
return b'=?utf-8?b?%s?=' % (b2a_base64(value.encode('utf-8')).rstrip(b'\n'),)
245+
return b'=?utf-8?b?%s?=' % (b2a_base64(value.encode('utf-8'), newline=False).rstrip(b'\n'),)
246246
else: # pragma: no cover
247-
return b'=?ISO8859-1?b?%s?=' % (b2a_base64(value.encode('ISO8859-1')).rstrip(b'\n'),)
247+
return b'=?ISO8859-1?b?%s?=' % (b2a_base64(value.encode('ISO8859-1'), newline=False).rstrip(b'\n'),)
248248

249249
def __repr__(self) -> str:
250250
params = f', {self.params!r}' if self.params else ''

tests/authentication/test_basic.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import base64
12
import pytest
23

34
from httoop.exceptions import InvalidHeader
@@ -27,7 +28,28 @@ def test_basic_authorization(headers):
2728
assert elem.password == 'test'
2829

2930

30-
@pytest.mark.parametrize('invalid', [b'foo', b'Zm9v', 'föo'.encode('latin1')])
31+
@pytest.mark.parametrize('invalid,username,password', [
32+
(base64.b64encode(b'username:pass:word'), b'username', b'pass:word'),
33+
(base64.b64encode(b'username:'), b'username', b''),
34+
(base64.b64encode(b'user\nname:password'), b'user\nname', b'password'),
35+
(base64.b64encode(b'username:pass\nword'), b'username', b'pass\nword'),
36+
# TODO: different encodings
37+
])
38+
def test_valid_headers(headers, invalid, username, password):
39+
headers.parse(b'Authorization: Basic %s' % (invalid,))
40+
elem = headers.element('Authorization')
41+
assert elem.params['username'] == username
42+
assert elem.params['password'] == password
43+
headers.clear()
44+
45+
46+
@pytest.mark.parametrize('invalid', [
47+
b'foo',
48+
b'Zm9v',
49+
'föo'.encode('latin1'),
50+
base64.b64encode(b':password'),
51+
base64.b64encode(b'username:password')+b'"$"_'
52+
])
3153
def test_invalid_headers(headers, invalid):
3254
headers.parse(b'Authorization: Basic %s' % (invalid,))
3355
with pytest.raises(InvalidHeader):

0 commit comments

Comments
 (0)