gh-143214: Add the wrapcol parameter in binascii.b2a_base64() and base64.b64encode() (GH-143216)

This commit is contained in:
Serhiy Storchaka 2026-01-14 14:44:53 +02:00 committed by GitHub
parent e370c8db52
commit a471a32f4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 274 additions and 105 deletions

View File

@ -51,7 +51,7 @@ The :rfc:`4648` encodings are suitable for encoding binary data so that it can b
safely sent by email, used as parts of URLs, or included as part of an HTTP
POST request.
.. function:: b64encode(s, altchars=None)
.. function:: b64encode(s, altchars=None, *, wrapcol=0)
Encode the :term:`bytes-like object` *s* using Base64 and return the encoded
:class:`bytes`.
@ -61,9 +61,16 @@ POST request.
This allows an application to e.g. generate URL or filesystem safe Base64
strings. The default is ``None``, for which the standard Base64 alphabet is used.
If *wrapcol* is non-zero, insert a newline (``b'\n'``) character
after at most every *wrapcol* characters.
If *wrapcol* is zero (default), do not insert any newlines.
May assert or raise a :exc:`ValueError` if the length of *altchars* is not 2. Raises a
:exc:`TypeError` if *altchars* is not a :term:`bytes-like object`.
.. versionchanged:: next
Added the *wrapcol* parameter.
.. function:: b64decode(s, altchars=None, validate=False)
@ -214,9 +221,9 @@ Refer to the documentation of the individual functions for more information.
instead of 4 consecutive spaces (ASCII 0x20) as supported by 'btoa'. This
feature is not supported by the "standard" Ascii85 encoding.
*wrapcol* controls whether the output should have newline (``b'\n'``)
characters added to it. If this is non-zero, each output line will be
at most this many characters long, excluding the trailing newline.
If *wrapcol* is non-zero, insert a newline (``b'\n'``) character
after at most every *wrapcol* characters.
If *wrapcol* is zero (default), do not insert any newlines.
*pad* controls whether the input is padded to a multiple of 4
before encoding. Note that the ``btoa`` implementation always pads.

View File

@ -58,7 +58,7 @@ The :mod:`binascii` module defines the following functions:
Valid base64:
* Conforms to :rfc:`3548`.
* Conforms to :rfc:`4648`.
* Contains only characters from the base64 alphabet.
* Contains no excess data after padding (including excess padding, newlines, etc.).
* Does not start with a padding.
@ -67,15 +67,24 @@ The :mod:`binascii` module defines the following functions:
Added the *strict_mode* parameter.
.. function:: b2a_base64(data, *, newline=True)
.. function:: b2a_base64(data, *, wrapcol=0, newline=True)
Convert binary data to a line of ASCII characters in base64 coding. The return
value is the converted line, including a newline char if *newline* is
true. The output of this function conforms to :rfc:`3548`.
Convert binary data to a line(s) of ASCII characters in base64 coding,
as specified in :rfc:`4648`.
If *wrapcol* is non-zero, insert a newline (``b'\n'``) character
after at most every *wrapcol* characters.
If *wrapcol* is zero (default), do not insert any newlines.
If *newline* is true (default), a newline character will be added
at the end of the output.
.. versionchanged:: 3.6
Added the *newline* parameter.
.. versionchanged:: next
Added the *wrapcol* parameter.
.. function:: a2b_qp(data, header=False)

View File

