Skip to main content

ravenrs_report/
base64.rs

1//! Base64, for images carried in layouts and in printable HTML.
2
3/// Encodes `bytes`.
4pub fn encode(bytes: &[u8]) -> String {
5    const TABLE: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6    let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
7    for chunk in bytes.chunks(3) {
8        let n = u32::from(chunk[0]) << 16
9            | u32::from(chunk.get(1).copied().unwrap_or(0)) << 8
10            | u32::from(chunk.get(2).copied().unwrap_or(0));
11        out.push(TABLE[(n >> 18) as usize & 63] as char);
12        out.push(TABLE[(n >> 12) as usize & 63] as char);
13        out.push(if chunk.len() > 1 { TABLE[(n >> 6) as usize & 63] as char } else { '=' });
14        out.push(if chunk.len() > 2 { TABLE[n as usize & 63] as char } else { '=' });
15    }
16    out
17}
18
19/// Decodes base64 text, ignoring whitespace; `None` when it is not valid.
20pub fn decode(text: &str) -> Option<Vec<u8>> {
21    let mut out = Vec::with_capacity(text.len() / 4 * 3);
22    let (mut buffer, mut bits) = (0_u32, 0_u32);
23    let mut padding = 0;
24    for byte in text.bytes() {
25        let value = match byte {
26            b'A'..=b'Z' => byte - b'A',
27            b'a'..=b'z' => byte - b'a' + 26,
28            b'0'..=b'9' => byte - b'0' + 52,
29            b'+' => 62,
30            b'/' => 63,
31            b'=' => {
32                padding += 1;
33                continue;
34            }
35            b' ' | b'\n' | b'\r' | b'\t' => continue,
36            _ => return None,
37        };
38        if padding > 0 {
39            return None;
40        }
41        buffer = (buffer << 6) | u32::from(value);
42        bits += 6;
43        if bits >= 8 {
44            bits -= 8;
45            out.push((buffer >> bits) as u8);
46            buffer &= (1 << bits) - 1;
47        }
48    }
49    Some(out)
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn matches_the_standard() {
58        assert_eq!(encode(b""), "");
59        assert_eq!(encode(b"f"), "Zg==");
60        assert_eq!(encode(b"fo"), "Zm8=");
61        assert_eq!(encode(b"foo"), "Zm9v");
62        assert_eq!(encode(b"foobar"), "Zm9vYmFy");
63    }
64
65    #[test]
66    fn decoding_undoes_encoding_and_refuses_bad_text() {
67        for bytes in [&b""[..], b"f", b"fo", b"foo", b"foobar", &[0, 255, 128, 7]] {
68            assert_eq!(decode(&encode(bytes)).as_deref(), Some(bytes));
69        }
70        assert_eq!(decode("Zm 9v\n").as_deref(), Some(&b"foo"[..]), "whitespace is ignored");
71        assert_eq!(decode("Zm9v!"), None);
72        assert_eq!(decode("Zg==Zg"), None, "nothing after the padding");
73    }
74}