diff --git a/crates/mailparsing/src/header.rs b/crates/mailparsing/src/header.rs index f9f0a4a8..ef71bf75 100644 --- a/crates/mailparsing/src/header.rs +++ b/crates/mailparsing/src/header.rs @@ -103,6 +103,10 @@ impl<'a> Header<'a> { Parser::parse_msg_id_header_list(self.get_raw_value()) } + pub fn as_unstructured(&self) -> Result { + Parser::parse_unstructured_header(self.get_raw_value()) + } + pub fn parse_headers>>( header_block: S, ) -> Result> { diff --git a/crates/mailparsing/src/headermap.rs b/crates/mailparsing/src/headermap.rs index 8988cc92..959d88ba 100644 --- a/crates/mailparsing/src/headermap.rs +++ b/crates/mailparsing/src/headermap.rs @@ -137,4 +137,18 @@ impl<'a> HeaderMap<'a> { Some(header) => Ok(Some(header.as_message_id_list()?)), } } + + pub fn subject(&self) -> Result> { + match self.get_first("Subject") { + None => Ok(None), + Some(header) => Ok(Some(header.as_unstructured()?)), + } + } + + pub fn comments(&self) -> Result> { + match self.get_first("Comments") { + None => Ok(None), + Some(header) => Ok(Some(header.as_unstructured()?)), + } + } } diff --git a/crates/mailparsing/src/rfc5322.pest b/crates/mailparsing/src/rfc5322.pest index b7a80883..d9b9c686 100644 --- a/crates/mailparsing/src/rfc5322.pest +++ b/crates/mailparsing/src/rfc5322.pest @@ -66,8 +66,8 @@ unstructured = { // ((fws? ~ vchar)* ~ wsp*) | obs_unstruct } -obs_unstruct = { (( "\r"* ~ "\n"* ~ (obs_utext ~ "\r"* ~ "\n"*)+) | fws)+ } -obs_utext = { "\u{00}" | obs_no_ws_ctl | vchar } +obs_unstruct = { (( "\r"* ~ "\n"* ~ ((encoded_word | obs_utext)~ "\r"* ~ "\n"*)+) | fws)+ } +obs_utext = @{ "\u{00}" | obs_no_ws_ctl | vchar } obs_no_ws_ctl = { '\u{01}'..'\u{08}' | '\u{0b}'..'\u{0c}' | '\u{0d}'..'\u{1f}' | "\u{7f}" } obs_ctext = { obs_no_ws_ctl } @@ -114,3 +114,4 @@ parse_mailbox = _{ SOI ~ mailbox ~ EOI } parse_address_list = _{ SOI ~ address_list ~ EOI } parse_msg_id = _{ SOI ~ msg_id ~ EOI } parse_msg_id_list = _{ SOI ~ msg_id_list ~ EOI } +parse_unstructured = _{ SOI ~ unstructured ~ EOI } diff --git a/crates/mailparsing/src/rfc5322_parser.rs b/crates/mailparsing/src/rfc5322_parser.rs index 431263d7..df021c38 100644 --- a/crates/mailparsing/src/rfc5322_parser.rs +++ b/crates/mailparsing/src/rfc5322_parser.rs @@ -143,6 +143,52 @@ impl Parser { ))) } + pub fn parse_unstructured_header(text: &str) -> Result { + let mut pairs = Self::parse(Rule::parse_unstructured, text) + .map_err(|err| MailParsingError::HeaderParse(format!("{err:#}")))? + .next() + .unwrap() + .into_inner(); + + Self::parse_unstructured(pairs.next().unwrap().into_inner()) + } + + fn parse_unstructured(pairs: Pairs) -> Result { + let mut result = String::new(); + let mut fws = false; + + for p in pairs { + match p.as_rule() { + Rule::encoded_word => { + result += &Self::parse_encoded_word(p)?; + // Implicit fws at end of encoded word + fws = true; + } + Rule::fws | Rule::cfws => { + if !result.is_empty() && !fws { + result.push(' '); + } + fws = true; + } + Rule::obs_utext => { + result += p.as_str(); + fws = false; + } + rule => { + return Err(MailParsingError::HeaderParse(format!( + "Unexpected {rule:?} {p:#?} in parse_unstructured" + ))) + } + }; + } + + if fws { + result.pop(); + } + + Ok(result) + } + fn parse_address(pairs: Pairs) -> Result
{ for p in pairs { match p.as_rule() { @@ -690,7 +736,7 @@ Some( "From: =?US-ASCII?Q?Keith_Moore?= \n", "To: =?ISO-8859-1*en-us?Q?Keld_J=F8rn_Simonsen?= \n", "CC: =?ISO-8859-1?Q?Andr=E9?= Pirard \n", - "Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n", + "Subject: Hello =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=\n", " =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=\n", "\n\n" ); @@ -762,6 +808,18 @@ Some( ], ), ) +"# + ); + let list = match msg.headers().subject() { + Err(err) => panic!("Doh.\n{err:#}"), + Ok(list) => list, + }; + k9::snapshot!( + list, + r#" +Some( + "Hello If you can read this you understand the example", +) "# ); }