Files
go-imap/search.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
}