mailparsing: support unstructured header field parsing

This commit is contained in:
Wez Furlong
2023-08-13 22:10:13 -07:00
parent 6896ddca26
commit 74a70e5650
4 changed files with 80 additions and 3 deletions
+4
View File
@@ -103,6 +103,10 @@ impl<'a> Header<'a> {
Parser::parse_msg_id_header_list(self.get_raw_value())
}
pub fn as_unstructured(&self) -> Result<String> {
Parser::parse_unstructured_header(self.get_raw_value())
}
pub fn parse_headers<S: Into<SharedString<'a>>>(
header_block: S,
) -> Result<HeaderParseResult<'a>> {
+14
View File
@@ -137,4 +137,18 @@ impl<'a> HeaderMap<'a> {
Some(header) => Ok(Some(header.as_message_id_list()?)),
}
}
pub fn subject(&self) -> Result<Option<String>> {
match self.get_first("Subject") {
None => Ok(None),
Some(header) => Ok(Some(header.as_unstructured()?)),
}
}
pub fn comments(&self) -> Result<Option<String>> {
match self.get_first("Comments") {
None => Ok(None),
Some(header) => Ok(Some(header.as_unstructured()?)),
}
}
}
+3 -2
View File
@@ -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 }
+59 -1
View File
@@ -143,6 +143,52 @@ impl Parser {
)))
}
pub fn parse_unstructured_header(text: &str) -> Result<String> {
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<Rule>) -> Result<String> {
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<Rule>) -> Result<Address> {
for p in pairs {
match p.as_rule() {
@@ -690,7 +736,7 @@ Some(
"From: =?US-ASCII?Q?Keith_Moore?= <moore@cs.utk.edu>\n",
"To: =?ISO-8859-1*en-us?Q?Keld_J=F8rn_Simonsen?= <keld@dkuug.dk>\n",
"CC: =?ISO-8859-1?Q?Andr=E9?= Pirard <PIRARD@vm1.ulg.ac.be>\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",
)
"#
);
}