Skip to main content

wowlab_parsers/parsers/parcel/
base64.rs

1//! Strict standard-base64 helpers (SPEC ยง1.3: canonical alphabet, required padding, no trailing bits).
2
3use base64::{Engine as _, engine::general_purpose::STANDARD};
4
5use super::errors::ParcelError;
6
7// Checked before structural decode so the codec returns `ERR_BASE64_TOKEN` first.
8pub(super) fn is_base64_alphabet(s: &str) -> bool {
9    s.bytes().all(|b| {
10        matches!(
11            b,
12            b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'+' | b'/' | b'='
13        )
14    })
15}
16
17pub(super) fn encode(bytes: &[u8]) -> String {
18    STANDARD.encode(bytes)
19}
20
21pub(super) fn decode(s: &str) -> Result<Vec<u8>, ParcelError> {
22    Ok(STANDARD.decode(s)?)
23}
24
25#[cfg(test)]
26mod tests {
27    use googletest::prelude::*;
28
29    use super::*;
30
31    #[gtest]
32    fn round_trip_basic() -> Result<()> {
33        verify_that!(encode(b""), eq(""))?;
34        verify_that!(encode(b"hello"), eq("aGVsbG8="))?;
35        verify_that!(decode("aGVsbG8=").or_fail()?, eq(b"hello"))?;
36        verify_that!(decode("").or_fail()?, eq(b""))?;
37
38        Ok(())
39    }
40
41    #[gtest]
42    fn rejects_missing_or_extra_padding() -> Result<()> {
43        verify_true!(decode("aGVsbG8").is_err())?;
44        verify_true!(decode("aGVsbG8==").is_err())?;
45
46        Ok(())
47    }
48
49    #[gtest]
50    fn rejects_non_canonical_padding() -> Result<()> {
51        for s in ["AB==", "AC==", "AD==", "AAB=", "AAC=", "AAD="] {
52            verify_true!(decode(s).is_err())?;
53        }
54
55        Ok(())
56    }
57
58    #[gtest]
59    fn accepts_canonical_padding() -> Result<()> {
60        verify_that!(decode("AA==").or_fail()?, eq(b"\0"))?;
61        verify_that!(decode("AAA=").or_fail()?, eq(b"\0\0"))?;
62        verify_that!(decode("AAAA").or_fail()?, eq(b"\0\0\0"))?;
63
64        Ok(())
65    }
66
67    #[gtest]
68    fn alphabet_check() -> Result<()> {
69        verify_true!(is_base64_alphabet("aGVsbG8="))?;
70        verify_true!(is_base64_alphabet("ABCabc012+/="))?;
71        verify_true!(is_base64_alphabet(""))?;
72        verify_false!(is_base64_alphabet("aGVs*G8="))?;
73        verify_false!(is_base64_alphabet("aGVs G8="))?;
74        verify_false!(is_base64_alphabet("aGVs\nG8="))?;
75        verify_false!(is_base64_alphabet("aGVs-G8="))?;
76
77        Ok(())
78    }
79}