◐ Shell
reader mode source ↗
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
File filter
Conversations
Jump to
Diff view
Apply and reload
Show whitespace
Diff view
Apply and reload
3 changes: 3 additions & 0 deletions Doc/library/binascii.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ The :mod:`binascii` module defines the following functions:

If *ignorechars* is specified, it should be a :term:`bytes-like object`
containing characters to ignore from the input when *strict_mode* is true.
The default value of *strict_mode* is ``True`` if *ignorechars* is specified,
``False`` otherwise.

Expand Down
126 changes: 86 additions & 40 deletions Lib/test/test_binascii.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,66 +118,78 @@ def addnoise(line):
# empty strings. TBD: shouldn't it raise an exception instead ?
self.assertEqual(binascii.a2b_base64(self.type2test(fillers)), b'')

def test_base64_strict_mode(self):
# Test base64 with strict mode on
def _assertRegexTemplate(assert_regex: str, data: bytes, non_strict_mode_expected_result: bytes):
with self.assertRaisesRegex(binascii.Error, assert_regex):
binascii.a2b_base64(self.type2test(data), strict_mode=True)
self.assertEqual(binascii.a2b_base64(self.type2test(data), strict_mode=False),
non_strict_mode_expected_result)
self.assertEqual(binascii.a2b_base64(self.type2test(data)),
non_strict_mode_expected_result)

def assertExcessData(data, non_strict_mode_expected_result: bytes):
_assertRegexTemplate(r'(?i)Excess data', data, non_strict_mode_expected_result)

def assertNonBase64Data(data, non_strict_mode_expected_result: bytes):
_assertRegexTemplate(r'(?i)Only base64 data', data, non_strict_mode_expected_result)

def assertLeadingPadding(data, non_strict_mode_expected_result: bytes):
_assertRegexTemplate(r'(?i)Leading padding', data, non_strict_mode_expected_result)

def assertDiscontinuousPadding(data, non_strict_mode_expected_result: bytes):
_assertRegexTemplate(r'(?i)Discontinuous padding', data, non_strict_mode_expected_result)

def assertExcessPadding(data, non_strict_mode_expected_result: bytes):
_assertRegexTemplate(r'(?i)Excess padding', data, non_strict_mode_expected_result)

# Test excess data exceptions
assertExcessData(b'ab==a', b'i')
assertExcessPadding(b'ab===', b'i')
assertExcessPadding(b'ab====', b'i')
assertNonBase64Data(b'ab==:', b'i')
assertExcessData(b'abc=a', b'i\xb7')
assertNonBase64Data(b'abc=:', b'i\xb7')
assertNonBase64Data(b'ab==\n', b'i')
assertExcessPadding(b'abc==', b'i\xb7')
assertExcessPadding(b'abc===', b'i\xb7')
assertExcessPadding(b'abc====', b'i\xb7')
assertExcessPadding(b'abc=====', b'i\xb7')

# Test non-base64 data exceptions
assertNonBase64Data(b'\nab==', b'i')
assertNonBase64Data(b'ab:(){:|:&};:==', b'i')
assertNonBase64Data(b'a\nb==', b'i')
assertNonBase64Data(b'a\x00b==', b'i')

# Test malformed padding
assertLeadingPadding(b'=', b'')
assertLeadingPadding(b'==', b'')
assertLeadingPadding(b'===', b'')
assertLeadingPadding(b'====', b'')
assertLeadingPadding(b'=====', b'')
assertDiscontinuousPadding(b'ab=c=', b'i\xb7')
assertDiscontinuousPadding(b'ab=ab==', b'i\xb6\x9b')
assertNonBase64Data(b'ab=:=', b'i')
assertExcessPadding(b'abcd=', b'i\xb7\x1d')
assertExcessPadding(b'abcd==', b'i\xb7\x1d')
assertExcessPadding(b'abcd===', b'i\xb7\x1d')
assertExcessPadding(b'abcd====', b'i\xb7\x1d')
assertExcessPadding(b'abcd=====', b'i\xb7\x1d')

