From 88a1f0d4bc17b60f8f23ea71f33a81aee78f8769 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 23 Oct 2022 20:58:03 +0300 Subject: [PATCH] melib/imap/parser: fix FETCH response parsing bug Closes #160 Closes #128 --- melib/src/backends/imap/protocol_parser.rs | 64 +++++++++++++++++----- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index b58e1b061..bb828a3e5 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -150,8 +150,9 @@ fn test_imap_required_responses() { assert_eq!(v.len(), 1); } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub struct Alert(String); + pub type ImapParseResult<'a, T> = Result<(&'a [u8], T, Option)>; pub struct ImapLineIterator<'a> { slice: &'a [u8], @@ -604,8 +605,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult> { i += (input.len() - i - rest.len()) + 1; } else { return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - String::from_utf8_lossy(input) + "Unexpected input while parsing UID FETCH response. Could not parse FLAGS: {:.40}.", + String::from_utf8_lossy(&input[i..]) )))); } } else if input[i..].starts_with(b"MODSEQ (") { @@ -639,8 +640,8 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult> { i += input.len() - i - rest.len(); } else { return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", - String::from_utf8_lossy(input) + "Unexpected input while parsing UID FETCH response. Could not parse RFC822: {:.40}", + String::from_utf8_lossy(&input[i..]) )))); } } else if input[i..].starts_with(b"ENVELOPE (") { @@ -650,7 +651,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult> { i += input.len() - i - rest.len(); } else { return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", + "Unexpected input while parsing UID FETCH response. Could not parse ENVELOPE: {:.40}", String::from_utf8_lossy(&input[i..]) )))); } @@ -672,7 +673,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult> { i += input.len() - i - rest.len(); } else { return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", + "Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (REFERENCES)]: {:.40}", String::from_utf8_lossy(&input[i..]) )))); } @@ -688,7 +689,7 @@ pub fn fetch_response(input: &[u8]) -> ImapParseResult> { i += input.len() - i - rest.len(); } else { return debug!(Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH response. Got: `{:.40}`", + "Unexpected input while parsing UID FETCH response. Could not parse BODY[HEADER.FIELDS (\"REFERENCES\"): {:.40}", String::from_utf8_lossy(&input[i..]) )))); } @@ -736,9 +737,9 @@ pub fn fetch_responses(mut input: &[u8]) -> ImapParseResult { return Err(MeliError::new(format!( - "Unexpected input while parsing UID FETCH responses: `{:.40}`, {}", + "Unexpected input while parsing UID FETCH responses: {} `{:.40}`", + err, String::from_utf8_lossy(input), - err ))); } } @@ -934,7 +935,7 @@ pub fn untagged_responses(input: &[u8]) -> ImapParseResult\") BODY[HEADER.FIELDS (REFERENCES)] {2}\r\n\r\nBODYSTRUCTURE ((\"text\" \"html\" (\"charset\" \"us-ascii\") \"\" NIL \"7BIT\" 17236 232 NIL NIL NIL NIL)(\"image\" \"jpeg\" (\"name\" \"image001.jpg\") \"\" \"image001.jpg\" \"base64\" 1918 NIL (\"inline\" (\"filename\" \"image001.jpg\" \"size\" \"1650\" \"creation-date\" \"Sun, 09 Aug 2015 20:56:04 GMT\" \"modification-date\" \"Sun, 14 Aug 2022 22:11:45 GMT\")) NIL NIL) \"related\" (\"boundary\" \"xx--xx\" \"type\" \"text/html\") NIL \"en-US\"))\r\n"; + + let mut address = SmallVec::new(); + address.push(Address::new(None, "xx@xx.com".to_string())); + let mut env = Envelope::new(0); + env.set_subject("xxxx/xxxx".as_bytes().to_vec()); + env.set_date("Fri, 24 Jun 2011 10:09:10 +0000".as_bytes()); + env.set_from(address.clone()); + env.set_to(address); + env.set_message_id("".as_bytes()); + assert_eq!( + fetch_response(input).unwrap(), + ( + &b""[..], + FetchResponse { + uid: Some(7608), + message_sequence_number: 198, + flags: Some((Flag::SEEN, vec![])), + modseq: None, + body: None, + references: None, + envelope: Some(env), + raw_fetch_value: input, + }, + None + ) + ); +} + pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec> { alt(( |input: &'a [u8]| -> IResult<&'a [u8], Vec> { @@ -1127,7 +1159,7 @@ pub fn select_response(input: &[u8]) -> Result { } #[test] -fn test_select_response() { +fn test_imap_select_response() { let r = b"* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)\r\n* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft \\*)] Flags permitted.\r\n* 45 EXISTS\r\n* 0 RECENT\r\n* OK [UNSEEN 16] First unseen.\r\n* OK [UIDVALIDITY 1554422056] UIDs valid\r\n* OK [UIDNEXT 50] Predicted next UID\r\n"; assert_eq!( @@ -1347,6 +1379,12 @@ pub fn envelope(input: &[u8]) -> IResult<&[u8], Envelope> { )) } +#[test] +fn test_imap_envelope() { + let input: &[u8] = b"(\"Fri, 24 Jun 2011 10:09:10 +0000\" \"xxxx/xxxx\" ((\"xx@xx.com\" NIL \"xx\" \"xx.com\")) NIL NIL ((\"xx@xx\" NIL \"xx\" \"xx.com\")) ((\"'xx, xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\") (\"'xx'\" NIL \"xx.xx\" \"xx.com\") (\"'xx xx'\" NIL \"xx.xx\" \"xx.com\") (\"xx.xx@xx.com\" NIL \"xx.xx\" \"xx.com\")) NIL NIL \"\")"; + _ = envelope(input).unwrap(); +} + /* Helper to build StrBuilder for Address structs */ macro_rules! str_builder { ($offset:expr, $length:expr) => { @@ -1366,7 +1404,7 @@ pub fn envelope_addresses<'a>( |input: &'a [u8]| -> IResult<&'a [u8], Option>> { let (input, _) = tag("(")(input)?; let (input, envelopes) = fold_many1( - delimited(tag("("), envelope_address, tag(")")), + delimited(tag("("), envelope_address, alt((tag(") "), tag(")")))), SmallVec::new, |mut acc, item| { acc.push(item);