1//! Base64 encoding support. 2use crate::error::ErrorStack; 3use crate::{cvt_n, LenType}; 4use libc::c_int; 5use openssl_macros::corresponds; 6 7/// Encodes a slice of bytes to a base64 string. 8/// 9/// # Panics 10/// 11/// Panics if the input length or computed output length overflow a signed C integer. 12#[corresponds(EVP_EncodeBlock)] 13pub fn encode_block(src: &[u8]) -> String { 14 assert!(src.len() <= c_int::max_value() as usize); 15 let src_len = src.len() as LenType; 16 17 let len = encoded_len(src_len).unwrap(); 18 let mut out = Vec::with_capacity(len as usize); 19 20 // SAFETY: `encoded_len` ensures space for 4 output characters 21 // for every 3 input bytes including padding and nul terminator. 22 // `EVP_EncodeBlock` will write only single byte ASCII characters. 23 // `EVP_EncodeBlock` will only write to not read from `out`. 24 unsafe { 25 let out_len = ffi::EVP_EncodeBlock(out.as_mut_ptr(), src.as_ptr(), src_len); 26 out.set_len(out_len as usize); 27 String::from_utf8_unchecked(out) 28 } 29} 30 31/// Decodes a base64-encoded string to bytes. 32/// 33/// # Panics 34/// 35/// Panics if the input length or computed output length overflow a signed C integer. 36#[corresponds(EVP_DecodeBlock)] 37pub fn decode_block(src: &str) -> Result<Vec<u8>, ErrorStack> { 38 let src = src.trim(); 39 40 // https://github.com/openssl/openssl/issues/12143 41 if src.is_empty() { 42 return Ok(vec![]); 43 } 44 45 assert!(src.len() <= c_int::max_value() as usize); 46 let src_len = src.len() as LenType; 47 48 let len = decoded_len(src_len).unwrap(); 49 let mut out = Vec::with_capacity(len as usize); 50 51 // SAFETY: `decoded_len` ensures space for 3 output bytes 52 // for every 4 input characters including padding. 53 // `EVP_DecodeBlock` can write fewer bytes after stripping 54 // leading and trailing whitespace, but never more. 55 // `EVP_DecodeBlock` will only write to not read from `out`. 56 unsafe { 57 let out_len = cvt_n(ffi::EVP_DecodeBlock( 58 out.as_mut_ptr(), 59 src.as_ptr(), 60 src_len, 61 ))?; 62 out.set_len(out_len as usize); 63 } 64 65 if src.ends_with('=') { 66 out.pop(); 67 if src.ends_with("==") { 68 out.pop(); 69 } 70 } 71 72 Ok(out) 73} 74 75fn encoded_len(src_len: LenType) -> Option<LenType> { 76 let mut len = (src_len / 3).checked_mul(4)?; 77 78 if src_len % 3 != 0 { 79 len = len.checked_add(4)?; 80 } 81 82 len = len.checked_add(1)?; 83 84 Some(len) 85} 86 87fn decoded_len(src_len: LenType) -> Option<LenType> { 88 let mut len = (src_len / 4).checked_mul(3)?; 89 90 if src_len % 4 != 0 { 91 len = len.checked_add(3)?; 92 } 93 94 Some(len) 95} 96 97#[cfg(test)] 98mod tests { 99 use super::*; 100 101 #[test] 102 fn test_encode_block() { 103 assert_eq!("".to_string(), encode_block(b"")); 104 assert_eq!("Zg==".to_string(), encode_block(b"f")); 105 assert_eq!("Zm8=".to_string(), encode_block(b"fo")); 106 assert_eq!("Zm9v".to_string(), encode_block(b"foo")); 107 assert_eq!("Zm9vYg==".to_string(), encode_block(b"foob")); 108 assert_eq!("Zm9vYmE=".to_string(), encode_block(b"fooba")); 109 assert_eq!("Zm9vYmFy".to_string(), encode_block(b"foobar")); 110 } 111 112 #[test] 113 fn test_decode_block() { 114 assert_eq!(b"".to_vec(), decode_block("").unwrap()); 115 assert_eq!(b"f".to_vec(), decode_block("Zg==").unwrap()); 116 assert_eq!(b"fo".to_vec(), decode_block("Zm8=").unwrap()); 117 assert_eq!(b"foo".to_vec(), decode_block("Zm9v").unwrap()); 118 assert_eq!(b"foob".to_vec(), decode_block("Zm9vYg==").unwrap()); 119 assert_eq!(b"fooba".to_vec(), decode_block("Zm9vYmE=").unwrap()); 120 assert_eq!(b"foobar".to_vec(), decode_block("Zm9vYmFy").unwrap()); 121 } 122 123 #[test] 124 fn test_strip_whitespace() { 125 assert_eq!(b"foobar".to_vec(), decode_block(" Zm9vYmFy\n").unwrap()); 126 assert_eq!(b"foob".to_vec(), decode_block(" Zm9vYg==\n").unwrap()); 127 } 128} 129