From 0227bbc3059ebc35b90afdeae65c284e67d6664d Mon Sep 17 00:00:00 2001 From: link2xt Date: Fri, 20 Oct 2023 13:10:15 +0000 Subject: [PATCH] fix(imap): fallback to STATUS if SELECT did not return UIDNEXT Winmail Pro Mail Server 5.1.0616 does not return UIDNEXT in response to SELECT, but returns it when explicitly requested via STATUS command: ? SELECT INBOX * FLAGS (\Draft \Answered \Flagged \Deleted \Seen \Recent) * OK [PERMANENTFLAGS (\Draft \Answered \Flagged \Deleted \Seen)] Limited * 2 EXISTS * 0 RECENT * OK [UIDVALIDITY 1697802109] Ok ? OK [READ-WRITE] Ok SELECT completed ? STATUS INBOX (UIDNEXT) * STATUS "INBOX" (UIDNEXT 4) ? OK STATUS completed Previously used FETCH method is reported to fail for some users, the FETCH command sometimes returns no results. Besides, there is no guarantee that the message with the highest sequence number has the highest UID. In the worst case if STATUS does not return UIDNEXT in response to explicit request, we fall back to setting UIDNEXT to 1 instead of returning an error. --- src/imap.rs | 55 +++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/src/imap.rs b/src/imap.rs index 8f009f8ba..970273a5f 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -82,7 +82,6 @@ const RFC724MID_UID: &str = "(UID BODY.PEEK[HEADER.FIELDS (\ MESSAGE-ID \ X-MICROSOFT-ORIGINAL-MESSAGE-ID\ )])"; -const JUST_UID: &str = "(UID)"; const BODY_FULL: &str = "(FLAGS BODY.PEEK[])"; const BODY_PARTIAL: &str = "(FLAGS RFC822.SIZE BODY.PEEK[HEADER])"; @@ -627,18 +626,6 @@ impl Imap { // UIDVALIDITY is modified, reset highest seen MODSEQ. set_modseq(context, folder, 0).await?; - if mailbox.exists == 0 { - info!(context, "Folder {folder:?} is empty."); - - // set uid_next=1 for empty folders. - // If we do not do this here, we'll miss the first message - // as we will get in here again and fetch from uid_next then. - // Also, the "fall back to fetching" below would need a non-zero mailbox.exists to work. - set_uid_next(context, folder, 1).await?; - set_uidvalidity(context, folder, new_uid_validity).await?; - return Ok(false); - } - // ============== uid_validity has changed or is being set the first time. ============== let new_uid_next = match mailbox.uid_next { @@ -646,25 +633,35 @@ impl Imap { None => { warn!( context, - "IMAP folder {folder:?} has no uid_next, fall back to fetching." + "SELECT response for IMAP folder {folder:?} has no UIDNEXT, fall back to STATUS command." ); - // note that we use fetch by sequence number - // and thus we only need to get exactly the - // last-index message. - let set = format!("{}", mailbox.exists); - let mut list = session - .inner - .fetch(set, JUST_UID) - .await - .context("Error fetching UID")?; - let mut new_last_seen_uid = None; - while let Some(fetch) = list.try_next().await? { - if fetch.message == mailbox.exists && fetch.uid.is_some() { - new_last_seen_uid = fetch.uid; - } + // RFC 3501 says STATUS command SHOULD NOT be used + // on the currently seleced mailbox because the same + // information can be obtained by other means, + // such as reading SELECT response. + // + // However, it also says that UIDNEXT is REQUIRED + // in the SELECT response and if we are here, + // it is actually not returned. + // + // In particular, Winmail Pro Mail Server 5.1.0616 + // never returns UIDNEXT in SELECT response, + // but responds to "SELECT INBOX (UIDNEXT)" command. + let status = session + .inner + .status(folder, "(UIDNEXT)") + .await + .context("STATUS (UIDNEXT) error for {folder:?}")?; + + if let Some(uid_next) = status.uid_next { + uid_next + } else { + warn!(context, "STATUS {folder} (UIDNEXT) did not return UIDNEXT"); + + // Set UIDNEXT to 1 as a last resort fallback. + 1 } - new_last_seen_uid.context("select: failed to fetch")? + 1 } };