src/b64.bend

emerging-ezhttp@0.13.0.0 · 87 lines · 0 definitions

1# ezhttp/src/b64: Base64 encoding for HTTP credentials (RFC 4648 §4). The
2# alphabet is the standard table, with `=` padding. Decoding is not required
3# for the Authorization helpers.
4import Base
5
6# how many octets of the current quantum are still missing
7type Phase is Data:
8  Need3{}
9  Need2{first: U32}
10  Need1{first: U32, second: U32}
11
12# RFC 4648 §4 alphabet, index 0 is `A`
13def b64.table() -> String:
14  "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
15
16# the character at a 0..63 index, `A` when the index is out of the table
17def b64.char(m: Maybe<&2, Char>) -> Char:
18  match m:
19    case None{}:
20      'A'
21    case Some{c}:
22      c
23
24# one 6-bit value as an alphabet character
25def b64.at(+n: U32) -> Char:
26  b64.char(String.get(b64.table(), U32.to_nat(U32.and(n, 63))))
27
28# 24 bits from three octets
29def b64.pack(+a: U32, +b: U32, +c: U32) -> U32:
30  U32.or(U32.or(U32.shln(a, 16n), U32.shln(b, 8n)), c)
31
32# the last two characters when the quantum was short
33def b64.tail.more(+n: U32, left: Nat) -> String:
34  match left:
35    case 0n:
36      SCon{b64.at(U32.shrn(n, 6n)), SCon{'=', SNil{}}}
37    case 1n+k:
38      SCon{'=', SCon{'=', SNil{}}}
39
40# four characters from a packed quantum; pad is how many `=` to write
41def b64.tail(+n: U32, pad: Nat) -> String:
42  match pad:
43    case 0n:
44      SCon{b64.at(U32.shrn(n, 6n)), SCon{b64.at(n), SNil{}}}
45    case 1n+p:
46      b64.tail.more(n, p)
47
48# one quantum as four characters
49def b64.quad(+a: U32, +b: U32, +c: U32, pad: Nat) -> String:
50  +n = b64.pack(a, b, c)
51  SCon{b64.at(U32.shrn(n, 18n)), SCon{b64.at(U32.shrn(n, 12n)), b64.tail(n, pad)}}
52
53# padding for a quantum that ended early
54def b64.end(phase: Phase) -> String:
55  match phase:
56    case Need3{}:
57      ""
58    case Need2{first}:
59      b64.quad(first, 0, 0, 2n)
60    case Need1{first, second}:
61      b64.quad(first, second, 0, 1n)
62
63# octets to Base64, three at a time (RFC 4648 §4)
64def b64.go(bs: List<&2, U32>, phase: Phase) -> String:
65  match bs:
66    case []:
67      b64.end(phase)
68    case h <> t:
69      match phase:
70        case Need3{}:
71          b64.go(t, Need2{h})
72        case Need2{first}:
73          b64.go(t, Need1{first, h})
74        case Need1{first, second}:
75          b64.quad(first, second, h, 0n) ++ b64.go(t, Need3{})
76
77# code points of a string, one octet each (credentials are ASCII)
78def b64.bytes(s: String) -> List<&2, U32>:
79  match s:
80    case SNil{}:
81      []
82    case SCon{h, t}:
83      Char.to_u32(h) <> b64.bytes(t)
84
85# Base64 of a string (RFC 4648 §4)
86def b64.encode(s: String) -> String:
87  b64.go(b64.bytes(s), Need3{})