1const STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4
5pub fn base64_encode(input: &[u8], out: &mut Vec<u8>) {
7 for c in input.chunks(3) {
8 let n = (u32::from(c[0]) << 16) | (u32::from(*c.get(1).unwrap_or(&0)) << 8) | u32::from(*c.get(2).unwrap_or(&0));
9 for k in 0..4 {
10 out.push(if k <= c.len() { STD[(n >> (18 - 6 * k) & 63) as usize] } else { b'=' });
11 }
12 }
13}
14
15fn value(b: u8, url: bool) -> Option<u32> {
16 Some(match b {
17 b'A'..=b'Z' => b - b'A',
18 b'a'..=b'z' => b - b'a' + 26,
19 b'0'..=b'9' => b - b'0' + 52,
20 b'+' if !url => 62,
21 b'/' if !url => 63,
22 b'-' if url => 62,
23 b'_' if url => 63,
24 _ => return None,
25 } as u32)
26}
27
28fn decode(input: &[u8], url: bool, out: &mut Vec<u8>) -> bool {
29 let body = if url {
30 if input.len() % 4 == 1 {
31 return false;
32 }
33 input
34 } else {
35 if input.len() % 4 != 0 {
36 return false;
37 }
38 let pad = input.iter().rev().take(2).take_while(|&&b| b == b'=').count();
39 &input[..input.len() - pad]
40 };
41 let start = out.len();
42 for c in body.chunks(4) {
43 let mut n = 0u32;
44 for (k, &b) in c.iter().enumerate() {
45 let Some(v) = value(b, url) else {
46 out.truncate(start);
47 return false;
48 };
49 n |= v << (18 - 6 * k);
50 }
51 let bytes = [(n >> 16) as u8, (n >> 8) as u8, n as u8];
52 let keep = c.len() - 1;
53 if c.len() == 1 || (keep < 3 && n & (0xff_ffff >> (8 * keep)) != 0) {
54 out.truncate(start);
56 return false;
57 }
58 out.extend_from_slice(&bytes[..keep]);
59 }
60 true
61}
62
63pub fn base64_decode(input: &[u8], out: &mut Vec<u8>) -> bool {
66 decode(input, false, out)
67}
68
69pub fn base64url_decode(input: &[u8], out: &mut Vec<u8>) -> bool {
72 decode(input, true, out)
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn rfc4648_vectors_round_trip() {
81 for (plain, enc) in [
82 ("", ""),
83 ("f", "Zg=="),
84 ("fo", "Zm8="),
85 ("foo", "Zm9v"),
86 ("foob", "Zm9vYg=="),
87 ("fooba", "Zm9vYmE="),
88 ("foobar", "Zm9vYmFy"),
89 ] {
90 let mut out = Vec::new();
91 base64_encode(plain.as_bytes(), &mut out);
92 assert_eq!(out, enc.as_bytes());
93 let mut back = Vec::new();
94 assert!(base64_decode(enc.as_bytes(), &mut back), "{enc}");
95 assert_eq!(back, plain.as_bytes());
96 let mut url = Vec::new();
97 assert!(base64url_decode(enc.trim_end_matches('=').as_bytes(), &mut url));
98 assert_eq!(url, plain.as_bytes());
99 }
100 }
101
102 #[test]
103 fn rejects_non_canonical_input() {
104 for bad in ["Zg", "Zh==", "Zm9=", "Z===", "Zm9v=", "Zm 9v", "Zm9v\n", "Zg=a", "-_==", "===="] {
105 let mut out = Vec::new();
106 assert!(!base64_decode(bad.as_bytes(), &mut out), "{bad}");
107 assert!(out.is_empty());
108 }
109 for bad in ["Z", "Zh", "+/", "Zg=="] {
110 assert!(!base64url_decode(bad.as_bytes(), &mut Vec::new()), "{bad}");
111 }
112 let mut out = Vec::new();
113 assert!(base64url_decode(b"-_8", &mut out));
114 assert_eq!(out, [0xfb, 0xff]);
115 }
116}