diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst index 8a241e51ebfee6..01ca99a0c3bb14 100644 --- a/Doc/library/binascii.rst +++ b/Doc/library/binascii.rst @@ -48,12 +48,15 @@ The :mod:`!binascii` module defines the following functions: Added the *backtick* parameter. -.. function:: a2b_base64(string, /, *, strict_mode=False) - a2b_base64(string, /, *, strict_mode=True, ignorechars) +.. function:: a2b_base64(string, /, *, alphabet=BASE64_ALPHABET, strict_mode=False) + a2b_base64(string, /, *, ignorechars, alphabet=BASE64_ALPHABET, strict_mode=True) Convert a block of base64 data back to binary and return the binary data. More than one line may be passed at a time. + Optional *alphabet* must be a :class:`bytes` object of length 64 which + specifies an alternative alphabet. + If *ignorechars* is specified, it should be a :term:`bytes-like object` containing characters to ignore from the input when *strict_mode* is true. If *ignorechars* contains the pad character ``'='``, the pad characters @@ -76,10 +79,10 @@ The :mod:`!binascii` module defines the following functions: Added the *strict_mode* parameter. .. versionchanged:: 3.15 - Added the *ignorechars* parameter. + Added the *alphabet* and *ignorechars* parameters. -.. function:: b2a_base64(data, *, wrapcol=0, newline=True) +.. function:: b2a_base64(data, *, alphabet=BASE64_ALPHABET, wrapcol=0, newline=True) Convert binary data to a line(s) of ASCII characters in base64 coding, as specified in :rfc:`4648`. @@ -95,7 +98,7 @@ The :mod:`!binascii` module defines the following functions: Added the *newline* parameter. .. versionchanged:: 3.15 - Added the *wrapcol* parameter. + Added the *alphabet* and *wrapcol* parameters. .. function:: a2b_ascii85(string, /, *, foldspaces=False, adobe=False, ignorechars=b"") @@ -148,7 +151,7 @@ The :mod:`!binascii` module defines the following functions: .. versionadded:: 3.15 -.. function:: a2b_base85(string, /) +.. function:: a2b_base85(string, /, *, alphabet=BASE85_ALPHABET) Convert Base85 data back to binary and return the binary data. More than one line may be passed at a time. @@ -158,49 +161,25 @@ The :mod:`!binascii` module defines the following functions: characters). Each group encodes 32 bits of binary data in the range from ``0`` to ``2 ** 32 - 1``, inclusive. + Optional *alphabet* must be a :class:`bytes` object of length 85 which + specifies an alternative alphabet. + Invalid Base85 data will raise :exc:`binascii.Error`. .. versionadded:: 3.15 -.. function:: b2a_base85(data, /, *, pad=False) +.. function:: b2a_base85(data, /, *, alphabet=BASE85_ALPHABET, pad=False) Convert binary data to a line of ASCII characters in Base85 coding. The return value is the converted line. - If *pad* is true, the input is padded with ``b'\0'`` so its length is a - multiple of 4 bytes before encoding. - - .. versionadded:: 3.15 - - -.. function:: a2b_z85(string, /) - - Convert Z85 data back to binary and return the binary data. - More than one line may be passed at a time. - - Valid Z85 data contains characters from the Z85 alphabet in groups - of five (except for the final group, which may have from two to five - characters). Each group encodes 32 bits of binary data in the range from - ``0`` to ``2 ** 32 - 1``, inclusive. - - See `Z85 specification `_ for more information. - - Invalid Z85 data will raise :exc:`binascii.Error`. - - .. versionadded:: 3.15 - - -.. function:: b2a_z85(data, /, *, pad=False) - - Convert binary data to a line of ASCII characters in Z85 coding. - The return value is the converted line. + Optional *alphabet* must be a :term:`bytes-like object` of length 85 which + specifies an alternative alphabet. If *pad* is true, the input is padded with ``b'\0'`` so its length is a multiple of 4 bytes before encoding. - See `Z85 specification `_ for more information. - .. versionadded:: 3.15 @@ -300,6 +279,67 @@ The :mod:`!binascii` module defines the following functions: but may be handled by reading a little more data and trying again. +.. data:: BASE64_ALPHABET + + The Base 64 alphabet according to :rfc:`4648`. + + .. versionadded:: next + +.. data:: URLSAFE_BASE64_ALPHABET + + The "URL and filename safe" Base 64 alphabet according to :rfc:`4648`. + + .. versionadded:: next + +.. data:: CRYPT_ALPHABET + + The Base 64 alphabet used in the :manpage:`crypt(3)` routine and in the GEDCOM format. + + .. versionadded:: next + +.. data:: BCRYPT_ALPHABET + + The Base 64 alphabet used in the ``bcrypt`` hashing function. + + .. versionadded:: next + +.. data:: UU_ALPHABET + + The uuencoding alphabet. + + .. versionadded:: next + +.. data:: XX_ALPHABET + + The xxencoding alphabet. + + .. versionadded:: next + +.. data:: BINHEX_ALPHABET + + The Base 64 alphabet used in BinHex 4 (HQX) within the classic Mac OS. + + .. versionadded:: next + +.. data:: BASE85_ALPHABET + + The Base85 alphabet. + + .. versionadded:: next + +.. data:: ASCII85_ALPHABET + + The Ascii85 alphabet. + + .. versionadded:: next + +.. data:: Z85_ALPHABET + + The `Z85 `_ alphabet. + + .. versionadded:: next + + .. seealso:: Module :mod:`base64` diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 459846e55ccf70..7c71c3e48efb38 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -644,13 +644,16 @@ binascii - :func:`~binascii.b2a_ascii85` and :func:`~binascii.a2b_ascii85` - :func:`~binascii.b2a_base85` and :func:`~binascii.a2b_base85` - - :func:`~binascii.b2a_z85` and :func:`~binascii.a2b_z85` (Contributed by James Seo and Serhiy Storchaka in :gh:`101178`.) * Added the *wrapcol* parameter in :func:`~binascii.b2a_base64`. (Contributed by Serhiy Storchaka in :gh:`143214`.) +* Added the *alphabet* parameter in :func:`~binascii.b2a_base64` and + :func:`~binascii.a2b_base64`. + (Contributed by Serhiy Storchaka in :gh:`145980`.) + * Added the *ignorechars* parameter in :func:`~binascii.a2b_base64`. (Contributed by Serhiy Storchaka in :gh:`144001`.) diff --git a/Lib/base64.py b/Lib/base64.py index 36688ce43917ce..876525b7992596 100644 --- a/Lib/base64.py +++ b/Lib/base64.py @@ -56,11 +56,13 @@ def b64encode(s, altchars=None, *, wrapcol=0): If wrapcol is non-zero, insert a newline (b'\\n') character after at most every wrapcol characters. """ - 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)) - return encoded + if len(altchars) != 2: + raise ValueError(f'invalid altchars: {altchars!r}') + alphabet = binascii.BASE64_ALPHABET[:-2] + altchars + return binascii.b2a_base64(s, wrapcol=wrapcol, newline=False, + alphabet=alphabet) + return binascii.b2a_base64(s, wrapcol=wrapcol, newline=False) def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, *, ignorechars=_NOT_SPECIFIED): @@ -100,9 +102,11 @@ def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, *, ignorechars=_NOT_SPE break s = s.translate(bytes.maketrans(altchars, b'+/')) else: - trans = bytes.maketrans(b'+/' + altchars, altchars + b'+/') - s = s.translate(trans) - ignorechars = ignorechars.translate(trans) + alphabet = binascii.BASE64_ALPHABET[:-2] + altchars + return binascii.a2b_base64(s, strict_mode=validate, + alphabet=alphabet, + ignorechars=ignorechars) + if ignorechars is _NOT_SPECIFIED: ignorechars = b'' result = binascii.a2b_base64(s, strict_mode=validate, @@ -140,7 +144,6 @@ def standard_b64decode(s): return b64decode(s) -_urlsafe_encode_translation = bytes.maketrans(b'+/', b'-_') _urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/') def urlsafe_b64encode(s): @@ -150,7 +153,8 @@ def urlsafe_b64encode(s): bytes object. The alphabet uses '-' instead of '+' and '_' instead of '/'. """ - return b64encode(s).translate(_urlsafe_encode_translation) + return binascii.b2a_base64(s, newline=False, + alphabet=binascii.URLSAFE_BASE64_ALPHABET) def urlsafe_b64decode(s): """Decode bytes using the URL- and filesystem-safe Base64 alphabet. @@ -393,14 +397,14 @@ def b85decode(b): def z85encode(s, pad=False): """Encode bytes-like object b in z85 format and return a bytes object.""" - return binascii.b2a_z85(s, pad=pad) + return binascii.b2a_base85(s, pad=pad, alphabet=binascii.Z85_ALPHABET) def z85decode(s): """Decode the z85-encoded bytes-like object or ASCII string b The result is returned as a bytes object. """ - return binascii.a2b_z85(s) + return binascii.a2b_base85(s, alphabet=binascii.Z85_ALPHABET) # Legacy interface. This code could be cleaned up since I don't believe # binascii has any line length limitations. It just doesn't seem worth it diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py index 9f0d15fe538810..779ebcc165a88c 100644 --- a/Lib/test/test_binascii.py +++ b/Lib/test/test_binascii.py @@ -10,10 +10,10 @@ # Note: "*_hex" functions are aliases for "(un)hexlify" -b2a_functions = ['b2a_ascii85', 'b2a_base64', 'b2a_base85', 'b2a_z85', +b2a_functions = ['b2a_ascii85', 'b2a_base64', 'b2a_base85', 'b2a_hex', 'b2a_qp', 'b2a_uu', 'hexlify'] -a2b_functions = ['a2b_ascii85', 'a2b_base64', 'a2b_base85', 'a2b_z85', +a2b_functions = ['a2b_ascii85', 'a2b_base64', 'a2b_base85', 'a2b_hex', 'a2b_qp', 'a2b_uu', 'unhexlify'] all_functions = a2b_functions + b2a_functions + ['crc32', 'crc_hqx'] @@ -46,6 +46,51 @@ def test_exceptions(self): self.assertIsSubclass(binascii.Error, Exception) self.assertIsSubclass(binascii.Incomplete, Exception) + def test_constants(self): + self.assertEqual(binascii.BASE64_ALPHABET, + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz' + b'0123456789+/') + self.assertEqual(binascii.URLSAFE_BASE64_ALPHABET, + binascii.BASE64_ALPHABET[:-2] + b'-_') + self.assertEqual(binascii.CRYPT_ALPHABET, + b'./0123456789' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz') + self.assertEqual(binascii.BCRYPT_ALPHABET, + b'./' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz' + b'0123456789') + self.assertEqual(binascii.UU_ALPHABET, bytes(range(32, 32+64))) + self.assertEqual(binascii.XX_ALPHABET, + b'+-0123456789' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz') + self.assertEqual(binascii.BINHEX_ALPHABET, + b'!"#$%&\'()*+,-012345689' + b'@ABCDEFGHIJKLMNPQRSTUVXYZ[' + b'`abcdefhijklmpqr') + + self.assertEqual(binascii.BASE85_ALPHABET, + b'0123456789' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'abcdefghijklmnopqrstuvwxyz' + b'!#$%&()*+-;<=>?@^_`{|}~') + self.assertEqual(binascii.ASCII85_ALPHABET, bytes(range(33, 33+85))) + self.assertEqual(binascii.Z85_ALPHABET, + b'0123456789' + b'abcdefghijklmnopqrstuvwxyz' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + b'.-:+=^!/*?&<>()[]{}@%$#') + + for name in ('BASE85_ALPHABET', 'ASCII85_ALPHABET', + 'Z85_ALPHABET'): + value = getattr(binascii, name) + self.assertIsInstance(value, bytes) + self.assertEqual(len(value), 85) + self.assertEqual(len(set(value)), 85) + def test_functions(self): # Check presence of all functions for name in all_functions: @@ -302,6 +347,33 @@ def assertInvalidLength(data, strict_mode=True): assertInvalidLength(b'A\tB\nC ??DE', # only 5 valid characters strict_mode=False) + def test_base64_alphabet(self): + alphabet = (b'!"#$%&\'()*+,-012345689@' + b'ABCDEFGHIJKLMNPQRSTUVXYZ[`abcdefhijklmpqr') + data = self.type2test(self.rawdata) + encoded = binascii.b2a_base64(data, alphabet=alphabet) + trans = bytes.maketrans(binascii.BASE64_ALPHABET, alphabet) + expected = binascii.b2a_base64(data).translate(trans) + self.assertEqual(encoded, expected) + self.assertEqual(binascii.a2b_base64(encoded, alphabet=alphabet), self.rawdata) + self.assertEqual(binascii.b2a_base64(data, alphabet=self.type2test(alphabet)), expected) + + data = self.type2test(b'') + self.assertEqual(binascii.b2a_base64(data, alphabet=alphabet), b'\n') + self.assertEqual(binascii.a2b_base64(data, alphabet=alphabet), b'') + + for func in binascii.b2a_base64, binascii.a2b_base64: + with self.assertRaises(TypeError): + func(data, alphabet=None) + with self.assertRaises(TypeError): + func(data, alphabet=alphabet.decode()) + with self.assertRaises(ValueError): + func(data, alphabet=alphabet[:-1]) + with self.assertRaises(ValueError): + func(data, alphabet=alphabet+b'?') + with self.assertRaises(TypeError): + binascii.a2b_base64(data, alphabet=bytearray(alphabet)) + def test_ascii85_valid(self): # Test Ascii85 with valid data ASCII85_PREFIX = b"<~" @@ -587,73 +659,32 @@ def test_base85_pad(self): b_pad_expected = b + b"\0" * padding self.assertEqual(b_pad, b_pad_expected) - def test_z85_valid(self): - # Test Z85 with valid data - lines, i = [], 0 - for k in range(1, len(self.rawdata) + 1): - b = self.type2test(self.rawdata[i:i + k]) - a = binascii.b2a_z85(b) - lines.append(a) - i += k - if i >= len(self.rawdata): - break - res = bytes() - for line in lines: - a = self.type2test(line) - b = binascii.a2b_z85(a) - res += b - self.assertEqual(res, self.rawdata) - - # Test decoding inputs with different length - self.assertEqual(binascii.a2b_z85(self.type2test(b'')), b'') - self.assertEqual(binascii.a2b_z85(self.type2test(b'a')), b'') - self.assertEqual(binascii.a2b_z85(self.type2test(b'ab')), b'\x1f') - self.assertEqual(binascii.a2b_z85(self.type2test(b'abc')), - b'\x1f\x85') - self.assertEqual(binascii.a2b_z85(self.type2test(b'abcd')), - b'\x1f\x85\x9a') - self.assertEqual(binascii.a2b_z85(self.type2test(b'abcde')), - b'\x1f\x85\x9a$') - self.assertEqual(binascii.a2b_z85(self.type2test(b'abcdef')), - b'\x1f\x85\x9a$') - self.assertEqual(binascii.a2b_z85(self.type2test(b'abcdefg')), - b'\x1f\x85\x9a$/') - - def test_z85_errors(self): - def _assertRegexTemplate(assert_regex, data, **kwargs): - with self.assertRaisesRegex(binascii.Error, assert_regex): - binascii.a2b_z85(self.type2test(data), **kwargs) - - def assertNonZ85Data(data): - _assertRegexTemplate(r"(?i)bad z85 character", data) - - def assertOverflow(data): - _assertRegexTemplate(r"(?i)z85 overflow", data) - - assertNonZ85Data(b"\xda") - assertNonZ85Data(b"00\0\0") - assertNonZ85Data(b"z !/") - assertNonZ85Data(b"By/JnB0hYQ\n") - - # Test Z85 with out-of-range encoded value - assertOverflow(b"%") - assertOverflow(b"%n") - assertOverflow(b"%nS") - assertOverflow(b"%nSc") - assertOverflow(b"%nSc1") - assertOverflow(b"%nSc0$") - assertOverflow(b"%nSc0%nSc0%nSD0") - - def test_z85_pad(self): - # Test Z85 with encode padding - rawdata = b"n1n3Tee\n ch@rAc\te\r$" - for i in range(1, len(rawdata) + 1): - padding = -i % 4 - b = rawdata[:i] - a_pad = binascii.b2a_z85(self.type2test(b), pad=True) - b_pad = binascii.a2b_z85(self.type2test(a_pad)) - b_pad_expected = b + b"\0" * padding - self.assertEqual(b_pad, b_pad_expected) + def test_base85_alphabet(self): + alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz' + b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#') + data = self.type2test(self.rawdata) + encoded = binascii.b2a_base85(data, alphabet=alphabet) + trans = bytes.maketrans(binascii.BASE85_ALPHABET, alphabet) + expected = binascii.b2a_base85(data).translate(trans) + self.assertEqual(encoded, expected) + self.assertEqual(binascii.a2b_base85(encoded, alphabet=alphabet), self.rawdata) + self.assertEqual(binascii.b2a_base85(data, alphabet=self.type2test(alphabet)), expected) + + data = self.type2test(b'') + self.assertEqual(binascii.b2a_base85(data, alphabet=alphabet), b'') + self.assertEqual(binascii.a2b_base85(data, alphabet=alphabet), b'') + + for func in binascii.b2a_base85, binascii.a2b_base85: + with self.assertRaises(TypeError): + func(data, alphabet=None) + with self.assertRaises(TypeError): + func(data, alphabet=alphabet.decode()) + with self.assertRaises(ValueError): + func(data, alphabet=alphabet[:-1]) + with self.assertRaises(ValueError): + func(data, alphabet=alphabet+b'?') + with self.assertRaises(TypeError): + binascii.a2b_base64(data, alphabet=bytearray(alphabet)) def test_uu(self): MAX_UU = 45 diff --git a/Misc/NEWS.d/next/Library/2026-03-15-16-38-48.gh-issue-145980.mRze5H.rst b/Misc/NEWS.d/next/Library/2026-03-15-16-38-48.gh-issue-145980.mRze5H.rst new file mode 100644 index 00000000000000..217d767138ddb8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-15-16-38-48.gh-issue-145980.mRze5H.rst @@ -0,0 +1,5 @@ +Added the *alphabet* parameter in :func:`~binascii.b2a_base64`, +:func:`~binascii.a2b_base64`, :func:`~binascii.b2a_base85` and +:func:`~binascii.a2b_base85` and a number of ``*_ALPHABET`` constants in the +:mod:`binascii` module. Removed :func:`~binascii.b2a_z85` and +:func:`~binascii.a2b_z85`. diff --git a/Modules/binascii.c b/Modules/binascii.c index c076b12fb149b2..f35d22e557372d 100644 --- a/Modules/binascii.c +++ b/Modules/binascii.c @@ -67,6 +67,7 @@ typedef struct binascii_state { PyObject *Error; PyObject *Incomplete; + PyObject *reverse_table_cache; } binascii_state; static inline binascii_state * @@ -228,26 +229,6 @@ static const unsigned char table_a2b_base85_a85[] Py_ALIGNED(64) = { -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, }; -static const unsigned char table_a2b_base85_z85[] Py_ALIGNED(64) = { - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,68,-1,84, 83,82,72,-1, 75,76,70,65, -1,63,62,69, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,64,-1, 73,66,74,71, - 81,36,37,38, 39,40,41,42, 43,44,45,46, 47,48,49,50, - 51,52,53,54, 55,56,57,58, 59,60,61,77, -1,78,67,-1, - -1,10,11,12, 13,14,15,16, 17,18,19,20, 21,22,23,24, - 25,26,27,28, 29,30,31,32, 33,34,35,79, -1,80,-1,-1, - - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, - -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -}; - static const unsigned char table_b2a_base85[] Py_ALIGNED(64) = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; @@ -256,9 +237,6 @@ static const unsigned char table_b2a_base85_a85[] Py_ALIGNED(64) = "!\"#$%&\'()*+,-./0123456789:;<=>?@" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstu"; -static const unsigned char table_b2a_base85_z85[] Py_ALIGNED(64) = - "0123456789abcdefghijklmnopqrstuvwxyz" \ - "ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/\x2a?&<>()[]{}@%$#"; /* clinic doesn't like '/' followed by '*' */ #define BASE85_A85_PREFIX '<' #define BASE85_A85_AFFIX '~' @@ -547,6 +525,52 @@ binascii_b2a_uu_impl(PyObject *module, Py_buffer *data, int backtick) return PyBytesWriter_FinishWithPointer(writer, ascii_data); } +static PyObject * +get_reverse_table(binascii_state *state, PyObject *alphabet, int size, int padchar) +{ + PyObject *reverse_table; + if (state == NULL) { + return NULL; + } + if (PyBytes_GET_SIZE(alphabet) != size) { + PyErr_Format(PyExc_ValueError, "alphabet must have length %d", size); + return NULL; + } + if (PyDict_GetItemRef(state->reverse_table_cache, alphabet, &reverse_table) < 0) { + return NULL; + } + if (reverse_table == NULL) { + unsigned char out[256]; + memset(out, (unsigned char)-1, 256); + const unsigned char *in = (const unsigned char *)PyBytes_AS_STRING(alphabet); + for (int i = 0; i < size; i++) { + out[in[i]] = i; + } + if (padchar >= 0) { + assert(padchar < 256); + out[padchar] = size; + } + reverse_table = PyBytes_FromStringAndSize((char *)out, 256); + if (reverse_table == NULL) { + return NULL; + } + if (PyDict_SetItem(state->reverse_table_cache, alphabet, reverse_table) < 0) { + Py_DECREF(reverse_table); + return NULL; + } + } + else { + if (!PyBytes_Check(reverse_table) + || PyBytes_GET_SIZE(reverse_table) != 256) + { + PyErr_SetString(PyExc_RuntimeError, "Broken binascii cache"); + Py_DECREF(reverse_table); + return NULL; + } + } + return reverse_table; +} + typedef unsigned char ignorecache_t[32]; static int @@ -576,6 +600,7 @@ binascii.a2b_base64 When set to true, bytes that are not part of the base64 standard are not allowed. The same applies to excess data after padding (= / ==). Set to True by default if ignorechars is specified, False otherwise. + alphabet: PyBytesObject(py_default="BASE64_ALPHABET") = NULL ignorechars: Py_buffer(py_default="") = None A byte string containing characters to ignore from the input when strict_mode is true. @@ -585,14 +610,16 @@ Decode a line of base64 data. static PyObject * binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, - Py_buffer *ignorechars) -/*[clinic end generated code: output=eab37aea4cfa6daa input=3be4937d72943835]*/ + PyBytesObject *alphabet, Py_buffer *ignorechars) +/*[clinic end generated code: output=72f15fcc0681d666 input=051bbf7cf17b6ba4]*/ { assert(data->len >= 0); const unsigned char *ascii_data = data->buf; size_t ascii_len = data->len; binascii_state *state = NULL; + PyObject *table_obj = NULL; + const unsigned char *table_a2b = table_a2b_base64; if (strict_mode == -1) { strict_mode = (ignorechars->buf != NULL); @@ -605,10 +632,20 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, memset(ignorecache, 0, sizeof(ignorecache)); } + if (alphabet != NULL) { + state = get_binascii_state(module); + table_obj = get_reverse_table(state, (PyObject *)alphabet, 64, BASE64_PAD); + if (table_obj == NULL) { + return NULL; + } + table_a2b = (const unsigned char *)PyBytes_AS_STRING(table_obj); + } + /* Allocate the buffer */ Py_ssize_t bin_len = ((ascii_len+3)/4)*3; /* Upper bound, corrected later */ PyBytesWriter *writer = PyBytesWriter_Create(bin_len); if (writer == NULL) { + Py_XDECREF(table_obj); return NULL; } unsigned char *bin_data = PyBytesWriter_GetData(writer); @@ -620,7 +657,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, */ if (ascii_len >= 4) { Py_ssize_t fast_chars = base64_decode_fast(ascii_data, (Py_ssize_t)ascii_len, - bin_data, table_a2b_base64); + bin_data, table_a2b); if (fast_chars > 0) { ascii_data += fast_chars; ascii_len -= fast_chars; @@ -672,7 +709,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, } } - unsigned char v = table_a2b_base64[this_ch]; + unsigned char v = table_a2b[this_ch]; if (v >= 64) { if (strict_mode && !ignorechar(this_ch, ignorechars, ignorecache)) { state = get_binascii_state(module); @@ -749,9 +786,11 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, } done: + Py_XDECREF(table_obj); return PyBytesWriter_FinishWithPointer(writer, bin_data); error_end: + Py_XDECREF(table_obj); PyBytesWriter_Discard(writer); return NULL; } @@ -765,19 +804,28 @@ binascii.b2a_base64 * wrapcol: size_t = 0 newline: bool = True + alphabet: Py_buffer(py_default="BASE64_ALPHABET") = None Base64-code line of data. [clinic start generated code]*/ static PyObject * binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol, - int newline) -/*[clinic end generated code: output=2edc7311a9515eac input=2ee4214e6d489e2e]*/ + int newline, Py_buffer *alphabet) +/*[clinic end generated code: output=9d9657e5fbe28c64 input=681cc9d3af74fc39]*/ { + const unsigned char *table_b2a = table_b2a_base64; const unsigned char *bin_data = data->buf; Py_ssize_t bin_len = data->len; assert(bin_len >= 0); + if (alphabet->buf != NULL) { + if (alphabet->len != 64) { + PyErr_SetString(PyExc_ValueError, "alphabet must have length 64"); + return NULL; + } + table_b2a = alphabet->buf; + } /* 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). @@ -809,7 +857,7 @@ binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol, /* Use the optimized fast path for complete 3-byte groups */ Py_ssize_t fast_bytes = base64_encode_fast(bin_data, bin_len, ascii_data, - table_b2a_base64); + table_b2a); bin_data += fast_bytes; ascii_data += (fast_bytes / 3) * 4; bin_len -= fast_bytes; @@ -818,17 +866,17 @@ binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol, if (bin_len == 1) { /* 1 byte remaining: produces 2 base64 chars + 2 padding */ unsigned int val = bin_data[0]; - *ascii_data++ = table_b2a_base64[(val >> 2) & 0x3f]; - *ascii_data++ = table_b2a_base64[(val << 4) & 0x3f]; + *ascii_data++ = table_b2a[(val >> 2) & 0x3f]; + *ascii_data++ = table_b2a[(val << 4) & 0x3f]; *ascii_data++ = BASE64_PAD; *ascii_data++ = BASE64_PAD; } else if (bin_len == 2) { /* 2 bytes remaining: produces 3 base64 chars + 1 padding */ unsigned int val = ((unsigned int)bin_data[0] << 8) | bin_data[1]; - *ascii_data++ = table_b2a_base64[(val >> 10) & 0x3f]; - *ascii_data++ = table_b2a_base64[(val >> 4) & 0x3f]; - *ascii_data++ = table_b2a_base64[(val << 2) & 0x3f]; + *ascii_data++ = table_b2a[(val >> 10) & 0x3f]; + *ascii_data++ = table_b2a[(val >> 4) & 0x3f]; + *ascii_data++ = table_b2a[(val << 2) & 0x3f]; *ascii_data++ = BASE64_PAD; } @@ -1125,13 +1173,36 @@ binascii_b2a_ascii85_impl(PyObject *module, Py_buffer *data, int foldspaces, return PyBytesWriter_FinishWithPointer(writer, ascii_data); } +/*[clinic input] +binascii.a2b_base85 + + data: ascii_buffer + / + * + alphabet: PyBytesObject(py_default="BASE85_ALPHABET") = NULL + +Decode a line of Base85 data. +[clinic start generated code]*/ + static PyObject * -base85_decode_impl(PyObject *module, Py_buffer *data, - const unsigned char table_a2b[], const char *name) +binascii_a2b_base85_impl(PyObject *module, Py_buffer *data, + PyBytesObject *alphabet) +/*[clinic end generated code: output=3e114af53812e8ff input=81779cd049d44a55]*/ { const unsigned char *ascii_data = data->buf; Py_ssize_t ascii_len = data->len; binascii_state *state = NULL; + PyObject *table_obj = NULL; + const unsigned char *table_a2b = table_a2b_base85; + + if (alphabet != NULL) { + state = get_binascii_state(module); + table_obj = get_reverse_table(state, (PyObject *)alphabet, 85, -1); + if (table_obj == NULL) { + return NULL; + } + table_a2b = (const unsigned char *)PyBytes_AS_STRING(table_obj); + } assert(ascii_len >= 0); @@ -1139,6 +1210,7 @@ base85_decode_impl(PyObject *module, Py_buffer *data, size_t bin_len = ((size_t)ascii_len + 4) / 5 * 4; PyBytesWriter *writer = PyBytesWriter_Create(bin_len); if (writer == NULL) { + Py_XDECREF(table_obj); return NULL; } unsigned char *bin_data = PyBytesWriter_GetData(writer); @@ -1164,8 +1236,8 @@ base85_decode_impl(PyObject *module, Py_buffer *data, state = get_binascii_state(module); if (state != NULL) { PyErr_Format(state->Error, - "%s overflow in hunk starting at byte %d", - name, (data->len - ascii_len) / 5 * 5); + "Base85 overflow in hunk starting at byte %d", + (data->len - ascii_len) / 5 * 5); } goto error; } @@ -1175,8 +1247,8 @@ base85_decode_impl(PyObject *module, Py_buffer *data, else { state = get_binascii_state(module); if (state != NULL) { - PyErr_Format(state->Error, "bad %s character at position %d", - name, data->len - ascii_len); + PyErr_Format(state->Error, "bad Base85 character at position %d", + data->len - ascii_len); } goto error; } @@ -1196,19 +1268,44 @@ base85_decode_impl(PyObject *module, Py_buffer *data, leftchar = 0; } + Py_XDECREF(table_obj); return PyBytesWriter_FinishWithPointer(writer, bin_data); error: PyBytesWriter_Discard(writer); + Py_XDECREF(table_obj); return NULL; } +/*[clinic input] +binascii.b2a_base85 + + data: Py_buffer + / + * + pad: bool = False + Pad input to a multiple of 4 before encoding. + alphabet: Py_buffer(py_default="BASE85_ALPHABET") = None + +Base85-code line of data. +[clinic start generated code]*/ + static PyObject * -base85_encode_impl(PyObject *module, Py_buffer *data, int pad, - const unsigned char table_b2a[], const char *name) +binascii_b2a_base85_impl(PyObject *module, Py_buffer *data, int pad, + Py_buffer *alphabet) +/*[clinic end generated code: output=a59f4f2ff6f0e69f input=cde4ebe8abfaa982]*/ { const unsigned char *bin_data = data->buf; Py_ssize_t bin_len = data->len; + const unsigned char *table_b2a = table_b2a_base85; + + if (alphabet->buf != NULL) { + if (alphabet->len != 85) { + PyErr_SetString(PyExc_ValueError, "alphabet must have length 85"); + return NULL; + } + table_b2a = alphabet->buf; + } assert(bin_len >= 0); @@ -1222,7 +1319,7 @@ base85_encode_impl(PyObject *module, Py_buffer *data, int pad, if (state == NULL) { return NULL; } - PyErr_Format(state->Error, "Too much data for %s", name); + PyErr_SetString(state->Error, "Too much data for Base85"); return NULL; } @@ -1272,76 +1369,6 @@ base85_encode_impl(PyObject *module, Py_buffer *data, int pad, return PyBytesWriter_FinishWithPointer(writer, ascii_data); } -/*[clinic input] -binascii.a2b_base85 - - data: ascii_buffer - / - -Decode a line of Base85 data. -[clinic start generated code]*/ - -static PyObject * -binascii_a2b_base85_impl(PyObject *module, Py_buffer *data) -/*[clinic end generated code: output=c2db6ab9181b0089 input=06c9d595352b5a2b]*/ -{ - return base85_decode_impl(module, data, table_a2b_base85, "Base85"); -} - -/*[clinic input] -binascii.b2a_base85 - - data: Py_buffer - / - * - pad: bool = False - Pad input to a multiple of 4 before encoding. - -Base85-code line of data. -[clinic start generated code]*/ - -static PyObject * -binascii_b2a_base85_impl(PyObject *module, Py_buffer *data, int pad) -/*[clinic end generated code: output=b317adb36a57740d input=89fde81b96dcec06]*/ -{ - return base85_encode_impl(module, data, pad, table_b2a_base85, "Base85"); -} - -/*[clinic input] -binascii.a2b_z85 - - data: ascii_buffer - / - -Decode a line of Z85 data. -[clinic start generated code]*/ - -static PyObject * -binascii_a2b_z85_impl(PyObject *module, Py_buffer *data) -/*[clinic end generated code: output=57d8260bb5267a98 input=c54baff4d81510a4]*/ -{ - return base85_decode_impl(module, data, table_a2b_base85_z85, "Z85"); -} - -/*[clinic input] -binascii.b2a_z85 - - data: Py_buffer - / - * - pad: bool = False - Pad input to a multiple of 4 before encoding. - -Z85-code line of data. -[clinic start generated code]*/ - -static PyObject * -binascii_b2a_z85_impl(PyObject *module, Py_buffer *data, int pad) -/*[clinic end generated code: output=88284835e332c9cf input=51d070a5a6cf82d8]*/ -{ - return base85_encode_impl(module, data, pad, table_b2a_base85_z85, "Z85"); -} - /*[clinic input] binascii.crc_hqx @@ -2003,8 +2030,6 @@ static struct PyMethodDef binascii_module_methods[] = { BINASCII_A2B_ASCII85_METHODDEF BINASCII_A2B_BASE85_METHODDEF BINASCII_B2A_BASE85_METHODDEF - BINASCII_A2B_Z85_METHODDEF - BINASCII_B2A_Z85_METHODDEF BINASCII_A2B_HEX_METHODDEF BINASCII_B2A_HEX_METHODDEF BINASCII_HEXLIFY_METHODDEF @@ -2038,6 +2063,85 @@ binascii_exec(PyObject *module) return -1; } + if (PyModule_Add(module, "BASE64_ALPHABET", + PyBytes_FromStringAndSize((const char *)table_b2a_base64, 64)) < 0) + { + return -1; + } + if (PyModule_Add(module, "URLSAFE_BASE64_ALPHABET", + PyBytes_FromString("ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_")) < 0) + { + return -1; + } + if (PyModule_Add(module, "CRYPT_ALPHABET", + PyBytes_FromString("./0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz")) < 0) + { + return -1; + } + if (PyModule_Add(module, "BCRYPT_ALPHABET", + PyBytes_FromString("./" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789")) < 0) + { + return -1; + } + if (PyModule_Add(module, "UU_ALPHABET", + PyBytes_FromString(" !\"#$%&'()*+,-./" + "0123456789:;<=>?@" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "[\\]^_")) < 0) + { + return -1; + } + if (PyModule_Add(module, "XX_ALPHABET", + PyBytes_FromString("+-0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz")) < 0) + { + return -1; + } + if (PyModule_Add(module, "BINHEX_ALPHABET", + PyBytes_FromString("!\"#$%&'()*+,-012345689@" + "ABCDEFGHIJKLMNPQRSTUVXYZ[`" + "abcdefhijklmpqr")) < 0) + { + return -1; + } + if (PyModule_Add(module, "BASE85_ALPHABET", + PyBytes_FromStringAndSize((const char *)table_b2a_base85, 85)) < 0) + { + return -1; + } + if (PyModule_Add(module, "ASCII85_ALPHABET", + PyBytes_FromStringAndSize((const char *)table_b2a_base85_a85, 85)) < 0) + { + return -1; + } + if (PyModule_Add(module, "Z85_ALPHABET", + PyBytes_FromString("0123456789" + "abcdefghijklmnopqrstuvwxyz" + /* clinic doesn't like '/' followed by '*' */ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + ".-:+=^!/\x2a?&<>()[]{}@%$#")) < 0) + { + return -1; + } + + state->reverse_table_cache = PyDict_New(); + if (state->reverse_table_cache == NULL) { + return -1; + } + + state->reverse_table_cache = PyDict_New(); + if (state->reverse_table_cache == NULL) { + return -1; + } + return 0; } @@ -2054,6 +2158,7 @@ binascii_traverse(PyObject *module, visitproc visit, void *arg) binascii_state *state = get_binascii_state(module); Py_VISIT(state->Error); Py_VISIT(state->Incomplete); + Py_VISIT(state->reverse_table_cache); return 0; } @@ -2063,6 +2168,7 @@ binascii_clear(PyObject *module) binascii_state *state = get_binascii_state(module); Py_CLEAR(state->Error); Py_CLEAR(state->Incomplete); + Py_CLEAR(state->reverse_table_cache); return 0; } diff --git a/Modules/clinic/binascii.c.h b/Modules/clinic/binascii.c.h index 68ab9c999b2894..f3ac720b1a35a3 100644 --- a/Modules/clinic/binascii.c.h +++ b/Modules/clinic/binascii.c.h @@ -117,7 +117,7 @@ binascii_b2a_uu(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj PyDoc_STRVAR(binascii_a2b_base64__doc__, "a2b_base64($module, data, /, *, strict_mode=,\n" -" ignorechars=)\n" +" alphabet=BASE64_ALPHABET, ignorechars=)\n" "--\n" "\n" "Decode a line of base64 data.\n" @@ -135,7 +135,7 @@ PyDoc_STRVAR(binascii_a2b_base64__doc__, static PyObject * binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode, - Py_buffer *ignorechars); + PyBytesObject *alphabet, Py_buffer *ignorechars); static PyObject * binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -143,7 +143,7 @@ binascii_a2b_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 2 + #define NUM_KEYWORDS 3 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -152,7 +152,7 @@ binascii_a2b_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(strict_mode), &_Py_ID(ignorechars), }, + .ob_item = { &_Py_ID(strict_mode), &_Py_ID(alphabet), &_Py_ID(ignorechars), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -161,17 +161,18 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "strict_mode", "ignorechars", NULL}; + static const char * const _keywords[] = {"", "strict_mode", "alphabet", "ignorechars", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "a2b_base64", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[3]; + PyObject *argsbuf[4]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; int strict_mode = -1; + PyBytesObject *alphabet = NULL; Py_buffer ignorechars = {NULL, NULL}; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, @@ -194,11 +195,21 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P goto skip_optional_kwonly; } } - if (PyObject_GetBuffer(args[2], &ignorechars, PyBUF_SIMPLE) != 0) { + if (args[2]) { + if (!PyBytes_Check(args[2])) { + _PyArg_BadArgument("a2b_base64", "argument 'alphabet'", "bytes", args[2]); + goto exit; + } + alphabet = (PyBytesObject *)args[2]; + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_a2b_base64_impl(module, &data, strict_mode, &ignorechars); + return_value = binascii_a2b_base64_impl(module, &data, strict_mode, alphabet, &ignorechars); exit: /* Cleanup for data */ @@ -213,7 +224,8 @@ binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P } PyDoc_STRVAR(binascii_b2a_base64__doc__, -"b2a_base64($module, data, /, *, wrapcol=0, newline=True)\n" +"b2a_base64($module, data, /, *, wrapcol=0, newline=True,\n" +" alphabet=BASE64_ALPHABET)\n" "--\n" "\n" "Base64-code line of data."); @@ -223,7 +235,7 @@ PyDoc_STRVAR(binascii_b2a_base64__doc__, static PyObject * binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, size_t wrapcol, - int newline); + int newline, Py_buffer *alphabet); static PyObject * binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) @@ -231,7 +243,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 2 + #define NUM_KEYWORDS 3 static struct { PyGC_Head _this_is_not_used; PyObject_VAR_HEAD @@ -240,7 +252,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(wrapcol), &_Py_ID(newline), }, + .ob_item = { &_Py_ID(wrapcol), &_Py_ID(newline), &_Py_ID(alphabet), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -249,18 +261,19 @@ 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[] = {"", "wrapcol", "newline", NULL}; + static const char * const _keywords[] = {"", "wrapcol", "newline", "alphabet", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, .fname = "b2a_base64", .kwtuple = KWTUPLE, }; #undef KWTUPLE - PyObject *argsbuf[3]; + PyObject *argsbuf[4]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; size_t wrapcol = 0; int newline = 1; + Py_buffer alphabet = {NULL, NULL}; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -281,18 +294,30 @@ binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P goto skip_optional_kwonly; } } - newline = PyObject_IsTrue(args[2]); - if (newline < 0) { + if (args[2]) { + newline = PyObject_IsTrue(args[2]); + if (newline < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (PyObject_GetBuffer(args[3], &alphabet, PyBUF_SIMPLE) != 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_b2a_base64_impl(module, &data, wrapcol, newline); + return_value = binascii_b2a_base64_impl(module, &data, wrapcol, newline, &alphabet); exit: /* Cleanup for data */ if (data.obj) { PyBuffer_Release(&data); } + /* Cleanup for alphabet */ + if (alphabet.obj) { + PyBuffer_Release(&alphabet); + } return return_value; } @@ -519,53 +544,20 @@ binascii_b2a_ascii85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, } PyDoc_STRVAR(binascii_a2b_base85__doc__, -"a2b_base85($module, data, /)\n" +"a2b_base85($module, data, /, *, alphabet=BASE85_ALPHABET)\n" "--\n" "\n" "Decode a line of Base85 data."); #define BINASCII_A2B_BASE85_METHODDEF \ - {"a2b_base85", (PyCFunction)binascii_a2b_base85, METH_O, binascii_a2b_base85__doc__}, - -static PyObject * -binascii_a2b_base85_impl(PyObject *module, Py_buffer *data); - -static PyObject * -binascii_a2b_base85(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - Py_buffer data = {NULL, NULL}; - - if (!ascii_buffer_converter(arg, &data)) { - goto exit; - } - return_value = binascii_a2b_base85_impl(module, &data); - -exit: - /* Cleanup for data */ - if (data.obj) - PyBuffer_Release(&data); - - return return_value; -} - -PyDoc_STRVAR(binascii_b2a_base85__doc__, -"b2a_base85($module, data, /, *, pad=False)\n" -"--\n" -"\n" -"Base85-code line of data.\n" -"\n" -" pad\n" -" Pad input to a multiple of 4 before encoding."); - -#define BINASCII_B2A_BASE85_METHODDEF \ - {"b2a_base85", _PyCFunction_CAST(binascii_b2a_base85), METH_FASTCALL|METH_KEYWORDS, binascii_b2a_base85__doc__}, + {"a2b_base85", _PyCFunction_CAST(binascii_a2b_base85), METH_FASTCALL|METH_KEYWORDS, binascii_a2b_base85__doc__}, static PyObject * -binascii_b2a_base85_impl(PyObject *module, Py_buffer *data, int pad); +binascii_a2b_base85_impl(PyObject *module, Py_buffer *data, + PyBytesObject *alphabet); static PyObject * -binascii_b2a_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +binascii_a2b_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { PyObject *return_value = NULL; #if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE) @@ -579,7 +571,7 @@ binascii_b2a_base85(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(pad), }, + .ob_item = { &_Py_ID(alphabet), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -588,67 +580,36 @@ binascii_b2a_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, P # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "pad", NULL}; + static const char * const _keywords[] = {"", "alphabet", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "b2a_base85", + .fname = "a2b_base85", .kwtuple = KWTUPLE, }; #undef KWTUPLE PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; Py_buffer data = {NULL, NULL}; - int pad = 0; + PyBytesObject *alphabet = NULL; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); if (!args) { goto exit; } - if (PyObject_GetBuffer(args[0], &data, PyBUF_SIMPLE) != 0) { + if (!ascii_buffer_converter(args[0], &data)) { goto exit; } if (!noptargs) { goto skip_optional_kwonly; } - pad = PyObject_IsTrue(args[1]); - if (pad < 0) { + if (!PyBytes_Check(args[1])) { + _PyArg_BadArgument("a2b_base85", "argument 'alphabet'", "bytes", args[1]); goto exit; } + alphabet = (PyBytesObject *)args[1]; skip_optional_kwonly: - return_value = binascii_b2a_base85_impl(module, &data, pad); - -exit: - /* Cleanup for data */ - if (data.obj) { - PyBuffer_Release(&data); - } - - return return_value; -} - -PyDoc_STRVAR(binascii_a2b_z85__doc__, -"a2b_z85($module, data, /)\n" -"--\n" -"\n" -"Decode a line of Z85 data."); - -#define BINASCII_A2B_Z85_METHODDEF \ - {"a2b_z85", (PyCFunction)binascii_a2b_z85, METH_O, binascii_a2b_z85__doc__}, - -static PyObject * -binascii_a2b_z85_impl(PyObject *module, Py_buffer *data); - -static PyObject * -binascii_a2b_z85(PyObject *module, PyObject *arg) -{ - PyObject *return_value = NULL; - Py_buffer data = {NULL, NULL}; - - if (!ascii_buffer_converter(arg, &data)) { - goto exit; - } - return_value = binascii_a2b_z85_impl(module, &data); + return_value = binascii_a2b_base85_impl(module, &data, alphabet); exit: /* Cleanup for data */ @@ -658,28 +619,29 @@ binascii_a2b_z85(PyObject *module, PyObject *arg) return return_value; } -PyDoc_STRVAR(binascii_b2a_z85__doc__, -"b2a_z85($module, data, /, *, pad=False)\n" +PyDoc_STRVAR(binascii_b2a_base85__doc__, +"b2a_base85($module, data, /, *, pad=False, alphabet=BASE85_ALPHABET)\n" "--\n" "\n" -"Z85-code line of data.\n" +"Base85-code line of data.\n" "\n" " pad\n" " Pad input to a multiple of 4 before encoding."); -#define BINASCII_B2A_Z85_METHODDEF \ - {"b2a_z85", _PyCFunction_CAST(binascii_b2a_z85), METH_FASTCALL|METH_KEYWORDS, binascii_b2a_z85__doc__}, +#define BINASCII_B2A_BASE85_METHODDEF \ + {"b2a_base85", _PyCFunction_CAST(binascii_b2a_base85), METH_FASTCALL|METH_KEYWORDS, binascii_b2a_base85__doc__}, static PyObject * -binascii_b2a_z85_impl(PyObject *module, Py_buffer *data, int pad); +binascii_b2a_base85_impl(PyObject *module, Py_buffer *data, int pad, + Py_buffer *alphabet); static PyObject * -binascii_b2a_z85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +binascii_b2a_base85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) { 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 @@ -688,7 +650,7 @@ binascii_b2a_z85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb } _kwtuple = { .ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS) .ob_hash = -1, - .ob_item = { &_Py_ID(pad), }, + .ob_item = { &_Py_ID(pad), &_Py_ID(alphabet), }, }; #undef NUM_KEYWORDS #define KWTUPLE (&_kwtuple.ob_base.ob_base) @@ -697,17 +659,18 @@ binascii_b2a_z85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb # define KWTUPLE NULL #endif // !Py_BUILD_CORE - static const char * const _keywords[] = {"", "pad", NULL}; + static const char * const _keywords[] = {"", "pad", "alphabet", NULL}; static _PyArg_Parser _parser = { .keywords = _keywords, - .fname = "b2a_z85", + .fname = "b2a_base85", .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}; int pad = 0; + Py_buffer alphabet = {NULL, NULL}; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, /*minpos*/ 1, /*maxpos*/ 1, /*minkw*/ 0, /*varpos*/ 0, argsbuf); @@ -720,18 +683,30 @@ binascii_b2a_z85(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyOb if (!noptargs) { goto skip_optional_kwonly; } - pad = PyObject_IsTrue(args[1]); - if (pad < 0) { + if (args[1]) { + pad = PyObject_IsTrue(args[1]); + if (pad < 0) { + goto exit; + } + if (!--noptargs) { + goto skip_optional_kwonly; + } + } + if (PyObject_GetBuffer(args[2], &alphabet, PyBUF_SIMPLE) != 0) { goto exit; } skip_optional_kwonly: - return_value = binascii_b2a_z85_impl(module, &data, pad); + return_value = binascii_b2a_base85_impl(module, &data, pad, &alphabet); exit: /* Cleanup for data */ if (data.obj) { PyBuffer_Release(&data); } + /* Cleanup for alphabet */ + if (alphabet.obj) { + PyBuffer_Release(&alphabet); + } return return_value; } @@ -1281,4 +1256,4 @@ binascii_b2a_qp(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObj return return_value; } -/*[clinic end generated code: output=28de2d0774a0a4d7 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0343bea327c9f0b5 input=a9049054013a1b77]*/