|
|
@ -22,12 +22,12 @@ use std;
|
|
|
|
use std::str::from_utf8;
|
|
|
|
use std::str::from_utf8;
|
|
|
|
use base64;
|
|
|
|
use base64;
|
|
|
|
use chrono;
|
|
|
|
use chrono;
|
|
|
|
use nom::{le_u8, is_hex_digit};
|
|
|
|
use nom::{is_hex_digit, le_u8};
|
|
|
|
use nom::{IResult,Needed,ErrorKind};
|
|
|
|
use nom::{ErrorKind, IResult, Needed};
|
|
|
|
use nom::{Compare, CompareResult};
|
|
|
|
use nom::{Compare, CompareResult};
|
|
|
|
use encoding::{Encoding, DecoderTrap};
|
|
|
|
use encoding::{DecoderTrap, Encoding};
|
|
|
|
|
|
|
|
|
|
|
|
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
|
|
|
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
|
|
|
|
if input.is_empty() || input.len() < 3 {
|
|
|
|
if input.is_empty() || input.len() < 3 {
|
|
|
|
IResult::Incomplete(Needed::Size(1))
|
|
|
|
IResult::Incomplete(Needed::Size(1))
|
|
|
|
} else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
|
|
|
} else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
|
|
@ -45,7 +45,7 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
input[2] - 87
|
|
|
|
input[2] - 87
|
|
|
|
};
|
|
|
|
};
|
|
|
|
IResult::Done(&input[3..], a*16+b)
|
|
|
|
IResult::Done(&input[3..], a * 16 + b)
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -70,18 +70,15 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
|
|
|
for (i, x) in input.iter().enumerate() {
|
|
|
|
for (i, x) in input.iter().enumerate() {
|
|
|
|
if *x == b'\n' {
|
|
|
|
if *x == b'\n' {
|
|
|
|
if (i + 1) < input_len &&
|
|
|
|
if (i + 1) < input_len &&
|
|
|
|
((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') {
|
|
|
|
((input[i + 1] != b' ' && input[i + 1] != b'\t') || input[i + 1] == b'\n')
|
|
|
|
return match from_utf8(&input[0..i]) {
|
|
|
|
{
|
|
|
|
Ok(v) => {
|
|
|
|
return match from_utf8(&input[0..i]) {
|
|
|
|
IResult::Done(&input[(i+1)..], v)
|
|
|
|
Ok(v) => IResult::Done(&input[(i + 1)..], v),
|
|
|
|
},
|
|
|
|
Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))),
|
|
|
|
Err(_) => {
|
|
|
|
};
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
} else if i + 1 > input_len {
|
|
|
|
},
|
|
|
|
return IResult::Incomplete(Needed::Size(1));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if i + 1 > input_len {
|
|
|
|
|
|
|
|
return IResult::Incomplete(Needed::Size(1));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
@ -90,12 +87,13 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Parse the name part of the header -> &str */
|
|
|
|
/* Parse the name part of the header -> &str */
|
|
|
|
named!(name<&str>,
|
|
|
|
named!(name<&str>, map_res!(is_not!(":\n"), from_utf8));
|
|
|
|
map_res!(is_not!(":\n"), from_utf8));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
|
|
|
|
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
|
|
|
|
named!(header<(&str, &str)>,
|
|
|
|
named!(
|
|
|
|
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)));
|
|
|
|
header<(&str, &str)>,
|
|
|
|
|
|
|
|
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value))
|
|
|
|
|
|
|
|
);
|
|
|
|
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
|
|
|
|
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
|
|
|
|
named!(pub headers<std::vec::Vec<(&str, &str)>>,
|
|
|
|
named!(pub headers<std::vec::Vec<(&str, &str)>>,
|
|
|
|
many1!(complete!(header)));
|
|
|
|
many1!(complete!(header)));
|
|
|
@ -126,103 +124,95 @@ named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
|
|
|
|
|
|
|
|
|
|
|
|
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
|
|
|
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
|
|
|
* extended */
|
|
|
|
* extended */
|
|
|
|
use encoding::all::{ISO_8859_1,ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK};
|
|
|
|
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK};
|
|
|
|
|
|
|
|
|
|
|
|
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|
|
|
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|
|
|
if input.len() < 5 {
|
|
|
|
if input.len() < 5 {
|
|
|
|
return IResult::Incomplete(Needed::Unknown);
|
|
|
|
return IResult::Incomplete(Needed::Unknown);
|
|
|
|
} else if input[0] != b'=' || input[1] != b'?' {
|
|
|
|
} else if input[0] != b'=' || input[1] != b'?' {
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for tag in &["UTF-8", "iso-8859-7", "windows-1253", "iso-8859-1", "iso-8859-2", "gbk"] {
|
|
|
|
for tag in &[
|
|
|
|
|
|
|
|
"UTF-8",
|
|
|
|
|
|
|
|
"iso-8859-7",
|
|
|
|
|
|
|
|
"windows-1253",
|
|
|
|
|
|
|
|
"iso-8859-1",
|
|
|
|
|
|
|
|
"iso-8859-2",
|
|
|
|
|
|
|
|
"gbk",
|
|
|
|
|
|
|
|
] {
|
|
|
|
if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) {
|
|
|
|
if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) {
|
|
|
|
let tag_len = tag.len();
|
|
|
|
let tag_len = tag.len();
|
|
|
|
/* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */
|
|
|
|
/* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */
|
|
|
|
if input[2+tag_len] != b'?' || input[2+tag_len+2] != b'?' {
|
|
|
|
if input[2 + tag_len] != b'?' || input[2 + tag_len + 2] != b'?' {
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* See if input ends with "?=" and get ending index */
|
|
|
|
/* See if input ends with "?=" and get ending index */
|
|
|
|
let mut encoded_idx = None;
|
|
|
|
let mut encoded_idx = None;
|
|
|
|
for i in (5+tag_len)..input.len() {
|
|
|
|
for i in (5 + tag_len)..input.len() {
|
|
|
|
if input[i] == b'?' && i < input.len() && input[i+1] == b'=' {
|
|
|
|
if input[i] == b'?' && i < input.len() && input[i + 1] == b'=' {
|
|
|
|
encoded_idx = Some(i);
|
|
|
|
encoded_idx = Some(i);
|
|
|
|
break;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if encoded_idx.is_none() {
|
|
|
|
if encoded_idx.is_none() {
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let encoded = &input[5+tag_len..encoded_idx.unwrap()];
|
|
|
|
let encoded = &input[5 + tag_len..encoded_idx.unwrap()];
|
|
|
|
|
|
|
|
|
|
|
|
let s:Vec<u8> = match input[2+tag_len+1] {
|
|
|
|
|
|
|
|
b'b' | b'B' => {
|
|
|
|
|
|
|
|
match base64::decode(encoded) {
|
|
|
|
|
|
|
|
Ok(v) => {
|
|
|
|
|
|
|
|
v
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Err(_) => {
|
|
|
|
|
|
|
|
encoded.to_vec()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
b'q' | b'Q' => {
|
|
|
|
|
|
|
|
match get_quoted_printed_bytes(encoded) {
|
|
|
|
|
|
|
|
IResult::Done(b"", s) => {
|
|
|
|
|
|
|
|
s
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => {
|
|
|
|
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let s: Vec<u8> = match input[2 + tag_len + 1] {
|
|
|
|
|
|
|
|
b'b' | b'B' => match base64::decode(encoded) {
|
|
|
|
|
|
|
|
Ok(v) => v,
|
|
|
|
|
|
|
|
Err(_) => encoded.to_vec(),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
_ => {
|
|
|
|
b'q' | b'Q' => match get_quoted_printed_bytes(encoded) {
|
|
|
|
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Done(b"", s) => s,
|
|
|
|
|
|
|
|
_ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
_ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
match *tag {
|
|
|
|
match *tag {
|
|
|
|
"UTF-8" => {
|
|
|
|
"UTF-8" => {
|
|
|
|
return IResult::Done(&input[encoded_idx.unwrap()+2..], s);
|
|
|
|
return IResult::Done(&input[encoded_idx.unwrap() + 2..], s);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
"iso-8859-7" => {
|
|
|
|
"iso-8859-7" => {
|
|
|
|
return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) {
|
|
|
|
return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) {
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
"windows-1253" => {
|
|
|
|
"windows-1253" => {
|
|
|
|
return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) {
|
|
|
|
return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) {
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
"iso-8859-1" => {
|
|
|
|
"iso-8859-1" => {
|
|
|
|
return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) {
|
|
|
|
return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) {
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
"iso-8859-2" => {
|
|
|
|
"iso-8859-2" => {
|
|
|
|
return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) {
|
|
|
|
return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) {
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
"gbk" => {
|
|
|
|
"gbk" => {
|
|
|
|
return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) {
|
|
|
|
return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) {
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
|
|
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
_ => {
|
|
|
|
panic!();
|
|
|
|
panic!();
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
continue;
|
|
|
@ -232,10 +222,14 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
named!(qp_underscore_header<u8>,
|
|
|
|
named!(qp_underscore_header<u8>, do_parse!(tag!("_") >> ({ b' ' })));
|
|
|
|
do_parse!(tag!("_") >> ( { b' ' } )));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
named!(get_quoted_printed_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8)));
|
|
|
|
named!(
|
|
|
|
|
|
|
|
get_quoted_printed_bytes<Vec<u8>>,
|
|
|
|
|
|
|
|
many0!(alt_complete!(
|
|
|
|
|
|
|
|
quoted_printable_byte | qp_underscore_header | le_u8
|
|
|
|
|
|
|
|
))
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
named!(encoded_word_list<String>, ws!(do_parse!(
|
|
|
|
named!(encoded_word_list<String>, ws!(do_parse!(
|
|
|
|
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
|
|
|
|
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
|
|
|
@ -273,41 +267,79 @@ named!(pub subject<String>, ws!(do_parse!(
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_subject() {
|
|
|
|
fn test_subject() {
|
|
|
|
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
|
|
|
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
|
|
|
assert_eq!((&b""[..], "list.free.de mailing list memberships reminder".to_string()), subject(subject_s).unwrap());
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
&b""[..],
|
|
|
|
|
|
|
|
"list.free.de mailing list memberships reminder".to_string()
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
subject(subject_s).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
|
|
|
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
|
|
|
assert_eq!((&b""[..], "Νέο προσωπικό μήνυμα αφίχθη".to_string()), subject(subject_s).unwrap());
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
&b""[..],
|
|
|
|
|
|
|
|
"Νέο προσωπικό μήνυμα αφίχθη".to_string()
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
subject(subject_s).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
|
|
|
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
|
|
|
assert_eq!((&b""[..], "moodle: ανασκόπηση ομάδας συζητήσεων".to_string()), subject(subject_s).unwrap());
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
(
|
|
|
|
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
|
|
|
&b""[..],
|
|
|
|
assert_eq!("Νέο προσωπικό μήνυμα αφί".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
|
|
|
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
subject(subject_s).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let subject_s =
|
|
|
|
|
|
|
|
"=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
"Νέο προσωπικό μήνυμα αφί".to_string(),
|
|
|
|
|
|
|
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
|
|
|
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
|
|
|
|
assert_eq!("Πρόσθε".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
"Πρόσθε".to_string(),
|
|
|
|
|
|
|
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
|
|
|
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
|
|
|
assert_eq!("Re: Πρόβλημα με την αυριανή βαρδια".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
|
|
|
|
|
|
|
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
|
|
|
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
|
|
|
|
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
|
|
|
|
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
|
|
|
|
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=".as_bytes();
|
|
|
|
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
|
|
|
|
assert_eq!((&b""[..], "Πρόσθετη εξεταστική".to_string()), subject(subject_s).unwrap());
|
|
|
|
.as_bytes();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
|
|
(
|
|
|
|
|
|
|
|
&b""[..],
|
|
|
|
|
|
|
|
"Πρόσθετη εξεταστική".to_string()
|
|
|
|
|
|
|
|
),
|
|
|
|
|
|
|
|
subject(subject_s).unwrap()
|
|
|
|
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fn eat_comments(input: &str) -> String {
|
|
|
|
fn eat_comments(input: &str) -> String {
|
|
|
|
let mut in_comment = false;
|
|
|
|
let mut in_comment = false;
|
|
|
|
input.chars().fold(String::with_capacity(input.len()), |mut acc, x| {
|
|
|
|
input
|
|
|
|
if x == '(' && !in_comment {
|
|
|
|
.chars()
|
|
|
|
in_comment = true;
|
|
|
|
.fold(String::with_capacity(input.len()), |mut acc, x| {
|
|
|
|
acc
|
|
|
|
if x == '(' && !in_comment {
|
|
|
|
} else if x == ')' && in_comment {
|
|
|
|
in_comment = true;
|
|
|
|
in_comment = false;
|
|
|
|
acc
|
|
|
|
acc
|
|
|
|
} else if x == ')' && in_comment {
|
|
|
|
} else if in_comment {
|
|
|
|
in_comment = false;
|
|
|
|
acc
|
|
|
|
acc
|
|
|
|
} else {
|
|
|
|
} else if in_comment {
|
|
|
|
acc.push(x); acc
|
|
|
|
acc
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
})
|
|
|
|
acc.push(x);
|
|
|
|
|
|
|
|
acc
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
@ -317,13 +349,16 @@ fn test_eat_comments() {
|
|
|
|
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
|
|
|
|
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
|
|
|
|
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
|
|
|
|
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
/*
|
|
|
|
* Date should tokenize input and convert the tokens,
|
|
|
|
* Date should tokenize input and convert the tokens,
|
|
|
|
* right now we expect input will have no extra spaces in between tokens
|
|
|
|
* right now we expect input will have no extra spaces in between tokens
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* We should use a custom parser here*/
|
|
|
|
* We should use a custom parser here*/
|
|
|
|
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
|
|
|
|
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
|
|
|
|
let parsed_result = subject(eat_comments(input).as_bytes()).to_full_result().unwrap().replace("-", "+");
|
|
|
|
let parsed_result = subject(eat_comments(input).as_bytes())
|
|
|
|
|
|
|
|
.to_full_result()
|
|
|
|
|
|
|
|
.unwrap()
|
|
|
|
|
|
|
|
.replace("-", "+");
|
|
|
|
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
|
|
|
|
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -340,16 +375,16 @@ named!(pub message_id<&str>,
|
|
|
|
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
|
|
|
|
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
|
|
|
|
);
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
fn message_id_peek(input: &[u8]) -> IResult<&[u8],&str> {
|
|
|
|
fn message_id_peek(input: &[u8]) -> IResult<&[u8], &str> {
|
|
|
|
let input_length = input.len();
|
|
|
|
let input_length = input.len();
|
|
|
|
if input.is_empty() {
|
|
|
|
if input.is_empty() {
|
|
|
|
IResult::Incomplete(Needed::Size(1))
|
|
|
|
IResult::Incomplete(Needed::Size(1))
|
|
|
|
} else if input_length == 2 || input[0] != b'<' {
|
|
|
|
} else if input_length == 2 || input[0] != b'<' {
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
|
|
|
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
|
|
|
if x == b'>' {
|
|
|
|
if x == b'>' {
|
|
|
|
return IResult::Done(&input[i+1..], from_utf8(&input[0..i+1]).unwrap());
|
|
|
|
return IResult::Done(&input[i + 1..], from_utf8(&input[0..i + 1]).unwrap());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IResult::Incomplete(Needed::Unknown)
|
|
|
|
IResult::Incomplete(Needed::Unknown)
|
|
|
@ -381,19 +416,24 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
|
|
|
|
fn test_attachments() {
|
|
|
|
fn test_attachments() {
|
|
|
|
use std::io::Read;
|
|
|
|
use std::io::Read;
|
|
|
|
let mut buffer: Vec<u8> = Vec::new();
|
|
|
|
let mut buffer: Vec<u8> = Vec::new();
|
|
|
|
let _ = std::fs::File::open("test/attachment_test").unwrap().read_to_end(&mut buffer);
|
|
|
|
let _ = std::fs::File::open("test/attachment_test")
|
|
|
|
|
|
|
|
.unwrap()
|
|
|
|
|
|
|
|
.read_to_end(&mut buffer);
|
|
|
|
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
|
|
|
|
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
|
|
|
|
let boundary_len = boundary.len();
|
|
|
|
let boundary_len = boundary.len();
|
|
|
|
let (_, body) = match mail(&buffer).to_full_result() {
|
|
|
|
let (_, body) = match mail(&buffer).to_full_result() {
|
|
|
|
Ok(v) => v,
|
|
|
|
Ok(v) => v,
|
|
|
|
Err(_) => { panic!() }
|
|
|
|
Err(_) => panic!(),
|
|
|
|
};
|
|
|
|
};
|
|
|
|
let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap();
|
|
|
|
let attachments = attachments(body, &boundary[0..boundary_len - 2], &boundary)
|
|
|
|
|
|
|
|
.to_full_result()
|
|
|
|
|
|
|
|
.unwrap();
|
|
|
|
assert_eq!(attachments.len(), 4);
|
|
|
|
assert_eq!(attachments.len(), 4);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
named!(content_type_parameter< (&str, &str) >,
|
|
|
|
named!(
|
|
|
|
do_parse!(
|
|
|
|
content_type_parameter<(&str, &str)>,
|
|
|
|
|
|
|
|
do_parse!(
|
|
|
|
tag!(";") >>
|
|
|
|
tag!(";") >>
|
|
|
|
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
|
|
|
|
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
|
|
|
|
value: map_res!(ws!(
|
|
|
|
value: map_res!(ws!(
|
|
|
@ -402,7 +442,8 @@ named!(content_type_parameter< (&str, &str) >,
|
|
|
|
( {
|
|
|
|
( {
|
|
|
|
(name, value)
|
|
|
|
(name, value)
|
|
|
|
} )
|
|
|
|
} )
|
|
|
|
));
|
|
|
|
)
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
|
|
|
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
|
|
|