fix(PROJ-86): defensive UID-Filterung gegen kaputten IMAP-Server (Sync-Loop)

q.sysops.de (Konto patrick@sysops.de) beantwortet "UID SEARCH UID N:*" auf
Mailboxen mit weniger Nachrichten als N fehlerhaft und liefert die letzte
existierende UID statt eines leeren Ergebnisses zurück (verifiziert per
Debug-Client: "UID SEARCH UID 22:*" auf 21-Nachrichten-Mailbox -> [21]).
Ohne Filterung wurde dieselbe UID bei jedem 5-Minuten-Sync erneut als
"neu" behandelt und re-indexiert - dabei driftete auch date_ts bei
Mails ohne Date-Header ständig auf "jetzt". Server-Antwort wird jetzt
client-seitig re-gefiltert, nur UIDs >= angefragtem Minimum zählen als neu.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UPFC6Jk2ke1Pq9XcuVGP1R
This commit is contained in:
sysops
2026-09-01 15:01:23 +02:00
co-authored by Claude Sonnet 5
parent 160b28099e
commit ffdfa585cc
+11 -1
View File
@@ -398,7 +398,17 @@ func (s *Scheduler) syncFolder(
if err != nil { if err != nil {
return 0, 0, fmt.Errorf("imap scheduler: uid search incremental %q: %w", folder, err) return 0, 0, fmt.Errorf("imap scheduler: uid search incremental %q: %w", folder, err)
} }
uids = searchData.AllUIDs() // Defensive re-filter: some IMAP servers mishandle open-ended "N:*"
// UID ranges and return UIDs below N anyway (observed against
// q.sysops.de — "UID SEARCH UID 22:*" on a 21-message mailbox
// returned [21] instead of an empty set). Trusting that blindly
// makes the sync loop rediscover the same top message as "new"
// forever, every cycle. Never trust the server's range filtering.
for _, uid := range searchData.AllUIDs() {
if uid >= minUID {
uids = append(uids, uid)
}
}
} else { } else {
// First sync or post-UIDVALIDITY-reset: fetch everything. // First sync or post-UIDVALIDITY-reset: fetch everything.
searchData, err := c.UIDSearch(&imapv2.SearchCriteria{}, nil).Wait() searchData, err := c.UIDSearch(&imapv2.SearchCriteria{}, nil).Wait()