]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-73613: Support Base32 and Base64 without padding (GH-147974)
authorSerhiy Storchaka <storchaka@gmail.com>
Sat, 4 Apr 2026 18:26:16 +0000 (21:26 +0300)
committerGitHub <noreply@github.com>
Sat, 4 Apr 2026 18:26:16 +0000 (21:26 +0300)
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.

13 files changed:
Doc/library/base64.rst
Doc/library/binascii.rst
Doc/whatsnew/3.15.rst
Include/internal/pycore_global_objects_fini_generated.h
Include/internal/pycore_global_strings.h
Include/internal/pycore_runtime_init_generated.h
Include/internal/pycore_unicodeobject_generated.h
Lib/base64.py
Lib/test/test_base64.py
Lib/test/test_binascii.py
Misc/NEWS.d/next/Library/2026-04-01-18-17-55.gh-issue-73613.PLEebm.rst [new file with mode: 0644]
Modules/binascii.c
Modules/clinic/binascii.c.h

index 1a1785cb58772e2bd1ca7c86cd0ebcb650ee493c..425dff8f2a9ad10cf273386a11611b0045990705 100644 (file)
@@ -51,7 +51,7 @@ The :rfc:`4648` encodings are suitable for encoding binary data so that it can b
 safely sent by email, used as parts of URLs, or included as part of an HTTP
 POST request.
 
-.. function:: b64encode(s, altchars=None, *, 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)
index 4a82d0742ae9db33ed5a3aa0b0be35d0281ca657..4f2edb7eff8a8f3c11d0452840043fdd95363719 100644 (file)
@@ -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.
index 287109035f1ee63f1a18f2a238de2fa021b42865..d1d4b92bcf4e973b4843913c303348ff47f3f53a 100644 (file)
@@ -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`.)
index 4b1e289c6ff468f90516ca150b67ed79cda10f04..beae65213a27b68d25f38efc0d4d571df05b925f 100644 (file)
@@ -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));
index 6ee649b59a5c375d0cbe6c3037a3334869138740..bb1c6dbaf039067e9b97536c76e6714daff3fa90 100644 (file)
@@ -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)
index 778db946c2a3aabb309990f1a25fdf0cc995ef14..64b029797ab9b3e2d14c233c6f917623f11df8e1 100644 (file)
@@ -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), \
index bd8f50ff0ee73254f179a6df0c4d9378450e8a47..461ee36dcebb6d36f2b5c80d758bbedd054115a6 100644 (file)
@@ -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));
index 47b90643e8da73f5ff3b236e2ec746d14ce3d09f..a94bec4d031c525b148388455699ca7a56b78d71 100644 (file)
@@ -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 @@ Decode the {encoding} encoded bytes-like object or ASCII string 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 @@ the letter O).  For security purposes the default is None, so that
 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''):\r
+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''):\r
+def z85decode(s, *, ignorechars=b''):
     """Decode the z85-encoded bytes-like object or ASCII string b
 
     The result is returned as a bytes object.
index d5f8f44e280b54fddc5be2c79b40e275052c3373..1a4dd56a553f4d3138c7440028f0d2a3f57205a9 100644 (file)
@@ -209,6 +209,25 @@ class BaseXYTestCase(unittest.TestCase):
                                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 @@ class BaseXYTestCase(unittest.TestCase):
         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 @@ class BaseXYTestCase(unittest.TestCase):
         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 @@ class BaseXYTestCase(unittest.TestCase):
             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 @@ class BaseXYTestCase(unittest.TestCase):
         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 @@ class BaseXYTestCase(unittest.TestCase):
         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
index c8dbf3fec40bb745f2c1531bf24dc2846e5fe31f..81cdacb96241e2ba8aab713a94390cf4e8474f4d 100644 (file)
@@ -233,6 +233,28 @@ class BinASCIITest(unittest.TestCase):
         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 @@ class BinASCIITest(unittest.TestCase):
         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 @@ class BinASCIITest(unittest.TestCase):
         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 (file)
index 0000000..8c50972
--- /dev/null
@@ -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.
index d0ef2d2d2cc4ce08b94142da3b893d987195448d..9193137877aef9f7f133f031d207eee524d97cf0 100644 (file)
@@ -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 @@ fastpath:
         /* 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 @@ fastpath:
             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 @@ fastpath:
         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 @@ fastpath:
         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 @@ fastpath:
             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 @@ fastpath:
         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;
     }
 
index d27a65997244bc97db82aad3b5c97f70f791be0c..0a2d33c428d10aa6209f229db85b93ee35e889c3 100644 (file)
@@ -118,7 +118,8 @@ exit:
 
 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 @@ exit:
 }
 
 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 @@ exit:
 }
 
 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 @@ exit:
 }
 
 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 @@ exit:
 
     return return_value;
 }
-/*[clinic end generated code: output=197a0f70aa392d39 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=2acab1ceb0058b1a input=a9049054013a1b77]*/