@ -435,12 +435,22 @@ argparse
inline code when color output is enabled.
(Contributed by Savannah Ostrowski in :gh:`142390`.)
base64 & binascii
-----------------
base64
------
* Added the *pad* parameter in :func:`~base64.z85encode`.
(Contributed by Hauke Dämpfling in :gh:`143103`.)
* Added the *wrapcol* parameter in :func:`~base64.b64encode`.
(Contributed by Serhiy Storchaka in :gh:`143214`.)
binascii
--------
* Added the *wrapcol* parameter in :func:`~binascii.b2a_base64`.
(Contributed by Serhiy Storchaka in :gh:`143214`.)
* CPython's underlying base64 implementation now encodes 2x faster and decodes 3x
faster thanks to simple CPU pipelining optimizations.
(Contributed by Gregory P. Smith & Serhiy Storchaka in :gh:`143262`.)
calendar
--------
@ -878,6 +888,13 @@ Optimizations
(Contributed by Chris Eibl, Ken Jin, and Brandt Bucher in :gh:`143068`.
Special thanks to the MSVC team including Hulon Jenkins.)
base64 & binascii
-----------------
* CPython's underlying base64 implementation now encodes 2x faster and decodes 3x
faster thanks to simple CPU pipelining optimizations.
(Contributed by Gregory P. Smith and Serhiy Storchaka in :gh:`143262`.)
csv
---

View File

@ -2142,6 +2142,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(which));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(who));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(withdata));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(wrapcol));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(writable));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(write));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(write_through));

View File

@ -865,6 +865,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(which)
STRUCT_FOR_ID(who)
STRUCT_FOR_ID(withdata)
STRUCT_FOR_ID(wrapcol)
STRUCT_FOR_ID(writable)
STRUCT_FOR_ID(write)
STRUCT_FOR_ID(write_through)

View File

@ -2140,6 +2140,7 @@ extern "C" {
INIT_ID(which), \
INIT_ID(who), \
INIT_ID(withdata), \
INIT_ID(wrapcol), \
INIT_ID(writable), \
INIT_ID(write), \
INIT_ID(write_through), \

View File

@ -3240,6 +3240,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp) {
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(wrapcol);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(writable);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));

View File

@ -45,14 +45,17 @@ def _bytes_from_decode_data(s):
# Base64 encoding/decoding uses binascii
def b64encode(s, altchars=None):
def b64encode(s, altchars=None, *, wrapcol=0):
"""Encode the bytes-like object s using Base64 and return a bytes object.
Optional altchars should be a byte string of length 2 which specifies an
alternative alphabet for the '+' and '/' characters. This allows an
application to e.g. generate url or filesystem safe Base64 strings.
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
every wrapcol characters.
"""
encoded = binascii.b2a_base64(s, newline=False)
encoded = binascii.b2a_base64(s, wrapcol=wrapcol, newline=False)
if altchars is not None:
assert len(altchars) == 2, repr(altchars)
return encoded.translate(bytes.maketrans(b'+/', altchars))
@ -327,9 +330,8 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
instead of 4 consecutive spaces (ASCII 0x20) as supported by 'btoa'. This
feature is not supported by the "standard" Adobe encoding.
wrapcol controls whether the output should have newline (b'\\n') characters
added to it. If this is non-zero, each output line will be at most this
many characters long, excluding the trailing newline.
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
every wrapcol characters.
pad controls whether the input is padded to a multiple of 4 before
encoding. Note that the btoa implementation always pads.
@ -566,11 +568,10 @@ def encodebytes(s):
"""Encode a bytestring into a bytes object containing multiple lines
of base-64 data."""
_input_type_check(s)
pieces = []
for i in range(0, len(s), MAXBINSIZE):
chunk = s[i : i + MAXBINSIZE]
pieces.append(binascii.b2a_base64(chunk))
return b"".join(pieces)
result = binascii.b2a_base64(s, wrapcol=MAXLINESIZE)
if result == b'\n':
return b''
return result
def decodebytes(s):

View File

