mail_internals/bind/
base64.rs

1use {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        //FIXME: check if line wrap should be used here, I thinks it should
23        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    //NOTE: *3/4 is NOT correct due to the way this
41    // relies on non-floting point division
42    max_output_len / 4 * 3
43}
44
45//NOTE: base64 does not have to care about the EncodedWordContext,
46// it is valid under all of them anyway
47///
48/// # Note
49/// for now this only supports utf8/ascii input, as
50/// we have to know where we can split
51#[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        // additional bytes in uf8 always start with binary b10xxxxxx
78        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            // the byte at the current index starts with that we are in a
86            // position where we can't split and have to move left until
87            // the beginning of the utf8
88            while is_utf8_continuation_byte(rest_bytes[tmp_split]) {
89                //UNDERFLOW_SAFE: if the string is correct (contains valid utf8) this cant undeflow as
90                // the first byte cant start with 0b10xxxxxx.
91                tmp_split -= 1;
92            }
93            tmp_split
94        };
95
96        let (this, _rest) = rest.split_at(split_idx);
97        //very important ;=)
98        rest = _rest;
99
100        extern_base64::encode_config_buf(this, config.clone(), &mut buff);
101        //FIXME add a write_str method to EncodedWordWriter
102        for ch in buff.chars() {
103            //SAFE: base64 consist of only ascii chars
104            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}