cargo fmt
This commit is contained in:
parent
99c6490eb2
commit
5fda64477c
24 changed files with 1173 additions and 728 deletions
|
@ -1,18 +1,18 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
combinator::{into, opt},
|
combinator::{into, opt},
|
||||||
multi::separated_list1,
|
multi::separated_list1,
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::error::IMFError;
|
||||||
use crate::fragments::lazy;
|
use crate::fragments::lazy;
|
||||||
use crate::fragments::model::{GroupRef, AddressRef, MailboxRef, MailboxList, AddressList};
|
|
||||||
use crate::fragments::mailbox::mailbox;
|
use crate::fragments::mailbox::mailbox;
|
||||||
use crate::fragments::misc_token::phrase;
|
use crate::fragments::misc_token::phrase;
|
||||||
use crate::fragments::whitespace::{cfws};
|
use crate::fragments::model::{AddressList, AddressRef, GroupRef, MailboxList, MailboxRef};
|
||||||
use crate::error::IMFError;
|
use crate::fragments::whitespace::cfws;
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a lazy::Mailbox<'a>> for MailboxRef {
|
impl<'a> TryFrom<&'a lazy::Mailbox<'a>> for MailboxRef {
|
||||||
type Error = IMFError<'a>;
|
type Error = IMFError<'a>;
|
||||||
|
@ -73,10 +73,13 @@ pub fn group(input: &str) -> IResult<&str, GroupRef> {
|
||||||
let (input, (grp_name, _, grp_list, _, _)) =
|
let (input, (grp_name, _, grp_list, _, _)) =
|
||||||
tuple((phrase, tag(":"), opt(group_list), tag(";"), opt(cfws)))(input)?;
|
tuple((phrase, tag(":"), opt(group_list), tag(";"), opt(cfws)))(input)?;
|
||||||
|
|
||||||
Ok((input, GroupRef {
|
Ok((
|
||||||
|
input,
|
||||||
|
GroupRef {
|
||||||
name: grp_name,
|
name: grp_name,
|
||||||
participants: grp_list.unwrap_or(vec![]),
|
participants: grp_list.unwrap_or(vec![]),
|
||||||
}))
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Group list
|
/// Group list
|
||||||
|
@ -128,7 +131,9 @@ mod tests {
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
match mailbox_list(r#"Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>, <boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>"#) {
|
match mailbox_list(
|
||||||
|
r#"Mary Smith <mary@x.test>, jdoe@example.org, Who? <one@y.test>, <boss@nil.test>, "Giant; \"Big\" Box" <sysservices@example.net>"#,
|
||||||
|
) {
|
||||||
Ok(("", _)) => (),
|
Ok(("", _)) => (),
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
@ -137,30 +142,47 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_address_list() {
|
fn test_address_list() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
address_list(r#"A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;, Mary Smith <mary@x.test>"#),
|
address_list(
|
||||||
Ok(("", vec![
|
r#"A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;, Mary Smith <mary@x.test>"#
|
||||||
|
),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
vec![
|
||||||
AddressRef::Many(GroupRef {
|
AddressRef::Many(GroupRef {
|
||||||
name: "A Group".to_string(),
|
name: "A Group".to_string(),
|
||||||
participants: vec![
|
participants: vec![
|
||||||
MailboxRef {
|
MailboxRef {
|
||||||
name: Some("Ed Jones".into()),
|
name: Some("Ed Jones".into()),
|
||||||
addrspec: AddrSpec { local_part: "c".into(), domain: "a.test".into() },
|
addrspec: AddrSpec {
|
||||||
|
local_part: "c".into(),
|
||||||
|
domain: "a.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MailboxRef {
|
MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
addrspec: AddrSpec { local_part: "joe".into(), domain: "where.test".into() },
|
addrspec: AddrSpec {
|
||||||
|
local_part: "joe".into(),
|
||||||
|
domain: "where.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
MailboxRef {
|
MailboxRef {
|
||||||
name: Some("John".into()),
|
name: Some("John".into()),
|
||||||
addrspec: AddrSpec { local_part: "jdoe".into(), domain: "one.test".into() },
|
addrspec: AddrSpec {
|
||||||
|
local_part: "jdoe".into(),
|
||||||
|
domain: "one.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
AddressRef::Single(MailboxRef {
|
AddressRef::Single(MailboxRef {
|
||||||
name: Some("Mary Smith".into()),
|
name: Some("Mary Smith".into()),
|
||||||
addrspec: AddrSpec { local_part: "mary".into(), domain: "x.test".into() },
|
addrspec: AddrSpec {
|
||||||
|
local_part: "mary".into(),
|
||||||
|
domain: "x.test".into()
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
]))
|
]
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
|
use crate::error::IMFError;
|
||||||
|
use crate::fragments::lazy;
|
||||||
|
use crate::fragments::whitespace::{cfws, fws};
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime};
|
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveTime};
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{tag, tag_no_case, take_while_m_n, is_a},
|
bytes::complete::{is_a, tag, tag_no_case, take_while_m_n},
|
||||||
character,
|
character,
|
||||||
character::complete::{one_of, alphanumeric1, digit0},
|
character::complete::{alphanumeric1, digit0, one_of},
|
||||||
combinator::{map, opt, value},
|
combinator::{map, opt, value},
|
||||||
sequence::{preceded, terminated, tuple, delimited },
|
sequence::{delimited, preceded, terminated, tuple},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
use crate::fragments::lazy;
|
|
||||||
use crate::fragments::whitespace::{fws, cfws};
|
|
||||||
use crate::error::IMFError;
|
|
||||||
|
|
||||||
const MIN: i32 = 60;
|
const MIN: i32 = 60;
|
||||||
const HOUR: i32 = 60 * MIN;
|
const HOUR: i32 = 60 * MIN;
|
||||||
|
@ -43,20 +43,31 @@ impl<'a> TryFrom<&'a lazy::DateTime<'a>> for DateTime<FixedOffset> {
|
||||||
/// due to an error in RFC0822 but are interpreted as their respective
|
/// due to an error in RFC0822 but are interpreted as their respective
|
||||||
/// timezone according to the RFC5322 definition
|
/// timezone according to the RFC5322 definition
|
||||||
pub fn section(input: &str) -> IResult<&str, Option<DateTime<FixedOffset>>> {
|
pub fn section(input: &str) -> IResult<&str, Option<DateTime<FixedOffset>>> {
|
||||||
map(terminated(
|
map(
|
||||||
|
terminated(
|
||||||
alt((
|
alt((
|
||||||
tuple((opt(terminated(strict_day_of_week, tag(","))), strict_date, strict_time_of_day, strict_zone )),
|
tuple((
|
||||||
tuple((opt(terminated(obs_day_of_week, tag(","))), obs_date, obs_time_of_day, alt((strict_zone, obs_zone)) )),
|
opt(terminated(strict_day_of_week, tag(","))),
|
||||||
|
strict_date,
|
||||||
|
strict_time_of_day,
|
||||||
|
strict_zone,
|
||||||
)),
|
)),
|
||||||
opt(cfws)
|
tuple((
|
||||||
), |res| {
|
opt(terminated(obs_day_of_week, tag(","))),
|
||||||
match res {
|
obs_date,
|
||||||
|
obs_time_of_day,
|
||||||
|
alt((strict_zone, obs_zone)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
opt(cfws),
|
||||||
|
),
|
||||||
|
|res| match res {
|
||||||
(_, Some(date), Some(time), Some(tz)) => {
|
(_, Some(date), Some(time), Some(tz)) => {
|
||||||
date.and_time(time).and_local_timezone(tz).earliest()
|
date.and_time(time).and_local_timezone(tz).earliest()
|
||||||
},
|
|
||||||
_ => None,
|
|
||||||
}
|
}
|
||||||
})(input)
|
_ => None,
|
||||||
|
},
|
||||||
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// day-of-week = ([FWS] day-name) / obs-day-of-week
|
/// day-of-week = ([FWS] day-name) / obs-day-of-week
|
||||||
|
@ -85,18 +96,16 @@ fn day_name(input: &str) -> IResult<&str, &str> {
|
||||||
|
|
||||||
/// date = day month year
|
/// date = day month year
|
||||||
fn strict_date(input: &str) -> IResult<&str, Option<NaiveDate>> {
|
fn strict_date(input: &str) -> IResult<&str, Option<NaiveDate>> {
|
||||||
map(
|
map(tuple((strict_day, month, strict_year)), |(d, m, y)| {
|
||||||
tuple((strict_day, month, strict_year)),
|
NaiveDate::from_ymd_opt(y, m, d)
|
||||||
|(d, m, y)| NaiveDate::from_ymd_opt(y, m, d)
|
})(input)
|
||||||
)(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// date = day month year
|
/// date = day month year
|
||||||
fn obs_date(input: &str) -> IResult<&str, Option<NaiveDate>> {
|
fn obs_date(input: &str) -> IResult<&str, Option<NaiveDate>> {
|
||||||
map(
|
map(tuple((obs_day, month, obs_year)), |(d, m, y)| {
|
||||||
tuple((obs_day, month, obs_year)),
|
NaiveDate::from_ymd_opt(y, m, d)
|
||||||
|(d, m, y)| NaiveDate::from_ymd_opt(y, m, d)
|
})(input)
|
||||||
)(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// day = ([FWS] 1*2DIGIT FWS) / obs-day
|
/// day = ([FWS] 1*2DIGIT FWS) / obs-day
|
||||||
|
@ -134,19 +143,22 @@ fn strict_year(input: &str) -> IResult<&str, i32> {
|
||||||
delimited(
|
delimited(
|
||||||
fws,
|
fws,
|
||||||
map(
|
map(
|
||||||
terminated(take_while_m_n(4,9,|c| c >= '\x30' && c <= '\x39'), digit0),
|
terminated(take_while_m_n(4, 9, |c| c >= '\x30' && c <= '\x39'), digit0),
|
||||||
|d: &str| d.parse::<i32>().unwrap()),
|
|d: &str| d.parse::<i32>().unwrap(),
|
||||||
|
),
|
||||||
fws,
|
fws,
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// obs-year = [CFWS] 2*DIGIT [CFWS]
|
/// obs-year = [CFWS] 2*DIGIT [CFWS]
|
||||||
fn obs_year(input: &str) -> IResult<&str, i32> {
|
fn obs_year(input: &str) -> IResult<&str, i32> {
|
||||||
map(delimited(
|
map(
|
||||||
|
delimited(
|
||||||
opt(cfws),
|
opt(cfws),
|
||||||
terminated(take_while_m_n(2,7,|c| c >= '\x30' && c <= '\x39'), digit0),
|
terminated(take_while_m_n(2, 7, |c| c >= '\x30' && c <= '\x39'), digit0),
|
||||||
opt(cfws)
|
opt(cfws),
|
||||||
), |cap: &str| {
|
),
|
||||||
|
|cap: &str| {
|
||||||
let d = cap.parse::<i32>().unwrap();
|
let d = cap.parse::<i32>().unwrap();
|
||||||
if d >= 0 && d <= 49 {
|
if d >= 0 && d <= 49 {
|
||||||
2000 + d
|
2000 + d
|
||||||
|
@ -155,22 +167,37 @@ fn obs_year(input: &str) -> IResult<&str, i32> {
|
||||||
} else {
|
} else {
|
||||||
d
|
d
|
||||||
}
|
}
|
||||||
})(input)
|
},
|
||||||
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// time-of-day = hour ":" minute [ ":" second ]
|
/// time-of-day = hour ":" minute [ ":" second ]
|
||||||
fn strict_time_of_day(input: &str) -> IResult<&str, Option<NaiveTime>> {
|
fn strict_time_of_day(input: &str) -> IResult<&str, Option<NaiveTime>> {
|
||||||
map(
|
map(
|
||||||
tuple((strict_time_digit, tag(":"), strict_time_digit, opt(preceded(tag(":"), strict_time_digit)))),
|
tuple((
|
||||||
|(hour, _, minute, maybe_sec)| NaiveTime::from_hms_opt(hour, minute, maybe_sec.unwrap_or(0)),
|
strict_time_digit,
|
||||||
|
tag(":"),
|
||||||
|
strict_time_digit,
|
||||||
|
opt(preceded(tag(":"), strict_time_digit)),
|
||||||
|
)),
|
||||||
|
|(hour, _, minute, maybe_sec)| {
|
||||||
|
NaiveTime::from_hms_opt(hour, minute, maybe_sec.unwrap_or(0))
|
||||||
|
},
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// time-of-day = hour ":" minute [ ":" second ]
|
/// time-of-day = hour ":" minute [ ":" second ]
|
||||||
fn obs_time_of_day(input: &str) -> IResult<&str, Option<NaiveTime>> {
|
fn obs_time_of_day(input: &str) -> IResult<&str, Option<NaiveTime>> {
|
||||||
map(
|
map(
|
||||||
tuple((obs_time_digit, tag(":"), obs_time_digit, opt(preceded(tag(":"), obs_time_digit)))),
|
tuple((
|
||||||
|(hour, _, minute, maybe_sec)| NaiveTime::from_hms_opt(hour, minute, maybe_sec.unwrap_or(0)),
|
obs_time_digit,
|
||||||
|
tag(":"),
|
||||||
|
obs_time_digit,
|
||||||
|
opt(preceded(tag(":"), obs_time_digit)),
|
||||||
|
)),
|
||||||
|
|(hour, _, minute, maybe_sec)| {
|
||||||
|
NaiveTime::from_hms_opt(hour, minute, maybe_sec.unwrap_or(0))
|
||||||
|
},
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,15 +216,21 @@ fn obs_time_digit(input: &str) -> IResult<&str, u32> {
|
||||||
/// ```
|
/// ```
|
||||||
fn strict_zone(input: &str) -> IResult<&str, Option<FixedOffset>> {
|
fn strict_zone(input: &str) -> IResult<&str, Option<FixedOffset>> {
|
||||||
map(
|
map(
|
||||||
tuple((opt(fws), is_a("+-"), take_while_m_n(2,2,|c| c >= '\x30' && c <= '\x39'), take_while_m_n(2,2,|c| c >= '\x30' && c <= '\x39'))),
|
tuple((
|
||||||
|
opt(fws),
|
||||||
|
is_a("+-"),
|
||||||
|
take_while_m_n(2, 2, |c| c >= '\x30' && c <= '\x39'),
|
||||||
|
take_while_m_n(2, 2, |c| c >= '\x30' && c <= '\x39'),
|
||||||
|
)),
|
||||||
|(_, op, dig_zone_hour, dig_zone_min)| {
|
|(_, op, dig_zone_hour, dig_zone_min)| {
|
||||||
let zone_hour = dig_zone_hour.parse::<i32>().unwrap() * HOUR;
|
let zone_hour = dig_zone_hour.parse::<i32>().unwrap() * HOUR;
|
||||||
let zone_min = dig_zone_min.parse::<i32>().unwrap() * MIN;
|
let zone_min = dig_zone_min.parse::<i32>().unwrap() * MIN;
|
||||||
match op {
|
match op {
|
||||||
"+" => FixedOffset::east_opt(zone_hour + zone_min),
|
"+" => FixedOffset::east_opt(zone_hour + zone_min),
|
||||||
"-" => FixedOffset::west_opt(zone_hour + zone_min),
|
"-" => FixedOffset::west_opt(zone_hour + zone_min),
|
||||||
_ => unreachable!(), }
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,18 +260,27 @@ fn obs_zone(input: &str) -> IResult<&str, Option<FixedOffset>> {
|
||||||
opt(fws),
|
opt(fws),
|
||||||
alt((
|
alt((
|
||||||
// Legacy UTC/GMT
|
// Legacy UTC/GMT
|
||||||
value(FixedOffset::west_opt(0 * HOUR), alt((tag("UTC"), tag("UT"), tag("GMT")))),
|
value(
|
||||||
|
FixedOffset::west_opt(0 * HOUR),
|
||||||
|
alt((tag("UTC"), tag("UT"), tag("GMT"))),
|
||||||
|
),
|
||||||
// USA Timezones
|
// USA Timezones
|
||||||
value(FixedOffset::west_opt(4 * HOUR), tag("EDT")),
|
value(FixedOffset::west_opt(4 * HOUR), tag("EDT")),
|
||||||
value(FixedOffset::west_opt(5 * HOUR), alt((tag("EST"), tag("CDT")))),
|
value(
|
||||||
value(FixedOffset::west_opt(6 * HOUR), alt((tag("CST"), tag("MDT")))),
|
FixedOffset::west_opt(5 * HOUR),
|
||||||
value(FixedOffset::west_opt(7 * HOUR), alt((tag("MST"), tag("PDT")))),
|
alt((tag("EST"), tag("CDT"))),
|
||||||
|
),
|
||||||
|
value(
|
||||||
|
FixedOffset::west_opt(6 * HOUR),
|
||||||
|
alt((tag("CST"), tag("MDT"))),
|
||||||
|
),
|
||||||
|
value(
|
||||||
|
FixedOffset::west_opt(7 * HOUR),
|
||||||
|
alt((tag("MST"), tag("PDT"))),
|
||||||
|
),
|
||||||
value(FixedOffset::west_opt(8 * HOUR), tag("PST")),
|
value(FixedOffset::west_opt(8 * HOUR), tag("PST")),
|
||||||
|
|
||||||
// Military Timezone UTC
|
// Military Timezone UTC
|
||||||
value(FixedOffset::west_opt(0 * HOUR), tag("Z")),
|
value(FixedOffset::west_opt(0 * HOUR), tag("Z")),
|
||||||
|
|
||||||
// Military Timezones East
|
// Military Timezones East
|
||||||
map(one_of("ABCDEFGHIKLMabcdefghiklm"), |c| match c {
|
map(one_of("ABCDEFGHIKLMabcdefghiklm"), |c| match c {
|
||||||
'A' | 'a' => FixedOffset::east_opt(1 * HOUR),
|
'A' | 'a' => FixedOffset::east_opt(1 * HOUR),
|
||||||
|
@ -255,7 +297,6 @@ fn obs_zone(input: &str) -> IResult<&str, Option<FixedOffset>> {
|
||||||
'M' | 'm' => FixedOffset::east_opt(12 * HOUR),
|
'M' | 'm' => FixedOffset::east_opt(12 * HOUR),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Military Timezones West
|
// Military Timezones West
|
||||||
map(one_of("nopqrstuvwxyNOPQRSTUVWXY"), |c| match c {
|
map(one_of("nopqrstuvwxyNOPQRSTUVWXY"), |c| match c {
|
||||||
'N' | 'n' => FixedOffset::west_opt(1 * HOUR),
|
'N' | 'n' => FixedOffset::west_opt(1 * HOUR),
|
||||||
|
@ -272,7 +313,6 @@ fn obs_zone(input: &str) -> IResult<&str, Option<FixedOffset>> {
|
||||||
'Y' | 'y' => FixedOffset::west_opt(12 * HOUR),
|
'Y' | 'y' => FixedOffset::west_opt(12 * HOUR),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// Unknown timezone
|
// Unknown timezone
|
||||||
value(FixedOffset::west_opt(0 * HOUR), alphanumeric1),
|
value(FixedOffset::west_opt(0 * HOUR), alphanumeric1),
|
||||||
)),
|
)),
|
||||||
|
@ -284,12 +324,19 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_section_rfc_strict() {
|
fn test_section_rfc_strict() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("Fri, 21 Nov 1997 09:55:06 -0600"),
|
section("Fri, 21 Nov 1997 09:55:06 -0600"),
|
||||||
Ok(("", Some(FixedOffset::west_opt(6 * HOUR).unwrap().with_ymd_and_hms(1997, 11, 21, 9, 55, 6).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::west_opt(6 * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(1997, 11, 21, 9, 55, 6)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,7 +344,15 @@ mod tests {
|
||||||
fn test_section_received() {
|
fn test_section_received() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("Sun, 18 Jun 2023 15:39:08 +0200 (CEST)"),
|
section("Sun, 18 Jun 2023 15:39:08 +0200 (CEST)"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(2 * HOUR).unwrap().with_ymd_and_hms(2023, 6, 18, 15, 39, 8).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(2 * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 6, 18, 15, 39, 8)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,8 +365,17 @@ mod tests {
|
||||||
Feb
|
Feb
|
||||||
1969
|
1969
|
||||||
23:32
|
23:32
|
||||||
-0330 (Newfoundland Time)"#),
|
-0330 (Newfoundland Time)"#
|
||||||
Ok(("", Some(FixedOffset::west_opt(3 * HOUR + 30 * MIN).unwrap().with_ymd_and_hms(1969, 2, 13, 23, 32, 00).unwrap()))),
|
),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::west_opt(3 * HOUR + 30 * MIN)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(1969, 2, 13, 23, 32, 00)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +383,15 @@ mod tests {
|
||||||
fn test_section_rfc_obs() {
|
fn test_section_rfc_obs() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 97 09:55:06 GMT"),
|
section("21 Nov 97 09:55:06 GMT"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(1997, 11, 21, 9, 55, 6).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(1997, 11, 21, 9, 55, 6)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,7 +399,15 @@ mod tests {
|
||||||
fn test_section_3digit_year() {
|
fn test_section_3digit_year() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 103 09:55:06 UT"),
|
section("21 Nov 103 09:55:06 UT"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2003, 11, 21, 9, 55, 6).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2003, 11, 21, 9, 55, 6)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +415,15 @@ mod tests {
|
||||||
fn test_section_rfc_obs_ws() {
|
fn test_section_rfc_obs_ws() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("Fri, 21 Nov 1997 09(comment): 55 : 06 -0600"),
|
section("Fri, 21 Nov 1997 09(comment): 55 : 06 -0600"),
|
||||||
Ok(("", Some(FixedOffset::west_opt(6 * HOUR).unwrap().with_ymd_and_hms(1997, 11, 21, 9, 55, 6).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::west_opt(6 * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(1997, 11, 21, 9, 55, 6)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,26 +431,56 @@ mod tests {
|
||||||
fn test_section_2digit_year() {
|
fn test_section_2digit_year() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 23 09:55:06Z"),
|
section("21 Nov 23 09:55:06Z"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 9, 55, 6).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 9, 55, 6)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_section_military_zone_east() {
|
fn test_section_military_zone_east() {
|
||||||
["a", "B", "c", "D", "e", "F", "g", "H", "i", "K", "l", "M"].iter().enumerate().for_each(|(i, x)| {
|
["a", "B", "c", "D", "e", "F", "g", "H", "i", "K", "l", "M"]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, x)| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section(format!("1 Jan 22 08:00:00 {}", x).as_str()),
|
section(format!("1 Jan 22 08:00:00 {}", x).as_str()),
|
||||||
Ok(("", Some(FixedOffset::east_opt((i as i32 + 1) * HOUR).unwrap().with_ymd_and_hms(2022, 01, 01, 8, 0, 0).unwrap())))
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt((i as i32 + 1) * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2022, 01, 01, 8, 0, 0)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_section_military_zone_west() {
|
fn test_section_military_zone_west() {
|
||||||
["N", "O", "P", "q", "r", "s", "T", "U", "V", "w", "x", "y"].iter().enumerate().for_each(|(i, x)| {
|
["N", "O", "P", "q", "r", "s", "T", "U", "V", "w", "x", "y"]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.for_each(|(i, x)| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section(format!("1 Jan 22 08:00:00 {}", x).as_str()),
|
section(format!("1 Jan 22 08:00:00 {}", x).as_str()),
|
||||||
Ok(("", Some(FixedOffset::west_opt((i as i32 + 1) * HOUR).unwrap().with_ymd_and_hms(2022, 01, 01, 8, 0, 0).unwrap())))
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::west_opt((i as i32 + 1) * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2022, 01, 01, 8, 0, 0)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
))
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -371,27 +489,75 @@ mod tests {
|
||||||
fn test_section_gmt() {
|
fn test_section_gmt() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 +0000"),
|
section("21 Nov 2023 07:07:07 +0000"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 -0000"),
|
section("21 Nov 2023 07:07:07 -0000"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 Z"),
|
section("21 Nov 2023 07:07:07 Z"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 GMT"),
|
section("21 Nov 2023 07:07:07 GMT"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 UT"),
|
section("21 Nov 2023 07:07:07 UT"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 07:07:07 UTC"),
|
section("21 Nov 2023 07:07:07 UTC"),
|
||||||
Ok(("", Some(FixedOffset::east_opt(0).unwrap().with_ymd_and_hms(2023, 11, 21, 7, 7, 7).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::east_opt(0)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 7, 7, 7)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -399,7 +565,15 @@ mod tests {
|
||||||
fn test_section_usa() {
|
fn test_section_usa() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section("21 Nov 2023 4:4:4 CST"),
|
section("21 Nov 2023 4:4:4 CST"),
|
||||||
Ok(("", Some(FixedOffset::west_opt(6 * HOUR).unwrap().with_ymd_and_hms(2023, 11, 21, 4, 4, 4).unwrap()))),
|
Ok((
|
||||||
|
"",
|
||||||
|
Some(
|
||||||
|
FixedOffset::west_opt(6 * HOUR)
|
||||||
|
.unwrap()
|
||||||
|
.with_ymd_and_hms(2023, 11, 21, 4, 4, 4)
|
||||||
|
.unwrap()
|
||||||
|
)
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use chrono::{DateTime, FixedOffset};
|
|
||||||
use crate::fragments::model::{
|
|
||||||
MailboxList, MailboxRef, AddressList,
|
|
||||||
MessageId, MessageIdList};
|
|
||||||
use crate::fragments::misc_token::{Unstructured, PhraseList};
|
|
||||||
use crate::fragments::trace::ReceivedLog;
|
|
||||||
use crate::fragments::lazy::Field as Lazy;
|
|
||||||
use crate::error::IMFError;
|
use crate::error::IMFError;
|
||||||
|
use crate::fragments::lazy::Field as Lazy;
|
||||||
|
use crate::fragments::misc_token::{PhraseList, Unstructured};
|
||||||
|
use crate::fragments::model::{AddressList, MailboxList, MailboxRef, MessageId, MessageIdList};
|
||||||
|
use crate::fragments::trace::ReceivedLog;
|
||||||
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum Field<'a> {
|
pub enum Field<'a> {
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{take_while, tag},
|
bytes::complete::{tag, take_while},
|
||||||
combinator::opt,
|
combinator::opt,
|
||||||
multi::many1,
|
multi::many1,
|
||||||
sequence::{delimited, pair, tuple},
|
sequence::{delimited, pair, tuple},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::error::IMFError;
|
||||||
use crate::fragments::lazy;
|
use crate::fragments::lazy;
|
||||||
use crate::fragments::whitespace::cfws;
|
|
||||||
use crate::fragments::words::dot_atom_text;
|
|
||||||
use crate::fragments::mailbox::is_dtext;
|
use crate::fragments::mailbox::is_dtext;
|
||||||
use crate::fragments::model::{MessageId, MessageIdList};
|
use crate::fragments::model::{MessageId, MessageIdList};
|
||||||
use crate::error::IMFError;
|
use crate::fragments::whitespace::cfws;
|
||||||
|
use crate::fragments::words::dot_atom_text;
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a lazy::Identifier<'a>> for MessageId<'a> {
|
impl<'a> TryFrom<&'a lazy::Identifier<'a>> for MessageId<'a> {
|
||||||
type Error = IMFError<'a>;
|
type Error = IMFError<'a>;
|
||||||
|
@ -45,7 +45,7 @@ pub fn msg_id(input: &str) -> IResult<&str, MessageId> {
|
||||||
tuple((id_left, tag("@"), id_right)),
|
tuple((id_left, tag("@"), id_right)),
|
||||||
pair(tag(">"), opt(cfws)),
|
pair(tag(">"), opt(cfws)),
|
||||||
)(input)?;
|
)(input)?;
|
||||||
Ok((input, MessageId{ left, right }))
|
Ok((input, MessageId { left, right }))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Missing obsolete
|
// Missing obsolete
|
||||||
|
@ -70,7 +70,13 @@ mod tests {
|
||||||
fn test_msg_id() {
|
fn test_msg_id() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
msg_id("<5678.21-Nov-1997@example.com>"),
|
msg_id("<5678.21-Nov-1997@example.com>"),
|
||||||
Ok(("", MessageId{left: "5678.21-Nov-1997", right: "example.com"})),
|
Ok((
|
||||||
|
"",
|
||||||
|
MessageId {
|
||||||
|
left: "5678.21-Nov-1997",
|
||||||
|
right: "example.com"
|
||||||
|
}
|
||||||
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
|
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
bytes::complete::{tag, take_while1},
|
||||||
bytes::complete::{take_while1, tag},
|
|
||||||
character::complete::space0,
|
character::complete::space0,
|
||||||
sequence::{terminated, tuple},
|
sequence::{terminated, tuple},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
|
@ -98,13 +98,15 @@ impl<'a> From<&'a str> for Field<'a> {
|
||||||
fn field_name(input: &str) -> IResult<&str, &str> {
|
fn field_name(input: &str) -> IResult<&str, &str> {
|
||||||
terminated(
|
terminated(
|
||||||
take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A'),
|
take_while1(|c| c >= '\x21' && c <= '\x7E' && c != '\x3A'),
|
||||||
tuple((space0, tag(":"), space0))
|
tuple((space0, tag(":"), space0)),
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn correct_field(input: &str) -> IResult<&str, Field> {
|
fn correct_field(input: &str) -> IResult<&str, Field> {
|
||||||
field_name(input)
|
field_name(input).map(|(rest, name)| {
|
||||||
.map(|(rest, name)| ("", match name.to_lowercase().as_ref() {
|
(
|
||||||
|
"",
|
||||||
|
match name.to_lowercase().as_ref() {
|
||||||
"date" => Date(DateTime(rest)),
|
"date" => Date(DateTime(rest)),
|
||||||
|
|
||||||
"from" => From(MailboxList(rest)),
|
"from" => From(MailboxList(rest)),
|
||||||
|
@ -127,5 +129,7 @@ fn correct_field(input: &str) -> IResult<&str, Field> {
|
||||||
"received" => Received(ReceivedLog(rest)),
|
"received" => Received(ReceivedLog(rest)),
|
||||||
|
|
||||||
_ => Optional(name, Unstructured(rest)),
|
_ => Optional(name, Unstructured(rest)),
|
||||||
}))
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{tag, is_a},
|
bytes::complete::{is_a, tag},
|
||||||
character::complete::satisfy,
|
character::complete::satisfy,
|
||||||
combinator::{into,map,opt,recognize},
|
combinator::{into, map, opt, recognize},
|
||||||
multi::{separated_list1, fold_many0, many0},
|
multi::{fold_many0, many0, separated_list1},
|
||||||
sequence::{delimited,pair,preceded,terminated,tuple},
|
sequence::{delimited, pair, preceded, terminated, tuple},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::fragments::model::{MailboxRef, AddrSpec};
|
|
||||||
use crate::fragments::misc_token::{phrase, word};
|
use crate::fragments::misc_token::{phrase, word};
|
||||||
|
use crate::fragments::model::{AddrSpec, MailboxRef};
|
||||||
|
use crate::fragments::quoted::quoted_string;
|
||||||
use crate::fragments::whitespace::{cfws, fws, is_obs_no_ws_ctl};
|
use crate::fragments::whitespace::{cfws, fws, is_obs_no_ws_ctl};
|
||||||
use crate::fragments::words::{atom, dot_atom};
|
use crate::fragments::words::{atom, dot_atom};
|
||||||
use crate::fragments::quoted::quoted_string;
|
|
||||||
|
|
||||||
/// Mailbox
|
/// Mailbox
|
||||||
///
|
///
|
||||||
|
@ -61,7 +61,10 @@ fn obs_route(input: &str) -> IResult<&str, Vec<String>> {
|
||||||
/// ```
|
/// ```
|
||||||
fn obs_domain_list(input: &str) -> IResult<&str, Vec<String>> {
|
fn obs_domain_list(input: &str) -> IResult<&str, Vec<String>> {
|
||||||
//@FIXME complexity is O(n) in term of domains here.
|
//@FIXME complexity is O(n) in term of domains here.
|
||||||
let (input, head) = preceded(pair(many0(alt((recognize(cfws), tag(",")))), tag("@")), obs_domain)(input)?;
|
let (input, head) = preceded(
|
||||||
|
pair(many0(alt((recognize(cfws), tag(",")))), tag("@")),
|
||||||
|
obs_domain,
|
||||||
|
)(input)?;
|
||||||
let (input, mut rest) = obs_domain_list_rest(input)?;
|
let (input, mut rest) = obs_domain_list_rest(input)?;
|
||||||
rest.insert(0, head);
|
rest.insert(0, head);
|
||||||
Ok((input, rest))
|
Ok((input, rest))
|
||||||
|
@ -73,7 +76,7 @@ fn obs_domain_list_rest(input: &str) -> IResult<&str, Vec<String>> {
|
||||||
pair(tag(","), opt(cfws)),
|
pair(tag(","), opt(cfws)),
|
||||||
opt(preceded(tag("@"), obs_domain)),
|
opt(preceded(tag("@"), obs_domain)),
|
||||||
)),
|
)),
|
||||||
|v: Vec<Option<String>>| v.into_iter().flatten().collect()
|
|v: Vec<Option<String>>| v.into_iter().flatten().collect(),
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +89,13 @@ fn obs_domain_list_rest(input: &str) -> IResult<&str, Vec<String>> {
|
||||||
/// so I force obsolete for now...
|
/// so I force obsolete for now...
|
||||||
pub fn addr_spec(input: &str) -> IResult<&str, AddrSpec> {
|
pub fn addr_spec(input: &str) -> IResult<&str, AddrSpec> {
|
||||||
map(
|
map(
|
||||||
tuple((obs_local_part, tag("@"), obs_domain, many0(pair(tag("@"), obs_domain)))),
|
tuple((
|
||||||
|(local_part, _, domain, _)|
|
obs_local_part,
|
||||||
AddrSpec { local_part, domain },
|
tag("@"),
|
||||||
|
obs_domain,
|
||||||
|
many0(pair(tag("@"), obs_domain)),
|
||||||
|
)),
|
||||||
|
|(local_part, _, domain, _)| AddrSpec { local_part, domain },
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +125,8 @@ fn obs_local_part(input: &str) -> IResult<&str, String> {
|
||||||
fold_many0(
|
fold_many0(
|
||||||
alt((map(is_a("."), Cow::Borrowed), word)),
|
alt((map(is_a("."), Cow::Borrowed), word)),
|
||||||
String::new,
|
String::new,
|
||||||
|acc, chunk| acc + &chunk)(input)
|
|acc, chunk| acc + &chunk,
|
||||||
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Domain
|
/// Domain
|
||||||
|
@ -140,7 +148,10 @@ pub fn strict_domain(input: &str) -> IResult<&str, String> {
|
||||||
/// obs-domain = atom *("." atom) / domain-literal
|
/// obs-domain = atom *("." atom) / domain-literal
|
||||||
/// ```
|
/// ```
|
||||||
pub fn obs_domain(input: &str) -> IResult<&str, String> {
|
pub fn obs_domain(input: &str) -> IResult<&str, String> {
|
||||||
alt((map(separated_list1(tag("."), atom), |v| v.join(".")), domain_litteral))(input)
|
alt((
|
||||||
|
map(separated_list1(tag("."), atom), |v| v.join(".")),
|
||||||
|
domain_litteral,
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Domain litteral
|
/// Domain litteral
|
||||||
|
@ -152,15 +163,16 @@ fn domain_litteral(input: &str) -> IResult<&str, String> {
|
||||||
delimited(
|
delimited(
|
||||||
pair(opt(cfws), tag("[")),
|
pair(opt(cfws), tag("[")),
|
||||||
inner_domain_litteral,
|
inner_domain_litteral,
|
||||||
pair(tag("]"), opt(cfws))
|
pair(tag("]"), opt(cfws)),
|
||||||
)(input)
|
)(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inner_domain_litteral(input: &str) -> IResult<&str, String> {
|
fn inner_domain_litteral(input: &str) -> IResult<&str, String> {
|
||||||
let (input, (cvec, maybe_wsp)) = pair(many0(pair(opt(fws), satisfy(is_dtext))), opt(fws))(input)?;
|
let (input, (cvec, maybe_wsp)) =
|
||||||
let mut domain = cvec.iter().fold(
|
pair(many0(pair(opt(fws), satisfy(is_dtext))), opt(fws))(input)?;
|
||||||
String::with_capacity(16),
|
let mut domain = cvec
|
||||||
|mut acc, (maybe_wsp, c)| {
|
.iter()
|
||||||
|
.fold(String::with_capacity(16), |mut acc, (maybe_wsp, c)| {
|
||||||
if let Some(wsp) = maybe_wsp {
|
if let Some(wsp) = maybe_wsp {
|
||||||
acc.push(*wsp);
|
acc.push(*wsp);
|
||||||
}
|
}
|
||||||
|
@ -174,7 +186,6 @@ fn inner_domain_litteral(input: &str) -> IResult<&str, String> {
|
||||||
Ok((input, domain))
|
Ok((input, domain))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn is_strict_dtext(c: char) -> bool {
|
fn is_strict_dtext(c: char) -> bool {
|
||||||
(c >= '\x21' && c <= '\x5A') || (c >= '\x5E' && c <= '\x7E') || !c.is_ascii()
|
(c >= '\x21' && c <= '\x5A') || (c >= '\x5E' && c <= '\x7E') || !c.is_ascii()
|
||||||
}
|
}
|
||||||
|
@ -198,89 +209,213 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_addr_spec() {
|
fn test_addr_spec() {
|
||||||
assert_eq!(addr_spec("alice@example.com"), Ok(("", AddrSpec{local_part: "alice".into(), domain: "example.com".into() })));
|
assert_eq!(
|
||||||
|
addr_spec("alice@example.com"),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "alice".into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(addr_spec("jsmith@[192.168.2.1]"), Ok(("", AddrSpec{local_part: "jsmith".into(), domain: "192.168.2.1".into() })));
|
assert_eq!(
|
||||||
assert_eq!(addr_spec("jsmith@[IPv6:2001:db8::1]"), Ok(("", AddrSpec{local_part: "jsmith".into(), domain: "IPv6:2001:db8::1".into() })));
|
addr_spec("jsmith@[192.168.2.1]"),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "jsmith".into(),
|
||||||
|
domain: "192.168.2.1".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
addr_spec("jsmith@[IPv6:2001:db8::1]"),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "jsmith".into(),
|
||||||
|
domain: "IPv6:2001:db8::1".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
// UTF-8
|
// UTF-8
|
||||||
assert_eq!(addr_spec("用户@例子.广告"), Ok(("", AddrSpec{local_part: "用户".into(), domain: "例子.广告".into()})));
|
assert_eq!(
|
||||||
|
addr_spec("用户@例子.广告"),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "用户".into(),
|
||||||
|
domain: "例子.广告".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
// ASCII Edge cases
|
// ASCII Edge cases
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec("user+mailbox/department=shipping@example.com"),
|
addr_spec("user+mailbox/department=shipping@example.com"),
|
||||||
Ok(("", AddrSpec{local_part: "user+mailbox/department=shipping".into(), domain: "example.com".into()})));
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "user+mailbox/department=shipping".into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec("!#$%&'*+-/=?^_`.{|}~@example.com"),
|
addr_spec("!#$%&'*+-/=?^_`.{|}~@example.com"),
|
||||||
Ok(("", AddrSpec{local_part: "!#$%&'*+-/=?^_`.{|}~".into(), domain: "example.com".into()})));
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "!#$%&'*+-/=?^_`.{|}~".into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec(r#""Abc@def"@example.com"#),
|
addr_spec(r#""Abc@def"@example.com"#),
|
||||||
Ok(("", AddrSpec{local_part: "Abc@def".into(), domain: "example.com".into()})));
|
Ok((
|
||||||
assert_eq!(addr_spec(r#""Fred\ Bloggs"@example.com"#), Ok(("", AddrSpec{local_part: "Fred Bloggs".into(), domain: "example.com".into()})));
|
"",
|
||||||
assert_eq!(addr_spec(r#""Joe.\\Blow"@example.com"#), Ok(("", AddrSpec{local_part: r#"Joe.\Blow"#.into(), domain: "example.com".into()})));
|
AddrSpec {
|
||||||
|
local_part: "Abc@def".into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
addr_spec(r#""Fred\ Bloggs"@example.com"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: "Fred Bloggs".into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
addr_spec(r#""Joe.\\Blow"@example.com"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
|
local_part: r#"Joe.\Blow"#.into(),
|
||||||
|
domain: "example.com".into()
|
||||||
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_mailbox() {
|
fn test_mailbox() {
|
||||||
assert_eq!(mailbox(r#""Joe Q. Public" <john.q.public@example.com>"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#""Joe Q. Public" <john.q.public@example.com>"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: Some("Joe Q. Public".into()),
|
name: Some("Joe Q. Public".into()),
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "john.q.public".into(),
|
local_part: "john.q.public".into(),
|
||||||
domain: "example.com".into(),
|
domain: "example.com".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(mailbox(r#"Mary Smith <mary@x.test>"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#"Mary Smith <mary@x.test>"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: Some("Mary Smith".into()),
|
name: Some("Mary Smith".into()),
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "mary".into(),
|
local_part: "mary".into(),
|
||||||
domain: "x.test".into(),
|
domain: "x.test".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(mailbox(r#"jdoe@example.org"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#"jdoe@example.org"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "jdoe".into(),
|
local_part: "jdoe".into(),
|
||||||
domain: "example.org".into(),
|
domain: "example.org".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(mailbox(r#"Who? <one@y.test>"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#"Who? <one@y.test>"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: Some("Who?".into()),
|
name: Some("Who?".into()),
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "one".into(),
|
local_part: "one".into(),
|
||||||
domain: "y.test".into(),
|
domain: "y.test".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(mailbox(r#"<boss@nil.test>"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#"<boss@nil.test>"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "boss".into(),
|
local_part: "boss".into(),
|
||||||
domain: "nil.test".into(),
|
domain: "nil.test".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(mailbox(r#""Giant; \"Big\" Box" <sysservices@example.net>"#), Ok(("", MailboxRef {
|
assert_eq!(
|
||||||
|
mailbox(r#""Giant; \"Big\" Box" <sysservices@example.net>"#),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: Some(r#"Giant; "Big" Box"#.into()),
|
name: Some(r#"Giant; "Big" Box"#.into()),
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "sysservices".into(),
|
local_part: "sysservices".into(),
|
||||||
domain: "example.net".into(),
|
domain: "example.net".into(),
|
||||||
}
|
}
|
||||||
})));
|
}
|
||||||
|
))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_obs_domain_list() {
|
fn test_obs_domain_list() {
|
||||||
assert_eq!(obs_domain_list(r#"(shhh it's coming)
|
assert_eq!(
|
||||||
|
obs_domain_list(
|
||||||
|
r#"(shhh it's coming)
|
||||||
,
|
,
|
||||||
(not yet)
|
(not yet)
|
||||||
@33+4.com,,,,
|
@33+4.com,,,,
|
||||||
,,,,
|
,,,,
|
||||||
(again)
|
(again)
|
||||||
@example.com,@yep.com,@a,@b,,,@c"#),
|
@example.com,@yep.com,@a,@b,,,@c"#
|
||||||
Ok(("", vec!["33+4.com".into(), "example.com".into(), "yep.com".into(), "a".into(), "b".into(), "c".into()]))
|
),
|
||||||
|
Ok((
|
||||||
|
"",
|
||||||
|
vec![
|
||||||
|
"33+4.com".into(),
|
||||||
|
"example.com".into(),
|
||||||
|
"yep.com".into(),
|
||||||
|
"a".into(),
|
||||||
|
"b".into(),
|
||||||
|
"c".into()
|
||||||
|
]
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,10 +423,13 @@ mod tests {
|
||||||
fn test_enron1() {
|
fn test_enron1() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec("a..howard@enron.com"),
|
addr_spec("a..howard@enron.com"),
|
||||||
Ok(("", AddrSpec {
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
local_part: "a..howard".into(),
|
local_part: "a..howard".into(),
|
||||||
domain: "enron.com".into(),
|
domain: "enron.com".into(),
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,10 +437,13 @@ mod tests {
|
||||||
fn test_enron2() {
|
fn test_enron2() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec(".nelson@enron.com"),
|
addr_spec(".nelson@enron.com"),
|
||||||
Ok(("", AddrSpec {
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
local_part: ".nelson".into(),
|
local_part: ".nelson".into(),
|
||||||
domain: "enron.com".into(),
|
domain: "enron.com".into(),
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,25 +451,30 @@ mod tests {
|
||||||
fn test_enron3() {
|
fn test_enron3() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
addr_spec("ecn2760.conf.@enron.com"),
|
addr_spec("ecn2760.conf.@enron.com"),
|
||||||
Ok(("", AddrSpec {
|
Ok((
|
||||||
|
"",
|
||||||
|
AddrSpec {
|
||||||
local_part: "ecn2760.conf.".into(),
|
local_part: "ecn2760.conf.".into(),
|
||||||
domain: "enron.com".into(),
|
domain: "enron.com".into(),
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_enron4() {
|
fn test_enron4() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
mailbox(r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#),
|
mailbox(r#"<"mark_kopinski/intl/acim/americancentury"@americancentury.com@enron.com>"#),
|
||||||
Ok(("", MailboxRef {
|
Ok((
|
||||||
|
"",
|
||||||
|
MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
addrspec: AddrSpec {
|
addrspec: AddrSpec {
|
||||||
local_part: "mark_kopinski/intl/acim/americancentury".into(),
|
local_part: "mark_kopinski/intl/acim/americancentury".into(),
|
||||||
domain: "americancentury.com".into(),
|
domain: "americancentury.com".into(),
|
||||||
}
|
}
|
||||||
}))
|
}
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{take_while1, tag},
|
bytes::complete::{tag, take_while1},
|
||||||
character::complete::space0,
|
character::complete::space0,
|
||||||
combinator::{into, opt},
|
combinator::{into, opt},
|
||||||
multi::{many0, many1, separated_list1},
|
multi::{many0, many1, separated_list1},
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use crate::error::IMFError;
|
||||||
use crate::fragments::lazy;
|
use crate::fragments::lazy;
|
||||||
use crate::fragments::quoted::quoted_string;
|
use crate::fragments::quoted::quoted_string;
|
||||||
use crate::fragments::whitespace::{fws, is_obs_no_ws_ctl};
|
use crate::fragments::whitespace::{fws, is_obs_no_ws_ctl};
|
||||||
use crate::fragments::words::{atom, is_vchar};
|
use crate::fragments::words::{atom, is_vchar};
|
||||||
use crate::error::IMFError;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Default)]
|
#[derive(Debug, PartialEq, Default)]
|
||||||
pub struct Unstructured(pub String);
|
pub struct Unstructured(pub String);
|
||||||
|
@ -101,7 +101,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_phrase() {
|
fn test_phrase() {
|
||||||
assert_eq!(phrase("hello world"), Ok(("", "hello world".into())));
|
assert_eq!(phrase("hello world"), Ok(("", "hello world".into())));
|
||||||
assert_eq!(phrase("salut \"le\" monde"), Ok(("", "salut le monde".into())));
|
assert_eq!(
|
||||||
assert_eq!(phrase("fin\r\n du\r\nmonde"), Ok(("\r\nmonde", "fin du".into())));
|
phrase("salut \"le\" monde"),
|
||||||
|
Ok(("", "salut le monde".into()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
phrase("fin\r\n du\r\nmonde"),
|
||||||
|
Ok(("\r\nmonde", "fin du".into()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,17 +2,17 @@
|
||||||
pub mod model;
|
pub mod model;
|
||||||
|
|
||||||
// Generic
|
// Generic
|
||||||
|
pub mod misc_token;
|
||||||
|
mod quoted;
|
||||||
pub mod whitespace;
|
pub mod whitespace;
|
||||||
mod words;
|
mod words;
|
||||||
mod quoted;
|
|
||||||
pub mod misc_token;
|
|
||||||
|
|
||||||
// Header specific
|
// Header specific
|
||||||
mod mailbox;
|
|
||||||
mod address;
|
mod address;
|
||||||
mod identification;
|
|
||||||
pub mod trace;
|
|
||||||
mod datetime;
|
mod datetime;
|
||||||
pub mod lazy;
|
|
||||||
pub mod eager;
|
pub mod eager;
|
||||||
|
mod identification;
|
||||||
|
pub mod lazy;
|
||||||
|
mod mailbox;
|
||||||
pub mod section;
|
pub mod section;
|
||||||
|
pub mod trace;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use chrono::{DateTime, FixedOffset};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use chrono::{DateTime,FixedOffset};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct AddrSpec {
|
pub struct AddrSpec {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{anychar, satisfy},
|
character::complete::{anychar, satisfy},
|
||||||
combinator::opt,
|
combinator::opt,
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::{pair, preceded},
|
sequence::{pair, preceded},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::fragments::whitespace::{fws, cfws, is_obs_no_ws_ctl};
|
use crate::fragments::whitespace::{cfws, fws, is_obs_no_ws_ctl};
|
||||||
|
|
||||||
/// Quoted pair
|
/// Quoted pair
|
||||||
///
|
///
|
||||||
|
@ -58,9 +58,9 @@ pub fn quoted_string(input: &str) -> IResult<&str, String> {
|
||||||
let (input, content) = many0(pair(opt(fws), qcontent))(input)?;
|
let (input, content) = many0(pair(opt(fws), qcontent))(input)?;
|
||||||
|
|
||||||
// Rebuild string
|
// Rebuild string
|
||||||
let mut qstring = content.iter().fold(
|
let mut qstring = content
|
||||||
String::with_capacity(16),
|
.iter()
|
||||||
|mut acc, (maybe_wsp, c)| {
|
.fold(String::with_capacity(16), |mut acc, (maybe_wsp, c)| {
|
||||||
if let Some(wsp) = maybe_wsp {
|
if let Some(wsp) = maybe_wsp {
|
||||||
acc.push(*wsp);
|
acc.push(*wsp);
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,13 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_quoted_string() {
|
fn test_quoted_string() {
|
||||||
assert_eq!(quoted_string(" \"hello\\\"world\" "), Ok(("", "hello\"world".to_string())));
|
assert_eq!(
|
||||||
assert_eq!(quoted_string("\"hello\r\n world\""), Ok(("", "hello world".to_string())));
|
quoted_string(" \"hello\\\"world\" "),
|
||||||
|
Ok(("", "hello\"world".to_string()))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
quoted_string("\"hello\r\n world\""),
|
||||||
|
Ok(("", "hello world".to_string()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset};
|
|
||||||
use crate::fragments::model::{MailboxRef,MessageId, AddressRef};
|
|
||||||
use crate::fragments::misc_token::{Unstructured, PhraseList};
|
|
||||||
use crate::fragments::trace::ReceivedLog;
|
|
||||||
use crate::fragments::eager::Field;
|
use crate::fragments::eager::Field;
|
||||||
use crate::fragments::lazy;
|
use crate::fragments::lazy;
|
||||||
|
use crate::fragments::misc_token::{PhraseList, Unstructured};
|
||||||
|
use crate::fragments::model::{AddressRef, MailboxRef, MessageId};
|
||||||
|
use crate::fragments::trace::ReceivedLog;
|
||||||
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Default)]
|
#[derive(Debug, PartialEq, Default)]
|
||||||
pub struct Section<'a> {
|
pub struct Section<'a> {
|
||||||
|
@ -48,7 +48,7 @@ pub struct Section<'a> {
|
||||||
//@FIXME min and max limits are not enforced,
|
//@FIXME min and max limits are not enforced,
|
||||||
// it may result in missing data or silently overriden data.
|
// it may result in missing data or silently overriden data.
|
||||||
impl<'a> FromIterator<&'a Field<'a>> for Section<'a> {
|
impl<'a> FromIterator<&'a Field<'a>> for Section<'a> {
|
||||||
fn from_iter<I: IntoIterator<Item=&'a Field<'a>>>(iter: I) -> Self {
|
fn from_iter<I: IntoIterator<Item = &'a Field<'a>>>(iter: I) -> Self {
|
||||||
let mut section = Section::default();
|
let mut section = Section::default();
|
||||||
for field in iter {
|
for field in iter {
|
||||||
match field {
|
match field {
|
||||||
|
@ -67,11 +67,12 @@ impl<'a> FromIterator<&'a Field<'a>> for Section<'a> {
|
||||||
Field::Keywords(v) => section.keywords.push(v),
|
Field::Keywords(v) => section.keywords.push(v),
|
||||||
Field::ReturnPath(v) => section.return_path.push(v),
|
Field::ReturnPath(v) => section.return_path.push(v),
|
||||||
Field::Received(v) => section.received.push(v),
|
Field::Received(v) => section.received.push(v),
|
||||||
Field::Optional(k, v) => { section.optional.insert(k, v); },
|
Field::Optional(k, v) => {
|
||||||
|
section.optional.insert(k, v);
|
||||||
|
}
|
||||||
Field::Rescue(v) => section.unparsed.push(v),
|
Field::Rescue(v) => section.unparsed.push(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
section
|
section
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
use crate::error::IMFError;
|
||||||
|
use crate::fragments::{datetime, lazy, mailbox, misc_token, model, whitespace};
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
combinator::{map, opt, recognize},
|
combinator::{map, opt, recognize},
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
use crate::fragments::{datetime, mailbox, model, misc_token, whitespace, lazy};
|
|
||||||
use crate::error::IMFError;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct ReceivedLog<'a>(pub &'a str);
|
pub struct ReceivedLog<'a>(pub &'a str);
|
||||||
|
@ -34,10 +34,7 @@ pub fn received_body(input: &str) -> IResult<&str, &str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn return_path_body(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
pub fn return_path_body(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
||||||
alt((
|
alt((map(mailbox::angle_addr, |a| Some(a)), empty_path))(input)
|
||||||
map(mailbox::angle_addr, |a| Some(a)),
|
|
||||||
empty_path
|
|
||||||
))(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn empty_path(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
fn empty_path(input: &str) -> IResult<&str, Option<model::MailboxRef>> {
|
||||||
|
@ -61,7 +58,6 @@ fn received_tokens(input: &str) -> IResult<&str, &str> {
|
||||||
))(input)
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -76,11 +72,14 @@ mod tests {
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
received_body(hdrs),
|
received_body(hdrs),
|
||||||
Ok(("", r#"from smtp.example.com ([10.83.2.2])
|
Ok((
|
||||||
|
"",
|
||||||
|
r#"from smtp.example.com ([10.83.2.2])
|
||||||
by server with LMTP
|
by server with LMTP
|
||||||
id xxxxxxxxx
|
id xxxxxxxxx
|
||||||
(envelope-from <gitlab@example.com>)
|
(envelope-from <gitlab@example.com>)
|
||||||
for <me@example.com>"#))
|
for <me@example.com>"#
|
||||||
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
|
use crate::fragments::quoted::quoted_pair;
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::tag,
|
bytes::complete::tag,
|
||||||
character::complete::{crlf, satisfy, space0, space1},
|
character::complete::{crlf, satisfy, space0, space1},
|
||||||
combinator::{recognize, opt},
|
combinator::{opt, recognize},
|
||||||
multi::{many0, many1},
|
multi::{many0, many1},
|
||||||
sequence::tuple,
|
sequence::tuple,
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
use crate::fragments::quoted::quoted_pair;
|
|
||||||
|
|
||||||
// --- whitespaces and comments
|
// --- whitespaces and comments
|
||||||
|
|
||||||
|
@ -40,7 +40,6 @@ fn fold_marker(input: &str) -> IResult<&str, &str> {
|
||||||
space1(input)
|
space1(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Folding White Space with Comment
|
/// Folding White Space with Comment
|
||||||
///
|
///
|
||||||
/// Note: we drop the comments for now...
|
/// Note: we drop the comments for now...
|
||||||
|
@ -92,7 +91,10 @@ pub fn ctext(input: &str) -> IResult<&str, char> {
|
||||||
/// obs-ctext
|
/// obs-ctext
|
||||||
///```
|
///```
|
||||||
pub fn is_restr_ctext(c: char) -> bool {
|
pub fn is_restr_ctext(c: char) -> bool {
|
||||||
(c >= '\x21' && c <= '\x27') || (c >= '\x2A' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E') || !c.is_ascii()
|
(c >= '\x21' && c <= '\x27')
|
||||||
|
|| (c >= '\x2A' && c <= '\x5B')
|
||||||
|
|| (c >= '\x5D' && c <= '\x7E')
|
||||||
|
|| !c.is_ascii()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_ctext(c: char) -> bool {
|
pub fn is_ctext(c: char) -> bool {
|
||||||
|
@ -109,7 +111,11 @@ pub fn is_ctext(c: char) -> bool {
|
||||||
/// %d127 ; white space characters
|
/// %d127 ; white space characters
|
||||||
/// ```
|
/// ```
|
||||||
pub fn is_obs_no_ws_ctl(c: char) -> bool {
|
pub fn is_obs_no_ws_ctl(c: char) -> bool {
|
||||||
(c >= '\x01' && c <= '\x08') || c == '\x0b' || c == '\x0b' || (c >= '\x0e' && c<= '\x1f') || c == '\x7F'
|
(c >= '\x01' && c <= '\x08')
|
||||||
|
|| c == '\x0b'
|
||||||
|
|| c == '\x0b'
|
||||||
|
|| (c >= '\x0e' && c <= '\x1f')
|
||||||
|
|| c == '\x7F'
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -133,8 +139,20 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cfws() {
|
fn test_cfws() {
|
||||||
assert_eq!(cfws("(A nice \\) chap) <pete(his account)@silly.test(his host)>"), Ok(("<pete(his account)@silly.test(his host)>", "(A nice \\) chap) ")));
|
assert_eq!(
|
||||||
assert_eq!(cfws("(Chris's host.)public.example>,"), Ok(("public.example>,", "(Chris's host.)")));
|
cfws("(A nice \\) chap) <pete(his account)@silly.test(his host)>"),
|
||||||
assert_eq!(cfws("(double (comment) is fun) wouch"), Ok(("wouch", "(double (comment) is fun) ")));
|
Ok((
|
||||||
|
"<pete(his account)@silly.test(his host)>",
|
||||||
|
"(A nice \\) chap) "
|
||||||
|
))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cfws("(Chris's host.)public.example>,"),
|
||||||
|
Ok(("public.example>,", "(Chris's host.)"))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
cfws("(double (comment) is fun) wouch"),
|
||||||
|
Ok(("wouch", "(double (comment) is fun) "))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
|
use crate::fragments::whitespace::cfws;
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
bytes::complete::{tag, take_while1},
|
bytes::complete::{tag, take_while1},
|
||||||
combinator::{recognize, opt},
|
combinator::{opt, recognize},
|
||||||
multi::many0,
|
multi::many0,
|
||||||
sequence::{delimited, pair},
|
sequence::{delimited, pair},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
use crate::fragments::whitespace::cfws;
|
|
||||||
|
|
||||||
|
|
||||||
/// VCHAR definition
|
/// VCHAR definition
|
||||||
pub fn is_vchar(c: char) -> bool {
|
pub fn is_vchar(c: char) -> bool {
|
||||||
|
@ -42,7 +41,10 @@ pub fn atom(input: &str) -> IResult<&str, &str> {
|
||||||
///
|
///
|
||||||
/// `1*atext *("." 1*atext)`
|
/// `1*atext *("." 1*atext)`
|
||||||
pub fn dot_atom_text(input: &str) -> IResult<&str, &str> {
|
pub fn dot_atom_text(input: &str) -> IResult<&str, &str> {
|
||||||
recognize(pair(take_while1(is_atext), many0(pair(tag("."), take_while1(is_atext)))))(input)
|
recognize(pair(
|
||||||
|
take_while1(is_atext),
|
||||||
|
many0(pair(tag("."), take_while1(is_atext))),
|
||||||
|
))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// dot-atom
|
/// dot-atom
|
||||||
|
@ -54,13 +56,19 @@ pub fn dot_atom(input: &str) -> IResult<&str, &str> {
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn is_special(c: char) -> bool {
|
pub fn is_special(c: char) -> bool {
|
||||||
c == '(' || c == ')' ||
|
c == '('
|
||||||
c == '<' || c == '>' ||
|
|| c == ')'
|
||||||
c == '[' || c == ']' ||
|
|| c == '<'
|
||||||
c == ':' || c == ';' ||
|
|| c == '>'
|
||||||
c == '@' || c == '\\' ||
|
|| c == '['
|
||||||
c == ',' || c == '.' ||
|
|| c == ']'
|
||||||
c == '"'
|
|| c == ':'
|
||||||
|
|| c == ';'
|
||||||
|
|| c == '@'
|
||||||
|
|| c == '\\'
|
||||||
|
|| c == ','
|
||||||
|
|| c == '.'
|
||||||
|
|| c == '"'
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -84,16 +92,25 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_atom() {
|
fn test_atom() {
|
||||||
assert_eq!(atom("(skip) imf_codec (hidden) aerogramme"), Ok(("aerogramme", "imf_codec")));
|
assert_eq!(
|
||||||
|
atom("(skip) imf_codec (hidden) aerogramme"),
|
||||||
|
Ok(("aerogramme", "imf_codec"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dot_atom_text() {
|
fn test_dot_atom_text() {
|
||||||
assert_eq!(dot_atom_text("quentin.dufour.io abcdef"), Ok((" abcdef", "quentin.dufour.io")));
|
assert_eq!(
|
||||||
|
dot_atom_text("quentin.dufour.io abcdef"),
|
||||||
|
Ok((" abcdef", "quentin.dufour.io"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_dot_atom() {
|
fn test_dot_atom() {
|
||||||
assert_eq!(dot_atom(" (skip) quentin.dufour.io abcdef"), Ok(("abcdef", "quentin.dufour.io")));
|
assert_eq!(
|
||||||
|
dot_atom(" (skip) quentin.dufour.io abcdef"),
|
||||||
|
Ok(("abcdef", "quentin.dufour.io"))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
character::complete::space1,
|
|
||||||
bytes::complete::is_not,
|
bytes::complete::is_not,
|
||||||
|
character::complete::space1,
|
||||||
combinator::{all_consuming, recognize},
|
combinator::{all_consuming, recognize},
|
||||||
multi::{many0, many1},
|
multi::{many0, many1},
|
||||||
sequence::{pair, tuple},
|
sequence::{pair, tuple},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::error::IMFError;
|
use crate::error::IMFError;
|
||||||
use crate::fragments::whitespace;
|
use crate::fragments::whitespace;
|
||||||
use crate::multipass::guess_charset;
|
|
||||||
use crate::multipass::field_lazy;
|
use crate::multipass::field_lazy;
|
||||||
|
use crate::multipass::guess_charset;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Parsed<'a> {
|
pub struct Parsed<'a> {
|
||||||
|
@ -21,7 +21,10 @@ pub struct Parsed<'a> {
|
||||||
pub fn new<'a>(gcha: &'a guess_charset::Parsed<'a>) -> Result<Parsed<'a>, IMFError<'a>> {
|
pub fn new<'a>(gcha: &'a guess_charset::Parsed<'a>) -> Result<Parsed<'a>, IMFError<'a>> {
|
||||||
all_consuming(many0(foldable_line))(&gcha.header)
|
all_consuming(many0(foldable_line))(&gcha.header)
|
||||||
.map_err(|e| IMFError::ExtractFields(e))
|
.map_err(|e| IMFError::ExtractFields(e))
|
||||||
.map(|(_, fields)| Parsed { fields, body: gcha.body })
|
.map(|(_, fields)| Parsed {
|
||||||
|
fields,
|
||||||
|
body: gcha.body,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parsed<'a> {
|
impl<'a> Parsed<'a> {
|
||||||
|
@ -38,8 +41,9 @@ fn foldable_line(input: &str) -> IResult<&str, &str> {
|
||||||
is_not("\r\n"),
|
is_not("\r\n"),
|
||||||
many0(pair(
|
many0(pair(
|
||||||
many1(pair(whitespace::perm_crlf, space1)),
|
many1(pair(whitespace::perm_crlf, space1)),
|
||||||
is_not("\r\n"))),
|
is_not("\r\n"),
|
||||||
whitespace::perm_crlf
|
)),
|
||||||
|
whitespace::perm_crlf,
|
||||||
)))(input)
|
)))(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@ pub struct Parsed<'a> {
|
||||||
|
|
||||||
pub fn new<'a>(p: &'a field_lazy::Parsed<'a>) -> Parsed<'a> {
|
pub fn new<'a>(p: &'a field_lazy::Parsed<'a>) -> Parsed<'a> {
|
||||||
Parsed {
|
Parsed {
|
||||||
fields: p.fields
|
fields: p
|
||||||
|
.fields
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|entry| entry.try_into().ok())
|
.filter_map(|entry| entry.try_into().ok())
|
||||||
.collect(),
|
.collect(),
|
||||||
|
@ -33,9 +34,12 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_field_body() {
|
fn test_field_body() {
|
||||||
assert_eq!(new(&field_lazy::Parsed {
|
assert_eq!(
|
||||||
|
new(&field_lazy::Parsed {
|
||||||
fields: vec![
|
fields: vec![
|
||||||
lazy::Field::From(lazy::MailboxList("hello@world.com,\r\n\talice@wonderlands.com\r\n")),
|
lazy::Field::From(lazy::MailboxList(
|
||||||
|
"hello@world.com,\r\n\talice@wonderlands.com\r\n"
|
||||||
|
)),
|
||||||
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
|
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
|
||||||
],
|
],
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
|
@ -66,14 +70,20 @@ mod tests {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::multipass::extract_fields;
|
|
||||||
use crate::fragments::misc_token;
|
use crate::fragments::misc_token;
|
||||||
|
use crate::multipass::extract_fields;
|
||||||
fn lazy_eager<F>(input: &str, func: F)
|
fn lazy_eager<F>(input: &str, func: F)
|
||||||
where F: Fn(&eager::Field) {
|
where
|
||||||
let field = extract_fields::Parsed { fields: vec![input], body: b""};
|
F: Fn(&eager::Field),
|
||||||
|
{
|
||||||
|
let field = extract_fields::Parsed {
|
||||||
|
fields: vec![input],
|
||||||
|
body: b"",
|
||||||
|
};
|
||||||
let lazy = field_lazy::new(&field);
|
let lazy = field_lazy::new(&field);
|
||||||
let eager = new(&lazy);
|
let eager = new(&lazy);
|
||||||
func(eager.fields.first().unwrap())
|
func(eager.fields.first().unwrap())
|
||||||
|
@ -83,7 +93,8 @@ mod tests {
|
||||||
fn test_from() {
|
fn test_from() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"From: \"Joe Q. Public\" <john.q.public@example.com>\r\n",
|
"From: \"Joe Q. Public\" <john.q.public@example.com>\r\n",
|
||||||
|from| assert_eq!(
|
|from| {
|
||||||
|
assert_eq!(
|
||||||
from,
|
from,
|
||||||
&eager::Field::From(vec![model::MailboxRef {
|
&eager::Field::From(vec![model::MailboxRef {
|
||||||
name: Some("Joe Q. Public".into()),
|
name: Some("Joe Q. Public".into()),
|
||||||
|
@ -93,6 +104,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}])
|
}])
|
||||||
)
|
)
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,7 +112,8 @@ mod tests {
|
||||||
fn test_sender() {
|
fn test_sender() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"Sender: Michael Jones <mjones@machine.example>\r\n",
|
"Sender: Michael Jones <mjones@machine.example>\r\n",
|
||||||
|sender| assert_eq!(
|
|sender| {
|
||||||
|
assert_eq!(
|
||||||
sender,
|
sender,
|
||||||
&eager::Field::Sender(model::MailboxRef {
|
&eager::Field::Sender(model::MailboxRef {
|
||||||
name: Some("Michael Jones".into()),
|
name: Some("Michael Jones".into()),
|
||||||
|
@ -110,6 +123,7 @@ mod tests {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,18 +131,18 @@ mod tests {
|
||||||
fn test_reply_to() {
|
fn test_reply_to() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"Reply-To: \"Mary Smith: Personal Account\" <smith@home.example>\r\n",
|
"Reply-To: \"Mary Smith: Personal Account\" <smith@home.example>\r\n",
|
||||||
|reply_to| assert_eq!(
|
|reply_to| {
|
||||||
|
assert_eq!(
|
||||||
reply_to,
|
reply_to,
|
||||||
&eager::Field::ReplyTo(
|
&eager::Field::ReplyTo(vec![model::AddressRef::Single(model::MailboxRef {
|
||||||
vec![model::AddressRef::Single(model::MailboxRef {
|
|
||||||
name: Some("Mary Smith: Personal Account".into()),
|
name: Some("Mary Smith: Personal Account".into()),
|
||||||
addrspec: model::AddrSpec {
|
addrspec: model::AddrSpec {
|
||||||
local_part: "smith".into(),
|
local_part: "smith".into(),
|
||||||
domain: "home.example".into(),
|
domain: "home.example".into(),
|
||||||
},
|
},
|
||||||
})]
|
})])
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,134 +150,144 @@ mod tests {
|
||||||
fn test_to() {
|
fn test_to() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;\r\n",
|
"To: A Group:Ed Jones <c@a.test>,joe@where.test,John <jdoe@one.test>;\r\n",
|
||||||
|to| assert_eq!(
|
|to| {
|
||||||
|
assert_eq!(
|
||||||
to,
|
to,
|
||||||
&eager::Field::To(vec![model::AddressRef::Many(model::GroupRef {
|
&eager::Field::To(vec![model::AddressRef::Many(model::GroupRef {
|
||||||
name: "A Group".into(),
|
name: "A Group".into(),
|
||||||
participants: vec![
|
participants: vec![
|
||||||
model::MailboxRef {
|
model::MailboxRef {
|
||||||
name: Some("Ed Jones".into()),
|
name: Some("Ed Jones".into()),
|
||||||
addrspec: model::AddrSpec { local_part: "c".into(), domain: "a.test".into() },
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "c".into(),
|
||||||
|
domain: "a.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
model::MailboxRef {
|
model::MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
addrspec: model::AddrSpec { local_part: "joe".into(), domain: "where.test".into() },
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "joe".into(),
|
||||||
|
domain: "where.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
model::MailboxRef {
|
model::MailboxRef {
|
||||||
name: Some("John".into()),
|
name: Some("John".into()),
|
||||||
addrspec: model::AddrSpec { local_part: "jdoe".into(), domain: "one.test".into() },
|
addrspec: model::AddrSpec {
|
||||||
|
local_part: "jdoe".into(),
|
||||||
|
domain: "one.test".into()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})])
|
})])
|
||||||
)
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cc() {
|
fn test_cc() {
|
||||||
lazy_eager(
|
lazy_eager("Cc: Undisclosed recipients:;\r\n", |cc| {
|
||||||
"Cc: Undisclosed recipients:;\r\n",
|
assert_eq!(
|
||||||
|cc| assert_eq!(
|
|
||||||
cc,
|
cc,
|
||||||
&eager::Field::Cc(vec![model::AddressRef::Many(model::GroupRef {
|
&eager::Field::Cc(vec![model::AddressRef::Many(model::GroupRef {
|
||||||
name: "Undisclosed recipients".into(),
|
name: "Undisclosed recipients".into(),
|
||||||
participants: vec![],
|
participants: vec![],
|
||||||
})]),
|
})]),
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bcc() {
|
fn test_bcc() {
|
||||||
lazy_eager(
|
lazy_eager("Bcc: (empty)\r\n", |bcc| {
|
||||||
"Bcc: (empty)\r\n",
|
assert_eq!(bcc, &eager::Field::Bcc(vec![]),)
|
||||||
|bcc| assert_eq!(
|
});
|
||||||
bcc,
|
|
||||||
&eager::Field::Bcc(vec![]),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
lazy_eager(
|
lazy_eager("Bcc: \r\n", |bcc| {
|
||||||
"Bcc: \r\n",
|
assert_eq!(bcc, &eager::Field::Bcc(vec![]),)
|
||||||
|bcc| assert_eq!(
|
});
|
||||||
bcc,
|
|
||||||
&eager::Field::Bcc(vec![]),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_message_id() {
|
fn test_message_id() {
|
||||||
lazy_eager(
|
lazy_eager("Message-ID: <310@[127.0.0.1]>\r\n", |msg_id| {
|
||||||
"Message-ID: <310@[127.0.0.1]>\r\n",
|
assert_eq!(
|
||||||
|msg_id| assert_eq!(
|
|
||||||
msg_id,
|
msg_id,
|
||||||
&eager::Field::MessageID(
|
&eager::Field::MessageID(model::MessageId {
|
||||||
model::MessageId { left: "310", right: "127.0.0.1" },
|
left: "310",
|
||||||
)
|
right: "127.0.0.1"
|
||||||
)
|
},)
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_in_reply_to() {
|
fn test_in_reply_to() {
|
||||||
lazy_eager(
|
lazy_eager("In-Reply-To: <a@b> <c@example.com>\r\n", |irt| {
|
||||||
"In-Reply-To: <a@b> <c@example.com>\r\n",
|
assert_eq!(
|
||||||
|irt| assert_eq!(
|
|
||||||
irt,
|
irt,
|
||||||
&eager::Field::InReplyTo(
|
&eager::Field::InReplyTo(vec![
|
||||||
vec![
|
model::MessageId {
|
||||||
model::MessageId { left: "a", right: "b" },
|
left: "a",
|
||||||
model::MessageId { left: "c", right: "example.com" },
|
right: "b"
|
||||||
]
|
},
|
||||||
)
|
model::MessageId {
|
||||||
)
|
left: "c",
|
||||||
|
right: "example.com"
|
||||||
|
},
|
||||||
|
])
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_references() {
|
fn test_references() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"References: <1234@local.machine.example> <3456@example.net>\r\n",
|
"References: <1234@local.machine.example> <3456@example.net>\r\n",
|
||||||
|refer| assert_eq!(
|
|refer| {
|
||||||
|
assert_eq!(
|
||||||
refer,
|
refer,
|
||||||
&eager::Field::References(
|
&eager::Field::References(vec![
|
||||||
vec![
|
model::MessageId {
|
||||||
model::MessageId { left: "1234", right: "local.machine.example" },
|
left: "1234",
|
||||||
model::MessageId { left: "3456", right: "example.net" },
|
right: "local.machine.example"
|
||||||
]
|
},
|
||||||
)
|
model::MessageId {
|
||||||
|
left: "3456",
|
||||||
|
right: "example.net"
|
||||||
|
},
|
||||||
|
])
|
||||||
)
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_subject() {
|
fn test_subject() {
|
||||||
lazy_eager(
|
lazy_eager("Subject: Aérogramme\r\n", |subject| {
|
||||||
"Subject: Aérogramme\r\n",
|
assert_eq!(
|
||||||
|subject| assert_eq!(
|
|
||||||
subject,
|
subject,
|
||||||
&eager::Field::Subject(misc_token::Unstructured("Aérogramme".into()))
|
&eager::Field::Subject(misc_token::Unstructured("Aérogramme".into()))
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_comments() {
|
fn test_comments() {
|
||||||
lazy_eager(
|
lazy_eager("Comments: 😛 easter egg!\r\n", |comments| {
|
||||||
"Comments: 😛 easter egg!\r\n",
|
assert_eq!(
|
||||||
|comments| assert_eq!(
|
|
||||||
comments,
|
comments,
|
||||||
&eager::Field::Comments(misc_token::Unstructured("😛 easter egg!".into())),
|
&eager::Field::Comments(misc_token::Unstructured("😛 easter egg!".into())),
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_keywords() {
|
fn test_keywords() {
|
||||||
lazy_eager(
|
lazy_eager(
|
||||||
"Keywords: fantasque, farfelu, fanfreluche\r\n",
|
"Keywords: fantasque, farfelu, fanfreluche\r\n",
|
||||||
|keywords| assert_eq!(
|
|keywords| {
|
||||||
|
assert_eq!(
|
||||||
keywords,
|
keywords,
|
||||||
&eager::Field::Keywords(misc_token::PhraseList(vec![
|
&eager::Field::Keywords(misc_token::PhraseList(vec![
|
||||||
"fantasque".into(),
|
"fantasque".into(),
|
||||||
|
@ -271,6 +295,7 @@ mod tests {
|
||||||
"fanfreluche".into()
|
"fanfreluche".into()
|
||||||
]))
|
]))
|
||||||
)
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -293,9 +318,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_wrong_fields() {
|
fn test_wrong_fields() {
|
||||||
let fullmail = r#"Return-Path: xoxo
|
let fullmail = r#"Return-Path: xoxo
|
||||||
From: !!!!
|
From: !!!!
|
||||||
|
|
||||||
Hello world"#;
|
Hello world"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
section(fullmail),
|
section(fullmail),
|
||||||
Ok(("Hello world", HeaderSection {
|
Ok(("Hello world", HeaderSection {
|
||||||
|
@ -308,5 +333,4 @@ Hello world"#;
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_field_name() {
|
fn test_field_name() {
|
||||||
assert_eq!(new(&extract_fields::Parsed {
|
assert_eq!(
|
||||||
|
new(&extract_fields::Parsed {
|
||||||
fields: vec![
|
fields: vec![
|
||||||
"From: hello@world.com,\r\n\talice@wonderlands.com\r\n",
|
"From: hello@world.com,\r\n\talice@wonderlands.com\r\n",
|
||||||
"Date: 12 Mar 1997 07:33:25 Z\r\n",
|
"Date: 12 Mar 1997 07:33:25 Z\r\n",
|
||||||
|
@ -36,10 +37,13 @@ mod tests {
|
||||||
}),
|
}),
|
||||||
Parsed {
|
Parsed {
|
||||||
fields: vec![
|
fields: vec![
|
||||||
lazy::Field::From(lazy::MailboxList("hello@world.com,\r\n\talice@wonderlands.com\r\n")),
|
lazy::Field::From(lazy::MailboxList(
|
||||||
|
"hello@world.com,\r\n\talice@wonderlands.com\r\n"
|
||||||
|
)),
|
||||||
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
|
lazy::Field::Date(lazy::DateTime("12 Mar 1997 07:33:25 Z\r\n")),
|
||||||
],
|
],
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use std::borrow::Cow;
|
|
||||||
use chardetng::EncodingDetector;
|
use chardetng::EncodingDetector;
|
||||||
use encoding_rs::Encoding;
|
use encoding_rs::Encoding;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::error::IMFError;
|
use crate::error::IMFError;
|
||||||
use crate::multipass::segment;
|
|
||||||
use crate::multipass::extract_fields;
|
use crate::multipass::extract_fields;
|
||||||
|
use crate::multipass::segment;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Parsed<'a> {
|
pub struct Parsed<'a> {
|
||||||
|
@ -30,7 +30,7 @@ pub fn new<'a>(seg: &'a segment::Parsed<'a>) -> Parsed<'a> {
|
||||||
header,
|
header,
|
||||||
encoding,
|
encoding,
|
||||||
malformed,
|
malformed,
|
||||||
body: seg.body
|
body: seg.body,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,13 +50,13 @@ mod tests {
|
||||||
new(&segment::Parsed {
|
new(&segment::Parsed {
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
header: b"From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n",
|
header: b"From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n",
|
||||||
}
|
}),
|
||||||
),
|
|
||||||
Parsed {
|
Parsed {
|
||||||
header: "From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n".into(),
|
header: "From: hello@world.com\r\nDate: 12 Mar 1997 07:33:25 Z\r\n".into(),
|
||||||
encoding: encoding_rs::UTF_8,
|
encoding: encoding_rs::UTF_8,
|
||||||
malformed: false,
|
malformed: false,
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,8 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_section() {
|
fn test_section() {
|
||||||
assert_eq!(new(&field_eager::Parsed {
|
assert_eq!(
|
||||||
|
new(&field_eager::Parsed {
|
||||||
fields: vec![
|
fields: vec![
|
||||||
eager::Field::From(vec![
|
eager::Field::From(vec![
|
||||||
model::MailboxRef {
|
model::MailboxRef {
|
||||||
|
@ -69,14 +70,17 @@ mod tests {
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
date: Some(&FixedOffset::east_opt(0)
|
date: Some(
|
||||||
|
&FixedOffset::east_opt(0)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_ymd_and_hms(1997, 03, 12, 7, 33, 25)
|
.with_ymd_and_hms(1997, 03, 12, 7, 33, 25)
|
||||||
.unwrap()),
|
.unwrap()
|
||||||
|
),
|
||||||
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
body: b"Hello world!",
|
body: b"Hello world!",
|
||||||
});
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
pub mod segment;
|
|
||||||
pub mod guess_charset;
|
|
||||||
pub mod extract_fields;
|
pub mod extract_fields;
|
||||||
pub mod field_lazy;
|
|
||||||
pub mod field_eager;
|
pub mod field_eager;
|
||||||
|
pub mod field_lazy;
|
||||||
|
pub mod guess_charset;
|
||||||
pub mod header_section;
|
pub mod header_section;
|
||||||
|
pub mod segment;
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use nom::{
|
use nom::{
|
||||||
IResult,
|
|
||||||
branch::alt,
|
branch::alt,
|
||||||
bytes::complete::{is_not, tag},
|
bytes::complete::{is_not, tag},
|
||||||
combinator::recognize,
|
combinator::recognize,
|
||||||
sequence::{pair, terminated},
|
|
||||||
multi::many0,
|
multi::many0,
|
||||||
|
sequence::{pair, terminated},
|
||||||
|
IResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::multipass::guess_charset;
|
|
||||||
use crate::error::IMFError;
|
use crate::error::IMFError;
|
||||||
|
use crate::multipass::guess_charset;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Parsed<'a> {
|
pub struct Parsed<'a> {
|
||||||
|
@ -21,10 +21,7 @@ const LF: u8 = 0x0A;
|
||||||
const CRLF: &[u8] = &[CR, LF];
|
const CRLF: &[u8] = &[CR, LF];
|
||||||
|
|
||||||
pub fn new<'a>(buffer: &'a [u8]) -> Result<Parsed<'a>, IMFError<'a>> {
|
pub fn new<'a>(buffer: &'a [u8]) -> Result<Parsed<'a>, IMFError<'a>> {
|
||||||
terminated(
|
terminated(recognize(many0(line)), obs_crlf)(buffer)
|
||||||
recognize(many0(line)),
|
|
||||||
obs_crlf
|
|
||||||
)(buffer)
|
|
||||||
.map_err(|e| IMFError::Segment(e))
|
.map_err(|e| IMFError::Segment(e))
|
||||||
.map(|(body, header)| Parsed { header, body })
|
.map(|(body, header)| Parsed { header, body })
|
||||||
}
|
}
|
||||||
|
@ -36,10 +33,7 @@ impl<'a> Parsed<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn line(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
|
fn line(input: &[u8]) -> IResult<&[u8], (&[u8], &[u8])> {
|
||||||
pair(
|
pair(is_not(CRLF), obs_crlf)(input)
|
||||||
is_not(CRLF),
|
|
||||||
obs_crlf,
|
|
||||||
)(input)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn obs_crlf(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
fn obs_crlf(input: &[u8]) -> IResult<&[u8], &[u8]> {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use imf_codec::multipass::segment;
|
|
||||||
use imf_codec::fragments::section::Section;
|
use imf_codec::fragments::section::Section;
|
||||||
|
use imf_codec::multipass::segment;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
|
||||||
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
||||||
where F: FnOnce(&Section) -> () {
|
where
|
||||||
|
F: FnOnce(&Section) -> (),
|
||||||
|
{
|
||||||
let seg = segment::new(input).unwrap();
|
let seg = segment::new(input).unwrap();
|
||||||
let charset = seg.charset();
|
let charset = seg.charset();
|
||||||
let fields = charset.fields().unwrap();
|
let fields = charset.fields().unwrap();
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
use imf_codec::fragments::section;
|
||||||
|
use imf_codec::multipass;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use imf_codec::multipass;
|
use std::path::PathBuf;
|
||||||
use imf_codec::fragments::section;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
||||||
where F: FnOnce(§ion::Section) -> () {
|
where
|
||||||
|
F: FnOnce(§ion::Section) -> (),
|
||||||
|
{
|
||||||
let seg = multipass::segment::new(input).unwrap();
|
let seg = multipass::segment::new(input).unwrap();
|
||||||
let charset = seg.charset();
|
let charset = seg.charset();
|
||||||
let fields = charset.fields().unwrap();
|
let fields = charset.fields().unwrap();
|
||||||
|
@ -28,17 +30,12 @@ fn test_enron500k() {
|
||||||
|
|
||||||
let known_bad_fields = HashSet::from([
|
let known_bad_fields = HashSet::from([
|
||||||
"white-s/calendar/113.", // To: east <7..>
|
"white-s/calendar/113.", // To: east <7..>
|
||||||
|
|
||||||
"skilling-j/inbox/223.", // From: pep <performance.>
|
"skilling-j/inbox/223.", // From: pep <performance.>
|
||||||
|
|
||||||
"jones-t/all_documents/9806.", // To: <"tibor.vizkelety":@enron.com>
|
"jones-t/all_documents/9806.", // To: <"tibor.vizkelety":@enron.com>
|
||||||
"jones-t/notes_inbox/3303.", // To: <"tibor.vizkelety":@enron.com>
|
"jones-t/notes_inbox/3303.", // To: <"tibor.vizkelety":@enron.com>
|
||||||
|
|
||||||
"lokey-t/calendar/33.", // A second Date entry for the calendar containing
|
"lokey-t/calendar/33.", // A second Date entry for the calendar containing
|
||||||
// Date: Monday, March 12
|
// Date: Monday, March 12
|
||||||
|
|
||||||
"zipper-a/inbox/199.", // To: e-mail <mari.>
|
"zipper-a/inbox/199.", // To: e-mail <mari.>
|
||||||
|
|
||||||
"dasovich-j/deleted_items/128.", // To: f62489 <g>
|
"dasovich-j/deleted_items/128.", // To: f62489 <g>
|
||||||
"dasovich-j/all_documents/677.", // To: w/assts <govt.>
|
"dasovich-j/all_documents/677.", // To: w/assts <govt.>
|
||||||
"dasovich-j/all_documents/8984.", // To: <"ft.com.users":@enron.com>
|
"dasovich-j/all_documents/8984.", // To: <"ft.com.users":@enron.com>
|
||||||
|
@ -54,7 +51,6 @@ fn test_enron500k() {
|
||||||
"dasovich-j/notes_inbox/1706.", // To: <"ft.com.users":@enron.com>
|
"dasovich-j/notes_inbox/1706.", // To: <"ft.com.users":@enron.com>
|
||||||
"dasovich-j/notes_inbox/1489.", // To: <"economist.com.readers":@enron.com>
|
"dasovich-j/notes_inbox/1489.", // To: <"economist.com.readers":@enron.com>
|
||||||
"dasovich-j/notes_inbox/5.", // To: w/assts <govt.>
|
"dasovich-j/notes_inbox/5.", // To: w/assts <govt.>
|
||||||
|
|
||||||
"kaminski-v/sites/19.", // To: <"the.desk":@enron.com>
|
"kaminski-v/sites/19.", // To: <"the.desk":@enron.com>
|
||||||
"kaminski-v/sites/1.", // To: <"the.desk":@enron.com>
|
"kaminski-v/sites/1.", // To: <"the.desk":@enron.com>
|
||||||
"kaminski-v/discussion_threads/5082.", // To: <"ft.com.users":@enron.com>
|
"kaminski-v/discussion_threads/5082.", // To: <"ft.com.users":@enron.com>
|
||||||
|
@ -71,7 +67,6 @@ fn test_enron500k() {
|
||||||
"kaminski-v/technical/7.", // To: <"the.desk":@enron.com>
|
"kaminski-v/technical/7.", // To: <"the.desk":@enron.com>
|
||||||
"kaminski-v/notes_inbox/140.", // To: dogs <breakthrough.>, cats <breaktkhrough.>, risk <breakthrough.>,\r\n\tleaders <breaktkhrough.>
|
"kaminski-v/notes_inbox/140.", // To: dogs <breakthrough.>, cats <breaktkhrough.>, risk <breakthrough.>,\r\n\tleaders <breaktkhrough.>
|
||||||
"kaminski-v/notes_inbox/95.", // To + CC failed: cats <breaktkhrough.>, risk <breakthrough.>, leaders <breaktkhrough.>
|
"kaminski-v/notes_inbox/95.", // To + CC failed: cats <breaktkhrough.>, risk <breakthrough.>, leaders <breaktkhrough.>
|
||||||
|
|
||||||
"kean-s/archiving/untitled/1232.", // To: w/assts <govt.>, mark.palmer@enron.com, karen.denne@enron.com
|
"kean-s/archiving/untitled/1232.", // To: w/assts <govt.>, mark.palmer@enron.com, karen.denne@enron.com
|
||||||
"kean-s/archiving/untitled/1688.", // To: w/assts <govt.>
|
"kean-s/archiving/untitled/1688.", // To: w/assts <govt.>
|
||||||
"kean-s/sent/198.", // To: w/assts <govt.>, mark.palmer@enron.com, karen.denne@enron.com
|
"kean-s/sent/198.", // To: w/assts <govt.>, mark.palmer@enron.com, karen.denne@enron.com
|
||||||
|
@ -83,7 +78,6 @@ fn test_enron500k() {
|
||||||
"kean-s/all_documents/640.", // To: w/assts <govt.>
|
"kean-s/all_documents/640.", // To: w/assts <govt.>
|
||||||
"kean-s/all_documents/1095.", // To: w/assts <govt.>
|
"kean-s/all_documents/1095.", // To: w/assts <govt.>
|
||||||
"kean-s/attachments/2030.", // To: w/assts <govt.>
|
"kean-s/attachments/2030.", // To: w/assts <govt.>
|
||||||
|
|
||||||
"williams-w3/operations_committee_isas/10.", // To: z34655 <m>
|
"williams-w3/operations_committee_isas/10.", // To: z34655 <m>
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -92,7 +86,10 @@ fn test_enron500k() {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
for entry in WalkDir::new(d.as_path()).into_iter().filter_map(|file| file.ok()) {
|
for entry in WalkDir::new(d.as_path())
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|file| file.ok())
|
||||||
|
{
|
||||||
if entry.metadata().unwrap().is_file() {
|
if entry.metadata().unwrap().is_file() {
|
||||||
let mail_path = entry.path();
|
let mail_path = entry.path();
|
||||||
let suffix = &mail_path.to_str().unwrap()[prefix_sz..];
|
let suffix = &mail_path.to_str().unwrap()[prefix_sz..];
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use chrono::{FixedOffset, TimeZone};
|
use chrono::{FixedOffset, TimeZone};
|
||||||
use std::collections::HashMap;
|
use imf_codec::fragments::{misc_token, model, section, trace};
|
||||||
use imf_codec::multipass;
|
use imf_codec::multipass;
|
||||||
use imf_codec::fragments::{model, misc_token, trace, section};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
fn parser<'a, F>(input: &'a [u8], func: F) -> ()
|
||||||
where F: FnOnce(§ion::Section) -> () {
|
where
|
||||||
|
F: FnOnce(§ion::Section) -> (),
|
||||||
|
{
|
||||||
let seg = multipass::segment::new(input).unwrap();
|
let seg = multipass::segment::new(input).unwrap();
|
||||||
let charset = seg.charset();
|
let charset = seg.charset();
|
||||||
let fields = charset.fields().unwrap();
|
let fields = charset.fields().unwrap();
|
||||||
|
@ -48,29 +50,35 @@ References: <1234@local.machine.example>
|
||||||
Unknown: unknown
|
Unknown: unknown
|
||||||
|
|
||||||
This is a reply to your hello.
|
This is a reply to your hello.
|
||||||
"#.as_bytes();
|
"#
|
||||||
parser(fullmail, |parsed_section|
|
.as_bytes();
|
||||||
|
parser(fullmail, |parsed_section| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parsed_section,
|
parsed_section,
|
||||||
§ion::Section {
|
§ion::Section {
|
||||||
date: Some(&FixedOffset::east_opt(2 * 3600)
|
date: Some(
|
||||||
|
&FixedOffset::east_opt(2 * 3600)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.with_ymd_and_hms(2023, 06, 13, 10, 01, 10)
|
.with_ymd_and_hms(2023, 06, 13, 10, 01, 10)
|
||||||
.unwrap()),
|
.unwrap()
|
||||||
|
),
|
||||||
|
|
||||||
from: vec![&model::MailboxRef {
|
from: vec![
|
||||||
|
&model::MailboxRef {
|
||||||
name: Some("Mary Smith".into()),
|
name: Some("Mary Smith".into()),
|
||||||
addrspec: model::AddrSpec {
|
addrspec: model::AddrSpec {
|
||||||
local_part: "mary".into(),
|
local_part: "mary".into(),
|
||||||
domain: "example.net".into(),
|
domain: "example.net".into(),
|
||||||
}
|
}
|
||||||
}, &model::MailboxRef {
|
},
|
||||||
|
&model::MailboxRef {
|
||||||
name: Some("Alan".into()),
|
name: Some("Alan".into()),
|
||||||
addrspec: model::AddrSpec {
|
addrspec: model::AddrSpec {
|
||||||
local_part: "alan".into(),
|
local_part: "alan".into(),
|
||||||
domain: "example".into(),
|
domain: "example".into(),
|
||||||
}
|
}
|
||||||
}],
|
}
|
||||||
|
],
|
||||||
|
|
||||||
sender: Some(&model::MailboxRef {
|
sender: Some(&model::MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -106,33 +114,41 @@ This is a reply to your hello.
|
||||||
|
|
||||||
bcc: vec![],
|
bcc: vec![],
|
||||||
|
|
||||||
msg_id: Some(&model::MessageId { left: "3456", right: "example.net" }),
|
msg_id: Some(&model::MessageId {
|
||||||
in_reply_to: vec![&model::MessageId { left: "1234", right: "local.machine.example" }],
|
left: "3456",
|
||||||
references: vec![&model::MessageId { left: "1234", right: "local.machine.example" }],
|
right: "example.net"
|
||||||
|
}),
|
||||||
|
in_reply_to: vec![&model::MessageId {
|
||||||
|
left: "1234",
|
||||||
|
right: "local.machine.example"
|
||||||
|
}],
|
||||||
|
references: vec![&model::MessageId {
|
||||||
|
left: "1234",
|
||||||
|
right: "local.machine.example"
|
||||||
|
}],
|
||||||
|
|
||||||
subject: Some(&misc_token::Unstructured("Re: Saying Hello".into())),
|
subject: Some(&misc_token::Unstructured("Re: Saying Hello".into())),
|
||||||
|
|
||||||
comments: vec![
|
comments: vec![
|
||||||
&misc_token::Unstructured("A simple message".into()),
|
&misc_token::Unstructured("A simple message".into()),
|
||||||
&misc_token::Unstructured("Not that complicated".into()),
|
&misc_token::Unstructured("Not that complicated".into()),
|
||||||
&misc_token::Unstructured("not valid header name but should be accepted by the parser.".into()),
|
&misc_token::Unstructured(
|
||||||
|
"not valid header name but should be accepted by the parser.".into()
|
||||||
|
),
|
||||||
],
|
],
|
||||||
|
|
||||||
keywords: vec![
|
keywords: vec![
|
||||||
&misc_token::PhraseList(vec![
|
&misc_token::PhraseList(vec!["hello".into(), "world".into(),]),
|
||||||
"hello".into(),
|
&misc_token::PhraseList(vec!["salut".into(), "le".into(), "monde".into(),]),
|
||||||
"world".into(),
|
|
||||||
]),
|
|
||||||
&misc_token::PhraseList(vec![
|
|
||||||
"salut".into(),
|
|
||||||
"le".into(),
|
|
||||||
"monde".into(),
|
|
||||||
]),
|
|
||||||
],
|
],
|
||||||
|
|
||||||
received: vec![
|
received: vec![&trace::ReceivedLog(
|
||||||
&trace::ReceivedLog("from smtp.example.com ([10.83.2.2])\n\tby doradille with LMTP\n\tid xyzabcd\n\t(envelope-from <gitlab@example.com>)\n\tfor <quentin@example.com>")
|
r#"from smtp.example.com ([10.83.2.2])
|
||||||
],
|
by doradille with LMTP
|
||||||
|
id xyzabcd
|
||||||
|
(envelope-from <gitlab@example.com>)
|
||||||
|
for <quentin@example.com>"#
|
||||||
|
)],
|
||||||
|
|
||||||
return_path: vec![&model::MailboxRef {
|
return_path: vec![&model::MailboxRef {
|
||||||
name: None,
|
name: None,
|
||||||
|
@ -143,7 +159,10 @@ This is a reply to your hello.
|
||||||
}],
|
}],
|
||||||
|
|
||||||
optional: HashMap::from([
|
optional: HashMap::from([
|
||||||
("Delivered-To", &misc_token::Unstructured("quentin@example.com".into())),
|
(
|
||||||
|
"Delivered-To",
|
||||||
|
&misc_token::Unstructured("quentin@example.com".into())
|
||||||
|
),
|
||||||
("Unknown", &misc_token::Unstructured("unknown".into())),
|
("Unknown", &misc_token::Unstructured("unknown".into())),
|
||||||
]),
|
]),
|
||||||
|
|
||||||
|
@ -155,5 +174,5 @@ This is a reply to your hello.
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue