Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion Lib/base64.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,17 @@ def b64decode(s, altchars=None, validate=_NOT_SPECIFIED, *, ignorechars=_NOT_SPE
break
s = s.translate(bytes.maketrans(altchars, b'+/'))
else:
trans = bytes.maketrans(b'+/' + altchars, altchars + b'+/')
altchars_out = (
altchars[0] if altchars[0] not in b'+/' else altchars[1],
altchars[1] if altchars[1] not in b'+/' else altchars[0],
)
trans_in = bytearray(altchars)
trans_out = bytearray(b'+/')
for b, b_out in zip(b'+/', altchars_out):
if b not in altchars:
trans_in.append(b)
trans_out.append(b_out)
trans = bytes.maketrans(trans_in, trans_out)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be simply:

Suggested change
altchars_out = (
altchars[0] if altchars[0] not in b'+/' else altchars[1],
altchars[1] if altchars[1] not in b'+/' else altchars[0],
)
trans_in = bytearray(altchars)
trans_out = bytearray(b'+/')
for b, b_out in zip(b'+/', altchars_out):
if b not in altchars:
trans_in.append(b)
trans_out.append(b_out)
trans = bytes.maketrans(trans_in, trans_out)
trans = bytes.maketrans(altchars + bytes(set(b'+/') - set(altchars)),
b'+/' + bytes(set(altchars) - set(b'+/')))

Copy link
Copy Markdown
Contributor Author

@mayeut mayeut Mar 15, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I commited the suggestion but after further reflection, the sets are unordered and thus the translation might not be correct when altchars and standard ones do not overlap (although this might be hard to catch in tests ?).
I'll fix that later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. What about the following?

trans_in = altchars
trans_out = b'+/'
if b'+' not in altchars and b'-' not in altchars:
    trans_in += b'+/'
    trans_out += altchars
elif b'+' not in altchars or b'-' not in altchars:
    trans_in += bytes(set(b'+/') - set(altchars))
    trans_out += bytes(set(altchars) - set(b'+/'))

You can also return your original code. #145981 also fixes this issue, so this code can be replaced, but your tests will remain.

s = s.translate(trans)
ignorechars = ignorechars.translate(trans)
if ignorechars is _NOT_SPECIFIED:
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_base64.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,13 @@ def test_b64decode_altchars(self):
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='+')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix translation in :func:`base64.b64decode` when altchars overlaps with the
standard ones.
Loading