use super::funejson::Value; fn sextet(b: u8) -> i64 { match b { b'A'..=b'Z' => (b - b'A') as i64, b'a'..=b'z' => (b - b'a' + 26) as i64, b'0'..=b'9' => (b - b'0' + 52) as i64, b'+' => 62, b'/' => 63, _ => -1, } } /// Standard, padded base64 back to bytes, strictly: no whitespace, no /// characters outside the alphabet, padding to a multiple of four, and zero /// bits after the last byte, so every byte string has exactly one spelling. /// /// # Panics /// Panics on any of those. pub fn base64_decode(text: &str) -> Vec { let mut values: Vec = Vec::with_capacity(text.len()); let mut padding = 0usize; // Bytes, not chars: every accepted character is ASCII, and any byte of a // multi-byte character fails the alphabet check just as the character does. for &b in text.as_bytes() { if b == b'=' { padding += 1; continue; } let v = sextet(b); if v < 0 || padding > 0 { panic!("base64 text may only contain A-Z, a-z, 0-9, + and /, with = padding at the end"); } values.push(v); } if text.len() % 4 != 0 { panic!("base64 text must be a multiple of 4 characters long, received {}", text.len()); } if padding > 2 { panic!("base64 text has too much = padding"); } let mut out = Vec::with_capacity(values.len() / 4 * 3); let mut chunks = values.chunks_exact(4); for c in &mut chunks { let n = (c[0] << 18) | (c[1] << 12) | (c[2] << 6) | c[3]; out.extend_from_slice(&[(n >> 16) & 255, (n >> 8) & 255, n & 255]); } match chunks.remainder() { [a, b] => { if b & 15 != 0 { panic!("base64 text has non-zero bits after its last byte"); } out.push(((a << 2) | (b >> 4)) & 255); } [a, b, c] => { if c & 3 != 0 { panic!("base64 text has non-zero bits after its last byte"); } let n = (a << 18) | (b << 12) | (c << 6); out.extend_from_slice(&[(n >> 16) & 255, (n >> 8) & 255]); } _ => {} } out } pub fn fune_vector(args: &[Value]) -> Value { let text = match &args[0] { Value::Str(s) => s.as_str(), _ => panic!("base64 text must be a string"), }; Value::Arr(base64_decode(text).into_iter().map(Value::Int).collect()) }