Functional Weave
Code in Python

encoding.base64@1.0.0

impl/python/base64_decode.py

2,204 bytes · the Python implementation · view raw

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