email_encoding/body/
base64.rs

1//! Base64 email body encoder.
2
3use std::fmt::{self, Write};
4use std::str;
5
6use ::base64::Engine;
7
8const LINE_LEN: usize = 76;
9const CRLF: &str = "\r\n";
10
11/// Base64 encode the provided bytes.
12///
13/// Splits the provided `b` into 57 bytes chunks and
14/// base64 encodes them, writing the resulting 76 characters
15/// CRLF sequence into `w`.
16///
17/// The last line may be less than 76 characters in length
18/// and will not end in CRLF.
19///
20/// # Examples
21///
22/// ```rust
23/// # fn main() -> std::fmt::Result {
24/// let input = "Hello!
25/// You've got mail!
26/// This one is base64 encoded.
27///
28/// Enjoy your bytes 📬📬📬";
29///
30/// let mut output = String::new();
31/// email_encoding::body::base64::encode(input.as_bytes(), &mut output)?;
32/// assert_eq!(
33///     output,
34///     concat!(
35///         "SGVsbG8hCllvdSd2ZSBnb3QgbWFpbCEKVGhpcyBvbmUgaXMgYmFzZTY0IGVuY29kZWQuCgpFbmpv\r\n",
36///         "eSB5b3VyIGJ5dGVzIPCfk6zwn5Os8J+TrA=="
37///     )
38/// );
39/// # Ok(())
40/// # }
41/// ```
42pub fn encode(b: &[u8], w: &mut dyn Write) -> fmt::Result {
43    let mut buf = [0; LINE_LEN];
44
45    let mut chunks = b.chunks(LINE_LEN / 4 * 3).peekable();
46    while let Some(chunk) = chunks.next() {
47        let len = ::base64::engine::general_purpose::STANDARD
48            .encode_slice(chunk, &mut buf)
49            .expect("base64 output `buf` is not big enough");
50
51        w.write_str(str::from_utf8(&buf[..len]).expect("base64 produced an invalid encode"))?;
52        if chunks.peek().is_some() {
53            w.write_str(CRLF)?;
54        }
55    }
56
57    Ok(())
58}
59
60/// Predict how many bytes [`encode`] is going to write given a `input_len` input length.
61///
62/// # Examples
63///
64/// ```rust
65/// # use email_encoding::body::base64::encoded_len;
66/// assert_eq!(encoded_len(0), 0);
67/// assert_eq!(encoded_len(16), 24);
68/// assert_eq!(encoded_len(300), 410);
69/// ```
70pub fn encoded_len(input_len: usize) -> usize {
71    let mut base64_len = input_len / 3 * 4;
72    if input_len % 3 != 0 {
73        base64_len += 4;
74    }
75    let mut crlf_len = base64_len / LINE_LEN * CRLF.len();
76    if crlf_len >= CRLF.len() && base64_len % LINE_LEN == 0 {
77        crlf_len -= CRLF.len();
78    }
79    base64_len + crlf_len
80}
81
82#[cfg(test)]
83mod tests {
84    use pretty_assertions::assert_eq;
85
86    use super::{encode, encoded_len};
87
88    #[test]
89    fn empty() {
90        let input = b"";
91        let mut output = String::new();
92
93        encode(input, &mut output).unwrap();
94
95        assert_eq!(output, "");
96        assert_eq!(output.len(), encoded_len(input.len()));
97    }
98
99    #[test]
100    fn oneline() {
101        let input = b"012";
102        let mut output = String::new();
103
104        encode(input, &mut output).unwrap();
105
106        assert_eq!(output, "MDEy");
107        assert_eq!(output.len(), encoded_len(input.len()));
108    }
109
110    #[test]
111    fn oneline_padded() {
112        let input = b"0123";
113        let mut output = String::new();
114
115        encode(input, &mut output).unwrap();
116
117        assert_eq!(output, "MDEyMw==");
118        assert_eq!(output.len(), encoded_len(input.len()));
119    }
120
121    #[test]
122    fn multiline() {
123        let input =
124            b"012345678998765432100123456789987654321001234567899876543210012345678998765432100";
125        let mut output = String::new();
126
127        encode(input, &mut output).unwrap();
128
129        assert_eq!(
130            output,
131            concat!(
132                "MDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2NTQz\r\n",
133                "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAw"
134            )
135        );
136        assert_eq!(output.len(), encoded_len(input.len()));
137    }
138
139    #[test]
140    fn multiline_padded() {
141        let input =
142            b"01234567899876543210012345678998765432100123456789987654321001234567899876543210";
143        let mut output = String::new();
144
145        encode(input, &mut output).unwrap();
146
147        assert_eq!(
148            output,
149            concat!(
150                "MDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2NTQz\r\n",
151                "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTA="
152            )
153        );
154        assert_eq!(output.len(), encoded_len(input.len()));
155    }
156
157    #[test]
158    fn multiline_exact() {
159        let input =
160            b"012345678998765432100123456789987654321001234567899876543210012345678998765432100123456789987654321001234567899876543210012345678998765432100123456789987654321001234567899";
161        let mut output = String::new();
162
163        encode(input, &mut output).unwrap();
164
165        assert_eq!(
166            output,
167            concat!(
168                "MDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2NTQz\r\n",
169                "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2\r\n",
170                "NTQzMjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5"
171            )
172        );
173        assert_eq!(output.len(), encoded_len(input.len()));
174    }
175}