https://github.com/python/cpython/commit/8bf8bf92921a13cc18f7d1b5bed8bd32c8485ba4
commit: 8bf8bf92921a13cc18f7d1b5bed8bd32c8485ba4
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-04-04T21:26:16+03:00
summary:
gh-73613: Support Base32 and Base64 without padding (GH-147974)
Add the padded parameter in functions related to Base32 and Base64 codecs
in the binascii and base64 modules. In the encoding functions it controls
whether the pad character can be added in the output, in the decoding
functions it controls whether padding is required in input.
Padding of input no longer required in base64.urlsafe_b64decode() by default.
files:
A Misc/NEWS.d/next/Library/2026-04-01-18-17-55.gh-issue-73613.PLEebm.rst
M Doc/library/base64.rst
M Doc/library/binascii.rst
M Doc/whatsnew/3.15.rst
M Include/internal/pycore_global_objects_fini_generated.h
M Include/internal/pycore_global_strings.h
M Include/internal/pycore_runtime_init_generated.h
M Include/internal/pycore_unicodeobject_generated.h
M Lib/base64.py
M Lib/test/test_base64.py
M Lib/test/test_binascii.py
M Modules/binascii.c
M Modules/clinic/binascii.c.h
diff --git a/Doc/library/base64.rst b/Doc/library/base64.rst
index 1a1785cb58772e..425dff8f2a9ad1 100644
--- a/Doc/library/base64.rst
+++ b/Doc/library/base64.rst
@@ -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, *, wrapcol=0)
+.. function:: b64encode(s, altchars=None, *, padded=True, wrapcol=0)
Encode the :term:`bytes-like object` *s* using Base64 and return the encoded
:class:`bytes`.
@@ -61,6 +61,10 @@ 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 *padded* is true (default), pad the encoded data with the '='
+ character to a size multiple of 4.
+ If *padded* is false, do not add the pad characters.
+
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.
@@ -69,11 +73,11 @@ POST request.
:exc:`TypeError` if *altchars* is not a :term:`bytes-like object`.
.. versionchanged:: 3.15
- Added the *wrapcol* parameter.
+ Added the *padded* and *wrapcol* parameters.
-.. function:: b64decode(s, altchars=None, validate=False)
- b64decode(s, altchars=None, validate=True, *, ignorechars)
+.. function:: b64decode(s, altchars=None, validate=False, *, padded=True)
+ b64decode(s, altchars=None, validate=True, *, ignorechars,
padded=True)
Decode the Base64 encoded :term:`bytes-like object` or ASCII string
*s* and return the decoded :class:`bytes`.
@@ -82,6 +86,11 @@ POST request.
of length 2 which specifies the alternative alphabet used instead of the
``+`` and ``/`` characters.
+ If *padded* is true, the last group of 4 base 64 alphabet characters must
+ be padded with the '=' character.
+ If *padded* is false, the '=' character is treated as other non-alphabet
+ characters (depending on the value of *validate* and *ignorechars*).
+
A :exc:`binascii.Error` exception is raised
if *s* is incorrectly padded.
@@ -106,7 +115,7 @@ POST request.
For more information about the strict base64 check, see
:func:`binascii.a2b_base64`
.. versionchanged:: 3.15
- Added the *ignorechars* parameter.
+ Added the *ignorechars* and *padded* parameters.
.. deprecated:: 3.15
Accepting the ``+`` and ``/`` characters with an alternative alphabet
@@ -125,16 +134,19 @@ POST request.
Base64 alphabet and return the decoded :class:`bytes`.
-.. function:: urlsafe_b64encode(s)
+.. function:: urlsafe_b64encode(s, *, padded=True)
Encode :term:`bytes-like object` *s* using the
URL- and filesystem-safe alphabet, which
substitutes ``-`` instead of ``+`` and ``_`` instead of ``/`` in the
standard Base64 alphabet, and return the encoded :class:`bytes`. The result
- can still contain ``=``.
+ can still contain ``=`` if *padded* is true (default).
+
+ .. versionchanged:: next
+ Added the *padded* parameter.
-.. function:: urlsafe_b64decode(s)
+.. function:: urlsafe_b64decode(s, *, padded=False)
Decode :term:`bytes-like object` or ASCII string *s*
using the URL- and filesystem-safe
@@ -142,24 +154,32 @@ POST request.
``/`` in the standard Base64 alphabet, and return the decoded
:class:`bytes`.
+ .. versionchanged:: next
+ Added the *padded* parameter.
+ Padding of input is no longer required by default.
+
.. deprecated:: 3.15
Accepting the ``+`` and ``/`` characters is now deprecated.
-.. function:: b32encode(s, *, wrapcol=0)
+.. function:: b32encode(s, *, padded=True, wrapcol=0)
Encode the :term:`bytes-like object` *s* using Base32 and return the
encoded :class:`bytes`.
+ If *padded* is true (default), pad the encoded data with the '='
+ character to a size multiple of 8.
+ If *padded* is false, do not add the pad characters.
+
If *wrapcol* is non-zero, insert a newline (``b'\n'``) character
after at most every *wrapcol* characters.
If *wrapcol* is zero (default), do not add any newlines.
.. versionchanged:: next
- Added the *wrapcol* parameter.
+ Added the *padded* and *wrapcol* parameters.
-.. function:: b32decode(s, casefold=False, map01=None, *, ignorechars=b'')
+.. function:: b32decode(s, casefold=False, map01=None, *, padded=True,
ignorechars=b'')
Decode the Base32 encoded :term:`bytes-like object` or ASCII string *s* and
return the decoded :class:`bytes`.
@@ -175,6 +195,11 @@ POST request.
digit 0 is always mapped to the letter O). For security purposes the
default is
``None``, so that 0 and 1 are not allowed in the input.
+ If *padded* is true, the last group of 8 base 32 alphabet characters must
+ be padded with the '=' character.
+ If *padded* is false, the '=' character is treated as other non-alphabet
+ characters (depending on the value of *ignorechars*).
+
*ignorechars* should be a :term:`bytes-like object` containing characters
to ignore from the input.
@@ -183,10 +208,10 @@ POST request.
input.
.. versionchanged:: next
- Added the *ignorechars* parameter.
+ Added the *ignorechars* and *padded* parameters.
-.. function:: b32hexencode(s, *, wrapcol=0)
+.. function:: b32hexencode(s, *, padded=True, wrapcol=0)
Similar to :func:`b32encode` but uses the Extended Hex Alphabet, as defined
in
:rfc:`4648`.
@@ -194,10 +219,10 @@ POST request.
.. versionadded:: 3.10
.. versionchanged:: next
- Added the *wrapcol* parameter.
+ Added the *padded* and *wrapcol* parameters.
-.. function:: b32hexdecode(s, casefold=False, *, ignorechars=b'')
+.. function:: b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b'')
Similar to :func:`b32decode` but uses the Extended Hex Alphabet, as defined
in
:rfc:`4648`.
@@ -210,7 +235,7 @@ POST request.
.. versionadded:: 3.10
.. versionchanged:: next
- Added the *ignorechars* parameter.
+ Added the *ignorechars* and *padded* parameters.
.. function:: b16encode(s, *, wrapcol=0)
diff --git a/Doc/library/binascii.rst b/Doc/library/binascii.rst
index 4a82d0742ae9db..4f2edb7eff8a8f 100644
--- a/Doc/library/binascii.rst
+++ b/Doc/library/binascii.rst
@@ -48,8 +48,8 @@ The :mod:`!binascii` module defines the following functions:
Added the *backtick* parameter.
-.. function:: a2b_base64(string, /, *, alphabet=BASE64_ALPHABET,
strict_mode=False)
- a2b_base64(string, /, *, ignorechars, alphabet=BASE64_ALPHABET,
strict_mode=True)
+.. function:: a2b_base64(string, /, *, padded=True, alphabet=BASE64_ALPHABET,
strict_mode=False)
+ a2b_base64(string, /, *, ignorechars, padded=True,
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.
@@ -57,6 +57,11 @@ The :mod:`!binascii` module defines the following functions:
Optional *alphabet* must be a :class:`bytes` object of length 64 which
specifies an alternative alphabet.
+ If *padded* is true, the last group of 4 base 64 alphabet characters must
+ be padded with the '=' character.
+ If *padded* is false, the '=' character is treated as other non-alphabet
+ characters (depending on the value of *strict_mode* and *ignorechars*).
+
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
@@ -79,14 +84,18 @@ The :mod:`!binascii` module defines the following functions:
Added the *strict_mode* parameter.
.. versionchanged:: 3.15
- Added the *alphabet* and *ignorechars* parameters.
+ Added the *alphabet*, *ignorechars* and *padded* parameters.
-.. function:: b2a_base64(data, *, alphabet=BASE64_ALPHABET, wrapcol=0,
newline=True)
+.. function:: b2a_base64(data, *, padded=True, 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`.
+ If *padded* is true (default), pad the encoded data with the '='
+ character to a size multiple of 4.
+ If *padded* is false, do not add the pad characters.
+
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.
@@ -98,7 +107,7 @@ The :mod:`!binascii` module defines the following functions:
Added the *newline* parameter.
.. versionchanged:: 3.15
- Added the *alphabet* and *wrapcol* parameters.
+ Added the *alphabet*, *padded* and *wrapcol* parameters.
.. function:: a2b_ascii85(string, /, *, foldspaces=False, adobe=False,
ignorechars=b'')
@@ -190,7 +199,7 @@ The :mod:`!binascii` module defines the following functions:
.. versionadded:: 3.15
-.. function:: a2b_base32(string, /, *, alphabet=BASE32_ALPHABET,
ignorechars=b'')
+.. function:: a2b_base32(string, /, *, padded=True, alphabet=BASE32_ALPHABET,
ignorechars=b'')
Convert base32 data back to binary and return the binary data.
@@ -208,6 +217,11 @@ The :mod:`!binascii` module defines the following
functions:
Optional *alphabet* must be a :class:`bytes` object of length 32 which
specifies an alternative alphabet.
+ If *padded* is true, the last group of 8 base 32 alphabet characters must
+ be padded with the '=' character.
+ If *padded* is false, the '=' character is treated as other non-alphabet
+ characters (depending on the value of *ignorechars*).
+
*ignorechars* should be a :term:`bytes-like object` containing characters
to ignore from the input.
If *ignorechars* contains the pad character ``'='``, the pad characters
@@ -218,7 +232,7 @@ The :mod:`!binascii` module defines the following functions:
.. versionadded:: next
-.. function:: b2a_base32(data, /, *, alphabet=BASE32_ALPHABET, wrapcol=0)
+.. function:: b2a_base32(data, /, *, padded=True, alphabet=BASE32_ALPHABET,
wrapcol=0)
Convert binary data to a line of ASCII characters in base32 coding,
as specified in :rfc:`4648`. The return value is the converted line.
@@ -226,6 +240,10 @@ The :mod:`!binascii` module defines the following
functions:
Optional *alphabet* must be a :term:`bytes-like object` of length 32 which
specifies an alternative alphabet.
+ If *padded* is true (default), pad the encoded data with the '='
+ character to a size multiple of 8.
+ If *padded* is false, do not add the pad characters.
+
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.
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 287109035f1ee6..d1d4b92bcf4e97 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -657,6 +657,13 @@ base64
* Added the *pad* parameter in :func:`~base64.z85encode`.
(Contributed by Hauke Dämpfling in :gh:`143103`.)
+* Added the *padded* parameter in
+ :func:`~base64.b32encode`, :func:`~base64.b32decode`,
+ :func:`~base64.b32hexencode`, :func:`~base64.b32hexdecode`,
+ :func:`~base64.b64encode`, :func:`~base64.b64decode`,
+ :func:`~base64.urlsafe_b64encode`, and :func:`~base64.urlsafe_b64decode`.
+ (Contributed by Serhiy Storchaka in :gh:`73613`.)
+
* Added the *wrapcol* parameter in :func:`~base64.b16encode`,
:func:`~base64.b32encode`, :func:`~base64.b32hexencode`,
:func:`~base64.b64encode`, :func:`~base64.b85encode`, and
@@ -686,6 +693,11 @@ binascii
(Contributed by James Seo and Serhiy Storchaka in :gh:`101178`.)
+* Added the *padded* parameter in
+ :func:`~binascii.b2a_base32`, :func:`~binascii.a2b_base32`,
+ :func:`~binascii.b2a_base64`, and :func:`~binascii.a2b_base64`.
+ (Contributed by Serhiy Storchaka in :gh:`73613`.)
+
* Added the *wrapcol* parameter in :func:`~binascii.b2a_base64`.
(Contributed by Serhiy Storchaka in :gh:`143214`.)
@@ -2027,3 +2039,9 @@ that may require changes to your code.
*dest* is now ``'foo'`` instead of ``'f'``.
Pass an explicit *dest* argument to preserve the old behavior.
(Contributed by Serhiy Storchaka in :gh:`138697`.)
+
+* Padding of input no longer required in :func:`base64.urlsafe_b64decode`.
+ Pass a new argument ``padded=True`` or use :func:`base64.b64decode`
+ with argument ``altchars=b'-_'`` (this works with older Python versions)
+ to make padding required.
+ (Contributed by Serhiy Storchaka in :gh:`73613`.)
diff --git a/Include/internal/pycore_global_objects_fini_generated.h
b/Include/internal/pycore_global_objects_fini_generated.h
index 4b1e289c6ff468..beae65213a27b6 100644
--- a/Include/internal/pycore_global_objects_fini_generated.h
+++ b/Include/internal/pycore_global_objects_fini_generated.h
@@ -1974,6 +1974,7 @@ _PyStaticObjects_CheckRefcnt(PyInterpreterState *interp) {
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(overlapped));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(owner));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pad));
+ _PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(padded));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(pages));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(parameter));
_PyStaticObject_CheckRefcnt((PyObject *)&_Py_ID(parent));
diff --git a/Include/internal/pycore_global_strings.h
b/Include/internal/pycore_global_strings.h
index 6ee649b59a5c37..bb1c6dbaf03906 100644
--- a/Include/internal/pycore_global_strings.h
+++ b/Include/internal/pycore_global_strings.h
@@ -697,6 +697,7 @@ struct _Py_global_strings {
STRUCT_FOR_ID(overlapped)
STRUCT_FOR_ID(owner)
STRUCT_FOR_ID(pad)
+ STRUCT_FOR_ID(padded)
STRUCT_FOR_ID(pages)
STRUCT_FOR_ID(parameter)
STRUCT_FOR_ID(parent)
diff --git a/Include/internal/pycore_runtime_init_generated.h
b/Include/internal/pycore_runtime_init_generated.h
index 778db946c2a3aa..64b029797ab9b3 100644
--- a/Include/internal/pycore_runtime_init_generated.h
+++ b/Include/internal/pycore_runtime_init_generated.h
@@ -1972,6 +1972,7 @@ extern "C" {
INIT_ID(overlapped), \
INIT_ID(owner), \
INIT_ID(pad), \
+ INIT_ID(padded), \
INIT_ID(pages), \
INIT_ID(parameter), \
INIT_ID(parent), \
diff --git a/Include/internal/pycore_unicodeobject_generated.h
b/Include/internal/pycore_unicodeobject_generated.h
index bd8f50ff0ee732..461ee36dcebb6d 100644
--- a/Include/internal/pycore_unicodeobject_generated.h
+++ b/Include/internal/pycore_unicodeobject_generated.h
@@ -2568,6 +2568,10 @@ _PyUnicode_InitStaticStrings(PyInterpreterState *interp)
{
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
assert(PyUnicode_GET_LENGTH(string) != 1);
+ string = &_Py_ID(padded);
+ _PyUnicode_InternStatic(interp, &string);
+ assert(_PyUnicode_CheckConsistency(string, 1));
+ assert(PyUnicode_GET_LENGTH(string) != 1);
string = &_Py_ID(pages);
_PyUnicode_InternStatic(interp, &string);
assert(_PyUnicode_CheckConsistency(string, 1));
diff --git a/Lib/base64.py b/Lib/base64.py
index 47b90643e8da73..a94bec4d031c52 100644
--- a/Lib/base64.py
+++ b/Lib/base64.py
@@ -46,13 +46,15 @@ def _bytes_from_decode_data(s):
# Base64 encoding/decoding uses binascii
-def b64encode(s, altchars=None, *, wrapcol=0):
+def b64encode(s, altchars=None, *, padded=True, 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 padded is false, omit padding in the output.
+
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
every wrapcol characters.
"""
@@ -60,18 +62,21 @@ def b64encode(s, altchars=None, *, wrapcol=0):
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,
+ return binascii.b2a_base64(s, padded=padded, wrapcol=wrapcol,
newline=False,
alphabet=alphabet)
- return binascii.b2a_base64(s, wrapcol=wrapcol, newline=False)
+ return binascii.b2a_base64(s, padded=padded, wrapcol=wrapcol,
newline=False)
-def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, *,
ignorechars=_NOT_SPECIFIED):
+def b64decode(s, altchars=None, validate=_NOT_SPECIFIED,
+ *, padded=True, ignorechars=_NOT_SPECIFIED):
"""Decode the Base64 encoded bytes-like object or ASCII string s.
Optional altchars must be a bytes-like object or ASCII string of length 2
which specifies the alternative alphabet used instead of the '+' and '/'
characters.
+ If padded is false, padding in input is not required.
+
The result is returned as a bytes object. A binascii.Error is raised if
s is incorrectly padded.
@@ -105,11 +110,11 @@ def b64decode(s, altchars=None, validate=_NOT_SPECIFIED,
*, ignorechars=_NOT_SPE
alphabet = binascii.BASE64_ALPHABET[:-2] + altchars
return binascii.a2b_base64(s, strict_mode=validate,
alphabet=alphabet,
- ignorechars=ignorechars)
+ padded=padded, ignorechars=ignorechars)
if ignorechars is _NOT_SPECIFIED:
ignorechars = b''
result = binascii.a2b_base64(s, strict_mode=validate,
- ignorechars=ignorechars)
+ padded=padded, ignorechars=ignorechars)
if badchar is not None:
import warnings
if validate:
@@ -145,17 +150,19 @@ def standard_b64decode(s):
_urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/')
-def urlsafe_b64encode(s):
+def urlsafe_b64encode(s, *, padded=True):
"""Encode bytes using the URL- and filesystem-safe Base64 alphabet.
Argument s is a bytes-like object to encode. The result is returned as a
bytes object. The alphabet uses '-' instead of '+' and '_' instead of
'/'.
+
+ If padded is false, omit padding in the output.
"""
- return binascii.b2a_base64(s, newline=False,
+ return binascii.b2a_base64(s, padded=padded, newline=False,
alphabet=binascii.URLSAFE_BASE64_ALPHABET)
-def urlsafe_b64decode(s):
+def urlsafe_b64decode(s, *, padded=False):
"""Decode bytes using the URL- and filesystem-safe Base64 alphabet.
Argument s is a bytes-like object or ASCII string to decode. The result
@@ -164,6 +171,8 @@ def urlsafe_b64decode(s):
alphabet, and are not a plus '+' or slash '/', are discarded prior to the
padding check.
+ If padded is false, padding in input is not required.
+
The alphabet uses '-' instead of '+' and '_' instead of '/'.
"""
s = _bytes_from_decode_data(s)
@@ -173,7 +182,7 @@ def urlsafe_b64decode(s):
badchar = b
break
s = s.translate(_urlsafe_decode_translation)
- result = binascii.a2b_base64(s, strict_mode=False)
+ result = binascii.a2b_base64(s, strict_mode=False, padded=padded)
if badchar is not None:
import warnings
warnings.warn(f'invalid character {chr(badchar)!a} in URL-safe Base64
data '
@@ -187,6 +196,8 @@ def urlsafe_b64decode(s):
_B32_ENCODE_DOCSTRING = '''
Encode the bytes-like objects using {encoding} and return a bytes object.
+If padded is false, omit padding in the output.
+
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
every wrapcol characters.
'''
@@ -196,6 +207,8 @@ def urlsafe_b64decode(s):
Optional casefold is a flag specifying whether a lowercase alphabet is
acceptable as input. For security purposes, the default is False.
+If padded is false, padding in input is not required.
+
ignorechars should be a byte string containing characters to ignore
from the input.
{extra_args}
@@ -213,11 +226,11 @@ def urlsafe_b64decode(s):
0 and 1 are not allowed in the input.
'''
-def b32encode(s, *, wrapcol=0):
- return binascii.b2a_base32(s, wrapcol=wrapcol)
+def b32encode(s, *, padded=True, wrapcol=0):
+ return binascii.b2a_base32(s, padded=padded, wrapcol=wrapcol)
b32encode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32')
-def b32decode(s, casefold=False, map01=None, *, ignorechars=b''):
+def b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b''):
s = _bytes_from_decode_data(s)
# Handle section 2.4 zero and one mapping. The flag map01 will be either
# False, or the character to map the digit 1 (one) to. It should be
@@ -228,22 +241,22 @@ def b32decode(s, casefold=False, map01=None, *,
ignorechars=b''):
s = s.translate(bytes.maketrans(b'01', b'O' + map01))
if casefold:
s = s.upper()
- return binascii.a2b_base32(s, ignorechars=ignorechars)
+ return binascii.a2b_base32(s, padded=padded, ignorechars=ignorechars)
b32decode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32',
extra_args=_B32_DECODE_MAP01_DOCSTRING)
-def b32hexencode(s, *, wrapcol=0):
- return binascii.b2a_base32(s, wrapcol=wrapcol,
+def b32hexencode(s, *, padded=True, wrapcol=0):
+ return binascii.b2a_base32(s, padded=padded, wrapcol=wrapcol,
alphabet=binascii.BASE32HEX_ALPHABET)
b32hexencode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32hex')
-def b32hexdecode(s, casefold=False, *, ignorechars=b''):
+def b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b''):
s = _bytes_from_decode_data(s)
# base32hex does not have the 01 mapping
if casefold:
s = s.upper()
return binascii.a2b_base32(s, alphabet=binascii.BASE32HEX_ALPHABET,
- ignorechars=ignorechars)
+ padded=padded, ignorechars=ignorechars)
b32hexdecode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32hex',
extra_args='')
@@ -341,7 +354,7 @@ def b85encode(b, pad=False, *, wrapcol=0):
"""
return binascii.b2a_base85(b, wrapcol=wrapcol, pad=pad)
-def b85decode(b, *, ignorechars=b''):
+def b85decode(b, *, ignorechars=b''):
"""Decode the base85-encoded bytes-like object or ASCII string b
The result is returned as a bytes object.
@@ -360,7 +373,7 @@ def z85encode(s, pad=False, *, wrapcol=0):
return binascii.b2a_base85(s, wrapcol=wrapcol, pad=pad,
alphabet=binascii.Z85_ALPHABET)
-def z85decode(s, *, ignorechars=b''):
+def z85decode(s, *, ignorechars=b''):
"""Decode the z85-encoded bytes-like object or ASCII string b
The result is returned as a bytes object.
diff --git a/Lib/test/test_base64.py b/Lib/test/test_base64.py
index d5f8f44e280b54..1a4dd56a553f4d 100644
--- a/Lib/test/test_base64.py
+++ b/Lib/test/test_base64.py
@@ -209,6 +209,25 @@ def test_b64encode(self):
b'\xd3V\xbeo\xf7\x1d', b'01a-b_cd')
self.check_encode_type_errors(base64.urlsafe_b64encode)
+ def test_b64encode_padded(self):
+ b64encode = base64.b64encode
+ self.assertEqual(b64encode(b'', padded=False), b'')
+ self.assertEqual(b64encode(b'a', padded=False), b'YQ')
+ self.assertEqual(b64encode(b'ab', padded=False), b'YWI')
+ self.assertEqual(b64encode(b'abc', padded=False), b'YWJj')
+ self.assertEqual(b64encode(b'\xfb', padded=False, altchars=b'-_'),
b'-w')
+ self.assertEqual(b64encode(b'\xfb\xff', padded=False, altchars=b'-_'),
+ b'-_8')
+ self.assertEqual(b64encode(b'\xfb\xff\xbf', padded=False,
altchars=b'-_'),
+ b'-_-_')
+
+ urlsafe_b64encode = base64.urlsafe_b64encode
+ self.assertEqual(urlsafe_b64encode(b'', padded=False), b'')
+ self.assertEqual(urlsafe_b64encode(b'\xfb', padded=False), b'-w')
+ self.assertEqual(urlsafe_b64encode(b'\xfb\xff', padded=False), b'-_8')
+ self.assertEqual(urlsafe_b64encode(b'\xfb\xff\xbf', padded=False),
+ b'-_-_')
+
def _common_test_wrapcol(self, func, data):
eq = self.assertEqual
expected = func(data)
@@ -314,6 +333,36 @@ def test_b64decode_padding_error(self):
self.assertRaises(binascii.Error, base64.b64decode, b'abc')
self.assertRaises(binascii.Error, base64.b64decode, 'abc')
+ def test_b64decode_padded(self):
+ b64decode = base64.b64decode
+ urlsafe_b64decode = base64.urlsafe_b64decode
+ def check(data, expected, padded=0):
+ if b'=' in data:
+ with self.assertRaisesRegex(binascii.Error, 'Padding not
allowed'):
+ b64decode(data, padded=False, validate=True)
+ self.assertEqual(b64decode(data, padded=False, ignorechars=b'='),
+ expected)
+ self.assertEqual(urlsafe_b64decode(data, padded=True), expected)
+ self.assertEqual(urlsafe_b64decode(data, padded=False), expected)
+ data = data.replace(b'=', b'')
+ self.assertEqual(b64decode(data, padded=False), expected)
+ self.assertEqual(b64decode(data, padded=False, validate=True),
+ expected)
+ self.assertEqual(urlsafe_b64decode(data), expected)
+
+ check(b'', b'')
+ check(b'YQ==', b'a')
+ check(b'YWI=', b'ab')
+ check(b'YWJj', b'abc')
+ check(b'Y=WJj', b'abc')
+ check(b'YW=Jj', b'abc')
+ check(b'YWJ=j', b'abc')
+
+ with self.assertRaisesRegex(binascii.Error, 'Incorrect padding'):
+ urlsafe_b64decode(b'YQ', padded=True)
+ with self.assertRaisesRegex(binascii.Error, 'Incorrect padding'):
+ urlsafe_b64decode(b'YWI', padded=True)
+
def _common_test_ignorechars(self, func):
eq = self.assertEqual
eq(func(b'', ignorechars=b' \n'), b'')
@@ -487,6 +536,15 @@ def test_b32encode(self):
self.check_other_types(base64.b32encode, b'abcd', b'MFRGGZA=')
self.check_encode_type_errors(base64.b32encode)
+ def test_b32encode_padded(self):
+ b32encode = base64.b32encode
+ self.assertEqual(b32encode(b'', padded=False), b'')
+ self.assertEqual(b32encode(b'a', padded=False), b'ME')
+ self.assertEqual(b32encode(b'ab', padded=False), b'MFRA')
+ self.assertEqual(b32encode(b'abc', padded=False), b'MFRGG')
+ self.assertEqual(b32encode(b'abcd', padded=False), b'MFRGGZA')
+ self.assertEqual(b32encode(b'abcde', padded=False), b'MFRGGZDF')
+
def test_b32encode_wrapcol(self):
eq = self.assertEqual
b = b'www.python.org'
@@ -564,6 +622,31 @@ def test_b32decode_map01(self):
eq(base64.b32decode(b'M%c023456' % map01, map01=map01), res)
eq(base64.b32decode(b'M%cO23456' % map01, map01=map01), res)
+ def test_b32decode_padded(self):
+ b32decode = base64.b32decode
+ def check(data, expected):
+ if b'=' in data:
+ with self.assertRaisesRegex(binascii.Error, 'Padding not
allowed'):
+ b32decode(data, padded=False)
+ self.assertEqual(b32decode(data, padded=False, ignorechars=b'='),
+ expected)
+ data = data.replace(b'=', b'')
+ self.assertEqual(b32decode(data, padded=False), expected)
+
+ check(b'', b'')
+ check(b'ME======', b'a')
+ check(b'MFRA====', b'ab')
+ check(b'MFRGG===', b'abc')
+ check(b'MFRGGZA=', b'abcd')
+ check(b'MFRGGZDF', b'abcde')
+ check(b'M=FRGGZDF', b'abcde')
+ check(b'MF=RGGZDF', b'abcde')
+ check(b'MFR=GGZDF', b'abcde')
+ check(b'MFRG=GZDF', b'abcde')
+ check(b'MFRGG=ZDF', b'abcde')
+ check(b'MFRGGZ=DF', b'abcde')
+ check(b'MFRGGZD=F', b'abcde')
+
def test_b32decode_ignorechars(self):
self._common_test_ignorechars(base64.b32decode)
eq = self.assertEqual
@@ -632,6 +715,8 @@ def test_b32hexencode(self):
for to_encode, expected in test_cases:
with self.subTest(to_decode=to_encode):
self.assertEqual(base64.b32hexencode(to_encode), expected)
+ self.assertEqual(base64.b32hexencode(to_encode, padded=False),
+ expected.rstrip(b'='))
def test_b32hexencode_other_types(self):
self.check_other_types(base64.b32hexencode, b'abcd', b'C5H66P0=')
@@ -679,6 +764,31 @@ def test_b32hexdecode_other_types(self):
self.check_other_types(base64.b32hexdecode, b'C5H66===', b'abc')
self.check_decode_type_errors(base64.b32hexdecode)
+ def test_b32hexdecode_padded(self):
+ b32hexdecode = base64.b32hexdecode
+ def check(data, expected):
+ if b'=' in data:
+ with self.assertRaisesRegex(binascii.Error, 'Padding not
allowed'):
+ b32hexdecode(data, padded=False)
+ self.assertEqual(b32hexdecode(data, padded=False,
ignorechars=b'='),
+ expected)
+ data = data.replace(b'=', b'')
+ self.assertEqual(b32hexdecode(data, padded=False), expected)
+
+ check(b'', b'')
+ check(b'C4======', b'a')
+ check(b'C5H0====', b'ab')
+ check(b'C5H66===', b'abc')
+ check(b'C5H66P0=', b'abcd')
+ check(b'C5H66P35', b'abcde')
+ check(b'C=5H66P35', b'abcde')
+ check(b'C5=H66P35', b'abcde')
+ check(b'C5H=66P35', b'abcde')
+ check(b'C5H6=6P35', b'abcde')
+ check(b'C5H66=P35', b'abcde')
+ check(b'C5H66P=35', b'abcde')
+ check(b'C5H66P3=5', b'abcde')
+
def test_b32hexdecode_ignorechars(self):
self._common_test_ignorechars(base64.b32hexdecode)
eq = self.assertEqual
diff --git a/Lib/test/test_binascii.py b/Lib/test/test_binascii.py
index c8dbf3fec40bb7..81cdacb96241e2 100644
--- a/Lib/test/test_binascii.py
+++ b/Lib/test/test_binascii.py
@@ -233,6 +233,28 @@ def assertInvalidLength(data, *args, length=None,
**kwargs):
assertExcessPadding(b'abcd====efgh', b'i\xb7\x1dy\xf8!')
assertExcessPadding(b'abcd=====efgh', b'i\xb7\x1dy\xf8!')
+ def test_a2b_base64_padded(self):
+ a2b_base64 = binascii.a2b_base64
+ t = self.type2test
+ def check(data, expected):
+ if b'=' in data:
+ with self.assertRaisesRegex(binascii.Error, 'Padding not
allowed'):
+ a2b_base64(t(data), padded=False, strict_mode=True)
+ self.assertEqual(a2b_base64(t(data), padded=False,
ignorechars=b'='),
+ expected)
+ data = data.replace(b'=', b'')
+ self.assertEqual(a2b_base64(t(data), padded=False), expected)
+ self.assertEqual(a2b_base64(t(data), padded=False,
strict_mode=True),
+ expected)
+
+ check(b'', b'')
+ check(b'YQ==', b'a')
+ check(b'YWI=', b'ab')
+ check(b'YWJj', b'abc')
+ check(b'Y=WJj', b'abc')
+ check(b'YW=Jj', b'abc')
+ check(b'YWJ=j', b'abc')
+
def _common_test_ignorechars(self, func):
eq = self.assertEqual
empty = self.type2test(b'')
@@ -913,6 +935,42 @@ def assertInvalidLength(data, *args, length=None,
**kwargs):
assertInvalidLength(b" ABC=====", ignorechars=b' ')
assertInvalidLength(b" ABCDEF==", ignorechars=b' ')
+ def test_a2b_base32_padded(self):
+ a2b_base32 = binascii.a2b_base32
+ t = self.type2test
+ def check(data, expected):
+ if b'=' in data:
+ with self.assertRaisesRegex(binascii.Error, 'Padding not
allowed'):
+ a2b_base32(t(data), padded=False)
+ self.assertEqual(a2b_base32(t(data), padded=False,
ignorechars=b'='),
+ expected)
+ data = data.replace(b'=', b'')
+ self.assertEqual(a2b_base32(t(data), padded=False), expected)
+
+ check(b'', b'')
+ check(b'ME======', b'a')
+ check(b'MFRA====', b'ab')
+ check(b'MFRGG===', b'abc')
+ check(b'MFRGGZA=', b'abcd')
+ check(b'MFRGGZDF', b'abcde')
+ check(b'M=FRGGZDF', b'abcde')
+ check(b'MF=RGGZDF', b'abcde')
+ check(b'MFR=GGZDF', b'abcde')
+ check(b'MFRG=GZDF', b'abcde')
+ check(b'MFRGG=ZDF', b'abcde')
+ check(b'MFRGGZ=DF', b'abcde')
+ check(b'MFRGGZD=F', b'abcde')
+
+ def test_b2a_base32_padded(self):
+ b2a_base32 = binascii.b2a_base32
+ t = self.type2test
+ self.assertEqual(b2a_base32(t(b''), padded=False), b'')
+ self.assertEqual(b2a_base32(t(b'a'), padded=False), b'ME')
+ self.assertEqual(b2a_base32(t(b'ab'), padded=False), b'MFRA')
+ self.assertEqual(b2a_base32(t(b'abc'), padded=False), b'MFRGG')
+ self.assertEqual(b2a_base32(t(b'abcd'), padded=False), b'MFRGGZA')
+ self.assertEqual(b2a_base32(t(b'abcde'), padded=False), b'MFRGGZDF')
+
def test_base32_wrapcol(self):
self._common_test_wrapcol(binascii.b2a_base32)
b = self.type2test(b'www.python.org')
@@ -1255,6 +1313,14 @@ def test_b2a_base64_newline(self):
self.assertEqual(binascii.b2a_base64(b, newline=True), b'\n')
self.assertEqual(binascii.b2a_base64(b, newline=False), b'')
+ def test_b2a_base64_padded(self):
+ b2a_base64 = binascii.b2a_base64
+ t = self.type2test
+ self.assertEqual(b2a_base64(t(b''), padded=False), b'\n')
+ self.assertEqual(b2a_base64(t(b'a'), padded=False), b'YQ\n')
+ self.assertEqual(b2a_base64(t(b'ab'), padded=False), b'YWI\n')
+ self.assertEqual(b2a_base64(t(b'abc'), padded=False), b'YWJj\n')
+
def test_b2a_base64_wrapcol(self):
self._common_test_wrapcol(binascii.b2a_base64)
b = self.type2test(b'www.python.org')
diff --git
a/Misc/NEWS.d/next/Library/2026-04-01-18-17-55.gh-issue-73613.PLEebm.rst
b/Misc/NEWS.d/next/Library/2026-04-01-18-17-55.gh-issue-73613.PLEebm.rst
new file mode 100644
index 00000000000000..8c50972d3ca45a
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-04-01-18-17-55.gh-issue-73613.PLEebm.rst
@@ -0,0 +1,7 @@
+Add the *padded* parameter in functions related to Base32 and Base64 codecs
+in the :mod:`binascii` and :mod:`base64` modules.
+In the encoding functions it controls whether the pad character can be added
+in the output, in the decoding functions it controls whether padding is
+required in input.
+Padding of input no longer required in :func:`base64.urlsafe_b64decode`
+by default.
diff --git a/Modules/binascii.c b/Modules/binascii.c
index d0ef2d2d2cc4ce..9193137877aef9 100644
--- a/Modules/binascii.c
+++ b/Modules/binascii.c
@@ -723,6 +723,8 @@ 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.
+ padded: bool = True
+ When set to false, padding in input is not required.
alphabet: PyBytesObject(c_default="NULL") = BASE64_ALPHABET
ignorechars: Py_buffer = NULL
A byte string containing characters to ignore from the input when
@@ -733,8 +735,9 @@ Decode a line of base64 data.
static PyObject *
binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
- PyBytesObject *alphabet, Py_buffer *ignorechars)
-/*[clinic end generated code: output=72f15fcc0681d666 input=195c8d60b03aaa6f]*/
+ int padded, PyBytesObject *alphabet,
+ Py_buffer *ignorechars)
+/*[clinic end generated code: output=525d840a299ff132 input=74a53dd3b23474b3]*/
{
assert(data->len >= 0);
@@ -798,7 +801,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data,
int strict_mode,
/* Check for pad sequences and ignore
** the invalid ones.
*/
- if (this_ch == BASE64_PAD) {
+ if (padded && this_ch == BASE64_PAD) {
pads++;
if (quad_pos >= 2 && quad_pos + pads <= 4) {
continue;
@@ -831,7 +834,10 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer
*data, int strict_mode,
if (strict_mode && !ignorechar(this_ch, ignorechars, ignorecache))
{
state = get_binascii_state(module);
if (state) {
- PyErr_SetString(state->Error, "Only base64 data is
allowed");
+ PyErr_SetString(state->Error,
+ (this_ch == BASE64_PAD)
+ ? "Padding not allowed"
+ : "Only base64 data is allowed");
}
goto error_end;
}
@@ -895,7 +901,7 @@ binascii_a2b_base64_impl(PyObject *module, Py_buffer *data,
int strict_mode,
goto error_end;
}
- if (quad_pos != 0 && quad_pos + pads < 4) {
+ if (padded && quad_pos != 0 && quad_pos + pads < 4) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Incorrect padding");
@@ -919,6 +925,8 @@ binascii.b2a_base64
data: Py_buffer
/
*
+ padded: bool = True
+ When set to false, omit padding in the output.
wrapcol: size_t = 0
newline: bool = True
alphabet: Py_buffer(c_default="{NULL, NULL}") = BASE64_ALPHABET
@@ -927,9 +935,9 @@ 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, Py_buffer *alphabet)
-/*[clinic end generated code: output=9d9657e5fbe28c64 input=ffa3af8520c312ac]*/
+binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, int padded,
+ size_t wrapcol, int newline, Py_buffer *alphabet)
+/*[clinic end generated code: output=a2057b906dc201ab input=cfa33ad73051d3f7]*/
{
const unsigned char *table_b2a = table_b2a_base64;
const unsigned char *bin_data = data->buf;
@@ -950,6 +958,11 @@ binascii_b2a_base64_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
* Use unsigned integer arithmetic to avoid signed integer overflow.
*/
size_t out_len = ((size_t)bin_len + 2u) / 3u * 4u;
+ unsigned int pads = (3 - (bin_len % 3)) % 3 * 4 / 3;
+ if (!padded) {
+ out_len -= pads;
+ pads = 0;
+ }
if (wrapcol && out_len) {
/* Each line should encode a whole number of bytes. */
wrapcol = wrapcol < 4 ? 4 : wrapcol / 4 * 4;
@@ -982,18 +995,23 @@ binascii_b2a_base64_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
/* Handle remaining 0-2 bytes */
if (bin_len == 1) {
/* 1 byte remaining: produces 2 base64 chars + 2 padding */
+ assert(!padded || pads == 2);
unsigned int val = bin_data[0];
*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 */
+ assert(!padded || pads == 1);
unsigned int val = ((unsigned int)bin_data[0] << 8) | bin_data[1];
*ascii_data++ = table_b2a[(val >> 10) & 0x3f];
*ascii_data++ = table_b2a[(val >> 4) & 0x3f];
*ascii_data++ = table_b2a[(val << 2) & 0x3f];
+ }
+ else {
+ assert(pads == 0);
+ }
+ for (; pads; pads--) {
*ascii_data++ = BASE64_PAD;
}
@@ -1512,6 +1530,8 @@ binascii.a2b_base32
data: ascii_buffer
/
*
+ padded: bool = True
+ When set to false, padding in input is not required.
alphabet: PyBytesObject(c_default="NULL") = BASE32_ALPHABET
ignorechars: Py_buffer = b''
A byte string containing characters to ignore from the input.
@@ -1520,9 +1540,9 @@ Decode a line of base32 data.
[clinic start generated code]*/
static PyObject *
-binascii_a2b_base32_impl(PyObject *module, Py_buffer *data,
+binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded,
PyBytesObject *alphabet, Py_buffer *ignorechars)
-/*[clinic end generated code: output=2cf7c8c9e6e98b88 input=b0333508aad1b3ac]*/
+/*[clinic end generated code: output=7dbbaa816d956b1c input=07a3721acdf9b688]*/
{
const unsigned char *ascii_data = data->buf;
Py_ssize_t ascii_len = data->len;
@@ -1581,7 +1601,7 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer
*data,
unsigned char this_ch = *ascii_data;
/* Check for pad sequences. They may only occur at certain positions.
*/
- if (this_ch == BASE32_PAD) {
+ if (padded && this_ch == BASE32_PAD) {
pads++;
if ((octa_pos == 2 || octa_pos == 4 || octa_pos == 5 || octa_pos
== 7)
@@ -1617,7 +1637,10 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer
*data,
if (!ignorechar(this_ch, ignorechars, ignorecache)) {
state = get_binascii_state(module);
if (state) {
- PyErr_SetString(state->Error, "Only base32 data is
allowed");
+ PyErr_SetString(state->Error,
+ (this_ch == BASE32_PAD)
+ ? "Padding not allowed"
+ : "Only base32 data is allowed");
}
goto error;
}
@@ -1692,7 +1715,7 @@ binascii_a2b_base32_impl(PyObject *module, Py_buffer
*data,
goto error;
}
- if (octa_pos != 0 && octa_pos + pads < 8) {
+ if (padded && octa_pos != 0 && octa_pos + pads < 8) {
state = get_binascii_state(module);
if (state) {
PyErr_SetString(state->Error, "Incorrect padding");
@@ -1715,6 +1738,8 @@ binascii.b2a_base32
data: Py_buffer
/
*
+ padded: bool = True
+ When set to false, omit padding in the output.
wrapcol: size_t = 0
alphabet: Py_buffer(c_default="{NULL, NULL}") = BASE32_ALPHABET
@@ -1722,9 +1747,9 @@ Base32-code line of data.
[clinic start generated code]*/
static PyObject *
-binascii_b2a_base32_impl(PyObject *module, Py_buffer *data, size_t wrapcol,
- Py_buffer *alphabet)
-/*[clinic end generated code: output=d41fafbdaf29e280 input=a3d93b73836f2879]*/
+binascii_b2a_base32_impl(PyObject *module, Py_buffer *data, int padded,
+ size_t wrapcol, Py_buffer *alphabet)
+/*[clinic end generated code: output=acc09e685569aab9 input=1889b0c497a1d3c2]*/
{
const unsigned char *table_b2a = table_b2a_base32;
const unsigned char *bin_data = data->buf;
@@ -1746,6 +1771,11 @@ binascii_b2a_base32_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
* Use unsigned integer arithmetic to avoid signed integer overflow.
*/
size_t ascii_len = ((size_t)bin_len + 4u) / 5u * 8u;
+ unsigned int pads = (5 - (bin_len % 5)) % 5 * 8 / 5;
+ if (!padded) {
+ ascii_len -= pads;
+ pads = 0;
+ }
if (wrapcol && ascii_len) {
/* Each line should encode a whole number of bytes. */
wrapcol = wrapcol < 8 ? 8 : wrapcol / 8 * 8;
@@ -1774,30 +1804,23 @@ binascii_b2a_base32_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
/* Handle the remaining 0-4 bytes. */
if (bin_len == 1) {
/* 1 byte remaining: produces 2 encoded + 6 padding chars. */
+ assert(!padded || pads == 6);
uint32_t val = bin_data[0];
*ascii_data++ = table_b2a[(val >> 3) & 0x1f];
*ascii_data++ = table_b2a[(val << 2) & 0x1f];
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
}
else if (bin_len == 2) {
/* 2 bytes remaining: produces 4 encoded + 4 padding chars. */
+ assert(!padded || pads == 4);
uint32_t val = ((uint32_t)bin_data[0] << 8) | bin_data[1];
*ascii_data++ = table_b2a[(val >> 11) & 0x1f];
*ascii_data++ = table_b2a[(val >> 6) & 0x1f];
*ascii_data++ = table_b2a[(val >> 1) & 0x1f];
*ascii_data++ = table_b2a[(val << 4) & 0x1f];
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
}
else if (bin_len == 3) {
/* 3 bytes remaining: produces 5 encoded + 3 padding chars. */
+ assert(!padded || pads == 3);
uint32_t val = ((uint32_t)bin_data[0] << 16)
| ((uint32_t)bin_data[1] << 8)
| bin_data[2];
@@ -1806,12 +1829,10 @@ binascii_b2a_base32_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
*ascii_data++ = table_b2a[(val >> 9) & 0x1f];
*ascii_data++ = table_b2a[(val >> 4) & 0x1f];
*ascii_data++ = table_b2a[(val << 1) & 0x1f];
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
- *ascii_data++ = BASE32_PAD;
}
else if (bin_len == 4) {
/* 4 bytes remaining: produces 7 encoded + 1 padding chars. */
+ assert(!padded || pads == 1);
uint32_t val = ((uint32_t)bin_data[0] << 24)
| ((uint32_t)bin_data[1] << 16)
| ((uint32_t)bin_data[2] << 8)
@@ -1823,6 +1844,11 @@ binascii_b2a_base32_impl(PyObject *module, Py_buffer
*data, size_t wrapcol,
*ascii_data++ = table_b2a[(val >> 7) & 0x1f];
*ascii_data++ = table_b2a[(val >> 2) & 0x1f];
*ascii_data++ = table_b2a[(val << 3) & 0x1f];
+ }
+ else {
+ assert(pads == 0);
+ }
+ for (; pads; pads--) {
*ascii_data++ = BASE32_PAD;
}
diff --git a/Modules/clinic/binascii.c.h b/Modules/clinic/binascii.c.h
index d27a65997244bc..0a2d33c428d10a 100644
--- a/Modules/clinic/binascii.c.h
+++ b/Modules/clinic/binascii.c.h
@@ -118,7 +118,8 @@ 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=<unrepresentable>,\n"
-" alphabet=BASE64_ALPHABET, ignorechars=<unrepresentable>)\n"
+" padded=True, alphabet=BASE64_ALPHABET,\n"
+" ignorechars=<unrepresentable>)\n"
"--\n"
"\n"
"Decode a line of base64 data.\n"
@@ -127,6 +128,8 @@ PyDoc_STRVAR(binascii_a2b_base64__doc__,
" When set to true, bytes that are not part of the base64 standard are\n"
" not allowed. The same applies to excess data after padding (= / ==).\n"
" Set to True by default if ignorechars is specified, False otherwise.\n"
+" padded\n"
+" When set to false, padding in input is not required.\n"
" ignorechars\n"
" A byte string containing characters to ignore from the input when\n"
" strict_mode is true.");
@@ -136,7 +139,8 @@ PyDoc_STRVAR(binascii_a2b_base64__doc__,
static PyObject *
binascii_a2b_base64_impl(PyObject *module, Py_buffer *data, int strict_mode,
- PyBytesObject *alphabet, Py_buffer *ignorechars);
+ int padded, PyBytesObject *alphabet,
+ Py_buffer *ignorechars);
static PyObject *
binascii_a2b_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
@@ -144,7 +148,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 3
+ #define NUM_KEYWORDS 4
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -153,7 +157,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(alphabet),
&_Py_ID(ignorechars), },
+ .ob_item = { &_Py_ID(strict_mode), &_Py_ID(padded), &_Py_ID(alphabet),
&_Py_ID(ignorechars), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -162,17 +166,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", "alphabet",
"ignorechars", NULL};
+ static const char * const _keywords[] = {"", "strict_mode", "padded",
"alphabet", "ignorechars", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "a2b_base64",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[4];
+ PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) -
1;
Py_buffer data = {NULL, NULL};
int strict_mode = -1;
+ int padded = 1;
PyBytesObject *alphabet = NULL;
Py_buffer ignorechars = {NULL, NULL};
@@ -197,20 +202,29 @@ binascii_a2b_base64(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
}
}
if (args[2]) {
- if (!PyBytes_Check(args[2])) {
- _PyArg_BadArgument("a2b_base64", "argument 'alphabet'", "bytes",
args[2]);
+ padded = PyObject_IsTrue(args[2]);
+ if (padded < 0) {
goto exit;
}
- alphabet = (PyBytesObject *)args[2];
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
- if (PyObject_GetBuffer(args[3], &ignorechars, PyBUF_SIMPLE) != 0) {
+ if (args[3]) {
+ if (!PyBytes_Check(args[3])) {
+ _PyArg_BadArgument("a2b_base64", "argument 'alphabet'", "bytes",
args[3]);
+ goto exit;
+ }
+ alphabet = (PyBytesObject *)args[3];
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (PyObject_GetBuffer(args[4], &ignorechars, PyBUF_SIMPLE) != 0) {
goto exit;
}
skip_optional_kwonly:
- return_value = binascii_a2b_base64_impl(module, &data, strict_mode,
alphabet, &ignorechars);
+ return_value = binascii_a2b_base64_impl(module, &data, strict_mode,
padded, alphabet, &ignorechars);
exit:
/* Cleanup for data */
@@ -225,18 +239,21 @@ 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, /, *, padded=True, wrapcol=0, newline=True,\n"
" alphabet=BASE64_ALPHABET)\n"
"--\n"
"\n"
-"Base64-code line of data.");
+"Base64-code line of data.\n"
+"\n"
+" padded\n"
+" When set to false, omit padding in the output.");
#define BINASCII_B2A_BASE64_METHODDEF \
{"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, size_t wrapcol,
- int newline, Py_buffer *alphabet);
+binascii_b2a_base64_impl(PyObject *module, Py_buffer *data, int padded,
+ size_t wrapcol, int newline, Py_buffer *alphabet);
static PyObject *
binascii_b2a_base64(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
@@ -244,7 +261,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 3
+ #define NUM_KEYWORDS 4
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -253,7 +270,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), &_Py_ID(alphabet), },
+ .ob_item = { &_Py_ID(padded), &_Py_ID(wrapcol), &_Py_ID(newline),
&_Py_ID(alphabet), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -262,16 +279,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[] = {"", "wrapcol", "newline",
"alphabet", NULL};
+ static const char * const _keywords[] = {"", "padded", "wrapcol",
"newline", "alphabet", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "b2a_base64",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[4];
+ PyObject *argsbuf[5];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) -
1;
Py_buffer data = {NULL, NULL};
+ int padded = 1;
size_t wrapcol = 0;
int newline = 1;
Py_buffer alphabet = {NULL, NULL};
@@ -288,7 +306,8 @@ binascii_b2a_base64(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
goto skip_optional_kwonly;
}
if (args[1]) {
- if (!_PyLong_Size_t_Converter(args[1], &wrapcol)) {
+ padded = PyObject_IsTrue(args[1]);
+ if (padded < 0) {
goto exit;
}
if (!--noptargs) {
@@ -296,7 +315,15 @@ binascii_b2a_base64(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
}
}
if (args[2]) {
- newline = PyObject_IsTrue(args[2]);
+ if (!_PyLong_Size_t_Converter(args[2], &wrapcol)) {
+ goto exit;
+ }
+ if (!--noptargs) {
+ goto skip_optional_kwonly;
+ }
+ }
+ if (args[3]) {
+ newline = PyObject_IsTrue(args[3]);
if (newline < 0) {
goto exit;
}
@@ -304,11 +331,11 @@ binascii_b2a_base64(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
goto skip_optional_kwonly;
}
}
- if (PyObject_GetBuffer(args[3], &alphabet, PyBUF_SIMPLE) != 0) {
+ if (PyObject_GetBuffer(args[4], &alphabet, PyBUF_SIMPLE) != 0) {
goto exit;
}
skip_optional_kwonly:
- return_value = binascii_b2a_base64_impl(module, &data, wrapcol, newline,
&alphabet);
+ return_value = binascii_b2a_base64_impl(module, &data, padded, wrapcol,
newline, &alphabet);
exit:
/* Cleanup for data */
@@ -740,12 +767,14 @@ binascii_b2a_base85(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
}
PyDoc_STRVAR(binascii_a2b_base32__doc__,
-"a2b_base32($module, data, /, *, alphabet=BASE32_ALPHABET,\n"
+"a2b_base32($module, data, /, *, padded=True, alphabet=BASE32_ALPHABET,\n"
" ignorechars=b\'\')\n"
"--\n"
"\n"
"Decode a line of base32 data.\n"
"\n"
+" padded\n"
+" When set to false, padding in input is not required.\n"
" ignorechars\n"
" A byte string containing characters to ignore from the input.");
@@ -753,7 +782,7 @@ PyDoc_STRVAR(binascii_a2b_base32__doc__,
{"a2b_base32", _PyCFunction_CAST(binascii_a2b_base32),
METH_FASTCALL|METH_KEYWORDS, binascii_a2b_base32__doc__},
static PyObject *
-binascii_a2b_base32_impl(PyObject *module, Py_buffer *data,
+binascii_a2b_base32_impl(PyObject *module, Py_buffer *data, int padded,
PyBytesObject *alphabet, Py_buffer *ignorechars);
static PyObject *
@@ -762,7 +791,7 @@ binascii_a2b_base32(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
@@ -771,7 +800,7 @@ binascii_a2b_base32(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(alphabet), &_Py_ID(ignorechars), },
+ .ob_item = { &_Py_ID(padded), &_Py_ID(alphabet), &_Py_ID(ignorechars),
},
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -780,16 +809,17 @@ binascii_a2b_base32(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"", "alphabet", "ignorechars",
NULL};
+ static const char * const _keywords[] = {"", "padded", "alphabet",
"ignorechars", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "a2b_base32",
.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 padded = 1;
PyBytesObject *alphabet = NULL;
Py_buffer ignorechars = {.buf = "", .obj = NULL, .len = 0};
@@ -805,20 +835,29 @@ binascii_a2b_base32(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
goto skip_optional_kwonly;
}
if (args[1]) {
- if (!PyBytes_Check(args[1])) {
- _PyArg_BadArgument("a2b_base32", "argument 'alphabet'", "bytes",
args[1]);
+ padded = PyObject_IsTrue(args[1]);
+ if (padded < 0) {
goto exit;
}
- alphabet = (PyBytesObject *)args[1];
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
- if (PyObject_GetBuffer(args[2], &ignorechars, PyBUF_SIMPLE) != 0) {
+ if (args[2]) {
+ if (!PyBytes_Check(args[2])) {
+ _PyArg_BadArgument("a2b_base32", "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_base32_impl(module, &data, alphabet,
&ignorechars);
+ return_value = binascii_a2b_base32_impl(module, &data, padded, alphabet,
&ignorechars);
exit:
/* Cleanup for data */
@@ -833,17 +872,21 @@ binascii_a2b_base32(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
}
PyDoc_STRVAR(binascii_b2a_base32__doc__,
-"b2a_base32($module, data, /, *, wrapcol=0, alphabet=BASE32_ALPHABET)\n"
+"b2a_base32($module, data, /, *, padded=True, wrapcol=0,\n"
+" alphabet=BASE32_ALPHABET)\n"
"--\n"
"\n"
-"Base32-code line of data.");
+"Base32-code line of data.\n"
+"\n"
+" padded\n"
+" When set to false, omit padding in the output.");
#define BINASCII_B2A_BASE32_METHODDEF \
{"b2a_base32", _PyCFunction_CAST(binascii_b2a_base32),
METH_FASTCALL|METH_KEYWORDS, binascii_b2a_base32__doc__},
static PyObject *
-binascii_b2a_base32_impl(PyObject *module, Py_buffer *data, size_t wrapcol,
- Py_buffer *alphabet);
+binascii_b2a_base32_impl(PyObject *module, Py_buffer *data, int padded,
+ size_t wrapcol, Py_buffer *alphabet);
static PyObject *
binascii_b2a_base32(PyObject *module, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
@@ -851,7 +894,7 @@ binascii_b2a_base32(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
@@ -860,7 +903,7 @@ binascii_b2a_base32(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(alphabet), },
+ .ob_item = { &_Py_ID(padded), &_Py_ID(wrapcol), &_Py_ID(alphabet), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -869,16 +912,17 @@ binascii_b2a_base32(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"", "wrapcol", "alphabet", NULL};
+ static const char * const _keywords[] = {"", "padded", "wrapcol",
"alphabet", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "b2a_base32",
.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 padded = 1;
size_t wrapcol = 0;
Py_buffer alphabet = {NULL, NULL};
@@ -894,18 +938,27 @@ binascii_b2a_base32(PyObject *module, PyObject *const
*args, Py_ssize_t nargs, P
goto skip_optional_kwonly;
}
if (args[1]) {
- if (!_PyLong_Size_t_Converter(args[1], &wrapcol)) {
+ padded = PyObject_IsTrue(args[1]);
+ if (padded < 0) {
goto exit;
}
if (!--noptargs) {
goto skip_optional_kwonly;
}
}
- if (PyObject_GetBuffer(args[2], &alphabet, PyBUF_SIMPLE) != 0) {
+ if (args[2]) {
+ if (!_PyLong_Size_t_Converter(args[2], &wrapcol)) {
+ 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_base32_impl(module, &data, wrapcol, &alphabet);
+ return_value = binascii_b2a_base32_impl(module, &data, padded, wrapcol,
&alphabet);
exit:
/* Cleanup for data */
@@ -1581,4 +1634,4 @@ binascii_b2a_qp(PyObject *module, PyObject *const *args,
Py_ssize_t nargs, PyObj
return return_value;
}
-/*[clinic end generated code: output=197a0f70aa392d39 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=2acab1ceb0058b1a input=a9049054013a1b77]*/
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]