Skip to content

Commit d8f7bca

Browse files
b84330808claude
andcommitted
gh-71986: Fix the base64 and bz2 codec incremental decoders
The base64_codec incremental decoder decoded each chunk on its own, so input split in the middle of a group of four characters failed with binascii.Error. Keep the incomplete group until the next call. The bz2_codec incremental decoder returned a str when called after the end of the compressed data. Return b'' instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent f7c39d2 commit d8f7bca

4 files changed

Lines changed: 53 additions & 5 deletions

File tree

‎Lib/encodings/base64_codec.py‎

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,25 @@ def encode(self, input, final=False):
2929
assert self.errors == 'strict'
3030
return base64.encodebytes(input)
3131

32-
class IncrementalDecoder(codecs.IncrementalDecoder):
33-
def decode(self, input, final=False):
34-
assert self.errors == 'strict'
35-
return base64.decodebytes(input)
32+
_alphabet = (b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
33+
b'abcdefghijklmnopqrstuvwxyz0123456789+/')
34+
35+
class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
36+
def _buffer_decode(self, input, errors, final):
37+
assert errors == 'strict'
38+
if not final:
39+
# Only decode complete groups of four base64 characters and keep
40+
# the rest for the next call. Other bytes, such as newlines and
41+
# padding, are not counted.
42+
count = len(input) - len(input.translate(None, _alphabet))
43+
remainder = count % 4
44+
end = len(input)
45+
while remainder:
46+
end -= 1
47+
if input[end] in _alphabet:
48+
remainder -= 1
49+
input = input[:end]
50+
return (base64.decodebytes(input), len(input))
3651

3752
class StreamWriter(Codec, codecs.StreamWriter):
3853
charbuffertype = bytes

‎Lib/encodings/bz2_codec.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def decode(self, input, final=False):
5252
try:
5353
return self.decompressobj.decompress(input)
5454
except EOFError:
55-
return ''
55+
return b''
5656

5757
def reset(self):
5858
self.decompressobj = bz2.BZ2Decompressor()

‎Lib/test/test_codecs.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3251,6 +3251,34 @@ def test_uu_invalid(self):
32513251
# Missing "begin" line
32523252
self.assertRaises(ValueError, codecs.decode, b"", "uu-codec")
32533253

3254+
def test_base64_incremental_decode(self):
3255+
# gh-71986: Input split in the middle of a group of four characters
3256+
data = bytes(range(256))
3257+
encoded = codecs.encode(data, "base64_codec")
3258+
for size in range(1, 6):
3259+
with self.subTest(size=size):
3260+
chunks = [encoded[i:i+size]
3261+
for i in range(0, len(encoded), size)]
3262+
decoded = b"".join(codecs.iterdecode(chunks, "base64_codec"))
3263+
self.assertEqual(decoded, data)
3264+
3265+
decoded = codecs.iterdecode([b"AA", b"AA\r\n", b"A", b"A=="],
3266+
"base64_codec")
3267+
self.assertEqual(b"".join(decoded), b"\x00\x00\x00\x00")
3268+
3269+
decoder = codecs.getincrementaldecoder("base64_codec")()
3270+
self.assertEqual(decoder.decode(b"AAA"), b"")
3271+
self.assertRaises(ValueError, decoder.decode, b"", final=True)
3272+
3273+
@unittest.skipUnless("bz2_codec" in bytes_transform_encodings,
3274+
"Requires bz2 support")
3275+
def test_bz2_incremental_decode_after_end(self):
3276+
# gh-71986: The decoder returned str after the end of the stream
3277+
decoder = codecs.getincrementaldecoder("bz2_codec")()
3278+
self.assertEqual(decoder.decode(codecs.encode(b"data", "bz2_codec")),
3279+
b"data")
3280+
self.assertEqual(decoder.decode(b"", final=True), b"")
3281+
32543282

32553283
# The codec system tries to add notes to exceptions in order to ensure
32563284
# the error mentions the operation being performed and the codec involved.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix the incremental decoders of the ``base64_codec`` and ``bz2_codec``
2+
codecs. The ``base64_codec`` decoder now accepts input split in the middle
3+
of a group of four characters, and the ``bz2_codec`` decoder now returns
4+
:class:`bytes` instead of :class:`str` when called after the end of the
5+
compressed data.

0 commit comments

Comments
 (0)