From 5e4d80d48e1d33843f6a95aa26cfbc320f1f8f6e Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Wed, 19 Feb 2025 21:29:14 +0100 Subject: [PATCH] implement the WITHIN IMAP extension, rfc 5032 for IMAP "SEARCH" command criteria "YOUNGER" and "OLDER". --- README.md | 5 ++--- imapserver/parse.go | 4 ++++ imapserver/search.go | 8 ++++++++ imapserver/search_test.go | 6 ++++++ imapserver/server.go | 3 ++- rfc/index.txt | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 614b4ba..203a353 100644 --- a/README.md +++ b/README.md @@ -138,9 +138,8 @@ https://nlnet.nl/project/Mox/. - "mox setup" command, with webapp for interactive setup - Automate DNS management, for setup and maintenance, such as DANE/DKIM key rotation. - Calendaring with CalDAV/iCal -- More IMAP extensions (PREVIEW, WITHIN, IMPORTANT, COMPRESS=DEFLATE, - CREATE-SPECIAL-USE, SAVEDATE, UNAUTHENTICATE, REPLACE, QUOTA, NOTIFY, - MULTIAPPEND, OBJECTID, MULTISEARCH, THREAD, SORT) +- More IMAP extensions (PREVIEW, IMPORTANT, COMPRESS=DEFLATE, UNAUTHENTICATE, + REPLACE, QUOTA, NOTIFY, MULTIAPPEND, OBJECTID, MULTISEARCH, THREAD, SORT) - Introbox, to which first-time senders are delivered - ARC, with forwarded email from trusted source - Add special IMAP mailbox ("Queue?") that contains queued but diff --git a/imapserver/parse.go b/imapserver/parse.go index 3f77dab..24de452 100644 --- a/imapserver/parse.go +++ b/imapserver/parse.go @@ -793,6 +793,7 @@ var searchKeyWords = []string{ "BEFORE", "BODY", "CC", "DELETED", "FLAGGED", "FROM", "KEYWORD", + "OLDER", "YOUNGER", // WITHIN extension, ../rfc/5032:72 "NEW", "OLD", "ON", "RECENT", "SEEN", "SINCE", "SUBJECT", "TEXT", "TO", @@ -933,6 +934,9 @@ func (p *parser) xsearchKey() *searchKey { p.xspace() sk.date = p.xdate() // ../rfc/8514:267 case "SAVEDATESUPPORTED": + case "OLDER", "YOUNGER": + p.xspace() + sk.number = int64(p.xnznumber()) default: p.xerrorf("missing case for op %q", sk.op) } diff --git a/imapserver/search.go b/imapserver/search.go index fb939d8..76a9f50 100644 --- a/imapserver/search.go +++ b/imapserver/search.go @@ -5,6 +5,7 @@ import ( "log/slog" "net/textproto" "strings" + "time" "github.com/mjl-/bstore" @@ -539,6 +540,13 @@ func (s *search) match0(sk searchKey) bool { // mailboxes, but we only have this metadata from the time we implemented this // feature. return s.m.SaveDate != nil + case "OLDER": + // ../rfc/5032:76 + seconds := int64(time.Since(s.m.Received) / time.Second) + return seconds >= sk.number + case "YOUNGER": + seconds := int64(time.Since(s.m.Received) / time.Second) + return seconds <= sk.number } if s.p == nil { diff --git a/imapserver/search_test.go b/imapserver/search_test.go index f456426..8175038 100644 --- a/imapserver/search_test.go +++ b/imapserver/search_test.go @@ -111,6 +111,12 @@ func TestSearch(t *testing.T) { tc.transactf("ok", "search before 1-Jan-2020") tc.xsearch() // Before is about received, not date header of message. + // WITHIN extension with OLDER & YOUNGER. + tc.transactf("ok", "search older 60") + tc.xsearch(1, 2, 3) + tc.transactf("ok", "search younger 60") + tc.xsearch() + // SAVEDATE extension. tc.transactf("ok", "search savedbefore %s", saveDate.Add(24*time.Hour).Format("2-Jan-2006")) tc.xsearch(1, 2, 3) diff --git a/imapserver/server.go b/imapserver/server.go index b1211eb..2b591bc 100644 --- a/imapserver/server.go +++ b/imapserver/server.go @@ -160,12 +160,13 @@ var authFailDelay = time.Second // After authentication failure. // QUOTA QUOTA=RES-STORAGE: ../rfc/9208:111 // METADATA: ../rfc/5464 // SAVEDATE: ../rfc/8514 +// WITHIN: ../rfc/5032 // // We always announce support for SCRAM PLUS-variants, also on connections without // TLS. The client should not be selecting PLUS variants on non-TLS connections, // instead opting to do the bare SCRAM variant without indicating the server claims // to support the PLUS variant (skipping the server downgrade detection check). -const serverCapabilities = "IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ACCEPT LIST-EXTENDED SPECIAL-USE CREATE-SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256-PLUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1-PLUS AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC STATUS=SIZE QUOTA QUOTA=RES-STORAGE METADATA SAVEDATE" +const serverCapabilities = "IMAP4rev2 IMAP4rev1 ENABLE LITERAL+ IDLE SASL-IR BINARY UNSELECT UIDPLUS ESEARCH SEARCHRES MOVE UTF8=ACCEPT LIST-EXTENDED SPECIAL-USE CREATE-SPECIAL-USE LIST-STATUS AUTH=SCRAM-SHA-256-PLUS AUTH=SCRAM-SHA-256 AUTH=SCRAM-SHA-1-PLUS AUTH=SCRAM-SHA-1 AUTH=CRAM-MD5 ID APPENDLIMIT=9223372036854775807 CONDSTORE QRESYNC STATUS=SIZE QUOTA QUOTA=RES-STORAGE METADATA SAVEDATE WITHIN" type conn struct { cid int64 diff --git a/rfc/index.txt b/rfc/index.txt index 6f78af9..cbd7923 100644 --- a/rfc/index.txt +++ b/rfc/index.txt @@ -190,7 +190,7 @@ https://www.iana.org/assignments/message-headers/message-headers.xhtml 4731 Yes - IMAP4 Extension to SEARCH Command for Controlling What Kind of Information Is Returned 4959 Yes - IMAP Extension for Simple Authentication and Security Layer (SASL) Initial Client Response 4978 Roadmap - The IMAP COMPRESS Extension -5032 Roadmap - WITHIN Search Extension to the IMAP Protocol +5032 Yes - WITHIN Search Extension to the IMAP Protocol 5092 Roadmap - IMAP URL Scheme 5161 Yes - The IMAP ENABLE Extension 5162 Yes Obs (RFC 7162) IMAP4 Extensions for Quick Mailbox Resynchronization