Close #17839: support bytes-like objects in base64 module · pythoncapi/cpython@fdf239a · GitHub
Skip to content

Commit fdf239a

Browse files
committed
Close python#17839: support bytes-like objects in base64 module
This mostly affected the encodebytes and decodebytes function (which are used by base64_codec) Also added a test to ensure all bytes-bytes codecs can handle memoryview input and tests for handling of multidimensional and non-bytes format input in the modern base64 API.
1 parent 73c6ee0 commit fdf239a

6 files changed

Lines changed: 172 additions & 69 deletions

File tree

Doc/library/base64.rst

Lines changed: 4 additions & 0 deletions

Doc/library/codecs.rst

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,36 +1208,41 @@ mappings.
12081208

12091209
.. tabularcolumns:: |l|L|L|
12101210

1211-
+----------------------+---------------------------+------------------------------+
1212-
| Codec | Purpose | Encoder/decoder |
1213-
+======================+===========================+==============================+
1214-
| base64_codec [#b64]_ | Convert operand to MIME | :meth:`base64.b64encode`, |
1215-
| | base64 (the result always | :meth:`base64.b64decode` |
1216-
| | includes a trailing | |
1217-
| | ``'\n'``) | |
1218-
+----------------------+---------------------------+------------------------------+
1219-
| bz2_codec | Compress the operand | :meth:`bz2.compress`, |
1220-
| | using bz2 | :meth:`bz2.decompress` |
1221-
+----------------------+---------------------------+------------------------------+
1222-
| hex_codec | Convert operand to | :meth:`base64.b16encode`, |
1223-
| | hexadecimal | :meth:`base64.b16decode` |
1224-
| | representation, with two | |
1225-
| | digits per byte | |
1226-
+----------------------+---------------------------+------------------------------+
1227-
| quopri_codec | Convert operand to MIME | :meth:`quopri.encodestring`, |
1228-
| | quoted printable | :meth:`quopri.decodestring` |
1229-
+----------------------+---------------------------+------------------------------+
1230-
| uu_codec | Convert the operand using | :meth:`uu.encode`, |
1231-
| | uuencode | :meth:`uu.decode` |
1232-
+----------------------+---------------------------+------------------------------+
1233-
| zlib_codec | Compress the operand | :meth:`zlib.compress`, |
1234-
| | using gzip | :meth:`zlib.decompress` |
1235-
+----------------------+---------------------------+------------------------------+
1236-
1237-
.. [#b64] Rather than accepting any :term:`bytes-like object`,
1238-
``'base64_codec'`` accepts only :class:`bytes` and :class:`bytearray` for
1239-
encoding and only :class:`bytes`, :class:`bytearray`, and ASCII-only
1240-
instances of :class:`str` for decoding
1211+
+----------------------+------------------------------+------------------------------+
1212+
| Codec | Purpose | Encoder / decoder |
1213+
+======================+==============================+==============================+
1214+
| base64_codec [#b64]_ | Convert operand to MIME | :meth:`base64.b64encode` / |
1215+
| | base64 (the result always | :meth:`base64.b64decode` |
1216+
| | includes a trailing | |
1217+
| | ``'\n'``) | |
1218+
| | | |
1219+
| | .. versionchanged:: 3.4 | |
1220+
| | accepts any | |
1221+
| | :term:`bytes-like object` | |
1222+
| | as input for encoding and | |
1223+
| | decoding | |
1224+
+----------------------+------------------------------+------------------------------+
1225+
| bz2_codec | Compress the operand | :meth:`bz2.compress` / |
1226+
| | using bz2 | :meth:`bz2.decompress` |
1227+
+----------------------+------------------------------+------------------------------+
1228+
| hex_codec | Convert operand to | :meth:`base64.b16encode` / |
1229+
| | hexadecimal | :meth:`base64.b16decode` |
1230+
| | representation, with two | |
1231+
| | digits per byte | |
1232+
+----------------------+------------------------------+------------------------------+
1233+
| quopri_codec | Convert operand to MIME | :meth:`quopri.encodestring` /|
1234+
| | quoted printable | :meth:`quopri.decodestring` |
1235+
+----------------------+------------------------------+------------------------------+
1236+
| uu_codec | Convert the operand using | :meth:`uu.encode` / |
1237+
| | uuencode | :meth:`uu.decode` |
1238+
+----------------------+------------------------------+------------------------------+
1239+
| zlib_codec | Compress the operand | :meth:`zlib.compress` / |
1240+
| | using gzip | :meth:`zlib.decompress` |
1241+
+----------------------+------------------------------+------------------------------+
1242+
1243+
.. [#b64] In addition to :term:`bytes-like objects <bytes-like object>`,
1244+
``'base64_codec'`` also accepts ASCII-only instances of :class:`str` for
1245+
decoding
12411246
12421247
12431248
The following codecs provide :class:`str` to :class:`str` mappings.

Lib/base64.py

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ def _bytes_from_decode_data(s):
3535
return s.encode('ascii')
3636
except UnicodeEncodeError:
3737
raise ValueError('string argument should contain only ASCII characters')
38-
elif isinstance(s, bytes_types):
38+
if isinstance(s, bytes_types):
3939
return s
40-
else:
41-
raise TypeError("argument should be bytes or ASCII string, not %s" % s.__class__.__name__)
42-
40+
try:
41+
return memoryview(s).tobytes()
42+
except TypeError:
43+
raise TypeError("argument should be a bytes-like object or ASCII "
44+
"string, not %r" % s.__class__.__name__) from None
4345

4446

4547
# Base64 encoding/decoding uses binascii
@@ -54,14 +56,9 @@ def b64encode(s, altchars=None):
5456
5557
The encoded byte string is returned.
5658
"""
57-
if not isinstance(s, bytes_types):
58-
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
5959
# Strip off the trailing newline
6060
encoded = binascii.b2a_base64(s)[:-1]
6161
if altchars is not None:
62-
if not isinstance(altchars, bytes_types):
63-
raise TypeError("expected bytes, not %s"
64-
% altchars.__class__.__name__)
6562
assert len(altchars) == 2, repr(altchars)
6663
return encoded.translate(bytes.maketrans(b'+/', altchars))
6764
return encoded
@@ -149,7 +146,7 @@ def b32encode(s):
149146
s is the byte string to encode. The encoded byte string is returned.
150147
"""
151148
if not isinstance(s, bytes_types):
152-
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
149+
s = memoryview(s).tobytes()
153150
leftover = len(s) % 5
154151
# Pad the last quantum with zero bits if necessary
155152
if leftover:
@@ -250,8 +247,6 @@ def b16encode(s):
250247
251248
s is the byte string to encode. The encoded byte string is returned.
252249
"""
253-
if not isinstance(s, bytes_types):
254-
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
255250
return binascii.hexlify(s).upper()
256251

257252

@@ -306,12 +301,26 @@ def decode(input, output):
306301
s = binascii.a2b_base64(line)
307302
output.write(s)
308303

304+
def _input_type_check(s):
305+
try:
306+
m = memoryview(s)
307+
except TypeError as err:
308+
msg = "expected bytes-like object, not %s" % s.__class__.__name__
309+
raise TypeError(msg) from err
310+
if m.format not in ('c', 'b', 'B'):
311+
msg = ("expected single byte elements, not %r from %s" %
312+
(m.format, s.__class__.__name__))
313+
raise TypeError(msg)
314+
if m.ndim != 1:
315+
msg = ("expected 1-D data, not %d-D data from %s" %
316+
(m.ndim, s.__class__.__name__))
317+
raise TypeError(msg)
318+
309319

310320
def encodebytes(s):
311321
"""Encode a bytestring into a bytestring containing multiple lines
312322
of base-64 data."""
313-
if not isinstance(s, bytes_types):
314-
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
323+
_input_type_check(s)
315324
pieces = []
316325
for i in range(0, len(s), MAXBINSIZE):
317326
chunk = s[i : i + MAXBINSIZE]
@@ -328,8 +337,7 @@ def encodestring(s):
328337

329338
def decodebytes(s):
330339
"""Decode a bytestring of base-64 data into a bytestring."""
331-
if not isinstance(s, bytes_types):
332-
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
340+
_input_type_check(s)
333341
return binascii.a2b_base64(s)
334342

335343
def decodestring(s):

Lib/test/test_base64.py

Lines changed: 87 additions & 23 deletions

0 commit comments

Comments
 (0)