Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions imap.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ const (

// Items
FetchBody FetchItem = "BODY"
FetchBodyPeek FetchItem = "BODY.PEEK[]"
FetchXGmMsgID FetchItem = "X-GM-MSGID"
FetchBodyStructure FetchItem = "BODYSTRUCTURE"
FetchEnvelope FetchItem = "ENVELOPE"
FetchFlags FetchItem = "FLAGS"
Expand Down
5 changes: 5 additions & 0 deletions message.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,9 @@ type Message struct {
// because some bad IMAP clients (looking at you, Outlook!) refuse responses
// containing items in a different order.
itemsOrder []FetchItem

// Google specific unique identifier
XGmMsgID uint64
}

// Create a new empty message that will contain the specified items.
Expand Down Expand Up @@ -259,6 +262,8 @@ func (m *Message) Parse(fields []interface{}) error {
m.Size, _ = ParseNumber(f)
case FetchUid:
m.Uid, _ = ParseNumber(f)
case FetchXGmMsgID:
m.XGmMsgID, _ = ParseInt64(f)
default:
// Likely to be a section of the body
// First check that the section name is correct
Expand Down
25 changes: 25 additions & 0 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,31 @@ func ParseNumber(f interface{}) (uint32, error) {
return uint32(nbr), nil
}

// ParseInt64 parses a number.
func ParseInt64(f interface{}) (uint64, error) {
// Useful for tests
if n, ok := f.(uint64); ok {
return n, nil
}

var s string
switch f := f.(type) {
case RawString:
s = string(f)
case string:
s = f
default:
return 0, newParseError("expected a number, got a non-atom")
}

nbr, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, &parseError{err}
}

return nbr, nil
}

// ParseString parses a string, which is either a literal, a quoted string or an
// atom.
func ParseString(f interface{}) (string, error) {
Expand Down
35 changes: 33 additions & 2 deletions search.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ func popSearchField(fields []interface{}) (interface{}, []interface{}, error) {
return fields[0], fields[1:], nil
}

func parseRawField(fields []interface{}) (interface{}, []interface{}) {
if len(fields) == 0 {
return nil, fields
}

if _, ok := fields[0].([]interface{}); ok {
return nil, fields
}

return fields[0], fields[1:]
}

type Raw struct {
Key string
Value interface{}
}

// SearchCriteria is a search criteria. A message matches the criteria if and
// only if it matches each one of its fields.
type SearchCriteria struct {
Expand All @@ -80,6 +97,8 @@ type SearchCriteria struct {

Not []*SearchCriteria // Each criteria doesn't match
Or [][2]*SearchCriteria // Each criteria pair has at least one match of two

Raw []Raw
}

// NewSearchCriteria creates a new search criteria.
Expand Down Expand Up @@ -250,8 +269,12 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io.
c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f)))
}
default: // Try to parse a sequence set
if c.SeqNum, err = ParseSeqSet(key); err != nil {
return nil, err
if seqNum, err := ParseSeqSet(key); err == nil {
c.SeqNum = seqNum
} else {
var rawValue interface{}
rawValue, fields = parseRawField(fields)
c.Raw = append(c.Raw, Raw{Key: key, Value: rawValue})
}
}

Expand Down Expand Up @@ -362,6 +385,14 @@ func (c *SearchCriteria) Format() []interface{} {
fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format())
}

for _, raw := range c.Raw {
field := []interface{}{RawString(raw.Key)}
if raw.Value != nil {
field = append(field, raw.Value)
}
fields = append(fields, field)
}

// Not a single criteria given, add ALL criteria as fallback
if len(fields) == 0 {
fields = append(fields, RawString("ALL"))
Expand Down
22 changes: 22 additions & 0 deletions search_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package imap

import (
"bytes"
"fmt"
"io"
"net/textproto"
"reflect"
Expand Down Expand Up @@ -65,6 +66,26 @@ var searchCriteriaTests = []struct {
}},
},
},
{
expected: `((X-GM-THRID) (X-GM-RAW "has:attachment") (X-GM-MSGID))`,
criteria: &SearchCriteria{
Raw: []Raw{
{"X-GM-THRID", nil},
{"X-GM-RAW", "has:attachment"},
{"X-GM-MSGID", nil},
},
},
},
{
expected: `((X-GM-THRID) (X-GM-MSGID) (X-GM-RAW "has:attachment"))`,
criteria: &SearchCriteria{
Raw: []Raw{
{"X-GM-THRID", nil},
{"X-GM-MSGID", nil},
{"X-GM-RAW", "has:attachment"},
},
},
},
{
expected: "(ALL)",
criteria: &SearchCriteria{},
Expand Down Expand Up @@ -93,6 +114,7 @@ func TestSearchCriteria_Parse(t *testing.T) {
b := bytes.NewBuffer([]byte(test.expected))
r := NewReader(b)
fields, _ := r.ReadFields()
fmt.Println(fields)

if err := criteria.ParseWithCharset(fields[0].([]interface{}), nil); err != nil {
t.Errorf("Cannot parse search criteria for #%v: %v", i+1, err)
Expand Down