mail_builder/encoders/
base64.rs

1/*
2 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
3 *
4 * SPDX-License-Identifier: Apache-2.0 OR MIT
5 */
6
7use std::io::{self, Write};
8
9const CHARPAD: u8 = b'=';
10
11#[inline(always)]
12pub fn base64_encode(input: &[u8]) -> io::Result<Vec<u8>> {
13    let mut buf = Vec::with_capacity(4 * (input.len() / 3));
14    base64_encode_mime(input, &mut buf, true)?;
15    Ok(buf)
16}
17
18pub fn base64_encode_mime(
19    input: &[u8],
20    mut output: impl Write,
21    is_inline: bool,
22) -> io::Result<usize> {
23    let mut i = 0;
24    let mut t1;
25    let mut t2;
26    let mut t3;
27    let mut bytes_written = 0;
28
29    if input.len() > 2 {
30        while i < input.len() - 2 {
31            t1 = input[i];
32            t2 = input[i + 1];
33            t3 = input[i + 2];
34
35            output.write_all(&[
36                E0[t1 as usize],
37                E1[(((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)) as usize],
38                E1[(((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)) as usize],
39                E2[t3 as usize],
40            ])?;
41
42            bytes_written += 4;
43
44            if !is_inline && bytes_written % 19 == 0 {
45                output.write_all(b"\r\n")?;
46            }
47
48            i += 3;
49        }
50    }
51
52    let remaining = input.len() - i;
53    if remaining > 0 {
54        t1 = input[i];
55        if remaining == 1 {
56            output.write_all(&[
57                E0[t1 as usize],
58                E1[((t1 & 0x03) << 4) as usize],
59                CHARPAD,
60                CHARPAD,
61            ])?;
62        } else {
63            t2 = input[i + 1];
64            output.write_all(&[
65                E0[t1 as usize],
66                E1[(((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)) as usize],
67                E2[((t2 & 0x0F) << 2) as usize],
68                CHARPAD,
69            ])?;
70        }
71
72        bytes_written += 4;
73
74        if !is_inline && bytes_written % 19 == 0 {
75            output.write_all(b"\r\n")?;
76        }
77    }
78
79    if !is_inline && bytes_written % 19 != 0 {
80        output.write_all(b"\r\n")?;
81    }
82
83    Ok(bytes_written)
84}
85
86#[cfg(test)]
87#[allow(clippy::items_after_test_module)]
88mod tests {
89
90    #[test]
91    fn encode_base64() {
92        for (input, expected_result, is_inline) in [
93            ("Test".to_string(), "VGVzdA==\r\n", false),
94            ("Ye".to_string(), "WWU=\r\n", false),
95            ("A".to_string(), "QQ==\r\n", false),
96            ("ro".to_string(), "cm8=\r\n", false),
97            (
98                "Are you a Shimano or Campagnolo person?".to_string(),
99                "QXJlIHlvdSBhIFNoaW1hbm8gb3IgQ2FtcGFnbm9sbyBwZXJzb24/\r\n",
100                false,
101            ),
102            (
103                "<!DOCTYPE html>\n<html>\n<body>\n</body>\n</html>\n".to_string(),
104                "PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8Ym9keT4KPC9ib2R5Pgo8L2h0bWw+Cg==\r\n",
105                false,
106            ),
107            ("áéíóú".to_string(), "w6HDqcOtw7PDug==\r\n", false),
108            (
109                " ".repeat(100),
110                concat!(
111                    "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg",
112                    "ICAgICAgICAgICAgICAgICAgICAgICAgICAg\r\n",
113                    "ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg",
114                    "ICAgICAgICAgICAgIA==\r\n",
115                ),
116                false,
117            ),
118        ] {
119            let mut output = Vec::new();
120            super::base64_encode_mime(input.as_bytes(), &mut output, is_inline).unwrap();
121            assert_eq!(std::str::from_utf8(&output).unwrap(), expected_result);
122        }
123    }
124}
125
126/*
127 * Table adapted from Nick Galbreath's "High performance base64 encoder / decoder"
128 *
129 * Copyright 2005, 2006, 2007 Nick Galbreath -- nickg [at] modp [dot] com
130 * All rights reserved.
131 *
132 * http://code.google.com/p/stringencoders/
133 *
134 * Released under bsd license.
135 *
136 */
137
138pub static E0: &[u8] = b"AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZaaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz0000111122223333444455556666777788889999++++////";
139pub static E1: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
140pub static E2: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";