203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
package imap
|
|
|
|
import (
|
|
"reflect"
|
|
"time"
|
|
)
|
|
|
|
// SearchOptions contains options for the SEARCH command.
|
|
type SearchOptions struct {
|
|
// Requires IMAP4rev2 or ESEARCH
|
|
ReturnMin bool
|
|
ReturnMax bool
|
|
ReturnAll bool
|
|
ReturnCount bool
|
|
// Requires IMAP4rev2 or SEARCHRES
|
|
ReturnSave bool
|
|
}
|
|
|
|
// SearchCriteria is a criteria for the SEARCH command.
|
|
//
|
|
// When multiple fields are populated, the result is the intersection ("and"
|
|
// function) of all messages that match the fields.
|
|
//
|
|
// And, Not and Or can be used to combine multiple criteria together. For
|
|
// instance, the following criteria matches messages not containing "hello":
|
|
//
|
|
// SearchCriteria{Not: []SearchCriteria{{
|
|
// Body: []string{"hello"},
|
|
// }}}
|
|
//
|
|
// The following criteria matches messages containing either "hello" or
|
|
// "world":
|
|
//
|
|
// SearchCriteria{Or: [][2]SearchCriteria{{
|
|
// {Body: []string{"hello"}},
|
|
// {Body: []string{"world"}},
|
|
// }}}
|
|
type SearchCriteria struct {
|
|
SeqNum []SeqSet
|
|
UID []UIDSet
|
|
|
|
// Only the date is used, the time and timezone are ignored
|
|
Since time.Time
|
|
Before time.Time
|
|
SentSince time.Time
|
|
SentBefore time.Time
|
|
|
|
Header []SearchCriteriaHeaderField
|
|
Body []string
|
|
Text []string
|
|
|
|
Flag []Flag
|
|
NotFlag []Flag
|
|
|
|
Larger int64
|
|
Smaller int64
|
|
|
|
Not []SearchCriteria
|
|
Or [][2]SearchCriteria
|
|
|
|
ModSeq *SearchCriteriaModSeq // requires CONDSTORE
|
|
}
|
|
|
|
// And intersects two search criteria.
|
|
func (criteria *SearchCriteria) And(other *SearchCriteria) {
|
|
criteria.SeqNum = append(criteria.SeqNum, other.SeqNum...)
|
|
criteria.UID = append(criteria.UID, other.UID...)
|
|
|
|
criteria.Since = intersectSince(criteria.Since, other.Since)
|
|
criteria.Before = intersectBefore(criteria.Before, other.Before)
|
|
criteria.SentSince = intersectSince(criteria.SentSince, other.SentSince)
|
|
criteria.SentBefore = intersectBefore(criteria.SentBefore, other.SentBefore)
|
|
|
|
criteria.Header = append(criteria.Header, other.Header...)
|
|
criteria.Body = append(criteria.Body, other.Body...)
|
|
criteria.Text = append(criteria.Text, other.Text...)
|
|
|
|
criteria.Flag = append(criteria.Flag, other.Flag...)
|
|
criteria.NotFlag = append(criteria.NotFlag, other.NotFlag...)
|
|
|
|
if criteria.Larger == 0 || other.Larger > criteria.Larger {
|
|
criteria.Larger = other.Larger
|
|
}
|
|
if criteria.Smaller == 0 || other.Smaller < criteria.Smaller {
|
|
criteria.Smaller = other.Smaller
|
|
}
|
|
|
|
criteria.Not = append(criteria.Not, other.Not...)
|
|
criteria.Or = append(criteria.Or, other.Or...)
|
|
}
|
|
|
|
func intersectSince(t1, t2 time.Time) time.Time {
|
|
switch {
|
|
case t1.IsZero():
|
|
return t2
|
|
case t2.IsZero():
|
|
return t1
|
|
case t1.After(t2):
|
|
return t1
|
|
default:
|
|
return t2
|
|
}
|
|
}
|
|
|
|
func intersectBefore(t1, t2 time.Time) time.Time {
|
|
switch {
|
|
case t1.IsZero():
|
|
return t2
|
|
case t2.IsZero():
|
|
return t1
|
|
case t1.Before(t2):
|
|
return t1
|
|
default:
|
|
return t2
|
|
}
|
|
}
|
|
|
|
type SearchCriteriaHeaderField struct {
|
|
Key, Value string
|
|
}
|
|
|
|
type SearchCriteriaModSeq struct {
|
|
ModSeq uint64
|
|
MetadataName string
|
|
MetadataType SearchCriteriaMetadataType
|
|
}
|
|
|
|
type SearchCriteriaMetadataType string
|
|
|
|
const (
|
|
SearchCriteriaMetadataAll SearchCriteriaMetadataType = "all"
|
|
SearchCriteriaMetadataPrivate SearchCriteriaMetadataType = "priv"
|
|
SearchCriteriaMetadataShared SearchCriteriaMetadataType = "shared"
|
|
)
|
|
|
|
// SearchData is the data returned by a SEARCH command.
|
|
type SearchData struct {
|
|
All NumSet
|
|
|
|
// requires IMAP4rev2 or ESEARCH
|
|
UID bool
|
|
Min uint32
|
|
Max uint32
|
|
Count uint32
|
|
|
|
// requires CONDSTORE
|
|
ModSeq uint64
|
|
}
|
|
|
|
// AllSeqNums returns All as a slice of sequence numbers.
|
|
func (data *SearchData) AllSeqNums() []uint32 {
|
|
seqSet, ok := data.All.(SeqSet)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Note: a dynamic sequence set would be a server bug
|
|
nums, ok := seqSet.Nums()
|
|
if !ok {
|
|
panic("imap: SearchData.All is a dynamic number set")
|
|
}
|
|
return nums
|
|
}
|
|
|
|
// AllUIDs returns All as a slice of UIDs.
|
|
func (data *SearchData) AllUIDs() []UID {
|
|
uidSet, ok := data.All.(UIDSet)
|
|
if !ok {
|
|
return nil
|
|
}
|
|
|
|
// Note: a dynamic sequence set would be a server bug
|
|
uids, ok := uidSet.Nums()
|
|
if !ok {
|
|
panic("imap: SearchData.All is a dynamic number set")
|
|
}
|
|
return uids
|
|
}
|
|
|
|
// searchRes is a special empty UIDSet 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.
|
|
//
|
|
// It's a UIDSet rather than a SeqSet so that it can be passed to the
|
|
// UID EXPUNGE command.
|
|
var (
|
|
searchRes = make(UIDSet, 0, 1)
|
|
searchResAddr = reflect.ValueOf(searchRes).Pointer()
|
|
)
|
|
|
|
// SearchRes returns a special marker which can be used instead of a UIDSet to
|
|
// reference the last SEARCH result. On the wire, it's encoded as '$'.
|
|
//
|
|
// It requires IMAP4rev2 or the SEARCHRES extension.
|
|
func SearchRes() UIDSet {
|
|
return searchRes
|
|
}
|
|
|
|
// IsSearchRes checks whether a sequence set is a reference to the last SEARCH
|
|
// result. See SearchRes.
|
|
func IsSearchRes(numSet NumSet) bool {
|
|
return reflect.ValueOf(numSet).Pointer() == searchResAddr
|
|
}
|