Implement new SearchCriteria from latest go-imap

This commit is contained in:
Michal Horejsek 2020-04-22 14:14:24 +02:00
parent cabcb3ae2b
commit 313e803fdd
4 changed files with 158 additions and 125 deletions

1
go.sum
View File

@ -60,6 +60,7 @@ github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a h1:bM
github.com/emersion/go-imap-appendlimit v0.0.0-20190308131241-25671c986a6a/go.mod h1:ikgISoP7pRAolqsVP64yMteJa2FIpS6ju88eBT6K1yQ=
github.com/emersion/go-imap-idle v0.0.0-20190519112320-2704abd7050e h1:L7bswVJZcf2YHofgom49oFRwVqmBj/qZqDy9/SJpZMY=
github.com/emersion/go-imap-idle v0.0.0-20190519112320-2704abd7050e/go.mod h1:o14zPKCmEH5WC1vU5SdPoZGgNvQx7zzKSnxPQlobo78=
github.com/emersion/go-imap-move v0.0.0-20190710073258-6e5a51a5b342/go.mod h1:QuMaZcKFDVI0yCrnAbPLfbwllz1wtOrZH8/vZ5yzp4w=
github.com/emersion/go-imap-quota v0.0.0-20171113212021-e883a2bc54d6 h1:CQ9z5Gk5HBUI8+kM5XNZXUoAv8RF1GnXmYrN4OkDvZU=
github.com/emersion/go-imap-quota v0.0.0-20171113212021-e883a2bc54d6/go.mod h1:iApyhIQBiU4XFyr+3kdJyyGqle82TbQyuP2o+OZHrV0=
github.com/emersion/go-imap-quota v0.0.0-20200423100218-dcfd1b7d2b41 h1:z5lDGnSURauBEDdNLj3o0+HogVYKQCGeY3Anl/xyRfU=

View File

