from typing import List def _sextet(ch: str) -> int: # Character ranges rather than str.isalnum(), which is true for letters # and digits of every script. if "A" <= ch <= "Z": return ord(ch) - 65 if "a" <= ch <= "z": return ord(ch) - 71 if "0" <= ch <= "9": return ord(ch) + 4 if ch == "+": return 62 if ch == "/": return 63 return -1 def base64_decode(text: str) -> List[int]: """Standard, padded base64 back to bytes, strictly. No whitespace, no characters outside the alphabet, padding to a multiple of four, and zero bits after the last byte, so every byte string has exactly one spelling. The standard library's decoder skips or accepts some of these, which is why this is written out. """ if not isinstance(text, str): raise TypeError("base64 text must be a string") values = [] padding = 0 for ch in text: if ch == "=": padding += 1 continue v = _sextet(ch) if v < 0 or padding > 0: raise ValueError("base64 text may only contain A-Z, a-z, 0-9, + and /, with = padding at the end") values.append(v) if len(text) % 4 != 0: raise ValueError("base64 text must be a multiple of 4 characters long, received %d" % len(text)) if padding > 2: raise ValueError("base64 text has too much = padding") out = [] n_full = len(values) - len(values) % 4 for i in range(0, n_full, 4): n = (values[i] << 18) | (values[i + 1] << 12) | (values[i + 2] << 6) | values[i + 3] out.extend(((n >> 16) & 255, (n >> 8) & 255, n & 255)) rest = len(values) - n_full if rest == 2: if values[n_full + 1] & 15: raise ValueError("base64 text has non-zero bits after its last byte") out.append(((values[n_full] << 2) | (values[n_full + 1] >> 4)) & 255) elif rest == 3: if values[n_full + 2] & 3: raise ValueError("base64 text has non-zero bits after its last byte") n = (values[n_full] << 18) | (values[n_full + 1] << 12) | (values[n_full + 2] << 6) out.extend(((n >> 16) & 255, (n >> 8) & 255)) return out