From 9976f8218819355a42d22a9d23c0a6f383c0fb47 Mon Sep 17 00:00:00 2001 From: Andy Horner Date: Sat, 18 Jan 2020 13:59:28 -0700 Subject: [PATCH 1/5] Added support for X-GM-RAW search fields --- search.go | 11 +++++++++++ search_test.go | 4 +++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/search.go b/search.go index 0ecb24d2..b655783d 100644 --- a/search.go +++ b/search.go @@ -71,6 +71,7 @@ type SearchCriteria struct { Header textproto.MIMEHeader // Each header field value is present Body []string // Each string is in the body Text []string // Each string is in the text (header + body) + XGMRaw []string // Gmail X-GM-RAW fields to be added to the query WithFlags []string // Each flag is present WithoutFlags []string // Each flag is not present @@ -249,6 +250,12 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io. } else { c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f))) } + case "X-GM-RAW": + if f, fields, err = popSearchField(fields); err != nil { + return nil, err + } else { + c.XGMRaw = append(c.XGMRaw, convertField(f, charsetReader)) + } default: // Try to parse a sequence set if c.SeqNum, err = ParseSeqSet(key); err != nil { return nil, err @@ -362,6 +369,10 @@ func (c *SearchCriteria) Format() []interface{} { fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format()) } + for _, gmRaw := range c.XGMRaw { + fields = append(fields, RawString("X-GM-RAW"), gmRaw) + } + // Not a single criteria given, add ALL criteria as fallback if len(fields) == 0 { fields = append(fields, RawString("ALL")) diff --git a/search_test.go b/search_test.go index 81c3e5e0..4824ff28 100644 --- a/search_test.go +++ b/search_test.go @@ -28,7 +28,8 @@ var searchCriteriaTests = []struct { `ANSWERED DELETED KEYWORD cc UNKEYWORD microsoft ` + `LARGER 4242 SMALLER 4342 ` + `NOT (SENTON "21-Nov-1997" HEADER "Content-Type" "text/csv") ` + - `OR (ON "5-Nov-1984" DRAFT FLAGGED UNANSWERED UNDELETED OLD) (UNDRAFT UNFLAGGED UNSEEN))`, + `OR (ON "5-Nov-1984" DRAFT FLAGGED UNANSWERED UNDELETED OLD) (UNDRAFT UNFLAGGED UNSEEN) ` + + `X-GM-RAW "has:attachment")`, criteria: &SearchCriteria{ SeqNum: searchSeqSet1, Uid: searchSeqSet2, @@ -63,6 +64,7 @@ var searchCriteriaTests = []struct { WithoutFlags: []string{DraftFlag, FlaggedFlag, SeenFlag}, }, }}, + XGMRaw: []string{"has:attachment"}, }, }, { From 83331cc5ed3cb4d544c7056dc5affcbbab426d5b Mon Sep 17 00:00:00 2001 From: Andy Horner Date: Sat, 18 Jan 2020 14:16:43 -0700 Subject: [PATCH 2/5] Added better test case --- search_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/search_test.go b/search_test.go index 4824ff28..fb62a4bc 100644 --- a/search_test.go +++ b/search_test.go @@ -28,8 +28,7 @@ var searchCriteriaTests = []struct { `ANSWERED DELETED KEYWORD cc UNKEYWORD microsoft ` + `LARGER 4242 SMALLER 4342 ` + `NOT (SENTON "21-Nov-1997" HEADER "Content-Type" "text/csv") ` + - `OR (ON "5-Nov-1984" DRAFT FLAGGED UNANSWERED UNDELETED OLD) (UNDRAFT UNFLAGGED UNSEEN) ` + - `X-GM-RAW "has:attachment")`, + `OR (ON "5-Nov-1984" DRAFT FLAGGED UNANSWERED UNDELETED OLD) (UNDRAFT UNFLAGGED UNSEEN))`, criteria: &SearchCriteria{ SeqNum: searchSeqSet1, Uid: searchSeqSet2, @@ -64,6 +63,11 @@ var searchCriteriaTests = []struct { WithoutFlags: []string{DraftFlag, FlaggedFlag, SeenFlag}, }, }}, + }, + }, + { + expected: `(X-GM-RAW "has:attachment")`, + criteria: &SearchCriteria{ XGMRaw: []string{"has:attachment"}, }, }, From 007ccc825dafb1e3b7c014d19ea05d44ab3fe4ab Mon Sep 17 00:00:00 2001 From: Andy Horner Date: Sat, 18 Jan 2020 16:55:32 -0700 Subject: [PATCH 3/5] Generalized raw search functionality --- search.go | 49 ++++++++++++++++++++++++++++++++++++++----------- search_test.go | 8 ++++++-- 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/search.go b/search.go index b655783d..33b29258 100644 --- a/search.go +++ b/search.go @@ -56,6 +56,25 @@ func popSearchField(fields []interface{}) (interface{}, []interface{}, error) { return fields[0], fields[1:], nil } +func parseRawField(fields []interface{}) ([]interface{}, []interface{}) { + // Fields is empty. This means this isn't a raw field value but rather another raw key + if len(fields) == 0 { + return nil, fields + } + + if value, ok := fields[0].([]interface{}); ok { + return value, fields[1:] + } else { + // First fields element isn't an array. This means this isn't a raw field value but rather another raw key + return nil, fields + } +} + +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 { @@ -71,7 +90,6 @@ type SearchCriteria struct { Header textproto.MIMEHeader // Each header field value is present Body []string // Each string is in the body Text []string // Each string is in the text (header + body) - XGMRaw []string // Gmail X-GM-RAW fields to be added to the query WithFlags []string // Each flag is present WithoutFlags []string // Each flag is not present @@ -81,6 +99,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 // Raw keys and values to be added to the search } // NewSearchCriteria creates a new search criteria. @@ -250,15 +270,18 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io. } else { c.WithoutFlags = append(c.WithoutFlags, CanonicalFlag(maybeString(f))) } - case "X-GM-RAW": - if f, fields, err = popSearchField(fields); err != nil { - return nil, err + default: // Try to parse a sequence set, otherwise assume we have raw fields + if seqNum, err := ParseSeqSet(key); err == nil { + c.SeqNum = seqNum } else { - c.XGMRaw = append(c.XGMRaw, convertField(f, charsetReader)) - } - default: // Try to parse a sequence set - if c.SeqNum, err = ParseSeqSet(key); err != nil { - return nil, err + // If ParseSeqSet fails, assume we have raw fields + //if c.Raw == nil { + // c.Raw = make([]Raw, 0) + //} + var rawValue []interface{} + rawValue, fields = parseRawField(fields) + c.Raw = append(c.Raw, Raw{Key: key, Value: rawValue}) + //c.Raw[key], fields = parseRawField(fields) } } @@ -369,8 +392,12 @@ func (c *SearchCriteria) Format() []interface{} { fields = append(fields, RawString("OR"), or[0].Format(), or[1].Format()) } - for _, gmRaw := range c.XGMRaw { - fields = append(fields, RawString("X-GM-RAW"), gmRaw) + for _, raw := range c.Raw { + if raw.Value == nil { + fields = append(fields, RawString(raw.Key)) + } else { + fields = append(fields, RawString(raw.Key), raw.Value) + } } // Not a single criteria given, add ALL criteria as fallback diff --git a/search_test.go b/search_test.go index fb62a4bc..ffee81e7 100644 --- a/search_test.go +++ b/search_test.go @@ -66,9 +66,13 @@ var searchCriteriaTests = []struct { }, }, { - expected: `(X-GM-RAW "has:attachment")`, + expected: `(X-GM-THRID X-GM-RAW ("has:attachment") X-GM-MSGID)`, criteria: &SearchCriteria{ - XGMRaw: []string{"has:attachment"}, + Raw: []Raw{ + {"X-GM-THRID", nil}, + {"X-GM-RAW", []interface{}{"has:attachment"}}, + {"X-GM-MSGID", nil}, + }, }, }, { From 3d0e1dad2b0b9f3ef38b63ea12d7f0447c72b24c Mon Sep 17 00:00:00 2001 From: Andy Horner Date: Sat, 18 Jan 2020 16:58:05 -0700 Subject: [PATCH 4/5] Cleanup comments --- search.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/search.go b/search.go index 33b29258..04efbd77 100644 --- a/search.go +++ b/search.go @@ -275,13 +275,9 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io. c.SeqNum = seqNum } else { // If ParseSeqSet fails, assume we have raw fields - //if c.Raw == nil { - // c.Raw = make([]Raw, 0) - //} var rawValue []interface{} rawValue, fields = parseRawField(fields) c.Raw = append(c.Raw, Raw{Key: key, Value: rawValue}) - //c.Raw[key], fields = parseRawField(fields) } } From ff937cdb3597481720bacd673d1e891eb7570a53 Mon Sep 17 00:00:00 2001 From: Andy Horner Date: Sat, 18 Jan 2020 18:53:00 -0700 Subject: [PATCH 5/5] Improved the way we handle raw search fields --- search.go | 22 +++++++++++----------- search_test.go | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/search.go b/search.go index 04efbd77..9dc5e870 100644 --- a/search.go +++ b/search.go @@ -56,23 +56,23 @@ func popSearchField(fields []interface{}) (interface{}, []interface{}, error) { return fields[0], fields[1:], nil } -func parseRawField(fields []interface{}) ([]interface{}, []interface{}) { +func parseRawField(fields []interface{}) (interface{}, []interface{}) { // Fields is empty. This means this isn't a raw field value but rather another raw key if len(fields) == 0 { return nil, fields } - if value, ok := fields[0].([]interface{}); ok { - return value, fields[1:] - } else { - // First fields element isn't an array. This means this isn't a raw field value but rather another raw key + // First element in fields is an array of interfaces, this means this field doesn't have a value and is nil + if _, ok := fields[0].([]interface{}); ok { return nil, fields } + + return fields[0], fields[1:] } type Raw struct { Key string - Value []interface{} + Value interface{} } // SearchCriteria is a search criteria. A message matches the criteria if and @@ -275,7 +275,7 @@ func (c *SearchCriteria) parseField(fields []interface{}, charsetReader func(io. c.SeqNum = seqNum } else { // If ParseSeqSet fails, assume we have raw fields - var rawValue []interface{} + var rawValue interface{} rawValue, fields = parseRawField(fields) c.Raw = append(c.Raw, Raw{Key: key, Value: rawValue}) } @@ -389,11 +389,11 @@ func (c *SearchCriteria) Format() []interface{} { } for _, raw := range c.Raw { - if raw.Value == nil { - fields = append(fields, RawString(raw.Key)) - } else { - fields = append(fields, RawString(raw.Key), raw.Value) + 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 diff --git a/search_test.go b/search_test.go index ffee81e7..206b8910 100644 --- a/search_test.go +++ b/search_test.go @@ -66,15 +66,25 @@ var searchCriteriaTests = []struct { }, }, { - expected: `(X-GM-THRID X-GM-RAW ("has:attachment") X-GM-MSGID)`, + expected: `((X-GM-THRID) (X-GM-RAW "has:attachment") (X-GM-MSGID))`, criteria: &SearchCriteria{ Raw: []Raw{ {"X-GM-THRID", nil}, - {"X-GM-RAW", []interface{}{"has:attachment"}}, + {"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{},