src/b64.bend
emerging-ezhttp@0.14.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{})