diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 6258ecb7..fd78c19f 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -300,7 +300,7 @@ impl MailBackend for ImapType { .chain_err_summary(|| { format!("Could not select mailbox {}", mailbox_path) })?; - let examine_response = protocol_parser::select_response(&response) + let mut examine_response = protocol_parser::select_response(&response) .chain_err_summary(|| { format!( "Could not parse select response for mailbox {}", @@ -345,6 +345,24 @@ impl MailBackend for ImapType { let mut exists: usize = examine_response.exists; /* reselecting the same mailbox with EXAMINE prevents expunging it */ conn.examine_mailbox(mailbox_hash, &mut response)?; + if examine_response.uidnext == 0 { + /* UIDNEXT shouldn't be 0, since exists != 0 at this point */ + conn.send_command( + format!("STATUS \"{}\" (UIDNEXT)", mailbox_path).as_bytes(), + )?; + conn.read_response(&mut response, RequiredResponses::STATUS)?; + let (_, status) = protocol_parser::status_response(response.as_bytes())?; + if let Some(uidnext) = status.uidnext { + if uidnext == 0 { + return Err(MeliError::new( + "IMAP server error: zero UIDNEXt with nonzero exists.", + )); + } + examine_response.uidnext = uidnext; + } else { + return Err(MeliError::new("IMAP server did not reply with UIDNEXT")); + } + } let mut tag_lck = uid_store.tag_index.write().unwrap(); diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index 4526e53d..021c88ec 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -1402,6 +1402,66 @@ pub fn bodystructure_has_attachments(input: &[u8]) -> bool { input.rfind(b" \"mixed\" ").is_some() || input.rfind(b" \"MIXED\" ").is_some() } +#[derive(Debug, Default, Clone)] +pub struct StatusResponse { + pub messages: Option, + pub recent: Option, + pub uidnext: Option, + pub uidvalidity: Option, + pub unseen: Option, +} + +// status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")" +// status-att = "MESSAGES" / "RECENT" / "UIDNEXT" / "UIDVALIDITY" / "UNSEEN" +pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> { + let (input, _) = tag("* STATUS ")(input)?; + let (input, _) = take_until(" (")(input)?; + let (input, _) = tag(" (")(input)?; + let (input, result) = permutation(( + opt(preceded( + tag("MESSAGES "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )), + opt(preceded( + tag("RECENT "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )), + opt(preceded( + tag("UIDNEXT "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )), + opt(preceded( + tag("UIDVALIDITY "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )), + opt(preceded( + tag("UNSEEN "), + map_res(digit1, |s| { + usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) + }), + )), + ))(input)?; + let (input, _) = tag(")\r\n")(input)?; + Ok(( + input, + StatusResponse { + messages: result.0, + recent: result.1, + uidnext: result.2, + uidvalidity: result.3, + unseen: result.4, + }, + )) +} + // mailbox = "INBOX" / astring // ; INBOX is case-insensitive. All case variants of // ; INBOX (e.g., "iNbOx") MUST be interpreted as INBOX