1 # Base 64 encoding and decoding
3 BASE64_STANDARD_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".utf8()
4 BASE64_STANDARD_DECODER : {Byte:Byte} = {b:Byte(i-1) for i,b in BASE64_STANDARD_ALPHABET}
6 BASE64_URL_ALPHABET := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".utf8()
7 BASE64_URL_DECODER : {Byte:Byte} = {b:Byte(i-1) for i,b in BASE64_STANDARD_ALPHABET}
9 _EQUAL_BYTE := Byte(0x3D)
13 func encode_text(text:Text, alphabet:[Byte]=BASE64_STANDARD_ALPHABET -> Base64)
14 return Base64.encode_bytes(text.utf8(), alphabet=alphabet)
16 func encode_bytes(bytes:[Byte], alphabet:[Byte]=BASE64_STANDARD_ALPHABET -> Base64)
20 while src + 2 <= Int64(bytes.length)
26 output.insert(alphabet[1 + (b1 >> 2)]!)
27 output.insert(alphabet[1 + (((b1 and 0b11) << 4) or (b2 >> 4))]!)
28 output.insert(alphabet[1 + (((b2 and 0b1111) << 2) or (b3 >> 6))]!)
29 output.insert(alphabet[1 + (b3 and 0b111111)]!)
31 if src + 1 == bytes.length
34 output.insert(alphabet[1 + (b1 >> 2)]!)
35 output.insert(alphabet[1 + (((b1 and 0b11) << 4) or (b2 >> 4))]!)
36 output.insert(alphabet[1 + ((b2 and 0b1111) << 2)]!)
37 output.insert(_EQUAL_BYTE)
38 else if src == bytes.length
40 output.insert(alphabet[1 + (b1 >> 2)]!)
41 output.insert(alphabet[1 + ((b1 and 0b11) << 4)]!)
42 output.insert(_EQUAL_BYTE)
43 output.insert(_EQUAL_BYTE)
45 return Base64.from_text(Text.from_utf8(output[])!)
47 func decode_text(b64:Base64 -> Text?)
48 bytes := b64.decode_bytes() or return none
49 return Text.from_utf8(bytes)
51 func decode_bytes(b64:Base64, decoder:{Byte:Byte}=BASE64_STANDARD_DECODER -> [Byte]?)
54 if bytes.length mod 4 != 0
58 while src + 3 <= Int64(bytes.length)
59 is_last_chunk := src + 3 >= bytes.length
68 if is_last_chunk and b4 == _EQUAL_BYTE
69 x1 := decoder[b1] or return none
70 x2 := decoder[b2] or return none
71 output.insert((x1 << 2) or (x2 >> 4))
72 if b3 != _EQUAL_BYTE and b4 == _EQUAL_BYTE
73 x3 := decoder[b3] or return none
74 output.insert((x2 << 4) or (x3 >> 2))
76 x1 := decoder[b1] or return none
77 x2 := decoder[b2] or return none
78 x3 := decoder[b3] or return none
79 x4 := decoder[b4] or return none
80 output.insert((x1 << 2) or (x2 >> 4))
81 output.insert(((x2 and 0b1111) << 4) or (x3 >> 2))
82 output.insert(((x3 and 0b11) << 6) or x4)
88 for test in ["", "A", "AB", "ABC", "ABCD", "ABCDE", "ABCDEF", "ABCDEFG", "ABCDEFGH", "ABCDEFGHI"]
89 b64 := Base64.encode_text(test)
90 assert b64.decode_text() == test
92 for test in [[Byte(0xFF)], [Byte(0xFF), 0xFE]]
93 b64 := Base64.encode_bytes(test)
94 assert b64.decode_bytes() == test
96 b64url := Base64.encode_bytes(test, alphabet=BASE64_URL_ALPHABET)
97 assert b64url.decode_bytes(decoder=BASE64_URL_DECODER) == test
99 all_bytes := [Byte(i) for i in (0).to(255)]
101 bytes := [all_bytes.random() for _ in len]
102 assert Base64.encode_bytes(bytes).decode_bytes() == bytes
104 func main(input|i=(/dev/stdin), output|o=(/dev/stdout), url|u=no, decode|d=no, test|t=no)
108 text := input.read()!.trim(" \r\n")
109 b := Base64.from_text(text)
110 decoder := if url then BASE64_URL_DECODER else BASE64_STANDARD_DECODER
111 (/dev/stdout).write_bytes(b.decode_bytes(decoder=decoder) or exit("Invalid Base64 encoding!"))!
113 bytes := input.read_bytes()!
114 alphabet := if url then BASE64_URL_ALPHABET else BASE64_STANDARD_ALPHABET
115 say(Base64.encode_bytes(bytes, alphabet=alphabet).text)