Skip to main content

hammer/
base64.rs

1//! Binary content, such as an exported file or an entry's image, which operations carry as base64 text.
2
3use serde::{Deserialize, Deserializer, Serializer};
4
5const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
6
7pub fn encode(bytes: &[u8]) -> String {
8    let mut out = String::with_capacity(bytes.len().div_ceil(3) * 4);
9    for chunk in bytes.chunks(3) {
10        let n = chunk.iter().enumerate().fold(0u32, |n, (i, b)| n | (*b as u32) << (16 - 8 * i));
11        for i in 0..4 {
12            out.push(if i <= chunk.len() { ALPHABET[(n >> (18 - 6 * i) & 63) as usize] as char } else { '=' });
13        }
14    }
15    out
16}
17
18pub fn decode(text: &str) -> Option<Vec<u8>> {
19    let mut out = Vec::with_capacity(text.len() / 4 * 3);
20    let (mut n, mut bits) = (0u32, 0);
21    for c in text.bytes().filter(|c| *c != b'=') {
22        n = n << 6 | ALPHABET.iter().position(|a| *a == c)? as u32;
23        bits += 6;
24        if bits >= 8 {
25            bits -= 8;
26            out.push((n >> bits) as u8);
27        }
28    }
29    Some(out)
30}
31
32pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
33    serializer.serialize_str(&encode(bytes))
34}
35
36pub mod bytes {
37    use super::*;
38
39    pub fn serialize<S: Serializer>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error> {
40        super::serialize(bytes, serializer)
41    }
42
43    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Vec<u8>, D::Error> {
44        let text = String::deserialize(deserializer)?;
45        decode(&text).ok_or_else(|| serde::de::Error::custom("invalid base64"))
46    }
47}
48
49pub mod option {
50    use super::*;
51
52    pub fn serialize<S: Serializer>(bytes: &Option<Vec<u8>>, serializer: S) -> Result<S::Ok, S::Error> {
53        match bytes {
54            Some(bytes) => super::serialize(bytes, serializer),
55            None => serializer.serialize_none(),
56        }
57    }
58
59    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Option<Vec<u8>>, D::Error> {
60        Option::<String>::deserialize(deserializer)?
61            .map(|text| decode(&text).ok_or_else(|| serde::de::Error::custom("invalid base64")))
62            .transpose()
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn round_trips() {
72        for bytes in [&b""[..], b"f", b"fo", b"foo", b"foob", b"fooba", b"foobar", &[0, 255, 128, 7]] {
73            assert_eq!(decode(&encode(bytes)).unwrap(), bytes);
74        }
75        assert_eq!(encode(b"foobar"), "Zm9vYmFy");
76        assert_eq!(encode(b"fo"), "Zm8=");
77    }
78}