@ -83,16 +83,15 @@ def body_encode(s, maxlinelen=76, eol=NL):
if not s:
return ""
encvec = []
max_unencoded = maxlinelen * 3 // 4
for i in range(0, len(s), max_unencoded):
# BAW: should encode() inherit b2a_base64()'s dubious behavior in
# adding a newline to the encoded string?
enc = b2a_base64(s[i:i + max_unencoded]).decode("ascii")
if enc.endswith(NL) and eol != NL:
enc = enc[:-1] + eol
encvec.append(enc)
return EMPTYSTRING.join(encvec)
if not eol:
return b2a_base64(s, newline=False).decode("ascii")
# BAW: should encode() inherit b2a_base64()'s dubious behavior in
# adding a newline to the encoded string?
enc = b2a_base64(s, wrapcol=maxlinelen).decode("ascii")
if eol != NL:
enc = enc.replace(NL, eol)
return enc
def decode(string):

View File

@ -129,19 +129,6 @@ def _finalize_set(msg, disposition, filename, cid, params):
msg.set_param(key, value)
# XXX: This is a cleaned-up version of base64mime.body_encode (including a bug
# fix in the calculation of unencoded_bytes_per_line). It would be nice to
# drop both this and quoprimime.body_encode in favor of enhanced binascii
# routines that accepted a max_line_length parameter.
def _encode_base64(data, max_line_length):
encoded_lines = []
unencoded_bytes_per_line = max_line_length // 4 * 3
for i in range(0, len(data), unencoded_bytes_per_line):
thisline = data[i:i+unencoded_bytes_per_line]
encoded_lines.append(binascii.b2a_base64(thisline).decode('ascii'))
return ''.join(encoded_lines)
def _encode_text(string, charset, cte, policy):
# If max_line_length is 0 or None, there is no limit.
maxlen = policy.max_line_length or sys.maxsize
@ -176,7 +163,7 @@ def _encode_text(string, charset, cte, policy):
data = quoprimime.body_encode(normal_body(lines).decode('latin-1'),
maxlen)
elif cte == 'base64':
data = _encode_base64(embedded_body(lines), maxlen)
data = binascii.b2a_base64(embedded_body(lines), wrapcol=maxlen).decode('ascii')
else:
raise ValueError("Unknown content transfer encoding {}".format(cte))
return cte, data
@ -234,7 +221,8 @@ def set_bytes_content(msg, data, maintype, subtype, cte='base64',
params=None, headers=None):
_prepare_set(msg, maintype, subtype, headers)
if cte == 'base64':
data = _encode_base64(data, max_line_length=msg.policy.max_line_length)
data = binascii.b2a_base64(data, wrapcol=msg.policy.max_line_length)
data = data.decode('ascii')
elif cte == 'quoted-printable':
# XXX: quoprimime.body_encode won't encode newline characters in data,
# so we can't use it. This means max_line_length is ignored. Another

View File

