Skip to main content

simoom_http1/
b64.rs

1//! Base64 (RFC 4648), strict: canonical encodings only.
2
3const STD: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
4
5/// Appends the padded standard base64 encoding of `input` to `out`.
6pub 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            // A lone character, or non-zero bits after the last whole byte: not canonical.
55            out.truncate(start);
56            return false;
57        }
58        out.extend_from_slice(&bytes[..keep]);
59    }
60    true
61}
62
63/// Decodes padded standard base64 into `out`; false (and `out` unchanged) if it is not
64/// canonical base64.
65pub fn base64_decode(input: &[u8], out: &mut Vec<u8>) -> bool {
66    decode(input, false, out)
67}
68
69/// Decodes unpadded base64url (as in `HTTP2-Settings`) into `out`; false (and `out`
70/// unchanged) if it is not canonical.
71pub 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}