~/bend-docscommunity

lib.bend source

lib.bend on the hub · documented module

# Base64 — standard RFC 4648 Base64 over Base's List<&2, U32> and String.## This package intentionally stays Base-only. Input list elements are interpreted# as octets: only the low 8 bits of each U32 participate in encoding. Decode# returns canonical octets in List<&2, U32>. Invalid alphabet, padding, length,# or unused padding bits return None.import Base# The standard alphabet: A-Z, a-z, 0-9, +, /.def Base64.alphabet() -> String:  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"def Base64.char(+n: U32) -> Char:  # n is always 0..63 at call sites; the fallback is unreachable there.  Maybe.default(&2, Char, String.get(Base64.alphabet(), U32.to_nat(n)), Chr{0})# Encode one complete 24-bit group.def Base64.encode.group(+a: U32, +b: U32, +c: U32) -> String:  +a8 = U32.and(a, 255)  +b8 = U32.and(b, 255)  +c8 = U32.and(c, 255)  +i0 = U32.shrn(a8, 2n)  +i1 = U32.or(U32.shln(U32.and(a8, 3), 4n), U32.shrn(b8, 4n))  +i2 = U32.or(U32.shln(U32.and(b8, 15), 2n), U32.shrn(c8, 6n))  +i3 = U32.and(c8, 63)  SCon{    Base64.char(i0),    SCon{Base64.char(i1),      SCon{Base64.char(i2), SCon{Base64.char(i3), SNil{}}}}  }# Encode a final one-byte group (two alphabet chars and == padding).def Base64.encode.one(+a: U32) -> String:  +a8 = U32.and(a, 255)  +i0 = U32.shrn(a8, 2n)  +i1 = U32.shln(U32.and(a8, 3), 4n)  SCon{Base64.char(i0), SCon{Base64.char(i1), SCon{'=', SCon{'=', SNil{}}}}}# Encode a final two-byte group (three alphabet chars and = padding).def Base64.encode.two(+a: U32, +b: U32) -> String:  +a8 = U32.and(a, 255)  +b8 = U32.and(b, 255)  +i0 = U32.shrn(a8, 2n)  +i1 = U32.or(U32.shln(U32.and(a8, 3), 4n), U32.shrn(b8, 4n))  +i2 = U32.shln(U32.and(b8, 15), 2n)  SCon{Base64.char(i0), SCon{Base64.char(i1),    SCon{Base64.char(i2), SCon{'=', SNil{}}}}}# Encode octets. The result is padded to a multiple of four characters.def Base64.encode.go(xs: List<&2, U32>) -> String:  match xs:    case Nil{}:      SNil{}    case +a <> Nil{}:      Base64.encode.one(a)    case +a <> +b <> Nil{}:      Base64.encode.two(a, b)    case +a <> +b <> +c <> t:      String.append(Base64.encode.group(a, b, c), Base64.encode.go(t))def Base64.encode(xs: List<&2, U32>) -> String:  Base64.encode.go(xs)# Convert a standard Base64 alphabet character to its sextet value.def Base64.value(+c: Char) -> Maybe<&2, U32>:  +x = Char.to_u32(c)  Bool.pick(Maybe<&2, U32>,    Bool.and(U32.is_ge(x, 65), U32.is_le(x, 90)),    Some{U32.sub(x, 65)},    Bool.pick(Maybe<&2, U32>,      Bool.and(U32.is_ge(x, 97), U32.is_le(x, 122)),      Some{U32.add(26, U32.sub(x, 97))},      Bool.pick(Maybe<&2, U32>,        Bool.and(U32.is_ge(x, 48), U32.is_le(x, 57)),        Some{U32.add(52, U32.sub(x, 48))},        Bool.pick(Maybe<&2, U32>, Char.is_eq(c, '+'), Some{62},          Bool.pick(Maybe<&2, U32>, Char.is_eq(c, '/'), Some{63}, None{})))))# Prepend a decoded group to an already decoded tail, propagating failure.def Base64.decode.prefix(  prefix: List<&2, U32>, m: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:  match m:    case None{}:      None{}    case Some{rest}:      Some{List.append(&2, U32, prefix, rest)}# Padding is legal only on the final quartet.def Base64.decode.end(  prefix: List<&2, U32>, r: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:  match r:    case Some{Nil{}}:      Some{prefix}    case _:      None{}# Decode a quartet with no padding into three octets.def Base64.decode.full(  r: Maybe<&2, List<&2, U32>>,  ma: Maybe<&2, U32>, mb: Maybe<&2, U32>,  mc: Maybe<&2, U32>, md: Maybe<&2, U32>) -> Maybe<&2, List<&2, U32>>:  match ma mb mc md:    case Some{+x0} Some{+x1} Some{+x2} Some{+x3}:      +o0 = U32.or(U32.shln(x0, 2n), U32.shrn(x1, 4n))      +o1 = U32.or(U32.shln(U32.and(x1, 15), 4n), U32.shrn(x2, 2n))      +o2 = U32.or(U32.shln(U32.and(x2, 3), 6n), x3)      Base64.decode.prefix(o0 <> o1 <> o2 <> Nil{}, r)    case _ _ _ _:      None{}# Decode a quartet ending in one '=' into two octets. RFC 4648 requires the# unused low four bits of the second sextet to be zero (canonical padding).def Base64.decode.one_pad(  r: Maybe<&2, List<&2, U32>>,  ma: Maybe<&2, U32>, mb: Maybe<&2, U32>, mc: Maybe<&2, U32>) -> Maybe<&2, List<&2, U32>>:  match ma mb mc:    case Some{+x0} Some{+x1} Some{+x2}:      Bool.pick(Maybe<&2, List<&2, U32>>,        U32.is_eq(U32.and(x2, 3), 0),        Base64.decode.end(          U32.or(U32.shln(x0, 2n), U32.shrn(x1, 4n)) <>            U32.or(U32.shln(U32.and(x1, 15), 4n), U32.shrn(x2, 2n)) <>            Nil{}, r),        None{})    case _ _ _:      None{}# Decode a quartet ending in '==' into one octet. Canonical padding requires# the unused low four bits of the second sextet to be zero.def Base64.decode.two_pad(  r: Maybe<&2, List<&2, U32>>,  ma: Maybe<&2, U32>, mb: Maybe<&2, U32>) -> Maybe<&2, List<&2, U32>>:  match ma mb:    case Some{+x0} Some{+x1}:      Bool.pick(Maybe<&2, List<&2, U32>>,        U32.is_eq(U32.and(x1, 15), 0),        Base64.decode.end(          U32.or(U32.shln(x0, 2n), U32.shrn(x1, 4n)) <> Nil{}, r),        None{})    case _ _:      None{}# Decode one quartet. Whitespace is not accepted; callers should normalize it# explicitly if a non-RFC transport requires whitespace tolerance.def Base64.decode.quartet(  +a: Char, +b: Char, +c: Char, +d: Char,  +r: Maybe<&2, List<&2, U32>>) -> Maybe<&2, List<&2, U32>>:  +ma = Base64.value(a)  +mb = Base64.value(b)  +mc = Base64.value(c)  +md = Base64.value(d)  Bool.pick(Maybe<&2, List<&2, U32>>, Char.is_eq(c, '='),    Bool.pick(Maybe<&2, List<&2, U32>>, Char.is_eq(d, '='),      Base64.decode.two_pad(r, ma, mb), None{}),    Bool.pick(Maybe<&2, List<&2, U32>>, Char.is_eq(d, '='),      Base64.decode.one_pad(r, ma, mb, mc),      Base64.decode.full(r, ma, mb, mc, md)))# Decode padded RFC 4648 Base64. Invalid input returns None.def Base64.decode.go(s: String) -> Maybe<&2, List<&2, U32>>:  match s:    case SNil{}:      Some{Nil{}}    case SCon{a, SCon{b, SCon{c, SCon{d, tail}}}}:      +r = Base64.decode.go(tail)      Base64.decode.quartet(a, b, c, d, r)    case _:      None{}def Base64.decode(s: String) -> Maybe<&2, List<&2, U32>>:  Base64.decode.go(s)