diff --git a/src/error.rs b/src/error.rs index 7374397..e42c4cc 100644 --- a/src/error.rs +++ b/src/error.rs @@ -14,4 +14,5 @@ pub enum IMFError<'a> { MessageIDList(nom::Err>), Unstructured(nom::Err>), PhraseList(nom::Err>), + ReceivedLog(nom::Err>), } diff --git a/src/fragments/address.rs b/src/fragments/address.rs index 75d2fca..16515b8 100644 --- a/src/fragments/address.rs +++ b/src/fragments/address.rs @@ -14,40 +14,40 @@ use crate::fragments::misc_token::phrase; use crate::fragments::whitespace::{cfws}; use crate::error::IMFError; -impl<'a> TryFrom> for MailboxRef { +impl<'a> TryFrom<&'a lazy::Mailbox<'a>> for MailboxRef { type Error = IMFError<'a>; - fn try_from(mx: lazy::Mailbox<'a>) -> Result { + fn try_from(mx: &'a lazy::Mailbox<'a>) -> Result { mailbox(mx.0) .map(|(_, m)| m) .map_err(|e| IMFError::Mailbox(e)) } } -impl<'a> TryFrom> for MailboxList { +impl<'a> TryFrom<&'a lazy::MailboxList<'a>> for MailboxList { type Error = IMFError<'a>; - fn try_from(ml: lazy::MailboxList<'a>) -> Result { + fn try_from(ml: &'a lazy::MailboxList<'a>) -> Result { mailbox_list(ml.0) .map(|(_, m)| m) .map_err(|e| IMFError::MailboxList(e)) } } -impl<'a> TryFrom> for AddressList { +impl<'a> TryFrom<&'a lazy::AddressList<'a>> for AddressList { type Error = IMFError<'a>; - fn try_from(al: lazy::AddressList<'a>) -> Result { + fn try_from(al: &'a lazy::AddressList<'a>) -> Result { address_list(al.0) .map(|(_, a)| a) .map_err(|e| IMFError::AddressList(e)) } } -impl<'a> TryFrom> for AddressList { +impl<'a> TryFrom<&'a lazy::NullableAddressList<'a>> for AddressList { type Error = IMFError<'a>; - fn try_from(nal: lazy::NullableAddressList<'a>) -> Result { + fn try_from(nal: &'a lazy::NullableAddressList<'a>) -> Result { opt(alt((address_list, address_list_cfws)))(nal.0) .map(|(_, a)| a.unwrap_or(vec![])) .map_err(|e| IMFError::NullableAddressList(e)) diff --git a/src/fragments/datetime.rs b/src/fragments/datetime.rs index 7af6589..34d68e4 100644 --- a/src/fragments/datetime.rs +++ b/src/fragments/datetime.rs @@ -18,10 +18,10 @@ use crate::error::IMFError; const MIN: i32 = 60; const HOUR: i32 = 60 * MIN; -impl<'a> TryFrom> for DateTime { +impl<'a> TryFrom<&'a lazy::DateTime<'a>> for DateTime { type Error = IMFError<'a>; - fn try_from(value: lazy::DateTime<'a>) -> Result { + fn try_from(value: &'a lazy::DateTime<'a>) -> Result { match section(value.0) { Ok((_, Some(dt))) => Ok(dt), Err(e) => Err(IMFError::DateTimeParse(e)), diff --git a/src/fragments/eager.rs b/src/fragments/eager.rs new file mode 100644 index 0000000..def4ab9 --- /dev/null +++ b/src/fragments/eager.rs @@ -0,0 +1,72 @@ +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; + +#[derive(Debug, PartialEq)] +pub enum Field<'a> { + // 3.6.1. The Origination Date Field + Date(DateTime), + + // 3.6.2. Originator Fields + From(MailboxList), + Sender(MailboxRef), + ReplyTo(AddressList), + + // 3.6.3. Destination Address Fields + To(AddressList), + Cc(AddressList), + Bcc(AddressList), + + // 3.6.4. Identification Fields + MessageID(MessageId<'a>), + InReplyTo(MessageIdList<'a>), + References(MessageIdList<'a>), + + // 3.6.5. Informational Fields + Subject(Unstructured), + Comments(Unstructured), + Keywords(PhraseList), + + // 3.6.6 Resent Fields (not implemented) + // 3.6.7 Trace Fields + Received(ReceivedLog<'a>), + ReturnPath(MailboxRef), + + // 3.6.8. Optional Fields + Optional(&'a str, Unstructured), + + // None + Rescue(&'a str), +} +use Field::*; + +impl<'a> TryFrom<&'a Lazy<'a>> for Field<'a> { + type Error = IMFError<'a>; + + fn try_from(l: &'a Lazy<'a>) -> Result { + match l { + Lazy::Date(v) => v.try_into().map(|v| Date(v)), + Lazy::From(v) => v.try_into().map(|v| From(v)), + Lazy::Sender(v) => v.try_into().map(|v| Sender(v)), + Lazy::ReplyTo(v) => v.try_into().map(|v| ReplyTo(v)), + Lazy::To(v) => v.try_into().map(|v| To(v)), + Lazy::Cc(v) => v.try_into().map(|v| Cc(v)), + Lazy::Bcc(v) => v.try_into().map(|v| Bcc(v)), + Lazy::MessageID(v) => v.try_into().map(|v| MessageID(v)), + Lazy::InReplyTo(v) => v.try_into().map(|v| InReplyTo(v)), + Lazy::References(v) => v.try_into().map(|v| References(v)), + Lazy::Subject(v) => v.try_into().map(|v| Subject(v)), + Lazy::Comments(v) => v.try_into().map(|v| Comments(v)), + Lazy::Keywords(v) => v.try_into().map(|v| Keywords(v)), + Lazy::Received(v) => v.try_into().map(|v| Received(v)), + Lazy::ReturnPath(v) => v.try_into().map(|v| ReturnPath(v)), + Lazy::Optional(k, v) => v.try_into().map(|v| Optional(k, v)), + Lazy::Rescue(v) => Ok(Rescue(*v)), + } + } +} diff --git a/src/fragments/identification.rs b/src/fragments/identification.rs index f8d51c6..42fa843 100644 --- a/src/fragments/identification.rs +++ b/src/fragments/identification.rs @@ -15,20 +15,20 @@ use crate::fragments::mailbox::is_dtext; use crate::fragments::model::{MessageId, MessageIdList}; use crate::error::IMFError; -impl<'a> TryFrom> for MessageId<'a> { +impl<'a> TryFrom<&'a lazy::Identifier<'a>> for MessageId<'a> { type Error = IMFError<'a>; - fn try_from(id: lazy::Identifier<'a>) -> Result { + fn try_from(id: &'a lazy::Identifier<'a>) -> Result { msg_id(id.0) .map(|(_, i)| i) .map_err(|e| IMFError::MessageID(e)) } } -impl<'a> TryFrom> for MessageIdList<'a> { +impl<'a> TryFrom<&'a lazy::IdentifierList<'a>> for MessageIdList<'a> { type Error = IMFError<'a>; - fn try_from(id: lazy::IdentifierList<'a>) -> Result { + fn try_from(id: &'a lazy::IdentifierList<'a>) -> Result { many1(msg_id)(id.0) .map(|(_, i)| i) .map_err(|e| IMFError::MessageIDList(e)) diff --git a/src/fragments/lazy.rs b/src/fragments/lazy.rs index 89e574c..a5a055c 100644 --- a/src/fragments/lazy.rs +++ b/src/fragments/lazy.rs @@ -71,7 +71,7 @@ pub enum Field<'a> { ReturnPath(Mailbox<'a>), // 3.6.8. Optional Fields - Optional(&'a str, &'a str), + Optional(&'a str, Unstructured<'a>), // None Rescue(&'a str), @@ -126,6 +126,6 @@ fn correct_field(input: &str) -> IResult<&str, Field> { "return-path" => ReturnPath(Mailbox(rest)), "received" => Received(ReceivedLog(rest)), - _ => Optional(name, rest), + _ => Optional(name, Unstructured(rest)), })) } diff --git a/src/fragments/misc_token.rs b/src/fragments/misc_token.rs index 9559861..da49777 100644 --- a/src/fragments/misc_token.rs +++ b/src/fragments/misc_token.rs @@ -15,25 +15,28 @@ use crate::fragments::whitespace::{fws, is_obs_no_ws_ctl}; use crate::fragments::words::{atom, is_vchar}; use crate::error::IMFError; -type Unstructured = String; -type PhraseList = Vec; +#[derive(Debug, PartialEq)] +pub struct Unstructured(pub String); -impl<'a> TryFrom> for Unstructured { +#[derive(Debug, PartialEq)] +pub struct PhraseList(pub Vec); + +impl<'a> TryFrom<&'a lazy::Unstructured<'a>> for Unstructured { type Error = IMFError<'a>; - fn try_from(input: lazy::Unstructured<'a>) -> Result { + fn try_from(input: &'a lazy::Unstructured<'a>) -> Result { unstructured(input.0) - .map(|(_, v)| v) + .map(|(_, v)| Unstructured(v)) .map_err(|e| IMFError::Unstructured(e)) } } -impl<'a> TryFrom> for PhraseList { +impl<'a> TryFrom<&'a lazy::PhraseList<'a>> for PhraseList { type Error = IMFError<'a>; - fn try_from(p: lazy::PhraseList<'a>) -> Result { + fn try_from(p: &'a lazy::PhraseList<'a>) -> Result { separated_list1(tag(","), phrase)(p.0) - .map(|(_, q)| q) + .map(|(_, q)| PhraseList(q)) .map_err(|e| IMFError::PhraseList(e)) } } diff --git a/src/fragments/mod.rs b/src/fragments/mod.rs index f92a049..f4a0ac6 100644 --- a/src/fragments/mod.rs +++ b/src/fragments/mod.rs @@ -14,6 +14,7 @@ mod identification; mod trace; mod datetime; pub mod lazy; +pub mod eager; // Header blocks pub mod header; diff --git a/src/fragments/trace.rs b/src/fragments/trace.rs index 596577a..d15dfd9 100644 --- a/src/fragments/trace.rs +++ b/src/fragments/trace.rs @@ -8,7 +8,21 @@ use nom::{ multi::many0, sequence::{delimited, pair, tuple}, }; -use crate::fragments::{datetime, mailbox, model, misc_token, whitespace}; +use crate::fragments::{datetime, mailbox, model, misc_token, whitespace, lazy}; +use crate::error::IMFError; + +#[derive(Debug, PartialEq)] +pub struct ReceivedLog<'a>(pub &'a str); + +impl<'a> TryFrom<&'a lazy::ReceivedLog<'a>> for ReceivedLog<'a> { + type Error = IMFError<'a>; + + fn try_from(input: &'a lazy::ReceivedLog<'a>) -> Result { + received_body(input.0) + .map_err(|e| IMFError::ReceivedLog(e)) + .map(|(_, v)| ReceivedLog(v)) + } +} pub fn received_body(input: &str) -> IResult<&str, &str> { map( diff --git a/src/multipass/field_eager.rs b/src/multipass/field_eager.rs new file mode 100644 index 0000000..5b62ea1 --- /dev/null +++ b/src/multipass/field_eager.rs @@ -0,0 +1,63 @@ +use crate::fragments::eager; +use crate::multipass::field_lazy; + +#[derive(Debug, PartialEq)] +pub struct Parsed<'a> { + pub fields: Vec>, + pub body: &'a [u8], +} + +impl<'a> From <&'a field_lazy::Parsed<'a>> for Parsed<'a> { + fn from(p: &'a field_lazy::Parsed<'a>) -> Self { + Parsed { + fields: p.fields.iter().filter_map(|entry| entry.try_into().ok()).collect(), + body: p.body, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::fragments::lazy; + use crate::fragments::model; + use chrono::{FixedOffset, TimeZone}; + + #[test] + fn test_field_name() { + assert_eq!(Parsed::from(&field_lazy::Parsed { + fields: vec![ + 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")), + ], + body: b"Hello world!", + }), + Parsed { + fields: vec![ + eager::Field::From(vec![ + model::MailboxRef { + name: None, + addrspec: model::AddrSpec { + local_part: "hello".into(), + domain: "world.com".into() + } + }, + model::MailboxRef { + name: None, + addrspec: model::AddrSpec { + local_part: "alice".into(), + domain: "wonderlands.com".into() + } + }, + ]), + eager::Field::Date( + FixedOffset::east_opt(0) + .unwrap() + .with_ymd_and_hms(1997, 03, 12, 7, 33, 25) + .unwrap() + ), + ], + body: b"Hello world!", + }); + } +} diff --git a/src/multipass/parse_field_lazy.rs b/src/multipass/field_lazy.rs similarity index 100% rename from src/multipass/parse_field_lazy.rs rename to src/multipass/field_lazy.rs diff --git a/src/multipass/mod.rs b/src/multipass/mod.rs index 1e2a203..78acf35 100644 --- a/src/multipass/mod.rs +++ b/src/multipass/mod.rs @@ -1,4 +1,5 @@ pub mod segment; pub mod guess_charset; pub mod extract_fields; -pub mod parse_field_lazy; +pub mod field_lazy; +pub mod field_eager;