Skip to content

Commit b1d8594

Browse files
gh-issue-148108: Revive binascii.Incomplete.
Make binascii.Incomplete a subclass of binascii.Error and use it for incomplete data errors.
1 parent 8bf8bf9 commit b1d8594

File tree

6 files changed

+56
-32
lines changed

6 files changed

+56
-32
lines changed

Doc/library/binascii.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,14 @@ The :mod:`!binascii` module defines the following functions:
349349

350350
.. exception:: Incomplete
351351

352-
Exception raised on incomplete data. These are usually not programming errors,
352+
A subclass of :exc:`Error` raised on incomplete data.
353+
These are usually not programming errors,
353354
but may be handled by reading a little more data and trying again.
354355

356+
.. versionchanged:: next
357+
Made :exc:`!Incomplete` a subclass of :exc:`!Error`.
358+
It is now raised on incomplete Base16, Base32, and Base64 data.
359+
355360

356361
.. data:: BASE64_ALPHABET
357362

Doc/whatsnew/3.15.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,10 @@ binascii
709709
:func:`~binascii.unhexlify`, and :func:`~binascii.a2b_base64`.
710710
(Contributed by Serhiy Storchaka in :gh:`144001` and :gh:`146431`.)
711711

712+
* :exc:`~binascii.Incomplete`, which is now a subclass of :exc:`~binascii.Error`,
713+
is now raised for incomplete data instead of :exc:`!Error`.
714+
(Contributed by Serhiy Storchaka in :gh:`148108`.)
715+
712716

713717
calendar
714718
--------

Lib/test/test_base64.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -330,8 +330,8 @@ def test_b64decode_altchars(self):
330330
self.assertRaises(ValueError, base64.b64decode, '', altchars='+/-')
331331

332332
def test_b64decode_padding_error(self):
333-
self.assertRaises(binascii.Error, base64.b64decode, b'abc')
334-
self.assertRaises(binascii.Error, base64.b64decode, 'abc')
333+
self.assertRaises(binascii.Incomplete, base64.b64decode, b'abc')
334+
self.assertRaises(binascii.Incomplete, base64.b64decode, 'abc')
335335

336336
def test_b64decode_padded(self):
337337
b64decode = base64.b64decode
@@ -358,9 +358,9 @@ def check(data, expected, padded=0):
358358
check(b'YW=Jj', b'abc')
359359
check(b'YWJ=j', b'abc')
360360

361-
with self.assertRaisesRegex(binascii.Error, 'Incorrect padding'):
361+
with self.assertRaisesRegex(binascii.Incomplete, 'Incorrect padding'):
362362
urlsafe_b64decode(b'YQ', padded=True)
363-
with self.assertRaisesRegex(binascii.Error, 'Incorrect padding'):
363+
with self.assertRaisesRegex(binascii.Incomplete, 'Incorrect padding'):
364364
urlsafe_b64decode(b'YWI', padded=True)
365365

366366
def _common_test_ignorechars(self, func):
@@ -483,7 +483,7 @@ def test_b64decode_invalid_chars(self):
483483
self.assertEqual(str(cm.warning),
484484
"invalid character '/' in URL-safe Base64 data "
485485
"will be discarded in future Python versions")
486-
with self.assertRaises(binascii.Error):
486+
with self.assertRaises(binascii.Incomplete):
487487
base64.b64decode(b'+/!', altchars=b'-_')
488488

489489
def _altchars_strategy():
@@ -879,7 +879,7 @@ def test_b16decode(self):
879879
# Non-alphabet characters
880880
self.assertRaises(binascii.Error, base64.b16decode, '0102AG')
881881
# Incorrect "padding"
882-
self.assertRaises(binascii.Error, base64.b16decode, '010')
882+
self.assertRaises(binascii.Incomplete, base64.b16decode, '010')
883883

884884
def test_b16decode_ignorechars(self):
885885
self._common_test_ignorechars(base64.b16decode)
@@ -1226,9 +1226,9 @@ def test_a85decode_errors(self):
12261226
with self.assertRaises(ValueError, msg=bytes([c])):
12271227
base64.a85decode(b'<~!!!!' + bytes([c]) + b'~>', adobe=True)
12281228

1229-
self.assertRaises(ValueError, base64.a85decode,
1229+
self.assertRaises(binascii.Incomplete, base64.a85decode,
12301230
b"malformed", adobe=True)
1231-
self.assertRaises(ValueError, base64.a85decode,
1231+
self.assertRaises(binascii.Incomplete, base64.a85decode,
12321232
b"<~still malformed", adobe=True)
12331233

12341234
# With adobe=False (the default), Adobe framing markers are disallowed

Lib/test/test_binascii.py

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -343,12 +343,12 @@ def test_base64errors(self):
343343
# Test base64 with invalid padding
344344
def assertIncorrectPadding(data, strict_mode=True):
345345
data = self.type2test(data)
346-
with self.assertRaisesRegex(binascii.Error, r'(?i)Incorrect padding'):
346+
with self.assertRaisesRegex(binascii.Incomplete, r'(?i)Incorrect padding'):
347347
binascii.a2b_base64(data)
348-
with self.assertRaisesRegex(binascii.Error, r'(?i)Incorrect padding'):
348+
with self.assertRaisesRegex(binascii.Incomplete, r'(?i)Incorrect padding'):
349349
binascii.a2b_base64(data, strict_mode=False)
350350
if strict_mode:
351-
with self.assertRaisesRegex(binascii.Error, r'(?i)Incorrect padding'):
351+
with self.assertRaisesRegex(binascii.Incomplete, r'(?i)Incorrect padding'):
352352
binascii.a2b_base64(data, strict_mode=True)
353353

354354
assertIncorrectPadding(b'ab')
@@ -361,18 +361,23 @@ def assertIncorrectPadding(data, strict_mode=True):
361361
assertIncorrectPadding(b'a\nb=', strict_mode=False)
362362

363363
# Test base64 with invalid number of valid characters (1 mod 4)
364-
def assertInvalidLength(data, strict_mode=True):
364+
def assertInvalidLength(data, strict_mode=True, incomplete=True):
365+
strict_incomplete = b'=' not in data
365366
n_data_chars = len(re.sub(br'[^A-Za-z0-9/+]', br'', data))
366367
data = self.type2test(data)
367368
expected_errmsg_re = \
368369
r'(?i)Invalid.+number of data characters.+' + str(n_data_chars)
369-
with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
370+
with self.assertRaisesRegex(binascii.Incomplete, expected_errmsg_re):
370371
binascii.a2b_base64(data)
371-
with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
372+
with self.assertRaisesRegex(binascii.Incomplete, expected_errmsg_re):
372373
binascii.a2b_base64(data, strict_mode=False)
373374
if strict_mode:
374-
with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
375-
binascii.a2b_base64(data, strict_mode=True)
375+
if strict_incomplete:
376+
with self.assertRaisesRegex(binascii.Incomplete, expected_errmsg_re):
377+
binascii.a2b_base64(data, strict_mode=True)
378+
else:
379+
with self.assertRaisesRegex(binascii.Error, expected_errmsg_re):
380+
binascii.a2b_base64(data, strict_mode=True)
376381

377382
assertInvalidLength(b'a')
378383
assertInvalidLength(b'a=')
@@ -495,12 +500,14 @@ def addnoise(line):
495500
self.assertEqual(b, b"")
496501

497502
def test_ascii85_errors(self):
498-
def _assertRegexTemplate(assert_regex, data, **kwargs):
499-
with self.assertRaisesRegex(binascii.Error, assert_regex):
503+
def _assertRegexTemplate(assert_regex, data, *,
504+
expected_errtype=binascii.Error, **kwargs):
505+
with self.assertRaisesRegex(expected_errtype, assert_regex):
500506
binascii.a2b_ascii85(self.type2test(data), **kwargs)
501507

502508
def assertMissingDelimiter(data):
503-
_assertRegexTemplate(r"(?i)end with b'~>'", data, adobe=True)
509+
_assertRegexTemplate(r"(?i)end with b'~>'", data, adobe=True,
510+
expected_errtype=binascii.Incomplete)
504511

505512
def assertOverflow(data):
506513
_assertRegexTemplate(r"(?i)Ascii85 overflow", data)
@@ -792,8 +799,9 @@ def _fixPadding(data):
792799
p = 8 - len_8 if len_8 else 0
793800
return fixed + b"=" * p
794801

795-
def _assertRegexTemplate(assert_regex, data, good_padding_result=None, **kwargs):
796-
with self.assertRaisesRegex(binascii.Error, assert_regex):
802+
def _assertRegexTemplate(assert_regex, data, good_padding_result=None, *,
803+
expected_errtype=binascii.Error, **kwargs):
804+
with self.assertRaisesRegex(expected_errtype, assert_regex):
797805
binascii.a2b_base32(self.type2test(data), **kwargs)
798806
if good_padding_result:
799807
fixed = self.type2test(_fixPadding(data))
@@ -812,16 +820,21 @@ def assertLeadingPadding(*args, **kwargs):
812820
_assertRegexTemplate(r"(?i)Leading padding", *args, **kwargs)
813821

814822
def assertIncorrectPadding(*args):
815-
_assertRegexTemplate(r"(?i)Incorrect padding", *args)
823+
_assertRegexTemplate(r"(?i)Incorrect padding", *args,
824+
expected_errtype=binascii.Incomplete)
816825

817826
def assertDiscontinuousPadding(*args):
818827
_assertRegexTemplate(r"(?i)Discontinuous padding", *args)
819828

820829
def assertInvalidLength(data, *args, length=None, **kwargs):
821830
if length is None:
822831
length = len(data.split(b'=', 1)[0].replace(b' ', b''))
832+
incomplete = b'=' not in data
833+
expected_errtype = binascii.Incomplete if incomplete else binascii.Error
823834
assert_regex = fr"(?i)Invalid.+number of data characters \({length}\)"
824-
_assertRegexTemplate(assert_regex, data, *args, **kwargs)
835+
_assertRegexTemplate(assert_regex, data, *args,
836+
expected_errtype=expected_errtype,
837+
**kwargs)
825838

826839
assertNonBase32Data(b"a")
827840
assertNonBase32Data(b"AA-")
@@ -1098,7 +1111,7 @@ def test_hex(self):
10981111
t = binascii.b2a_hex(self.type2test(s))
10991112
u = binascii.a2b_hex(self.type2test(t))
11001113
self.assertEqual(s, u)
1101-
self.assertRaises(binascii.Error, binascii.a2b_hex, t[:-1])
1114+
self.assertRaises(binascii.Incomplete, binascii.a2b_hex, t[:-1])
11021115
self.assertRaises(binascii.Error, binascii.a2b_hex, t[:-1] + b'q')
11031116
self.assertRaises(binascii.Error, binascii.a2b_hex, bytes([255, 255]))
11041117
self.assertRaises(binascii.Error, binascii.a2b_hex, b'0G')
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Make :exc:`binascii.Incomplete` a subclass of :exc:`binascii.Error` and use
2+
it for incomplete data errors.

Modules/binascii.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
892892
state = get_binascii_state(module);
893893
if (state) {
894894
unsigned char *bin_data_start = PyBytesWriter_GetData(writer);
895-
PyErr_Format(state->Error,
895+
PyErr_Format(ascii_len ? state->Error : state->Incomplete,
896896
"Invalid base64-encoded string: "
897897
"number of data characters (%zd) cannot be 1 more "
898898
"than a multiple of 4",
@@ -904,7 +904,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
904904
if (padded && quad_pos != 0 && quad_pos + pads < 4) {
905905
state = get_binascii_state(module);
906906
if (state) {
907-
PyErr_SetString(state->Error, "Incorrect padding");
907+
PyErr_SetString(state->Incomplete, "Incorrect padding");
908908
}
909909
goto error_end;
910910
}
@@ -1060,7 +1060,7 @@ binascii_a2b_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces,
10601060
{
10611061
state = get_binascii_state(module);
10621062
if (state != NULL) {
1063-
PyErr_SetString(state->Error,
1063+
PyErr_SetString(state->Incomplete,
10641064
"Ascii85 encoded byte sequences must end with b'~>'");
10651065
}
10661066
return NULL;
@@ -1706,7 +1706,7 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded,
17061706
state = get_binascii_state(module);
17071707
if (state) {
17081708
unsigned char *bin_data_start = PyBytesWriter_GetData(writer);
1709-
PyErr_Format(state->Error,
1709+
PyErr_Format(ascii_len ? state->Error : state->Incomplete,
17101710
"Invalid base32-encoded string: "
17111711
"number of data characters (%zd) "
17121712
"cannot be 1, 3, or 6 more than a multiple of 8",
@@ -1718,7 +1718,7 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded,
17181718
if (padded && octa_pos != 0 && octa_pos + pads < 8) {
17191719
state = get_binascii_state(module);
17201720
if (state) {
1721-
PyErr_SetString(state->Error, "Incorrect padding");
1721+
PyErr_SetString(state->Incomplete, "Incorrect padding");
17221722
}
17231723
goto error;
17241724
}
@@ -2212,7 +2212,7 @@ binascii_a2b_hex_impl(PyObject *module, Py_buffer *hexstr,
22122212
if (pair_pos) {
22132213
state = get_binascii_state(module);
22142214
if (state) {
2215-
PyErr_SetString(state->Error, "Odd number of hexadecimal digits");
2215+
PyErr_SetString(state->Incomplete, "Odd number of hexadecimal digits");
22162216
}
22172217
goto error;
22182218
}
@@ -2570,7 +2570,7 @@ binascii_exec(PyObject *module)
25702570
return -1;
25712571
}
25722572

2573-
state->Incomplete = PyErr_NewException("binascii.Incomplete", NULL, NULL);
2573+
state->Incomplete = PyErr_NewException("binascii.Incomplete", state->Error, NULL);
25742574
if (PyModule_AddObjectRef(module, "Incomplete", state->Incomplete) < 0) {
25752575
return -1;
25762576
}

0 commit comments

Comments
 (0)