@ -170,20 +170,15 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
return nil, err
}
var apiIDsFromUID []string
if criteria.Uid != nil {
if apiIDs, err := im.apiIDsFromSeqSet(true, criteria.Uid); err == nil {
apiIDsFromUID = append(apiIDsFromUID, apiIDs...)
apiIDsByUID, err := im.apiIDsFromSeqSet(true, criteria.Uid)
if err != nil {
return nil, err
}
apiIDs = arrayIntersection(apiIDs, apiIDsByUID)
}
// Apply filters.
for _, apiID := range apiIDs {
// Filter on UIDs.
if len(apiIDsFromUID) > 0 && !isStringInList(apiIDsFromUID, apiID) {
continue
}
// Get message.
storeMessage, err := im.storeMailbox.GetMessage(apiID)
if err != nil {
@ -192,79 +187,7 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
}
m := storeMessage.Message()
// Filter addresses.
/*if criteria.From != "" && !addressMatch([]*mail.Address{m.Sender}, criteria.From) {
continue
}
if criteria.To != "" && !addressMatch(m.ToList, criteria.To) {
continue
}
if criteria.Cc != "" && !addressMatch(m.CCList, criteria.Cc) {
continue
}
if criteria.Bcc != "" && !addressMatch(m.BCCList, criteria.Bcc) {
continue
}*/
// Filter strings.
/*if criteria.Subject != "" && !strings.Contains(strings.ToLower(m.Subject), strings.ToLower(criteria.Subject)) {
continue
}
if criteria.Keyword != "" && !hasKeyword(m, criteria.Keyword) {
continue
}
if criteria.Unkeyword != "" && hasKeyword(m, criteria.Unkeyword) {
continue
}
if criteria.Header[0] != "" {
h := message.GetHeader(m)
if val := h.Get(criteria.Header[0]); val == "" {
continue // Field is not in header.
} else if criteria.Header[1] != "" && !strings.Contains(strings.ToLower(val), strings.ToLower(criteria.Header[1])) {
continue // Field is in header, second criteria is non-zero and field value not matched (case insensitive).
}
}
// Filter flags.
if criteria.Flagged && !isStringInList(m.LabelIDs, pmapi.StarredLabel) {
continue
}
if criteria.Unflagged && isStringInList(m.LabelIDs, pmapi.StarredLabel) {
continue
}
if criteria.Seen && m.Unread == 1 {
continue
}
if criteria.Unseen && m.Unread == 0 {
continue
}
if criteria.Deleted {
continue
}
// if criteria.Undeleted { // All messages matches this criteria }
if criteria.Draft && (m.Has(pmapi.FlagSent) || m.Has(pmapi.FlagReceived)) {
continue
}
if criteria.Undraft && !(m.Has(pmapi.FlagSent) || m.Has(pmapi.FlagReceived)) {
continue
}
if criteria.Answered && !(m.Has(pmapi.FlagReplied) || m.Has(pmapi.FlagRepliedAll)) {
continue
}
if criteria.Unanswered && (m.Has(pmapi.FlagReplied) || m.Has(pmapi.FlagRepliedAll)) {
continue
}
if criteria.Recent && m.Has(pmapi.FlagOpened) { // opened means not recent
continue
}
if criteria.Old && !m.Has(pmapi.FlagOpened) {
continue
}
if criteria.New && !(!m.Has(pmapi.FlagOpened) && m.Unread == 1) {
continue
}*/
// Filter internal date.
// Filter by time.
if !criteria.Before.IsZero() {
if truncated := criteria.Before.Truncate(24 * time.Hour); m.Time > truncated.Unix() {
continue
@ -275,15 +198,8 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
continue
}
}
/*if !criteria.On.IsZero() {
truncated := criteria.On.Truncate(24 * time.Hour)
if m.Time < truncated.Unix() || m.Time > truncated.Add(24*time.Hour).Unix() {
continue
}
}*/
if !(criteria.SentBefore.IsZero() && criteria.SentSince.IsZero() /*&& criteria.SentOn.IsZero()*/) {
if !criteria.SentBefore.IsZero() || !criteria.SentSince.IsZero() {
if t, err := m.Header.Date(); err == nil && !t.IsZero() {
// Filter header date.
if !criteria.SentBefore.IsZero() {
if truncated := criteria.SentBefore.Truncate(24 * time.Hour); t.Unix() > truncated.Unix() {
continue
@ -294,16 +210,81 @@ func (im *imapMailbox) SearchMessages(isUID bool, criteria *imap.SearchCriteria)
continue
}
}
/*if !criteria.SentOn.IsZero() {
truncated := criteria.SentOn.Truncate(24 * time.Hour)
if t.Unix() < truncated.Unix() || t.Unix() > truncated.Add(24*time.Hour).Unix() {
continue
}
}*/
}
}
// Filter size (only if size was already calculated).
// Filter by headers.
header := message.GetHeader(m)
headerMatch := true
for criteriaKey, criteriaValues := range criteria.Header {
for _, criteriaValue := range criteriaValues {
if criteriaValue == "" {
continue
}
switch criteriaKey {
case "From":
headerMatch = addressMatch([]*mail.Address{m.Sender}, criteriaValue)
case "To":
headerMatch = addressMatch(m.ToList, criteriaValue)
case "Cc":
headerMatch = addressMatch(m.CCList, criteriaValue)
case "Bcc":
headerMatch = addressMatch(m.BCCList, criteriaValue)
default:
if messageValue := header.Get(criteriaKey); messageValue == "" {
headerMatch = false // Field is not in header.
} else if !strings.Contains(strings.ToLower(messageValue), strings.ToLower(criteriaValue)) {
headerMatch = false // Field is in header but value not matched (case insensitive).
}
}
if !headerMatch {
break
}
}
if !headerMatch {
break
}
}
if !headerMatch {
continue
}
// Filter by flags.
messageFlagsMap := make(map[string]bool)
if isStringInList(m.LabelIDs, pmapi.StarredLabel) {
messageFlagsMap[imap.FlaggedFlag] = true
}
if m.Unread == 0 {
messageFlagsMap[imap.SeenFlag] = true
}
if m.Has(pmapi.FlagReplied) || m.Has(pmapi.FlagRepliedAll) {
messageFlagsMap[imap.AnsweredFlag] = true
}
if m.Has(pmapi.FlagSent) || m.Has(pmapi.FlagReceived) {
messageFlagsMap[imap.DraftFlag] = true
}
if !m.Has(pmapi.FlagOpened) {
messageFlagsMap[imap.RecentFlag] = true
}
flagMatch := true
for _, flag := range criteria.WithFlags {
if !messageFlagsMap[flag] {
flagMatch = false
break
}
}
for _, flag := range criteria.WithoutFlags {
if messageFlagsMap[flag] {
flagMatch = false
break
}
}
if !flagMatch {
continue
}
// Filter by size (only if size was already calculated).
if m.Size > 0 {
if criteria.Larger != 0 && m.Size <= int64(criteria.Larger) {
continue
@ -452,13 +433,17 @@ func (im *imapMailbox) apiIDsFromSeqSet(uid bool, seqSet *imap.SeqSet) ([]string
return apiIDs, nil
}
func isAddressInList(addrs []*mail.Address, query string) bool { //nolint[deadcode]
for _, addr := range addrs {
if strings.Contains(addr.Address, query) || strings.Contains(addr.Name, query) {
return true
func arrayIntersection(a, b []string) (c []string) {
m := make(map[string]bool)
for _, item := range a {
m[item] = true
}
for _, item := range b {
if _, ok := m[item]; ok {
c = append(c, item)
}
}
return false
return
}
func isStringInList(list []string, s string) bool {
@ -478,12 +463,3 @@ func addressMatch(addresses []*mail.Address, criteria string) bool {
}
return false
}
func hasKeyword(m *pmapi.Message, keyword string) bool {
for _, v := range message.GetHeader(m) {
if strings.Contains(strings.ToLower(strings.Join(v, " ")), strings.ToLower(keyword)) {
return true
}
}
return false
}

View File

@ -2,28 +2,79 @@ Feature: IMAP search messages
Background:
Given there is connected user "user"
Given there are messages in mailbox "INBOX" for "user"
| from | to | subject | body |
| john.doe@mail.com | user@pm.me | foo | hello |
| jane.doe@mail.com | name@pm.me | bar | world |
| from | to | cc | subject | read | starred | body |
| john.doe@email.com | user@pm.me | | foo | false | false | hello |
| jane.doe@email.com | user@pm.me | name@pm.me | bar | true | true | world |
| jane.doe@email.com | name@pm.me | | baz | true | false | bye |
And there is IMAP client logged in as "user"
And there is IMAP client selected in "INBOX"
Scenario: Search by subject
Scenario: Search by Sequence numbers
When IMAP client searches for "1"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search by UID
When IMAP client searches for "UID 2"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search by Sequence numbers and UID
When IMAP client searches for "1 UID 1"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"
Scenario: Search by Sequence numbers and UID without match
When IMAP client searches for "1 UID 2"
Then IMAP response is "OK"
And IMAP response contains "SEARCH[^0-9]*$"
Scenario: Search by Subject
When IMAP client searches for "SUBJECT foo"
Then IMAP response is "OK"
And IMAP response has 1 message
And IMAP response contains "SEARCH 3[^0-9]*$"
Scenario: Search by text
When IMAP client searches for "TEXT world"
Then IMAP response is "OK"
And IMAP response has 1 message
Scenario: Search by from
Scenario: Search by From
When IMAP client searches for "FROM jane.doe@email.com"
Then IMAP response is "OK"
And IMAP response has 1 message
And IMAP response contains "SEARCH 1 2[^0-9]*$"
Scenario: Search by to
Scenario: Search by To
When IMAP client searches for "TO user@pm.me"
Then IMAP response is "OK"
And IMAP response has 1 message
And IMAP response contains "SEARCH 2 3[^0-9]*$"
Scenario: Search by CC
When IMAP client searches for "CC name@pm.me"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search flagged messages
When IMAP client searches for "FLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 2[^0-9]*$"
Scenario: Search not flagged messages
When IMAP client searches for "UNFLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 3[^0-9]*$"
Scenario: Search seen messages
When IMAP client searches for "SEEN"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 2[^0-9]*$"
Scenario: Search unseen messages
When IMAP client searches for "UNSEEN"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 3[^0-9]*$"
Scenario: Search recent messages
When IMAP client searches for "RECENT"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1 2 3[^0-9]*$"
Scenario: Search by more criterias
When IMAP client searches for "SUBJECT baz TO name@pm.me SEEN UNFLAGGED"
Then IMAP response is "OK"
And IMAP response contains "SEARCH 1[^0-9]*$"

View File

@ -97,6 +97,11 @@ func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bdd
message.ToList = []*mail.Address{{
Address: ctx.EnsureAddress(account.Username(), cell.Value),
}}
case "cc":
message.AddressID = ctx.EnsureAddressID(account.Username(), cell.Value)
message.CCList = []*mail.Address{{
Address: ctx.EnsureAddress(account.Username(), cell.Value),
}}
case "subject":
message.Subject = cell.Value
case "body":