[case testAllBase64Features_librt] from typing import Any, cast import base64 import binascii import random import sys from librt.base64 import b64encode, b64decode, urlsafe_b64encode, urlsafe_b64decode from testutil import assertRaises rand_values = [random.randbytes(random.randint(1, 2000)) for _ in range(2000)] def test_encode_basic() -> None: assert b64encode(b"x") == b"eA==" with assertRaises(TypeError): b64encode(cast(Any, bytearray(b"x"))) def check_encode(b: bytes) -> None: assert b64encode(b) == getattr(base64, "b64encode")(b) def test_encode_different_strings() -> None: for i in range(256): check_encode(bytes([i])) check_encode(bytes([i]) + b"x") check_encode(bytes([i]) + b"xy") check_encode(bytes([i]) + b"xyz") check_encode(bytes([i]) + b"xyza") check_encode(b"x" + bytes([i])) check_encode(b"xy" + bytes([i])) check_encode(b"xyz" + bytes([i])) check_encode(b"xyza" + bytes([i])) b = b"a\x00\xb7" * 1000 for i in range(1000): check_encode(b[:i]) for b in b"", b"ab", b"bac", b"1234", b"xyz88", b"abc" * 200: check_encode(b) for b in rand_values: check_encode(b) def test_encode_wrappers() -> None: funcs: list[Any] = [b64encode, urlsafe_b64encode] for enc in funcs: assert enc(b"x") == b"eA==" with assertRaises(TypeError): enc() with assertRaises(TypeError): enc(b"x", b"y") def test_decode_basic() -> None: assert b64decode(b"eA==") == b"x" with assertRaises(TypeError): b64decode(cast(Any, bytearray(b"eA=="))) for non_ascii in "\x80", "foo\u100bar", "foo\ua1234bar": with assertRaises(ValueError): b64decode(non_ascii) def check_decode(b: bytes, encoded: bool = False) -> None: if encoded: enc = b else: enc = b64encode(b) assert b64decode(enc) == getattr(base64, "b64decode")(enc) if getattr(enc, "isascii")(): # Test stub has no "isascii" enc_str = enc.decode("ascii") assert b64decode(enc_str) == getattr(base64, "b64decode")(enc_str) def test_decode_different_strings() -> None: for i in range(256): check_decode(bytes([i])) check_decode(bytes([i]) + b"x") check_decode(bytes([i]) + b"xy") check_decode(bytes([i]) + b"xyz") check_decode(bytes([i]) + b"xyza") check_decode(b"x" + bytes([i])) check_decode(b"xy" + bytes([i])) check_decode(b"xyz" + bytes([i])) check_decode(b"xyza" + bytes([i])) b = b"a\x00\xb7" * 1000 for i in range(1000): check_decode(b[:i]) for b in b"", b"ab", b"bac", b"1234", b"xyz88", b"abc" * 200: check_decode(b) for b in rand_values: check_decode(b) def is_base64_char(x: int) -> bool: c = chr(x) return ('a' <= c <= 'z') or ('A' <= c <= 'Z') or ('0' <= c <= '9') or c in '+/=' def test_decode_with_non_base64_chars() -> None: # For stdlib compatibility, non-base64 characters should be ignored. # Invalid characters as a suffix use a fast path. check_decode(b"eA== ", encoded=True) check_decode(b"eA==\n", encoded=True) check_decode(b"eA== \t\n", encoded=True) check_decode(b"\n", encoded=True) check_decode(b" e A = = ", encoded=True) # Special case: Two different encodings of the same data check_decode(b"eAa=", encoded=True) check_decode(b"eAY=", encoded=True) for x in range(256): if not is_base64_char(x): b = bytes([x]) check_decode(b, encoded=True) check_decode(b"eA==" + b, encoded=True) check_decode(b"e" + b + b"A==", encoded=True) check_decode(b"eA=" + b + b"=", encoded=True) def has_stdlib_b64decode_bugfix() -> bool: # stdlib b64decode has a bug in older python versions where it skips processing the input data # after the first padded quad. It was changed to conform to RFC 4648 section 3.3 in cpython 3.13.13+, # 3.14.4+ and 3.15+. The librt implementation was changed to match the correct behavior regardless # of python version so some inputs result in different results than stdlib on older python. _, minor, micro, _, _ = sys.version_info return minor > 14 or (minor == 14 and micro >= 4) or (minor == 13 and micro >= 13) def check_decode_error(b: bytes, ignore_stdlib: bool = False) -> None: if not ignore_stdlib: with assertRaises(binascii.Error): getattr(base64, "b64decode")(b) # The raised error is different, since librt shouldn't depend on binascii with assertRaises(ValueError): b64decode(b) def test_decode_with_invalid_padding() -> None: check_decode_error(b"eA") check_decode_error(b"eA=") check_decode_error(b"eHk") check_decode_error(b"eA = ") check_decode_error(b"eA==x", ignore_stdlib=not has_stdlib_b64decode_bugfix()) def test_decode_with_extra_data_after_padding() -> None: check_decode(b"=", encoded=True) check_decode(b"==", encoded=True) check_decode(b"===", encoded=True) check_decode(b"====", encoded=True) check_decode(b"eA===", encoded=True) check_decode(b"eHk==", encoded=True) if has_stdlib_b64decode_bugfix(): check_decode(b"eA=a=", encoded=True) check_decode(b"eHk=x", encoded=True) check_decode(b"eA==abc=======efg", encoded=True) def test_decode_wrappers() -> None: funcs: list[Any] = [b64decode, urlsafe_b64decode] for dec in funcs: assert dec(b"eA==") == b"x" with assertRaises(TypeError): dec() with assertRaises(TypeError): dec(b"x", b"y") def check_urlsafe_encode(b: bytes) -> None: assert urlsafe_b64encode(b) == getattr(base64, "urlsafe_b64encode")(b) def test_urlsafe_b64encode() -> None: check_urlsafe_encode(b"") check_urlsafe_encode(b"a") check_urlsafe_encode(b"\xf8") check_urlsafe_encode(b"\xfc") check_urlsafe_encode(b"\xfcx") check_urlsafe_encode(b"\xfcxy") check_urlsafe_encode(b"\xfcxyz") check_urlsafe_encode(bytes([x for x in range(256)])) for b in rand_values: check_urlsafe_encode(b) def check_urlsafe_decode(b: bytes) -> None: enc = urlsafe_b64encode(b) assert urlsafe_b64decode(enc) == getattr(base64, "urlsafe_b64decode")(enc) enc2 = b64encode(b) assert urlsafe_b64decode(enc2) == getattr(base64, "urlsafe_b64decode")(enc2) def test_urlsafe_b64decode() -> None: # Don't test everything, since the implementation is mostly shared with b64decode. check_urlsafe_decode(b"") check_urlsafe_decode(b"a") check_urlsafe_decode(b"\xf8") check_urlsafe_decode(b"\xfc") check_urlsafe_decode(b"\xfcx") check_urlsafe_decode(b"\xfcxy") check_urlsafe_decode(b"\xfcxyz") check_urlsafe_decode(bytes([x for x in range(256)])) for b in rand_values: check_urlsafe_decode(b) assert urlsafe_b64decode(b" e A = == !") == b"x" def test_urlsafe_b64decode_errors() -> None: for b in b"eA", b"eA=", b"eHk": with assertRaises(ValueError): b64decode(b) [out version>=3.15] driver.py:28: FutureWarning: invalid character '+' in URL-safe Base64 data will be discarded in future Python versions test_func() driver.py:28: FutureWarning: invalid character '/' in URL-safe Base64 data will be discarded in future Python versions test_func() [case testBase64UsedAtTopLevelOnly_librt] from librt.base64 import b64encode # The only reference to b64encode is at module top level encoded = b64encode(b"x") def test_top_level_only_encode() -> None: assert encoded == b"eA=="