diff --git a/src/datetime.rs b/src/datetime.rs index 199f7cd..b1278a9 100644 --- a/src/datetime.rs +++ b/src/datetime.rs @@ -1,13 +1,10 @@ -use chrono::DateTime; +use chrono::{DateTime, FixedOffset}; use nom::IResult; -use crate::{model,misc_token}; +use crate::misc_token; -pub fn section(input: &str) -> IResult<&str, model::HeaderDate> { +pub fn section(input: &str) -> IResult<&str, DateTime> { // @FIXME want to extract datetime our way in the future // to better handle obsolete/bad cases instead of returning raw text. let (input, raw_date) = misc_token::unstructured(input)?; - match DateTime::parse_from_rfc2822(&raw_date) { - Ok(chronodt) => Ok((input, model::HeaderDate::Parsed(chronodt))), - Err(e) => Ok((input, model::HeaderDate::Unknown(raw_date, e))), - } + Ok((input, DateTime::parse_from_rfc2822(&raw_date).unwrap())) } diff --git a/src/header.rs b/src/header.rs index 5598222..184cbbd 100644 --- a/src/header.rs +++ b/src/header.rs @@ -1,5 +1,7 @@ +use chrono::{DateTime, FixedOffset}; use nom::{ IResult, + Parser, branch::alt, bytes::complete::{is_not, take_while1, take_while, tag, tag_no_case}, character::complete::space0, @@ -11,7 +13,7 @@ use nom::{ use crate::whitespace::{fws, perm_crlf}; use crate::words::vchar_seq; use crate::misc_token::{phrase, unstructured}; -use crate::model::{HeaderSection, HeaderDate, MailboxRef, AddressRef}; +use crate::model::{HeaderSection, MailboxRef, AddressRef, Field, FieldBody}; use crate::mailbox::mailbox; use crate::address::{mailbox_list, address_list, address_list_cfws}; use crate::identification::msg_id; @@ -24,7 +26,7 @@ use crate::{datetime, trace, model}; /// See: https://www.rfc-editor.org/rfc/rfc5322.html#section-2.2 pub fn section(input: &str) -> IResult<&str, HeaderSection> { let (input, headers) = fold_many0( - alt((header_field, unknown_field, rescue)), + alt((known_field, unknown_field, rescue_field)), HeaderSection::default, |mut section, head| { match head { @@ -32,87 +34,91 @@ pub fn section(input: &str) -> IResult<&str, HeaderSection> { // it may result in missing data or silently overriden data. // 3.6.1. The Origination Date Field - HeaderField::Date(d) => { - // | orig-date | 1 | 1 | | - section.date = d; + // | orig-date | 1 | 1 | | + Field::Date(FieldBody::Correct(d)) => { + section.date = Some(d); } // 3.6.2. Originator Fields - HeaderField::From(v) => { + Field::From(FieldBody::Correct(v)) => { // | from | 1 | 1 | See sender and 3.6.2 | section.from = v; } - HeaderField::Sender(mbx) => { + Field::Sender(FieldBody::Correct(mbx)) => { // | sender | 0* | 1 | MUST occur with multi-address from - see 3.6.2 | section.sender = Some(mbx); } - HeaderField::ReplyTo(addr_list) => { + Field::ReplyTo(FieldBody::Correct(addr_list)) => { // | reply-to | 0 | 1 | | section.reply_to = addr_list; } // 3.6.3. Destination Address Fields - HeaderField::To(addr_list) => { + Field::To(FieldBody::Correct(addr_list)) => { // | to | 0 | 1 | | section.to = addr_list; } - HeaderField::Cc(addr_list) => { + Field::Cc(FieldBody::Correct(addr_list)) => { // | cc | 0 | 1 | | section.cc = addr_list; } - HeaderField::Bcc(addr_list) => { + Field::Bcc(FieldBody::Correct(addr_list)) => { // | bcc | 0 | 1 | | section.bcc = addr_list; } // 3.6.4. Identification Fields - HeaderField::MessageID(msg_id) => { + Field::MessageID(FieldBody::Correct(msg_id)) => { // | message-id | 0* | 1 | SHOULD be present - see 3.6.4 | section.msg_id = Some(msg_id); } - HeaderField::InReplyTo(id_list) => { + Field::InReplyTo(FieldBody::Correct(id_list)) => { // | in-reply-to | 0* | 1 | SHOULD occur in some replies - see 3.6.4 | section.in_reply_to = id_list; } - HeaderField::References(id_list) => { + Field::References(FieldBody::Correct(id_list)) => { // | in-reply-to | 0* | 1 | SHOULD occur in some replies - see 3.6.4 | section.references = id_list; } // 3.6.5. Informational Fields - HeaderField::Subject(title) => { + Field::Subject(FieldBody::Correct(title)) => { // | subject | 0 | 1 | | section.subject = Some(title); } - HeaderField::Comments(coms) => { + Field::Comments(FieldBody::Correct(coms)) => { // | comments | 0 | unlimited | | section.comments.push(coms); } - HeaderField::Keywords(mut kws) => { + Field::Keywords(FieldBody::Correct(mut kws)) => { // | keywords | 0 | unlimited | | section.keywords.append(&mut kws); } // 3.6.6 Resent Fields are not implemented // 3.6.7 Trace Fields - HeaderField::ReturnPath(maybe_mbx) => { + Field::ReturnPath(FieldBody::Correct(maybe_mbx)) => { if let Some(mbx) = maybe_mbx { section.return_path.push(mbx); } } - HeaderField::Received(log) => { + Field::Received(FieldBody::Correct(log)) => { section.received.push(log); } // 3.6.8. Optional Fields - HeaderField::Optional(name, body) => { + Field::Optional(name, body) => { section.optional.insert(name, body); } // Rescue - HeaderField::Rescue(x) => { + Field::Rescue(x) => { section.unparsed.push(x); } + + bad_field => { + section.bad_fields.push(bad_field); + } }; section } @@ -122,48 +128,13 @@ pub fn section(input: &str) -> IResult<&str, HeaderSection> { Ok((input, headers)) } -#[derive(Debug, PartialEq)] -pub enum HeaderField<'a> { - // 3.6.1. The Origination Date Field - Date(HeaderDate), - // 3.6.2. Originator Fields - From(Vec), - Sender(MailboxRef), - ReplyTo(Vec), - - // 3.6.3. Destination Address Fields - To(Vec), - Cc(Vec), - Bcc(Vec), - - // 3.6.4. Identification Fields - MessageID(model::MessageId<'a>), - InReplyTo(Vec>), - References(Vec>), - - // 3.6.5. Informational Fields - Subject(String), - Comments(String), - Keywords(Vec), - - // 3.6.6 Resent Fields (not implemented) - // 3.6.7 Trace Fields - Received(&'a str), - ReturnPath(Option), - - // 3.6.8. Optional Fields - Optional(&'a str, String), - - // None - Rescue(&'a str), -} /// Parse one known header field /// /// RFC5322 optional-field seems to be a generalization of the field terminology. /// We use it to parse all header names: -pub fn header_field(input: &str) -> IResult<&str, HeaderField> { +pub fn known_field(input: &str) -> IResult<&str, Field> { terminated( alt(( // 3.6.1. The Origination Date Field @@ -183,96 +154,117 @@ pub fn header_field(input: &str) -> IResult<&str, HeaderField> { )(input) } -// 3.6.1. The Origination Date Field -fn date(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Date"), datetime::section)(input)?; - Ok((input, HeaderField::Date(body))) -} - -// 3.6.2. Originator Fields -fn from(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("From"), mailbox_list)(input)?; - Ok((input, HeaderField::From(body))) -} -fn sender(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Sender"), mailbox)(input)?; - Ok((input, HeaderField::Sender(body))) -} -fn reply_to(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Reply-To"), address_list)(input)?; - Ok((input, HeaderField::ReplyTo(body))) -} - -// 3.6.3. Destination Address Fields -fn to(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("To"), address_list)(input)?; - Ok((input, HeaderField::To(body))) -} -fn cc(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Cc"), address_list)(input)?; - Ok((input, HeaderField::Cc(body))) -} -fn bcc(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded( - field_name_tag("Bcc"), - opt(alt((address_list, address_list_cfws))), - )(input)?; - - Ok((input, HeaderField::Bcc(body.unwrap_or(vec![])))) -} - -// 3.6.4. Identification Fields -fn msg_id_field(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Message-ID"), msg_id)(input)?; - Ok((input, HeaderField::MessageID(body))) -} -fn in_reply_to(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("In-Reply-To"), many1(msg_id))(input)?; - Ok((input, HeaderField::InReplyTo(body))) -} -fn references(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("References"), many1(msg_id))(input)?; - Ok((input, HeaderField::References(body))) -} - -// 3.6.5. Informational Fields -fn subject(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Subject"), unstructured)(input)?; - Ok((input, HeaderField::Subject(body))) -} -fn comments(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded(field_name_tag("Comments"), unstructured)(input)?; - Ok((input, HeaderField::Comments(body))) -} -fn keywords(input: &str) -> IResult<&str, HeaderField> { - let (input, body) = preceded( - field_name_tag("Keywords"), - separated_list1(tag(","), phrase), - )(input)?; - Ok((input, HeaderField::Keywords(body))) -} - +/// A high-level function to match more easily a field name fn field_name_tag(field_name: &str) -> impl FnMut(&str) -> IResult<&str, &str> + '_ { move |input: &str| { recognize(tuple((tag_no_case(field_name), space0, tag(":"), space0)))(input) } } +// 3.6.1. The Origination Date Field +fn date(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Date"), alt(( + map(datetime::section, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Date(b))(input) +} + +// 3.6.2. Originator Fields +fn from(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("From"), alt(( + map(mailbox_list, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::From(b))(input) +} +fn sender(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Sender"), alt(( + map(mailbox, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Sender(b))(input) +} +fn reply_to(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Reply-To"), alt(( + map(address_list, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::ReplyTo(b))(input) +} + +// 3.6.3. Destination Address Fields +fn to(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("To"), alt(( + map(address_list, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::To(b))(input) +} +fn cc(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Cc"), alt(( + map(address_list, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Cc(b))(input) +} +fn bcc(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Bcc"), alt(( + map(opt(alt((address_list, address_list_cfws))), |dt| FieldBody::Correct(dt.unwrap_or(vec![]))), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Bcc(b))(input) +} + +// 3.6.4. Identification Fields +fn msg_id_field(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Message-ID"), alt(( + map(msg_id, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::MessageID(b))(input) +} +fn in_reply_to(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("In-Reply-To"), alt(( + map(many1(msg_id), |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::InReplyTo(b))(input) +} +fn references(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("References"), alt(( + map(many1(msg_id), |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::References(b))(input) +} + +// 3.6.5. Informational Fields +fn subject(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Subject"), alt(( + map(unstructured, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Subject(b))(input) +} +fn comments(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Comments"), alt(( + map(unstructured, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Comments(b))(input) +} +fn keywords(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Keywords"), alt(( + map(separated_list1(tag(","), phrase), |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Keywords(b))(input) +} + + // 3.6.6 Resent fields // Not implemented // 3.6.7 Trace fields -fn return_path(input: &str) -> IResult<&str, HeaderField> { - map( - preceded(pair(tag("Return-Path:"), space0), trace::return_path_body), - |body| HeaderField::ReturnPath(body), - )(input) +fn return_path(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Return-Path"), alt(( + map(trace::return_path_body, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::ReturnPath(b))(input) } -fn received(input: &str) -> IResult<&str, HeaderField> { - map( - preceded(pair(tag("Received:"), space0), trace::received_body), - |body| HeaderField::Received(body), - )(input) +fn received(input: &str) -> IResult<&str, Field> { + map(preceded(field_name_tag("Received"), alt(( + map(trace::received_body, |dt| FieldBody::Correct(dt)), + map(rescue, |r| FieldBody::Failed(r))))), + |b| Field::Received(b))(input) } /// Optional field @@ -284,12 +276,12 @@ fn received(input: &str) -> IResult<&str, HeaderField> { /// %d59-126 ; characters not including /// ; ":". /// ``` -fn unknown_field(input: &str) -> IResult<&str, HeaderField> { +fn unknown_field(input: &str) -> IResult<&str, Field> { // Extract field name let (input, field_name) = field_name(input)?; let (input, body) = unstructured(input)?; let (input, _) = perm_crlf(input)?; - Ok((input, HeaderField::Optional(field_name, body))) + Ok((input, Field::Optional(field_name, body))) } fn field_name(input: &str) -> IResult<&str, &str> { terminated( @@ -306,68 +298,74 @@ fn field_name(input: &str) -> IResult<&str, &str> { /// /// ```abnf /// rescue = *(*any FWS) *any CRLF -fn rescue(input: &str) -> IResult<&str, HeaderField> { - map(recognize(pair( +fn rescue(input: &str) -> IResult<&str, &str> { + recognize(pair( many0(pair(is_not("\r\n"), fws)), - pair(is_not("\r\n"), perm_crlf), - )), |x| HeaderField::Rescue(x))(input) + is_not("\r\n"), + ))(input) } +fn rescue_field(input: &str) -> IResult<&str, Field> { + map(terminated(rescue, perm_crlf), |r| Field::Rescue(r))(input) +} + + + #[cfg(test)] mod tests { use super::*; use crate::model::{GroupRef, AddrSpec}; // 3.6.1. The Origination Date Field - #[test] +/* #[test] fn test_datetime() { let datefield = "Date: Thu,\r\n 13\r\n Feb\r\n 1969\r\n 23:32\r\n -0330 (Newfoundland Time)\r\n"; - let (input, v) = header_field(datefield).unwrap(); + let (input, v) = known_field(datefield).unwrap(); assert_eq!(input, ""); match v { - HeaderField::Date(HeaderDate::Parsed(_)) => (), + Field::Date(HeaderDate::Parsed(_)) => (), _ => panic!("Date has not been parsed"), }; - } + }*/ // 3.6.2. Originator Fields #[test] fn test_from() { assert_eq!( - header_field("From: \"Joe Q. Public\" \r\n"), - Ok(("", HeaderField::From(vec![MailboxRef { + known_field("From: \"Joe Q. Public\" \r\n"), + Ok(("", Field::From(FieldBody::Correct(vec![MailboxRef { name: Some("Joe Q. Public".into()), addrspec: AddrSpec { local_part: "john.q.public".into(), domain: "example.com".into(), } - }]))), + }])))), ); } #[test] fn test_sender() { assert_eq!( - header_field("Sender: Michael Jones \r\n"), - Ok(("", HeaderField::Sender(MailboxRef { + known_field("Sender: Michael Jones \r\n"), + Ok(("", Field::Sender(FieldBody::Correct(MailboxRef { name: Some("Michael Jones".into()), addrspec: AddrSpec { local_part: "mjones".into(), domain: "machine.example".into(), }, - }))), + })))), ); } #[test] fn test_reply_to() { assert_eq!( - header_field("Reply-To: \"Mary Smith: Personal Account\" \r\n"), - Ok(("", HeaderField::ReplyTo(vec![AddressRef::Single(MailboxRef { + known_field("Reply-To: \"Mary Smith: Personal Account\" \r\n"), + Ok(("", Field::ReplyTo(FieldBody::Correct(vec![AddressRef::Single(MailboxRef { name: Some("Mary Smith: Personal Account".into()), addrspec: AddrSpec { local_part: "smith".into(), domain: "home.example".into(), }, - })]))) + })])))) ); } @@ -375,8 +373,8 @@ mod tests { #[test] fn test_to() { assert_eq!( - header_field("To: A Group:Ed Jones ,joe@where.test,John ;\r\n"), - Ok(("", HeaderField::To(vec![AddressRef::Many(GroupRef { + known_field("To: A Group:Ed Jones ,joe@where.test,John ;\r\n"), + Ok(("", Field::To(FieldBody::Correct(vec![AddressRef::Many(GroupRef { name: "A Group".into(), participants: vec![ MailboxRef { @@ -392,28 +390,28 @@ mod tests { addrspec: AddrSpec { local_part: "jdoe".into(), domain: "one.test".into() }, }, ] - })]))) + })])))) ); } #[test] fn test_cc() { assert_eq!( - header_field("Cc: Undisclosed recipients:;\r\n"), - Ok(("", HeaderField::Cc(vec![AddressRef::Many(GroupRef { + known_field("Cc: Undisclosed recipients:;\r\n"), + Ok(("", Field::Cc(FieldBody::Correct(vec![AddressRef::Many(GroupRef { name: "Undisclosed recipients".into(), participants: vec![], - })]))) + })])))) ); } #[test] fn test_bcc() { assert_eq!( - header_field("Bcc: (empty)\r\n"), - Ok(("", HeaderField::Bcc(vec![]))) + known_field("Bcc: (empty)\r\n"), + Ok(("", Field::Bcc(FieldBody::Correct(vec![])))) ); assert_eq!( - header_field("Bcc: \r\n"), - Ok(("", HeaderField::Bcc(vec![]))) + known_field("Bcc: \r\n"), + Ok(("", Field::Bcc(FieldBody::Correct(vec![])))) ); } @@ -422,28 +420,28 @@ mod tests { #[test] fn test_message_id() { assert_eq!( - header_field("Message-ID: <310@[127.0.0.1]>\r\n"), - Ok(("", HeaderField::MessageID(model::MessageId { left: "310", right: "127.0.0.1" }))) + known_field("Message-ID: <310@[127.0.0.1]>\r\n"), + Ok(("", Field::MessageID(FieldBody::Correct(model::MessageId { left: "310", right: "127.0.0.1" })))) ); } #[test] fn test_in_reply_to() { assert_eq!( - header_field("In-Reply-To: \r\n"), - Ok(("", HeaderField::InReplyTo(vec![ + known_field("In-Reply-To: \r\n"), + Ok(("", Field::InReplyTo(FieldBody::Correct(vec![ model::MessageId { left: "a", right: "b" }, model::MessageId { left: "c", right: "example.com" }, - ]))) + ])))) ); } #[test] fn test_references() { assert_eq!( - header_field("References: <1234@local.machine.example> <3456@example.net>\r\n"), - Ok(("", HeaderField::References(vec![ + known_field("References: <1234@local.machine.example> <3456@example.net>\r\n"), + Ok(("", Field::References(FieldBody::Correct(vec![ model::MessageId { left: "1234", right: "local.machine.example" }, model::MessageId { left: "3456", right: "example.net" }, - ]))) + ])))) ); } @@ -451,36 +449,54 @@ mod tests { #[test] fn test_subject() { assert_eq!( - header_field("Subject: Aérogramme\r\n"), - Ok(("", HeaderField::Subject("Aérogramme".into()))) + known_field("Subject: Aérogramme\r\n"), + Ok(("", Field::Subject(FieldBody::Correct("Aérogramme".into())))) ); } #[test] fn test_comments() { assert_eq!( - header_field("Comments: 😛 easter egg!\r\n"), - Ok(("", HeaderField::Comments("😛 easter egg!".into()))) + known_field("Comments: 😛 easter egg!\r\n"), + Ok(("", Field::Comments(FieldBody::Correct("😛 easter egg!".into())))) ); } #[test] fn test_keywords() { assert_eq!( - header_field("Keywords: fantasque, farfelu, fanfreluche\r\n"), - Ok(("", HeaderField::Keywords(vec!["fantasque".into(), "farfelu".into(), "fanfreluche".into()]))) + known_field("Keywords: fantasque, farfelu, fanfreluche\r\n"), + Ok(("", Field::Keywords(FieldBody::Correct(vec!["fantasque".into(), "farfelu".into(), "fanfreluche".into()])))) ); } // Test invalid field name #[test] fn test_invalid_field_name() { - assert!(header_field("Unknown: unknown\r\n").is_err()); + assert!(known_field("Unknown: unknown\r\n").is_err()); } #[test] - fn test_rescue() { + fn test_rescue_field() { assert_eq!( - rescue("Héron: élan\r\n\tnoël: test\r\n"), - Ok(("", HeaderField::Rescue("Héron: élan\r\n\tnoël: test\r\n"))), + rescue_field("Héron: élan\r\n\tnoël: test\r\nFrom: ..."), + Ok(("From: ...", Field::Rescue("Héron: élan\r\n\tnoël: test"))), + ); + } + + #[test] + fn test_wrong_fields() { + let fullmail = r#"Return-Path: xoxo +From: !!!! + +Hello world"#; + assert_eq!( + section(fullmail), + Ok(("Hello world", HeaderSection { + bad_fields: vec![ + Field::ReturnPath(FieldBody::Failed("xoxo")), + Field::From(FieldBody::Failed("!!!!")), + ], + ..Default::default() + })) ); } @@ -524,7 +540,7 @@ This is a reply to your hello. assert_eq!( section(fullmail), Ok(("This is a reply to your hello.\n", HeaderSection { - date: model::HeaderDate::Parsed(FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2023, 06, 13, 10, 01, 10).unwrap()), + date: Some(FixedOffset::east_opt(2 * 3600).unwrap().with_ymd_and_hms(2023, 06, 13, 10, 01, 10).unwrap()), from: vec![MailboxRef { name: Some("Mary Smith".into()), @@ -608,12 +624,12 @@ This is a reply to your hello. ]), unparsed: vec![ - "Héron: Raté\n Raté raté\n", - "Not a real header but should still recover\n", + "Héron: Raté\n Raté raté", + "Not a real header but should still recover", ], + + bad_fields: vec![], })) ); } } - - diff --git a/src/model.rs b/src/model.rs index 6b4b810..2371a54 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,14 +1,6 @@ use std::collections::HashMap; use chrono::{DateTime,FixedOffset,ParseError}; -#[derive(Debug, PartialEq, Default)] -pub enum HeaderDate { - Parsed(DateTime), - Unknown(String, ParseError), - #[default] - None, -} - #[derive(Debug, PartialEq)] pub struct AddrSpec { pub local_part: String, @@ -63,6 +55,49 @@ pub struct MessageId<'a> { pub right: &'a str, } +#[derive(Debug, PartialEq)] +pub enum FieldBody<'a, T> { + Correct(T), + Failed(&'a str), +} + +#[derive(Debug, PartialEq)] +pub enum Field<'a> { + // 3.6.1. The Origination Date Field + Date(FieldBody<'a, DateTime>), + + // 3.6.2. Originator Fields + From(FieldBody<'a, Vec>), + Sender(FieldBody<'a, MailboxRef>), + ReplyTo(FieldBody<'a, Vec>), + + // 3.6.3. Destination Address Fields + To(FieldBody<'a, Vec>), + Cc(FieldBody<'a, Vec>), + Bcc(FieldBody<'a, Vec>), + + // 3.6.4. Identification Fields + MessageID(FieldBody<'a, MessageId<'a>>), + InReplyTo(FieldBody<'a, Vec>>), + References(FieldBody<'a, Vec>>), + + // 3.6.5. Informational Fields + Subject(FieldBody<'a, String>), + Comments(FieldBody<'a, String>), + Keywords(FieldBody<'a, Vec>), + + // 3.6.6 Resent Fields (not implemented) + // 3.6.7 Trace Fields + Received(FieldBody<'a, &'a str>), + ReturnPath(FieldBody<'a, Option>), + + // 3.6.8. Optional Fields + Optional(&'a str, String), + + // None + Rescue(&'a str), +} + /// Permissive Header Section /// /// This is a structure intended for parsing/decoding, @@ -72,7 +107,7 @@ pub struct MessageId<'a> { #[derive(Debug, PartialEq, Default)] pub struct HeaderSection<'a> { // 3.6.1. The Origination Date Field - pub date: HeaderDate, + pub date: Option>, // 3.6.2. Originator Fields pub from: Vec, @@ -101,5 +136,8 @@ pub struct HeaderSection<'a> { // 3.6.8. Optional Fields pub optional: HashMap<&'a str, String>, + + // Recovery + pub bad_fields: Vec>, pub unparsed: Vec<&'a str>, } diff --git a/src/parse.rs b/src/parse.rs index 115bc8b..ce2d455 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -1,4 +1,4 @@ -use imf_codec::header; +//use imf_codec::header; fn main() { let hdr = r#"Return-Path: @@ -33,5 +33,5 @@ References: <1234@local.machine.example> This is a reply to your hello. "#; - println!("{:?}", header::section(hdr)); + //println!("{:?}", header::section(hdr)); }