@@ -3,6 +3,7 @@ from typing import Any, cast
33import base64
44import binascii
55import random
6+ import sys
67
78from librt.base64 import b64encode, b64decode, urlsafe_b64encode, urlsafe_b64decode
89
@@ -121,6 +122,14 @@ def test_decode_with_non_base64_chars() -> None:
121122 check_decode(b"e" + b + b"A==", encoded=True)
122123 check_decode(b"eA=" + b + b"=", encoded=True)
123124
125+ def has_stdlib_b64decode_bugfix() -> bool:
126+ # stdlib b64decode has a bug in older python versions where it skips processing the input data
127+ # after the first padded quad. It was changed to conform to RFC 4648 section 3.3 in cpython 3.13.13+,
128+ # 3.14.4+ and 3.15+. The librt implementation was changed to match the correct behavior regardless
129+ # of python version so some inputs result in different results than stdlib on older python.
130+ _, minor, micro, _, _ = sys.version_info
131+ return minor > 14 or (minor == 14 and micro >= 4) or (minor == 13 and micro >= 13)
132+
124133def check_decode_error(b: bytes, ignore_stdlib: bool = False) -> None:
125134 if not ignore_stdlib:
126135 with assertRaises(binascii.Error):
@@ -135,9 +144,7 @@ def test_decode_with_invalid_padding() -> None:
135144 check_decode_error(b"eA=")
136145 check_decode_error(b"eHk")
137146 check_decode_error(b"eA = ")
138-
139- # Here stdlib behavior seems nonsensical, so we don't try to duplicate it
140- check_decode_error(b"eA=a=", ignore_stdlib=True)
147+ check_decode_error(b"eA==x", ignore_stdlib=not has_stdlib_b64decode_bugfix())
141148
142149def test_decode_with_extra_data_after_padding() -> None:
143150 check_decode(b"=", encoded=True)
@@ -146,10 +153,10 @@ def test_decode_with_extra_data_after_padding() -> None:
146153 check_decode(b"====", encoded=True)
147154 check_decode(b"eA===", encoded=True)
148155 check_decode(b"eHk==", encoded=True)
149- # TODO: behavior in these cases changed in Python 3.14.4, we should match that.
150- # check_decode(b"eA==x ", encoded=True)
151- # check_decode(b"eHk=x", encoded=True)
152- # check_decode(b"eA==abc=======efg", encoded=True)
156+ if has_stdlib_b64decode_bugfix():
157+ check_decode(b"eA=a= ", encoded=True)
158+ check_decode(b"eHk=x", encoded=True)
159+ check_decode(b"eA==abc=======efg", encoded=True)
153160
154161def test_decode_wrappers() -> None:
155162 funcs: list[Any] = [b64decode, urlsafe_b64decode]
0 commit comments