email_encoding/body/
base64.rs1use core::{
4 fmt::{self, Write},
5 str,
6};
7
8use ::base64::Engine;
9
10const LINE_LEN: usize = 76;
11const CRLF: &str = "\r\n";
12
13pub fn encode(b: &[u8], w: &mut dyn Write) -> fmt::Result {
45 let mut buf = [0; LINE_LEN];
46
47 let mut chunks = b.chunks(LINE_LEN / 4 * 3).peekable();
48 while let Some(chunk) = chunks.next() {
49 let len = ::base64::engine::general_purpose::STANDARD
50 .encode_slice(chunk, &mut buf)
51 .expect("base64 output `buf` is not big enough");
52
53 w.write_str(str::from_utf8(&buf[..len]).expect("base64 produced an invalid encode"))?;
54 if chunks.peek().is_some() {
55 w.write_str(CRLF)?;
56 }
57 }
58
59 Ok(())
60}
61
62pub const fn encoded_len(input_len: usize) -> usize {
77 macro_rules! checked {
79 ($val:expr) => {
80 match $val {
81 Some(val) => val,
82 None => panic!("overflow"),
83 }
84 };
85 }
86
87 let mut base64_len = checked!((input_len / 3).checked_mul(4));
88 if input_len % 3 != 0 {
89 base64_len = checked!(base64_len.checked_add(4));
90 }
91 let mut crlf_len = base64_len / LINE_LEN * CRLF.len();
92 if crlf_len >= CRLF.len() && base64_len % LINE_LEN == 0 {
93 crlf_len -= CRLF.len();
94 }
95 checked!(base64_len.checked_add(crlf_len))
96}
97
98#[cfg(test)]
99mod tests {
100 use alloc::string::String;
101
102 use pretty_assertions::assert_eq;
103
104 use super::{encode, encoded_len};
105
106 #[test]
107 fn empty() {
108 let input = b"";
109 let mut output = String::new();
110
111 encode(input, &mut output).unwrap();
112
113 assert_eq!(output, "");
114 assert_eq!(output.len(), encoded_len(input.len()));
115 }
116
117 #[test]
118 fn oneline() {
119 let input = b"012";
120 let mut output = String::new();
121
122 encode(input, &mut output).unwrap();
123
124 assert_eq!(output, "MDEy");
125 assert_eq!(output.len(), encoded_len(input.len()));
126 }
127
128 #[test]
129 fn oneline_padded() {
130 let input = b"0123";
131 let mut output = String::new();
132
133 encode(input, &mut output).unwrap();
134
135 assert_eq!(output, "MDEyMw==");
136 assert_eq!(output.len(), encoded_len(input.len()));
137 }
138
139 #[test]
140 fn multiline() {
141 let input =
142 b"012345678998765432100123456789987654321001234567899876543210012345678998765432100";
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 "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAw"
152 )
153 );
154 assert_eq!(output.len(), encoded_len(input.len()));
155 }
156
157 #[test]
158 fn multiline_padded() {
159 let input =
160 b"01234567899876543210012345678998765432100123456789987654321001234567899876543210";
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 "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTA="
170 )
171 );
172 assert_eq!(output.len(), encoded_len(input.len()));
173 }
174
175 #[test]
176 fn multiline_exact() {
177 let input =
178 b"012345678998765432100123456789987654321001234567899876543210012345678998765432100123456789987654321001234567899876543210012345678998765432100123456789987654321001234567899";
179 let mut output = String::new();
180
181 encode(input, &mut output).unwrap();
182
183 assert_eq!(
184 output,
185 concat!(
186 "MDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2NTQz\r\n",
187 "MjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5ODc2\r\n",
188 "NTQzMjEwMDEyMzQ1Njc4OTk4NzY1NDMyMTAwMTIzNDU2Nzg5OTg3NjU0MzIxMDAxMjM0NTY3ODk5"
189 )
190 );
191 assert_eq!(output.len(), encoded_len(input.len()));
192 }
193
194 #[test]
195 #[should_panic(expected = "overflow")]
196 fn overflow() {
197 encoded_len(usize::MAX);
198 }
199}