mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
Align base64 encodings and binascii keywords with Python 3.15 (#9139)
* Align base64 encodings with Python 3.15 Accept hexlify keyword arguments and migrate base64 and its tests directly from CPython v3.15.0 using update_lib. Assisted-by: OpenAI Codex:model version unavailable * Update Hypothesis fallback for multi-argument examples Import the CPython 3.15 final fallback helper with scripts/update_lib migrate. Keep canonical base64 round-trip examples unchanged while passing their positional arguments as one subTest message. Validation: six canonical base64 methods, fourteen fixed examples; bounded source-only runs with the existing same-native PR binary. Assisted-by: OpenAI Codex:model version unavailable
This commit is contained in:
1 parent
8f12a32256
commit
365ff68354
4 files changed
+765
-347
No files matched your search
Vendored
+173
-300
@@ -4,7 +4,6 @@
|
||||
# Modified 30-Dec-2003 by Barry Warsaw to add full RFC 3548 support
|
||||
# Modified 22-May-2007 by Guido van Rossum to use bytes everywhere
|
||||
|
||||
import struct
|
||||
import binascii
|
||||
|
||||
|
||||
@@ -26,6 +25,8 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
_NOT_SPECIFIED = ['NOT SPECIFIED']
|
||||
|
||||
bytes_types = (bytes, bytearray) # Types acceptable as binary data
|
||||
|
||||
def _bytes_from_decode_data(s):
|
||||
@@ -45,44 +46,90 @@ def _bytes_from_decode_data(s):
|
||||
|
||||
# Base64 encoding/decoding uses binascii
|
||||
|
||||
def b64encode(s, altchars=None):
|
||||
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.
|
||||
"""
|
||||
encoded = binascii.b2a_base64(s, newline=False)
|
||||
if altchars is not None:
|
||||
assert len(altchars) == 2, repr(altchars)
|
||||
return encoded.translate(bytes.maketrans(b'+/', altchars))
|
||||
return encoded
|
||||
if len(altchars) != 2:
|
||||
raise ValueError(f'invalid altchars: {altchars!r}')
|
||||
alphabet = binascii.BASE64_ALPHABET[:-2] + altchars
|
||||
return binascii.b2a_base64(s, padded=padded, wrapcol=wrapcol, newline=False,
|
||||
alphabet=alphabet)
|
||||
return binascii.b2a_base64(s, padded=padded, wrapcol=wrapcol, newline=False)
|
||||
|
||||
|
||||
def b64decode(s, altchars=None, validate=False):
|
||||
def b64decode(s, altchars=None, validate=_NOT_SPECIFIED,
|
||||
*, padded=True, ignorechars=_NOT_SPECIFIED, canonical=False):
|
||||
"""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.
|
||||
|
||||
If validate is False (the default), characters that are neither in the
|
||||
normal base-64 alphabet nor the alternative alphabet are discarded prior
|
||||
to the padding check. If validate is True, these non-alphabet characters
|
||||
in the input result in a binascii.Error.
|
||||
If ignorechars is specified, it should be a byte string containing
|
||||
characters to ignore from the input. The default value of validate is
|
||||
True if ignorechars is specified, False otherwise.
|
||||
|
||||
If validate is false, characters that are neither in the normal base-64
|
||||
alphabet nor the alternative alphabet are discarded prior to the
|
||||
padding check. If validate is true, these non-alphabet characters in
|
||||
the input result in a binascii.Error if they are not in ignorechars.
|
||||
For more information about the strict base64 check, see:
|
||||
|
||||
https://docs.python.org/3.11/library/binascii.html#binascii.a2b_base64
|
||||
"""
|
||||
s = _bytes_from_decode_data(s)
|
||||
if validate is _NOT_SPECIFIED:
|
||||
validate = ignorechars is not _NOT_SPECIFIED
|
||||
badchar = None
|
||||
if altchars is not None:
|
||||
altchars = _bytes_from_decode_data(altchars)
|
||||
assert len(altchars) == 2, repr(altchars)
|
||||
s = s.translate(bytes.maketrans(altchars, b'+/'))
|
||||
return binascii.a2b_base64(s, strict_mode=validate)
|
||||
if len(altchars) != 2:
|
||||
raise ValueError(f'invalid altchars: {altchars!r}')
|
||||
if ignorechars is _NOT_SPECIFIED:
|
||||
for b in b'+/':
|
||||
if b not in altchars and b in s:
|
||||
badchar = b
|
||||
break
|
||||
s = s.translate(bytes.maketrans(altchars, b'+/'))
|
||||
else:
|
||||
alphabet = binascii.BASE64_ALPHABET[:-2] + altchars
|
||||
return binascii.a2b_base64(s, strict_mode=validate,
|
||||
alphabet=alphabet,
|
||||
padded=padded, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
if ignorechars is _NOT_SPECIFIED:
|
||||
ignorechars = b''
|
||||
result = binascii.a2b_base64(s, strict_mode=validate,
|
||||
padded=padded, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
if badchar is not None:
|
||||
import warnings
|
||||
if validate:
|
||||
warnings.warn(f'invalid character {chr(badchar)!a} in Base64 data '
|
||||
f'with altchars={altchars!r} and validate=True '
|
||||
f'will be an error in future Python versions',
|
||||
DeprecationWarning, stacklevel=2)
|
||||
else:
|
||||
warnings.warn(f'invalid character {chr(badchar)!a} in Base64 data '
|
||||
f'with altchars={altchars!r} and validate=False '
|
||||
f'will be discarded in future Python versions',
|
||||
FutureWarning, stacklevel=2)
|
||||
return result
|
||||
|
||||
|
||||
def standard_b64encode(s):
|
||||
@@ -103,19 +150,21 @@ def standard_b64decode(s):
|
||||
return b64decode(s)
|
||||
|
||||
|
||||
_urlsafe_encode_translation = bytes.maketrans(b'+/', b'-_')
|
||||
_urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/')
|
||||
|
||||
def urlsafe_b64encode(s):
|
||||
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
|
||||
'/'.
|
||||
"""
|
||||
return b64encode(s).translate(_urlsafe_encode_translation)
|
||||
|
||||
def urlsafe_b64decode(s):
|
||||
If padded is false, omit padding in the output.
|
||||
"""
|
||||
return binascii.b2a_base64(s, padded=padded, newline=False,
|
||||
alphabet=binascii.URLSAFE_BASE64_ALPHABET)
|
||||
|
||||
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
|
||||
@@ -124,23 +173,46 @@ 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)
|
||||
badchar = None
|
||||
for b in b'+/':
|
||||
if b in s:
|
||||
badchar = b
|
||||
break
|
||||
s = s.translate(_urlsafe_decode_translation)
|
||||
return b64decode(s)
|
||||
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 '
|
||||
f'will be discarded in future Python versions',
|
||||
FutureWarning, stacklevel=2)
|
||||
return result
|
||||
|
||||
|
||||
|
||||
# Base32 encoding/decoding must be done in Python
|
||||
_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.
|
||||
'''
|
||||
_B32_DECODE_DOCSTRING = '''
|
||||
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}
|
||||
The result is returned as a bytes object. A binascii.Error is raised if
|
||||
the input is incorrectly padded or if there are non-alphabet
|
||||
@@ -155,108 +227,41 @@ mapped to (when map01 is not None, the 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.
|
||||
'''
|
||||
_b32alphabet = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
_b32hexalphabet = b'0123456789ABCDEFGHIJKLMNOPQRSTUV'
|
||||
_b32tab2 = {}
|
||||
_b32rev = {}
|
||||
|
||||
def _b32encode(alphabet, s):
|
||||
# Delay the initialization of the table to not waste memory
|
||||
# if the function is never called
|
||||
if alphabet not in _b32tab2:
|
||||
b32tab = [bytes((i,)) for i in alphabet]
|
||||
_b32tab2[alphabet] = [a + b for a in b32tab for b in b32tab]
|
||||
b32tab = None
|
||||
def b32encode(s, *, padded=True, wrapcol=0):
|
||||
return binascii.b2a_base32(s, padded=padded, wrapcol=wrapcol)
|
||||
b32encode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32')
|
||||
|
||||
if not isinstance(s, bytes_types):
|
||||
s = memoryview(s).tobytes()
|
||||
leftover = len(s) % 5
|
||||
# Pad the last quantum with zero bits if necessary
|
||||
if leftover:
|
||||
s = s + b'\0' * (5 - leftover) # Don't use += !
|
||||
encoded = bytearray()
|
||||
from_bytes = int.from_bytes
|
||||
b32tab2 = _b32tab2[alphabet]
|
||||
for i in range(0, len(s), 5):
|
||||
c = from_bytes(s[i: i + 5]) # big endian
|
||||
encoded += (b32tab2[c >> 30] + # bits 1 - 10
|
||||
b32tab2[(c >> 20) & 0x3ff] + # bits 11 - 20
|
||||
b32tab2[(c >> 10) & 0x3ff] + # bits 21 - 30
|
||||
b32tab2[c & 0x3ff] # bits 31 - 40
|
||||
)
|
||||
# Adjust for any leftover partial quanta
|
||||
if leftover == 1:
|
||||
encoded[-6:] = b'======'
|
||||
elif leftover == 2:
|
||||
encoded[-4:] = b'===='
|
||||
elif leftover == 3:
|
||||
encoded[-3:] = b'==='
|
||||
elif leftover == 4:
|
||||
encoded[-1:] = b'='
|
||||
return bytes(encoded)
|
||||
|
||||
def _b32decode(alphabet, s, casefold=False, map01=None):
|
||||
# Delay the initialization of the table to not waste memory
|
||||
# if the function is never called
|
||||
if alphabet not in _b32rev:
|
||||
_b32rev[alphabet] = {v: k for k, v in enumerate(alphabet)}
|
||||
def b32decode(s, casefold=False, map01=None, *, padded=True, ignorechars=b'',
|
||||
canonical=False):
|
||||
s = _bytes_from_decode_data(s)
|
||||
if len(s) % 8:
|
||||
raise binascii.Error('Incorrect padding')
|
||||
# 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
|
||||
# either L (el) or I (eye).
|
||||
if map01 is not None:
|
||||
map01 = _bytes_from_decode_data(map01)
|
||||
assert len(map01) == 1, repr(map01)
|
||||
s = s.translate(bytes.maketrans(b'01', b'O' + map01))
|
||||
if casefold:
|
||||
s = s.upper()
|
||||
# Strip off pad characters from the right. We need to count the pad
|
||||
# characters because this will tell us how many null bytes to remove from
|
||||
# the end of the decoded string.
|
||||
l = len(s)
|
||||
s = s.rstrip(b'=')
|
||||
padchars = l - len(s)
|
||||
# Now decode the full quanta
|
||||
decoded = bytearray()
|
||||
b32rev = _b32rev[alphabet]
|
||||
for i in range(0, len(s), 8):
|
||||
quanta = s[i: i + 8]
|
||||
acc = 0
|
||||
try:
|
||||
for c in quanta:
|
||||
acc = (acc << 5) + b32rev[c]
|
||||
except KeyError:
|
||||
raise binascii.Error('Non-base32 digit found') from None
|
||||
decoded += acc.to_bytes(5) # big endian
|
||||
# Process the last, partial quanta
|
||||
if l % 8 or padchars not in {0, 1, 3, 4, 6}:
|
||||
raise binascii.Error('Incorrect padding')
|
||||
if padchars and decoded:
|
||||
acc <<= 5 * padchars
|
||||
last = acc.to_bytes(5) # big endian
|
||||
leftover = (43 - 5 * padchars) // 8 # 1: 4, 3: 3, 4: 2, 6: 1
|
||||
decoded[-5:] = last[:leftover]
|
||||
return bytes(decoded)
|
||||
|
||||
|
||||
def b32encode(s):
|
||||
return _b32encode(_b32alphabet, s)
|
||||
b32encode.__doc__ = _B32_ENCODE_DOCSTRING.format(encoding='base32')
|
||||
|
||||
def b32decode(s, casefold=False, map01=None):
|
||||
return _b32decode(_b32alphabet, s, casefold, map01)
|
||||
return binascii.a2b_base32(s, padded=padded, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
b32decode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32',
|
||||
extra_args=_B32_DECODE_MAP01_DOCSTRING)
|
||||
|
||||
def b32hexencode(s):
|
||||
return _b32encode(_b32hexalphabet, s)
|
||||
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):
|
||||
def b32hexdecode(s, casefold=False, *, padded=True, ignorechars=b'',
|
||||
canonical=False):
|
||||
s = _bytes_from_decode_data(s)
|
||||
# base32hex does not have the 01 mapping
|
||||
return _b32decode(_b32hexalphabet, s, casefold)
|
||||
if casefold:
|
||||
s = s.upper()
|
||||
return binascii.a2b_base32(s, alphabet=binascii.BASE32HEX_ALPHABET,
|
||||
padded=padded, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
b32hexdecode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32hex',
|
||||
extra_args='')
|
||||
|
||||
@@ -264,62 +269,47 @@ b32hexdecode.__doc__ = _B32_DECODE_DOCSTRING.format(encoding='base32hex',
|
||||
# RFC 4648, Base 16 Alphabet specifies uppercase, but hexlify() returns
|
||||
# lowercase. The RFC also recommends against accepting input case
|
||||
# insensitively.
|
||||
def b16encode(s):
|
||||
def b16encode(s, *, wrapcol=0):
|
||||
"""Encode the bytes-like object s using Base16 and return a bytes object.
|
||||
|
||||
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
|
||||
every wrapcol characters.
|
||||
"""
|
||||
return binascii.hexlify(s).upper()
|
||||
if not wrapcol:
|
||||
return binascii.hexlify(s).upper()
|
||||
if wrapcol < 0:
|
||||
raise ValueError('Negative wrapcol')
|
||||
if wrapcol < 2:
|
||||
wrapcol = 2
|
||||
return binascii.hexlify(s, bytes_per_sep=-(wrapcol//2), sep=b'\n').upper()
|
||||
|
||||
|
||||
def b16decode(s, casefold=False):
|
||||
def b16decode(s, casefold=False, *, ignorechars=b''):
|
||||
"""Decode the Base16 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.
|
||||
|
||||
ignorechars should be a byte string containing characters to ignore
|
||||
from the input.
|
||||
|
||||
The result is returned as a bytes object. A binascii.Error is raised if
|
||||
s is incorrectly padded or if there are non-alphabet characters present
|
||||
in the input.
|
||||
"""
|
||||
s = _bytes_from_decode_data(s)
|
||||
if casefold:
|
||||
s = s.upper()
|
||||
if s.translate(None, delete=b'0123456789ABCDEF'):
|
||||
raise binascii.Error('Non-base16 digit found')
|
||||
return binascii.unhexlify(s)
|
||||
if not casefold:
|
||||
s = _bytes_from_decode_data(s)
|
||||
if not isinstance(ignorechars, bytes):
|
||||
ignorechars = bytes(memoryview(ignorechars))
|
||||
for b in b'abcdef':
|
||||
if b in s and b not in ignorechars:
|
||||
raise binascii.Error('Non-base16 digit found')
|
||||
s = s.translate(None, delete=b'abcdef')
|
||||
return binascii.unhexlify(s, ignorechars=ignorechars)
|
||||
|
||||
#
|
||||
# Ascii85 encoding/decoding
|
||||
#
|
||||
|
||||
_a85chars = None
|
||||
_a85chars2 = None
|
||||
_A85START = b"<~"
|
||||
_A85END = b"~>"
|
||||
|
||||
def _85encode(b, chars, chars2, pad=False, foldnuls=False, foldspaces=False):
|
||||
# Helper function for a85encode and b85encode
|
||||
if not isinstance(b, bytes_types):
|
||||
b = memoryview(b).tobytes()
|
||||
|
||||
padding = (-len(b)) % 4
|
||||
if padding:
|
||||
b = b + b'\0' * padding
|
||||
words = struct.Struct('!%dI' % (len(b) // 4)).unpack(b)
|
||||
|
||||
chunks = [b'z' if foldnuls and not word else
|
||||
b'y' if foldspaces and word == 0x20202020 else
|
||||
(chars2[word // 614125] +
|
||||
chars2[word // 85 % 7225] +
|
||||
chars[word % 85])
|
||||
for word in words]
|
||||
|
||||
if padding and not pad:
|
||||
if chunks[-1] == b'z':
|
||||
chunks[-1] = chars[0] * 5
|
||||
chunks[-1] = chunks[-1][:-padding]
|
||||
|
||||
return b''.join(chunks)
|
||||
|
||||
def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
|
||||
"""Encode bytes-like object b using Ascii85 and return a bytes object.
|
||||
|
||||
@@ -340,31 +330,11 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False):
|
||||
with ~>, they must not use a leading <~.
|
||||
|
||||
"""
|
||||
global _a85chars, _a85chars2
|
||||
# Delay the initialization of tables to not waste memory
|
||||
# if the function is never called
|
||||
if _a85chars2 is None:
|
||||
_a85chars = [bytes((i,)) for i in range(33, 118)]
|
||||
_a85chars2 = [(a + b) for a in _a85chars for b in _a85chars]
|
||||
return binascii.b2a_ascii85(b, foldspaces=foldspaces,
|
||||
adobe=adobe, wrapcol=wrapcol, pad=pad)
|
||||
|
||||
result = _85encode(b, _a85chars, _a85chars2, pad, True, foldspaces)
|
||||
|
||||
if adobe:
|
||||
result = _A85START + result
|
||||
if wrapcol:
|
||||
wrapcol = max(2 if adobe else 1, wrapcol)
|
||||
chunks = [result[i: i + wrapcol]
|
||||
for i in range(0, len(result), wrapcol)]
|
||||
if adobe:
|
||||
if len(chunks[-1]) + 2 > wrapcol:
|
||||
chunks.append(b'')
|
||||
result = b'\n'.join(chunks)
|
||||
if adobe:
|
||||
result += _A85END
|
||||
|
||||
return result
|
||||
|
||||
def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
|
||||
def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v',
|
||||
canonical=False):
|
||||
"""Decode the Ascii85 encoded bytes-like object or ASCII string b.
|
||||
|
||||
foldspaces is a flag that specifies whether the 'y' short sequence
|
||||
@@ -380,156 +350,60 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'):
|
||||
input. This should only contain whitespace characters, and by default
|
||||
contains all whitespace characters in ASCII.
|
||||
|
||||
If canonical is true, non-canonical encodings are rejected.
|
||||
|
||||
The result is returned as a bytes object.
|
||||
"""
|
||||
b = _bytes_from_decode_data(b)
|
||||
if adobe:
|
||||
if not b.endswith(_A85END):
|
||||
raise ValueError(
|
||||
"Ascii85 encoded byte sequences must end "
|
||||
"with {!r}".format(_A85END)
|
||||
)
|
||||
if b.startswith(_A85START):
|
||||
b = b[2:-2] # Strip off start/end markers
|
||||
else:
|
||||
b = b[:-2]
|
||||
#
|
||||
# We have to go through this stepwise, so as to ignore spaces and handle
|
||||
# special short sequences
|
||||
#
|
||||
packI = struct.Struct('!I').pack
|
||||
decoded = []
|
||||
decoded_append = decoded.append
|
||||
curr = []
|
||||
curr_append = curr.append
|
||||
curr_clear = curr.clear
|
||||
for x in b + b'u' * 4:
|
||||
if b'!'[0] <= x <= b'u'[0]:
|
||||
curr_append(x)
|
||||
if len(curr) == 5:
|
||||
acc = 0
|
||||
for x in curr:
|
||||
acc = 85 * acc + (x - 33)
|
||||
try:
|
||||
decoded_append(packI(acc))
|
||||
except struct.error:
|
||||
raise ValueError('Ascii85 overflow') from None
|
||||
curr_clear()
|
||||
elif x == b'z'[0]:
|
||||
if curr:
|
||||
raise ValueError('z inside Ascii85 5-tuple')
|
||||
decoded_append(b'\0\0\0\0')
|
||||
elif foldspaces and x == b'y'[0]:
|
||||
if curr:
|
||||
raise ValueError('y inside Ascii85 5-tuple')
|
||||
decoded_append(b'\x20\x20\x20\x20')
|
||||
elif x in ignorechars:
|
||||
# Skip whitespace
|
||||
continue
|
||||
else:
|
||||
raise ValueError('Non-Ascii85 digit found: %c' % x)
|
||||
return binascii.a2b_ascii85(b, foldspaces=foldspaces,
|
||||
adobe=adobe, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
|
||||
result = b''.join(decoded)
|
||||
padding = 4 - len(curr)
|
||||
if padding:
|
||||
# Throw away the extra padding
|
||||
result = result[:-padding]
|
||||
return result
|
||||
|
||||
# The following code is originally taken (with permission) from Mercurial
|
||||
|
||||
_b85alphabet = (b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
b"abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~")
|
||||
_b85chars = None
|
||||
_b85chars2 = None
|
||||
_b85dec = None
|
||||
|
||||
def b85encode(b, pad=False):
|
||||
def b85encode(b, pad=False, *, wrapcol=0):
|
||||
"""Encode bytes-like object b in base85 format and return a bytes object.
|
||||
|
||||
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
|
||||
every wrapcol characters.
|
||||
|
||||
The input is padded with b'\0' so its length is a multiple of 4
|
||||
bytes before encoding. If pad is true, all the resulting
|
||||
characters are retained in the output, which will always be a
|
||||
multiple of 5 bytes.
|
||||
"""
|
||||
global _b85chars, _b85chars2
|
||||
# Delay the initialization of tables to not waste memory
|
||||
# if the function is never called
|
||||
if _b85chars2 is None:
|
||||
_b85chars = [bytes((i,)) for i in _b85alphabet]
|
||||
_b85chars2 = [(a + b) for a in _b85chars for b in _b85chars]
|
||||
return _85encode(b, _b85chars, _b85chars2, pad)
|
||||
return binascii.b2a_base85(b, wrapcol=wrapcol, pad=pad)
|
||||
|
||||
def b85decode(b):
|
||||
def b85decode(b, *, ignorechars=b'', canonical=False):
|
||||
"""Decode the base85-encoded bytes-like object or ASCII string b
|
||||
|
||||
If canonical is true, non-canonical encodings are rejected.
|
||||
|
||||
The result is returned as a bytes object.
|
||||
"""
|
||||
global _b85dec
|
||||
# Delay the initialization of tables to not waste memory
|
||||
# if the function is never called
|
||||
if _b85dec is None:
|
||||
# we don't assign to _b85dec directly to avoid issues when
|
||||
# multiple threads call this function simultaneously
|
||||
b85dec_tmp = [None] * 256
|
||||
for i, c in enumerate(_b85alphabet):
|
||||
b85dec_tmp[c] = i
|
||||
_b85dec = b85dec_tmp
|
||||
return binascii.a2b_base85(b, ignorechars=ignorechars,
|
||||
canonical=canonical)
|
||||
|
||||
b = _bytes_from_decode_data(b)
|
||||
padding = (-len(b)) % 5
|
||||
b = b + b'~' * padding
|
||||
out = []
|
||||
packI = struct.Struct('!I').pack
|
||||
for i in range(0, len(b), 5):
|
||||
chunk = b[i:i + 5]
|
||||
acc = 0
|
||||
try:
|
||||
for c in chunk:
|
||||
acc = acc * 85 + _b85dec[c]
|
||||
except TypeError:
|
||||
for j, c in enumerate(chunk):
|
||||
if _b85dec[c] is None:
|
||||
raise ValueError('bad base85 character at position %d'
|
||||
% (i + j)) from None
|
||||
raise
|
||||
try:
|
||||
out.append(packI(acc))
|
||||
except struct.error:
|
||||
raise ValueError('base85 overflow in hunk starting at byte %d'
|
||||
% i) from None
|
||||
def z85encode(s, pad=False, *, wrapcol=0):
|
||||
"""Encode bytes-like object b in z85 format and return a bytes object.
|
||||
|
||||
result = b''.join(out)
|
||||
if padding:
|
||||
result = result[:-padding]
|
||||
return result
|
||||
If wrapcol is non-zero, insert a newline (b'\\n') character after at most
|
||||
every wrapcol characters.
|
||||
|
||||
_z85alphabet = (b'0123456789abcdefghijklmnopqrstuvwxyz'
|
||||
b'ABCDEFGHIJKLMNOPQRSTUVWXYZ.-:+=^!/*?&<>()[]{}@%$#')
|
||||
# Translating b85 valid but z85 invalid chars to b'\x00' is required
|
||||
# to prevent them from being decoded as b85 valid chars.
|
||||
_z85_b85_decode_diff = b';_`|~'
|
||||
_z85_decode_translation = bytes.maketrans(
|
||||
_z85alphabet + _z85_b85_decode_diff,
|
||||
_b85alphabet + b'\x00' * len(_z85_b85_decode_diff)
|
||||
)
|
||||
_z85_encode_translation = bytes.maketrans(_b85alphabet, _z85alphabet)
|
||||
The input is padded with b'\0' so its length is a multiple of
|
||||
bytes before encoding. If pad is true, all the resulting
|
||||
characters are retained in the output, which will always be a
|
||||
multiple of 5 bytes, as required by the ZeroMQ standard.
|
||||
"""
|
||||
return binascii.b2a_base85(s, wrapcol=wrapcol, pad=pad,
|
||||
alphabet=binascii.Z85_ALPHABET)
|
||||
|
||||
def z85encode(s):
|
||||
"""Encode bytes-like object b in z85 format and return a bytes object."""
|
||||
return b85encode(s).translate(_z85_encode_translation)
|
||||
|
||||
def z85decode(s):
|
||||
def z85decode(s, *, ignorechars=b'', canonical=False):
|
||||
"""Decode the z85-encoded bytes-like object or ASCII string b
|
||||
|
||||
If canonical is true, non-canonical encodings are rejected.
|
||||
|
||||
The result is returned as a bytes object.
|
||||
"""
|
||||
s = _bytes_from_decode_data(s)
|
||||
s = s.translate(_z85_decode_translation)
|
||||
try:
|
||||
return b85decode(s)
|
||||
except ValueError as e:
|
||||
raise ValueError(e.args[0].replace('base85', 'z85')) from None
|
||||
return binascii.a2b_base85(s, alphabet=binascii.Z85_ALPHABET,
|
||||
ignorechars=ignorechars, canonical=canonical)
|
||||
|
||||
# Legacy interface. This code could be cleaned up since I don't believe
|
||||
# binascii has any line length limitations. It just doesn't seem worth it
|
||||
@@ -573,11 +447,10 @@ def encodebytes(s):
|
||||
"""Encode a bytestring into a bytes object containing multiple lines
|
||||
of base-64 data."""
|
||||
_input_type_check(s)
|
||||
pieces = []
|
||||
for i in range(0, len(s), MAXBINSIZE):
|
||||
chunk = s[i : i + MAXBINSIZE]
|
||||
pieces.append(binascii.b2a_base64(chunk))
|
||||
return b"".join(pieces)
|
||||
result = binascii.b2a_base64(s, wrapcol=MAXLINESIZE)
|
||||
if result == b'\n':
|
||||
return b''
|
||||
return result
|
||||
|
||||
|
||||
def decodebytes(s):
|
||||
|
||||
+7
-1
@@ -24,7 +24,13 @@ def given(*_args, **_kwargs):
|
||||
@functools.wraps(f)
|
||||
def test_function(self):
|
||||
for example_args, example_kwargs in examples:
|
||||
with self.subTest(*example_args, **example_kwargs):
|
||||
if len(example_args) < 2:
|
||||
subtest_args = example_args
|
||||
else:
|
||||
# subTest takes up to one positional argument.
|
||||
# When there are more, display them as a tuple
|
||||
subtest_args = [example_args]
|
||||
with self.subTest(*subtest_args, **example_kwargs):
|
||||
f(self, *example_args, **example_kwargs)
|
||||
|
||||
else:
|
||||
|
||||
Vendored
+569
-40
@@ -1,9 +1,11 @@
|
||||
import unittest
|
||||
import base64
|
||||
import binascii
|
||||
import string
|
||||
import sys
|
||||
import os
|
||||
from array import array
|
||||
from test.support import cpython_only
|
||||
from test.support import cpython_only, check_impl_detail
|
||||
from test.support import os_helper
|
||||
from test.support import script_helper
|
||||
from test.support.import_helper import ensure_lazy_imports
|
||||
@@ -14,6 +16,8 @@ class LazyImportTest(unittest.TestCase):
|
||||
def test_lazy_import(self):
|
||||
ensure_lazy_imports("base64", {"re", "getopt"})
|
||||
|
||||
from test.support.hypothesis_helper import hypothesis
|
||||
|
||||
|
||||
class LegacyBase64TestCase(unittest.TestCase):
|
||||
|
||||
@@ -68,6 +72,13 @@ class LegacyBase64TestCase(unittest.TestCase):
|
||||
eq(base64.decodebytes(array('B', b'YWJj\n')), b'abc')
|
||||
self.check_type_errors(base64.decodebytes)
|
||||
|
||||
@hypothesis.given(payload=hypothesis.strategies.binary())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz')
|
||||
def test_bytes_encode_decode_round_trip(self, payload):
|
||||
encoded = base64.encodebytes(payload)
|
||||
decoded = base64.decodebytes(encoded)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_encode(self):
|
||||
eq = self.assertEqual
|
||||
from io import BytesIO, StringIO
|
||||
@@ -96,6 +107,19 @@ class LegacyBase64TestCase(unittest.TestCase):
|
||||
self.assertRaises(TypeError, base64.encode, BytesIO(b'YWJj\n'), StringIO())
|
||||
self.assertRaises(TypeError, base64.encode, StringIO('YWJj\n'), StringIO())
|
||||
|
||||
@hypothesis.given(payload=hypothesis.strategies.binary())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz')
|
||||
def test_legacy_encode_decode_round_trip(self, payload):
|
||||
from io import BytesIO
|
||||
payload_file_r = BytesIO(payload)
|
||||
encoded_file_w = BytesIO()
|
||||
base64.encode(payload_file_r, encoded_file_w)
|
||||
encoded_file_r = BytesIO(encoded_file_w.getvalue())
|
||||
decoded_file_w = BytesIO()
|
||||
base64.decode(encoded_file_r, decoded_file_w)
|
||||
decoded = decoded_file_w.getvalue()
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
|
||||
class BaseXYTestCase(unittest.TestCase):
|
||||
|
||||
@@ -149,6 +173,7 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
b"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNE"
|
||||
b"RUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NT"
|
||||
b"Y3ODkhQCMwXiYqKCk7Ojw+LC4gW117fQ==")
|
||||
|
||||
# Test with arbitrary alternative characters
|
||||
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=b'*$'), b'01a*b$cd')
|
||||
eq(base64.b64encode(b'\xd3V\xbeo\xf7\x1d', altchars=bytearray(b'*$')),
|
||||
@@ -184,6 +209,55 @@ 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)
|
||||
eq(func(data, wrapcol=0), expected)
|
||||
eq(func(data, wrapcol=80), expected)
|
||||
eq(func(b'', wrapcol=0), func(b''))
|
||||
eq(func(b'', wrapcol=1), func(b''))
|
||||
eq(func(data, wrapcol=sys.maxsize), expected)
|
||||
if check_impl_detail():
|
||||
eq(func(data, wrapcol=sys.maxsize*2), expected)
|
||||
with self.assertRaises(OverflowError):
|
||||
func(data, wrapcol=2**1000)
|
||||
with self.assertRaises(ValueError):
|
||||
func(data, wrapcol=-80)
|
||||
with self.assertRaises(TypeError):
|
||||
func(data, wrapcol=80.0)
|
||||
with self.assertRaises(TypeError):
|
||||
func(data, wrapcol='80')
|
||||
if func is not base64.b16encode:
|
||||
with self.assertRaises(TypeError):
|
||||
func(data, wrapcol=None)
|
||||
|
||||
def test_b64encode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'www.python.org'
|
||||
self._common_test_wrapcol(base64.b64encode, b)
|
||||
eq(base64.b64encode(b, wrapcol=8), b'd3d3LnB5\ndGhvbi5v\ncmc=')
|
||||
eq(base64.b64encode(b, wrapcol=11), b'd3d3LnB5\ndGhvbi5v\ncmc=')
|
||||
eq(base64.b64encode(b, wrapcol=1), b'd3d3\nLnB5\ndGhv\nbi5v\ncmc=')
|
||||
|
||||
def test_b64decode(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
@@ -241,29 +315,100 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
eq(base64.b64decode(data_str, altchars=altchars), res)
|
||||
eq(base64.b64decode(data, altchars=altchars_str), res)
|
||||
eq(base64.b64decode(data_str, altchars=altchars_str), res)
|
||||
eq(base64.b64decode(data, altchars=altchars, ignorechars=b'\n'), res)
|
||||
|
||||
eq(base64.b64decode(b'/----', altchars=b'-+', ignorechars=b'/'), b'\xfb\xef\xbe')
|
||||
eq(base64.b64decode(b'/----', altchars=b'+-', ignorechars=b'/'), b'\xff\xff\xff')
|
||||
eq(base64.b64decode(b'+----', altchars=b'-/', ignorechars=b'+'), b'\xfb\xef\xbe')
|
||||
eq(base64.b64decode(b'+----', altchars=b'/-', ignorechars=b'+'), b'\xff\xff\xff')
|
||||
eq(base64.b64decode(b'+/+/', altchars=b'/+', ignorechars=b''), b'\xff\xef\xfe')
|
||||
eq(base64.b64decode(b'/+/+', altchars=b'+/', ignorechars=b''), b'\xff\xef\xfe')
|
||||
|
||||
self.assertRaises(ValueError, base64.b64decode, b'', altchars=b'+')
|
||||
self.assertRaises(ValueError, base64.b64decode, b'', altchars=b'+/-')
|
||||
self.assertRaises(ValueError, base64.b64decode, '', altchars='+')
|
||||
self.assertRaises(ValueError, base64.b64decode, '', altchars='+/-')
|
||||
|
||||
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'')
|
||||
eq(func(b'', ignorechars=b''), b'')
|
||||
eq(func(b' \n', ignorechars=b' \n'), b'')
|
||||
with self.assertRaises(binascii.Error):
|
||||
func(b' \n', ignorechars=b'')
|
||||
with self.assertRaises(binascii.Error):
|
||||
func(b' \n', ignorechars=b' ')
|
||||
with self.assertRaises(binascii.Error):
|
||||
func(b' \n', ignorechars=b'\n')
|
||||
with self.assertRaises(TypeError):
|
||||
func(b'', ignorechars='')
|
||||
with self.assertRaises(TypeError):
|
||||
func(b'', ignorechars=[])
|
||||
with self.assertRaises(TypeError):
|
||||
func(b'', ignorechars=None)
|
||||
|
||||
def test_b64decode_invalid_chars(self):
|
||||
# issue 1466065: Test some invalid characters.
|
||||
tests = ((b'%3d==', b'\xdd'),
|
||||
(b'$3d==', b'\xdd'),
|
||||
(b'[==', b''),
|
||||
(b'YW]3=', b'am'),
|
||||
(b'3{d==', b'\xdd'),
|
||||
(b'3d}==', b'\xdd'),
|
||||
(b'@@', b''),
|
||||
(b'!', b''),
|
||||
(b"YWJj\n", b"abc"),
|
||||
(b'YWJj\nYWI=', b'abcab'))
|
||||
tests = ((b'%3d==', b'\xdd', b'%$'),
|
||||
(b'$3d==', b'\xdd', b'%$'),
|
||||
(b'[==', b'', b'[='),
|
||||
(b'YW]3=', b'am', b']'),
|
||||
(b'3{d==', b'\xdd', b'{}'),
|
||||
(b'3d}==', b'\xdd', b'{}'),
|
||||
(b'@@', b'', b'@!'),
|
||||
(b'!', b'', b'@!'),
|
||||
(b"YWJj\n", b"abc", b'\n'),
|
||||
(b'YWJj\nYWI=', b'abcab', b'\n'),
|
||||
(b'=YWJj', b'abc', b'='),
|
||||
(b'Y=WJj', b'abc', b'='),
|
||||
(b'Y==WJj', b'abc', b'='),
|
||||
(b'Y===WJj', b'abc', b'='),
|
||||
(b'YW=Jj', b'abc', b'='),
|
||||
(b'YWJj=', b'abc', b'='),
|
||||
(b'YW\nJj', b'abc', b'\n'),
|
||||
(b'YW\nJj', b'abc', bytearray(b'\n')),
|
||||
(b'YW\nJj', b'abc', memoryview(b'\n')),
|
||||
)
|
||||
funcs = (
|
||||
base64.b64decode,
|
||||
base64.standard_b64decode,
|
||||
base64.urlsafe_b64decode,
|
||||
)
|
||||
for bstr, res in tests:
|
||||
for bstr, res, ignorechars in tests:
|
||||
for func in funcs:
|
||||
with self.subTest(bstr=bstr, func=func):
|
||||
self.assertEqual(func(bstr), res)
|
||||
@@ -272,14 +417,111 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
base64.b64decode(bstr, validate=True)
|
||||
with self.assertRaises(binascii.Error):
|
||||
base64.b64decode(bstr.decode('ascii'), validate=True)
|
||||
with self.assertRaises(binascii.Error):
|
||||
# Even empty ignorechars enables the strict mode.
|
||||
base64.b64decode(bstr, ignorechars=b'')
|
||||
r = base64.b64decode(bstr, ignorechars=ignorechars)
|
||||
self.assertEqual(r, res)
|
||||
|
||||
# Normal alphabet characters not discarded when alternative given
|
||||
res = b'\xfb\xef\xff'
|
||||
self.assertEqual(base64.b64decode(b'++//', validate=True), res)
|
||||
self.assertEqual(base64.b64decode(b'++//', '-_', validate=True), res)
|
||||
self.assertEqual(base64.b64decode(b'--__', '-_', validate=True), res)
|
||||
self.assertEqual(base64.urlsafe_b64decode(b'++//'), res)
|
||||
self.assertEqual(base64.urlsafe_b64decode(b'--__'), res)
|
||||
self._common_test_ignorechars(base64.b64decode)
|
||||
|
||||
# Normal alphabet characters will be discarded when alternative given
|
||||
discarded = ("invalid character %a in Base64 data with %s "
|
||||
"will be discarded in future Python versions")
|
||||
error = ("invalid character %a in Base64 data with %s "
|
||||
"will be an error in future Python versions")
|
||||
with self.assertWarns(FutureWarning) as cm:
|
||||
r = base64.b64decode(b'++++', altchars=b'-_')
|
||||
self.assertEqual(r, b'\xfb\xef\xbe')
|
||||
self.assertEqual(str(cm.warning),
|
||||
discarded % ('+', "altchars=b'-_' and validate=False"))
|
||||
with self.assertWarns(FutureWarning) as cm:
|
||||
r = base64.b64decode(b'////', altchars=b'-_')
|
||||
self.assertEqual(r, b'\xff\xff\xff')
|
||||
self.assertEqual(str(cm.warning),
|
||||
discarded % ('/', "altchars=b'-_' and validate=False"))
|
||||
with self.assertWarns(DeprecationWarning) as cm:
|
||||
r = base64.b64decode(b'++++', altchars=b'-_', validate=True)
|
||||
self.assertEqual(r, b'\xfb\xef\xbe')
|
||||
self.assertEqual(str(cm.warning),
|
||||
error % ('+', "altchars=b'-_' and validate=True"))
|
||||
with self.assertWarns(DeprecationWarning) as cm:
|
||||
r = base64.b64decode(b'////', altchars=b'-_', validate=True)
|
||||
self.assertEqual(r, b'\xff\xff\xff')
|
||||
self.assertEqual(str(cm.warning),
|
||||
error % ('/', "altchars=b'-_' and validate=True"))
|
||||
r = base64.b64decode(b'++++', altchars=b'-_', ignorechars=b'+')
|
||||
self.assertEqual(r, b'')
|
||||
r = base64.b64decode(b'////', altchars=b'-_', ignorechars=b'/')
|
||||
self.assertEqual(r, b'')
|
||||
r = base64.b64decode(b'++++////', altchars=b'-_', validate=False, ignorechars=b'')
|
||||
self.assertEqual(r, b'')
|
||||
with self.assertRaisesRegex(binascii.Error, 'Only base64 data is allowed'):
|
||||
base64.b64decode(b'////', altchars=b'-_', ignorechars=b'')
|
||||
with self.assertRaisesRegex(binascii.Error, 'Only base64 data is allowed'):
|
||||
base64.b64decode(b'++++', altchars=b'-_', ignorechars=b'')
|
||||
r = base64.b64decode(b'++++YWJj----____', altchars=b'-_', ignorechars=b'+')
|
||||
self.assertEqual(r, b'abc\xfb\xef\xbe\xff\xff\xff')
|
||||
r = base64.b64decode(b'////YWJj----____', altchars=b'-_', ignorechars=b'/')
|
||||
self.assertEqual(r, b'abc\xfb\xef\xbe\xff\xff\xff')
|
||||
r = base64.b64decode(b'++++,,,,', altchars=b'+,', ignorechars=b'+')
|
||||
self.assertEqual(r, b'\xfb\xef\xbe\xff\xff\xff')
|
||||
r = base64.b64decode(b'////YWJj++++,,,,', altchars=b'+,', ignorechars=b'/')
|
||||
self.assertEqual(r, b'abc\xfb\xef\xbe\xff\xff\xff')
|
||||
r = base64.b64decode(b'----////', altchars=b'-/', ignorechars=b'/')
|
||||
self.assertEqual(r, b'\xfb\xef\xbe\xff\xff\xff')
|
||||
r = base64.b64decode(b'++++YWJj----////', altchars=b'-/', ignorechars=b'+')
|
||||
self.assertEqual(r, b'abc\xfb\xef\xbe\xff\xff\xff')
|
||||
|
||||
with self.assertWarns(FutureWarning) as cm:
|
||||
self.assertEqual(base64.urlsafe_b64decode(b'++++'), b'\xfb\xef\xbe')
|
||||
self.assertEqual(str(cm.warning),
|
||||
"invalid character '+' in URL-safe Base64 data "
|
||||
"will be discarded in future Python versions")
|
||||
with self.assertWarns(FutureWarning) as cm:
|
||||
self.assertEqual(base64.urlsafe_b64decode(b'////'), b'\xff\xff\xff')
|
||||
self.assertEqual(str(cm.warning),
|
||||
"invalid character '/' in URL-safe Base64 data "
|
||||
"will be discarded in future Python versions")
|
||||
with self.assertRaises(binascii.Error):
|
||||
base64.b64decode(b'+/!', altchars=b'-_')
|
||||
|
||||
def _altchars_strategy():
|
||||
"""Generate 'altchars' for base64 encoding."""
|
||||
reserved_chars = (string.digits + string.ascii_letters + "=").encode()
|
||||
allowed_chars = hypothesis.strategies.sampled_from(
|
||||
[n for n in range(256) if n not in reserved_chars])
|
||||
two_bytes_strategy = hypothesis.strategies.lists(
|
||||
allowed_chars, min_size=2, max_size=2, unique=True).map(bytes)
|
||||
return (hypothesis.strategies.none()
|
||||
| hypothesis.strategies.just(b"_-")
|
||||
| two_bytes_strategy)
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
altchars=_altchars_strategy(),
|
||||
validate=hypothesis.strategies.booleans())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', b"_-", True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', b"_-", False)
|
||||
def test_b64_encode_decode_round_trip(self, payload, altchars, validate):
|
||||
encoded = base64.b64encode(payload, altchars=altchars)
|
||||
decoded = base64.b64decode(encoded, altchars=altchars,
|
||||
validate=validate)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
@hypothesis.given(payload=hypothesis.strategies.binary())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz')
|
||||
def test_standard_b64_encode_decode_round_trip(self, payload):
|
||||
encoded = base64.standard_b64encode(payload)
|
||||
decoded = base64.standard_b64decode(encoded)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
@hypothesis.given(payload=hypothesis.strategies.binary())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz')
|
||||
def test_urlsafe_b64_encode_decode_round_trip(self, payload):
|
||||
encoded = base64.urlsafe_b64encode(payload)
|
||||
decoded = base64.urlsafe_b64decode(encoded)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_b32encode(self):
|
||||
eq = self.assertEqual
|
||||
@@ -294,6 +536,23 @@ 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'
|
||||
self._common_test_wrapcol(base64.b32encode, b)
|
||||
eq(base64.b32encode(b, wrapcol=16), b'O53XOLTQPF2GQ33O\nFZXXEZY=')
|
||||
eq(base64.b32encode(b, wrapcol=23), b'O53XOLTQPF2GQ33O\nFZXXEZY=')
|
||||
eq(base64.b32encode(b, wrapcol=1), b'O53XOLTQ\nPF2GQ33O\nFZXXEZY=')
|
||||
|
||||
def test_b32decode(self):
|
||||
eq = self.assertEqual
|
||||
tests = {b'': b'',
|
||||
@@ -329,6 +588,7 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
|
||||
for data, res in tests.items():
|
||||
eq(base64.b32decode(data, True), res)
|
||||
eq(base64.b32decode(data, casefold=True), res)
|
||||
eq(base64.b32decode(data.decode('ascii'), True), res)
|
||||
|
||||
self.assertRaises(binascii.Error, base64.b32decode, b'me======')
|
||||
@@ -347,6 +607,11 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
self.assertRaises(binascii.Error, base64.b32decode, b'M1O23456')
|
||||
self.assertRaises(binascii.Error, base64.b32decode, b'ML023456')
|
||||
self.assertRaises(binascii.Error, base64.b32decode, b'MI023456')
|
||||
self.assertRaises(ValueError, base64.b32decode, b'', map01=b'')
|
||||
self.assertRaises(ValueError, base64.b32decode, b'', map01=b'LI')
|
||||
self.assertRaises(TypeError, base64.b32decode, b'', map01=0)
|
||||
eq(base64.b32decode(b'MLO23456', map01=None), res_L)
|
||||
self.assertRaises(binascii.Error, base64.b32decode, b'M1023456', map01=None)
|
||||
|
||||
data = b'M1023456'
|
||||
data_str = data.decode('ascii')
|
||||
@@ -362,6 +627,56 @@ 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
|
||||
eq(base64.b32decode(b'MFRG\n GZDF\n', ignorechars=b' \n'), b'abcde')
|
||||
eq(base64.b32decode(b'MFRG\n GZDF\n', ignorechars=bytearray(b' \n')), b'abcde')
|
||||
eq(base64.b32decode(b'M=======FRGGZDF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MF======RGGZDF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFR=====GGZDF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRG====GZDF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRGG===ZDF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRGGZ==DF', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRGGZD=F', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRGGZDF=', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32decode(b'MFRA======', ignorechars=b'='), b'ab')
|
||||
|
||||
eq(base64.b32decode(b'mfRggzDfmzTQ====', ignorechars=b'mfgz'),
|
||||
b'\x88\xe7')
|
||||
eq(base64.b32decode(b'mfRggzDfmzTQ====', casefold=True, ignorechars=b'mfgz'),
|
||||
b'abcdefg')
|
||||
eq(base64.b32decode(b'M0F1R0G1G0Z1D0F1', ignorechars=b'01'), b'abcde')
|
||||
eq(base64.b32decode(b'M0F1R0G1G0Z1D0F1', map01=b'L', ignorechars=b'01'),
|
||||
b'c\x8a\xb8\xb8\xcb3\xb2\xb1\xb8\xab')
|
||||
eq(base64.b32decode(b'M0F1R0G1G0Z1D0F1', map01=b'I', ignorechars=b'01'),
|
||||
b'c\x8a\x88\xb8\xc83\xb2\x81\xb8\xa8')
|
||||
|
||||
def test_b32decode_error(self):
|
||||
tests = [b'abc', b'ABCDEF==', b'==ABCDEF']
|
||||
prefixes = [b'M', b'ME', b'MFRA', b'MFRGG', b'MFRGGZA', b'MFRGGZDF']
|
||||
@@ -378,6 +693,19 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
with self.assertRaises(binascii.Error):
|
||||
base64.b32decode(data.decode('ascii'))
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
casefold=hypothesis.strategies.booleans(),
|
||||
map01=(
|
||||
hypothesis.strategies.none()
|
||||
| hypothesis.strategies.binary(min_size=1, max_size=1)))
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True, None)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False, None)
|
||||
def test_b32_encode_decode_round_trip(self, payload, casefold, map01):
|
||||
encoded = base64.b32encode(payload)
|
||||
decoded = base64.b32decode(encoded, casefold=casefold, map01=map01)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_b32hexencode(self):
|
||||
test_cases = [
|
||||
# to_encode, expected
|
||||
@@ -392,11 +720,21 @@ 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=')
|
||||
self.check_encode_type_errors(base64.b32hexencode)
|
||||
|
||||
def test_b32hexencode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'www.python.org'
|
||||
self._common_test_wrapcol(base64.b32hexencode, b)
|
||||
eq(base64.b32hexencode(b, wrapcol=16), b'ETRNEBJGF5Q6GRRE\n5PNN4PO=')
|
||||
eq(base64.b32hexencode(b, wrapcol=23), b'ETRNEBJGF5Q6GRRE\n5PNN4PO=')
|
||||
eq(base64.b32hexencode(b, wrapcol=1), b'ETRNEBJG\nF5Q6GRRE\n5PNN4PO=')
|
||||
|
||||
def test_b32hexdecode(self):
|
||||
test_cases = [
|
||||
# to_decode, expected, casefold
|
||||
@@ -431,6 +769,53 @@ 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
|
||||
eq(base64.b32hexdecode(b'C5H6\n 6P35\n', ignorechars=b' \n'), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H6\n 6P35\n', ignorechars=bytearray(b' \n')), b'abcde')
|
||||
eq(base64.b32hexdecode(b'========C5H66P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C=======5H66P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5======H66P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H=====66P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H6====6P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H66===P35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H66P==35', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H66P3=5', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H66P35=', ignorechars=b'='), b'abcde')
|
||||
eq(base64.b32hexdecode(b'C5H0======', ignorechars=b'='), b'ab')
|
||||
|
||||
eq(base64.b32hexdecode(b'c5h66p35cpjmg===', ignorechars=b'cghjmp'),
|
||||
b')\x8c2')
|
||||
eq(base64.b32hexdecode(b'c5h66p35cpjmg===', casefold=True,
|
||||
ignorechars=b'cghjmp'),
|
||||
b'abcdefgh')
|
||||
|
||||
def test_b32hexdecode_error(self):
|
||||
tests = [b'abc', b'ABCDEF==', b'==ABCDEF', b'c4======']
|
||||
prefixes = [b'M', b'ME', b'MFRA', b'MFRGG', b'MFRGGZA', b'MFRGGZDF']
|
||||
@@ -447,6 +832,15 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
with self.assertRaises(binascii.Error):
|
||||
base64.b32hexdecode(data.decode('ascii'))
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
casefold=hypothesis.strategies.booleans())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False)
|
||||
def test_b32_hexencode_decode_round_trip(self, payload, casefold):
|
||||
encoded = base64.b32hexencode(payload)
|
||||
decoded = base64.b32hexdecode(encoded, casefold=casefold)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_b16encode(self):
|
||||
eq = self.assertEqual
|
||||
@@ -457,6 +851,14 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
b'0102ABCDEF')
|
||||
self.check_encode_type_errors(base64.b16encode)
|
||||
|
||||
def test_b16encode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'\x01\x02\xab\xcd\xef'
|
||||
self._common_test_wrapcol(base64.b16encode, b)
|
||||
eq(base64.b16encode(b, wrapcol=4), b'0102\nABCD\nEF')
|
||||
eq(base64.b16encode(b, wrapcol=5), b'0102\nABCD\nEF')
|
||||
eq(base64.b16encode(b, wrapcol=1), b'01\n02\nAB\nCD\nEF')
|
||||
|
||||
def test_b16decode(self):
|
||||
eq = self.assertEqual
|
||||
eq(base64.b16decode(b'0102ABCDEF'), b'\x01\x02\xab\xcd\xef')
|
||||
@@ -484,6 +886,24 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
# Incorrect "padding"
|
||||
self.assertRaises(binascii.Error, base64.b16decode, '010')
|
||||
|
||||
def test_b16decode_ignorechars(self):
|
||||
self._common_test_ignorechars(base64.b16decode)
|
||||
eq = self.assertEqual
|
||||
eq(base64.b16decode(b'A B\nC D\n', ignorechars=b' \n'), b'\xab\xcd')
|
||||
eq(base64.b16decode(b'A B\nC D\n', ignorechars=bytearray(b' \n')), b'\xab\xcd')
|
||||
eq(base64.b16decode(b'aBcD', ignorechars=b'ac'), b'\xbd')
|
||||
eq(base64.b16decode(b'aBcD', casefold=True, ignorechars=b'ac'), b'\xab\xcd')
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
casefold=hypothesis.strategies.booleans())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False)
|
||||
def test_b16_encode_decode_round_trip(self, payload, casefold):
|
||||
endoded = base64.b16encode(payload)
|
||||
decoded = base64.b16decode(endoded, casefold=casefold)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_a85encode(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
@@ -522,18 +942,33 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
|
||||
self.assertRaises(TypeError, base64.a85encode, "")
|
||||
|
||||
eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=False),
|
||||
b'GB\\6`E-\nZP=Df.1\nGEb>')
|
||||
eq(base64.a85encode(b"\0\0\0\0www.python.org", wrapcol=7, adobe=False),
|
||||
b'zGB\\6`E\n-ZP=Df.\n1GEb>')
|
||||
eq(base64.a85encode(b"www.python.org", wrapcol=7, adobe=True),
|
||||
b'<~GB\\6`\nE-ZP=Df\n.1GEb>\n~>')
|
||||
|
||||
eq(base64.a85encode(b' '*8, foldspaces=True, adobe=False), b'yy')
|
||||
eq(base64.a85encode(b' '*7, foldspaces=True, adobe=False), b'y+<Vd')
|
||||
eq(base64.a85encode(b' '*6, foldspaces=True, adobe=False), b'y+<U')
|
||||
eq(base64.a85encode(b' '*5, foldspaces=True, adobe=False), b'y+9')
|
||||
|
||||
def test_a85encode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'www.python.org'
|
||||
self._common_test_wrapcol(base64.a85encode, b)
|
||||
eq(base64.a85encode(b, wrapcol=7), b'GB\\6`E-\nZP=Df.1\nGEb>')
|
||||
eq(base64.a85encode(b"\0\0\0\0www.python.org", wrapcol=7),
|
||||
b'zGB\\6`E\n-ZP=Df.\n1GEb>')
|
||||
eq(base64.a85encode(b, wrapcol=75), b'GB\\6`E-ZP=Df.1GEb>')
|
||||
eq(base64.a85encode(b, wrapcol=1),
|
||||
b'G\nB\n\\\n6\n`\nE\n-\nZ\nP\n=\nD\nf\n.\n1\nG\nE\nb\n>')
|
||||
eq(base64.a85encode(b, wrapcol=7, adobe=True),
|
||||
b'<~GB\\6`\nE-ZP=Df\n.1GEb>\n~>')
|
||||
eq(base64.a85encode(b, wrapcol=1),
|
||||
b'G\nB\n\\\n6\n`\nE\n-\nZ\nP\n=\nD\nf\n.\n1\nG\nE\nb\n>')
|
||||
eq(base64.a85encode(b, wrapcol=1, adobe=True),
|
||||
b'<~\nGB\n\\6\n`E\n-Z\nP=\nDf\n.1\nGE\nb>\n~>')
|
||||
with self.assertRaises(ValueError):
|
||||
base64.a85encode(b, wrapcol=-7, adobe=True)
|
||||
eq(base64.a85encode(b'', wrapcol=1, adobe=True), b'<~\n~>')
|
||||
eq(base64.a85encode(b'', wrapcol=3, adobe=True), b'<~\n~>')
|
||||
eq(base64.a85encode(b'', wrapcol=4, adobe=True), b'<~~>')
|
||||
|
||||
def test_b85encode(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
@@ -568,11 +1003,20 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
self.check_other_types(base64.b85encode, b"www.python.org",
|
||||
b'cXxL#aCvlSZ*DGca%T')
|
||||
|
||||
def test_b85encode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'www.python.org'
|
||||
self._common_test_wrapcol(base64.b85encode, b)
|
||||
eq(base64.b85encode(b, wrapcol=10), b'cXxL#aCvlS\nZ*DGca%T')
|
||||
eq(base64.b85encode(b, wrapcol=14), b'cXxL#aCvlS\nZ*DGca%T')
|
||||
eq(base64.b85encode(b, wrapcol=1), b'cXxL#\naCvlS\nZ*DGc\na%T')
|
||||
|
||||
def test_z85encode(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
tests = {
|
||||
b'': b'',
|
||||
b'\x86\x4F\xD2\x6F\xB5\x59\xF7\x5B': b'HelloWorld',
|
||||
b'www.python.org': b'CxXl-AcVLsz/dgCA+t',
|
||||
bytes(range(255)): b"""009c61o!#m2NH?C3>iWS5d]J*6CRx17-skh9337x"""
|
||||
b"""ar.{NbQB=+c[cR@eg&FcfFLssg=mfIi5%2YjuU>)kTv.7l}6Nnnj=AD"""
|
||||
@@ -602,6 +1046,14 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
self.check_other_types(base64.z85encode, b"www.python.org",
|
||||
b'CxXl-AcVLsz/dgCA+t')
|
||||
|
||||
def test_z85encode_wrapcol(self):
|
||||
eq = self.assertEqual
|
||||
b = b'www.python.org'
|
||||
self._common_test_wrapcol(base64.z85encode, b)
|
||||
eq(base64.z85encode(b, wrapcol=10), b'CxXl-AcVLs\nz/dgCA+t')
|
||||
eq(base64.z85encode(b, wrapcol=14), b'CxXl-AcVLs\nz/dgCA+t')
|
||||
eq(base64.z85encode(b, wrapcol=1), b'CxXl-\nAcVLs\nz/dgC\nA+t')
|
||||
|
||||
def test_a85decode(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
@@ -733,6 +1185,12 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
eq(base64.a85decode(b'G^+IX'), b"xxxx")
|
||||
eq(base64.a85decode(b'G^+IXGQ7^D'), b"xxxxx\x00\x00\x00")
|
||||
|
||||
eq(base64.a85encode(b"\x00", pad=True), b'z')
|
||||
eq(base64.a85encode(b"\x00"*2, pad=True), b'z')
|
||||
eq(base64.a85encode(b"\x00"*3, pad=True), b'z')
|
||||
eq(base64.a85encode(b"\x00"*4, pad=True), b'z')
|
||||
eq(base64.a85encode(b"\x00"*5, pad=True), b'zz')
|
||||
|
||||
def test_b85_padding(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
@@ -748,6 +1206,21 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
eq(base64.b85decode(b'czAet'), b"xxxx")
|
||||
eq(base64.b85decode(b'czAetcmMzZ'), b"xxxxx\x00\x00\x00")
|
||||
|
||||
def test_z85_padding(self):
|
||||
eq = self.assertEqual
|
||||
|
||||
eq(base64.z85encode(b"x", pad=True), b'CMmZz')
|
||||
eq(base64.z85encode(b"xx", pad=True), b'CZ6h*')
|
||||
eq(base64.z85encode(b"xxx", pad=True), b'CZaDk')
|
||||
eq(base64.z85encode(b"xxxx", pad=True), b'CZaET')
|
||||
eq(base64.z85encode(b"xxxxx", pad=True), b'CZaETCMmZz')
|
||||
|
||||
eq(base64.z85decode(b'CMmZz'), b"x\x00\x00\x00")
|
||||
eq(base64.z85decode(b'CZ6h*'), b"xx\x00\x00")
|
||||
eq(base64.z85decode(b'CZaDk'), b"xxx\x00")
|
||||
eq(base64.z85decode(b'CZaET'), b"xxxx")
|
||||
eq(base64.z85decode(b'CZaETCMmZz'), b"xxxxx\x00\x00\x00")
|
||||
|
||||
def test_a85decode_errors(self):
|
||||
illegal = (set(range(32)) | set(range(118, 256))) - set(b' \t\n\r\v')
|
||||
for c in illegal:
|
||||
@@ -787,24 +1260,20 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
|
||||
self.assertEqual(base64.a85decode(b"a b\nc", ignorechars=b" \n"),
|
||||
b'\xc9\x89')
|
||||
with self.assertRaises(ValueError):
|
||||
base64.a85decode(b"a b\nc", ignorechars=b"")
|
||||
with self.assertRaises(ValueError):
|
||||
base64.a85decode(b"a b\nc", ignorechars=b" ")
|
||||
with self.assertRaises(ValueError):
|
||||
base64.a85decode(b"a b\nc", ignorechars=b"\n")
|
||||
with self.assertRaises(TypeError):
|
||||
base64.a85decode(b"a b\nc", ignorechars=" \n")
|
||||
with self.assertRaises(TypeError):
|
||||
base64.a85decode(b"a b\nc", ignorechars=None)
|
||||
self._common_test_ignorechars(base64.a85decode)
|
||||
|
||||
def test_b85decode_errors(self):
|
||||
illegal = list(range(33)) + \
|
||||
list(b'"\',./:[\\]') + \
|
||||
list(range(128, 256))
|
||||
for c in illegal:
|
||||
with self.assertRaises(ValueError, msg=bytes([c])):
|
||||
base64.b85decode(b'0000' + bytes([c]))
|
||||
b = bytes([c])
|
||||
with self.assertRaises(ValueError, msg=b):
|
||||
base64.b85decode(b'0000' + b)
|
||||
self.assertEqual(base64.b85decode(b'0000' + b, ignorechars=b),
|
||||
b'\x00\x00\x00')
|
||||
|
||||
self._common_test_ignorechars(base64.b85decode)
|
||||
|
||||
self.assertRaises(ValueError, base64.b85decode, b'|')
|
||||
self.assertRaises(ValueError, base64.b85decode, b'|N')
|
||||
@@ -817,8 +1286,13 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
list(b'"\',;_`|\\~') + \
|
||||
list(range(128, 256))
|
||||
for c in illegal:
|
||||
with self.assertRaises(ValueError, msg=bytes([c])):
|
||||
base64.z85decode(b'0000' + bytes([c]))
|
||||
b = bytes([c])
|
||||
with self.assertRaises(ValueError, msg=b):
|
||||
base64.z85decode(b'0000' + b)
|
||||
self.assertEqual(base64.z85decode(b'0000' + b, ignorechars=b),
|
||||
b'\x00\x00\x00')
|
||||
|
||||
self._common_test_ignorechars(base64.z85decode)
|
||||
|
||||
# b'\xff\xff\xff\xff' encodes to b'%nSc0', the following will overflow:
|
||||
self.assertRaises(ValueError, base64.z85decode, b'%')
|
||||
@@ -827,6 +1301,61 @@ class BaseXYTestCase(unittest.TestCase):
|
||||
self.assertRaises(ValueError, base64.z85decode, b'%nSc')
|
||||
self.assertRaises(ValueError, base64.z85decode, b'%nSc1')
|
||||
|
||||
def add_padding(self, payload):
|
||||
"""Add the expected padding for test_?85_encode_decode_round_trip."""
|
||||
if len(payload) % 4 != 0:
|
||||
padding = b"\0" * ((-len(payload)) % 4)
|
||||
payload = payload + padding
|
||||
return payload
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
foldspaces=hypothesis.strategies.booleans(),
|
||||
wrapcol=(
|
||||
hypothesis.strategies.just(0)
|
||||
| hypothesis.strategies.integers(1, 1000)),
|
||||
pad=hypothesis.strategies.booleans(),
|
||||
adobe=hypothesis.strategies.booleans(),
|
||||
)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False, 0, False, False)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False, 20, True, True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True, 0, False, True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True, 20, True, False)
|
||||
def test_a85_encode_decode_round_trip(
|
||||
self, payload, foldspaces, wrapcol, pad, adobe
|
||||
):
|
||||
encoded = base64.a85encode(
|
||||
payload, foldspaces=foldspaces, wrapcol=wrapcol,
|
||||
pad=pad, adobe=adobe,
|
||||
)
|
||||
if wrapcol:
|
||||
if adobe and wrapcol == 1:
|
||||
# "adobe" needs wrapcol to be at least 2.
|
||||
# a85decode quietly uses 2 if 1 is given; it's not worth
|
||||
# loudly deprecating this behavior.
|
||||
wrapcol = 2
|
||||
for line in encoded.splitlines(keepends=False):
|
||||
self.assertLessEqual(len(line), wrapcol)
|
||||
if adobe:
|
||||
self.assertTrue(encoded.startswith(b'<~'))
|
||||
self.assertTrue(encoded.endswith(b'~>'))
|
||||
decoded = base64.a85decode(encoded, foldspaces=foldspaces, adobe=adobe)
|
||||
if pad:
|
||||
payload = self.add_padding(payload)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
@hypothesis.given(
|
||||
payload=hypothesis.strategies.binary(),
|
||||
pad=hypothesis.strategies.booleans())
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', True)
|
||||
@hypothesis.example(b'abcdefghijklmnopqrstuvwxyz', False)
|
||||
def test_b85_encode_decode_round_trip(self, payload, pad):
|
||||
encoded = base64.b85encode(payload, pad=pad)
|
||||
if pad:
|
||||
payload = self.add_padding(payload)
|
||||
decoded = base64.b85decode(encoded)
|
||||
self.assertEqual(payload, decoded)
|
||||
|
||||
def test_decode_nonascii_str(self):
|
||||
decode_funcs = (base64.b64decode,
|
||||
base64.standard_b64decode,
|
||||
|
||||
@@ -156,14 +156,24 @@ mod decl {
|
||||
vm.ctx.new_exception_type("binascii", "Incomplete", None)
|
||||
}
|
||||
|
||||
#[derive(FromArgs)]
|
||||
struct HexlifyArgs {
|
||||
#[pyarg(any)]
|
||||
data: ArgBytesLike,
|
||||
#[pyarg(any, optional)]
|
||||
sep: OptionalArg<ArgAsciiBuffer>,
|
||||
#[pyarg(any, optional)]
|
||||
bytes_per_sep: OptionalArg<isize>,
|
||||
}
|
||||
|
||||
#[pyfunction(name = "b2a_hex")]
|
||||
#[pyfunction]
|
||||
fn hexlify(
|
||||
data: ArgBytesLike,
|
||||
sep: OptionalArg<ArgAsciiBuffer>,
|
||||
bytes_per_sep: OptionalArg<isize>,
|
||||
vm: &VirtualMachine,
|
||||
) -> PyResult<Vec<u8>> {
|
||||
fn hexlify(args: HexlifyArgs, vm: &VirtualMachine) -> PyResult<Vec<u8>> {
|
||||
let HexlifyArgs {
|
||||
data,
|
||||
sep,
|
||||
bytes_per_sep,
|
||||
} = args;
|
||||
let sep = match sep {
|
||||
OptionalArg::Present(sep) => sep.with_ref(|sep| {
|
||||
let [sep] = sep else {
|
||||
|
||||
Reference in new issue
Block a user