@ -122,13 +122,7 @@ _controlCharPat = re.compile(
r"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f]")
def _encode_base64(s, maxlinelength=76):
# copied from base64.encodebytes(), with added maxlinelength argument
maxbinsize = (maxlinelength//4)*3
pieces = []
for i in range(0, len(s), maxbinsize):
chunk = s[i : i + maxbinsize]
pieces.append(binascii.b2a_base64(chunk))
return b''.join(pieces)
return binascii.b2a_base64(s, wrapcol=maxlinelength, newline=False)
def _decode_base64(s):
if isinstance(s, str):
@ -382,11 +376,10 @@ class _PlistWriter(_DumbXMLWriter):
def write_bytes(self, data):
self.begin_element("data")
self._indent_level -= 1
maxlinelength = max(
16,
76 - len((self.indent * self._indent_level).expandtabs()))
for line in _encode_base64(data, maxlinelength).split(b"\n"):
wrapcol = 76 - len((self.indent * self._indent_level).expandtabs())
wrapcol = max(16, wrapcol)
encoded = binascii.b2a_base64(data, wrapcol=wrapcol, newline=False)
for line in encoded.split(b"\n"):
if line:
self.writeln(line)
self._indent_level += 1

View File

@ -1534,11 +1534,8 @@ def DER_cert_to_PEM_cert(der_cert_bytes):
"""Takes a certificate in binary DER format and returns the
PEM version of it as a string."""
f = str(base64.standard_b64encode(der_cert_bytes), 'ASCII', 'strict')
ss = [PEM_HEADER]
ss += [f[i:i+64] for i in range(0, len(f), 64)]
ss.append(PEM_FOOTER + '\n')
return '\n'.join(ss)
f = str(base64.b64encode(der_cert_bytes, wrapcol=64), 'ASCII')
return f'{PEM_HEADER}\n{f}\n{PEM_FOOTER}\n'
def PEM_cert_to_DER_cert(pem_cert_string):
"""Takes a certificate in ASCII PEM format and returns the

View File

@ -2,9 +2,10 @@ import unittest
import base64
import binascii
import string
import sys
import os
from array import array
from test.support import cpython_only
from test.support import cpython_only, check_impl_detail
from test.support import os_helper
from test.support import script_helper
from test.support.import_helper import ensure_lazy_imports
@ -172,6 +173,7 @@ class BaseXYTestCase(unittest.TestCase):
b"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE"
b"RUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NT"
b"Y3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ==")
# Test with arbitrary alternative characters
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=b'*$'), b'01a*b$cd')
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=bytearray(b'*$')),
@ -207,6 +209,31 @@ class BaseXYTestCase(unittest.TestCase):
b'\xd3V\xbeo\xf7\x1d', b'01a-b_cd')
self.check_encode_type_errors(base64.urlsafe_b64encode)
def test_b64encode_wrapcol(self):
eq = self.assertEqual
b = b'www.python.org'
eq(base64.b64encode(b, wrapcol=0), b'd3d3LnB5dGhvbi5vcmc=')
eq(base64.b64encode(b, wrapcol=8), b'd3d3LnB5\ndGhvbi5v\ncmc=')
eq(base64.b64encode(b, wrapcol=11), b'd3d3LnB5\ndGhvbi5v\ncmc=')
eq(base64.b64encode(b, wrapcol=76), b'd3d3LnB5dGhvbi5vcmc=')
eq(base64.b64encode(b, wrapcol=1), b'd3d3\nLnB5\ndGhv\nbi5v\ncmc=')
eq(base64.b64encode(b, wrapcol=sys.maxsize), b'd3d3LnB5dGhvbi5vcmc=')
if check_impl_detail():
eq(base64.b64encode(b, wrapcol=sys.maxsize*2),
b'd3d3LnB5dGhvbi5vcmc=')
with self.assertRaises(OverflowError):
base64.b64encode(b, wrapcol=2**1000)
with self.assertRaises(ValueError):
base64.b64encode(b, wrapcol=-8)
with self.assertRaises(TypeError):
base64.b64encode(b, wrapcol=8.0)
with self.assertRaises(TypeError):
base64.b64encode(b, wrapcol='8')
with self.assertRaises(TypeError):
base64.b64encode(b, wrapcol=None)
eq(base64.b64encode(b'', wrapcol=0), b'')
eq(base64.b64encode(b'', wrapcol=8), b'')
def test_b64decode(self):
eq = self.assertEqual
@ -614,18 +641,46 @@ class BaseXYTestCase(unittest.TestCase):
self.assertRaises(TypeError, base64.a85encode, "")
eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=False),
b'GB\\6`E-\nZP=Df.1\nGEb>')
eq(base64.a85encode(b"\0\0\0\0www.python.org", wrapcol=7, adobe=False),
b'zGB\\6`E\n-ZP=Df.\n1GEb>')
eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=True),
b'<~GB\\6`\nE-ZP=Df\n.1GEb>\n~>')
eq(base64.a85encode(b' '*8, foldspaces=True, adobe=False), b'yy')
eq(base64.a85encode(b' '*7, foldspaces=True, adobe=False), b'y+<Vd')
eq(base64.a85encode(b' '*6, foldspaces=True, adobe=False), b'y+<U')
eq(base64.a85encode(b' '*5, foldspaces=True, adobe=False), b'y+9')
def test_a85encode_wrapcol(self):
eq = self.assertEqual
b = b'www.python.org'
eq(base64.a85encode(b, wrapcol=0), b'GB\\6`E-ZP=Df.1GEb>')
eq(base64.a85encode(b, wrapcol=7), b'GB\\6`E-\nZP=Df.1\nGEb>')
eq(base64.a85encode(b"\0\0\0\0www.python.org", wrapcol=7),
b'zGB\\6`E\n-ZP=Df.\n1GEb>')
eq(base64.a85encode(b, wrapcol=75), b'GB\\6`E-ZP=Df.1GEb>')
eq(base64.a85encode(b, wrapcol=1),
b'G\nB\n\\\n6\n`\nE\n-\nZ\nP\n=\nD\nf\n.\n1\nG\nE\nb\n>')
eq(base64.a85encode(b, wrapcol=7, adobe=True),
b'<~GB\\6`\nE-ZP=Df\n.1GEb>\n~>')
eq(base64.a85encode(b, wrapcol=1),
b'G\nB\n\\\n6\n`\nE\n-\nZ\nP\n=\nD\nf\n.\n1\nG\nE\nb\n>')
eq(base64.a85encode(b, wrapcol=1, adobe=True),
b'<~\nGB\n\\6\n`E\n-Z\nP=\nDf\n.1\nGE\nb>\n~>')
eq(base64.a85encode(b, wrapcol=sys.maxsize), b'GB\\6`E-ZP=Df.1GEb>')
if check_impl_detail():
eq(base64.a85encode(b, wrapcol=2**1000), b'GB\\6`E-ZP=Df.1GEb>')
eq(base64.a85encode(b, wrapcol=-7),
b'G\nB\n\\\n6\n`\nE\n-\nZ\nP\n=\nD\nf\n.\n1\nG\nE\nb\n>')
eq(base64.a85encode(b, wrapcol=-7, adobe=True),
b'<~\nGB\n\\6\n`E\n-Z\nP=\nDf\n.1\nGE\nb>\n~>')
with self.assertRaises(TypeError):
base64.a85encode(b, wrapcol=7.0)
with self.assertRaises(TypeError):
base64.a85encode(b, wrapcol='7')
if check_impl_detail():
eq(base64.a85encode(b, wrapcol=None), b'GB\\6`E-ZP=Df.1GEb>')
eq(base64.a85encode(b'', wrapcol=0), b'')
eq(base64.a85encode(b'', wrapcol=7), b'')
eq(base64.a85encode(b'', wrapcol=1, adobe=True), b'<~\n~>')
eq(base64.a85encode(b'', wrapcol=3, adobe=True), b'<~\n~>')
eq(base64.a85encode(b'', wrapcol=4, adobe=True), b'<~~>')
def test_b85encode(self):
eq = self.assertEqual

View File

@ -4,7 +4,8 @@ import unittest
import binascii
import array
import re
from test.support import bigmemtest, _1G, _4G
import sys
from test.support import bigmemtest, _1G, _4G, check_impl_detail
from test.support.hypothesis_helper import hypothesis
@ -479,6 +480,45 @@ class BinASCIITest(unittest.TestCase):
b'aGVsbG8=\n')
self.assertEqual(binascii.b2a_base64(b, newline=False),
b'aGVsbG8=')
b = self.type2test(b'')
self.assertEqual(binascii.b2a_base64(b), b'\n')
self.assertEqual(binascii.b2a_base64(b, newline=True), b'\n')
self.assertEqual(binascii.b2a_base64(b, newline=False), b'')
def test_b2a_base64_wrapcol(self):
b = self.type2test(b'www.python.org')
self.assertEqual(binascii.b2a_base64(b),
b'd3d3LnB5dGhvbi5vcmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=0),
b'd3d3LnB5dGhvbi5vcmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=8),
b'd3d3LnB5\ndGhvbi5v\ncmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=11),
b'd3d3LnB5\ndGhvbi5v\ncmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=76),
b'd3d3LnB5dGhvbi5vcmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=8, newline=False),
b'd3d3LnB5\ndGhvbi5v\ncmc=')
self.assertEqual(binascii.b2a_base64(b, wrapcol=1),
b'd3d3\nLnB5\ndGhv\nbi5v\ncmc=\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=sys.maxsize),
b'd3d3LnB5dGhvbi5vcmc=\n')
if check_impl_detail():
self.assertEqual(binascii.b2a_base64(b, wrapcol=sys.maxsize*2),
b'd3d3LnB5dGhvbi5vcmc=\n')
with self.assertRaises(OverflowError):
binascii.b2a_base64(b, wrapcol=2**1000)
with self.assertRaises(ValueError):
binascii.b2a_base64(b, wrapcol=-8)
with self.assertRaises(TypeError):
binascii.b2a_base64(b, wrapcol=8.0)
with self.assertRaises(TypeError):
binascii.b2a_base64(b, wrapcol='8')
b = self.type2test(b'')
self.assertEqual(binascii.b2a_base64(b), b'\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=0), b'\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=8), b'\n')
self.assertEqual(binascii.b2a_base64(b, wrapcol=8, newline=False), b'')
@hypothesis.given(
binary=hypothesis.strategies.binary(),

View File

@ -0,0 +1,2 @@
Add the *wrapcol* parameter in :func:`binascii.b2a_base64` and
:func:`base64.b64encode`.

View File

@ -272,6 +272,32 @@ ascii_buffer_converter(PyObject *arg, Py_buffer *buf)
return Py_CLEANUP_SUPPORTED;
}
/* The function inserts '\n' each width characters, moving the data right.
* It assumes that we allocated enough space for all of the newlines in data.
* Returns the size of the data including the newlines.
*/
static Py_ssize_t
wraplines(unsigned char *data, Py_ssize_t size, size_t width)
{
if ((size_t)size <= width) {
return size;
}
unsigned char *src = data + size;
Py_ssize_t newlines = (size - 1) / width;
Py_ssize_t line_len = size - newlines * width;
size += newlines;
unsigned char *dst = data + size;
while ((src -= line_len) != data) {
dst -= line_len;
memmove(dst, src, line_len);
*--dst = '\n';
line_len = width;
}
assert(dst == data + width);
return size;
}
#include "clinic/binascii.c.h"
/*[clinic input]
@ -622,39 +648,44 @@ binascii.b2a_base64
data: Py_buffer
/
*
wrapcol: size_t = 0
newline: bool = True
Base64-code line of data.
[clinic start generated code]*/
static PyObject *
binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, int newline)
/*[clinic end generated code: output=4ad62c8e8485d3b3 input=0e20ff59c5f2e3e1]*/
binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol,
int newline)
/*[clinic end generated code: output=2edc7311a9515eac input=2ee4214e6d489e2e]*/
{
const unsigned char *bin_data;
Py_ssize_t bin_len;
binascii_state *state;
bin_data = data->buf;
bin_len = data->len;
const unsigned char *bin_data = data->buf;
Py_ssize_t bin_len = data->len;
assert(bin_len >= 0);
if ( bin_len > BASE64_MAXBIN ) {
state = get_binascii_state(module);
if (state == NULL) {
return NULL;
}
PyErr_SetString(state->Error, "Too much data for base64 line");
return NULL;
/* Each group of 3 bytes (rounded up) gets encoded as 4 characters,
* not counting newlines.
* Note that 'b' gets encoded as 'Yg==' (1 in, 4 out).
*
* Use unsigned integer arithmetic to avoid signed integer overflow.
*/
size_t out_len = ((size_t)bin_len + 2u) / 3u * 4u;
if (out_len > PY_SSIZE_T_MAX) {
goto toolong;
}
if (wrapcol && out_len) {
/* Each line should encode a whole number of bytes. */
wrapcol = wrapcol < 4 ? 4 : wrapcol / 4 * 4;
out_len += (out_len - 1u) / wrapcol;
if (out_len > PY_SSIZE_T_MAX) {
goto toolong;
}
}
/* We're lazy and allocate too much (fixed up later).
"+2" leaves room for up to two pad characters.
Note that 'b' gets encoded as 'Yg==\n' (1 in, 5 out). */
Py_ssize_t out_len = bin_len*2 + 2;
if (newline) {
out_len++;
if (out_len > PY_SSIZE_T_MAX) {
goto toolong;
}
}
PyBytesWriter *writer = PyBytesWriter_Create(out_len);
if (writer == NULL) {
@ -687,10 +718,22 @@ binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, int newline)
*ascii_data++ = BASE64_PAD;
}
if (wrapcol) {
unsigned char *start = PyBytesWriter_GetData(writer);
ascii_data = start + wraplines(start, ascii_data - start, wrapcol);
}
if (newline)
*ascii_data++ = '\n'; /* Append a courtesy newline */
return PyBytesWriter_FinishWithPointer(writer, ascii_data);
toolong:;
binascii_state *state = get_binascii_state(module);
if (state == NULL) {
return NULL;
}
PyErr_SetString(state->Error, "Too much data for base64");
return NULL;
}

View File

@ -6,6 +6,7 @@ preserve
# include "pycore_gc.h" // PyGC_Head
# include "pycore_runtime.h" // _Py_ID()
#endif
#include "pycore_long.h" // _PyLong_Size_t_Converter()
#include "pycore_modsupport.h" // _PyArg_UnpackKeywords()
PyDoc_STRVAR(binascii_a2b_uu__doc__,
@ -193,7 +194,7 @@ exit:
}
PyDoc_STRVAR(binascii_b2a_base64__doc__,
"b2a_base64($module, data, /, *, newline=True)\n"
"b2a_base64($module, data, /, *, wrapcol=0, newline=True)\n"
"--\n"
"\n"
"Base64-code line of data.");
@ -202,7 +203,8 @@ PyDoc_STRVAR(binascii_b2a_base64__doc__,
{"b2a_base64", _PyCFunction_CAST(binascii_b2a_base64), METH_FASTCALL|METH_KEYWORDS, binascii_b2a_base64__doc__},
static PyObject *
binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, int newline);
binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol,
int newline);
static PyObject *
binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
@ -210,7 +212,7 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
#define NUM_KEYWORDS 1
#define NUM_KEYWORDS 2
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@ -219,7 +221,7 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
.ob_item = { &_Py_ID(newline), },
.ob_item = { &_Py_ID(wrapcol), &_Py_ID(newline), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@ -228,16 +230,17 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
static const char * const _keywords[] = {"", "newline", NULL};
static const char * const _keywords[] = {"", "wrapcol", "newline", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "b2a_base64",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
PyObject *argsbuf[2];
PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1;
Py_buffer data = {NULL, NULL};
size_t wrapcol = 0;
int newline = 1;
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
@ -251,12 +254,20 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P
if (!noptargs) {
goto skip_optional_kwonly;
}
newline = PyObject_IsTrue(args[1]);
if (args[1]) {
if (!_PyLong_Size_t_Converter(args[1], &wrapcol)) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
newline = PyObject_IsTrue(args[2]);
if (newline < 0) {
goto exit;
}
skip_optional_kwonly:
return_value = binascii_b2a_base64_impl(module, &data, newline);
return_value = binascii_b2a_base64_impl(module, &data, wrapcol, newline);
exit:
/* Cleanup for data */
@ -812,4 +823,4 @@ exit:
return return_value;
}
/*[clinic end generated code: output=fba6a71e0d7d092f input=a9049054013a1b77]*/
/*[clinic end generated code: output=644ccdc8e0d56e65 input=a9049054013a1b77]*/