Skip to main content

rama_boring/
base64.rs

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