base64.rs (3100B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU Affero General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 12 13 You should have received a copy of the GNU Affero General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 17 use std::fmt::Display; 18 19 use base64::{Engine, display::Base64Display}; 20 21 /** Encode bytes using standard base64 with `=` padding */ 22 pub fn encode(bytes: impl AsRef<[u8]>) -> String { 23 base64::engine::general_purpose::STANDARD.encode(bytes) 24 } 25 26 /** Format bytes using standard base64 with `=` padding */ 27 pub fn fmt(bytes: impl AsRef<[u8]>) -> impl Display { 28 std::fmt::from_fn(move |f| { 29 Base64Display::new(bytes.as_ref(), &base64::engine::general_purpose::STANDARD).fmt(f) 30 }) 31 } 32 33 #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] 34 pub enum Base64Error { 35 #[error("invalid base64 format")] 36 Format, 37 #[error("invalid length: base64 input must be a multiple of 4")] 38 Length, 39 } 40 41 /** Decode a standard base64 string (with `=` padding) */ 42 pub fn decode(encoded: impl AsRef<[u8]>) -> Result<Vec<u8>, Base64Error> { 43 base64::engine::general_purpose::STANDARD 44 .decode(encoded.as_ref()) 45 .map_err(|e| match e { 46 base64::DecodeError::InvalidLength(_) => Base64Error::Length, 47 base64::DecodeError::InvalidByte(_, _) 48 | base64::DecodeError::InvalidLastSymbol { .. } 49 | base64::DecodeError::InvalidPadding => Base64Error::Format, 50 }) 51 } 52 53 #[cfg(test)] 54 mod test { 55 use crate::encoding::base64::{Base64Error, decode, encode, fmt}; 56 57 #[test] 58 fn base64() { 59 // RFC test vectors 60 for (decoded, encoded) in [ 61 ("", ""), 62 ("f", "Zg=="), 63 ("fo", "Zm8="), 64 ("foo", "Zm9v"), 65 ("foob", "Zm9vYg=="), 66 ("fooba", "Zm9vYmE="), 67 ("foobar", "Zm9vYmFy"), 68 ] { 69 assert_eq!(encode(decoded.as_bytes()), encoded); 70 assert_eq!(fmt(decoded).to_string(), encoded); 71 assert_eq!(decode(encoded.as_bytes()).unwrap(), decoded.as_bytes()); 72 } 73 74 // Invalid length 75 assert_eq!(decode(b"Zg="), Err(Base64Error::Format)); 76 assert_eq!(decode(b"Z"), Err(Base64Error::Length)); 77 78 // Invalid characters 79 assert_eq!(decode(b"Zg=!"), Err(Base64Error::Format)); 80 assert_eq!(decode(b"Z\x00=="), Err(Base64Error::Format)); 81 82 // Invalid padding 83 assert_eq!(decode(b"===="), Err(Base64Error::Format)); 84 assert_eq!(decode(b"A=BC"), Err(Base64Error::Format)); 85 assert_eq!(decode(b"AA==QUJD"), Err(Base64Error::Format)); 86 } 87 }