def test_base64_invalidchars(self):
def assertNonBase64Data(data, expected, ignorechars):
data = self.type2test(data)
assert_regex = r'(?i)Only base64 data'
@@ -195,10 +207,11 @@ def assertNonBase64Data(data, expected, ignorechars):
assertNonBase64Data(b'ab:(){:|:&};:==', b'i', ignorechars=b':;(){}|&')
assertNonBase64Data(b'a\nb==', b'i', ignorechars=b'\n')
assertNonBase64Data(b'a\x00b==', b'i', ignorechars=b'\x00')
assertNonBase64Data(b'ab==:', b'i', ignorechars=b':')
assertNonBase64Data(b'abc=:', b'i\xb7', ignorechars=b':')
assertNonBase64Data(b'ab==\n', b'i', ignorechars=b'\n')
assertNonBase64Data(b'ab=:=', b'i', ignorechars=b':')
assertNonBase64Data(b'a\nb==', b'i', ignorechars=bytearray(b'\n'))
assertNonBase64Data(b'a\nb==', b'i', ignorechars=memoryview(b'\n'))

Expand All @@ -221,36 +234,69 @@ def assertNonBase64Data(data, expected, ignorechars):
with self.assertRaises(TypeError):
binascii.a2b_base64(data, ignorechars=None)

def test_base64errors(self):
# Test base64 with invalid padding
def assertIncorrectPadding(data):
with self.assertRaisesRegex(binascii.Error, r'(?i)Incorrect padding'):
binascii.a2b_base64(self.type2test(data))

assertIncorrectPadding(b'ab')
assertIncorrectPadding(b'ab=')
assertIncorrectPadding(b'abc')
assertIncorrectPadding(b'abcdef')
assertIncorrectPadding(b'abcdef=')
assertIncorrectPadding(b'abcdefg')
assertIncorrectPadding(b'a=b=')
assertIncorrectPadding(b'a\nb=')

# Test base64 with invalid number of valid characters (1 mod 4)
def assertInvalidLength(data):
n_data_chars = len(re.sub(br'[^A-Za-z0-9/+]', br'', data))
expected_errmsg_re = \
r'(?i)Invalid.+number of data characters.+' + str(n_data_chars)
with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
binascii.a2b_base64(self.type2test(data))

assertInvalidLength(b'a')
assertInvalidLength(b'a=')
assertInvalidLength(b'a==')
assertInvalidLength(b'a===')
assertInvalidLength(b'a' * 5)
assertInvalidLength(b'a' * (4 * 87 + 1))
assertInvalidLength(b'A\tB\nC ??DE') # only 5 valid characters

def test_uu(self):
MAX_UU = 45
Expand Down
34 changes: 17 additions & 17 deletions Modules/binascii.c
Original file line number Diff line number Diff line change
Expand Up @@ -564,26 +564,24 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
pads++;

if (strict_mode) {
if (quad_pos == 0) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, (ascii_data == data->buf)
? "Leading padding not allowed"
: "Excess padding not allowed");
}
goto error_end;
}
if (quad_pos == 1) {
/* Set an error below. */
break;
}
if (quad_pos + pads > 4) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Excess padding not allowed");
}
goto error_end;
}
}
else {
if (quad_pos >= 2 && quad_pos + pads >= 4) {
Expand All @@ -592,8 +590,8 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
*/
goto done;
}
}
continue;
}

unsigned char v = table_a2b_base64[this_ch];
Expand All @@ -609,7 +607,9 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
}

// Characters that are not '=', in the middle of the padding, are not allowed
if (strict_mode && pads) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, (quad_pos + pads == 4)
Expand Down Expand Up @@ -662,7 +662,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
goto error_end;
}

if (quad_pos != 0 && quad_pos + pads != 4) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Incorrect padding");
Loading
Toggle all file notes Toggle all file annotations