Skip to main content

reddb_types/
encoding.rs

1const BASE64_ALPHABET: &[u8; 64] =
2    b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
3const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
4
5/// Encode bytes with the padded RFC 4648 standard base64 alphabet.
6pub fn base64_encode(input: &[u8]) -> String {
7    let mut out = String::with_capacity(input.len().div_ceil(3) * 4);
8    let mut chunks = input.chunks_exact(3);
9    for chunk in chunks.by_ref() {
10        let bits = ((chunk[0] as u32) << 16) | ((chunk[1] as u32) << 8) | chunk[2] as u32;
11        out.push(BASE64_ALPHABET[((bits >> 18) & 0x3f) as usize] as char);
12        out.push(BASE64_ALPHABET[((bits >> 12) & 0x3f) as usize] as char);
13        out.push(BASE64_ALPHABET[((bits >> 6) & 0x3f) as usize] as char);
14        out.push(BASE64_ALPHABET[(bits & 0x3f) as usize] as char);
15    }
16    match chunks.remainder() {
17        [] => {}
18        [first] => {
19            let bits = (*first as u32) << 16;
20            out.push(BASE64_ALPHABET[((bits >> 18) & 0x3f) as usize] as char);
21            out.push(BASE64_ALPHABET[((bits >> 12) & 0x3f) as usize] as char);
22            out.push_str("==");
23        }
24        [first, second] => {
25            let bits = ((*first as u32) << 16) | ((*second as u32) << 8);
26            out.push(BASE64_ALPHABET[((bits >> 18) & 0x3f) as usize] as char);
27            out.push(BASE64_ALPHABET[((bits >> 12) & 0x3f) as usize] as char);
28            out.push(BASE64_ALPHABET[((bits >> 6) & 0x3f) as usize] as char);
29            out.push('=');
30        }
31        _ => unreachable!(),
32    }
33    out
34}
35
36/// Escape string content for insertion between JSON double quotes.
37pub fn json_escape(input: &str) -> String {
38    let mut out = String::with_capacity(input.len());
39    for character in input.chars() {
40        match character {
41            '"' => out.push_str("\\\""),
42            '\\' => out.push_str("\\\\"),
43            '\n' => out.push_str("\\n"),
44            '\r' => out.push_str("\\r"),
45            '\t' => out.push_str("\\t"),
46            control if (control as u32) < 0x20 => {
47                let byte = control as usize;
48                out.push_str("\\u00");
49                out.push(HEX_DIGITS[byte >> 4] as char);
50                out.push(HEX_DIGITS[byte & 0x0f] as char);
51            }
52            other => out.push(other),
53        }
54    }
55    out
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61
62    #[test]
63    fn base64_standard_vectors() {
64        let vectors: &[(&[u8], &str)] = &[
65            (b"", ""),
66            (b"f", "Zg=="),
67            (b"fo", "Zm8="),
68            (b"foo", "Zm9v"),
69            (b"foob", "Zm9vYg=="),
70            (b"fooba", "Zm9vYmE="),
71            (b"foobar", "Zm9vYmFy"),
72            (&[0xfb, 0xff, 0xbf], "+/+/"),
73        ];
74
75        for (input, expected) in vectors {
76            assert_eq!(base64_encode(input), *expected);
77        }
78    }
79
80    #[test]
81    fn json_escape_vectors() {
82        let vectors = [
83            ("", ""),
84            ("plain / text", "plain / text"),
85            ("\"\\\n\r\t", "\\\"\\\\\\n\\r\\t"),
86            ("\0\u{0008}\u{000c}\u{001f}", "\\u0000\\u0008\\u000c\\u001f"),
87            ("olá 🧪", "olá 🧪"),
88        ];
89
90        for (input, expected) in vectors {
91            assert_eq!(json_escape(input), expected);
92        }
93    }
94}