mail_internals/bind/
base64.rs1use {base64 as extern_base64};
2use soft_ascii_string::{ SoftAsciiString, SoftAsciiChar};
3use failure::Fail;
4
5use ::utils::is_utf8_continuation_byte;
6use ::error::{EncodingError, EncodingErrorKind};
7
8use super::encoded_word::EncodedWordWriter;
9
10const CHARSET: extern_base64::CharacterSet = extern_base64::CharacterSet::Standard;
11const NO_LINE_WRAP: extern_base64::LineWrap = extern_base64::LineWrap::NoWrap;
12const LINE_WRAP: extern_base64::LineWrap =
13 extern_base64::LineWrap::Wrap(78, extern_base64::LineEnding::CRLF);
14const USE_PADDING: bool = true;
15const ECW_STRIP_WHITESPACE: bool = false;
16const NON_ECW_STRIP_WHITESPACE: bool = true;
17
18
19#[inline]
20pub fn normal_encode<R: AsRef<[u8]>>(input: R) -> SoftAsciiString {
21 let res = extern_base64::encode_config( input.as_ref(), extern_base64::Config::new(
22 CHARSET, USE_PADDING, NON_ECW_STRIP_WHITESPACE, LINE_WRAP
24 ));
25 SoftAsciiString::from_unchecked(res)
26}
27
28#[inline]
29pub fn normal_decode<R: AsRef<[u8]>>(input: R) -> Result<Vec<u8>, EncodingError> {
30 extern_base64::decode_config( input.as_ref(), extern_base64::Config::new(
31 CHARSET, USE_PADDING, NON_ECW_STRIP_WHITESPACE, LINE_WRAP
32 )).map_err(|err| err
33 .context(EncodingErrorKind::Malformed)
34 .into()
35 )
36}
37
38#[inline(always)]
39fn calc_max_input_len(max_output_len: usize) -> usize {
40 max_output_len / 4 * 3
43}
44
45#[inline(always)]
52pub fn encoded_word_encode<O, R: AsRef<str>>( input: R, out: &mut O )
53 where O: EncodedWordWriter
54{
55 _encoded_word_encode(input.as_ref(), out)
56}
57
58fn _encoded_word_encode<O>( input: &str, out: &mut O )
59 where O: EncodedWordWriter
60{
61 let config = extern_base64::Config::new(
62 CHARSET, USE_PADDING, ECW_STRIP_WHITESPACE, NO_LINE_WRAP
63 );
64
65 debug_assert!( USE_PADDING == true, "size calculation is tailored for padding");
66
67 let max_output_len = out.max_payload_len();
68 let max_input_len = calc_max_input_len(max_output_len);
69 let mut rest = input;
70 let mut buff = String::with_capacity(max_output_len);
71
72 out.write_ecw_start();
73
74 loop {
75 buff.clear();
76
77 let rest_len = rest.len();
79 let split_idx = if max_input_len >= rest_len {
80 rest_len
81 } else {
82 let mut tmp_split = max_input_len;
83 let rest_bytes = rest.as_bytes();
84
85 while is_utf8_continuation_byte(rest_bytes[tmp_split]) {
89 tmp_split -= 1;
92 }
93 tmp_split
94 };
95
96 let (this, _rest) = rest.split_at(split_idx);
97 rest = _rest;
99
100 extern_base64::encode_config_buf(this, config.clone(), &mut buff);
101 for ch in buff.chars() {
103 out.write_char(SoftAsciiChar::from_unchecked(ch))
105 }
106
107 if rest.len() == 0 {
108 break
109 } else {
110 out.start_next_encoded_word();
111 }
112 }
113 out.write_ecw_end();
114}
115
116#[inline(always)]
117pub fn encoded_word_decode<R: AsRef<[u8]>>(input: R)
118 -> Result<Vec<u8>, EncodingError>
119{
120 extern_base64::decode_config(input.as_ref(), extern_base64::Config::new(
121 CHARSET, USE_PADDING, ECW_STRIP_WHITESPACE, NO_LINE_WRAP
122 )).map_err(|err| err
123 .context(EncodingErrorKind::Malformed)
124 .into()
125 )
126}
127
128
129
130
131#[cfg(test)]
132mod test {
133 use soft_ascii_string::SoftAsciiStr;
134 use bind::encoded_word::{VecWriter, EncodedWordEncoding};
135 use super::*;
136
137 #[test]
138 fn encoding_uses_line_wrap() {
139 let input = concat!(
140 "0123456789", "0123456789",
141 "0123456789", "0123456789",
142 "0123456789", "0123456789",
143 );
144
145 let res = normal_encode(input);
146
147 assert_eq!(res.as_str(),
148 "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nz\r\ng5");
149
150 let dec = normal_decode(res).unwrap();
151
152 assert_eq!(dec, input.as_bytes());
153 }
154
155 #[test]
156 fn calc_max_input_len_from_max_output_len() {
157 assert!(USE_PADDING, "algorithm is specific to the usage of padding");
158 assert_eq!(45, calc_max_input_len(60));
159 assert_eq!(45, calc_max_input_len(61));
160 assert_eq!(45, calc_max_input_len(62));
161 assert_eq!(45, calc_max_input_len(63));
162 assert_eq!(48, calc_max_input_len(64));
163 }
164
165 #[test]
166 fn encode_decode_normal() {
167 let pairs: &[(&str,&[u8])] = &[
168 (
169 "this is some\r\nlong\r\ntest.",
170 b"dGhpcyBpcyBzb21lDQpsb25nDQp0ZXN0Lg=="
171 ),
172 (
173 "",
174 b""
175 )
176 ];
177 for &(raw, encoded) in pairs.iter() {
178 assert_eq!(
179 normal_encode(raw).as_bytes(),
180 encoded
181 );
182 assert_eq!(
183 assert_ok!(normal_decode(encoded)),
184 raw.as_bytes()
185 )
186
187 }
188 }
189
190 macro_rules! test_ecw_encode {
191 ($name:ident, data $data:expr => [$($item:expr),*]) => {
192 #[test]
193 fn $name() {
194 let test_data = $data;
195 let mut out = VecWriter::new(
196 SoftAsciiStr::from_unchecked("utf8"),
197 EncodedWordEncoding::Base64
198 );
199
200 encoded_word_encode( test_data, &mut out );
201
202 let expected = &[
203 $($item),*
204 ];
205
206 let iter = expected.iter()
207 .zip( out.data().iter().map(|x|x.as_str()) )
208 .enumerate();
209
210 for ( idx, (expected, got) ) in iter {
211 if *expected != got {
212 panic!( " item nr {}: {:?} != {:?} ", idx, expected, got );
213 }
214 }
215
216 let e_len = expected.len();
217 let g_len = out.data().len();
218 if e_len > g_len {
219 panic!( "expected following additional items: {:?}", &expected[g_len..e_len])
220 }
221 if e_len < g_len {
222 panic!( "got following additional items: {:?}", &out.data()[e_len..g_len])
223 }
224 }
225 };
226 }
227
228 test_ecw_encode! { ecw_simple,
229 data "()\"" => [
230 "=?utf8?B?KCki?="
231 ]
232 }
233
234 test_ecw_encode! { ecw_simple_max_len,
235 data "012345678901234567890123456789012345678944448888" => [
236 "=?utf8?B?MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTQ0NDQ4ODg4?="
237 ]
238 }
239
240 test_ecw_encode! { multiple_ecws,
241 data "012345678901234567890123456789012345678944448888NEWWORD" => [
242 "=?utf8?B?MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTQ0NDQ4ODg4?=",
243 "=?utf8?B?TkVXV09SRA==?="
244 ]
245 }
246
247 test_ecw_encode! { ecw_end_in_multibyte_codepoint,
248 data "01234567890123456789012345678901234567894444888↓" => [
249 "=?utf8?B?MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTQ0NDQ4ODg=?=",
250 "=?utf8?B?4oaT?="
251 ]
252 }
253
254
255 #[test]
256 fn decode_encoded_word() {
257 assert_eq!(
258 assert_ok!(encoded_word_decode("dGhpc19jcmF6eV9lbmNvZGVkX3dvcmQ=")),
259 b"this_crazy_encoded_word"
260 );
261 }
262}