From 99f28195dbfad2572bdcffdda674ec1fed422143 Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Fri, 7 Apr 2023 18:21:21 +0200 Subject: [PATCH] Add support for SEARCHRES --- imapserver/capability.go | 2 +- internal/imapwire/decoder.go | 5 +++++ internal/imapwire/encoder.go | 5 +++-- search.go | 24 ++++++++++++++++++++++++ seqset.go | 3 +++ 5 files changed, 36 insertions(+), 3 deletions(-) diff --git a/imapserver/capability.go b/imapserver/capability.go index 9c303314..ffcd1d64 100644 --- a/imapserver/capability.go +++ b/imapserver/capability.go @@ -64,11 +64,11 @@ func (c *Conn) availableCaps() []imap.Cap { imap.CapEnable, imap.CapIdle, }...) - // TODO: implement imap.CapSearchRes addAvailableCaps(&caps, available, []imap.Cap{ imap.CapNamespace, imap.CapUIDPlus, imap.CapESearch, + imap.CapSearchRes, imap.CapListExtended, imap.CapListStatus, imap.CapMove, diff --git a/internal/imapwire/decoder.go b/internal/imapwire/decoder.go index 80b6a911..363364fd 100644 --- a/internal/imapwire/decoder.go +++ b/internal/imapwire/decoder.go @@ -461,6 +461,11 @@ func (dec *Decoder) ExpectMailbox(ptr *string) bool { } func (dec *Decoder) ExpectSeqSet(ptr *imap.SeqSet) bool { + if dec.Special('$') { + *ptr = imap.SearchRes() + return true + } + var s string if !dec.Expect(dec.Func(&s, isSeqSetChar), "sequence-set") { return false diff --git a/internal/imapwire/encoder.go b/internal/imapwire/encoder.go index 399e4611..f7e00c44 100644 --- a/internal/imapwire/encoder.go +++ b/internal/imapwire/encoder.go @@ -159,11 +159,12 @@ func (enc *Encoder) Mailbox(name string) *Encoder { } func (enc *Encoder) SeqSet(seqSet imap.SeqSet) *Encoder { - if len(seqSet) == 0 { + s := seqSet.String() + if s == "" { enc.setErr(fmt.Errorf("imapwire: cannot encode empty sequence set")) return enc } - return enc.writeString(seqSet.String()) + return enc.writeString(s) } func (enc *Encoder) Flag(flag imap.Flag) *Encoder { diff --git a/search.go b/search.go index 425c700c..3af63d06 100644 --- a/search.go +++ b/search.go @@ -1,6 +1,7 @@ package imap import ( + "reflect" "time" ) @@ -11,6 +12,8 @@ type SearchOptions struct { ReturnMax bool ReturnAll bool ReturnCount bool + // Requires IMAP4rev2 or SEARCHRES + ReturnSave bool } // SearchCriteria is a criteria for the SEARCH command. @@ -116,3 +119,24 @@ func (data *SearchData) AllNums() []uint32 { nums, _ := data.All.Nums() return nums } + +// searchRes is a special empty SeqSet which can be used as a marker. It has +// a non-zero cap so that its data pointer is non-nil and can be compared. +var ( + searchRes = make(SeqSet, 0, 1) + searchResAddr = reflect.ValueOf(searchRes).Pointer() +) + +// SearchRes returns a special marker which can be used instead of a SeqSet to +// reference the last SEARCH result. On the wire, it's encoded as '$'. +// +// It requires IMAP4rev2 or the SEARCHRES extension. +func SearchRes() SeqSet { + return searchRes +} + +// IsSearchRes checks whether a sequence set is a reference to the last SEARCH +// result. See SearchRes. +func IsSearchRes(seqSet SeqSet) bool { + return reflect.ValueOf(seqSet).Pointer() == searchResAddr +} diff --git a/seqset.go b/seqset.go index 3dcc3627..84fd5aba 100644 --- a/seqset.go +++ b/seqset.go @@ -214,6 +214,9 @@ func (s SeqSet) Nums() (nums []uint32, ok bool) { // String returns a sorted representation of all contained sequence values. func (s SeqSet) String() string { + if IsSearchRes(s) { + return "$" + } if len(s) == 0 { return "" }