rusthound_ce/utils/
b64.rs1pub fn b64_encode(input: &[u8]) -> String {
8 const TABLE: &[u8] =
9 b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
10
11 let mut out = String::with_capacity((input.len() + 2) / 3 * 4);
12 for chunk in input.chunks(3) {
13 let b0 = chunk[0] as usize;
14 let b1 = if chunk.len() > 1 { chunk[1] as usize } else { 0 };
15 let b2 = if chunk.len() > 2 { chunk[2] as usize } else { 0 };
16
17 out.push(TABLE[(b0 >> 2) & 0x3f] as char);
18 out.push(TABLE[((b0 << 4) | (b1 >> 4)) & 0x3f] as char);
19 out.push(if chunk.len() > 1 {
20 TABLE[((b1 << 2) | (b2 >> 6)) & 0x3f] as char
21 } else {
22 '='
23 });
24 out.push(if chunk.len() > 2 {
25 TABLE[b2 & 0x3f] as char
26 } else {
27 '='
28 });
29 }
30 out
31}
32
33pub fn b64_decode(input: &str) -> Option<Vec<u8>> {
35 fn val(c: u8) -> Option<u8> {
36 match c {
37 b'A'..=b'Z' => Some(c - b'A'),
38 b'a'..=b'z' => Some(c - b'a' + 26),
39 b'0'..=b'9' => Some(c - b'0' + 52),
40 b'+' => Some(62),
41 b'/' => Some(63),
42 _ => None,
43 }
44 }
45
46 let bytes: Vec<u8> = input
48 .bytes()
49 .filter(|&b| b != b'=' && b != b'\r' && b != b'\n' && b != b' ')
50 .collect();
51
52 if bytes.len() % 4 == 1 {
53 return None; }
55
56 let mut out = Vec::with_capacity(bytes.len() * 3 / 4);
57 let mut i = 0;
58 while i + 1 < bytes.len() {
59 let v0 = val(bytes[i])?;
60 let v1 = val(bytes[i + 1])?;
61 out.push((v0 << 2) | (v1 >> 4));
62 if i + 2 < bytes.len() {
63 let v2 = val(bytes[i + 2])?;
64 out.push((v1 << 4) | (v2 >> 2));
65 if i + 3 < bytes.len() {
66 let v3 = val(bytes[i + 3])?;
67 out.push((v2 << 6) | v3);
68 }
69 }
70 i += 4;
71 }
72 Some(out)
73}
74
75#[cfg(test)]
78mod tests {
79 use super::*;
80
81 #[test]
82 fn rfc4648_vectors() {
83 assert_eq!(b64_encode(b""), "");
85 assert_eq!(b64_encode(b"f"), "Zg==");
86 assert_eq!(b64_encode(b"fo"), "Zm8=");
87 assert_eq!(b64_encode(b"foo"), "Zm9v");
88 assert_eq!(b64_encode(b"foob"), "Zm9vYg==");
89 assert_eq!(b64_encode(b"fooba"), "Zm9vYmE=");
90 assert_eq!(b64_encode(b"foobar"), "Zm9vYmFy");
91 }
92
93 #[test]
94 fn decode_rfc4648_vectors() {
95 assert_eq!(b64_decode(""), Some(b"".to_vec()));
96 assert_eq!(b64_decode("Zg=="), Some(b"f".to_vec()));
97 assert_eq!(b64_decode("Zm8="), Some(b"fo".to_vec()));
98 assert_eq!(b64_decode("Zm9v"), Some(b"foo".to_vec()));
99 assert_eq!(b64_decode("Zm9vYg=="), Some(b"foob".to_vec()));
100 assert_eq!(b64_decode("Zm9vYmE="), Some(b"fooba".to_vec()));
101 assert_eq!(b64_decode("Zm9vYmFy"), Some(b"foobar".to_vec()));
102 }
103
104 #[test]
105 fn roundtrip_arbitrary_bytes() {
106 let data: Vec<u8> = (0u8..=255).collect();
107 let encoded = b64_encode(&data);
108 let decoded = b64_decode(&encoded).expect("roundtrip decode failed");
109 assert_eq!(data, decoded);
110 }
111
112 #[test]
113 fn decode_without_padding() {
114 assert_eq!(b64_decode("Zg"), Some(b"f".to_vec()));
116 assert_eq!(b64_decode("Zm8"), Some(b"fo".to_vec()));
117 }
118
119 #[test]
120 fn decode_invalid_char_returns_none() {
121 assert_eq!(b64_decode("TQ!Q"), None);
122 assert_eq!(b64_decode("TQ@Q"), None);
123 }
124
125 #[test]
126 fn decode_impossible_length_returns_none() {
127 assert_eq!(b64_decode("A"), None);
129 }
130
131 #[test]
132 fn decode_ignores_whitespace_and_padding() {
133 assert_eq!(b64_decode("Zm9v\r\nYmFy"), Some(b"foobar".to_vec()));
135 assert_eq!(b64_decode("Zm9vYmFy=="), Some(b"foobar".to_vec()));
136 }
137}