use soft_ascii_string::{ SoftAsciiChar, SoftAsciiString };
use { quoted_printable as extern_quoted_printable };
use failure::Fail;
use ::error::{EncodingError, EncodingErrorKind};
use super::encoded_word::EncodedWordWriter;
pub fn normal_encode<A: AsRef<[u8]>>(data: A) -> SoftAsciiString {
let encoded = extern_quoted_printable::encode_to_str(data);
SoftAsciiString::from_unchecked(encoded)
}
#[inline]
pub fn normal_decode<R: AsRef<[u8]>>(input: R)
-> Result<Vec<u8>, EncodingError>
{
extern_quoted_printable::decode(
input.as_ref(), extern_quoted_printable::ParseMode::Strict
).map_err(|err| err
.context(EncodingErrorKind::Malformed)
.into()
)
}
#[inline(always)]
pub fn encoded_word_decode<R: AsRef<[u8]>>( input: R ) -> Result<Vec<u8>, EncodingError> {
normal_decode( input )
}
pub fn encoded_word_encode_utf8<'a, O>(word: &str, writer: &mut O )
where O: EncodedWordWriter
{
let iter = word.char_indices().map( |(idx, ch)| {
&word.as_bytes()[idx..idx+ch.len_utf8()]
});
encoded_word_encode(iter, writer );
}
pub fn encoded_word_encode<'a, I, O>(input: I, out: &mut O )
where I: Iterator<Item=&'a [u8]>, O: EncodedWordWriter
{
out.write_ecw_start();
let max_payload_len = out.max_payload_len();
let mut remaining = max_payload_len;
let mut buf = [SoftAsciiChar::from_unchecked('X'); 16];
for chunk in input {
let mut buf_idx = 0;
for byte in chunk {
let byte = *byte;
match byte {
b'!' | b'*' |
b'+' | b'-' |
b'/' | b'_' |
b'0'...b'9' |
b'A'...b'Z' |
b'a'...b'z' => {
buf[buf_idx] = SoftAsciiChar::from_unchecked(byte as char);
buf_idx += 1;
},
_otherwise => {
buf[buf_idx] = SoftAsciiChar::from_unchecked('=');
buf[buf_idx+1] = lower_nibble_to_hex( byte >> 4 );
buf[buf_idx+2] = lower_nibble_to_hex( byte );
buf_idx += 3;
}
}
}
if buf_idx > remaining {
out.start_next_encoded_word();
remaining = max_payload_len;
}
if buf_idx > remaining {
panic!( "single character longer then max length ({:?}) of encoded word", remaining );
}
for idx in 0..buf_idx {
out.write_char( buf[idx] )
}
remaining -= buf_idx;
}
out.write_ecw_end()
}
#[inline]
fn lower_nibble_to_hex( half_byte: u8 ) -> SoftAsciiChar {
static CHARS: &[char] = &[
'0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F'
];
SoftAsciiChar::from_unchecked(CHARS[ (half_byte & 0x0F) as usize ])
}
#[cfg(test)]
mod test {
use soft_ascii_string::SoftAsciiStr;
use ::bind::encoded_word::EncodedWordEncoding;
use super::super::encoded_word::VecWriter;
use super::*;
#[test]
fn to_hex() {
let data = &[
('0', 0b11110000),
('0', 0b0 ),
('7', 0b0111),
('7', 0b10111),
('F', 0b1111)
];
for &(ch, byte) in data {
assert_eq!( lower_nibble_to_hex( byte), ch );
}
}
macro_rules! test_ecw_encode {
($name:ident, data $data:expr => [$($item:expr),*]) => {
#[test]
fn $name() {
let test_data = $data;
let mut out = VecWriter::new(
SoftAsciiStr::from_unchecked("utf8"),
EncodedWordEncoding::QuotedPrintable
);
encoded_word_encode_utf8( test_data, &mut out );
let expected = &[
$($item),*
];
let iter = expected.iter()
.zip( out.data().iter().map(|x|x.as_str()) )
.enumerate();
for ( idx, (expected, got) ) in iter {
if *expected != got {
panic!( " item nr {}: {:?} != {:?} ", idx, expected, got );
}
}
let e_len = expected.len();
let g_len = out.data().len();
if e_len > g_len {
panic!( "expected following additional items: {:?}", &expected[g_len..e_len])
}
if e_len < g_len {
panic!( "got following additional items: {:?}", &out.data()[e_len..g_len])
}
}
};
}
test_ecw_encode! { can_be_used_in_comments,
data "()\"" => [
"=?utf8?Q?=28=29=22?="
]
}
test_ecw_encode! { can_be_used_in_phrase,
data "{}~@#$%^&*()=|\\[]';:." => [
"=?utf8?Q?=7B=7D=7E=40=23=24=25=5E=26*=28=29=3D=7C=5C=5B=5D=27=3B=3A=2E?="
]
}
test_ecw_encode! { bad_chars_in_all_contexts,
data "?= \t\r\n" => [
"=?utf8?Q?=3F=3D=20=09=0D=0A?="
]
}
test_ecw_encode!{ encode_ascii,
data "abcdefghijklmnopqrstuvwxyz \t?=0123456789!@#$%^&*()_+-" => [
"=?utf8?Q?abcdefghijklmnopqrstuvwxyz=20=09=3F=3D0123456789!=40=23=24=25=5E?=",
"=?utf8?Q?=26*=28=29_+-?="
]
}
test_ecw_encode! { how_it_handales_newlines,
data "\r\n" => [
"=?utf8?Q?=0D=0A?="
]
}
test_ecw_encode! { split_into_multiple_ecws,
data "0123456789012345678901234567890123456789012345678901234567891234newline" => [
"=?utf8?Q?0123456789012345678901234567890123456789012345678901234567891234?=",
"=?utf8?Q?newline?="
]
}
test_ecw_encode!{ bigger_chunks,
data "ランダムテキスト ראַנדאָם טעקסט" => [
"=?utf8?Q?=E3=83=A9=E3=83=B3=E3=83=80=E3=83=A0=E3=83=86=E3=82=AD=E3=82=B9?=",
"=?utf8?Q?=E3=83=88=20=D7=A8=D7=90=D6=B7=D7=A0=D7=93=D7=90=D6=B8=D7=9D=20?=",
"=?utf8?Q?=D7=98=D7=A2=D7=A7=D7=A1=D7=98?="
]
}
#[test]
fn ecw_decode() {
let pairs = [
("=28=29=22", "()\""),
(
"=7B=7D=7E=40=23=24=25=5E=26*=28=29=3D=7C=5C=5B=5D=27=3B=3A=2E",
"{}~@#$%^&*()=|\\[]';:."
),
(
"=3F=3D=20=09=0D=0A",
"?= \t\r\n"
),
(
"=26*=28=29_+-",
"&*()_+-"
),
(
"abcdefghijklmnopqrstuvwxyz=20=09=3F=3D0123456789!=40=23=24=25=5E",
"abcdefghijklmnopqrstuvwxyz \t?=0123456789!@#$%^"
),
(
"=0D=0A",
"\r\n"
),
(
"=E3=83=A9=E3=83=B3=E3=83=80=E3=83=A0=E3=83=86=E3=82=AD=E3=82=B9",
"ランダムテキス"
),
(
"=E3=83=88=20=D7=A8=D7=90=D6=B7=D7=A0=D7=93=D7=90=D6=B8=D7=9D=20",
"ト ראַנדאָם "
),
(
"=D7=98=D7=A2=D7=A7=D7=A1=D7=98",
"טעקסט"
)
];
for &(inp, outp) in pairs.iter() {
let dec = assert_ok!(encoded_word_decode(inp));
let dec = String::from_utf8(dec).unwrap();
assert_eq!(
outp.as_bytes(),
dec.as_bytes()
);
}
}
#[test]
fn normal_encode_text() {
let text = concat!(
"This is a llllllllllllllllllllllllllllllllllllll00000000000000000000ng test 0123456789qwertyuio\r\n",
"With many lines\r\n",
"And utf→→→→8"
);
let encoded = normal_encode(text);
assert_eq!(
concat!(
"This is a llllllllllllllllllllllllllllllllllllll00000000000000000000ng test=\r\n",
" 0123456789qwertyuio\r\n",
"With many lines\r\n",
"And utf=E2=86=92=E2=86=92=E2=86=92=E2=86=928"
),
encoded.as_str()
);
}
#[test]
fn normal_decode_text() {
let text = concat!(
"This is a llllllllllllllllllllllllllllllllllllll00000000000000000000ng test=\r\n",
" 0123456789qwertyuio\r\n",
"With many lines\r\n",
"And utf=E2=86=92=E2=86=92=E2=86=92=E2=86=928"
);
let encoded = String::from_utf8(normal_decode(text).unwrap()).unwrap();
assert_eq!(
concat!(
"This is a llllllllllllllllllllllllllllllllllllll00000000000000000000ng test 0123456789qwertyuio\r\n",
"With many lines\r\n",
"And utf→→→→8"
),
encoded.as_str()
);
}
}