| [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==" |