From 5cdcf1af295918bf9370c18b135baec2b0b24499 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 2 Mar 2026 19:38:39 +0100 Subject: [PATCH 1/4] test: addresses without terminating \n should salvage as much text as possible --- resources/address.json | 11 +++++++++++ src/parsers/fields/address.rs | 16 ++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/resources/address.json b/resources/address.json index 75bef972..057f7599 100644 --- a/resources/address.json +++ b/resources/address.json @@ -777,5 +777,16 @@ } ] } + }, + { + "header": "John Doe", + "expected": { + "List": [ + { + "name": "John Doe", + "address": null + } + ] + } } ] diff --git a/src/parsers/fields/address.rs b/src/parsers/fields/address.rs index 885040e0..0a56ea53 100644 --- a/src/parsers/fields/address.rs +++ b/src/parsers/fields/address.rs @@ -461,14 +461,14 @@ mod tests { #[test] fn parse_addresses() { for test in load_tests("address.json") { - assert_eq!( - MessageStream::new(test.header.as_bytes()) - .parse_address() - .unwrap_address(), - test.expected, - "failed for {:?}", - test.header - ); + let with_trim = MessageStream::new(test.header.trim().as_bytes()) + .parse_address() + .unwrap_address(); + let without_trim = MessageStream::new(test.header.as_bytes()) + .parse_address() + .unwrap_address(); + assert_eq!(with_trim, without_trim); + assert_eq!(without_trim, test.expected, "failed for {:?}", test.header); } } } From 12b385247e82edfd99a017c85bfd56801d7979f4 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 2 Mar 2026 19:39:39 +0100 Subject: [PATCH 2/4] fix: address parsing when last \n is missing --- src/parsers/fields/address.rs | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/parsers/fields/address.rs b/src/parsers/fields/address.rs index 0a56ea53..6ea56fbd 100644 --- a/src/parsers/fields/address.rs +++ b/src/parsers/fields/address.rs @@ -209,9 +209,9 @@ impl<'x> MessageStream<'x> { result: Vec::new(), }; - while let Some(ch) = self.next() { - match ch { - b'\n' => { + loop { + match self.next() { + Some(b'\n') | None => { parser.add_token(self, false); if self.try_next_is_space() { if !parser.is_token_start { @@ -222,7 +222,7 @@ impl<'x> MessageStream<'x> { break; } } - b'\\' if parser.state != AddressState::Name && !parser.is_escaped => { + Some(b'\\') if parser.state != AddressState::Name && !parser.is_escaped => { if parser.token_start > 0 { if parser.state == AddressState::Quote { parser.token_end = self.offset() - 1; @@ -232,24 +232,24 @@ impl<'x> MessageStream<'x> { parser.is_escaped = true; continue; } - b',' if parser.state == AddressState::Name => { + Some(b',') if parser.state == AddressState::Name => { parser.add_token(self, false); parser.add_address(); continue; } - b'<' if parser.state == AddressState::Name => { + Some(b'<') if parser.state == AddressState::Name => { parser.is_token_email = false; parser.add_token(self, false); parser.state_stack.push(AddressState::Name); parser.state = AddressState::Address; continue; } - b'>' if parser.state == AddressState::Address => { + Some(b'>') if parser.state == AddressState::Address => { parser.add_token(self, false); parser.state = parser.state_stack.pop().unwrap(); continue; } - b'"' if !parser.is_escaped => match parser.state { + Some(b'"') if !parser.is_escaped => match parser.state { AddressState::Name => { parser.state_stack.push(AddressState::Name); parser.state = AddressState::Quote; @@ -263,10 +263,12 @@ impl<'x> MessageStream<'x> { } _ => (), }, - b'@' if parser.state == AddressState::Name => { + Some(b'@') if parser.state == AddressState::Name => { parser.is_token_email = true; } - b'=' if parser.is_token_start && !parser.is_escaped && self.peek_char(b'?') => { + Some(b'=') + if parser.is_token_start && !parser.is_escaped && self.peek_char(b'?') => + { self.checkpoint(); if let Some(token) = self.decode_rfc2047() { let add_space = parser.state != AddressState::Quote; // Make borrow-checker happy @@ -281,7 +283,7 @@ impl<'x> MessageStream<'x> { } self.restore(); } - b' ' | b'\t' => { + Some(b' ' | b'\t') => { if !parser.is_token_start { parser.is_token_start = true; } @@ -298,8 +300,8 @@ impl<'x> MessageStream<'x> { } continue; } - b'\r' => continue, - b'(' if parser.state != AddressState::Quote && !parser.is_escaped => { + Some(b'\r') => continue, + Some(b'(') if parser.state != AddressState::Quote && !parser.is_escaped => { parser.state_stack.push(parser.state); if parser.state != AddressState::Comment { parser.add_token(self, false); @@ -307,7 +309,7 @@ impl<'x> MessageStream<'x> { continue; } } - b')' if parser.state == AddressState::Comment && !parser.is_escaped => { + Some(b')') if parser.state == AddressState::Comment && !parser.is_escaped => { let new_state = parser.state_stack.pop().unwrap(); if parser.state != new_state { parser.add_token(self, false); @@ -315,13 +317,13 @@ impl<'x> MessageStream<'x> { continue; } } - b':' if parser.state == AddressState::Name && !parser.is_escaped => { + Some(b':') if parser.state == AddressState::Name && !parser.is_escaped => { parser.add_group(); parser.add_token(self, false); parser.add_group_details(); continue; } - b';' if parser.state == AddressState::Name => { + Some(b';') if parser.state == AddressState::Name => { parser.add_token(self, false); parser.add_address(); parser.add_group(); From 6c04af963a938343023603edd61a606856e1ca83 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 2 Mar 2026 19:44:04 +0100 Subject: [PATCH 3/4] refactor: remove unwrap --- src/parsers/fields/address.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/parsers/fields/address.rs b/src/parsers/fields/address.rs index 6ea56fbd..497a9a8a 100644 --- a/src/parsers/fields/address.rs +++ b/src/parsers/fields/address.rs @@ -362,12 +362,14 @@ impl<'x> MessageStream<'x> { } fn concat_tokens<'x>(tokens: &mut Vec>) -> Cow<'x, str> { - if tokens.len() == 1 { - tokens.pop().unwrap() - } else { + if 1 < tokens.len() { let result = tokens.concat(); tokens.clear(); result.into() + } else if let Some(tok) = tokens.pop() { + tok + } else { + Cow::Borrowed("") } } From 0c2969cc46b4253500ab73932ab358539b1debb0 Mon Sep 17 00:00:00 2001 From: c Date: Mon, 2 Mar 2026 19:46:17 +0100 Subject: [PATCH 4/4] refactor: needless branching --- src/parsers/fields/address.rs | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/parsers/fields/address.rs b/src/parsers/fields/address.rs index 497a9a8a..e5c8277f 100644 --- a/src/parsers/fields/address.rs +++ b/src/parsers/fields/address.rs @@ -214,9 +214,7 @@ impl<'x> MessageStream<'x> { Some(b'\n') | None => { parser.add_token(self, false); if self.try_next_is_space() { - if !parser.is_token_start { - parser.is_token_start = true; - } + parser.is_token_start = true; continue; } else { break; @@ -284,12 +282,9 @@ impl<'x> MessageStream<'x> { self.restore(); } Some(b' ' | b'\t') => { - if !parser.is_token_start { - parser.is_token_start = true; - } - if parser.is_escaped { - parser.is_escaped = false; - } + parser.is_token_start = true; + parser.is_escaped = false; + if parser.state == AddressState::Quote { if parser.token_start == 0 { parser.token_start = self.offset(); @@ -332,13 +327,8 @@ impl<'x> MessageStream<'x> { _ => (), } - if parser.is_escaped { - parser.is_escaped = false; - } - - if parser.is_token_start { - parser.is_token_start = false; - } + parser.is_escaped = false; + parser.is_token_start = false; if parser.token_start == 0 { parser.token_start = self.offset();