diff options
author | Daniel Baumann <daniel@debian.org> | 2024-11-20 07:48:15 +0100 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-11-20 07:48:15 +0100 |
commit | 08ee41641952141f57e87048dc4867a800f4587a (patch) | |
tree | 89107d02a7a5e84ba38b4a6458daf09074b990b6 /src/base64_decode.rs | |
parent | Initial commit. (diff) | |
download | rust-auth-git2-08ee41641952141f57e87048dc4867a800f4587a.tar.xz rust-auth-git2-08ee41641952141f57e87048dc4867a800f4587a.zip |
Adding upstream version 0.5.5.upstream/0.5.5upstream
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to 'src/base64_decode.rs')
-rw-r--r-- | src/base64_decode.rs | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/src/base64_decode.rs b/src/base64_decode.rs new file mode 100644 index 0000000..de8147c --- /dev/null +++ b/src/base64_decode.rs @@ -0,0 +1,110 @@ +/// An error that can occur during base64 decoding. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Error { + InvalidBase64Char(u8), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InvalidBase64Char(value) => write!(f, "Invalid base64 character: {:?}", char::from_u32(*value as u32).unwrap()), + } + } +} + +/// Decode a base64 string. +/// +/// Padding in the input is optional. +pub fn base64_decode(input: &[u8]) -> Result<Vec<u8>, Error> { + let input = match input.iter().rposition(|&byte| byte != b'=' && !byte.is_ascii_whitespace()) { + Some(x) => &input[..=x], + None => return Ok(Vec::new()), + }; + + let mut output = Vec::with_capacity((input.len() + 3) / 4 * 3); + let mut decoder = Base64Decoder::new(); + + for &byte in input { + if byte.is_ascii_whitespace() { + continue; + } + if let Some(byte) = decoder.feed(byte)? { + output.push(byte); + } + } + + Ok(output) +} + +/// Get the 6 bit value for a base64 character. +fn base64_value(byte: u8) -> Result<u8, Error> { + match byte { + b'A'..=b'Z' => Ok(byte - b'A'), + b'a'..=b'z' => Ok(byte - b'a' + 26), + b'0'..=b'9' => Ok(byte - b'0' + 52), + b'+' => Ok(62), + b'/' => Ok(63), + byte => Err(Error::InvalidBase64Char(byte)), + } +} + +/// Decoder for base64 data. +struct Base64Decoder { + /// The current buffer. + buffer: u16, + + /// The number of valid bits in the buffer. + valid_bits: u8, +} + +impl Base64Decoder { + /// Create a new base64 decoder. + fn new() -> Self { + Self { + buffer: 0, + valid_bits: 0, + } + } + + /// Feed a base64 character to the decoder. + /// + /// Returns `Ok(Some(u8))` if a new character is fully decoded. + /// Returns `Ok(None)` if there is no new character available yet. + fn feed(&mut self, byte: u8) -> Result<Option<u8>, Error> { + debug_assert!(self.valid_bits < 8); + // Paste the new 6 bit value at the least significant position in the buffer. + self.buffer |= (base64_value(byte)? as u16) << (10 - self.valid_bits); + // Bump the number of valid bits. + self.valid_bits += 6; + // Consume the most significant byte if it is complete. + Ok(self.consume_buffer_front()) + } + + /// Consume the first character in the buffer. + fn consume_buffer_front(&mut self) -> Option<u8> { + if self.valid_bits >= 8 { + let value = self.buffer >> 8 & 0xFF; + self.buffer <<= 8; + self.valid_bits -= 8; + Some(value as u8) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use assert2::assert; + + #[test] + fn test_decode_base64() { + assert!(let Ok(b"0") = base64_decode(b"MA").as_deref()); + assert!(let Ok(b"0") = base64_decode(b"MA=").as_deref()); + assert!(let Ok(b"0") = base64_decode(b"MA==").as_deref()); + assert!(let Ok(b"aap noot mies") = base64_decode(b"YWFwIG5vb3QgbWllcw").as_deref()); + assert!(let Ok(b"aap noot mies") = base64_decode(b"YWFwIG5vb3QgbWllcw=").as_deref()); + assert!(let Ok(b"aap noot mies") = base64_decode(b"YWFwIG5vb3QgbWllcw==").as_deref()); + } +} |