You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(imap): always advance expected UIDNEXT to avoid skipping IDLE in a loop
Ensure the client does not busy loop
skipping IDLE if UIDNEXT of the mailbox is higher than
the last seen UID plus 1, e.g. if the message
with UID=UIDNEXT-1 was deleted before we fetched it.
We do not fallback to UIDNEXT=1 anymore
if the STATUS command cannot determine UIDNEXT.
There are no known IMAP servers with broken STATUS so far.
This allows to guarantee that select_with_uidvalidity()
sets UIDNEXT for the mailbox and use it in fetch_new_messages()
to ensure that UIDNEXT always advances even
when there are no messages to fetch.
Copy file name to clipboardExpand all lines: src/imap.rs
+65-54Lines changed: 65 additions & 54 deletions
Original file line number
Diff line number
Diff line change
@@ -568,9 +568,14 @@ impl Imap {
568
568
Ok(())
569
569
}
570
570
571
-
/// Select a folder and take care of uidvalidity changes.
572
-
/// Also, when selecting a folder for the first time, sets the uid_next to the current
571
+
/// Selects a folder and takes care of UIDVALIDITY changes.
572
+
///
573
+
/// When selecting a folder for the first time, sets the uid_next to the current
573
574
/// mailbox.uid_next so that no old emails are fetched.
575
+
///
576
+
/// Makes sure that UIDNEXT is known for `selected_mailbox`
577
+
/// and errors out if UIDNEXT cannot be determined.
578
+
///
574
579
/// Returns Result<new_emails> (i.e. whether new emails arrived),
575
580
/// if in doubt, returns new_emails=true so emails are fetched.
576
581
pub(crate)asyncfnselect_with_uidvalidity(
@@ -591,6 +596,37 @@ impl Imap {
591
596
let new_uid_validity = mailbox
592
597
.uid_validity
593
598
.with_context(|| format!("No UIDVALIDITY for folder {folder}"))?;
599
+
let new_uid_next = ifletSome(uid_next) = mailbox.uid_next{
600
+
uid_next
601
+
}else{
602
+
warn!(
603
+
context,
604
+
"SELECT response for IMAP folder {folder:?} has no UIDNEXT, fall back to STATUS command."
605
+
);
606
+
607
+
// RFC 3501 says STATUS command SHOULD NOT be used
608
+
// on the currently selected mailbox because the same
609
+
// information can be obtained by other means,
610
+
// such as reading SELECT response.
611
+
//
612
+
// However, it also says that UIDNEXT is REQUIRED
613
+
// in the SELECT response and if we are here,
614
+
// it is actually not returned.
615
+
//
616
+
// In particular, Winmail Pro Mail Server 5.1.0616
617
+
// never returns UIDNEXT in SELECT response,
618
+
// but responds to "STATUS INBOX (UIDNEXT)" command.
619
+
let status = session
620
+
.inner
621
+
.status(folder,"(UIDNEXT)")
622
+
.await
623
+
.context("STATUS (UIDNEXT) error for {folder:?}")?;
624
+
625
+
status
626
+
.uid_next
627
+
.context("STATUS {folder} (UIDNEXT) did not return UIDNEXT")?
628
+
};
629
+
mailbox.uid_next = Some(new_uid_next);
594
630
595
631
let old_uid_validity = get_uidvalidity(context, folder)
596
632
.await
@@ -606,18 +642,16 @@ impl Imap {
606
642
// the caller tries to fetch new messages (we could of course run a SELECT command now, but trying to fetch
607
643
// new messages is only one command, just as a SELECT command)
608
644
true
609
-
}elseifletSome(uid_next) = mailbox.uid_next{
610
-
ifuid_next < old_uid_next {
645
+
}else{
646
+
ifnew_uid_next < old_uid_next {
611
647
warn!(
612
648
context,
613
-
"The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...",
649
+
"The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {new_uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...",
0 commit comments